diff --git a/.bun-version b/.bun-version
new file mode 100644
index 0000000000..88c5fb891d
--- /dev/null
+++ b/.bun-version
@@ -0,0 +1 @@
+1.4.0
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6742d53aa1..21d8c43a7f 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,4 +1,4 @@
-# Dependency update PRs wait out the same 7-day cooldown that .npmrc's
+# Dependency update PRs wait out the same 7-day cooldown that bunfig.toml's
# min-release-age enforces locally. Security-advisory PRs are exempt
# from cooldown by design, so CVE fixes still arrive promptly.
version: 2
diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml
index c140daa67f..8185ec79f4 100644
--- a/.github/workflows/build-binaries.yml
+++ b/.github/workflows/build-binaries.yml
@@ -16,7 +16,6 @@ on:
concurrency:
group: release-prime-agent
cancel-in-progress: false
- queue: max
permissions:
contents: read
@@ -40,6 +39,14 @@ jobs:
fetch-tags: true
persist-credentials: false
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
+ with:
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
- name: Resolve release context
id: context
env:
@@ -72,7 +79,7 @@ jobs:
build_ref="$REF_NAME"
publish_production=true
else
- production_version=$(node -p "require('./package.json').version")
+ production_version=$(bun -e "console.log(require('./package.json').version)")
build_ref="$GITHUB_SHA_VALUE"
beta_version="${production_version}-beta.${RUN_NUMBER}.${RUN_ATTEMPT}.${GITHUB_SHA_VALUE::7}"
publish_beta=true
@@ -80,7 +87,7 @@ jobs:
previous_version=
if [ -n "$BEFORE_SHA" ] && ! printf '%s\n' "$BEFORE_SHA" | grep -Eq '^0+$' && git cat-file -e "${BEFORE_SHA}:package.json"; then
git show "${BEFORE_SHA}:package.json" > /tmp/previous-package.json
- previous_version=$(node -p "require('/tmp/previous-package.json').version")
+ previous_version=$(bun -e "console.log(require('/tmp/previous-package.json').version)")
fi
if [ -z "$previous_version" ] || [ "$production_version" != "$previous_version" ]; then
@@ -133,20 +140,41 @@ jobs:
ref: ${{ env.BUILD_REF }}
persist-credentials: false
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
with:
- node-version: '22'
- registry-url: 'https://registry.npmjs.org'
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
+ - 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
- - name: Install dependencies
- run: npm ci
+ - name: Install dependencies for all release platforms
+ run: bun install --frozen-lockfile --os=* --cpu=*
- name: Build
- run: npm run build
+ run: bun run build
- name: Check
- run: npm run check
+ run: bun run check
+
+ - name: Compile binaries for all platforms
+ working-directory: packages/coding-agent
+ run: |
+ mkdir -p binaries
+ for platform in darwin-arm64 darwin-x64 linux-arm64 linux-x64 windows-x64 windows-arm64; do
+ echo "Compiling for $platform..."
+ mkdir -p "binaries/$platform"
+ bun build --compile --minify --keep-names --bytecode --format=esm --external koffi --target="bun-$platform" ./dist/bun/cli.js --outfile "binaries/$platform/pi"
+ done
+
+ - name: Copy binary sidecar assets
+ working-directory: packages/coding-agent
+ run: bun run copy-binary-assets
- name: Pack production release
if: env.PUBLISH_PRODUCTION == 'true'
@@ -154,7 +182,7 @@ jobs:
PRIME_AGENT_DOWNLOAD_BASE_URL: ${{ vars.R2_PUBLIC_BASE_URL }}
run: |
test -n "$PRIME_AGENT_DOWNLOAD_BASE_URL"
- npm run release:pack -- \
+ bun run release:pack -- \
--channel stable \
--version "$PRODUCTION_VERSION" \
--base-url "$PRIME_AGENT_DOWNLOAD_BASE_URL" \
@@ -166,12 +194,65 @@ jobs:
PRIME_AGENT_DOWNLOAD_BASE_URL: ${{ vars.R2_PUBLIC_BASE_URL }}
run: |
test -n "$PRIME_AGENT_DOWNLOAD_BASE_URL"
- npm run release:pack -- \
+ bun run release:pack -- \
--channel beta \
--version "$BETA_VERSION" \
--base-url "$PRIME_AGENT_DOWNLOAD_BASE_URL" \
--out-dir packages/coding-agent/release/beta
+ - name: Smoke test host-platform archive
+ if: env.PUBLISH_PRODUCTION == 'true' || env.PUBLISH_BETA == 'true'
+ run: |
+ # Determine which archive to test (prefer production, fall back to beta)
+ ARTIFACTS_DIR=""
+ if [ "$PUBLISH_PRODUCTION" = "true" ]; then
+ ARTIFACTS_DIR="packages/coding-agent/release/production/artifacts"
+ else
+ ARTIFACTS_DIR="packages/coding-agent/release/beta/artifacts"
+ fi
+
+ # Detect host platform
+ OS=$(uname -s)
+ ARCH=$(uname -m)
+ case "$OS" in
+ Linux)
+ case "$ARCH" in
+ x86_64) PLATFORM="linux-x64" ;;
+ aarch64|arm64) PLATFORM="linux-arm64" ;;
+ *) echo "Unknown host arch: $ARCH"; exit 1 ;;
+ esac
+ ;;
+ Darwin)
+ case "$ARCH" in
+ x86_64) PLATFORM="darwin-x64" ;;
+ arm64) PLATFORM="darwin-arm64" ;;
+ *) echo "Unknown host arch: $ARCH"; exit 1 ;;
+ esac
+ ;;
+ *)
+ echo "Unknown host OS: $OS"; exit 1 ;;
+ esac
+
+ # Extract host-platform archive and run --version
+ ARCHIVE=$(ls "$ARTIFACTS_DIR"/*-"${PLATFORM}.tar.gz" 2>/dev/null | head -1)
+ if [ -z "$ARCHIVE" ]; then
+ echo "No archive for host platform ${PLATFORM} in ${ARTIFACTS_DIR}"
+ exit 1
+ fi
+
+ SMOKE_DIR=$(mktemp -d)
+ tar -xzf "$ARCHIVE" -C "$SMOKE_DIR"
+ chmod +x "$SMOKE_DIR/prime-agent"
+ if "$SMOKE_DIR/prime-agent" --version >/dev/null 2>&1; then
+ echo "Smoke test passed: $("$SMOKE_DIR/prime-agent" --version 2>&1)"
+ else
+ echo "Smoke test FAILED."
+ "$SMOKE_DIR/prime-agent" --version 2>&1 || true
+ rm -rf "$SMOKE_DIR"
+ exit 1
+ fi
+ rm -rf "$SMOKE_DIR"
+
- name: Upload production artifacts
if: env.PUBLISH_PRODUCTION == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
@@ -212,6 +293,14 @@ jobs:
ref: ${{ env.BUILD_REF }}
persist-credentials: false
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
+ with:
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
- name: Download production artifacts
if: env.PUBLISH_PRODUCTION == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
@@ -232,17 +321,20 @@ jobs:
export INSTALL_BASE_URL
test -n "$INSTALL_BASE_URL"
- node - <<'NODE'
+ bun -e '
const fs = require("node:fs");
const baseUrl = process.env.INSTALL_BASE_URL;
if (!baseUrl) throw new Error("INSTALL_BASE_URL is required");
- const installer = fs.readFileSync("install.sh", "utf8");
- const renderInstaller = (channel) => installer
+ const renderInstaller = (source, channel) => source
.replaceAll("__PRIME_AGENT_DOWNLOAD_BASE_URL__", baseUrl)
.replaceAll("__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__", channel);
- fs.writeFileSync("/tmp/prime-agent-install.sh", renderInstaller("stable"));
- fs.writeFileSync("/tmp/prime-agent-install-beta.sh", renderInstaller("beta"));
- NODE
+ const shellInstaller = fs.readFileSync("install.sh", "utf8");
+ const powershellInstaller = fs.readFileSync("install.ps1", "utf8");
+ fs.writeFileSync("/tmp/prime-agent-install.sh", renderInstaller(shellInstaller, "stable"));
+ fs.writeFileSync("/tmp/prime-agent-install-beta.sh", renderInstaller(shellInstaller, "beta"));
+ fs.writeFileSync("/tmp/prime-agent-install.ps1", renderInstaller(powershellInstaller, "stable"));
+ fs.writeFileSync("/tmp/prime-agent-install-beta.ps1", renderInstaller(powershellInstaller, "beta"));
+ '
- name: Extract production release notes
if: env.PUBLISH_PRODUCTION == 'true'
@@ -257,22 +349,24 @@ jobs:
run: |
PRODUCTION_DIR=release-artifacts/production
RELEASE_PREFIX="releases/v${PRODUCTION_VERSION}"
- TARBALL="$PRODUCTION_DIR/prime-agent-${PRODUCTION_VERSION}.tgz"
- test -f "$TARBALL"
test -f "$PRODUCTION_DIR/SHA256SUMS"
test -f "$PRODUCTION_DIR/stable"
test -f "$PRODUCTION_DIR/latest.json"
test -n "$R2_BUCKET"
test -n "$R2_ENDPOINT_URL"
- for artifact in "$PRODUCTION_DIR"/*.tgz; do
+ for artifact in "$PRODUCTION_DIR"/*.tar.gz "$PRODUCTION_DIR"/*.zip; do
+ test -f "$artifact" || continue
+ content_type="application/gzip"
+ case "$artifact" in
+ *.zip) content_type="application/zip" ;;
+ esac
aws s3 cp "$artifact" "s3://${R2_BUCKET}/${RELEASE_PREFIX}/$(basename "$artifact")" \
--endpoint-url "$R2_ENDPOINT_URL" \
- --content-type application/gzip \
+ --content-type "$content_type" \
--cache-control 'public, max-age=31536000, immutable'
done
-
aws s3 cp "$PRODUCTION_DIR/SHA256SUMS" "s3://${R2_BUCKET}/${RELEASE_PREFIX}/SHA256SUMS" \
--endpoint-url "$R2_ENDPOINT_URL" \
--content-type text/plain \
@@ -298,6 +392,16 @@ jobs:
--content-type text/x-shellscript \
--cache-control no-cache
+ aws s3 cp /tmp/prime-agent-install.ps1 "s3://${R2_BUCKET}/install.ps1" \
+ --endpoint-url "$R2_ENDPOINT_URL" \
+ --content-type text/x-powershell \
+ --cache-control no-cache
+
+ aws s3 cp /tmp/prime-agent-install-beta.ps1 "s3://${R2_BUCKET}/install-beta.ps1" \
+ --endpoint-url "$R2_ENDPOINT_URL" \
+ --content-type text/x-powershell \
+ --cache-control no-cache
+
- name: Create production GitHub release
if: env.PUBLISH_PRODUCTION == 'true'
env:
@@ -325,22 +429,24 @@ jobs:
run: |
BETA_DIR=release-artifacts/beta
RELEASE_PREFIX="releases/v${BETA_VERSION}"
- TARBALL="$BETA_DIR/prime-agent-${BETA_VERSION}.tgz"
- test -f "$TARBALL"
test -f "$BETA_DIR/SHA256SUMS"
test -f "$BETA_DIR/beta"
test -f "$BETA_DIR/beta.json"
test -n "$R2_BUCKET"
test -n "$R2_ENDPOINT_URL"
- for artifact in "$BETA_DIR"/*.tgz; do
+ for artifact in "$BETA_DIR"/*.tar.gz "$BETA_DIR"/*.zip; do
+ test -f "$artifact" || continue
+ content_type="application/gzip"
+ case "$artifact" in
+ *.zip) content_type="application/zip" ;;
+ esac
aws s3 cp "$artifact" "s3://${R2_BUCKET}/${RELEASE_PREFIX}/$(basename "$artifact")" \
--endpoint-url "$R2_ENDPOINT_URL" \
- --content-type application/gzip \
+ --content-type "$content_type" \
--cache-control 'public, max-age=31536000, immutable'
done
-
aws s3 cp "$BETA_DIR/SHA256SUMS" "s3://${R2_BUCKET}/${RELEASE_PREFIX}/SHA256SUMS" \
--endpoint-url "$R2_ENDPOINT_URL" \
--content-type text/plain \
@@ -359,6 +465,7 @@ jobs:
fi
BETA_DIR=release-artifacts/beta
+
aws s3 cp "$BETA_DIR/beta.json" "s3://${R2_BUCKET}/beta.json" \
--endpoint-url "$R2_ENDPOINT_URL" \
--content-type application/json \
@@ -379,6 +486,16 @@ jobs:
--content-type text/x-shellscript \
--cache-control no-cache
+ aws s3 cp /tmp/prime-agent-install.ps1 "s3://${R2_BUCKET}/install.ps1" \
+ --endpoint-url "$R2_ENDPOINT_URL" \
+ --content-type text/x-powershell \
+ --cache-control no-cache
+
+ aws s3 cp /tmp/prime-agent-install-beta.ps1 "s3://${R2_BUCKET}/install-beta.ps1" \
+ --endpoint-url "$R2_ENDPOINT_URL" \
+ --content-type text/x-powershell \
+ --cache-control no-cache
+
printf 'Automated beta build from `%s` (`%s`).\n' "$DEFAULT_BRANCH" "$BUILD_REF" > /tmp/beta-release-notes.md
if gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/beta" >/dev/null 2>&1; then
@@ -410,3 +527,4 @@ jobs:
gh release upload beta "$BETA_DIR"/* --clobber
echo "Beta installer: ${R2_PUBLIC_BASE_URL%/}/install-beta.sh"
+ echo "Windows beta installer: ${R2_PUBLIC_BASE_URL%/}/install-beta.ps1"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index af521a547c..5925ef9be7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,7 +4,6 @@ on:
push:
branches: [main]
pull_request:
- branches: [main]
concurrency:
group: ci-${{ github.ref }}
@@ -42,11 +41,13 @@ jobs:
with:
persist-credentials: false
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
with:
- node-version: 22
- cache: npm
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
- name: Install system dependencies
run: |
@@ -54,14 +55,14 @@ jobs:
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
+ - name: Install dependencies (frozen lockfile)
+ run: bun install --frozen-lockfile
- name: Build
- run: npm run build
+ run: bun run build
- name: Check
- run: npm run check
+ run: bun run check
test:
name: Test (${{ matrix.name }})
@@ -75,39 +76,27 @@ jobs:
include:
- name: agent-core
package: packages/agent
- command: npm test
+ command: bun run test
install_uv: false
- name: ai
package: packages/ai
- command: npm test
+ command: bun run test
install_uv: false
- name: tui
package: packages/tui
- command: npm test
+ command: bun run test
install_uv: false
- name: coding-agent 1/3
package: packages/coding-agent
- command: npm run test:ci -- --shard=1/3
+ command: bun run test:ci -- --shard=1/3
install_uv: true
- name: coding-agent 2/3
package: packages/coding-agent
- command: npm run test:ci -- --shard=2/3
+ command: bun run test:ci -- --shard=2/3
install_uv: true
- name: coding-agent 3/3
package: packages/coding-agent
- command: npm run test:ci -- --shard=3/3
- install_uv: true
- - name: coding-agent process smoke
- package: packages/coding-agent
- command: npm run test:process
- install_uv: true
- - name: coding-agent kernel
- package: packages/coding-agent
- command: npm run test:kernel
- install_uv: true
- - name: runtime python
- package: prime-agent-runtime
- command: uv run python -m unittest discover -s test
+ command: bun run test:ci -- --shard=3/3
install_uv: true
steps:
- name: Checkout
@@ -115,11 +104,13 @@ jobs:
with:
persist-credentials: false
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
with:
- node-version: 22
- cache: npm
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
- name: Install system dependencies
run: |
@@ -127,32 +118,319 @@ jobs:
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
+ - name: Install dependencies (frozen lockfile)
+ run: bun install --frozen-lockfile
- name: Build
- run: npm run build
+ run: bun run build
- - name: Install uv
+ - name: Install uv 0.12.4
if: matrix.install_uv
- run: |
- python3 -m pip install --user uv
- echo "$HOME/.local/bin" >> "$GITHUB_PATH"
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
+ with:
+ version: "0.12.4"
- name: Test
working-directory: ${{ matrix.package }}
run: ${{ matrix.command }}
+ process-stress:
+ name: Process stress
+ needs: trust
+ if: needs.trust.outputs.allowed == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
+ with:
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
+ - 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 (frozen lockfile)
+ run: bun install --frozen-lockfile
+
+ - name: Build
+ run: bun run build
+
+ - name: Install uv 0.12.4
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
+ with:
+ version: "0.12.4"
+
+ - name: Run process stress tests
+ working-directory: packages/coding-agent
+ env:
+ PRIME_AGENT_STRESS_WORKERS: "10"
+ run: bun run test:process-stress
+
+ kernel-heavy:
+ name: Kernel-heavy
+ needs: trust
+ if: needs.trust.outputs.allowed == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
+ with:
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
+ - 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 (frozen lockfile)
+ run: bun install --frozen-lockfile
+
+ - name: Build
+ run: bun run build
+
+ - name: Install uv 0.12.4
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
+ with:
+ version: "0.12.4"
+
+ - name: Run kernel-heavy tests
+ working-directory: packages/coding-agent
+ run: bun run test:kernel
+
+ runtime-python:
+ name: Runtime Python
+ needs: trust
+ if: needs.trust.outputs.allowed == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
+ with:
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
+ - name: Install dependencies (frozen lockfile)
+ run: bun install --frozen-lockfile
+
+ - name: Install uv 0.12.4
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
+ with:
+ version: "0.12.4"
+
+ - name: Install Python runtime dependencies
+ working-directory: prime-agent-runtime
+ run: uv sync
+
+ - name: Run Python runtime tests
+ working-directory: prime-agent-runtime
+ run: uv run python -m unittest discover -s test
+
+ artifact-smoke:
+ name: Artifact smoke
+ needs: trust
+ if: needs.trust.outputs.allowed == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
+ with:
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
+ - name: Install dependencies for all release platforms
+ run: bun install --frozen-lockfile --os=* --cpu=*
+
+ - name: Build
+ run: bun run build
+
+ - name: Build binary
+ working-directory: packages/coding-agent
+ run: bun run build:binary
+
+ - name: Run artifact smoke test
+ working-directory: packages/coding-agent
+ run: bun test test/compiled-artifact-smoke.test.ts
+
build-check-test:
name: build-check-test
if: always() && needs.trust.outputs.allowed == 'true'
- needs: [trust, build-check, test]
+ needs: [trust, build-check, test, process-stress, kernel-heavy, runtime-python, artifact-smoke, windows]
runs-on: ubuntu-latest
steps:
- name: Verify CI results
env:
BUILD_CHECK_RESULT: ${{ needs.build-check.result }}
TEST_RESULT: ${{ needs.test.result }}
+ PROCESS_STRESS_RESULT: ${{ needs.process-stress.result }}
+ KERNEL_HEAVY_RESULT: ${{ needs.kernel-heavy.result }}
+ RUNTIME_PYTHON_RESULT: ${{ needs.runtime-python.result }}
+ ARTIFACT_SMOKE_RESULT: ${{ needs.artifact-smoke.result }}
+ WINDOWS_RESULT: ${{ needs.windows.result }}
run: |
test "$BUILD_CHECK_RESULT" = success
test "$TEST_RESULT" = success
+ test "$PROCESS_STRESS_RESULT" = success
+ test "$KERNEL_HEAVY_RESULT" = success
+ test "$RUNTIME_PYTHON_RESULT" = success
+ test "$ARTIFACT_SMOKE_RESULT" = success
+ test "$WINDOWS_RESULT" = success
+
+ windows:
+ name: Windows native
+ needs: trust
+ if: needs.trust.outputs.allowed == 'true'
+ runs-on: windows-latest
+ timeout-minutes: 30
+ steps:
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ with:
+ persist-credentials: false
+
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
+ with:
+ bun-version: 1.4.0
+
+ - name: Install uv 0.12.4
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
+ with:
+ version: "0.12.4"
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Type check
+ run: bun run check:type
+
+ - name: Validate installers
+ run: bun run check:installer
+
+ - name: Run Windows behavior tests
+ working-directory: packages/coding-agent
+ run: >-
+ bun test
+ test/bash-close-hang-windows.test.ts
+ test/child-process.test.ts
+ test/daemon-socket.test.ts
+ test/exec.test.ts
+ test/kernel-bash-shell.test.ts
+
+ - name: Test terminal behavior
+ working-directory: packages/tui
+ run: >-
+ bun test
+ test/fullscreen.test.ts
+ test/input.test.ts
+ test/stdin-buffer.test.ts
+ test/terminal.test.ts
+ test/terminal-image.test.ts
+ test/tui-cell-size-input.test.ts
+ test/tui-render.test.ts
+
+ - name: Bootstrap the CPython kernel
+ working-directory: packages/coding-agent
+ env:
+ PRIME_AGENT_INSTALL_UV: "0"
+ PRIME_AGENT_KERNEL_VENV: ${{ runner.temp }}\Prime Agent ø\kernel-venv
+ run: bun src/core/kernel/bootstrap-cli.ts
+
+ - name: Test persistent CPython kernel behavior
+ working-directory: packages/coding-agent
+ env:
+ PRIME_AGENT_BASH_SHELL: C:\Program Files\Git\bin\bash.exe
+ PRIME_AGENT_KERNEL_VENV: ${{ runner.temp }}\Prime Agent ø\kernel-venv
+ PRIME_AGENT_TEST_TAGS: kernel-heavy
+ run: >-
+ bun ../../scripts/run-with-clean-env.ts bun test --isolate --timeout 30000
+ test/acp-kernel-features.test.ts
+ test/kernel-goal-skill.test.ts
+ test/repl-kernel-execute.test.ts
+ test/repl-kernel-parent-watchdog.test.ts
+
+ - name: Test the Windows CPython runtime
+ working-directory: prime-agent-runtime
+ env:
+ PRIME_AGENT_BASH_SHELL: C:\Program Files\Git\bin\bash.exe
+ run: >-
+ uv run python -m unittest
+ test.test_winjob
+ test.test_repl.ReplTest.test_interrupt_without_pthread_kill_cancels_awaited_cell
+ test.test_repl.ReplTest.test_interrupt_sync_blocking
+ test.test_repl.ReplTest.test_interrupt_await_suspended
+
+ - name: Build the native Windows executable
+ run: bun run build:binary
+
+ - name: Cross-compile and inspect the Windows Arm64 executable
+ working-directory: packages/coding-agent
+ shell: pwsh
+ run: |
+ bun build --compile --minify --keep-names --bytecode --format=esm --external koffi --target=bun-windows-arm64 ./dist/bun/cli.js --outfile "$env:RUNNER_TEMP\prime-agent-arm64.exe"
+ $artifacts = @(
+ @{ Path = "dist\pi.exe"; Machine = 0x8664 },
+ @{ Path = "$env:RUNNER_TEMP\prime-agent-arm64.exe"; Machine = 0xAA64 }
+ )
+ foreach ($artifact in $artifacts) {
+ $bytes = [System.IO.File]::ReadAllBytes($artifact.Path)
+ if ($bytes.Length -lt 64 -or $bytes[0] -ne 0x4D -or $bytes[1] -ne 0x5A) {
+ throw "$($artifact.Path) is not a PE executable"
+ }
+ $peOffset = [BitConverter]::ToInt32($bytes, 0x3C)
+ $machine = [BitConverter]::ToUInt16($bytes, $peOffset + 4)
+ if ($machine -ne $artifact.Machine) {
+ throw "$($artifact.Path) has PE machine 0x$($machine.ToString('X4')), expected 0x$($artifact.Machine.ToString('X4'))"
+ }
+ }
+
+ - name: Test the PowerShell installer end to end
+ shell: pwsh
+ run: ./scripts/test-windows-installer.ps1
+
+ - name: Smoke test the native Windows executable
+ shell: pwsh
+ run: |
+ & ./packages/coding-agent/dist/pi.exe --version
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ & ./packages/coding-agent/dist/pi.exe --help
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
diff --git a/.github/workflows/nightly-process-stress.yml b/.github/workflows/nightly-process-stress.yml
index e47f42ca85..41d049f8c9 100644
--- a/.github/workflows/nightly-process-stress.yml
+++ b/.github/workflows/nightly-process-stress.yml
@@ -23,11 +23,13 @@ jobs:
with:
persist-credentials: false
- - name: Setup Node.js
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ - name: Setup Bun 1.4.0
+ uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
with:
- node-version: 22
- cache: npm
+ bun-version: 1.4.0
+
+ - name: Verify Bun 1.4.0
+ run: bun run check:bun-version
- name: Install system dependencies
run: |
@@ -35,19 +37,19 @@ jobs:
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
+ - name: Install dependencies (frozen lockfile)
+ run: bun install --frozen-lockfile
- name: Build
- run: npm run build
+ run: bun run build
- - name: Install uv
- run: |
- python3 -m pip install --user uv
- echo "$HOME/.local/bin" >> "$GITHUB_PATH"
+ - name: Install uv 0.12.4
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
+ with:
+ version: "0.12.4"
- name: Run process stress tests
working-directory: packages/coding-agent
env:
PRIME_AGENT_STRESS_WORKERS: "10"
- run: npm run test:process-stress
+ run: bun run test:process-stress
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 64a0169cd0..4453e34be2 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -4,7 +4,7 @@
STAGED_FILES=$(git diff --cached --name-only)
echo "Running formatting, linting, and type checking..."
-npm run check
+bun run check
if [ $? -ne 0 ]; then
echo "Checks failed. Please fix the errors before committing."
exit 1
diff --git a/.npmrc b/.npmrc
deleted file mode 100644
index 2ebd6374a1..0000000000
--- a/.npmrc
+++ /dev/null
@@ -1,6 +0,0 @@
-# Supply-chain cooldown: never resolve to package versions published
-# less than 7 days ago. Only affects version resolution (install/update of new
-# versions); already-locked versions and `npm ci` are unaffected.
-# Requires npm >= 11.10 to be enforced; older npm ignores this key.
-# Urgent security patch override: npm install --min-release-age=0
-min-release-age=7
diff --git a/AGENTS.md b/AGENTS.md
index 85e64ff351..c8fe302c1a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -22,10 +22,10 @@
## Commands
-- After code changes (not documentation changes): `npm run check` (get full output, no tail). Fix all errors, warnings, and infos before committing.
-- Note: `npm run check` does not run tests.
-- NEVER run: `npm run dev`, `npm run build`, `npm test`
-- Only run specific tests if user instructs: `npx tsx ../../node_modules/vitest/dist/cli.js --run test/specific.test.ts`
+- After code changes (not documentation changes): `bun run check` (get full output, no tail). Fix all errors, warnings, and infos before committing.
+- Note: `bun run check` does not run tests.
+- NEVER run: `bun run dev`, `bun run build`, `bun test`
+- Only run specific tests if user instructs: `bun test test/specific.test.ts`
- Run tests from the package root, not the repo root.
- If you create or modify a test file, you MUST run that test file and iterate until it passes.
- When writing tests, run them, identify issues in either the test or implementation, and iterate until fixed.
@@ -43,9 +43,9 @@
## Dependencies
-- A 7-day minimum release age applies to all dependency updates: `.npmrc` sets `min-release-age=7` and `.github/dependabot.yml` uses a matching `cooldown`. Never bypass it for routine updates.
-- Enforcement requires npm >= 11.10; older npm silently ignores the setting, so use a current npm when updating dependencies.
-- For an urgent security patch younger than 7 days, override explicitly: `npm install --min-release-age=0 `.
+- A 7-day minimum release age applies to all dependency updates: `bunfig.toml` sets `minimumReleaseAge = 604800` and `.github/dependabot.yml` uses a matching `cooldown`. Never bypass it for routine updates.
+- Bun 1.4.0 enforces this setting for dependency updates.
+- For an urgent security patch younger than 7 days, override explicitly: `bun add --minimum-release-age=0 `.
## GitHub Workflow
@@ -195,8 +195,8 @@ Create provider file exporting:
2. **Run release script**:
```bash
- npm run release:patch # Fixes and additions
- npm run release:minor # API breaking changes
+ bun run release:patch # Fixes and additions
+ bun run release:minor # API breaking changes
```
The script handles: version bump, folding `.changes/` fragments into the release section, commit, tag, and publish.
diff --git a/README.md b/README.md
index e4b77ad79f..088b1f1dcb 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,13 @@ Install the latest stable release on macOS or Linux:
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh
```
-The installer downloads a versioned release, verifies its SHA-256 checksum, installs the `prime-agent` command, and can prepare the Python runtime used by the agent.
+On Windows, use PowerShell. WSL is not required; Git Bash is required for shell commands.
+
+```powershell
+irm https://app.primeintellect.ai/prime-agent/install.ps1 | iex
+```
+
+The installers download a versioned release, verify its SHA-256 checksum, install the `prime-agent` command, and can prepare the Python runtime used by the agent. See [Windows Setup](packages/coding-agent/docs/windows.md) for PATH, update, uninstall, and troubleshooting guidance.
Start Prime Agent from the repository or directory you want it to work in:
diff --git a/bun.lock b/bun.lock
new file mode 100644
index 0000000000..c1cfdb646d
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,780 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "prime-agent",
+ "dependencies": {
+ "@earendil-works/pi-coding-agent": "^0.9.1",
+ "get-east-asian-width": "^1.6.0",
+ },
+ "devDependencies": {
+ "@anthropic-ai/sandbox-runtime": "^0.0.55",
+ "@biomejs/biome": "2.5.5",
+ "@types/node": "^22.10.5",
+ "@typescript/native-preview": "7.0.0-dev.20260120.1",
+ "@vitest/expect": "4.1.10",
+ "@vitest/runner": "4.1.10",
+ "@vitest/spy": "4.1.10",
+ "bun-types": "1.4.0",
+ "husky": "^9.1.7",
+ "jiti": "^2.7.0",
+ "typescript": "^7.0.2",
+ },
+ },
+ "packages/agent": {
+ "name": "@earendil-works/pi-agent-core",
+ "version": "0.9.1",
+ "dependencies": {
+ "@earendil-works/pi-ai": "^0.9.1",
+ "typebox": "^1.3.9",
+ },
+ "devDependencies": {
+ "@types/node": "^24.3.0",
+ "typescript": "^7.0.2",
+ },
+ },
+ "packages/ai": {
+ "name": "@earendil-works/pi-ai",
+ "version": "0.9.1",
+ "bin": {
+ "pi-ai": "./dist/cli.js",
+ },
+ "dependencies": {
+ "@anthropic-ai/sdk": "^0.91.1",
+ "@aws-sdk/client-bedrock-runtime": "^3.1095.0",
+ "@google/genai": "^1.40.0",
+ "@mistralai/mistralai": "2.2.1",
+ "chalk": "^5.6.2",
+ "openai": "6.47.0",
+ "partial-json": "^0.1.7",
+ "proxy-agent": "^6.5.0",
+ "typebox": "^1.3.9",
+ "undici": "^7.29.0",
+ "zod-to-json-schema": "^3.24.6",
+ },
+ "devDependencies": {
+ "@types/node": "^24.3.0",
+ "canvas": "^3.2.0",
+ },
+ },
+ "packages/coding-agent": {
+ "name": "@earendil-works/pi-coding-agent",
+ "version": "0.9.1",
+ "bin": {
+ "pi": "dist/bundle/cli.js",
+ },
+ "dependencies": {
+ "@agentclientprotocol/sdk": "^1.3.0",
+ "@earendil-works/pi-agent-core": "^0.9.1",
+ "@earendil-works/pi-ai": "^0.9.1",
+ "@earendil-works/pi-tui": "^0.9.1",
+ "@silvia-odwyer/photon-node": "^0.3.4",
+ "chalk": "^5.5.0",
+ "cli-highlight": "^2.1.11",
+ "diff": "^9.0.0",
+ "extract-zip": "^2.0.1",
+ "file-type": "^21.1.1",
+ "glob": "^13.0.1",
+ "grok-mermaid": "0.2.3",
+ "hosted-git-info": "^9.0.2",
+ "ignore": "^7.0.5",
+ "jiti": "^2.7.0",
+ "marked": "^18.0.7",
+ "minimatch": "^10.2.3",
+ "proper-lockfile": "^4.1.2",
+ "strip-ansi": "^7.1.0",
+ "typebox": "^1.3.9",
+ "undici": "^7.29.0",
+ "uuid": "^14.0.0",
+ "yaml": "^2.9.0",
+ },
+ "devDependencies": {
+ "@types/diff": "^8.0.0",
+ "@types/hosted-git-info": "^3.0.5",
+ "@types/ms": "^2.1.0",
+ "@types/node": "^24.3.0",
+ "@types/proper-lockfile": "^4.1.4",
+ "typescript": "^7.0.2",
+ },
+ "optionalDependencies": {
+ "@mariozechner/clipboard": "^0.3.9",
+ },
+ },
+ "packages/coding-agent/examples/extensions/custom-provider-anthropic": {
+ "name": "pi-extension-custom-provider-anthropic",
+ "version": "0.1.1",
+ "dependencies": {
+ "@anthropic-ai/sdk": "^0.52.0",
+ },
+ },
+ "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo": {
+ "name": "pi-extension-custom-provider-gitlab-duo",
+ "version": "0.1.1",
+ },
+ "packages/coding-agent/examples/extensions/sandbox": {
+ "name": "pi-extension-sandbox",
+ "version": "1.5.1",
+ "dependencies": {
+ "@anthropic-ai/sandbox-runtime": "^0.0.55",
+ },
+ },
+ "packages/coding-agent/examples/extensions/with-deps": {
+ "name": "pi-extension-with-deps",
+ "version": "0.1.1",
+ "dependencies": {
+ "ms": "^2.1.3",
+ },
+ "devDependencies": {
+ "@types/ms": "^2.1.0",
+ },
+ },
+ "packages/tui": {
+ "name": "@earendil-works/pi-tui",
+ "version": "0.9.1",
+ "dependencies": {
+ "@types/mime-types": "^3.0.1",
+ "chalk": "^5.5.0",
+ "get-east-asian-width": "^1.6.0",
+ "marked": "^18.0.7",
+ "mime-types": "^3.0.1",
+ },
+ "devDependencies": {
+ "@xterm/headless": "^6.0.0",
+ "@xterm/xterm": "^6.0.0",
+ },
+ "optionalDependencies": {
+ "koffi": "^2.9.0",
+ },
+ },
+ },
+ "overrides": {
+ "rimraf": "6.1.2",
+ "shell-quote": "^1.10.0",
+ },
+ "packages": {
+ "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@1.4.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-/eufudw+aFY1LKLolT6yFE6UMmYRl7fMJ/DEONSIyR6wI3slHWITBsANRGqXEY8FRzqUxwh7QEaGiZHcJPVThg=="],
+
+ "@anthropic-ai/sandbox-runtime": ["@anthropic-ai/sandbox-runtime@0.0.55", "", { "dependencies": { "@pondwader/socks5-server": "^1.0.10", "commander": "^12.1.0", "node-forge": "^1.4.0", "shell-quote": "^1.8.4", "zod": "^3.24.1" }, "bin": { "srt": "dist/cli.js" } }, "sha512-XGTLbzitn0y6OCdmqP5aYrBR0HlNi87ue++m9vMPxcNwaQeOj9i3f9CSWmXua8mv+Q8E55k4ZGlzJmpDHzQFBw=="],
+
+ "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="],
+
+ "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1117.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/credential-provider-node": "^3.972.81", "@aws-sdk/eventstream-handler-node": "^3.972.34", "@aws-sdk/middleware-eventstream": "^3.972.29", "@aws-sdk/middleware-websocket": "^3.972.52", "@aws-sdk/token-providers": "3.1117.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/node-http-handler": "^4.11.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-rUc58ZS5GgsvzfM265XsrNno35dsRIwQb0aDfJIZbJyn1VGP+6XVL5Pwm8Rg9uABQSdKpdTayu5R3ZuDlFfIHg=="],
+
+ "@aws-sdk/core": ["@aws-sdk/core@3.977.9", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@aws-sdk/xml-builder": "^3.972.40", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.33.3", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA=="],
+
+ "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.70", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw=="],
+
+ "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.72", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/node-http-handler": "^4.11.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w=="],
+
+ "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.15", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/credential-provider-env": "^3.972.70", "@aws-sdk/credential-provider-http": "^3.972.72", "@aws-sdk/credential-provider-login": "^3.972.77", "@aws-sdk/credential-provider-process": "^3.972.70", "@aws-sdk/credential-provider-sso": "^3.973.14", "@aws-sdk/credential-provider-web-identity": "^3.972.76", "@aws-sdk/nested-clients": "^3.997.44", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg=="],
+
+ "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.77", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/nested-clients": "^3.997.44", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ=="],
+
+ "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.81", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.70", "@aws-sdk/credential-provider-http": "^3.972.72", "@aws-sdk/credential-provider-ini": "^3.973.15", "@aws-sdk/credential-provider-process": "^3.972.70", "@aws-sdk/credential-provider-sso": "^3.973.14", "@aws-sdk/credential-provider-web-identity": "^3.972.76", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-Rml+WitoFvXmv6JZ18U/xGdGDGGvB/mOin0ya0lTnTrdC0Z1lrVxTYh7iNklZBcvcRMrs4DoEf6xy1KWyrLQQw=="],
+
+ "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.70", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q=="],
+
+ "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.14", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/nested-clients": "^3.997.44", "@aws-sdk/token-providers": "3.1116.0", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA=="],
+
+ "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.76", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/nested-clients": "^3.997.44", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA=="],
+
+ "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.34", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-cTeVzpu1xEAkryTZBYhGwnQ6gOGyp8ZYZvmn0Sg/nI/ABmy/CRHHxPDJDUi9PxwxUtGGaatvfRUB3FCgT/rSWw=="],
+
+ "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.29", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-dlRzHCgyB8W6hLuDC5pcT5q+ziPt00n4QGgGBE17ucLVU4zMa6lsbuUdQ2Pm75Z5VA8GF+R/+SgrRcaTdIzSIQ=="],
+
+ "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.52", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-vsPPM+nMbKJlUCFU+eoGZbdxdxDIAX9LbpjSXaR5Ufpmqgp8TdYQnoExhLu4T3umW/JIIPny1ydbhWidZZYokQ=="],
+
+ "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.44", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/signature-v4-multi-region": "^3.996.46", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/fetch-http-handler": "^5.7.2", "@smithy/node-http-handler": "^4.11.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw=="],
+
+ "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.46", "", { "dependencies": { "@aws-sdk/types": "^3.974.5", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ=="],
+
+ "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1117.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/nested-clients": "^3.997.44", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-I7QIf9aR0+J4Q1qlLQw006d8hs3Pb/J6mOVSCQ/fnQ4AXHTQ6Y/M45O2dWDRKoYqsQJ2kN8sg6b5709/qCzp9A=="],
+
+ "@aws-sdk/types": ["@aws-sdk/types@3.974.5", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ=="],
+
+ "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.40", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA=="],
+
+ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
+
+ "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
+
+ "@biomejs/biome": ["@biomejs/biome@2.5.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.5", "@biomejs/cli-darwin-x64": "2.5.5", "@biomejs/cli-linux-arm64": "2.5.5", "@biomejs/cli-linux-arm64-musl": "2.5.5", "@biomejs/cli-linux-x64": "2.5.5", "@biomejs/cli-linux-x64-musl": "2.5.5", "@biomejs/cli-win32-arm64": "2.5.5", "@biomejs/cli-win32-x64": "2.5.5" }, "bin": { "biome": "bin/biome" } }, "sha512-r1S8nFsAG1MY+vJFZALzIvwXAJv6ejDQ0mxP21Tgr9YK3ZFtjrvbBwDdNhx1rUqvccEIeNg20cYCNzl6Cr69pQ=="],
+
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kUrAhXVWUrwmAUnV2iXSK7umxKFysTwvqK+Ty6ptUcLY/7T3SnCAjUowE4uvwaEej6nXZ7hu/dTtbokKdsPeag=="],
+
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-DamiYc5bUYZ2uxlfc+RLEPtz1Abb6PO5eTbOkufLpSGwd/7AMQAdxhFYiXmwwkJL8IsT8S7GvdgwDHqaMFAvKw=="],
+
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lRKF/pH/1RiYiBKExi3TCZVAtvzEm77aifrvcNiDFrR9WxeAnDUjDnseb6y2XV85mjitLs6SILGm2XG77cHtSQ=="],
+
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-U4WMl/sy/E/Q73vf15VspakLRRs2LDFcCeBxJnQfXzssb88zpV6PJPaQ3ezhQ7H6Ht2/8bvuZeHgJWzmoxllZg=="],
+
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.5", "", { "os": "linux", "cpu": "x64" }, "sha512-H/O39nJEw/2Zm/fm7hrmxxoF8kK/aU1uCoPp70ruXVbomaAdLpJJnCmL11Q2JotT8QVHH06So04Oq53lCSwSwQ=="],
+
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.5", "", { "os": "linux", "cpu": "x64" }, "sha512-m7wC7tjX5Lrmo69dc4md8FeKpPU1NTCY1v7xUoQQ2vadWwNnBS0KZOG8471otFPHrTHihQJAjQPgMObpLvDe6A=="],
+
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-7BryINPuYypLUAH3o/o5ZdgomJ4zn3EDR0ChZJst7n32S6ZhKbgHXuYydLu+YAnx59ehGFR0z/MG6qnzQi3Yyw=="],
+
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.5", "", { "os": "win32", "cpu": "x64" }, "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g=="],
+
+ "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
+
+ "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@workspace:packages/agent"],
+
+ "@earendil-works/pi-ai": ["@earendil-works/pi-ai@workspace:packages/ai"],
+
+ "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@workspace:packages/coding-agent"],
+
+ "@earendil-works/pi-tui": ["@earendil-works/pi-tui@workspace:packages/tui"],
+
+ "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="],
+
+ "@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="],
+
+ "@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ=="],
+
+ "@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.9", "", { "os": "darwin" }, "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ=="],
+
+ "@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg=="],
+
+ "@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw=="],
+
+ "@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ=="],
+
+ "@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.9", "", { "os": "linux", "cpu": "none" }, "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw=="],
+
+ "@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw=="],
+
+ "@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ=="],
+
+ "@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ=="],
+
+ "@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA=="],
+
+ "@mistralai/mistralai": ["@mistralai/mistralai@2.2.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" } }, "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ=="],
+
+ "@pondwader/socks5-server": ["@pondwader/socks5-server@1.0.10", "", {}, "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg=="],
+
+ "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
+
+ "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
+
+ "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
+
+ "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
+
+ "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
+
+ "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
+
+ "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
+
+ "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
+
+ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
+
+ "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="],
+
+ "@smithy/core": ["@smithy/core@3.33.3", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg=="],
+
+ "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="],
+
+ "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.7.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw=="],
+
+ "@smithy/node-http-handler": ["@smithy/node-http-handler@4.11.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-2jY1tSpERfPfWqyBV2pH+iGFaghVsIJszJNsT7hxtQYhVJpWDyc0LqOWI+nXOxOAHaEfZ4PXXtp1wW1TGpHhkA=="],
+
+ "@smithy/signature-v4": ["@smithy/signature-v4@5.7.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow=="],
+
+ "@smithy/types": ["@smithy/types@4.17.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-FOKpVZob9MPTn2znRzGrnsMHv7BOsKVw3XiP/cOyYLDVZ9qKp4nifIiSCuUU/fIj5Vu0UOAxCFr+qRAtG0NUkA=="],
+
+ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
+
+ "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
+
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
+
+ "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
+
+ "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
+
+ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
+
+ "@types/diff": ["@types/diff@8.0.0", "", { "dependencies": { "diff": "*" } }, "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw=="],
+
+ "@types/hosted-git-info": ["@types/hosted-git-info@3.0.5", "", {}, "sha512-Dmngh7U003cOHPhKGyA7LWqrnvcTyILNgNPmNCxlx7j8MIi54iBliiT8XqVLIQ3GchoOjVAyBzNJVyuaJjqokg=="],
+
+ "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="],
+
+ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+
+ "@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="],
+
+ "@types/proper-lockfile": ["@types/proper-lockfile@4.1.4", "", { "dependencies": { "@types/retry": "*" } }, "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ=="],
+
+ "@types/retry": ["@types/retry@0.12.5", "", {}, "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw=="],
+
+ "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
+
+ "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260120.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260120.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260120.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-nnEf37C9ue7OBRnF2zmV/OCBmV5Y7T/K4mCHa+nxgiXcF/1w8sA0cgdFl+gHQ0mysqUJ+Bu5btAMeWgpLyjrgg=="],
+
+ "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260120.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-r3pWFuR2H7mn6ScwpH5jJljKQqKto0npVuJSk6pRwFwexpTyxOGmJTZJ1V0AWiisaNxU2+CNAqWFJSJYIE/QTg=="],
+
+ "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260120.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cuC1+wLbUP+Ip2UT94G134fqRdp5w3b3dhcCO6/FQ4yXxvRNyv/WK+upHBUFDaeSOeHgDTyO9/QFYUWwC4If1A=="],
+
+ "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "arm" }, "sha512-vN6OYVySol/kQZjJGmAzd6L30SyVlCgmCXS8WjUYtE5clN0YrzQHop16RK29fYZHMxpkOniVBtRPxUYQANZBlQ=="],
+
+ "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-zZGvEGY7wcHYefMZ87KNmvjN3NLIhsCMHEpHZiGCS3khKf+8z6ZsanrzCjOTodvL01VPyBzHxV1EtkSxAcLiQg=="],
+
+ "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "x64" }, "sha512-JBfNhWd/asd5MDeS3VgRvE24pGKBkmvLub6tsux6ypr+Yhy+o0WaAEzVpmlRYZUqss2ai5tvOu4dzPBXzZAtFw=="],
+
+ "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260120.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-tTndRtYCq2xwgE0VkTi9ACNiJaV43+PqvBqCxk8ceYi3X36Ve+CCnwlZfZJ4k9NxZthtrAwF/kUmpC9iIYbq1w=="],
+
+ "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260120.1", "", { "os": "win32", "cpu": "x64" }, "sha512-oZia7hFL6k9pVepfonuPI86Jmyz6WlJKR57tWCDwRNmpA7odxuTq1PbvcYgy1z4+wHF1nnKKJY0PMAiq6ac18w=="],
+
+ "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
+
+ "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
+
+ "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
+
+ "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
+
+ "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
+
+ "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
+
+ "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
+
+ "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
+
+ "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
+
+ "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
+
+ "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
+
+ "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
+
+ "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
+
+ "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
+
+ "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
+
+ "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
+
+ "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
+
+ "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
+
+ "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
+
+ "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
+
+ "@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="],
+
+ "@vitest/pretty-format": ["@vitest/pretty-format@4.1.10", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q=="],
+
+ "@vitest/runner": ["@vitest/runner@4.1.10", "", { "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" } }, "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg=="],
+
+ "@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="],
+
+ "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="],
+
+ "@xterm/headless": ["@xterm/headless@6.0.0", "", {}, "sha512-5Yj1QINYCyzrZtf8OFIHi47iQtI+0qYFPHmouEfG8dHNxbZ9Tb9YGSuLcsEwj9Z+OL75GJqPyJbyoFer80a2Hw=="],
+
+ "@xterm/xterm": ["@xterm/xterm@6.0.0", "", {}, "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg=="],
+
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
+ "ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
+
+ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+
+ "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
+
+ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
+
+ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
+
+ "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
+
+ "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
+
+ "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
+
+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+ "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
+
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
+
+ "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="],
+
+ "canvas": ["canvas@3.2.3", "", { "dependencies": { "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.3" } }, "sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw=="],
+
+ "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
+
+ "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+
+ "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
+
+ "cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="],
+
+ "cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
+
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
+ "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
+
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
+
+ "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
+
+ "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
+
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="],
+
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
+
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
+
+ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
+ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+
+ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
+ "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
+
+ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
+
+ "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
+
+ "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
+
+ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
+
+ "file-type": ["file-type@21.3.4", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g=="],
+
+ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
+
+ "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
+
+ "gaxios": ["gaxios@7.3.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ=="],
+
+ "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
+
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+ "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
+
+ "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
+
+ "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
+
+ "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
+
+ "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
+ "google-auth-library": ["google-auth-library@10.9.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw=="],
+
+ "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "grok-mermaid": ["grok-mermaid@0.2.3", "", {}, "sha512-/4KopAbsjvuRP9MdPtlDjOHUmUVEohOX73JNcsWpzAtFxh+bq5+Dhb6gzvRieLDwIPQIR3/vy8V1NNTuz4Zsmg=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
+
+ "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="],
+
+ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
+
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+ "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
+
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+ "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
+
+ "ip-address": ["ip-address@10.5.0", "", {}, "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g=="],
+
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
+
+ "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
+
+ "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
+
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
+
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
+
+ "koffi": ["koffi@2.16.3", "", {}, "sha512-E9y1AsgYGlaxMhcZzHr8y96QF2U5XzA12GGVAfbWqIubTwPNMXQarfBzePNXHe0xtIEtNd6ifAv3GAKYGUeBAQ=="],
+
+ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
+
+ "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
+
+ "marked": ["marked@18.0.11", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-HnslJfsZkRPBDJRHvVtAaWlZHEpSu7u8LgQuJCELjRKuWR+hpq4A7sLq3p8HaI9ypVoXDXxV34CsQJEe1+J5Aw=="],
+
+ "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+
+ "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
+
+ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
+
+ "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
+
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
+ "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+
+ "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
+
+ "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
+
+ "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="],
+
+ "node-abi": ["node-abi@3.95.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-T9iGctuocf0qIWFFOTxPzjT5q0SILqaBYXt272tlBHvTKC5+3JnkMirLxNJNkXHtFyBjU2Jx+NL4Zipr0B/c6Q=="],
+
+ "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
+
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
+
+ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
+
+ "node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
+
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
+
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+ "openai": ["openai@6.47.0", "", { "peerDependencies": { "@aws-sdk/credential-provider-node": ">=3.972.0 <4", "@smithy/hash-node": ">=4.3.0 <5", "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@aws-sdk/credential-provider-node", "@smithy/hash-node", "@smithy/signature-v4", "ws", "zod"] }, "sha512-xYr+R9woSzWxVxeiqkkNbHhv89tZDEI6eBMbrdPnv3poh+mijHvbhS35a+3o6xHa411/ns8j5ENY3So9DCXWYw=="],
+
+ "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
+
+ "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
+
+ "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
+
+ "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
+
+ "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="],
+
+ "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],
+
+ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
+
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
+
+ "pi-extension-custom-provider-anthropic": ["pi-extension-custom-provider-anthropic@workspace:packages/coding-agent/examples/extensions/custom-provider-anthropic"],
+
+ "pi-extension-custom-provider-gitlab-duo": ["pi-extension-custom-provider-gitlab-duo@workspace:packages/coding-agent/examples/extensions/custom-provider-gitlab-duo"],
+
+ "pi-extension-sandbox": ["pi-extension-sandbox@workspace:packages/coding-agent/examples/extensions/sandbox"],
+
+ "pi-extension-with-deps": ["pi-extension-with-deps@workspace:packages/coding-agent/examples/extensions/with-deps"],
+
+ "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
+
+ "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
+
+ "protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="],
+
+ "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
+
+ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
+
+ "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
+
+ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
+
+ "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
+
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
+ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
+
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="],
+
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+ "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
+
+ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
+
+ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
+
+ "socks": ["socks@2.8.9", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw=="],
+
+ "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
+
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
+ "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
+
+ "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
+
+ "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="],
+
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "tar-fs": ["tar-fs@2.1.5", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw=="],
+
+ "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
+
+ "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
+
+ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
+
+ "tinyrainbow": ["tinyrainbow@3.1.1", "", {}, "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw=="],
+
+ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
+
+ "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
+
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
+
+ "typebox": ["typebox@1.3.18", "", {}, "sha512-/wYPoDqxWZSxV/XD8Eskzr3YluXC9CaWJOuUYMkj+lLVLkyeEIQKzHvMuS/IRc3OLTIBC32LAtHgXo/WFEOMHQ=="],
+
+ "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
+
+ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
+
+ "undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="],
+
+ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
+
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "uuid": ["uuid@14.0.2", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ=="],
+
+ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
+
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+ "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
+
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+ "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
+
+ "yargs": ["yargs@16.2.2", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w=="],
+
+ "yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
+
+ "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
+
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+
+ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
+
+ "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1116.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.9", "@aws-sdk/nested-clients": "^3.997.44", "@aws-sdk/types": "^3.974.5", "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q=="],
+
+ "@earendil-works/pi-agent-core/@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
+
+ "@earendil-works/pi-ai/@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
+
+ "@earendil-works/pi-coding-agent/@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
+
+ "@mistralai/mistralai/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
+
+ "@types/yauzl/@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
+
+ "bun-types/@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
+
+ "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
+
+ "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
+
+ "p-retry/@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
+
+ "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+
+ "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
+
+ "path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
+
+ "pi-extension-custom-provider-anthropic/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.52.0", "", { "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-d4c+fg+xy9e46c8+YnrrgIQR45CZlAi7PwdzIfDXDM6ACxEZli1/fxhURsq30ZpMZy6LvSkr41jGq5aF5TD7rQ=="],
+
+ "protobufjs/@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
+
+ "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "@earendil-works/pi-agent-core/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
+ "@earendil-works/pi-ai/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
+ "@earendil-works/pi-coding-agent/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
+ "@types/yauzl/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
+ "bun-types/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
+ "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "protobufjs/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
+
+ "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ }
+}
diff --git a/bunfig.toml b/bunfig.toml
new file mode 100644
index 0000000000..09021efcc1
--- /dev/null
+++ b/bunfig.toml
@@ -0,0 +1,8 @@
+[install]
+linker = "hoisted"
+minimumReleaseAge = 604800
+
+[test]
+preload = ["./packages/coding-agent/test/bun-test-preload.ts"]
+timeout = 30000
+pathIgnorePatterns = ["**/daemon-supervisor-process.test.ts", "**/compiled-artifact-smoke.test.ts", "**/daemon-supervisor-monitor.test.ts"]
diff --git a/install.ps1 b/install.ps1
new file mode 100644
index 0000000000..f7871b737b
--- /dev/null
+++ b/install.ps1
@@ -0,0 +1,234 @@
+#!/usr/bin/env pwsh
+<#
+.SYNOPSIS
+Install Prime Agent on Windows without WSL.
+.DESCRIPTION
+Downloads a checksummed Windows release archive, installs it under the current
+user's LocalAppData directory, and adds the Prime Agent command directory to the
+user PATH. Git Bash is required by Prime Agent's shell tool after installation.
+.PARAMETER Version
+Install a specific release version. A leading v is accepted.
+.PARAMETER Channel
+Resolve the current stable or beta release when Version is omitted.
+.PARAMETER Update
+Install and activate the requested release over the current command shim.
+.PARAMETER Uninstall
+Remove Prime Agent and its user PATH entry.
+#>
+
+[CmdletBinding()]
+param(
+ [string]$Version,
+ [ValidateSet("stable", "beta")]
+ [string]$Channel,
+ [switch]$Update,
+ [switch]$Uninstall
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+$ProgressPreference = "SilentlyContinue"
+
+$DownloadBaseUrl = if ($env:PRIME_AGENT_DOWNLOAD_BASE_URL) {
+ $env:PRIME_AGENT_DOWNLOAD_BASE_URL.TrimEnd("/")
+} else {
+ "__PRIME_AGENT_DOWNLOAD_BASE_URL__"
+}
+$DefaultChannel = "__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__"
+if ($DefaultChannel -eq "__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__") {
+ $DefaultChannel = "stable"
+}
+if (-not $Channel) {
+ $Channel = $DefaultChannel
+}
+
+$CommandName = if ($env:PRIME_AGENT_CMD) { $env:PRIME_AGENT_CMD } else { "prime-agent" }
+if ($CommandName -notmatch "^[A-Za-z0-9._-]+$") {
+ throw "Invalid PRIME_AGENT_CMD value: $CommandName"
+}
+if (-not $env:LOCALAPPDATA) {
+ throw "LOCALAPPDATA is required for a per-user Prime Agent installation."
+}
+
+$InstallRoot = Join-Path $env:LOCALAPPDATA "PrimeAgent"
+$VersionsDir = Join-Path $InstallRoot "versions"
+$BinDir = Join-Path $InstallRoot "bin"
+$CommandShim = Join-Path $BinDir "$CommandName.cmd"
+
+function Write-Step([string]$Message) {
+ Write-Host "prime-agent: $Message" -ForegroundColor Cyan
+}
+
+function Get-ReleasePlatform {
+ $architecture = if ($env:PROCESSOR_ARCHITEW6432) {
+ $env:PROCESSOR_ARCHITEW6432
+ } else {
+ $env:PROCESSOR_ARCHITECTURE
+ }
+ switch ($architecture.ToUpperInvariant()) {
+ "AMD64" { return "windows-x64" }
+ "ARM64" { return "windows-arm64" }
+ default { throw "Unsupported Windows architecture: $architecture" }
+ }
+}
+
+function Assert-ReleaseVersion([string]$Value) {
+ $normalized = $Value.Trim().TrimStart("v")
+ if ($normalized -notmatch "^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$") {
+ throw "Invalid Prime Agent release version: $Value"
+ }
+ return $normalized
+}
+
+function Resolve-ReleaseVersion {
+ if ($Version) {
+ return Assert-ReleaseVersion $Version
+ }
+ if ($env:PRIME_AGENT_VERSION) {
+ return Assert-ReleaseVersion $env:PRIME_AGENT_VERSION
+ }
+ $channelUrl = "$DownloadBaseUrl/$Channel"
+ Write-Step "resolving the $Channel release"
+ $response = Invoke-WebRequest -Uri $channelUrl -UseBasicParsing -TimeoutSec 30
+ return Assert-ReleaseVersion $response.Content
+}
+
+function Add-UserPathEntry([string]$Entry) {
+ $userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
+ $entries = @($userPath -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ })
+ $present = $entries | Where-Object { [string]::Equals($_, $Entry, [System.StringComparison]::OrdinalIgnoreCase) }
+ if (-not $present) {
+ $newPath = (@($entries) + $Entry) -join ";"
+ [Environment]::SetEnvironmentVariable("PATH", $newPath, "User")
+ }
+ if (-not (($env:PATH -split ";") | Where-Object { [string]::Equals($_, $Entry, [System.StringComparison]::OrdinalIgnoreCase) })) {
+ $env:PATH = "$env:PATH;$Entry"
+ }
+}
+
+function Remove-UserPathEntry([string]$Entry) {
+ $userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
+ $entries = @($userPath -split ";" | ForEach-Object { $_.Trim() } | Where-Object {
+ $_ -and -not [string]::Equals($_, $Entry, [System.StringComparison]::OrdinalIgnoreCase)
+ })
+ [Environment]::SetEnvironmentVariable("PATH", ($entries -join ";"), "User")
+}
+
+function Get-ExpectedChecksum([string]$ChecksumsPath, [string]$ArtifactName) {
+ foreach ($line in Get-Content -LiteralPath $ChecksumsPath) {
+ if ($line -match "^([0-9A-Fa-f]{64})\s+\*?(.+)$" -and $Matches[2].Trim() -eq $ArtifactName) {
+ return $Matches[1].ToLowerInvariant()
+ }
+ }
+ throw "SHA256SUMS does not contain $ArtifactName"
+}
+
+function Assert-ArchiveLayout([string]$Directory) {
+ $required = @(
+ "prime-agent.exe",
+ "package.json",
+ "README.md",
+ "CHANGELOG.md",
+ "install.sh",
+ "install.ps1",
+ "photon_rs_bg.wasm",
+ "prime-agent-runtime",
+ "skills",
+ "theme",
+ "assets",
+ "export-html",
+ "docs",
+ "examples"
+ )
+ foreach ($name in $required) {
+ if (-not (Test-Path -LiteralPath (Join-Path $Directory $name))) {
+ throw "Release archive is missing required sidecar: $name"
+ }
+ }
+}
+
+function Set-ActiveVersion([string]$VersionDirectory) {
+ New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
+ $binaryPath = Join-Path $VersionDirectory "prime-agent.exe"
+ $temporaryShim = Join-Path $BinDir "$CommandName.cmd.$([guid]::NewGuid().ToString("N")).tmp"
+ $shim = "@echo off`r`n`"$binaryPath`" %*`r`n"
+ $backupShim = "$CommandShim.backup"
+ try {
+ Set-Content -LiteralPath $temporaryShim -Value $shim -Encoding Ascii -NoNewline
+ if (Test-Path -LiteralPath $CommandShim) {
+ [System.IO.File]::Replace($temporaryShim, $CommandShim, $backupShim, $true)
+ Remove-Item -LiteralPath $backupShim -Force -ErrorAction SilentlyContinue
+ } else {
+ Move-Item -LiteralPath $temporaryShim -Destination $CommandShim
+ }
+ } finally {
+ Remove-Item -LiteralPath $temporaryShim -Force -ErrorAction SilentlyContinue
+ }
+ Add-UserPathEntry $BinDir
+}
+
+function Install-Release([string]$TargetVersion) {
+ $platform = Get-ReleasePlatform
+ $artifactName = "prime-agent-$TargetVersion-$platform.zip"
+ $releaseUrl = "$DownloadBaseUrl/releases/v$TargetVersion"
+ $versionDirectory = Join-Path $VersionsDir "v$TargetVersion"
+
+ if ((Test-Path -LiteralPath (Join-Path $versionDirectory "prime-agent.exe")) -and
+ (Test-Path -LiteralPath (Join-Path $versionDirectory "package.json"))) {
+ Set-ActiveVersion $versionDirectory
+ Write-Step "Prime Agent v$TargetVersion is active"
+ return
+ }
+
+ $temporaryRoot = Join-Path ([System.IO.Path]::GetTempPath()) "prime-agent-install-$([guid]::NewGuid().ToString("N"))"
+ $archivePath = Join-Path $temporaryRoot $artifactName
+ $checksumsPath = Join-Path $temporaryRoot "SHA256SUMS"
+ $stagingDirectory = Join-Path $temporaryRoot "staging"
+
+ New-Item -ItemType Directory -Path $temporaryRoot -Force | Out-Null
+ try {
+ Write-Step "downloading Prime Agent v$TargetVersion for $platform"
+ Invoke-WebRequest -Uri "$releaseUrl/$artifactName" -OutFile $archivePath -UseBasicParsing -TimeoutSec 120
+ Invoke-WebRequest -Uri "$releaseUrl/SHA256SUMS" -OutFile $checksumsPath -UseBasicParsing -TimeoutSec 30
+
+ $expectedHash = Get-ExpectedChecksum $checksumsPath $artifactName
+ $actualHash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant()
+ if ($actualHash -ne $expectedHash) {
+ throw "SHA-256 mismatch for ${artifactName}: expected $expectedHash, got $actualHash"
+ }
+
+ Expand-Archive -LiteralPath $archivePath -DestinationPath $stagingDirectory -Force
+ Assert-ArchiveLayout $stagingDirectory
+
+ New-Item -ItemType Directory -Path $VersionsDir -Force | Out-Null
+ if (Test-Path -LiteralPath $versionDirectory) {
+ Remove-Item -LiteralPath $versionDirectory -Recurse -Force
+ }
+ Move-Item -LiteralPath $stagingDirectory -Destination $versionDirectory
+ Set-ActiveVersion $versionDirectory
+ Write-Step "installed Prime Agent v$TargetVersion"
+ } finally {
+ Remove-Item -LiteralPath $temporaryRoot -Recurse -Force -ErrorAction SilentlyContinue
+ }
+}
+
+function Uninstall-PrimeAgent {
+ Remove-UserPathEntry $BinDir
+ Remove-Item -LiteralPath $InstallRoot -Recurse -Force -ErrorAction SilentlyContinue
+ Write-Step "uninstalled Prime Agent"
+}
+
+if ($Uninstall) {
+ Uninstall-PrimeAgent
+ exit 0
+}
+if ($DownloadBaseUrl -eq "__PRIME_AGENT_DOWNLOAD_BASE_URL__") {
+ throw "Installer download URL is not configured. Use the published installer or set PRIME_AGENT_DOWNLOAD_BASE_URL."
+}
+
+$targetVersion = Resolve-ReleaseVersion
+Install-Release $targetVersion
+if ($Update) {
+ Write-Step "update complete"
+}
+Write-Host "Run '$CommandName' in a new terminal. Git Bash is required for shell commands." -ForegroundColor Green
diff --git a/install.sh b/install.sh
index c1b5c52458..46a4f40693 100755
--- a/install.sh
+++ b/install.sh
@@ -13,10 +13,58 @@ if [ "$prime_agent_default_release_channel" = "$prime_agent_unconfigured_default
prime_agent_default_release_channel=stable
fi
prime_agent_release_channel="${PRIME_AGENT_RELEASE_CHANNEL:-$prime_agent_default_release_channel}"
-prime_agent_package="${PRIME_AGENT_PACKAGE:-prime-agent}"
prime_agent_cmd="${PRIME_AGENT_CMD:-prime-agent}"
+prime_agent_persisted_versions_dir=
+prime_agent_persisted_symlink=
+prime_agent_persisted_paths_invalid=0
+prime_agent_script_dir=
+case "$0" in
+ */*)
+ prime_agent_script_dir=$(CDPATH= cd -P "$(dirname "$0")" 2>/dev/null && pwd)
+ ;;
+esac
+if [ -n "$prime_agent_script_dir" ] && [ -f "$prime_agent_script_dir/.install-paths" ]; then
+ _persisted_versions=$(sed -n '1p' "$prime_agent_script_dir/.install-paths")
+ _persisted_symlink=$(sed -n '2p' "$prime_agent_script_dir/.install-paths")
+ _persisted_cmd=$(sed -n '3p' "$prime_agent_script_dir/.install-paths")
+ if [ -z "$_persisted_cmd" ]; then
+ _persisted_cmd=$(basename "$_persisted_symlink")
+ fi
+ _persisted_versions_physical=
+ _persisted_target_dir=
+ if [ -d "$_persisted_versions" ]; then
+ _persisted_versions_physical=$(CDPATH= cd -P "$_persisted_versions" 2>/dev/null && pwd)
+ fi
+ if [ -L "$_persisted_symlink" ]; then
+ _persisted_target=$(readlink "$_persisted_symlink" 2>/dev/null || printf '')
+ if [ -n "$_persisted_target" ] && [ -d "$(dirname "$_persisted_target")" ]; then
+ _persisted_target_dir=$(CDPATH= cd -P "$(dirname "$_persisted_target")" 2>/dev/null && pwd)
+ fi
+ fi
+ if [ -n "$_persisted_versions_physical" ] &&
+ [ "$_persisted_versions_physical" = "$(dirname "$prime_agent_script_dir")" ] &&
+ [ "$_persisted_target_dir" = "$prime_agent_script_dir" ] &&
+ [ "$(basename "$_persisted_symlink")" = "$_persisted_cmd" ]; then
+ prime_agent_persisted_versions_dir="$_persisted_versions_physical"
+ prime_agent_persisted_symlink="$_persisted_symlink"
+ if [ -z "${PRIME_AGENT_CMD:-}" ]; then
+ prime_agent_cmd="$_persisted_cmd"
+ fi
+ else
+ prime_agent_persisted_paths_invalid=1
+ fi
+fi
+prime_agent_default_data_home="${XDG_DATA_HOME:-${HOME:+$HOME/.local/share}}"
+prime_agent_default_bin_home="${XDG_BIN_HOME:-${HOME:+$HOME/.local/bin}}"
+prime_agent_binary_versions_dir="${PRIME_AGENT_VERSIONS_DIR:-${prime_agent_persisted_versions_dir:-${prime_agent_default_data_home:+$prime_agent_default_data_home/prime-agent/versions}}}"
+if [ -n "${PRIME_AGENT_BIN_DIR:-}" ]; then
+ prime_agent_binary_symlink="${PRIME_AGENT_BIN_DIR%/}/$prime_agent_cmd"
+elif [ -n "$prime_agent_persisted_symlink" ]; then
+ prime_agent_binary_symlink="$prime_agent_persisted_symlink"
+else
+ prime_agent_binary_symlink="${prime_agent_default_bin_home:+$prime_agent_default_bin_home/$prime_agent_cmd}"
+fi
prime_agent_esc=$(printf '\033')
-prime_agent_original_path="${PATH:-}"
prime_agent_reset="${prime_agent_esc}[0m"
prime_agent_bold="${prime_agent_esc}[1m"
prime_agent_italic="${prime_agent_esc}[3m"
@@ -33,7 +81,7 @@ prime_agent_color_dim="${prime_agent_esc}[38;2;113;113;122m"
prime_agent_color_primary="${prime_agent_esc}[38;2;127;91;213m"
prime_agent_color_scan="${prime_agent_esc}[38;2;14;165;233m"
prime_agent_color_warning="${prime_agent_esc}[38;2;245;158;11m"
-readonly prime_agent_unconfigured_base_url prime_agent_unconfigured_default_release_channel prime_agent_base_url prime_agent_default_release_channel prime_agent_release_channel prime_agent_package prime_agent_cmd prime_agent_esc prime_agent_original_path
+readonly prime_agent_unconfigured_base_url prime_agent_unconfigured_default_release_channel prime_agent_base_url prime_agent_default_release_channel prime_agent_release_channel prime_agent_cmd prime_agent_esc
readonly prime_agent_reset prime_agent_bold prime_agent_italic prime_agent_hide_cursor prime_agent_show_cursor prime_agent_home_cursor prime_agent_clear_screen prime_agent_clear_line
readonly prime_agent_sync_start prime_agent_sync_end
readonly prime_agent_color_text prime_agent_color_muted prime_agent_color_dim prime_agent_color_primary prime_agent_color_scan prime_agent_color_warning
@@ -51,12 +99,13 @@ prime_agent_screen_layout_lab_width=0
prime_agent_screen_render_lab_width=0
prime_agent_screen_compact=0
prime_agent_download_dir=
-prime_agent_bootstrap_kernel_on_install=0
prime_agent_screen_title=
prime_agent_screen_status=
prime_agent_screen_detail=
-prime_agent_screen_question=
prime_agent_animation_frame=0
+prime_agent_binary_rollback_version=
+prime_agent_binary_lock_dir=
+prime_agent_is_update=0
main() {
if [ "$prime_agent_base_url" = "$prime_agent_unconfigured_base_url" ]; then
@@ -64,82 +113,55 @@ main() {
printf 'Set PRIME_AGENT_DOWNLOAD_BASE_URL or use the installer published by the release workflow.\n' >&2
exit 1
fi
-
- prime_agent_install_traps
- prime_agent_init_screen
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Installing Prime Agent" "" "" ""
- else
- printf '\n\033[1m Installing Prime Agent\033[0m\n\033[2m npm global install\033[0m\n\n'
- fi
-
- start_preflight_checks
-
- if finish_preflight_checks; then
- check_status=0
- else
- check_status=$?
+ if [ "$prime_agent_persisted_paths_invalid" = 1 ]; then
+ printf 'error: installed Prime Agent path metadata does not match this sidecar or active command.\n' >&2
+ exit 1
fi
-
- if [ "$check_status" -ne 0 ]; then
- if ! install_node_npm_interactive; then
- exit "$check_status"
- fi
-
- start_preflight_checks
- if finish_preflight_checks; then
- check_status=0
- else
- check_status=$?
- fi
-
- if [ "$check_status" -ne 0 ]; then
- exit "$check_status"
- fi
+ if [ -z "$prime_agent_binary_versions_dir" ] || [ -z "$prime_agent_binary_symlink" ]; then
+ printf 'error: HOME is not set; set HOME or explicit PRIME_AGENT_VERSIONS_DIR and PRIME_AGENT_BIN_DIR paths.\n' >&2
+ exit 1
fi
- version="$(resolve_prime_agent_version "$@")"
- tarball_name="$prime_agent_package-$version.tgz"
- tarball_url="$prime_agent_base_url/releases/v$version/$tarball_name"
-
- confirm_install "$version" "$tarball_url"
- confirm_kernel_runtime_setup
-
- download_dir=$(create_temp_dir)
- prime_agent_download_dir="$download_dir"
- tarball_path="$download_dir/$tarball_name"
+ prime_agent_is_update=0
+ _prime_agent_positional=
+ for _prime_agent_arg in "$@"; do
+ case "$_prime_agent_arg" in
+ --method=*)
+ printf 'error: --method is no longer supported; Prime Agent installs as a compiled Bun binary.\n' >&2
+ exit 1
+ ;;
+ --update)
+ prime_agent_is_update=1
+ ;;
+ *)
+ if [ -z "$_prime_agent_positional" ]; then
+ _prime_agent_positional="$_prime_agent_arg"
+ else
+ _prime_agent_positional="$_prime_agent_positional $_prime_agent_arg"
+ fi
+ ;;
+ esac
+ done
- download_prime_agent_package "$version" "$tarball_url" "$tarball_path"
- install_prime_agent_package "$tarball_path"
- rm -rf "$download_dir"
- prime_agent_download_dir=
+ prime_agent_install_traps
+ prime_agent_init_screen
- if [ "${PRIME_AGENT_NODE_INSTALLED_STANDALONE:-0}" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "Checking your shell PATH." ""
- configure_standalone_node_path
- elif command -v "$prime_agent_cmd" >/dev/null 2>&1; then
+ if [ "$prime_agent_is_update" = 1 ]; then
if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "Run it with: $prime_agent_cmd" ""
+ prime_agent_screen "Updating Prime Agent" "" "" ""
else
- printf '\nPrime Agent was installed successfully.\n'
- printf '\nRun it with: %s\n' "$prime_agent_cmd"
+ printf '\n\033[1m Updating Prime Agent\033[0m\n'
fi
- else
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "PATH update needed for $prime_agent_cmd." ""
- prime_agent_restore_terminal
- else
- printf '\nPrime Agent was installed successfully.\n'
- fi
- cat <' "$prime_agent_screen_title" ;;
- *) printf '%s %s' "$prime_agent_screen_title" "$prime_agent_screen_question" ;;
- esac
-}
-
prime_agent_set_lab_line() {
lab_row="$1"
prime_agent_lab_width="$prime_agent_screen_render_lab_width"
@@ -646,37 +653,6 @@ prime_agent_print_centered_line() {
fi
}
-prime_agent_place_prompt_cursor() {
- max_width=$((prime_agent_screen_cols - 4))
- if [ "$max_width" -lt 1 ]; then
- max_width=1
- fi
- prompt_text=$(prime_agent_fit_ascii "$(prime_agent_screen_primary_text)" "$max_width")
- prompt_width=${#prompt_text}
- content_height=$(prime_agent_content_height)
- top=$(((prime_agent_screen_rows - content_height) / 2))
- if [ "$top" -lt 0 ]; then
- top=0
- fi
- prompt_index=0
- if prime_agent_show_logo; then
- prompt_index=$((prompt_index + 15))
- fi
- row=$((top + prompt_index + 1))
- col=$(((prime_agent_screen_cols - prompt_width) / 2 + prompt_width + 2))
- if [ "$col" -lt 1 ]; then
- col=1
- fi
- if [ "$col" -gt "$prime_agent_screen_cols" ]; then
- col="$prime_agent_screen_cols"
- fi
- if ( : <>/dev/tty ) 2>/dev/null; then
- printf '%s%s%s[%s;%sH' "$prime_agent_reset" "$prime_agent_show_cursor" "$prime_agent_esc" "$row" "$col" >/dev/tty
- else
- printf '%s%s%s[%s;%sH' "$prime_agent_reset" "$prime_agent_show_cursor" "$prime_agent_esc" "$row" "$col" >&2
- fi
-}
-
prime_agent_pulse() {
case $((prime_agent_screen_frame % 4)) in
0) printf '.' ;;
@@ -755,15 +731,6 @@ prime_agent_run_quiet_with_animation() {
prime_agent_run_quiet_with_animation_command "$title" "$status" "$detail" pulse "$@"
}
-prime_agent_run_quiet_with_animation_steps() {
- title="$1"
- status="$2"
- details="$3"
- shift 3
-
- prime_agent_run_quiet_with_animation_command "$title" "$status" "$details" static "$@"
-}
-
prime_agent_run_quiet_with_animation_command() {
title="$1"
status="$2"
@@ -805,122 +772,6 @@ prime_agent_run_quiet_with_animation_command() {
return "$command_status"
}
-prime_agent_prompt_yes_no() {
- question="$1"
- detail="$2"
- input_prompt="$3"
-
- if ( : <>/dev/tty ) 2>/dev/null; then
- prompt_input=tty
- exec 3<>/dev/tty
- elif [ -t 0 ]; then
- prompt_input=stdin
- else
- return 2
- fi
-
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "$question" "" "$detail" "$input_prompt"
- prime_agent_place_prompt_cursor "$input_prompt"
- else
- printf '%s\n' "$detail"
- if [ "$prompt_input" = tty ]; then
- printf '%s ' "$input_prompt" >&3
- else
- printf '%s ' "$input_prompt" >&2
- fi
- fi
-
- if [ "$prompt_input" = tty ]; then
- if ! IFS= read -r answer <&3; then
- answer=
- fi
- exec 3>&-
- else
- if ! IFS= read -r answer; then
- answer=
- fi
- fi
-
- case "$answer" in
- n|N|no|NO)
- return 1
- ;;
- esac
- return 0
-}
-
-start_preflight_checks() {
- preflight_dir=$(create_temp_dir)
- preflight_file="$preflight_dir/preflight"
- run_preflight_checks >"$preflight_file" &
- preflight_pid=$!
-}
-
-finish_preflight_checks() {
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- while kill -0 "$preflight_pid" 2>/dev/null; do
- prime_agent_screen "Checking Node.js and npm$(prime_agent_pulse)" "" "" ""
- sleep 0.18
- done
- fi
-
- if wait "$preflight_pid"; then
- preflight_status=0
- else
- preflight_status=$?
- fi
-
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- if [ "$preflight_status" -ne 0 ]; then
- preflight_summary=$(sed -n '1p' "$preflight_file")
- prime_agent_screen "Node.js 20.6.0 or newer is required" "" "$preflight_summary" ""
- sleep 0.4
- elif [ -s "$preflight_file" ]; then
- preflight_summary="Existing $prime_agent_cmd command found on PATH."
- prime_agent_screen "Environment ready" "" "$preflight_summary" ""
- sleep 0.4
- fi
- else
- cat "$preflight_file"
- fi
- rm -rf "$preflight_dir"
- return "$preflight_status"
-}
-
-run_preflight_checks() {
- status=0
- yellow="${prime_agent_esc}[33m"
- reset="${prime_agent_esc}[0m"
-
- if command -v node >/dev/null 2>&1; then
- node_version=$(node --version)
- if ! node -e 'const [major, minor, patch] = process.versions.node.split(".").map(Number); process.exit(major > 20 || (major === 20 && (minor > 6 || (minor === 6 && patch >= 0))) ? 0 : 1)' >/dev/null; then
- printf 'error: Prime Agent requires Node.js 20.6.0 or newer. Found %s.\n' "$node_version"
- status=1
- fi
- else
- printf 'error: Node.js 20.6.0 or newer is required to install Prime Agent.\n'
- status=1
- fi
-
- if ! command -v npm >/dev/null 2>&1; then
- printf 'error: npm is required to install Prime Agent.\n'
- status=1
- fi
-
- if [ "$status" -ne 0 ]; then
- printf '\n'
- fi
-
- if prime_agent_path=$(command -v "$prime_agent_cmd" 2>/dev/null); then
- printf '%sExisting %s found at: %s%s\n' "$yellow" "$prime_agent_cmd" "$prime_agent_path" "$reset"
- printf '\n'
- fi
-
- return "$status"
-}
-
resolve_prime_agent_version() {
if [ "${1:-}" ]; then
case "$1" in
@@ -987,633 +838,615 @@ normalize_version() {
printf '%s' "$version"
}
-install_node_npm_interactive() {
- method=$(detect_node_install_method)
- case "$method" in
- homebrew) label="Homebrew" ;;
- apt) label="apt" ;;
- apk) label="apk" ;;
- standalone) label="standalone Node.js" ;;
- *)
- method=standalone
- label="standalone Node.js"
- ;;
- esac
-
- if prime_agent_prompt_yes_no \
- "Install Node.js and npm with $label?" \
- "Required before Prime Agent can be installed." \
- "Install? [Y/n]"; then
- install_node_npm "$method" "$label"
- return
- else
- prompt_status=$?
+detect_shell_profile() {
+ if [ -n "${PRIME_AGENT_SHELL_PROFILE:-}" ]; then
+ printf '%s' "$PRIME_AGENT_SHELL_PROFILE"
+ return 0
fi
- if [ "$prompt_status" -eq 2 ]; then
- printf 'No terminal detected; install Node.js 20.6.0 or newer and npm, then run this installer again.\n'
- else
- printf '\nInstall Node.js 20.6.0 or newer and npm, then run this installer again.\n'
+ if [ -z "${HOME:-}" ]; then
+ return 1
fi
- return 1
-}
-detect_node_install_method() {
- case "$(uname -s)" in
- Darwin)
- if command -v brew >/dev/null 2>&1; then
- printf 'homebrew'
- else
- printf 'standalone'
- fi
+ shell_name="${SHELL:-}"
+ shell_name="${shell_name##*/}"
+ case "$shell_name" in
+ zsh)
+ printf '%s/.zshrc' "${ZDOTDIR:-$HOME}"
;;
- Linux)
- if command -v apt-cache >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1 && apt_node_candidate_is_new_enough; then
- printf 'apt'
- elif command -v apk >/dev/null 2>&1 && apk_node_candidate_is_new_enough; then
- printf 'apk'
- else
- printf 'standalone'
- fi
+ bash)
+ printf '%s/.bashrc' "$HOME"
;;
*)
- printf 'standalone'
+ if [ -f "$HOME/.zshrc" ]; then
+ printf '%s/.zshrc' "$HOME"
+ elif [ -f "$HOME/.bashrc" ]; then
+ printf '%s/.bashrc' "$HOME"
+ else
+ printf '%s/.profile' "$HOME"
+ fi
;;
esac
}
-apt_node_candidate_is_new_enough() {
- version=$(apt-cache policy nodejs 2>/dev/null | awk '/Candidate:/ { print $2; exit }')
- [ -n "$version" ] && [ "$version" != "(none)" ] && node_version_string_is_new_enough "$version"
-}
-
-apk_node_candidate_is_new_enough() {
- version=$(apk search -x nodejs 2>/dev/null | awk -F- '/^nodejs-/ { print $2; exit }')
- [ -n "$version" ] && node_version_string_is_new_enough "$version"
-}
-
-node_version_string_is_new_enough() {
- version="${1#v}"
- case "$version" in
- [0-9]*) ;;
- *) return 1 ;;
+prime_agent_run_checksum_check() {
+ checksum_dir="$1"
+ selected_checksums_name="$2"
+ checker="$3"
+ case "$checker" in
+ sha256sum)
+ (cd "$checksum_dir" && sha256sum -c "$selected_checksums_name")
+ ;;
+ shasum)
+ (cd "$checksum_dir" && shasum -a 256 -c "$selected_checksums_name")
+ ;;
esac
- version="${version%%[!0-9.]*}"
- version_ifs=${IFS- }
- IFS=.
- set -- $version
- IFS=$version_ifs
- major="${1:-}"
- minor="${2:-0}"
- patch="${3:-0}"
- case "$major" in ''|*[!0-9]*) return 1 ;; esac
- case "$minor" in ''|*[!0-9]*) minor=0 ;; esac
- case "$patch" in ''|*[!0-9]*) patch=0 ;; esac
-
- [ "$major" -gt 20 ] && return 0
- [ "$major" -eq 20 ] && [ "$minor" -gt 6 ] && return 0
- [ "$major" -eq 20 ] && [ "$minor" -eq 6 ] && [ "$patch" -ge 0 ] && return 0
- return 1
}
-install_node_npm() {
- method="$1"
- label="$2"
-
- if [ "$prime_agent_screen_enabled" != 1 ]; then
- printf '\nInstalling Node.js and npm with %s...\n\n' "$label"
- run_node_install_method "$method"
- else
- prepare_sudo_for_node_install "$method"
- node_install_details="Using $label.
-Resolving Node.js packages.
-Downloading Node.js runtime.
-Installing npm.
-Preparing Prime Agent setup."
- prime_agent_run_quiet_with_animation_steps \
- "Installing Node.js and npm" \
- "Installing Node.js and npm" \
- "$node_install_details" \
- run_node_install_method "$method"
- fi
-
- if [ "$method" = standalone ]; then
- load_standalone_node
- PRIME_AGENT_NODE_INSTALLED_STANDALONE=1
- fi
- hash -r
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Node.js and npm installed" "" "Continuing Prime Agent setup." ""
- else
- printf '\nNode.js and npm are installed.\n\n'
- fi
-}
-
-node_install_needs_sudo() {
- if [ "${EUID:-$(id -u)}" -eq 0 ]; then
- return 1
- fi
-
- case "$1" in
- apt|apk)
- return 0
- ;;
- standalone)
- [ "$(uname -s)" = Linux ] || return 1
- command -v xz >/dev/null 2>&1 && return 1
- command -v apt-get >/dev/null 2>&1 || command -v apk >/dev/null 2>&1
+prime_agent_detect_binary_platform() {
+ _os=$(uname -s)
+ _arch=$(uname -m)
+ case "$_os" in
+ Darwin)
+ case "$_arch" in
+ x86_64|amd64) printf 'darwin-x64' ;;
+ arm64|aarch64) printf 'darwin-arm64' ;;
+ *) printf 'error: unsupported macOS architecture for binary install: %s\n' "$_arch" >&2; return 1 ;;
+ esac
;;
- *)
- return 1
+ Linux)
+ if [ -f /etc/alpine-release ] || { command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; }; then
+ printf 'error: Prime Agent compiled binaries require glibc Linux and do not support musl systems.\n' >&2
+ return 1
+ fi
+ case "$_arch" in
+ x86_64|amd64) printf 'linux-x64' ;;
+ arm64|aarch64) printf 'linux-arm64' ;;
+ *) printf 'error: unsupported Linux architecture for binary install: %s\n' "$_arch" >&2; return 1 ;;
+ esac
;;
+ *) printf 'error: unsupported platform for binary install: %s %s\n' "$_os" "$_arch" >&2; return 1 ;;
esac
}
-prepare_sudo_for_node_install() {
- method="$1"
- if ! node_install_needs_sudo "$method"; then
- return 0
- fi
-
- prime_agent_screen "Preparing Node.js install" "" "This may ask for your sudo password." ""
- prime_agent_restore_terminal
- printf '\n'
- sudo -v
+prime_agent_binary_artifact_name() {
+ _version="$1"
+ _platform="$2"
+ printf 'prime-agent-%s-%s.tar.gz' "$_version" "$_platform"
}
-run_node_install_method() {
- case "$1" in
- homebrew) install_node_with_homebrew ;;
- apt) install_node_with_apt ;;
- apk) install_node_with_apk ;;
- standalone) install_node_standalone ;;
- esac
+prime_agent_binary_target_version_dir() {
+ printf '%s/v%s' "$prime_agent_binary_versions_dir" "$1"
}
-install_node_with_homebrew() {
- if brew list node >/dev/null 2>&1; then
- brew upgrade node
- else
- brew install node
+prime_agent_binary_smoke_binary() {
+ _binary="$1"
+ if [ ! -x "$_binary" ]; then
+ return 1
+ fi
+ if ! "$_binary" --version >/dev/null 2>&1; then
+ return 1
fi
}
-install_node_with_apt() {
- print_sudo_note
- if [ "${EUID:-$(id -u)}" -eq 0 ]; then
- apt-get update
- apt-get install -y nodejs npm
- else
- sudo sh -c 'apt-get update && apt-get install -y nodejs npm'
+prime_agent_binary_validate_layout() {
+ _root="$1"
+ _binary="$2"
+ if ! prime_agent_binary_smoke_binary "$_binary"; then
+ printf 'error: the downloaded Prime Agent binary did not run correctly.\n' >&2
+ return 1
fi
+ for _relative_path in \
+ package.json README.md CHANGELOG.md install.sh photon_rs_bg.wasm \
+ prime-agent-runtime/pyproject.toml \
+ theme/prime.json theme/dark.json theme/light.json theme/theme-schema.json \
+ export-html/template.html export-html/template.css export-html/template.js; do
+ if [ ! -f "$_root/$_relative_path" ]; then
+ printf 'error: release archive is missing required sidecar: %s\n' "$_relative_path" >&2
+ return 1
+ fi
+ done
+ for _relative_dir in skills assets docs examples export-html/vendor; do
+ if [ ! -d "$_root/$_relative_dir" ]; then
+ printf 'error: release archive is missing required sidecar directory: %s\n' "$_relative_dir" >&2
+ return 1
+ fi
+ done
}
-install_node_with_apk() {
- print_sudo_note
- run_with_sudo apk add --update-cache nodejs npm
+prime_agent_binary_canonicalize_install_paths() {
+ mkdir -p "$prime_agent_binary_versions_dir"
+ prime_agent_binary_versions_dir=$(CDPATH= cd -P "$prime_agent_binary_versions_dir" && pwd)
+ _link_dir=$(dirname "$prime_agent_binary_symlink")
+ _link_name=$(basename "$prime_agent_binary_symlink")
+ mkdir -p "$_link_dir"
+ _link_dir=$(CDPATH= cd -P "$_link_dir" && pwd)
+ prime_agent_binary_symlink="$_link_dir/$_link_name"
}
-install_node_standalone() {
- node_platform=$(detect_node_binary_platform) || {
- printf 'Unsupported operating system for automatic Node.js install: %s\n' "$(uname -s)"
- return 1
- }
- node_arch=$(detect_node_binary_arch) || {
- printf 'Unsupported CPU architecture for automatic Node.js install: %s\n' "$(uname -m)"
- return 1
- }
- node_dist_base="https://nodejs.org/dist/latest-v22.x"
- node_base_dir=$(node_standalone_base_dir)
- node_tmp_dir=$(create_temp_dir)
-
- mkdir -p "$node_tmp_dir" "$node_base_dir"
-
- printf 'Resolving Node.js binary for %s-%s\n' "$node_platform" "$node_arch"
- curl -fsSL "$node_dist_base/SHASUMS256.txt" -o "$node_tmp_dir/SHASUMS256.txt"
- node_file=$(awk -v suffix="-$node_platform-$node_arch.tar.xz" '
- index($2, "node-v") == 1 && length($2) >= length(suffix) && substr($2, length($2) - length(suffix) + 1) == suffix { print $2; exit }
- ' "$node_tmp_dir/SHASUMS256.txt")
- if [ -z "$node_file" ]; then
- printf 'No Node.js binary is available for %s-%s.\n' "$node_platform" "$node_arch"
- rm -rf "$node_tmp_dir"
- return 1
- fi
- case "$node_file" in
- */*|*\\*|*..*)
- printf 'Unsafe Node.js archive name in checksum manifest: %s\n' "$node_file"
- rm -rf "$node_tmp_dir"
- return 1
- ;;
- node-v*-"$node_platform"-"$node_arch".tar.xz) ;;
- *)
- printf 'Unexpected Node.js archive name in checksum manifest: %s\n' "$node_file"
- rm -rf "$node_tmp_dir"
+prime_agent_binary_acquire_lock() {
+ _versions_dir="$1"
+ _lock_root="$_versions_dir/.install-locks"
+ _timeout="${PRIME_AGENT_INSTALL_LOCK_TIMEOUT_SECONDS:-60}"
+ case "$_timeout" in
+ ''|*[!0-9]*)
+ printf 'error: PRIME_AGENT_INSTALL_LOCK_TIMEOUT_SECONDS must be a non-negative integer.\n' >&2
return 1
;;
esac
+ mkdir -p "$_lock_root"
+ rm -rf "$_lock_root/choosing-$$"
+ for _entry in "$_lock_root"/*-"$$"; do
+ [ -d "$_entry" ] || continue
+ rm -rf "$_entry"
+ done
+ _choosing_dir="$_lock_root/choosing-$$"
+ mkdir "$_choosing_dir"
+ prime_agent_binary_lock_dir="$_choosing_dir"
+
+ _max_ticket=0
+ for _entry in "$_lock_root"/*-*; do
+ [ -d "$_entry" ] || continue
+ _entry_name=$(basename "$_entry")
+ case "$_entry_name" in choosing-*) continue ;; esac
+ _entry_ticket=${_entry_name%%-*}
+ _entry_pid=${_entry_name#*-}
+ case "$_entry_ticket:$_entry_pid" in *[!0-9:]*|:*) continue ;; esac
+ if ! kill -0 "$_entry_pid" 2>/dev/null; then
+ rm -rf "$_entry"
+ continue
+ fi
+ if [ "$_entry_ticket" -gt "$_max_ticket" ]; then
+ _max_ticket="$_entry_ticket"
+ fi
+ done
+ _my_ticket=$((_max_ticket + 1))
+ while :; do
+ _my_contender="$_lock_root/$_my_ticket-$$"
+ if mkdir "$_my_contender" 2>/dev/null; then
+ break
+ fi
+ _my_ticket=$((_my_ticket + 1))
+ done
+ rm -rf "$_choosing_dir"
+ prime_agent_binary_lock_dir="$_my_contender"
- printf 'Downloading Node.js %s\n' "${node_file%.tar.xz}"
- curl -fsSL "$node_dist_base/$node_file" -o "$node_tmp_dir/$node_file"
- verify_node_standalone_download "$node_tmp_dir" "$node_file"
- ensure_node_standalone_extract_tools "$node_platform"
-
- node_dir="$node_base_dir/${node_file%.tar.xz}"
- rm -rf "$node_dir"
- printf 'Extracting Node.js to %s\n' "$node_dir"
- tar -xf "$node_tmp_dir/$node_file" -C "$node_base_dir"
- rm -f "$node_base_dir/current"
- ln -s "$node_dir" "$node_base_dir/current"
- rm -rf "$node_tmp_dir"
- printf 'Node.js installed at %s\n' "$node_dir"
+ _waited=0
+ while :; do
+ _blocked=0
+ for _entry in "$_lock_root"/choosing-*; do
+ [ -d "$_entry" ] || continue
+ _entry_pid=${_entry##*-}
+ case "$_entry_pid" in ''|*[!0-9]*) rm -rf "$_entry"; continue ;; esac
+ if kill -0 "$_entry_pid" 2>/dev/null; then
+ _blocked=1
+ else
+ rm -rf "$_entry"
+ fi
+ done
+ for _entry in "$_lock_root"/*-*; do
+ [ -d "$_entry" ] || continue
+ _entry_name=$(basename "$_entry")
+ case "$_entry_name" in choosing-*|"$_my_ticket-$$") continue ;; esac
+ _entry_ticket=${_entry_name%%-*}
+ _entry_pid=${_entry_name#*-}
+ case "$_entry_ticket:$_entry_pid" in *[!0-9:]*|:*) continue ;; esac
+ if ! kill -0 "$_entry_pid" 2>/dev/null; then
+ rm -rf "$_entry"
+ continue
+ fi
+ if [ "$_entry_ticket" -lt "$_my_ticket" ] ||
+ { [ "$_entry_ticket" -eq "$_my_ticket" ] && [ "$_entry_pid" -lt "$$" ]; }; then
+ _blocked=1
+ fi
+ done
+ if [ "$_blocked" = 0 ]; then
+ return 0
+ fi
+ if [ "$_waited" -ge "$_timeout" ]; then
+ printf 'error: timed out waiting for another Prime Agent install or update to finish.\n' >&2
+ return 1
+ fi
+ sleep 1
+ _waited=$((_waited + 1))
+ done
}
-verify_node_standalone_download() {
- checksum_dir="$1"
- checksum_file_name="$2"
- awk -v file="$checksum_file_name" '$2 == file { print }' "$checksum_dir/SHASUMS256.txt" >"$checksum_dir/SHASUMS256.selected"
+prime_agent_binary_write_install_paths() {
+ _version_dir="$1"
+ _state_path="$_version_dir/.install-paths"
+ _state_tmp="${_state_path}.tmp.$$"
+ printf '%s\n%s\n%s\n' "$prime_agent_binary_versions_dir" "$prime_agent_binary_symlink" "$prime_agent_cmd" > "$_state_tmp"
+ mv -f "$_state_tmp" "$_state_path"
+}
- if command -v sha256sum >/dev/null 2>&1; then
- printf 'Verifying Node.js download\n'
- (cd "$checksum_dir" && sha256sum -c SHASUMS256.selected)
- elif command -v shasum >/dev/null 2>&1; then
- printf 'Verifying Node.js download\n'
- (cd "$checksum_dir" && shasum -a 256 -c SHASUMS256.selected)
- else
- printf 'error: sha256sum or shasum is required to verify the Node.js download.\n'
+prime_agent_binary_atomic_symlink() {
+ _target="$1"
+ _link="$2"
+ _target_dir=$(cd "$(dirname "$_target")" 2>/dev/null && pwd -P) || return 1
+ _target="$_target_dir/$(basename "$_target")"
+ # Create parent dir if needed
+ _link_dir=$(dirname "$_link")
+ if [ ! -d "$_link_dir" ]; then
+ mkdir -p "$_link_dir"
+ fi
+ if [ -d "$_link" ] && [ ! -L "$_link" ]; then
+ printf 'error: cannot activate Prime Agent because the command path is a directory: %s\n' "$_link" >&2
return 1
fi
+ # Atomic replacement with temp symlink
+ _tmp="${_link}.tmp.$$"
+ ln -sf "$_target" "$_tmp"
+ mv -f "$_tmp" "$_link"
}
-ensure_node_standalone_extract_tools() {
- extract_platform="$1"
-
- if [ "$extract_platform" = linux ] && ! command -v xz >/dev/null 2>&1; then
- printf 'Installing xz-utils for Node.js archive extraction\n'
- print_sudo_note
- if command -v apt-get >/dev/null 2>&1; then
- run_with_sudo apt-get update
- run_with_sudo apt-get install -y xz-utils
- elif command -v apk >/dev/null 2>&1; then
- run_with_sudo apk add --update-cache xz
- else
- printf 'xz is required to extract Node.js. Install xz and run this installer again.\n'
- return 1
+prime_agent_binary_fresh_install() {
+ _version=
+ _version="$(resolve_prime_agent_version "$@")"
+ _platform=
+ if ! _platform=$(prime_agent_detect_binary_platform); then
+ exit 1
+ fi
+ _artifact_name="$(prime_agent_binary_artifact_name "$_version" "$_platform")"
+ _artifact_url="$prime_agent_base_url/releases/v$_version/$_artifact_name"
+ prime_agent_binary_canonicalize_install_paths
+ _versions_dir="$prime_agent_binary_versions_dir"
+ _version_dir="$(prime_agent_binary_target_version_dir "$_version")"
+
+ _download_dir=$(create_temp_dir)
+ prime_agent_download_dir="$_download_dir"
+ _artifact_path="$_download_dir/$_artifact_name"
+
+ prime_agent_binary_acquire_lock "$_versions_dir"
+
+ # Version directories are immutable. Reuse a healthy existing install instead
+ # of replacing files underneath the active command symlink.
+ _existing_binary="$_version_dir/$prime_agent_cmd"
+ if [ ! -x "$_existing_binary" ] && [ -x "$_version_dir/pi" ]; then
+ _existing_binary="$_version_dir/pi"
+ fi
+ if [ ! -x "$_existing_binary" ] && [ -x "$_version_dir/prime-agent" ]; then
+ _existing_binary="$_version_dir/prime-agent"
+ fi
+ if [ -x "$_existing_binary" ] && prime_agent_binary_validate_layout "$_version_dir" "$_existing_binary"; then
+ prime_agent_binary_write_install_paths "$_version_dir"
+ prime_agent_binary_atomic_symlink "$_existing_binary" "$prime_agent_binary_symlink"
+ if prime_agent_binary_smoke_binary "$prime_agent_binary_symlink"; then
+ prime_agent_configure_binary_path "$_version"
+ return
fi
fi
-}
-load_standalone_node() {
- PRIME_AGENT_STANDALONE_NODE_BIN="$(node_standalone_base_dir)/current/bin"
- PATH="$PRIME_AGENT_STANDALONE_NODE_BIN:$PATH"
- export PRIME_AGENT_STANDALONE_NODE_BIN PATH
-}
+ prime_agent_run_quiet_with_animation "Downloading Prime Agent v$_version" "Downloading Prime Agent v$_version" "Fetching the compiled binary for $_platform." curl -fsSL "$_artifact_url" -o "$_artifact_path"
+
+ _checksums_url="$prime_agent_base_url/releases/v$_version/SHA256SUMS"
+ _checksums_path="$_download_dir/SHA256SUMS"
+ prime_agent_run_quiet_with_animation "Downloading checksums" "Downloading release checksums" "Prime Agent v$_version" curl -fsSL "$_checksums_url" -o "$_checksums_path"
+
+ prime_agent_verify_binary_checksum "$_checksums_path" "$_artifact_path"
-node_standalone_base_dir() {
- if [ -n "${XDG_DATA_HOME:-}" ]; then
- printf '%s/prime-agent-node' "$XDG_DATA_HOME"
+ # Extract to a clean versioned directory
+ rm -rf "$_version_dir"
+ mkdir -p "$_version_dir"
+ prime_agent_run_quiet_with_animation "Extracting Prime Agent" "Extracting Prime Agent v$_version" "Installing to $_version_dir" tar -xzf "$_artifact_path" -C "$_version_dir"
+
+ # Find the binary inside the extracted archive.
+ # The archive may contain a wrapper dir (e.g. pi/) or be flat.
+ _binary_path=
+ if [ -f "$_version_dir/pi" ]; then
+ _binary_path="$_version_dir/pi"
+ elif [ -f "$_version_dir/$prime_agent_cmd" ]; then
+ _binary_path="$_version_dir/$prime_agent_cmd"
+ elif [ -f "$_version_dir/prime-agent" ]; then
+ _binary_path="$_version_dir/prime-agent"
else
- printf '%s/.local/share/prime-agent-node' "$HOME"
+ for _d in "$_version_dir"/*/; do
+ _d="${_d%/}"
+ if [ -f "${_d}/pi" ]; then
+ _binary_path="${_d}/pi"
+ break
+ elif [ -f "${_d}/$prime_agent_cmd" ]; then
+ _binary_path="${_d}/$prime_agent_cmd"
+ break
+ elif [ -f "${_d}/prime-agent" ]; then
+ _binary_path="${_d}/prime-agent"
+ break
+ fi
+ done
fi
-}
-detect_node_binary_platform() {
- case "$(uname -s)" in
- Darwin) printf 'darwin' ;;
- Linux) printf 'linux' ;;
- *) return 1 ;;
- esac
-}
+ if [ -z "$_binary_path" ]; then
+ printf 'error: could not find the prime-agent binary in the downloaded artifact.\n' >&2
+ rm -rf "$_version_dir" "$_download_dir"
+ prime_agent_download_dir=
+ exit 1
+ fi
-detect_node_binary_arch() {
- case "$(uname -m)" in
- x86_64|amd64) printf 'x64' ;;
- arm64|aarch64) printf 'arm64' ;;
- armv7l) printf 'armv7l' ;;
- ppc64le) printf 'ppc64le' ;;
- s390x) printf 's390x' ;;
- *) return 1 ;;
- esac
-}
+ chmod +x "$_binary_path" 2>/dev/null || true
+ # Ensure bundled install.sh is executable (required sidecar for self-update)
+ if [ -f "$_version_dir/install.sh" ]; then
+ chmod +x "$_version_dir/install.sh" 2>/dev/null || true
+ fi
-print_sudo_note() {
- if [ "${EUID:-$(id -u)}" -ne 0 ]; then
- printf 'This may ask for your sudo password.\n\n'
+ # If the binary was inside a wrapper dir, move contents up to version_dir
+ _binary_dir=$(dirname "$_binary_path")
+ if [ "$_binary_dir" != "$_version_dir" ]; then
+ cp -R "$_binary_dir/." "$_version_dir/"
+ rm -rf "$_binary_dir"
+ _binary_path="$_version_dir/$(basename "$_binary_path")"
fi
-}
+ if ! prime_agent_binary_validate_layout "$_version_dir" "$_binary_path"; then
+ rm -rf "$_version_dir" "$_download_dir"
+ prime_agent_download_dir=
+ exit 1
+ fi
+ prime_agent_binary_write_install_paths "$_version_dir"
-run_with_sudo() {
- if [ "${EUID:-$(id -u)}" -eq 0 ]; then
- "$@"
- else
- sudo "$@"
+ # Create and verify the stable symlink. Restore a healthy prior target if the
+ # activated command cannot run through its public path.
+ _old_target=
+ if [ -L "$prime_agent_binary_symlink" ]; then
+ _old_target=$(readlink "$prime_agent_binary_symlink" 2>/dev/null || printf '')
+ fi
+ mkdir -p "$(dirname "$prime_agent_binary_symlink")"
+ prime_agent_binary_atomic_symlink "$_binary_path" "$prime_agent_binary_symlink"
+ if ! prime_agent_binary_smoke_binary "$prime_agent_binary_symlink"; then
+ _old_target_dir=
+ if [ -n "$_old_target" ]; then
+ _old_target_dir=$(dirname "$_old_target")
+ fi
+ if [ -n "$_old_target" ] && [ "$_old_target_dir" != "$_version_dir" ] &&
+ prime_agent_binary_smoke_binary "$_old_target"; then
+ prime_agent_binary_atomic_symlink "$_old_target" "$prime_agent_binary_symlink"
+ else
+ rm -f "$prime_agent_binary_symlink"
+ fi
+ rm -rf "$_version_dir" "$_download_dir"
+ prime_agent_download_dir=
+ printf 'error: the installed Prime Agent command did not run correctly.\n' >&2
+ exit 1
fi
+
+ rm -rf "$_download_dir"
+ prime_agent_download_dir=
+
+ prime_agent_configure_binary_path "$_version"
+ # The Python kernel will be bootstrapped on first ipython use.
}
-configure_standalone_node_path() {
- if original_prime_agent_path=$(resolve_prime_agent_with_original_path); then
- case "$original_prime_agent_path" in
- "$PRIME_AGENT_STANDALONE_NODE_BIN/"*)
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "Run it with: $prime_agent_cmd" ""
- else
- printf '\nRun it with: %s\n' "$prime_agent_cmd"
- fi
- return 0
- ;;
- esac
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "PATH update needed for $prime_agent_cmd." ""
- else
- printf '%s was installed, but your shell is not using that install yet.\n' "$prime_agent_cmd"
- printf 'Your shell currently resolves %s to: %s\n' "$prime_agent_cmd" "$original_prime_agent_path"
+prime_agent_binary_update() {
+ _update_version=
+ _update_version="$(resolve_prime_agent_version "$@")"
+ prime_agent_binary_canonicalize_install_paths
+ _versions_dir="$prime_agent_binary_versions_dir"
+ prime_agent_binary_acquire_lock "$_versions_dir"
+
+ # Read current version from the symlink target's package.json
+ _current_version=
+ if [ -L "$prime_agent_binary_symlink" ]; then
+ _symlink_target=$(readlink "$prime_agent_binary_symlink")
+ _pkg_dir=$(dirname "$_symlink_target")
+ if [ -f "$_pkg_dir/package.json" ]; then
+ _current_version=$(sed -n 's/.*"version": *"\([^"]*\)".*/\1/p' "$_pkg_dir/package.json" 2>/dev/null || printf '')
fi
- else
+ fi
+
+ if [ -n "$_current_version" ] && [ "$_current_version" = "$_update_version" ] &&
+ prime_agent_binary_validate_layout "$_pkg_dir" "$prime_agent_binary_symlink"; then
if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "PATH update needed for $prime_agent_cmd." ""
+ prime_agent_screen "Prime Agent is up to date" "" "v$_current_version" ""
else
- printf '%s was installed, but your shell is not using that install yet.\n' "$prime_agent_cmd"
+ printf '\nPrime Agent v%s is already installed.\n' "$_current_version"
fi
+ return
fi
- profile=$(detect_shell_profile) || {
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_restore_terminal
- printf '\n'
+ _platform=
+ if ! _platform=$(prime_agent_detect_binary_platform); then
+ exit 1
+ fi
+ _artifact_name="$(prime_agent_binary_artifact_name "$_update_version" "$_platform")"
+ _artifact_url="$prime_agent_base_url/releases/v$_update_version/$_artifact_name"
+ _version_dir="$(prime_agent_binary_target_version_dir "$_update_version")"
+ prime_agent_binary_rollback_version=
+ if [ -L "$prime_agent_binary_symlink" ]; then
+ _old_target=$(readlink "$prime_agent_binary_symlink" 2>/dev/null || printf '')
+ _old_version_dir=$(dirname "$_old_target" 2>/dev/null || printf '')
+ if [ "$_old_version_dir" != "$_version_dir" ]; then
+ prime_agent_binary_rollback_version="$_old_version_dir"
fi
- print_standalone_path_manual_instructions
- return 0
- }
+ fi
- if shell_profile_has_standalone_node_path "$profile"; then
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "Run: $(prime_agent_source_profile_command "$profile")" ""
+ _download_dir=$(create_temp_dir)
+ prime_agent_download_dir="$_download_dir"
+ _artifact_path="$_download_dir/$_artifact_name"
+
+ if [ "$prime_agent_screen_enabled" = 1 ]; then
+ if [ -n "$_current_version" ]; then
+ prime_agent_screen "Updating Prime Agent" "" "v$_current_version to v$_update_version" ""
else
- printf '%s already contains %s.\n' "$profile" "$PRIME_AGENT_STANDALONE_NODE_BIN"
- printf 'Restart your shell or run: %s\n' "$(prime_agent_source_profile_command "$profile")"
+ prime_agent_screen "Updating Prime Agent" "" "to v$_update_version" ""
fi
- return 0
fi
- prompt_add_standalone_node_path "$profile"
-}
+ prime_agent_run_quiet_with_animation "Downloading Prime Agent v$_update_version" "Downloading Prime Agent v$_update_version" "Fetching the compiled binary for $_platform." curl -fsSL "$_artifact_url" -o "$_artifact_path"
-resolve_prime_agent_with_original_path() {
- saved_path=$PATH
- PATH=$prime_agent_original_path
- if command -v "$prime_agent_cmd" 2>/dev/null; then
- status=0
+ _checksums_url="$prime_agent_base_url/releases/v$_update_version/SHA256SUMS"
+ _checksums_path="$_download_dir/SHA256SUMS"
+ prime_agent_run_quiet_with_animation "Downloading checksums" "Downloading release checksums" "Prime Agent v$_update_version" curl -fsSL "$_checksums_url" -o "$_checksums_path"
+
+ prime_agent_verify_binary_checksum "$_checksums_path" "$_artifact_path"
+
+ # Extract to a fresh version directory
+ rm -rf "$_version_dir"
+ mkdir -p "$_version_dir"
+ prime_agent_run_quiet_with_animation "Extracting Prime Agent" "Extracting Prime Agent v$_update_version" "Preparing the update." tar -xzf "$_artifact_path" -C "$_version_dir"
+
+ # Find the binary
+ _binary_path=
+ if [ -f "$_version_dir/pi" ]; then
+ _binary_path="$_version_dir/pi"
+ elif [ -f "$_version_dir/$prime_agent_cmd" ]; then
+ _binary_path="$_version_dir/$prime_agent_cmd"
+ elif [ -f "$_version_dir/prime-agent" ]; then
+ _binary_path="$_version_dir/prime-agent"
else
- status=$?
+ for _d in "$_version_dir"/*/; do
+ _d="${_d%/}"
+ if [ -f "${_d}/pi" ]; then
+ _binary_path="${_d}/pi"
+ break
+ elif [ -f "${_d}/$prime_agent_cmd" ]; then
+ _binary_path="${_d}/$prime_agent_cmd"
+ break
+ elif [ -f "${_d}/prime-agent" ]; then
+ _binary_path="${_d}/prime-agent"
+ break
+ fi
+ done
fi
- PATH=$saved_path
- return "$status"
-}
-detect_shell_profile() {
- if [ -n "${PRIME_AGENT_SHELL_PROFILE:-}" ]; then
- printf '%s' "$PRIME_AGENT_SHELL_PROFILE"
- return 0
- fi
- if [ -z "${HOME:-}" ]; then
- return 1
+ if [ -z "$_binary_path" ]; then
+ printf 'error: could not find the prime-agent binary in the downloaded artifact.\n' >&2
+ rm -rf "$_version_dir" "$_download_dir"
+ prime_agent_download_dir=
+ exit 1
fi
- shell_name="${SHELL:-}"
- shell_name="${shell_name##*/}"
- case "$shell_name" in
- zsh)
- printf '%s/.zshrc' "${ZDOTDIR:-$HOME}"
- ;;
- bash)
- printf '%s/.bashrc' "$HOME"
- ;;
- *)
- if [ -f "$HOME/.zshrc" ]; then
- printf '%s/.zshrc' "$HOME"
- elif [ -f "$HOME/.bashrc" ]; then
- printf '%s/.bashrc' "$HOME"
- else
- printf '%s/.profile' "$HOME"
- fi
- ;;
- esac
-}
-
-shell_profile_has_standalone_node_path() {
- profile="$1"
- [ -f "$profile" ] && grep -F "$PRIME_AGENT_STANDALONE_NODE_BIN" "$profile" >/dev/null 2>&1
-}
+ chmod +x "$_binary_path" 2>/dev/null || true
+ # Ensure bundled install.sh is executable (required sidecar for self-update)
+ if [ -f "$_version_dir/install.sh" ]; then
+ chmod +x "$_version_dir/install.sh" 2>/dev/null || true
+ fi
-prompt_add_standalone_node_path() {
- profile="$1"
- path_line=$(standalone_node_path_line)
+ # If the binary was inside a wrapper dir, move contents up
+ _binary_dir=$(dirname "$_binary_path")
+ if [ "$_binary_dir" != "$_version_dir" ]; then
+ cp -R "$_binary_dir/." "$_version_dir/"
+ rm -rf "$_binary_dir"
+ _binary_path="$_version_dir/$(basename "$_binary_path")"
+ fi
+ if ! prime_agent_binary_validate_layout "$_version_dir" "$_binary_path"; then
+ rm -rf "$_version_dir" "$_download_dir"
+ prime_agent_download_dir=
+ exit 1
+ fi
+ prime_agent_binary_write_install_paths "$_version_dir"
- if ! prime_agent_prompt_yes_no \
- "Add standalone Node.js to your PATH?" \
- "Updates $profile so future shells can run $prime_agent_cmd." \
- "Update PATH? [Y/n]"; then
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_restore_terminal
- printf '\n'
+ # Atomically switch the symlink, then verify the activated path. If activation
+ # fails, restore the previous immutable version before returning an error.
+ prime_agent_binary_atomic_symlink "$_binary_path" "$prime_agent_binary_symlink"
+ if ! prime_agent_binary_smoke_binary "$prime_agent_binary_symlink"; then
+ if prime_agent_binary_rollback; then
+ printf 'error: activation failed; restored the previous Prime Agent version.\n' >&2
+ else
+ rm -f "$prime_agent_binary_symlink"
+ printf 'error: activation failed and no healthy rollback version was available.\n' >&2
fi
- print_standalone_path_manual_instructions
- return 0
+ rm -rf "$_version_dir" "$_download_dir"
+ prime_agent_download_dir=
+ exit 1
fi
- mkdir -p "$(dirname "$profile")"
- {
- printf '\n# Prime Agent standalone Node.js\n'
- printf '%s\n' "$path_line"
- } >>"$profile"
+ rm -rf "$_download_dir"
+ prime_agent_download_dir=
+
if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Prime Agent installed" "" "Run: $(prime_agent_source_profile_command "$profile")" ""
+ prime_agent_screen "Prime Agent updated" "" "v$_update_version installed." ""
else
- printf 'Added %s to %s.\n' "$PRIME_AGENT_STANDALONE_NODE_BIN" "$profile"
- printf 'Restart your shell or run: %s\n' "$(prime_agent_source_profile_command "$profile")"
+ printf '\nPrime Agent was updated to v%s.\n' "$_update_version"
fi
-}
-
-print_standalone_path_manual_instructions() {
- printf 'Add this to your shell profile to use %s from new shells:\n\n' "$prime_agent_cmd"
- printf ' %s\n' "$(standalone_node_path_line)"
- printf '\nThen restart your shell and run: %s\n' "$prime_agent_cmd"
-}
-standalone_node_path_line() {
- printf 'export PATH="%s:$PATH"' "$PRIME_AGENT_STANDALONE_NODE_BIN"
+ prime_agent_configure_binary_path "$_update_version"
}
-prime_agent_shell_quote() {
- quoted=$(printf '%s' "$1" | sed "s/'/'\\\\''/g")
- printf "'%s'" "$quoted"
-}
-
-prime_agent_source_profile_command() {
- printf '. %s && %s' "$(prime_agent_shell_quote "$1")" "$prime_agent_cmd"
-}
-
-download_prime_agent_package() {
- version="$1"
- tarball_url="$2"
- tarball_path="$3"
- download_dir=$(dirname "$tarball_path")
- tarball_name=$(basename "$tarball_path")
- checksums_url="$prime_agent_base_url/releases/v$version/SHA256SUMS"
- checksums_path="$download_dir/SHA256SUMS"
-
- if ! command -v curl >/dev/null 2>&1; then
- printf 'error: curl is required to download Prime Agent.\n' >&2
- exit 1
+prime_agent_binary_rollback() {
+ if [ -z "$prime_agent_binary_rollback_version" ] || [ ! -d "$prime_agent_binary_rollback_version" ]; then
+ return 1
fi
-
- prime_agent_run_quiet_with_animation \
- "Downloading checksums" \
- "Downloading release checksums" \
- "Prime Agent v$version" \
- curl -fsSL "$checksums_url" -o "$checksums_path"
-
- prime_agent_run_quiet_with_animation \
- "Downloading Prime Agent" \
- "Downloading Prime Agent v$version" \
- "Fetching the verified package." \
- curl -fsSL "$tarball_url" -o "$tarball_path"
-
- verify_prime_agent_package_checksum "$checksums_path" "$tarball_path"
+ _rollback_binary=
+ if [ -f "$prime_agent_binary_rollback_version/pi" ]; then
+ _rollback_binary="$prime_agent_binary_rollback_version/pi"
+ elif [ -f "$prime_agent_binary_rollback_version/$prime_agent_cmd" ]; then
+ _rollback_binary="$prime_agent_binary_rollback_version/$prime_agent_cmd"
+ elif [ -f "$prime_agent_binary_rollback_version/prime-agent" ]; then
+ _rollback_binary="$prime_agent_binary_rollback_version/prime-agent"
+ fi
+ if [ -z "$_rollback_binary" ] || [ ! -x "$_rollback_binary" ]; then
+ return 1
+ fi
+ prime_agent_binary_atomic_symlink "$_rollback_binary" "$prime_agent_binary_symlink"
+ prime_agent_binary_smoke_binary "$prime_agent_binary_symlink"
}
-verify_prime_agent_package_checksum() {
- checksums_path="$1"
- tarball_path="$2"
- checksum_dir=$(dirname "$tarball_path")
- tarball_name=$(basename "$tarball_path")
- selected_checksums_path="$checksum_dir/SHA256SUMS.selected"
+prime_agent_verify_binary_checksum() {
+ _checksums_path="$1"
+ _artifact_path="$2"
+ _checksum_dir=$(dirname "$_artifact_path")
+ _artifact_name=$(basename "$_artifact_path")
+ _selected_checksums_path="$_checksum_dir/SHA256SUMS.selected"
- if ! awk -v file="$tarball_name" '$2 == file { print; found = 1; exit } END { if (!found) exit 1 }' \
- "$checksums_path" >"$selected_checksums_path"; then
- printf 'error: checksum for %s was not found in %s\n' "$tarball_name" "$checksums_path" >&2
+ if ! awk -v file="$_artifact_name" '$2 == file { print; found = 1; exit } END { if (!found) exit 1 }' "$_checksums_path" >"$_selected_checksums_path"; then
+ printf 'error: checksum for %s was not found in %s\n' "$_artifact_name" "$_checksums_path" >&2
exit 1
fi
if command -v sha256sum >/dev/null 2>&1; then
- prime_agent_run_quiet_with_animation \
- "Verifying download" \
- "Verifying Prime Agent download" \
- "Checking SHA-256." \
- prime_agent_run_checksum_check "$checksum_dir" "$(basename "$selected_checksums_path")" sha256sum
+ prime_agent_run_quiet_with_animation "Verifying download" "Verifying Prime Agent download" "Checking SHA-256." prime_agent_run_checksum_check "$_checksum_dir" "$(basename "$_selected_checksums_path")" sha256sum
elif command -v shasum >/dev/null 2>&1; then
- prime_agent_run_quiet_with_animation \
- "Verifying download" \
- "Verifying Prime Agent download" \
- "Checking SHA-256." \
- prime_agent_run_checksum_check "$checksum_dir" "$(basename "$selected_checksums_path")" shasum
+ prime_agent_run_quiet_with_animation "Verifying download" "Verifying Prime Agent download" "Checking SHA-256." prime_agent_run_checksum_check "$_checksum_dir" "$(basename "$_selected_checksums_path")" shasum
else
- printf 'error: sha256sum or shasum is required to verify the Prime Agent download.\n' >&2
+ printf 'error: sha256sum or shasum is required to verify the download.\n' >&2
exit 1
fi
}
-prime_agent_run_checksum_check() {
- checksum_dir="$1"
- selected_checksums_name="$2"
- checker="$3"
- case "$checker" in
- sha256sum)
- (cd "$checksum_dir" && sha256sum -c "$selected_checksums_name")
- ;;
- shasum)
- (cd "$checksum_dir" && shasum -a 256 -c "$selected_checksums_name")
- ;;
- esac
+prime_agent_shell_quote() {
+ printf "'"
+ printf '%s' "$1" | sed "s/'/'\\\\''/g"
+ printf "'"
}
-confirm_install() {
- version="$1"
- tarball_url="$2"
+prime_agent_configure_binary_path() {
+ _installed_version="${1:-}"
- if prime_agent_prompt_yes_no \
- "Install Prime Agent v$version globally with npm?" \
- "Downloads the verified release and runs npm install -g." \
- "Install? [Y/n]"; then
- return 0
- else
- prompt_status=$?
- fi
-
- if [ "$prompt_status" -eq 2 ]; then
- printf 'This will download, verify, and install:\n\n %s\n\n' "$tarball_url"
- printf 'No terminal detected; continuing without confirmation.\n'
- return 0
- fi
-
- if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Installation cancelled" "" "No changes were made." ""
- exit 0
- fi
- printf '\nInstallation cancelled.\n'
- exit 0
-}
-
-confirm_kernel_runtime_setup() {
- case "${PRIME_AGENT_BOOTSTRAP_KERNEL_ON_INSTALL:-}" in
- 1)
- prime_agent_bootstrap_kernel_on_install=1
- return
- ;;
- 0)
- prime_agent_bootstrap_kernel_on_install=0
+ if command -v "$prime_agent_cmd" >/dev/null 2>&1; then
+ _existing_path="$(command -v "$prime_agent_cmd")"
+ if [ "$_existing_path" = "$prime_agent_binary_symlink" ]; then
+ if [ "$prime_agent_screen_enabled" = 1 ]; then
+ prime_agent_screen "Prime Agent installed" "" "Run it with: $prime_agent_cmd" ""
+ else
+ printf '\nRun it with: %s\n' "$prime_agent_cmd"
+ fi
return
- ;;
- esac
-
- if prime_agent_prompt_yes_no \
- "Prepare Python runtime now?" \
- "Installs uv, Python 3.11, and the Prime Agent runtime." \
- "Prepare? [Y/n]"; then
- prime_agent_bootstrap_kernel_on_install=1
- return
- else
- prompt_status=$?
+ fi
fi
- if [ "$prompt_status" -eq 2 ]; then
- printf 'No terminal detected; preparing the Python runtime during install.\n'
- prime_agent_bootstrap_kernel_on_install=1
- return
+ _bin_dir=$(dirname "$prime_agent_binary_symlink")
+ if [ -n "${_existing_path:-}" ] && [ "$_existing_path" != "$prime_agent_binary_symlink" ]; then
+ printf 'warning: %s at %s currently shadows the new binary at %s.\n' \
+ "$prime_agent_cmd" "$_existing_path" "$prime_agent_binary_symlink" >&2
fi
- prime_agent_bootstrap_kernel_on_install=0
if [ "$prime_agent_screen_enabled" = 1 ]; then
- prime_agent_screen "Python setup skipped" "" "The runtime can be prepared on first ipython use." ""
- sleep 0.4
+ prime_agent_screen "Prime Agent installed" "" "PATH update needed for $prime_agent_cmd." ""
+ prime_agent_restore_terminal
else
- printf '\nSkipping Python runtime setup.\n'
+ printf '\nPrime Agent was installed to %s.\n' "$_bin_dir"
fi
-}
-install_prime_agent_package() {
- tarball_path="$1"
- if [ "$prime_agent_bootstrap_kernel_on_install" = 1 ]; then
- npm_install_details="Preparing global install.
-Linking command binaries.
-Installing runtime packages.
-Preloading search tools.
-Preparing Python kernel.
-Finalizing npm install."
- prime_agent_run_quiet_with_animation_steps \
- "Installing Prime Agent" \
- "Installing Prime Agent" \
- "$npm_install_details" \
- env PRIME_AGENT_BOOTSTRAP_TOOLS_ON_INSTALL=1 PRIME_AGENT_BOOTSTRAP_KERNEL_ON_INSTALL=1 PRIME_AGENT_INSTALL_UV=1 npm install -g --no-fund --no-audit --loglevel=error --progress=false "$tarball_path"
+ _profile=$(detect_shell_profile)
+ _quoted_bin_dir=$(prime_agent_shell_quote "$_bin_dir")
+ _quoted_cmd=$(prime_agent_shell_quote "$prime_agent_cmd")
+ if [ -n "$_profile" ] && [ -w "$_profile" ] 2>/dev/null; then
+ if ! grep -F -q -- "$_bin_dir" "$_profile" 2>/dev/null; then
+ printf '\nexport PATH=%s:"$PATH"\n' "$_quoted_bin_dir" >> "$_profile"
+ printf 'Added %s to %s.\n' "$_bin_dir" "$_profile"
+ fi
+ printf '\nRestart your shell or run: export PATH=%s:"$PATH" && %s\n' "$_quoted_bin_dir" "$_quoted_cmd"
else
- npm_install_details="Preparing global install.
-Linking command binaries.
-Installing runtime packages.
-Preloading search tools.
-Finalizing npm install."
- prime_agent_run_quiet_with_animation_steps \
- "Installing Prime Agent" \
- "Installing Prime Agent" \
- "$npm_install_details" \
- env PRIME_AGENT_BOOTSTRAP_TOOLS_ON_INSTALL=1 npm install -g --no-fund --no-audit --loglevel=error --progress=false "$tarball_path"
+ printf '\nAdd to your shell profile:\n'
+ printf ' export PATH=%s:"$PATH"\n' "$_quoted_bin_dir"
+ printf '\nThen restart your shell and run: %s\n' "$_quoted_cmd"
fi
}
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index b5fb6acd3e..0000000000
--- a/package-lock.json
+++ /dev/null
@@ -1,5837 +0,0 @@
-{
- "name": "prime-agent",
- "version": "0.9.1",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "prime-agent",
- "version": "0.9.1",
- "workspaces": [
- "packages/*",
- "packages/coding-agent/examples/extensions/with-deps",
- "packages/coding-agent/examples/extensions/custom-provider-anthropic",
- "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo",
- "packages/coding-agent/examples/extensions/sandbox"
- ],
- "dependencies": {
- "@earendil-works/pi-coding-agent": "^0.9.1",
- "get-east-asian-width": "^1.6.0"
- },
- "devDependencies": {
- "@anthropic-ai/sandbox-runtime": "^0.0.55",
- "@biomejs/biome": "2.5.5",
- "@types/node": "^22.10.5",
- "@typescript/native-preview": "7.0.0-dev.20260120.1",
- "concurrently": "^9.2.4",
- "husky": "^9.1.7",
- "jiti": "^2.7.0",
- "shx": "^0.4.0",
- "tsx": "^4.23.1",
- "typescript": "^7.0.2"
- },
- "engines": {
- "node": ">=22.8.0"
- }
- },
- "node_modules/@agentclientprotocol/sdk": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.3.0.tgz",
- "integrity": "sha512-i3h/efaeuMUFAO1HSfo97QZQnnvMd7wWBYtBsdL6UMZg3a78sk3Ffya5Xu7C7tYsXomXoDXJBAzQF2PcFKAhIQ==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "zod": "^3.25.0 || ^4.0.0"
- }
- },
- "node_modules/@anthropic-ai/sandbox-runtime": {
- "version": "0.0.55",
- "resolved": "https://registry.npmjs.org/@anthropic-ai/sandbox-runtime/-/sandbox-runtime-0.0.55.tgz",
- "integrity": "sha512-XGTLbzitn0y6OCdmqP5aYrBR0HlNi87ue++m9vMPxcNwaQeOj9i3f9CSWmXua8mv+Q8E55k4ZGlzJmpDHzQFBw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@pondwader/socks5-server": "^1.0.10",
- "commander": "^12.1.0",
- "node-forge": "^1.4.0",
- "shell-quote": "^1.8.4",
- "zod": "^3.24.1"
- },
- "bin": {
- "srt": "dist/cli.js"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@anthropic-ai/sdk": {
- "version": "0.91.1",
- "license": "MIT",
- "dependencies": {
- "json-schema-to-ts": "^3.1.1"
- },
- "bin": {
- "anthropic-ai-sdk": "bin/cli"
- },
- "peerDependencies": {
- "zod": "^3.25.0 || ^4.0.0"
- },
- "peerDependenciesMeta": {
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/@aws-sdk/client-bedrock-runtime": {
- "version": "3.1095.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1095.0.tgz",
- "integrity": "sha512-DWcwoQdQPrQJxnG3hz1sG88EjfzGv3SReRy4mtAO+pZXtLX+trriTa20o+qcTmVzwqS43JXtX26ythOzErev9A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/credential-provider-node": "^3.972.72",
- "@aws-sdk/eventstream-handler-node": "^3.972.30",
- "@aws-sdk/middleware-eventstream": "^3.972.25",
- "@aws-sdk/middleware-websocket": "^3.972.43",
- "@aws-sdk/token-providers": "3.1095.0",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/fetch-http-handler": "^5.6.10",
- "@smithy/node-http-handler": "^4.9.10",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/core": {
- "version": "3.977.1",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.1.tgz",
- "integrity": "sha512-KVtQRtc00ES/y+Sc3vYXeP6pCIcNlBJCZOwvqSy8ZpVGmbM5+IG+AfhuTKQ2oXmIVqZJewaGMMpzPkywC6xg0w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.974.2",
- "@aws-sdk/xml-builder": "^3.972.37",
- "@aws/lambda-invoke-store": "^0.3.0",
- "@smithy/core": "^3.29.8",
- "@smithy/signature-v4": "^5.6.9",
- "@smithy/types": "^4.16.1",
- "bowser": "^2.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.972.61",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.61.tgz",
- "integrity": "sha512-qihs2ekMb89Nxd2JenCgVFhjbkb3EIo7HEBCBzyZACKVJdrLUZBLOmAE3xr0Sayml8n/jZSzwO/IufIiIzO7PQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.972.63",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.63.tgz",
- "integrity": "sha512-yfozsS8wkWZEi/n6IsrodcFKBWZ0iNAezhJbTReMNc0z1Px17qdeAeuL1/wziCAmCZyXiW7QzP75ggJkBQv8jQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/fetch-http-handler": "^5.6.10",
- "@smithy/node-http-handler": "^4.9.10",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.973.6",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.6.tgz",
- "integrity": "sha512-jGLTW1bj148GL/6/IMlfY2fMYS9FtHOG+NahkFD4y0qkzYudNUahelxryY68/HGMslYuHClk1XaS/3b3eJzEkg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/credential-provider-env": "^3.972.61",
- "@aws-sdk/credential-provider-http": "^3.972.63",
- "@aws-sdk/credential-provider-login": "^3.972.68",
- "@aws-sdk/credential-provider-process": "^3.972.61",
- "@aws-sdk/credential-provider-sso": "^3.973.5",
- "@aws-sdk/credential-provider-web-identity": "^3.972.67",
- "@aws-sdk/nested-clients": "^3.997.35",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/credential-provider-imds": "^4.4.13",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-login": {
- "version": "3.972.68",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.68.tgz",
- "integrity": "sha512-w6tNci6g7RqFpLhj1f5xseBvaNojb4Pkgp5Jp5apl9hrJtaf2AA+rX9+qlhlWUK6kcyAFYPA7emO+55zj+S98Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/nested-clients": "^3.997.35",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.972.72",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.72.tgz",
- "integrity": "sha512-blQ7F5QGzylnzeh5549zQLoCAiMHkXFLjFovEMaVy4b2X8JhUu+u9NXro1hyK95YHdVFNmBHKs2hIHtZchxKlQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/credential-provider-env": "^3.972.61",
- "@aws-sdk/credential-provider-http": "^3.972.63",
- "@aws-sdk/credential-provider-ini": "^3.973.6",
- "@aws-sdk/credential-provider-process": "^3.972.61",
- "@aws-sdk/credential-provider-sso": "^3.973.5",
- "@aws-sdk/credential-provider-web-identity": "^3.972.67",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/credential-provider-imds": "^4.4.13",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.972.61",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.61.tgz",
- "integrity": "sha512-xzRuj+fUVO4nkafKQJVKAF97kGpeQbfjuwmRrtGZNf42/1dkmcz6o7dswBy7alY0htQn5sCL1GWQYEykviWZkA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.973.5",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.5.tgz",
- "integrity": "sha512-fZRjjWhLFelsDoOYjqShQTrIGYC3Pf9Mx9Czf+1ikfQDgktxjze33dVo1q1/ZQ+T0qbtejVoHNHrfD5aJVpv/w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/nested-clients": "^3.997.35",
- "@aws-sdk/token-providers": "3.1095.0",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.972.67",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.67.tgz",
- "integrity": "sha512-FTNZ05gkPBA6CKbU3N4zPgybV+stdazwMOya75CmGdcJL7p8Fw/BdHP8WVxJd0mvzyPK2cg/C3gli58Ir4HgCw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/nested-clients": "^3.997.35",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/eventstream-handler-node": {
- "version": "3.972.30",
- "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.30.tgz",
- "integrity": "sha512-hJboPgIpq5+ADc++/B9TBqn65CXV21cZLGB8V5RBQbxkZ/rQ6qMfcxTnW/SvQlasX4jhaSG8B1wsVjhQyDrsnQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-eventstream": {
- "version": "3.972.25",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.25.tgz",
- "integrity": "sha512-9SFbPzJDHHR5k6Q6KvXVas/veUm/TzNcNTFM2UhdXHZHpyIvI2lS+s4cxljw1BihGpVhsAkQDo/2nW7dHxpf4Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-websocket": {
- "version": "3.972.43",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.43.tgz",
- "integrity": "sha512-n29++15Vma64Kd0enp9Bo8a6LTm8TvUoMbJEwqXtIksv0oEs+SUCRMm3gozDfPD1Ly0k/sSBughxarlLlRF6Xw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/fetch-http-handler": "^5.6.10",
- "@smithy/signature-v4": "^5.6.9",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">= 14.0.0"
- }
- },
- "node_modules/@aws-sdk/nested-clients": {
- "version": "3.997.35",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.35.tgz",
- "integrity": "sha512-2MJfseVG/aXvIyOIBlYA/Oaf6qFDdsu4D8RKsEUdOQpVuLaor0BdxIBBtJLBNQQEe6Ku3YMvLljwb1MwVUpzRw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/signature-v4-multi-region": "^3.996.42",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/fetch-http-handler": "^5.6.10",
- "@smithy/node-http-handler": "^4.9.10",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/signature-v4-multi-region": {
- "version": "3.996.42",
- "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.42.tgz",
- "integrity": "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.974.2",
- "@smithy/signature-v4": "^5.6.9",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/token-providers": {
- "version": "3.1095.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1095.0.tgz",
- "integrity": "sha512-65SudS6y4nzaYHybtqcpm3sHe5jLhdMn68HRKS1nUx690BtQeaAQOoujQ+dpOjBATIVGVgKKjEP8tR+U06QJQA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "^3.977.0",
- "@aws-sdk/nested-clients": "^3.997.35",
- "@aws-sdk/types": "^3.974.2",
- "@smithy/core": "^3.29.8",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/types": {
- "version": "3.974.2",
- "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz",
- "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws-sdk/xml-builder": {
- "version": "3.972.37",
- "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.37.tgz",
- "integrity": "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@aws/lambda-invoke-store": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
- "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.29.2",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@biomejs/biome": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.5.tgz",
- "integrity": "sha512-r1S8nFsAG1MY+vJFZALzIvwXAJv6ejDQ0mxP21Tgr9YK3ZFtjrvbBwDdNhx1rUqvccEIeNg20cYCNzl6Cr69pQ==",
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "bin": {
- "biome": "bin/biome"
- },
- "engines": {
- "node": ">=14.21.3"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/biome"
- },
- "optionalDependencies": {
- "@biomejs/cli-darwin-arm64": "2.5.5",
- "@biomejs/cli-darwin-x64": "2.5.5",
- "@biomejs/cli-linux-arm64": "2.5.5",
- "@biomejs/cli-linux-arm64-musl": "2.5.5",
- "@biomejs/cli-linux-x64": "2.5.5",
- "@biomejs/cli-linux-x64-musl": "2.5.5",
- "@biomejs/cli-win32-arm64": "2.5.5",
- "@biomejs/cli-win32-x64": "2.5.5"
- }
- },
- "node_modules/@biomejs/cli-darwin-arm64": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.5.tgz",
- "integrity": "sha512-kUrAhXVWUrwmAUnV2iXSK7umxKFysTwvqK+Ty6ptUcLY/7T3SnCAjUowE4uvwaEej6nXZ7hu/dTtbokKdsPeag==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@biomejs/cli-darwin-x64": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.5.tgz",
- "integrity": "sha512-DamiYc5bUYZ2uxlfc+RLEPtz1Abb6PO5eTbOkufLpSGwd/7AMQAdxhFYiXmwwkJL8IsT8S7GvdgwDHqaMFAvKw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@biomejs/cli-linux-arm64": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.5.tgz",
- "integrity": "sha512-lRKF/pH/1RiYiBKExi3TCZVAtvzEm77aifrvcNiDFrR9WxeAnDUjDnseb6y2XV85mjitLs6SILGm2XG77cHtSQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@biomejs/cli-linux-arm64-musl": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.5.tgz",
- "integrity": "sha512-U4WMl/sy/E/Q73vf15VspakLRRs2LDFcCeBxJnQfXzssb88zpV6PJPaQ3ezhQ7H6Ht2/8bvuZeHgJWzmoxllZg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@biomejs/cli-linux-x64": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.5.tgz",
- "integrity": "sha512-H/O39nJEw/2Zm/fm7hrmxxoF8kK/aU1uCoPp70ruXVbomaAdLpJJnCmL11Q2JotT8QVHH06So04Oq53lCSwSwQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@biomejs/cli-linux-x64-musl": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.5.tgz",
- "integrity": "sha512-m7wC7tjX5Lrmo69dc4md8FeKpPU1NTCY1v7xUoQQ2vadWwNnBS0KZOG8471otFPHrTHihQJAjQPgMObpLvDe6A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@biomejs/cli-win32-arm64": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.5.tgz",
- "integrity": "sha512-7BryINPuYypLUAH3o/o5ZdgomJ4zn3EDR0ChZJst7n32S6ZhKbgHXuYydLu+YAnx59ehGFR0z/MG6qnzQi3Yyw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@biomejs/cli-win32-x64": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.5.tgz",
- "integrity": "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT OR Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=14.21.3"
- }
- },
- "node_modules/@borewit/text-codec": {
- "version": "0.2.2",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Borewit"
- }
- },
- "node_modules/@earendil-works/pi-agent-core": {
- "resolved": "packages/agent",
- "link": true
- },
- "node_modules/@earendil-works/pi-ai": {
- "resolved": "packages/ai",
- "link": true
- },
- "node_modules/@earendil-works/pi-coding-agent": {
- "resolved": "packages/coding-agent",
- "link": true
- },
- "node_modules/@earendil-works/pi-tui": {
- "resolved": "packages/tui",
- "link": true
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
- "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
- "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
- "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
- "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
- "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
- "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
- "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
- "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
- "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
- "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
- "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
- "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
- "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
- "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
- "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
- "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
- "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
- "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
- "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
- "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
- "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
- "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
- "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
- "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@google/genai": {
- "version": "1.52.0",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "google-auth-library": "^10.3.0",
- "p-retry": "^4.6.2",
- "protobufjs": "^7.5.4",
- "ws": "^8.18.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@modelcontextprotocol/sdk": "^1.25.2"
- },
- "peerDependenciesMeta": {
- "@modelcontextprotocol/sdk": {
- "optional": true
- }
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@mariozechner/clipboard": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz",
- "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@mariozechner/clipboard-darwin-arm64": "0.3.9",
- "@mariozechner/clipboard-darwin-universal": "0.3.9",
- "@mariozechner/clipboard-darwin-x64": "0.3.9",
- "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9",
- "@mariozechner/clipboard-linux-arm64-musl": "0.3.9",
- "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9",
- "@mariozechner/clipboard-linux-x64-gnu": "0.3.9",
- "@mariozechner/clipboard-linux-x64-musl": "0.3.9",
- "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9",
- "@mariozechner/clipboard-win32-x64-msvc": "0.3.9"
- }
- },
- "node_modules/@mariozechner/clipboard-darwin-arm64": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz",
- "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-darwin-universal": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz",
- "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==",
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-darwin-x64": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz",
- "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-linux-arm64-gnu": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz",
- "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-linux-arm64-musl": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz",
- "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-linux-riscv64-gnu": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz",
- "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-linux-x64-gnu": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz",
- "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-linux-x64-musl": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz",
- "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-win32-arm64-msvc": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz",
- "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mariozechner/clipboard-win32-x64-msvc": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz",
- "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@mistralai/mistralai": {
- "version": "2.2.1",
- "license": "Apache-2.0",
- "dependencies": {
- "ws": "^8.18.0",
- "zod": "^3.25.0 || ^4.0.0",
- "zod-to-json-schema": "^3.25.0"
- }
- },
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.0.tgz",
- "integrity": "sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@tybys/wasm-util": "^0.10.3"
- },
- "engines": {
- "node": "^20.19.0 || ^22.13.0 || >=23.5.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Brooooooklyn"
- },
- "peerDependencies": {
- "@emnapi/core": "^2.0.0-alpha.3",
- "@emnapi/runtime": "^2.0.0-alpha.3"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@oxc-project/types": {
- "version": "0.139.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
- "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/Boshen"
- }
- },
- "node_modules/@pondwader/socks5-server": {
- "version": "1.0.10",
- "license": "MIT"
- },
- "node_modules/@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/base64": {
- "version": "1.1.2",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/codegen": {
- "version": "2.0.5",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/eventemitter": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
- "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/fetch": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
- "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.1"
- }
- },
- "node_modules/@protobufjs/float": {
- "version": "1.0.2",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/path": {
- "version": "1.1.2",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/pool": {
- "version": "1.1.0",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/utf8": {
- "version": "1.1.1",
- "license": "BSD-3-Clause"
- },
- "node_modules/@rolldown/binding-android-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
- "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
- "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
- "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
- "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
- "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
- "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
- "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
- "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
- "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
- "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
- "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
- "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
- "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
- "cpu": [
- "wasm32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "1.11.1",
- "@emnapi/runtime": "1.11.1",
- "@napi-rs/wasm-runtime": "^1.1.6"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
- "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.2",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
- "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
- "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
- "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
- "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
- "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@silvia-odwyer/photon-node": {
- "version": "0.3.4",
- "license": "Apache-2.0"
- },
- "node_modules/@smithy/core": {
- "version": "3.30.0",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.30.0.tgz",
- "integrity": "sha512-dl2yRglDxfzH9uJ4fSo4zTaAHa0zH7+V7BZMRWy8hEYIKT1BiqMUK/CN6T3ADQ3kbA5N1tmUulroJ2UtONS7Kw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/credential-provider-imds": {
- "version": "4.4.14",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.14.tgz",
- "integrity": "sha512-QgbuahIb2qxQeZQvNK0sw3aF3JH5zwH8j2lLp5DUasVXexGGMWULAR+7z0omPXFolCP/m5wN9M5lm9EGdSviTQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.30.0",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/fetch-http-handler": {
- "version": "5.6.11",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.11.tgz",
- "integrity": "sha512-o0Zkj1nKqJAoq+a+BrkhU39tRftMNjLwpc/z06Frfl43wpbHrJMaSAVZE4vTqlxtVkNaGaT0bIDxOp7tkFTuQQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.30.0",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/node-http-handler": {
- "version": "4.9.11",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.11.tgz",
- "integrity": "sha512-slbzbz8taEOzoXv/9y34YNBoE+ZHmddLykCgjDAjvMAsu2nM5s2Gzwa5OGF721tD8s+CKeFUBds5lSj9lcbuDg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.30.0",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/signature-v4": {
- "version": "5.6.10",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.10.tgz",
- "integrity": "sha512-EXhWePm3SXJAX38npIy4TXL2Aex/OVgCClTjelN2QHw/U+8CQUH8C7AaxsVzQcYieYPseywn48s89DmvuGjiAg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.30.0",
- "@smithy/types": "^4.16.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/types": {
- "version": "4.16.1",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz",
- "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@standard-schema/spec": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
- "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@tokenizer/inflate": {
- "version": "0.4.1",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.3",
- "token-types": "^6.1.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Borewit"
- }
- },
- "node_modules/@tokenizer/token": {
- "version": "0.3.0",
- "license": "MIT"
- },
- "node_modules/@tootallnate/quickjs-emscripten": {
- "version": "0.23.0",
- "license": "MIT"
- },
- "node_modules/@tybys/wasm-util": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
- "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@types/chai": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
- "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/deep-eql": "*",
- "assertion-error": "^2.0.1"
- }
- },
- "node_modules/@types/deep-eql": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
- "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/diff": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@types/diff/-/diff-8.0.0.tgz",
- "integrity": "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw==",
- "deprecated": "This is a stub types definition. diff provides its own type definitions, so you do not need this installed.",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "diff": "*"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/hosted-git-info": {
- "version": "3.0.5",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/mime-types": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz",
- "integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==",
- "license": "MIT"
- },
- "node_modules/@types/ms": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "22.20.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
- "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.21.0"
- }
- },
- "node_modules/@types/proper-lockfile": {
- "version": "4.1.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/retry": "*"
- }
- },
- "node_modules/@types/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
- "license": "MIT"
- },
- "node_modules/@types/yauzl": {
- "version": "2.10.3",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@typescript/native-preview": {
- "version": "7.0.0-dev.20260120.1",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsgo": "bin/tsgo.js"
- },
- "optionalDependencies": {
- "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260120.1",
- "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260120.1",
- "@typescript/native-preview-linux-arm": "7.0.0-dev.20260120.1",
- "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260120.1",
- "@typescript/native-preview-linux-x64": "7.0.0-dev.20260120.1",
- "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260120.1",
- "@typescript/native-preview-win32-x64": "7.0.0-dev.20260120.1"
- }
- },
- "node_modules/@typescript/native-preview-darwin-arm64": {
- "version": "7.0.0-dev.20260120.1",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@typescript/native-preview-darwin-x64": {
- "version": "7.0.0-dev.20260120.1",
- "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260120.1.tgz",
- "integrity": "sha512-cuC1+wLbUP+Ip2UT94G134fqRdp5w3b3dhcCO6/FQ4yXxvRNyv/WK+upHBUFDaeSOeHgDTyO9/QFYUWwC4If1A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@typescript/native-preview-linux-arm": {
- "version": "7.0.0-dev.20260120.1",
- "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260120.1.tgz",
- "integrity": "sha512-vN6OYVySol/kQZjJGmAzd6L30SyVlCgmCXS8WjUYtE5clN0YrzQHop16RK29fYZHMxpkOniVBtRPxUYQANZBlQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@typescript/native-preview-linux-arm64": {
- "version": "7.0.0-dev.20260120.1",
- "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260120.1.tgz",
- "integrity": "sha512-zZGvEGY7wcHYefMZ87KNmvjN3NLIhsCMHEpHZiGCS3khKf+8z6ZsanrzCjOTodvL01VPyBzHxV1EtkSxAcLiQg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@typescript/native-preview-linux-x64": {
- "version": "7.0.0-dev.20260120.1",
- "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260120.1.tgz",
- "integrity": "sha512-JBfNhWd/asd5MDeS3VgRvE24pGKBkmvLub6tsux6ypr+Yhy+o0WaAEzVpmlRYZUqss2ai5tvOu4dzPBXzZAtFw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@typescript/native-preview-win32-arm64": {
- "version": "7.0.0-dev.20260120.1",
- "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260120.1.tgz",
- "integrity": "sha512-tTndRtYCq2xwgE0VkTi9ACNiJaV43+PqvBqCxk8ceYi3X36Ve+CCnwlZfZJ4k9NxZthtrAwF/kUmpC9iIYbq1w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@typescript/native-preview-win32-x64": {
- "version": "7.0.0-dev.20260120.1",
- "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260120.1.tgz",
- "integrity": "sha512-oZia7hFL6k9pVepfonuPI86Jmyz6WlJKR57tWCDwRNmpA7odxuTq1PbvcYgy1z4+wHF1nnKKJY0PMAiq6ac18w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@typescript/typescript-aix-ppc64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
- "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-darwin-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
- "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-darwin-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
- "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-freebsd-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
- "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-freebsd-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
- "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-arm": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
- "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
- "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-loong64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
- "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-mips64el": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
- "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-ppc64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
- "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-riscv64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
- "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-s390x": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
- "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
- "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-netbsd-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
- "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-netbsd-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
- "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-openbsd-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
- "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-openbsd-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
- "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-sunos-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
- "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-win32-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
- "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-win32-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
- "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@vitest/expect": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
- "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@standard-schema/spec": "^1.1.0",
- "@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.10",
- "@vitest/utils": "4.1.10",
- "chai": "^6.2.2",
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/mocker": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
- "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/spy": "4.1.10",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.21"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/@vitest/pretty-format": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
- "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
- "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/utils": "4.1.10",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/snapshot": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
- "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "4.1.10",
- "@vitest/utils": "4.1.10",
- "magic-string": "^0.30.21",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/spy": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
- "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/utils": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
- "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "4.1.10",
- "convert-source-map": "^2.0.0",
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@xterm/headless": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-6.0.0.tgz",
- "integrity": "sha512-5Yj1QINYCyzrZtf8OFIHi47iQtI+0qYFPHmouEfG8dHNxbZ9Tb9YGSuLcsEwj9Z+OL75GJqPyJbyoFer80a2Hw==",
- "dev": true,
- "license": "MIT",
- "workspaces": [
- "addons/*"
- ]
- },
- "node_modules/@xterm/xterm": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz",
- "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==",
- "dev": true,
- "license": "MIT",
- "workspaces": [
- "addons/*"
- ]
- },
- "node_modules/agent-base": {
- "version": "7.1.4",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/ansi-regex": {
- "version": "6.2.2",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/any-promise": {
- "version": "1.3.0",
- "license": "MIT"
- },
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/ast-types": {
- "version": "0.13.4",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/balanced-match": {
- "version": "4.0.4",
- "license": "MIT",
- "engines": {
- "node": "18 || 20 || >=22"
- }
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/basic-ftp": {
- "version": "5.3.1",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/bignumber.js": {
- "version": "9.3.1",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/bl": {
- "version": "4.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
- }
- },
- "node_modules/bowser": {
- "version": "2.14.1",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
- "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
- "license": "MIT"
- },
- "node_modules/brace-expansion": {
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
- "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^4.0.2"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/buffer": {
- "version": "5.7.1",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
- }
- },
- "node_modules/buffer-crc32": {
- "version": "0.2.13",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "license": "BSD-3-Clause"
- },
- "node_modules/canvas": {
- "version": "3.2.3",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "node-addon-api": "^7.0.0",
- "prebuild-install": "^7.1.3"
- },
- "engines": {
- "node": "^18.12.0 || >= 20.9.0"
- }
- },
- "node_modules/chai": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
- "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/chalk": {
- "version": "5.6.2",
- "license": "MIT",
- "engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/chownr": {
- "version": "1.1.4",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/cli-highlight": {
- "version": "2.1.11",
- "license": "ISC",
- "dependencies": {
- "chalk": "^4.0.0",
- "highlight.js": "^10.7.1",
- "mz": "^2.4.0",
- "parse5": "^5.1.1",
- "parse5-htmlparser2-tree-adapter": "^6.0.0",
- "yargs": "^16.0.0"
- },
- "bin": {
- "highlight": "bin/highlight"
- },
- "engines": {
- "node": ">=8.0.0",
- "npm": ">=5.0.0"
- }
- },
- "node_modules/cli-highlight/node_modules/chalk": {
- "version": "4.1.2",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/cli-highlight/node_modules/supports-color": {
- "version": "7.2.0",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui": {
- "version": "7.0.4",
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "node_modules/cliui/node_modules/ansi-regex": {
- "version": "5.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/strip-ansi": {
- "version": "6.0.1",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "license": "MIT"
- },
- "node_modules/commander": {
- "version": "12.1.0",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/concurrently": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz",
- "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "4.1.2",
- "rxjs": "7.8.2",
- "shell-quote": "1.9.0",
- "supports-color": "8.1.1",
- "tree-kill": "1.2.2",
- "yargs": "17.7.2"
- },
- "bin": {
- "conc": "dist/bin/concurrently.js",
- "concurrently": "dist/bin/concurrently.js"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
- }
- },
- "node_modules/concurrently/node_modules/ansi-regex": {
- "version": "5.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/concurrently/node_modules/chalk": {
- "version": "4.1.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": {
- "version": "7.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/concurrently/node_modules/cliui": {
- "version": "8.0.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/concurrently/node_modules/strip-ansi": {
- "version": "6.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/concurrently/node_modules/yargs": {
- "version": "17.7.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/concurrently/node_modules/yargs-parser": {
- "version": "21.1.1",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cross-spawn": {
- "version": "6.0.6",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "nice-try": "^1.0.4",
- "path-key": "^2.0.1",
- "semver": "^5.5.0",
- "shebang-command": "^1.2.0",
- "which": "^1.2.9"
- },
- "engines": {
- "node": ">=4.8"
- }
- },
- "node_modules/cross-spawn/node_modules/semver": {
- "version": "5.7.2",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver"
- }
- },
- "node_modules/data-uri-to-buffer": {
- "version": "4.0.1",
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decompress-response": {
- "version": "6.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mimic-response": "^3.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/deep-extend": {
- "version": "0.6.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/degenerator": {
- "version": "5.0.1",
- "license": "MIT",
- "dependencies": {
- "ast-types": "^0.13.4",
- "escodegen": "^2.1.0",
- "esprima": "^4.0.1"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/diff": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz",
- "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "license": "MIT"
- },
- "node_modules/end-of-stream": {
- "version": "1.4.5",
- "license": "MIT",
- "dependencies": {
- "once": "^1.4.0"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-module-lexer": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
- "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/esbuild": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
- "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "peer": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.28.1",
- "@esbuild/android-arm": "0.28.1",
- "@esbuild/android-arm64": "0.28.1",
- "@esbuild/android-x64": "0.28.1",
- "@esbuild/darwin-arm64": "0.28.1",
- "@esbuild/darwin-x64": "0.28.1",
- "@esbuild/freebsd-arm64": "0.28.1",
- "@esbuild/freebsd-x64": "0.28.1",
- "@esbuild/linux-arm": "0.28.1",
- "@esbuild/linux-arm64": "0.28.1",
- "@esbuild/linux-ia32": "0.28.1",
- "@esbuild/linux-loong64": "0.28.1",
- "@esbuild/linux-mips64el": "0.28.1",
- "@esbuild/linux-ppc64": "0.28.1",
- "@esbuild/linux-riscv64": "0.28.1",
- "@esbuild/linux-s390x": "0.28.1",
- "@esbuild/linux-x64": "0.28.1",
- "@esbuild/netbsd-arm64": "0.28.1",
- "@esbuild/netbsd-x64": "0.28.1",
- "@esbuild/openbsd-arm64": "0.28.1",
- "@esbuild/openbsd-x64": "0.28.1",
- "@esbuild/openharmony-arm64": "0.28.1",
- "@esbuild/sunos-x64": "0.28.1",
- "@esbuild/win32-arm64": "0.28.1",
- "@esbuild/win32-ia32": "0.28.1",
- "@esbuild/win32-x64": "0.28.1"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escodegen": {
- "version": "2.1.0",
- "license": "BSD-2-Clause",
- "dependencies": {
- "esprima": "^4.0.1",
- "estraverse": "^5.2.0",
- "esutils": "^2.0.2"
- },
- "bin": {
- "escodegen": "bin/escodegen.js",
- "esgenerate": "bin/esgenerate.js"
- },
- "engines": {
- "node": ">=6.0"
- },
- "optionalDependencies": {
- "source-map": "~0.6.1"
- }
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/execa": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cross-spawn": "^6.0.0",
- "get-stream": "^4.0.0",
- "is-stream": "^1.1.0",
- "npm-run-path": "^2.0.0",
- "p-finally": "^1.0.0",
- "signal-exit": "^3.0.0",
- "strip-eof": "^1.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/execa/node_modules/get-stream": {
- "version": "4.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pump": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/expand-template": {
- "version": "2.0.3",
- "dev": true,
- "license": "(MIT OR WTFPL)",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/expect-type": {
- "version": "1.3.0",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/extend": {
- "version": "3.0.2",
- "license": "MIT"
- },
- "node_modules/extract-zip": {
- "version": "2.0.1",
- "license": "BSD-2-Clause",
- "dependencies": {
- "debug": "^4.1.1",
- "get-stream": "^5.1.0",
- "yauzl": "^2.10.0"
- },
- "bin": {
- "extract-zip": "cli.js"
- },
- "engines": {
- "node": ">= 10.17.0"
- },
- "optionalDependencies": {
- "@types/yauzl": "^2.9.1"
- }
- },
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fastq": {
- "version": "1.20.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/fd-slicer": {
- "version": "1.1.0",
- "license": "MIT",
- "dependencies": {
- "pend": "~1.2.0"
- }
- },
- "node_modules/fetch-blob": {
- "version": "3.2.0",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "node-domexception": "^1.0.0",
- "web-streams-polyfill": "^3.0.3"
- },
- "engines": {
- "node": "^12.20 || >= 14.13"
- }
- },
- "node_modules/file-type": {
- "version": "21.3.4",
- "license": "MIT",
- "dependencies": {
- "@tokenizer/inflate": "^0.4.1",
- "strtok3": "^10.3.4",
- "token-types": "^6.1.1",
- "uint8array-extras": "^1.4.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/file-type?sponsor=1"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/formdata-polyfill": {
- "version": "4.0.10",
- "license": "MIT",
- "dependencies": {
- "fetch-blob": "^3.1.2"
- },
- "engines": {
- "node": ">=12.20.0"
- }
- },
- "node_modules/fs-constants": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gaxios": {
- "version": "7.1.4",
- "license": "Apache-2.0",
- "dependencies": {
- "extend": "^3.0.2",
- "https-proxy-agent": "^7.0.1",
- "node-fetch": "^3.3.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/gcp-metadata": {
- "version": "8.1.2",
- "license": "Apache-2.0",
- "dependencies": {
- "gaxios": "^7.0.0",
- "google-logging-utils": "^1.0.0",
- "json-bigint": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-east-asian-width": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
- "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/get-stream": {
- "version": "5.2.0",
- "license": "MIT",
- "dependencies": {
- "pump": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/get-uri": {
- "version": "6.0.5",
- "license": "MIT",
- "dependencies": {
- "basic-ftp": "^5.0.2",
- "data-uri-to-buffer": "^6.0.2",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/get-uri/node_modules/data-uri-to-buffer": {
- "version": "6.0.2",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/github-from-package": {
- "version": "0.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/glob": {
- "version": "13.0.6",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "minimatch": "^10.2.2",
- "minipass": "^7.1.3",
- "path-scurry": "^2.0.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/google-auth-library": {
- "version": "10.6.2",
- "license": "Apache-2.0",
- "dependencies": {
- "base64-js": "^1.3.0",
- "ecdsa-sig-formatter": "^1.0.11",
- "gaxios": "^7.1.4",
- "gcp-metadata": "8.1.2",
- "google-logging-utils": "1.1.3",
- "jws": "^4.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/google-logging-utils": {
- "version": "1.1.3",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "license": "ISC"
- },
- "node_modules/grok-mermaid": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/grok-mermaid/-/grok-mermaid-0.2.3.tgz",
- "integrity": "sha512-/4KopAbsjvuRP9MdPtlDjOHUmUVEohOX73JNcsWpzAtFxh+bq5+Dhb6gzvRieLDwIPQIR3/vy8V1NNTuz4Zsmg==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/highlight.js": {
- "version": "10.7.3",
- "license": "BSD-3-Clause",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/hosted-git-info": {
- "version": "9.0.3",
- "license": "ISC",
- "dependencies": {
- "lru-cache": "^11.1.0"
- },
- "engines": {
- "node": "^20.17.0 || >=22.9.0"
- }
- },
- "node_modules/http-proxy-agent": {
- "version": "7.0.2",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/husky": {
- "version": "9.1.7",
- "dev": true,
- "license": "MIT",
- "bin": {
- "husky": "bin.js"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/typicode"
- }
- },
- "node_modules/ieee754": {
- "version": "1.2.1",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/ignore": {
- "version": "7.0.5",
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/ini": {
- "version": "1.3.8",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/interpret": {
- "version": "1.4.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/ip-address": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz",
- "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.16.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-stream": {
- "version": "1.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jiti": {
- "version": "2.7.0",
- "license": "MIT",
- "peer": true,
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "node_modules/json-bigint": {
- "version": "1.0.0",
- "license": "MIT",
- "dependencies": {
- "bignumber.js": "^9.0.0"
- }
- },
- "node_modules/json-schema-to-ts": {
- "version": "3.1.1",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "ts-algebra": "^2.0.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/jwa": {
- "version": "2.0.1",
- "license": "MIT",
- "dependencies": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jws": {
- "version": "4.0.1",
- "license": "MIT",
- "dependencies": {
- "jwa": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/koffi": {
- "version": "2.16.2",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "funding": {
- "url": "https://liberapay.com/Koromix"
- }
- },
- "node_modules/lightningcss": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
- "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
- "dev": true,
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-android-arm64": "1.33.0",
- "lightningcss-darwin-arm64": "1.33.0",
- "lightningcss-darwin-x64": "1.33.0",
- "lightningcss-freebsd-x64": "1.33.0",
- "lightningcss-linux-arm-gnueabihf": "1.33.0",
- "lightningcss-linux-arm64-gnu": "1.33.0",
- "lightningcss-linux-arm64-musl": "1.33.0",
- "lightningcss-linux-x64-gnu": "1.33.0",
- "lightningcss-linux-x64-musl": "1.33.0",
- "lightningcss-win32-arm64-msvc": "1.33.0",
- "lightningcss-win32-x64-msvc": "1.33.0"
- }
- },
- "node_modules/lightningcss-android-arm64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
- "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
- "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
- "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
- "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
- "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
- "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
- "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
- "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
- "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
- "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.33.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
- "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/long": {
- "version": "5.3.2",
- "license": "Apache-2.0"
- },
- "node_modules/lru-cache": {
- "version": "11.5.2",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
- "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/magic-string": {
- "version": "0.30.21",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
- "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
- }
- },
- "node_modules/marked": {
- "version": "18.0.7",
- "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.7.tgz",
- "integrity": "sha512-iDVQ5ldaiKXn6b2JroX5kgRfmwgqolW7NpaEzTl1k/2Zh1njIEN9yniyLV/mOvWwtsE8OGgkjsCYvijuPk1dtA==",
- "license": "MIT",
- "bin": {
- "marked": "bin/marked.js"
- },
- "engines": {
- "node": ">= 20"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/mime-db": {
- "version": "1.54.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "3.0.2",
- "license": "MIT",
- "dependencies": {
- "mime-db": "^1.54.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/mimic-response": {
- "version": "3.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/minimatch": {
- "version": "10.2.5",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "brace-expansion": "^5.0.5"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.3",
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/mkdirp-classic": {
- "version": "0.5.3",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "license": "MIT"
- },
- "node_modules/mz": {
- "version": "2.7.0",
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.17",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
- "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/napi-build-utils": {
- "version": "2.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/netmask": {
- "version": "2.1.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/nice-try": {
- "version": "1.0.5",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-abi": {
- "version": "3.92.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.3.5"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/node-addon-api": {
- "version": "7.1.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-domexception": {
- "version": "1.0.0",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=10.5.0"
- }
- },
- "node_modules/node-fetch": {
- "version": "3.3.2",
- "license": "MIT",
- "dependencies": {
- "data-uri-to-buffer": "^4.0.0",
- "fetch-blob": "^3.1.4",
- "formdata-polyfill": "^4.0.10"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/node-fetch"
- }
- },
- "node_modules/node-forge": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
- "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
- "dev": true,
- "license": "(BSD-3-Clause OR GPL-2.0)",
- "engines": {
- "node": ">= 6.13.0"
- }
- },
- "node_modules/npm-run-path": {
- "version": "2.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/obug": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz",
- "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/sxzz",
- "https://opencollective.com/debug"
- ],
- "license": "MIT",
- "engines": {
- "node": ">=12.20.0"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/openai": {
- "version": "6.47.0",
- "resolved": "https://registry.npmjs.org/openai/-/openai-6.47.0.tgz",
- "integrity": "sha512-xYr+R9woSzWxVxeiqkkNbHhv89tZDEI6eBMbrdPnv3poh+mijHvbhS35a+3o6xHa411/ns8j5ENY3So9DCXWYw==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@aws-sdk/credential-provider-node": ">=3.972.0 <4",
- "@smithy/hash-node": ">=4.3.0 <5",
- "@smithy/signature-v4": ">=5.4.0 <6",
- "ws": "^8.18.0",
- "zod": "^3.25 || ^4.0"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-provider-node": {
- "optional": true
- },
- "@smithy/hash-node": {
- "optional": true
- },
- "@smithy/signature-v4": {
- "optional": true
- },
- "ws": {
- "optional": true
- },
- "zod": {
- "optional": true
- }
- }
- },
- "node_modules/p-finally": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/p-retry": {
- "version": "4.6.2",
- "license": "MIT",
- "dependencies": {
- "@types/retry": "0.12.0",
- "retry": "^0.13.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pac-proxy-agent": {
- "version": "7.2.0",
- "license": "MIT",
- "dependencies": {
- "@tootallnate/quickjs-emscripten": "^0.23.0",
- "agent-base": "^7.1.2",
- "debug": "^4.3.4",
- "get-uri": "^6.0.1",
- "http-proxy-agent": "^7.0.0",
- "https-proxy-agent": "^7.0.6",
- "pac-resolver": "^7.0.1",
- "socks-proxy-agent": "^8.0.5"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/pac-resolver": {
- "version": "7.0.1",
- "license": "MIT",
- "dependencies": {
- "degenerator": "^5.0.0",
- "netmask": "^2.0.2"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/parse5": {
- "version": "5.1.1",
- "license": "MIT"
- },
- "node_modules/parse5-htmlparser2-tree-adapter": {
- "version": "6.0.1",
- "license": "MIT",
- "dependencies": {
- "parse5": "^6.0.1"
- }
- },
- "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
- "version": "6.0.1",
- "license": "MIT"
- },
- "node_modules/partial-json": {
- "version": "0.1.7",
- "license": "MIT"
- },
- "node_modules/path-key": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-scurry": {
- "version": "2.0.2",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "18 || 20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/pend": {
- "version": "1.2.0",
- "license": "MIT"
- },
- "node_modules/pi-extension-custom-provider-anthropic": {
- "resolved": "packages/coding-agent/examples/extensions/custom-provider-anthropic",
- "link": true
- },
- "node_modules/pi-extension-custom-provider-gitlab-duo": {
- "resolved": "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo",
- "link": true
- },
- "node_modules/pi-extension-sandbox": {
- "resolved": "packages/coding-agent/examples/extensions/sandbox",
- "link": true
- },
- "node_modules/pi-extension-with-deps": {
- "resolved": "packages/coding-agent/examples/extensions/with-deps",
- "link": true
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.25",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
- "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.16",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/prebuild-install": {
- "version": "7.1.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "detect-libc": "^2.0.0",
- "expand-template": "^2.0.3",
- "github-from-package": "0.0.0",
- "minimist": "^1.2.3",
- "mkdirp-classic": "^0.5.3",
- "napi-build-utils": "^2.0.0",
- "node-abi": "^3.3.0",
- "pump": "^3.0.0",
- "rc": "^1.2.7",
- "simple-get": "^4.0.0",
- "tar-fs": "^2.0.0",
- "tunnel-agent": "^0.6.0"
- },
- "bin": {
- "prebuild-install": "bin.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/proper-lockfile": {
- "version": "4.1.2",
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "retry": "^0.12.0",
- "signal-exit": "^3.0.2"
- }
- },
- "node_modules/proper-lockfile/node_modules/retry": {
- "version": "0.12.0",
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/protobufjs": {
- "version": "7.6.4",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz",
- "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==",
- "hasInstallScript": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.5",
- "@protobufjs/eventemitter": "^1.1.1",
- "@protobufjs/fetch": "^1.1.1",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.1",
- "@types/node": ">=13.7.0",
- "long": "^5.3.2"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/proxy-agent": {
- "version": "6.5.0",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "^4.3.4",
- "http-proxy-agent": "^7.0.1",
- "https-proxy-agent": "^7.0.6",
- "lru-cache": "^7.14.1",
- "pac-proxy-agent": "^7.1.0",
- "proxy-from-env": "^1.1.0",
- "socks-proxy-agent": "^8.0.5"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/proxy-agent/node_modules/lru-cache": {
- "version": "7.18.3",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "license": "MIT"
- },
- "node_modules/pump": {
- "version": "3.0.4",
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/rc": {
- "version": "1.2.8",
- "dev": true,
- "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
- "dependencies": {
- "deep-extend": "^0.6.0",
- "ini": "~1.3.0",
- "minimist": "^1.2.0",
- "strip-json-comments": "~2.0.1"
- },
- "bin": {
- "rc": "cli.js"
- }
- },
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/rechoir": {
- "version": "0.6.2",
- "dev": true,
- "dependencies": {
- "resolve": "^1.1.6"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.12",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "is-core-module": "^2.16.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/retry": {
- "version": "0.13.1",
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/reusify": {
- "version": "1.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rolldown": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
- "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@oxc-project/types": "=0.139.0",
- "@rolldown/pluginutils": "^1.0.0"
- },
- "bin": {
- "rolldown": "bin/cli.mjs"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.1.5",
- "@rolldown/binding-darwin-arm64": "1.1.5",
- "@rolldown/binding-darwin-x64": "1.1.5",
- "@rolldown/binding-freebsd-x64": "1.1.5",
- "@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
- "@rolldown/binding-linux-arm64-gnu": "1.1.5",
- "@rolldown/binding-linux-arm64-musl": "1.1.5",
- "@rolldown/binding-linux-ppc64-gnu": "1.1.5",
- "@rolldown/binding-linux-s390x-gnu": "1.1.5",
- "@rolldown/binding-linux-x64-gnu": "1.1.5",
- "@rolldown/binding-linux-x64-musl": "1.1.5",
- "@rolldown/binding-openharmony-arm64": "1.1.5",
- "@rolldown/binding-wasm32-wasi": "1.1.5",
- "@rolldown/binding-win32-arm64-msvc": "1.1.5",
- "@rolldown/binding-win32-x64-msvc": "1.1.5"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/rxjs": {
- "version": "7.8.2",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "7.8.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
- "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/shebang-command": {
- "version": "1.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/shebang-regex": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/shell-quote": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz",
- "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/shelljs": {
- "version": "0.9.2",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "execa": "^1.0.0",
- "fast-glob": "^3.3.2",
- "interpret": "^1.0.0",
- "rechoir": "^0.6.2"
- },
- "bin": {
- "shjs": "bin/shjs"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/shx": {
- "version": "0.4.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.8",
- "shelljs": "^0.9.2"
- },
- "bin": {
- "shx": "lib/cli.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/siginfo": {
- "version": "2.0.0",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/signal-exit": {
- "version": "3.0.7",
- "license": "ISC"
- },
- "node_modules/simple-concat": {
- "version": "1.0.1",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/simple-get": {
- "version": "4.0.1",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "decompress-response": "^6.0.0",
- "once": "^1.3.1",
- "simple-concat": "^1.0.0"
- }
- },
- "node_modules/smart-buffer": {
- "version": "4.2.0",
- "license": "MIT",
- "engines": {
- "node": ">= 6.0.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/socks": {
- "version": "2.8.8",
- "license": "MIT",
- "dependencies": {
- "ip-address": "^10.1.1",
- "smart-buffer": "^4.2.0"
- },
- "engines": {
- "node": ">= 10.0.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/socks-proxy-agent": {
- "version": "8.0.5",
- "license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "^4.3.4",
- "socks": "^2.8.3"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "license": "BSD-3-Clause",
- "optional": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/stackback": {
- "version": "0.0.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/std-env": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
- "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/string-width": {
- "version": "4.2.3",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width/node_modules/ansi-regex": {
- "version": "5.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width/node_modules/strip-ansi": {
- "version": "6.0.1",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "7.2.0",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.2.2"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/strip-eof": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/strtok3": {
- "version": "10.3.5",
- "license": "MIT",
- "dependencies": {
- "@tokenizer/token": "^0.3.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Borewit"
- }
- },
- "node_modules/supports-color": {
- "version": "8.1.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/tar-fs": {
- "version": "2.1.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chownr": "^1.1.1",
- "mkdirp-classic": "^0.5.2",
- "pump": "^3.0.0",
- "tar-stream": "^2.1.4"
- }
- },
- "node_modules/tar-stream": {
- "version": "2.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/thenify": {
- "version": "3.3.1",
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0"
- }
- },
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "license": "MIT",
- "dependencies": {
- "thenify": ">= 3.1.0 < 4"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/tinybench": {
- "version": "2.9.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tinyexec": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
- "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tinyglobby": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
- "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.4"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/tinyrainbow": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz",
- "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/token-types": {
- "version": "6.1.2",
- "license": "MIT",
- "dependencies": {
- "@borewit/text-codec": "^0.2.1",
- "@tokenizer/token": "^0.3.0",
- "ieee754": "^1.2.1"
- },
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Borewit"
- }
- },
- "node_modules/tree-kill": {
- "version": "1.2.2",
- "dev": true,
- "license": "MIT",
- "bin": {
- "tree-kill": "cli.js"
- }
- },
- "node_modules/ts-algebra": {
- "version": "2.0.0",
- "license": "MIT"
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "license": "0BSD"
- },
- "node_modules/tsx": {
- "version": "4.23.1",
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz",
- "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "esbuild": "~0.28.0"
- },
- "bin": {
- "tsx": "dist/cli.mjs"
- },
- "engines": {
- "node": ">=18.0.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- }
- },
- "node_modules/tunnel-agent": {
- "version": "0.6.0",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/typebox": {
- "version": "1.3.10",
- "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.10.tgz",
- "integrity": "sha512-L0MT00X96q0P30f1NrGULgaSbmu8hfTjWbLULeVoM+j5u0jR5wyefoDquX2NmRa39f0KiNIBo1wWSaVvHJTZlA==",
- "license": "MIT"
- },
- "node_modules/typescript": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
- "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc"
- },
- "engines": {
- "node": ">=16.20.0"
- },
- "optionalDependencies": {
- "@typescript/typescript-aix-ppc64": "7.0.2",
- "@typescript/typescript-darwin-arm64": "7.0.2",
- "@typescript/typescript-darwin-x64": "7.0.2",
- "@typescript/typescript-freebsd-arm64": "7.0.2",
- "@typescript/typescript-freebsd-x64": "7.0.2",
- "@typescript/typescript-linux-arm": "7.0.2",
- "@typescript/typescript-linux-arm64": "7.0.2",
- "@typescript/typescript-linux-loong64": "7.0.2",
- "@typescript/typescript-linux-mips64el": "7.0.2",
- "@typescript/typescript-linux-ppc64": "7.0.2",
- "@typescript/typescript-linux-riscv64": "7.0.2",
- "@typescript/typescript-linux-s390x": "7.0.2",
- "@typescript/typescript-linux-x64": "7.0.2",
- "@typescript/typescript-netbsd-arm64": "7.0.2",
- "@typescript/typescript-netbsd-x64": "7.0.2",
- "@typescript/typescript-openbsd-arm64": "7.0.2",
- "@typescript/typescript-openbsd-x64": "7.0.2",
- "@typescript/typescript-sunos-x64": "7.0.2",
- "@typescript/typescript-win32-arm64": "7.0.2",
- "@typescript/typescript-win32-x64": "7.0.2"
- }
- },
- "node_modules/uint8array-extras": {
- "version": "1.5.0",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/undici": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
- "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
- "license": "MIT",
- "engines": {
- "node": ">=20.18.1"
- }
- },
- "node_modules/undici-types": {
- "version": "6.21.0",
- "license": "MIT"
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/uuid": {
- "version": "14.0.0",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist-node/bin/uuid"
- }
- },
- "node_modules/vite": {
- "version": "8.1.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
- "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "lightningcss": "^1.32.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.17",
- "rolldown": "~1.1.5",
- "tinyglobby": "^0.2.17"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.3.0",
- "esbuild": "^0.27.0 || ^0.28.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "@vitejs/devtools": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/vite/node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/vitest": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
- "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/expect": "4.1.10",
- "@vitest/mocker": "4.1.10",
- "@vitest/pretty-format": "4.1.10",
- "@vitest/runner": "4.1.10",
- "@vitest/snapshot": "4.1.10",
- "@vitest/spy": "4.1.10",
- "@vitest/utils": "4.1.10",
- "es-module-lexer": "^2.0.0",
- "expect-type": "^1.3.0",
- "magic-string": "^0.30.21",
- "obug": "^2.1.1",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "std-env": "^4.0.0-rc.1",
- "tinybench": "^2.9.0",
- "tinyexec": "^1.0.2",
- "tinyglobby": "^0.2.15",
- "tinyrainbow": "^3.1.0",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
- "why-is-node-running": "^2.3.0"
- },
- "bin": {
- "vitest": "vitest.mjs"
- },
- "engines": {
- "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "@edge-runtime/vm": "*",
- "@opentelemetry/api": "^1.9.0",
- "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.1.10",
- "@vitest/browser-preview": "4.1.10",
- "@vitest/browser-webdriverio": "4.1.10",
- "@vitest/coverage-istanbul": "4.1.10",
- "@vitest/coverage-v8": "4.1.10",
- "@vitest/ui": "4.1.10",
- "happy-dom": "*",
- "jsdom": "*",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "@edge-runtime/vm": {
- "optional": true
- },
- "@opentelemetry/api": {
- "optional": true
- },
- "@types/node": {
- "optional": true
- },
- "@vitest/browser-playwright": {
- "optional": true
- },
- "@vitest/browser-preview": {
- "optional": true
- },
- "@vitest/browser-webdriverio": {
- "optional": true
- },
- "@vitest/coverage-istanbul": {
- "optional": true
- },
- "@vitest/coverage-v8": {
- "optional": true
- },
- "@vitest/ui": {
- "optional": true
- },
- "happy-dom": {
- "optional": true
- },
- "jsdom": {
- "optional": true
- },
- "vite": {
- "optional": false
- }
- }
- },
- "node_modules/vitest/node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/web-streams-polyfill": {
- "version": "3.3.3",
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/which": {
- "version": "1.3.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "which": "bin/which"
- }
- },
- "node_modules/why-is-node-running": {
- "version": "2.3.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "siginfo": "^2.0.0",
- "stackback": "0.0.2"
- },
- "bin": {
- "why-is-node-running": "cli.js"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "7.0.0",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi/node_modules/ansi-regex": {
- "version": "5.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi/node_modules/strip-ansi": {
- "version": "6.0.1",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "license": "ISC"
- },
- "node_modules/ws": {
- "version": "8.21.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
- "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yaml": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
- "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
- "license": "ISC",
- "peer": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/eemeli"
- }
- },
- "node_modules/yargs": {
- "version": "16.2.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz",
- "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==",
- "license": "MIT",
- "dependencies": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs-parser": {
- "version": "20.2.9",
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yauzl": {
- "version": "2.10.0",
- "license": "MIT",
- "dependencies": {
- "buffer-crc32": "~0.2.3",
- "fd-slicer": "~1.1.0"
- }
- },
- "node_modules/zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
- "license": "MIT",
- "peer": true,
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- },
- "node_modules/zod-to-json-schema": {
- "version": "3.25.2",
- "license": "ISC",
- "peerDependencies": {
- "zod": "^3.25.28 || ^4"
- }
- },
- "packages/agent": {
- "name": "@earendil-works/pi-agent-core",
- "version": "0.9.1",
- "license": "MIT",
- "dependencies": {
- "@earendil-works/pi-ai": "^0.9.1",
- "typebox": "^1.3.9"
- },
- "devDependencies": {
- "@types/node": "^24.3.0",
- "typescript": "^7.0.2",
- "vitest": "^4.1.10"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "packages/agent/node_modules/@types/node": {
- "version": "24.13.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
- "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.18.0"
- }
- },
- "packages/agent/node_modules/undici-types": {
- "version": "7.18.2",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
- "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
- "dev": true,
- "license": "MIT"
- },
- "packages/ai": {
- "name": "@earendil-works/pi-ai",
- "version": "0.9.1",
- "license": "MIT",
- "dependencies": {
- "@anthropic-ai/sdk": "^0.91.1",
- "@aws-sdk/client-bedrock-runtime": "^3.1095.0",
- "@google/genai": "^1.40.0",
- "@mistralai/mistralai": "^2.2.0",
- "chalk": "^5.6.2",
- "openai": "6.47.0",
- "partial-json": "^0.1.7",
- "proxy-agent": "^6.5.0",
- "typebox": "^1.3.9",
- "undici": "^7.29.0",
- "zod-to-json-schema": "^3.24.6"
- },
- "bin": {
- "pi-ai": "dist/cli.js"
- },
- "devDependencies": {
- "@types/node": "^24.3.0",
- "canvas": "^3.2.0",
- "vitest": "^4.1.10"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "packages/ai/node_modules/@types/node": {
- "version": "24.13.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
- "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.18.0"
- }
- },
- "packages/ai/node_modules/undici-types": {
- "version": "7.18.2",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
- "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
- "dev": true,
- "license": "MIT"
- },
- "packages/coding-agent": {
- "name": "@earendil-works/pi-coding-agent",
- "version": "0.9.1",
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "@agentclientprotocol/sdk": "^1.3.0",
- "@earendil-works/pi-agent-core": "^0.9.1",
- "@earendil-works/pi-ai": "^0.9.1",
- "@earendil-works/pi-tui": "^0.9.1",
- "@silvia-odwyer/photon-node": "^0.3.4",
- "chalk": "^5.5.0",
- "cli-highlight": "^2.1.11",
- "diff": "^9.0.0",
- "extract-zip": "^2.0.1",
- "file-type": "^21.1.1",
- "glob": "^13.0.1",
- "grok-mermaid": "0.2.3",
- "hosted-git-info": "^9.0.2",
- "ignore": "^7.0.5",
- "jiti": "^2.7.0",
- "marked": "^18.0.7",
- "minimatch": "^10.2.3",
- "proper-lockfile": "^4.1.2",
- "strip-ansi": "^7.1.0",
- "typebox": "^1.3.9",
- "undici": "^7.29.0",
- "uuid": "^14.0.0",
- "yaml": "^2.9.0"
- },
- "bin": {
- "pi": "dist/bundle/cli.js"
- },
- "devDependencies": {
- "@types/diff": "^8.0.0",
- "@types/hosted-git-info": "^3.0.5",
- "@types/ms": "^2.1.0",
- "@types/node": "^24.3.0",
- "@types/proper-lockfile": "^4.1.4",
- "esbuild": "^0.28.1",
- "shx": "^0.4.0",
- "typescript": "^7.0.2",
- "vitest": "^4.1.10"
- },
- "engines": {
- "node": ">=22.8.0"
- },
- "optionalDependencies": {
- "@mariozechner/clipboard": "^0.3.9"
- }
- },
- "packages/coding-agent/examples/extensions/custom-provider-anthropic": {
- "name": "pi-extension-custom-provider-anthropic",
- "version": "0.1.1",
- "dependencies": {
- "@anthropic-ai/sdk": "^0.52.0"
- }
- },
- "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo": {
- "name": "pi-extension-custom-provider-gitlab-duo",
- "version": "0.1.1"
- },
- "packages/coding-agent/examples/extensions/sandbox": {
- "name": "pi-extension-sandbox",
- "version": "1.5.1",
- "dependencies": {
- "@anthropic-ai/sandbox-runtime": "^0.0.55"
- }
- },
- "packages/coding-agent/examples/extensions/sandbox/node_modules/@anthropic-ai/sandbox-runtime": {
- "version": "0.0.26",
- "license": "Apache-2.0",
- "dependencies": {
- "@pondwader/socks5-server": "^1.0.10",
- "@types/lodash-es": "^4.17.12",
- "commander": "^12.1.0",
- "lodash-es": "^4.17.21",
- "shell-quote": "^1.8.3",
- "zod": "^3.24.1"
- },
- "bin": {
- "srt": "dist/cli.js"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "packages/coding-agent/examples/extensions/sandbox/node_modules/@types/lodash": {
- "version": "4.17.23",
- "license": "MIT"
- },
- "packages/coding-agent/examples/extensions/sandbox/node_modules/@types/lodash-es": {
- "version": "4.17.12",
- "license": "MIT",
- "dependencies": {
- "@types/lodash": "*"
- }
- },
- "packages/coding-agent/examples/extensions/sandbox/node_modules/lodash-es": {
- "version": "4.18.1",
- "license": "MIT"
- },
- "packages/coding-agent/examples/extensions/with-deps": {
- "name": "pi-extension-with-deps",
- "version": "0.1.1",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "devDependencies": {
- "@types/ms": "^2.1.0"
- }
- },
- "packages/coding-agent/node_modules/@anthropic-ai/sdk": {
- "version": "0.52.0",
- "license": "MIT",
- "bin": {
- "anthropic-ai-sdk": "bin/cli"
- }
- },
- "packages/coding-agent/node_modules/@types/node": {
- "version": "24.13.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
- "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.18.0"
- }
- },
- "packages/coding-agent/node_modules/undici-types": {
- "version": "7.18.2",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
- "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
- "dev": true,
- "license": "MIT"
- },
- "packages/tui": {
- "name": "@earendil-works/pi-tui",
- "version": "0.9.1",
- "license": "MIT",
- "dependencies": {
- "@types/mime-types": "^3.0.1",
- "chalk": "^5.5.0",
- "get-east-asian-width": "^1.6.0",
- "marked": "^18.0.7",
- "mime-types": "^3.0.1"
- },
- "devDependencies": {
- "@xterm/headless": "^6.0.0",
- "@xterm/xterm": "^6.0.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "optionalDependencies": {
- "koffi": "^2.9.0"
- }
- }
- }
-}
diff --git a/package.json b/package.json
index 6d64610db8..dbbab7e11d 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,7 @@
"name": "prime-agent",
"private": true,
"type": "module",
+ "packageManager": "bun@1.4.0",
"workspaces": [
"packages/*",
"packages/coding-agent/examples/extensions/with-deps",
@@ -10,43 +11,53 @@
"packages/coding-agent/examples/extensions/sandbox"
],
"scripts": {
- "clean": "npm run clean --workspaces",
- "build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build",
- "dev": "concurrently --names \"ai,agent,coding-agent,tui\" --prefix-colors \"cyan,yellow,red,magenta\" \"cd packages/ai && npm run dev\" \"cd packages/agent && npm run dev\" \"cd packages/coding-agent && npm run dev\" \"cd packages/tui && npm run dev\"",
- "dev:tsc": "cd packages/ai && npm run dev:tsc",
- "check": "biome check --write --error-on-warnings . && tsgo --noEmit && npm run check:installer && npm run check:browser-smoke",
- "check:installer": "node scripts/check-installer-render.mjs",
- "check:browser-smoke": "node scripts/check-browser-smoke.mjs",
- "profile:tui": "node scripts/profile-coding-agent-node.mjs --mode tui",
- "profile:rpc": "node scripts/profile-coding-agent-node.mjs --mode rpc",
- "test": "npm run test --workspaces --if-present",
- "version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
- "version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
- "version:major": "npm version major -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
- "version:set": "npm version -ws",
- "prepublishOnly": "npm run clean && npm run build && npm run check",
- "publish": "npm run prepublishOnly && npm publish -ws --access public",
- "publish:dry": "npm run prepublishOnly && npm publish -ws --access public --dry-run",
- "release:pack": "node scripts/pack-prime-agent-release.mjs",
- "release:patch": "node scripts/release.mjs patch",
- "release:minor": "node scripts/release.mjs minor",
- "release:major": "node scripts/release.mjs major",
- "prepare": "husky"
+ "clean": "bun scripts/remove-paths.ts packages/tui/dist packages/ai/dist packages/agent/dist packages/coding-agent/dist",
+ "build": "bun run --cwd packages/tui build && bun run --cwd packages/ai build && bun run --cwd packages/agent build && bun run --cwd packages/coding-agent build",
+ "dev": "bun scripts/dev.ts",
+ "dev:tsc": "bun run --cwd packages/ai dev:tsc",
+ "models:refresh": "bun run --cwd packages/ai generate-models",
+ "check:bun-version": "bun scripts/check-bun-version.ts",
+ "check": "bun run check:bun-version && bun --bun biome check --write --error-on-warnings . && bun run check:type && bun run check:installer && bun run check:browser-smoke",
+ "check:installer": "bun scripts/check-installer-render.mjs && bun scripts/check-powershell-installer.mjs",
+ "check:browser-smoke": "bun scripts/check-browser-smoke.mjs",
+ "profile:tui": "bun scripts/profile-coding-agent-node.mjs --mode tui",
+ "profile:rpc": "bun scripts/profile-coding-agent-node.mjs --mode rpc",
+ "test": "bun run --cwd packages/tui test && bun run --cwd packages/ai test && bun run --cwd packages/agent test && bun run --cwd packages/coding-agent test",
+ "version:patch": "bun scripts/set-version.ts patch",
+ "version:minor": "bun scripts/set-version.ts minor",
+ "version:major": "bun scripts/set-version.ts major",
+ "version:set": "bun scripts/set-version.ts",
+ "prepublishOnly": "bun run clean && bun run build && bun run check",
+ "publish": "bun run prepublishOnly && bun scripts/publish-workspaces.ts",
+ "publish:dry": "bun run prepublishOnly && bun scripts/publish-workspaces.ts --dry-run",
+ "release:pack": "bun scripts/pack-prime-agent-release.mjs",
+ "release:patch": "bun scripts/release.mjs patch",
+ "release:minor": "bun scripts/release.mjs minor",
+ "release:major": "bun scripts/release.mjs major",
+ "prepare": "bun --bun husky",
+ "build:binary": "bun run --cwd packages/coding-agent build:binary",
+ "dev:cli": "bun packages/coding-agent/src/bun/cli.ts",
+ "check:type": "bun --bun tsgo --noEmit",
+ "test:process": "bun run --cwd packages/coding-agent test:process",
+ "test:process-stress": "bun run --cwd packages/coding-agent test:process-stress",
+ "test:kernel": "bun run --cwd packages/coding-agent test:kernel"
},
"devDependencies": {
"@anthropic-ai/sandbox-runtime": "^0.0.55",
"@biomejs/biome": "2.5.5",
"@types/node": "^22.10.5",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
- "concurrently": "^9.2.4",
+ "bun-types": "1.4.0",
"husky": "^9.1.7",
"jiti": "^2.7.0",
- "shx": "^0.4.0",
- "tsx": "^4.23.1",
- "typescript": "^7.0.2"
+ "typescript": "^7.0.2",
+ "@vitest/expect": "4.1.10",
+ "@vitest/runner": "4.1.10",
+ "@vitest/spy": "4.1.10"
},
"engines": {
- "node": ">=22.8.0"
+ "node": ">=22.8.0",
+ "bun": "1.4.0"
},
"version": "0.9.1",
"dependencies": {
@@ -55,9 +66,6 @@
},
"overrides": {
"rimraf": "6.1.2",
- "gaxios": {
- "rimraf": "6.1.2"
- },
"shell-quote": "^1.10.0"
}
}
diff --git a/packages/agent/README.md b/packages/agent/README.md
index e28f449c89..a451cb49f5 100644
--- a/packages/agent/README.md
+++ b/packages/agent/README.md
@@ -18,7 +18,7 @@ Release docs use the Prime Agent package names. The source workspace manifests s
## Workspace Package
```bash
-npm install prime-agent-core
+bun add prime-agent-core
```
## Quick Start
diff --git a/packages/agent/package.json b/packages/agent/package.json
index e512344cc3..1a55f80f35 100644
--- a/packages/agent/package.json
+++ b/packages/agent/package.json
@@ -10,11 +10,11 @@
"README.md"
],
"scripts": {
- "clean": "shx rm -rf dist",
- "build": "tsgo -p tsconfig.build.json",
- "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
- "test": "vitest --run",
- "prepublishOnly": "npm run clean && npm run build"
+ "clean": "bun ../../scripts/remove-paths.ts dist",
+ "build": "bun --bun tsgo -p tsconfig.build.json",
+ "dev": "bun --bun tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
+ "test": "bun ../../scripts/run-with-clean-env.ts bun test --isolate --timeout 30000",
+ "prepublishOnly": "bun run clean && bun run build"
},
"dependencies": {
"@earendil-works/pi-ai": "^0.9.1",
@@ -35,11 +35,11 @@
"directory": "packages/agent"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=20.0.0",
+ "bun": "1.4.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
- "typescript": "^7.0.2",
- "vitest": "^4.1.10"
+ "typescript": "^7.0.2"
}
}
diff --git a/packages/agent/vitest.config.ts b/packages/agent/vitest.config.ts
deleted file mode 100644
index f80e2c0497..0000000000
--- a/packages/agent/vitest.config.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { fileURLToPath } from "node:url";
-import { defineConfig } from "vitest/config";
-
-const aiSrcIndex = fileURLToPath(new URL("../ai/src/index.ts", import.meta.url));
-const aiSrcOAuth = fileURLToPath(new URL("../ai/src/oauth.ts", import.meta.url));
-
-export default defineConfig({
- test: {
- globals: true,
- environment: "node",
- testTimeout: 30000, // 30 seconds for API calls
- },
- resolve: {
- alias: [
- { find: /^@earendil-works\/pi-ai$/, replacement: aiSrcIndex },
- { find: /^@earendil-works\/pi-ai\/oauth$/, replacement: aiSrcOAuth },
- { find: /^@mariozechner\/pi-ai$/, replacement: aiSrcIndex },
- { find: /^@mariozechner\/pi-ai\/oauth$/, replacement: aiSrcOAuth },
- ],
- },
-});
diff --git a/packages/ai/.changes/bun-primary-runtime.md b/packages/ai/.changes/bun-primary-runtime.md
new file mode 100644
index 0000000000..f0b83b5388
--- /dev/null
+++ b/packages/ai/.changes/bun-primary-runtime.md
@@ -0,0 +1 @@
+- Made Bun 1.4.0 the primary development and test runtime while preserving Node-compatible package output.
diff --git a/packages/ai/README.md b/packages/ai/README.md
index a35c679302..25f5197a19 100644
--- a/packages/ai/README.md
+++ b/packages/ai/README.md
@@ -91,7 +91,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
## Installation
```bash
-npm install prime-agent-ai
+bun add prime-agent-ai
```
TypeBox exports are re-exported from `prime-agent-ai`: `Type`, `Static`, and `TSchema`.
@@ -1150,9 +1150,9 @@ Official docs: [Application Default Credentials](https://cloud.google.com/docs/a
The quickest way to authenticate:
```bash
-npx prime-agent-ai login # interactive provider selection
-npx prime-agent-ai login anthropic # login to specific provider
-npx prime-agent-ai list # list available providers
+bunx prime-agent-ai login # interactive provider selection
+bunx prime-agent-ai login anthropic # login to specific provider
+bunx prime-agent-ai list # list available providers
```
Credentials are saved to `auth.json` in the current directory.
@@ -1272,6 +1272,8 @@ Create a new provider file (for example `amazon-bedrock.ts`) that exports:
#### 4. Model Generation (`scripts/generate-models.ts`)
+Normal builds compile the committed `src/models.generated.ts` snapshot and do not contact live provider APIs. Refresh the snapshot intentionally from the repository root with `bun run models:refresh`, then review and commit the generated diff. Live catalog changes must not make unrelated pull-request builds nondeterministic.
+
- Add logic to fetch and parse models from the provider's source (e.g., models.dev API)
- Map provider model data to the standardized `Model` interface
- Handle provider-specific quirks (pricing format, capability flags, model ID transformations)
diff --git a/packages/ai/package.json b/packages/ai/package.json
index 3e8400cb5e..c6a6e323e6 100644
--- a/packages/ai/package.json
+++ b/packages/ai/package.json
@@ -63,19 +63,19 @@
"README.md"
],
"scripts": {
- "clean": "shx rm -rf dist",
- "generate-models": "npx tsx scripts/generate-models.ts",
- "build": "npm run generate-models && tsgo -p tsconfig.build.json",
- "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
- "dev:tsc": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
- "test": "vitest --run",
- "prepublishOnly": "npm run clean && npm run build"
+ "clean": "bun ../../scripts/remove-paths.ts dist",
+ "generate-models": "bun scripts/generate-models.ts",
+ "build": "bun --bun tsgo -p tsconfig.build.json",
+ "dev": "bun --bun tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
+ "dev:tsc": "bun --bun tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
+ "test": "bun ../../scripts/run-with-clean-env.ts bun test --isolate --timeout 30000",
+ "prepublishOnly": "bun run clean && bun run build"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.91.1",
"@aws-sdk/client-bedrock-runtime": "^3.1095.0",
"@google/genai": "^1.40.0",
- "@mistralai/mistralai": "^2.2.0",
+ "@mistralai/mistralai": "2.2.1",
"typebox": "^1.3.9",
"chalk": "^5.6.2",
"openai": "6.47.0",
@@ -102,11 +102,11 @@
"directory": "packages/ai"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=20.0.0",
+ "bun": "1.4.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
- "canvas": "^3.2.0",
- "vitest": "^4.1.10"
+ "canvas": "^3.2.0"
}
}
diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts
index 1ba0fc83c4..6488f95be3 100644
--- a/packages/ai/scripts/generate-models.ts
+++ b/packages/ai/scripts/generate-models.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env tsx
+#!/usr/bin/env bun
import { readFileSync, writeFileSync } from "fs";
import { homedir } from "os";
@@ -2373,7 +2373,7 @@ async function generateModels() {
// Generate TypeScript file
let output = `// This file is auto-generated by scripts/generate-models.ts
-// Do not edit manually - run 'npm run generate-models' to update
+// Do not edit manually - run 'bun run generate-models' to update
import type { Model } from "./types.js";
diff --git a/packages/ai/scripts/generate-test-image.ts b/packages/ai/scripts/generate-test-image.ts
index 7b50eeaae6..d0bd9560d9 100644
--- a/packages/ai/scripts/generate-test-image.ts
+++ b/packages/ai/scripts/generate-test-image.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env tsx
+#!/usr/bin/env bun
import { createCanvas } from "canvas";
import { writeFileSync } from "fs";
diff --git a/packages/ai/src/cli.ts b/packages/ai/src/cli.ts
index 7a4ff1fd33..a378141c64 100644
--- a/packages/ai/src/cli.ts
+++ b/packages/ai/src/cli.ts
@@ -64,7 +64,7 @@ async function main(): Promise {
if (!command || command === "help" || command === "--help" || command === "-h") {
const providerList = PROVIDERS.map((p) => ` ${p.id.padEnd(20)} ${p.name}`).join("\n");
- console.log(`Usage: npx @earendil-works/pi-ai [provider]
+ console.log(`Usage: bunx @earendil-works/pi-ai [provider]
Commands:
login [provider] Login to an OAuth provider
@@ -74,9 +74,9 @@ Providers:
${providerList}
Examples:
- npx @earendil-works/pi-ai login # interactive provider selection
- npx @earendil-works/pi-ai login anthropic # login to specific provider
- npx @earendil-works/pi-ai list # list providers
+ bunx @earendil-works/pi-ai login # interactive provider selection
+ bunx @earendil-works/pi-ai login anthropic # login to specific provider
+ bunx @earendil-works/pi-ai list # list providers
`);
return;
}
@@ -113,7 +113,7 @@ Examples:
if (!PROVIDERS.some((p) => p.id === provider)) {
console.error(`Unknown provider: ${provider}`);
- console.error(`Use 'npx @earendil-works/pi-ai list' to see available providers`);
+ console.error(`Use 'bunx @earendil-works/pi-ai list' to see available providers`);
process.exit(1);
}
@@ -123,7 +123,7 @@ Examples:
}
console.error(`Unknown command: ${command}`);
- console.error(`Use 'npx @earendil-works/pi-ai --help' for usage`);
+ console.error(`Use 'bunx @earendil-works/pi-ai --help' for usage`);
process.exit(1);
}
diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts
index a3945be9a1..5c7b432e0c 100644
--- a/packages/ai/src/models.generated.ts
+++ b/packages/ai/src/models.generated.ts
@@ -1,5 +1,5 @@
// This file is auto-generated by scripts/generate-models.ts
-// Do not edit manually - run 'npm run generate-models' to update
+// Do not edit manually - run 'bun run generate-models' to update
import type { Model } from "./types.js";
@@ -91,6 +91,24 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"bedrock-converse-stream">,
+ "anthropic.claude-fable-5-1": {
+ id: "anthropic.claude-fable-5-1",
+ name: "Claude Fable 5.1",
+ api: "bedrock-converse-stream",
+ provider: "amazon-bedrock",
+ baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
+ input: ["text", "image"],
+ cost: {
+ input: 10,
+ output: 50,
+ cacheRead: 0.25,
+ cacheWrite: 12.5,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"bedrock-converse-stream">,
"anthropic.claude-haiku-4-5-20251001-v1:0": {
id: "anthropic.claude-haiku-4-5-20251001-v1:0",
name: "Claude Haiku 4.5",
@@ -637,6 +655,24 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"bedrock-converse-stream">,
+ "global.anthropic.claude-fable-5-1": {
+ id: "global.anthropic.claude-fable-5-1",
+ name: "Claude Fable 5.1 (Global)",
+ api: "bedrock-converse-stream",
+ provider: "amazon-bedrock",
+ baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
+ input: ["text", "image"],
+ cost: {
+ input: 10,
+ output: 50,
+ cacheRead: 0.25,
+ cacheWrite: 12.5,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"bedrock-converse-stream">,
"global.anthropic.claude-haiku-4-5-20251001-v1:0": {
id: "global.anthropic.claude-haiku-4-5-20251001-v1:0",
name: "Claude Haiku 4.5 (Global)",
@@ -1728,6 +1764,24 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"bedrock-converse-stream">,
+ "us.anthropic.claude-fable-5-1": {
+ id: "us.anthropic.claude-fable-5-1",
+ name: "Claude Fable 5.1 (US)",
+ api: "bedrock-converse-stream",
+ provider: "amazon-bedrock",
+ baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
+ input: ["text", "image"],
+ cost: {
+ input: 11,
+ output: 55,
+ cacheRead: 0.275,
+ cacheWrite: 13.75,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"bedrock-converse-stream">,
"us.anthropic.claude-haiku-4-5-20251001-v1:0": {
id: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
name: "Claude Haiku 4.5 (US)",
@@ -2094,6 +2148,24 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
+ "claude-fable-5-1": {
+ id: "claude-fable-5-1",
+ name: "Claude Fable 5.1",
+ api: "anthropic-messages",
+ provider: "anthropic",
+ baseUrl: "https://api.anthropic.com",
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
+ input: ["text", "image"],
+ cost: {
+ input: 10,
+ output: 50,
+ cacheRead: 0.25,
+ cacheWrite: 12.5,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"anthropic-messages">,
"claude-haiku-4-5": {
id: "claude-haiku-4-5",
name: "Claude Haiku 4.5 (latest)",
@@ -3904,6 +3976,24 @@ export const MODELS = {
contextWindow: 262144,
maxTokens: 256000,
} satisfies Model<"openai-completions">,
+ "@cf/zai-org/glm-5.3": {
+ id: "@cf/zai-org/glm-5.3",
+ name: "Glm 5.3",
+ api: "openai-completions",
+ provider: "cloudflare-workers-ai",
+ baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
+ compat: {"sendSessionAffinityHeaders":true},
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 1.4,
+ output: 4.4,
+ cacheRead: 0.26,
+ cacheWrite: 0,
+ },
+ contextWindow: 1310720,
+ maxTokens: 1310720,
+ } satisfies Model<"openai-completions">,
"@cf/zai-org/glm-5.3-flash": {
id: "@cf/zai-org/glm-5.3-flash",
name: "Glm 5.3 Flash",
@@ -3920,7 +4010,7 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 1310720,
- maxTokens: 1310720,
+ maxTokens: 1048576,
} satisfies Model<"openai-completions">,
},
"deepseek": {
@@ -3964,23 +4054,6 @@ export const MODELS = {
} satisfies Model<"openai-completions">,
},
"fireworks": {
- "accounts/fireworks/models/deepseek-v4-flash": {
- id: "accounts/fireworks/models/deepseek-v4-flash",
- name: "DeepSeek V4 Flash",
- api: "anthropic-messages",
- provider: "fireworks",
- baseUrl: "https://api.fireworks.ai/inference",
- reasoning: true,
- input: ["text"],
- cost: {
- input: 0.14,
- output: 0.28,
- cacheRead: 0.028,
- cacheWrite: 0,
- },
- contextWindow: 1000000,
- maxTokens: 384000,
- } satisfies Model<"anthropic-messages">,
"accounts/fireworks/models/deepseek-v4-flash-0731": {
id: "accounts/fireworks/models/deepseek-v4-flash-0731",
name: "DeepSeek V4 Flash 0731",
@@ -3990,9 +4063,9 @@ export const MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.14,
- output: 0.28,
- cacheRead: 0.028,
+ input: 0.22,
+ output: 0.66,
+ cacheRead: 0.007,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -4032,6 +4105,40 @@ export const MODELS = {
contextWindow: 1048575,
maxTokens: 131072,
} satisfies Model<"anthropic-messages">,
+ "accounts/fireworks/models/glm-5p3": {
+ id: "accounts/fireworks/models/glm-5p3",
+ name: "GLM 5.3",
+ api: "anthropic-messages",
+ provider: "fireworks",
+ baseUrl: "https://api.fireworks.ai/inference",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 1.4,
+ output: 4.4,
+ cacheRead: 0.26,
+ cacheWrite: 0,
+ },
+ contextWindow: 1000000,
+ maxTokens: 131072,
+ } satisfies Model<"anthropic-messages">,
+ "accounts/fireworks/models/glm-5p3-flash": {
+ id: "accounts/fireworks/models/glm-5p3-flash",
+ name: "GLM 5.3 Flash",
+ api: "anthropic-messages",
+ provider: "fireworks",
+ baseUrl: "https://api.fireworks.ai/inference",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0.15,
+ output: 0.5,
+ cacheRead: 0.029,
+ cacheWrite: 0,
+ },
+ contextWindow: 1000000,
+ maxTokens: 131072,
+ } satisfies Model<"anthropic-messages">,
"accounts/fireworks/models/gpt-oss-120b": {
id: "accounts/fireworks/models/gpt-oss-120b",
name: "GPT OSS 120B",
@@ -5133,23 +5240,6 @@ export const MODELS = {
contextWindow: 1048576,
maxTokens: 65536,
} satisfies Model<"google-generative-ai">,
- "gemini-robotics-er-1.6-preview": {
- id: "gemini-robotics-er-1.6-preview",
- name: "Gemini Robotics-ER 1.6 Preview",
- api: "google-generative-ai",
- provider: "google",
- baseUrl: "https://generativelanguage.googleapis.com/v1beta",
- reasoning: true,
- input: ["text", "image"],
- cost: {
- input: 1,
- output: 5,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 131072,
- maxTokens: 65536,
- } satisfies Model<"google-generative-ai">,
"gemma-4-26b-a4b-it": {
id: "gemma-4-26b-a4b-it",
name: "Gemma 4 26B A4B IT",
@@ -5517,6 +5607,23 @@ export const MODELS = {
contextWindow: 131072,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
+ "qwen/qwen3.8-27b": {
+ id: "qwen/qwen3.8-27b",
+ name: "Qwen3.8 27B",
+ api: "openai-completions",
+ provider: "groq",
+ baseUrl: "https://api.groq.com/openai/v1",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0.8,
+ output: 4,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 131042,
+ maxTokens: 16384,
+ } satisfies Model<"openai-completions">,
},
"huggingface": {
"MiniMaxAI/MiniMax-M2": {
@@ -6744,6 +6851,24 @@ export const MODELS = {
contextWindow: 262144,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
+ "zai-org/GLM-5.3": {
+ id: "zai-org/GLM-5.3",
+ name: "GLM-5.3",
+ api: "openai-completions",
+ provider: "huggingface",
+ baseUrl: "https://router.huggingface.co/v1",
+ compat: {"supportsDeveloperRole":false},
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 1.4,
+ output: 4.4,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 131072,
+ } satisfies Model<"openai-completions">,
"zai-org/GLM-5.3-Flash": {
id: "zai-org/GLM-5.3-Flash",
name: "GLM-5.3-Flash",
@@ -6752,7 +6877,7 @@ export const MODELS = {
baseUrl: "https://router.huggingface.co/v1",
compat: {"supportsDeveloperRole":false},
reasoning: true,
- input: ["text"],
+ input: ["text", "image"],
cost: {
input: 0.15,
output: 0.5,
@@ -9582,23 +9707,6 @@ export const MODELS = {
contextWindow: 256000,
maxTokens: 256000,
} satisfies Model<"openai-responses">,
- "hy3-free": {
- id: "hy3-free",
- name: "Hy3 Free",
- api: "openai-completions",
- provider: "opencode",
- baseUrl: "https://opencode.ai/zen/v1",
- reasoning: true,
- input: ["text"],
- cost: {
- input: 0,
- output: 0,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 190000,
- maxTokens: 64000,
- } satisfies Model<"openai-completions">,
"kimi-k2.5": {
id: "kimi-k2.5",
name: "Kimi K2.5",
@@ -9668,6 +9776,23 @@ export const MODELS = {
contextWindow: 1048576,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
+ "ling-3.0-flash-fin-free": {
+ id: "ling-3.0-flash-fin-free",
+ name: "Ling 3.0 Flash Fin Free",
+ api: "openai-completions",
+ provider: "opencode",
+ baseUrl: "https://opencode.ai/zen/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 32768,
+ } satisfies Model<"openai-completions">,
"mimo-v2.5-free": {
id: "mimo-v2.5-free",
name: "MiMo V2.5 Free",
@@ -10002,21 +10127,38 @@ export const MODELS = {
} satisfies Model<"openai-responses">,
"hy3": {
id: "hy3",
- name: "Hy3 (8x usage)",
+ name: "Hy3",
api: "openai-completions",
provider: "opencode-go",
baseUrl: "https://opencode.ai/zen/go/v1",
reasoning: true,
input: ["text"],
cost: {
- input: 0.0175,
- output: 0.0725,
- cacheRead: 0.004375,
+ input: 0.14,
+ output: 0.58,
+ cacheRead: 0.035,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 64000,
} satisfies Model<"openai-completions">,
+ "hy4-preview": {
+ id: "hy4-preview",
+ name: "Hy4 preview",
+ api: "openai-completions",
+ provider: "opencode-go",
+ baseUrl: "https://opencode.ai/zen/go/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0.834,
+ output: 2.501,
+ cacheRead: 0.042,
+ cacheWrite: 0,
+ },
+ contextWindow: 1024000,
+ maxTokens: 64000,
+ } satisfies Model<"openai-completions">,
"kimi-k2.6": {
id: "kimi-k2.6",
name: "Kimi K2.6",
@@ -10438,6 +10580,24 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
+ "anthropic/claude-fable-5.1": {
+ id: "anthropic/claude-fable-5.1",
+ name: "Anthropic: Claude Fable 5.1",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"},
+ input: ["text", "image"],
+ cost: {
+ input: 10,
+ output: 50,
+ cacheRead: 0.25,
+ cacheWrite: 12.5,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"openai-completions">,
"anthropic/claude-haiku-4.5": {
id: "anthropic/claude-haiku-4.5",
name: "Anthropic: Claude Haiku 4.5",
@@ -10550,9 +10710,9 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
- "anthropic/claude-opus-4.7-fast": {
- id: "anthropic/claude-opus-4.7-fast",
- name: "Anthropic: Claude Opus 4.7 (Fast)",
+ "anthropic/claude-opus-4.8": {
+ id: "anthropic/claude-opus-4.8",
+ name: "Anthropic: Claude Opus 4.8",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
@@ -10560,17 +10720,17 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
- input: 30,
- output: 150,
- cacheRead: 3,
- cacheWrite: 37.5,
+ input: 5,
+ output: 25,
+ cacheRead: 0.5,
+ cacheWrite: 6.25,
},
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
- "anthropic/claude-opus-4.8": {
- id: "anthropic/claude-opus-4.8",
- name: "Anthropic: Claude Opus 4.8",
+ "anthropic/claude-opus-5": {
+ id: "anthropic/claude-opus-5",
+ name: "Claude Opus 5",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
@@ -10586,69 +10746,15 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
- "anthropic/claude-opus-4.8-fast": {
- id: "anthropic/claude-opus-4.8-fast",
- name: "Anthropic: Claude Opus 4.8 (Fast)",
+ "anthropic/claude-sonnet-4": {
+ id: "anthropic/claude-sonnet-4",
+ name: "Anthropic: Claude Sonnet 4",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
+ compat: {"supportsReasoningEffort":false},
reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"},
- input: ["text", "image"],
- cost: {
- input: 10,
- output: 50,
- cacheRead: 1,
- cacheWrite: 12.5,
- },
- contextWindow: 1000000,
- maxTokens: 128000,
- } satisfies Model<"openai-completions">,
- "anthropic/claude-opus-5": {
- id: "anthropic/claude-opus-5",
- name: "Claude Opus 5",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"},
- input: ["text", "image"],
- cost: {
- input: 5,
- output: 25,
- cacheRead: 0.5,
- cacheWrite: 6.25,
- },
- contextWindow: 1000000,
- maxTokens: 128000,
- } satisfies Model<"openai-completions">,
- "anthropic/claude-opus-5-fast": {
- id: "anthropic/claude-opus-5-fast",
- name: "Claude Opus 5 (Fast)",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"},
- input: ["text", "image"],
- cost: {
- input: 10,
- output: 50,
- cacheRead: 1,
- cacheWrite: 12.5,
- },
- contextWindow: 1000000,
- maxTokens: 128000,
- } satisfies Model<"openai-completions">,
- "anthropic/claude-sonnet-4": {
- id: "anthropic/claude-sonnet-4",
- name: "Anthropic: Claude Sonnet 4",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- compat: {"supportsReasoningEffort":false},
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null},
+ thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null},
input: ["text", "image"],
cost: {
input: 3,
@@ -10725,30 +10831,13 @@ export const MODELS = {
thinkingLevelMap: {"off":null,"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null},
input: ["text"],
cost: {
- input: 0.22,
- output: 0.85,
+ input: 0.25,
+ output: 0.7999999999999999,
cacheRead: 0.06,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 235929,
- } satisfies Model<"openai-completions">,
- "arcee-ai/virtuoso-large": {
- id: "arcee-ai/virtuoso-large",
- name: "Arcee AI: Virtuoso Large",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- reasoning: false,
- input: ["text"],
- cost: {
- input: 0.75,
- output: 1.2,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 131072,
- maxTokens: 64000,
+ maxTokens: 80000,
} satisfies Model<"openai-completions">,
"auto": {
id: "auto",
@@ -11109,9 +11198,9 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null},
input: ["text"],
cost: {
- input: 0.07,
- output: 0.14,
- cacheRead: 0.014,
+ input: 0.065,
+ output: 0.18,
+ cacheRead: 0.016,
cacheWrite: 0,
},
contextWindow: 1310720,
@@ -11128,9 +11217,9 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null},
input: ["text", "image"],
cost: {
- input: 0.44,
- output: 1.32,
- cacheRead: 0.014,
+ input: 0.22,
+ output: 0.66,
+ cacheRead: 0.007,
cacheWrite: 0,
},
contextWindow: 1048576,
@@ -11147,9 +11236,9 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null},
input: ["text"],
cost: {
- input: 0.87,
- output: 1.74,
- cacheRead: 0.0725,
+ input: 1.04226,
+ output: 2.08452,
+ cacheRead: 0.086855,
cacheWrite: 0,
},
contextWindow: 1048576,
@@ -11166,9 +11255,9 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null},
input: ["text"],
cost: {
- input: 1.32,
- output: 3.9600000000000004,
- cacheRead: 0.044,
+ input: 0.66,
+ output: 1.9800000000000002,
+ cacheRead: 0.022,
cacheWrite: 0,
},
contextWindow: 1048576,
@@ -11461,10 +11550,10 @@ export const MODELS = {
thinkingLevelMap: {"off":null,"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":null,"max":null},
input: ["text", "image"],
cost: {
- input: 0.375,
- output: 1.875,
- cacheRead: 0.0375,
- cacheWrite: 0.0208333333333333,
+ input: 0.75,
+ output: 3.75,
+ cacheRead: 0.075,
+ cacheWrite: 0.0416666666666667,
},
contextWindow: 1048576,
maxTokens: 65536,
@@ -11500,7 +11589,7 @@ export const MODELS = {
cacheRead: 0.04,
cacheWrite: 0,
},
- contextWindow: 262144,
+ contextWindow: 131072,
maxTokens: 117964,
} satisfies Model<"openai-completions">,
"google/gemma-4-26b-a4b-it": {
@@ -11596,6 +11685,24 @@ export const MODELS = {
contextWindow: 131072,
maxTokens: 117964,
} satisfies Model<"openai-completions">,
+ "ibm-granite/granite-4.2-8b": {
+ id: "ibm-granite/granite-4.2-8b",
+ name: "IBM: Granite 4.2 8B",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ thinkingLevelMap: {"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":null},
+ input: ["text"],
+ cost: {
+ input: 0.09999999999999999,
+ output: 0.15,
+ cacheRead: 0.049999999999999996,
+ cacheWrite: 0,
+ },
+ contextWindow: 131072,
+ maxTokens: 117964,
+ } satisfies Model<"openai-completions">,
"inception/mercury-2": {
id: "inception/mercury-2",
name: "Inception: Mercury 2",
@@ -11614,6 +11721,24 @@ export const MODELS = {
contextWindow: 128000,
maxTokens: 50000,
} satisfies Model<"openai-completions">,
+ "inception/mercury-2.5-preview": {
+ id: "inception/mercury-2.5-preview",
+ name: "Inception: Mercury 2.5 Preview",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":null,"max":null},
+ input: ["text"],
+ cost: {
+ input: 0.04,
+ output: 0.15,
+ cacheRead: 0.004,
+ cacheWrite: 0,
+ },
+ contextWindow: 260000,
+ maxTokens: 65536,
+ } satisfies Model<"openai-completions">,
"inclusionai/ling-3.0-flash": {
id: "inclusionai/ling-3.0-flash",
name: "Ling-3.0-flash",
@@ -11652,23 +11777,6 @@ export const MODELS = {
contextWindow: 262144,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
- "kwaipilot/kat-coder-air-v2.5": {
- id: "kwaipilot/kat-coder-air-v2.5",
- name: "Kwaipilot: KAT-Coder-Air V2.5",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- reasoning: false,
- input: ["text"],
- cost: {
- input: 0.15,
- output: 0.6,
- cacheRead: 0.03,
- cacheWrite: 0,
- },
- contextWindow: 256000,
- maxTokens: 80000,
- } satisfies Model<"openai-completions">,
"kwaipilot/kat-coder-pro-v2": {
id: "kwaipilot/kat-coder-pro-v2",
name: "Kwaipilot: KAT-Coder-Pro V2",
@@ -11684,7 +11792,7 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 80000,
+ maxTokens: 144000,
} satisfies Model<"openai-completions">,
"kwaipilot/kat-coder-pro-v2.5": {
id: "kwaipilot/kat-coder-pro-v2.5",
@@ -11701,7 +11809,7 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 80000,
+ maxTokens: 235929,
} satisfies Model<"openai-completions">,
"liquid/lfm-2.5-2.6b:free": {
id: "liquid/lfm-2.5-2.6b:free",
@@ -11802,12 +11910,12 @@ export const MODELS = {
input: ["text", "image"],
cost: {
input: 0.19999999999999998,
- output: 0.7999999999999999,
+ output: 0.696,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 1048576,
- maxTokens: 16384,
+ maxTokens: 115200,
} satisfies Model<"openai-completions">,
"meta-llama/llama-4-scout": {
id: "meta-llama/llama-4-scout",
@@ -11818,13 +11926,13 @@ export const MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.11,
- output: 0.33999999999999997,
- cacheRead: 0.055,
+ input: 0.09999999999999999,
+ output: 0.3,
+ cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 1310720,
- maxTokens: 8192,
+ maxTokens: 16384,
} satisfies Model<"openai-completions">,
"meta/muse-glimmer-30b": {
id: "meta/muse-glimmer-30b",
@@ -11836,13 +11944,13 @@ export const MODELS = {
thinkingLevelMap: {"off":null,"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":null},
input: ["text", "image"],
cost: {
- input: 0.35,
- output: 1.5,
+ input: 0.3,
+ output: 1.2,
cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 131072,
- maxTokens: 117964,
+ maxTokens: 16384,
} satisfies Model<"openai-completions">,
"meta/muse-spark-1.1": {
id: "meta/muse-spark-1.1",
@@ -12076,9 +12184,9 @@ export const MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.44,
- output: 2.2,
- cacheRead: 0.044,
+ input: 0.39999999999999997,
+ output: 2,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -12338,8 +12446,8 @@ export const MODELS = {
cacheRead: 0.01,
cacheWrite: 0,
},
- contextWindow: 32000,
- maxTokens: 25600,
+ contextWindow: 32768,
+ maxTokens: 26214,
} satisfies Model<"openai-completions">,
"moonshotai/kimi-k2": {
id: "moonshotai/kimi-k2",
@@ -12628,13 +12736,13 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null},
input: ["text"],
cost: {
- input: 0.09999999999999999,
- output: 0.25,
- cacheRead: 0.049999999999999996,
+ input: 0.08,
+ output: 0.19999999999999998,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 235929,
+ maxTokens: 131072,
} satisfies Model<"openai-completions">,
"nvidia/nemotron-3.5-lightning:free": {
id: "nvidia/nemotron-3.5-lightning:free",
@@ -14161,13 +14269,13 @@ export const MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.13,
- output: 0.52,
+ input: 0.15,
+ output: 0.6,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 32768,
+ maxTokens: 16384,
} satisfies Model<"openai-completions">,
"qwen/qwen3-vl-30b-a3b-thinking": {
id: "qwen/qwen3-vl-30b-a3b-thinking",
@@ -14252,13 +14360,13 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null},
input: ["text", "image"],
cost: {
- input: 0.26,
- output: 2.08,
+ input: 0.29,
+ output: 2.4,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 235929,
+ maxTokens: 81920,
} satisfies Model<"openai-completions">,
"qwen/qwen3.5-27b": {
id: "qwen/qwen3.5-27b",
@@ -14557,11 +14665,11 @@ export const MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0.25,
+ cacheRead: 0.19999999999999998,
cacheWrite: 0,
},
contextWindow: 1048576,
- maxTokens: 262144,
+ maxTokens: 131072,
} satisfies Model<"openai-completions">,
"qwen/qwen3.8-27b": {
id: "qwen/qwen3.8-27b",
@@ -14752,9 +14860,9 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":null},
input: ["text"],
cost: {
- input: 0.13199999999999998,
- output: 0.5279999999999999,
- cacheRead: 0.032999999999999995,
+ input: 0.0825,
+ output: 0.33,
+ cacheRead: 0.020625,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -14823,13 +14931,13 @@ export const MODELS = {
thinkingLevelMap: {"minimal":"minimal","low":"low","medium":"medium","high":"high","xhigh":null,"max":"max"},
input: ["text", "image"],
cost: {
- input: 0.95,
+ input: 1,
output: 4.05,
- cacheRead: 0.16,
+ cacheRead: 0.16999999999999998,
cacheWrite: 0,
},
contextWindow: 1048576,
- maxTokens: 262144,
+ maxTokens: 471859,
} satisfies Model<"openai-completions">,
"thinkingmachines/inkling-small": {
id: "thinkingmachines/inkling-small",
@@ -15235,13 +15343,13 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null,"max":null},
input: ["text"],
cost: {
- input: 1.26,
- output: 3.9600000000000004,
- cacheRead: 0.234,
+ input: 0.966,
+ output: 3.036,
+ cacheRead: 0.1794,
cacheWrite: 0,
},
contextWindow: 204800,
- maxTokens: 182476,
+ maxTokens: 128000,
} satisfies Model<"openai-completions">,
"z-ai/glm-5.2": {
id: "z-ai/glm-5.2",
@@ -15294,7 +15402,7 @@ export const MODELS = {
cacheRead: 0.26,
cacheWrite: 0,
},
- contextWindow: 1048576,
+ contextWindow: 1310720,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"z-ai/glm-5.3-flash": {
@@ -15346,7 +15454,7 @@ export const MODELS = {
cost: {
input: 10,
output: 50,
- cacheRead: 1,
+ cacheRead: 0.25,
cacheWrite: 12.5,
},
contextWindow: 1000000,
@@ -15418,13 +15526,13 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max","max":null},
input: ["text"],
cost: {
- input: 0.03,
- output: 0.09999999999999999,
- cacheRead: 0.007,
+ input: 0.049999999999999996,
+ output: 0.16,
+ cacheRead: 0.013000000000000001,
cacheWrite: 0,
},
contextWindow: 1310720,
- maxTokens: 131072,
+ maxTokens: 393216,
} satisfies Model<"openai-completions">,
"~google/gemini-flash-latest": {
id: "~google/gemini-flash-latest",
@@ -15436,10 +15544,10 @@ export const MODELS = {
thinkingLevelMap: {"off":null,"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":null,"max":null},
input: ["text", "image"],
cost: {
- input: 0.375,
- output: 1.875,
- cacheRead: 0.0375,
- cacheWrite: 0.0208333333333333,
+ input: 0.75,
+ output: 3.75,
+ cacheRead: 0.075,
+ cacheWrite: 0.0416666666666667,
},
contextWindow: 1048576,
maxTokens: 65536,
@@ -15544,13 +15652,13 @@ export const MODELS = {
thinkingLevelMap: {"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"},
input: ["text"],
cost: {
- input: 1.4,
- output: 4.4,
- cacheRead: 0.26,
+ input: 1.17,
+ output: 3.9600000000000004,
+ cacheRead: 0.234,
cacheWrite: 0,
},
- contextWindow: 1048576,
- maxTokens: 131072,
+ contextWindow: 1310720,
+ maxTokens: 943718,
} satisfies Model<"openai-completions">,
},
"prime-inference": {
@@ -16129,7 +16237,7 @@ export const MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
- contextWindow: 262144,
+ contextWindow: 131072,
maxTokens: 117964,
} satisfies Model<"openai-completions">,
"meta-llama/Llama-3.2-1B-Instruct": {
@@ -16202,7 +16310,26 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 1048576,
- maxTokens: 16384,
+ maxTokens: 115200,
+ } satisfies Model<"openai-completions">,
+ "meta/muse-spark-1.2": {
+ id: "meta/muse-spark-1.2",
+ name: "Muse Spark 1.2",
+ api: "openai-completions",
+ provider: "prime-inference",
+ baseUrl: "https://api.pinference.ai/api/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false},
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"minimal":"minimal","low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":null},
+ input: ["text", "image"],
+ cost: {
+ input: 1.25,
+ output: 4.25,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 943718,
} satisfies Model<"openai-completions">,
"minimax/minimax-m2.5": {
id: "minimax/minimax-m2.5",
@@ -16824,8 +16951,8 @@ export const MODELS = {
thinkingLevelMap: {"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":"max"},
input: ["text", "image"],
cost: {
- input: 1,
- output: 6,
+ input: 0.2,
+ output: 1.2,
cacheRead: 0,
cacheWrite: 0,
},
@@ -17169,7 +17296,7 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 32768,
+ maxTokens: 16384,
} satisfies Model<"openai-completions">,
"qwen/qwen3-vl-8b-instruct": {
id: "qwen/qwen3-vl-8b-instruct",
@@ -17344,6 +17471,25 @@ export const MODELS = {
maxTokens: 30000,
featured: true,
} satisfies Model<"openai-completions">,
+ "x-ai/grok-4.6": {
+ id: "x-ai/grok-4.6",
+ name: "Grok 4.6",
+ api: "openai-completions",
+ provider: "prime-inference",
+ baseUrl: "https://api.pinference.ai/api/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false},
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"minimal":null,"low":"low","medium":"medium","high":"high","xhigh":"xhigh","max":null},
+ input: ["text", "image"],
+ cost: {
+ input: 2,
+ output: 6,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 500000,
+ maxTokens: 450000,
+ } satisfies Model<"openai-completions">,
"xiaomi/mimo-v2.5": {
id: "xiaomi/mimo-v2.5",
name: "Mimo V2.5",
@@ -17514,7 +17660,7 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 204800,
- maxTokens: 182476,
+ maxTokens: 128000,
featured: true,
} satisfies Model<"openai-completions">,
"z-ai/glm-5.2": {
@@ -17553,7 +17699,7 @@ export const MODELS = {
cacheRead: 0,
cacheWrite: 0,
},
- contextWindow: 1048576,
+ contextWindow: 1310720,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"z-ai/glm-5.3-flash": {
@@ -18013,11 +18159,11 @@ export const MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.25,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 131072,
+ maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
"alibaba/qwen3.8-27b": {
id: "alibaba/qwen3.8-27b",
@@ -18028,10 +18174,10 @@ export const MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.55,
- output: 3.3000000000000003,
- cacheRead: 0.11,
- cacheWrite: 0,
+ input: 0.5,
+ output: 3,
+ cacheRead: 0.09999999999999999,
+ cacheWrite: 0.625,
},
contextWindow: 1000000,
maxTokens: 131072,
@@ -18053,6 +18199,23 @@ export const MODELS = {
contextWindow: 991000,
maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
+ "alibaba/qwen3.8-flash-next": {
+ id: "alibaba/qwen3.8-flash-next",
+ name: "Qwen 3.8 Flash Next",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0.12,
+ output: 0.39999999999999997,
+ cacheRead: 0.01,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 1048576,
+ } satisfies Model<"anthropic-messages">,
"alibaba/qwen3.8-max": {
id: "alibaba/qwen3.8-max",
name: "Qwen 3.8 Max",
@@ -18173,6 +18336,24 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
+ "anthropic/claude-fable-5.1": {
+ id: "anthropic/claude-fable-5.1",
+ name: "Claude Fable 5.1",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"xhigh":"xhigh","max":"max"},
+ input: ["text", "image"],
+ cost: {
+ input: 10,
+ output: 50,
+ cacheRead: 0.25,
+ cacheWrite: 12.5,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"anthropic-messages">,
"anthropic/claude-haiku-4.5": {
id: "anthropic/claude-haiku-4.5",
name: "Claude Haiku 4.5",
@@ -18487,23 +18668,6 @@ export const MODELS = {
contextWindow: 128000,
maxTokens: 8192,
} satisfies Model<"anthropic-messages">,
- "deepseek/deepseek-v3": {
- id: "deepseek/deepseek-v3",
- name: "DeepSeek V3 0324",
- api: "anthropic-messages",
- provider: "vercel-ai-gateway",
- baseUrl: "https://ai-gateway.vercel.sh",
- reasoning: false,
- input: ["text"],
- cost: {
- input: 0.27,
- output: 1.12,
- cacheRead: 0.135,
- cacheWrite: 0,
- },
- contextWindow: 163840,
- maxTokens: 163840,
- } satisfies Model<"anthropic-messages">,
"deepseek/deepseek-v3.1": {
id: "deepseek/deepseek-v3.1",
name: "DeepSeek V3.1",
@@ -18620,8 +18784,8 @@ export const MODELS = {
cacheRead: 0.007,
cacheWrite: 0,
},
- contextWindow: 1000000,
- maxTokens: 384000,
+ contextWindow: 1048576,
+ maxTokens: 1048576,
} satisfies Model<"anthropic-messages">,
"deepseek/deepseek-v4-pro": {
id: "deepseek/deepseek-v4-pro",
@@ -18632,13 +18796,13 @@ export const MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 1.74,
- output: 3.48,
- cacheRead: 0.14,
+ input: 0.66,
+ output: 1.9800000000000002,
+ cacheRead: 0.022,
cacheWrite: 0,
},
- contextWindow: 1048600,
- maxTokens: 1048600,
+ contextWindow: 1000000,
+ maxTokens: 384000,
} satisfies Model<"anthropic-messages">,
"deepseek/deepseek-v4-pro-0813": {
id: "deepseek/deepseek-v4-pro-0813",
@@ -19271,7 +19435,7 @@ export const MODELS = {
} satisfies Model<"anthropic-messages">,
"minimax/minimax-m2.7": {
id: "minimax/minimax-m2.7",
- name: "Minimax M2.7",
+ name: "MiniMax M2.7",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
@@ -19288,7 +19452,7 @@ export const MODELS = {
} satisfies Model<"anthropic-messages">,
"minimax/minimax-m2.7-free": {
id: "minimax/minimax-m2.7-free",
- name: "Minimax M2.7 (Free)",
+ name: "MiniMax M2.7 (Free)",
api: "anthropic-messages",
provider: "vercel-ai-gateway",
baseUrl: "https://ai-gateway.vercel.sh",
@@ -19334,8 +19498,8 @@ export const MODELS = {
cacheRead: 0.06,
cacheWrite: 0,
},
- contextWindow: 1000000,
- maxTokens: 1000000,
+ contextWindow: 512000,
+ maxTokens: 512000,
} satisfies Model<"anthropic-messages">,
"minimax/minimax-m3-free": {
id: "minimax/minimax-m3-free",
@@ -19637,7 +19801,7 @@ export const MODELS = {
cost: {
input: 0.95,
output: 4,
- cacheRead: 0.19,
+ cacheRead: 0.16,
cacheWrite: 0,
},
contextWindow: 256000,
@@ -19757,8 +19921,8 @@ export const MODELS = {
input: ["text"],
cost: {
input: 0.049999999999999996,
- output: 0.15,
- cacheRead: 0.049999999999999996,
+ output: 0.19999999999999998,
+ cacheRead: 0.01,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -21086,13 +21250,30 @@ export const MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.13199999999999998,
- output: 0.5279999999999999,
- cacheRead: 0.032999999999999995,
+ input: 0.14,
+ output: 0.58,
+ cacheRead: 0.035,
cacheWrite: 0,
},
- contextWindow: 256000,
- maxTokens: 128000,
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"anthropic-messages">,
+ "tencent/hy4-preview": {
+ id: "tencent/hy4-preview",
+ name: "Tencent Hy4 Preview",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0.834,
+ output: 2.501,
+ cacheRead: 0.041999999999999996,
+ cacheWrite: 0,
+ },
+ contextWindow: 1024000,
+ maxTokens: 64000,
} satisfies Model<"anthropic-messages">,
"thinkingmachines/inkling": {
id: "thinkingmachines/inkling",
@@ -21162,6 +21343,23 @@ export const MODELS = {
contextWindow: 1050000,
maxTokens: 131000,
} satisfies Model<"anthropic-messages">,
+ "xiaomi/mimo-v2.5-pro-ultraspeed": {
+ id: "xiaomi/mimo-v2.5-pro-ultraspeed",
+ name: "MiMo V2.5 Pro UltraSpeed",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 1.305,
+ output: 2.61,
+ cacheRead: 0.0108,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 131072,
+ } satisfies Model<"anthropic-messages">,
"zai/glm-4.5": {
id: "zai/glm-4.5",
name: "GLM 4.5",
@@ -21377,11 +21575,11 @@ export const MODELS = {
cost: {
input: 1.4,
output: 4.4,
- cacheRead: 0.26,
+ cacheRead: 0.14,
cacheWrite: 0,
},
contextWindow: 1000000,
- maxTokens: 12800,
+ maxTokens: 1000000,
} satisfies Model<"anthropic-messages">,
"zai/glm-5.3-flash": {
id: "zai/glm-5.3-flash",
diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts
index 0a540c337e..76eda44a36 100644
--- a/packages/ai/src/providers/amazon-bedrock.ts
+++ b/packages/ai/src/providers/amazon-bedrock.ts
@@ -208,6 +208,9 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt
if (nextCommandInput !== undefined) {
commandInput = nextCommandInput as typeof commandInput;
}
+ if (options.signal?.aborted) {
+ throw new Error("Request was aborted");
+ }
const command = new ConverseStreamCommand(commandInput);
const response = await client.send(command, { abortSignal: options.signal });
diff --git a/packages/ai/src/providers/register-builtins.ts b/packages/ai/src/providers/register-builtins.ts
index 6447568084..5ed356d285 100644
--- a/packages/ai/src/providers/register-builtins.ts
+++ b/packages/ai/src/providers/register-builtins.ts
@@ -115,17 +115,32 @@ let openAIResponsesProviderModulePromise:
let bedrockProviderModuleOverride:
| LazyProviderModule<"bedrock-converse-stream", BedrockOptions, SimpleStreamOptions>
| undefined;
+let bedrockProviderModuleLoaderOverride: (() => Promise) | undefined;
let bedrockProviderModulePromise:
| Promise>
| undefined;
-export function setBedrockProviderModule(module: BedrockProviderModule): void {
- bedrockProviderModuleOverride = {
+function toLazyBedrockProviderModule(
+ module: BedrockProviderModule,
+): LazyProviderModule<"bedrock-converse-stream", BedrockOptions, SimpleStreamOptions> {
+ return {
stream: module.streamBedrock,
streamSimple: module.streamSimpleBedrock,
};
}
+export function setBedrockProviderModule(module: BedrockProviderModule): void {
+ bedrockProviderModuleLoaderOverride = undefined;
+ bedrockProviderModulePromise = undefined;
+ bedrockProviderModuleOverride = toLazyBedrockProviderModule(module);
+}
+
+export function setBedrockProviderModuleLoader(loader: () => Promise): void {
+ bedrockProviderModuleOverride = undefined;
+ bedrockProviderModulePromise = undefined;
+ bedrockProviderModuleLoaderOverride = loader;
+}
+
function forwardStream(target: AssistantMessageEventStream, source: AsyncIterable): void {
(async () => {
for await (const event of source) {
@@ -310,13 +325,9 @@ function loadBedrockProviderModule(): Promise<
if (bedrockProviderModuleOverride) {
return Promise.resolve(bedrockProviderModuleOverride);
}
- bedrockProviderModulePromise ||= importNodeOnlyProvider("./amazon-bedrock.js").then((module) => {
- const provider = module as BedrockProviderModule;
- return {
- stream: provider.streamBedrock,
- streamSimple: provider.streamSimpleBedrock,
- };
- });
+ bedrockProviderModulePromise ||= (
+ bedrockProviderModuleLoaderOverride?.() ?? importNodeOnlyProvider("./amazon-bedrock.js")
+ ).then((module) => toLazyBedrockProviderModule(module as BedrockProviderModule));
return bedrockProviderModulePromise;
}
diff --git a/packages/ai/test/anthropic-oauth.test.ts b/packages/ai/test/anthropic-oauth.test.ts
index f8e066c669..49b8d790aa 100644
--- a/packages/ai/test/anthropic-oauth.test.ts
+++ b/packages/ai/test/anthropic-oauth.test.ts
@@ -1,4 +1,4 @@
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it, vi } from "bun:test";
import { loginAnthropic, refreshAnthropicToken } from "../src/utils/oauth/anthropic.js";
function jsonResponse(body: unknown, status: number = 200): Response {
@@ -30,12 +30,17 @@ function getJsonBody(init?: RequestInit): Record {
return JSON.parse(init.body) as Record;
}
-describe.sequential("Anthropic OAuth", () => {
+describe("Anthropic OAuth", () => {
+ let origFetch: typeof globalThis.fetch | undefined;
afterEach(() => {
- vi.unstubAllGlobals();
+ if (origFetch !== undefined) {
+ globalThis.fetch = origFetch;
+ origFetch = undefined;
+ }
});
it("keeps the localhost redirect_uri for manual callback login", async () => {
+ origFetch = globalThis.fetch;
let authUrl = "";
const fetchMock = vi.fn(async (input: unknown, init?: RequestInit): Promise => {
expect(getUrl(input)).toBe("https://platform.claude.com/v1/oauth/token");
@@ -50,7 +55,7 @@ describe.sequential("Anthropic OAuth", () => {
expires_in: 3600,
});
});
- vi.stubGlobal("fetch", fetchMock);
+ globalThis.fetch = fetchMock;
const credentials = await loginAnthropic({
onAuth: (info) => {
@@ -70,10 +75,11 @@ describe.sequential("Anthropic OAuth", () => {
expect(credentials.access).toBe("access-token");
expect(credentials.refresh).toBe("refresh-token");
- expect(fetchMock).toHaveBeenCalledOnce();
+ expect(fetchMock).toHaveBeenCalledTimes(1);
});
it("omits scope from refresh token requests", async () => {
+ origFetch = globalThis.fetch;
const fetchMock = vi.fn(async (input: unknown, init?: RequestInit): Promise => {
expect(getUrl(input)).toBe("https://platform.claude.com/v1/oauth/token");
expect(init?.method).toBe("POST");
@@ -88,12 +94,12 @@ describe.sequential("Anthropic OAuth", () => {
expires_in: 3600,
});
});
- vi.stubGlobal("fetch", fetchMock);
+ globalThis.fetch = fetchMock;
const credentials = await refreshAnthropicToken("refresh-token");
expect(credentials.access).toBe("new-access-token");
expect(credentials.refresh).toBe("new-refresh-token");
- expect(fetchMock).toHaveBeenCalledOnce();
+ expect(fetchMock).toHaveBeenCalledTimes(1);
});
});
diff --git a/packages/ai/test/azure-openai-base-url.test.ts b/packages/ai/test/azure-openai-base-url.test.ts
index 3a8e9c1916..4271eb1c52 100644
--- a/packages/ai/test/azure-openai-base-url.test.ts
+++ b/packages/ai/test/azure-openai-base-url.test.ts
@@ -1,4 +1,4 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
import { getModel } from "../src/models.js";
import { streamAzureOpenAIResponses } from "../src/providers/azure-openai-responses.js";
import type { Context } from "../src/types.js";
@@ -11,11 +11,11 @@ interface CapturedAzureClientOptions {
baseURL: string;
}
-const azureMock = vi.hoisted(() => ({
- constructorCalls: [] as CapturedAzureClientOptions[],
-}));
+const azureMock: { constructorCalls: CapturedAzureClientOptions[] } = {
+ constructorCalls: [],
+};
-vi.mock("openai", () => {
+mock.module("openai", () => {
class AzureOpenAI {
responses = {
create: () => {
diff --git a/packages/ai/test/bedrock-endpoint-resolution.test.ts b/packages/ai/test/bedrock-endpoint-resolution.test.ts
index 374599a473..d2f8d69ddd 100644
--- a/packages/ai/test/bedrock-endpoint-resolution.test.ts
+++ b/packages/ai/test/bedrock-endpoint-resolution.test.ts
@@ -1,10 +1,10 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
-const bedrockMock = vi.hoisted(() => ({
- constructorCalls: [] as Array>,
-}));
+const bedrockMock: { constructorCalls: Array> } = {
+ constructorCalls: [],
+};
-vi.mock("@aws-sdk/client-bedrock-runtime", () => {
+mock.module("@aws-sdk/client-bedrock-runtime", () => {
class BedrockRuntimeServiceException extends Error {}
class BedrockRuntimeClient {
diff --git a/packages/ai/test/codex-websocket-cached-probe.ts b/packages/ai/test/codex-websocket-cached-probe.ts
index 9bab146687..93e72f9a36 100644
--- a/packages/ai/test/codex-websocket-cached-probe.ts
+++ b/packages/ai/test/codex-websocket-cached-probe.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env tsx
+#!/usr/bin/env bun
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
@@ -79,7 +79,7 @@ function required(value: string | undefined, flag: string): string {
}
function printHelp(): void {
- console.log(`Usage: npx tsx test/codex-websocket-cached-probe.ts [options]
+ console.log(`Usage: bun test/codex-websocket-cached-probe.ts [options]
Options:
--turns Number of user turns. Default: ${DEFAULT_TURNS}
diff --git a/packages/ai/test/github-copilot-anthropic.test.ts b/packages/ai/test/github-copilot-anthropic.test.ts
index 92bff083be..5362c4171e 100644
--- a/packages/ai/test/github-copilot-anthropic.test.ts
+++ b/packages/ai/test/github-copilot-anthropic.test.ts
@@ -1,13 +1,16 @@
-import { describe, expect, it, vi } from "vitest";
+import { describe, expect, it, mock } from "bun:test";
import { getModel } from "../src/models.js";
import type { Context } from "../src/types.js";
-const mockState = vi.hoisted(() => ({
- constructorOpts: undefined as Record | undefined,
- createParams: undefined as Record | undefined,
-}));
+const mockState: {
+ constructorOpts: Record | undefined;
+ createParams: Record | undefined;
+} = {
+ constructorOpts: undefined,
+ createParams: undefined,
+};
-vi.mock("@anthropic-ai/sdk", () => {
+mock.module("@anthropic-ai/sdk", () => {
function createSseResponse(): Response {
const body = [
`event: message_start\ndata: ${JSON.stringify({
diff --git a/packages/ai/test/github-copilot-oauth.test.ts b/packages/ai/test/github-copilot-oauth.test.ts
index 0367892cb8..9808b16624 100644
--- a/packages/ai/test/github-copilot-oauth.test.ts
+++ b/packages/ai/test/github-copilot-oauth.test.ts
@@ -1,6 +1,13 @@
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it, vi } from "bun:test";
import { loginGitHubCopilot } from "../src/utils/oauth/github-copilot.js";
+/** Flush microtask queue so async continuations run */
+async function flush(times = 10): Promise {
+ for (let i = 0; i < times; i++) {
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
+ }
+}
+
function jsonResponse(body: unknown, status: number = 200): Response {
return new Response(JSON.stringify(body), {
status,
@@ -20,19 +27,22 @@ function getUrl(input: unknown): string {
if (input instanceof Request) {
return input.url;
}
- throw new Error(`Unsupported fetch input: ${String(input)}`);
+ throw new Error(`Unsupported fetch input: \${String(input)}`);
}
describe("GitHub Copilot OAuth device flow", () => {
+ let origFetchGH: typeof globalThis.fetch | undefined;
afterEach(() => {
- vi.unstubAllGlobals();
+ if (origFetchGH !== undefined) {
+ globalThis.fetch = origFetchGH;
+ origFetchGH = undefined;
+ }
vi.useRealTimers();
});
it("waits before the first poll and increases the safety margin after slow_down", async () => {
vi.useFakeTimers();
- const startTime = new Date("2026-03-09T00:00:00Z");
- vi.setSystemTime(startTime);
+ const baseTime = Date.now();
const accessTokenPollTimes: number[] = [];
const accessTokenResponses = [
@@ -89,10 +99,11 @@ describe("GitHub Copilot OAuth device flow", () => {
return new Response("", { status: 200 });
}
- throw new Error(`Unexpected fetch URL: ${url}`);
+ throw new Error(`Unexpected fetch URL: \${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchGH = globalThis.fetch;
+ globalThis.fetch = fetchMock;
const loginPromise = loginGitHubCopilot({
onAuth: () => {},
@@ -100,38 +111,41 @@ describe("GitHub Copilot OAuth device flow", () => {
onProgress: () => {},
});
- await vi.advanceTimersByTimeAsync(0);
+ // Let loginGitHubCopilot initialize and enter the poll loop
+ await flush(30);
+
expect(accessTokenPollTimes).toHaveLength(0);
- await vi.advanceTimersByTimeAsync(5999);
+ vi.advanceTimersByTime(5999);
+ await flush();
expect(accessTokenPollTimes).toHaveLength(0);
- await vi.advanceTimersByTimeAsync(1);
+ vi.advanceTimersByTime(1);
+ await flush();
expect(accessTokenPollTimes).toHaveLength(1);
- await vi.advanceTimersByTimeAsync(5999);
+ vi.advanceTimersByTime(5999);
+ await flush();
expect(accessTokenPollTimes).toHaveLength(1);
- await vi.advanceTimersByTimeAsync(1);
+ vi.advanceTimersByTime(1);
+ await flush();
expect(accessTokenPollTimes).toHaveLength(2);
- await vi.advanceTimersByTimeAsync(13999);
+ vi.advanceTimersByTime(13999);
+ await flush();
expect(accessTokenPollTimes).toHaveLength(2);
- await vi.advanceTimersByTimeAsync(1);
+ vi.advanceTimersByTime(1);
+ await flush();
await loginPromise;
- expect(accessTokenPollTimes).toEqual([
- startTime.getTime() + 6000,
- startTime.getTime() + 12000,
- startTime.getTime() + 26000,
- ]);
+ expect(accessTokenPollTimes).toEqual([baseTime + 6000, baseTime + 12000, baseTime + 26000]);
});
it("uses the remaining lifetime for a final poll before timing out after repeated slow_down responses", async () => {
vi.useFakeTimers();
- const startTime = new Date("2026-03-09T00:00:00Z");
- vi.setSystemTime(startTime);
+ const baseTime = Date.now();
const accessTokenPollTimes: number[] = [];
const accessTokenResponses = [
@@ -142,7 +156,6 @@ describe("GitHub Copilot OAuth device flow", () => {
const fetchMock = vi.fn(async (input: unknown): Promise => {
const url = getUrl(input);
-
if (url.endsWith("/login/device/code")) {
return jsonResponse({
device_code: "device-code",
@@ -152,45 +165,50 @@ describe("GitHub Copilot OAuth device flow", () => {
expires_in: 25,
});
}
-
if (url.endsWith("/login/oauth/access_token")) {
accessTokenPollTimes.push(Date.now());
const response = accessTokenResponses.shift();
- if (!response) {
- throw new Error("Unexpected extra access token poll");
- }
+ if (!response) throw new Error("Unexpected extra access token poll");
return response;
}
-
- throw new Error(`Unexpected fetch URL: ${url}`);
+ throw new Error(`Unexpected fetch URL: \${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchGH = globalThis.fetch;
+ globalThis.fetch = fetchMock;
const loginPromise = loginGitHubCopilot({
onAuth: () => {},
onPrompt: async () => "",
});
- const rejection = expect(loginPromise).rejects.toThrow(
- /Device flow timed out after one or more slow_down responses/,
- );
-
- await vi.advanceTimersByTimeAsync(6000);
- expect(accessTokenPollTimes).toEqual([startTime.getTime() + 6000]);
-
- await vi.advanceTimersByTimeAsync(14000);
- expect(accessTokenPollTimes).toEqual([startTime.getTime() + 6000, startTime.getTime() + 20000]);
-
- await vi.advanceTimersByTimeAsync(4999);
- expect(accessTokenPollTimes).toEqual([startTime.getTime() + 6000, startTime.getTime() + 20000]);
-
- await vi.advanceTimersByTimeAsync(1);
- await rejection;
-
- expect(accessTokenPollTimes).toEqual([
- startTime.getTime() + 6000,
- startTime.getTime() + 20000,
- startTime.getTime() + 25000,
- ]);
+ // Let loginGitHubCopilot initialize and enter the poll loop
+ await flush(30);
+
+ // First wait: ceil(5000 * 1.2) = 6000ms
+ vi.advanceTimersByTime(6000);
+ await flush();
+ expect(accessTokenPollTimes).toEqual([baseTime + 6000]);
+
+ // After slow_down: ceil(10000 * 1.4) = 14000ms
+ vi.advanceTimersByTime(14000);
+ await flush();
+ expect(accessTokenPollTimes).toEqual([baseTime + 6000, baseTime + 20000]);
+
+ // After second slow_down: min(ceil(15000*1.4), remaining=5000) = 5000ms
+ vi.advanceTimersByTime(4999);
+ await flush();
+ expect(accessTokenPollTimes).toEqual([baseTime + 6000, baseTime + 20000]);
+
+ // Deadline crossed, loop exits, slowDownResponses>0 -> throws
+ vi.advanceTimersByTime(1);
+ await flush();
+ try {
+ await loginPromise;
+ expect.unreachable("should have thrown");
+ } catch (e) {
+ expect((e as Error).message).toMatch(/Device flow timed out after one or more slow_down responses/);
+ }
+
+ expect(accessTokenPollTimes).toEqual([baseTime + 6000, baseTime + 20000, baseTime + 25000]);
});
});
diff --git a/packages/ai/test/google-vertex-api-key-resolution.test.ts b/packages/ai/test/google-vertex-api-key-resolution.test.ts
index d05702c3af..be1d69945a 100644
--- a/packages/ai/test/google-vertex-api-key-resolution.test.ts
+++ b/packages/ai/test/google-vertex-api-key-resolution.test.ts
@@ -1,10 +1,10 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
-const googleGenAiMock = vi.hoisted(() => ({
- constructorCalls: [] as Array>,
-}));
+const googleGenAiMock: { constructorCalls: Array> } = {
+ constructorCalls: [],
+};
-vi.mock("@google/genai", () => {
+mock.module("@google/genai", () => {
class GoogleGenAI {
models = {
generateContentStream: async function* () {
diff --git a/packages/ai/test/google-vertex-thinking-budget.test.ts b/packages/ai/test/google-vertex-thinking-budget.test.ts
index 8851388215..9406b137c8 100644
--- a/packages/ai/test/google-vertex-thinking-budget.test.ts
+++ b/packages/ai/test/google-vertex-thinking-budget.test.ts
@@ -1,9 +1,10 @@
-import type * as GoogleGenAi from "@google/genai";
+import { describe, expect, it, mock } from "bun:test";
import type { GenerateContentParameters } from "@google/genai";
-import { describe, expect, it, vi } from "vitest";
-vi.mock("@google/genai", async (importOriginal) => {
- const actual = await importOriginal();
+// Import the module normally before mocking so we can spread its exports
+import * as googleGenAiActual from "@google/genai";
+
+mock.module("@google/genai", () => {
class GoogleGenAI {
models = {
generateContentStream: async function* () {
@@ -16,7 +17,7 @@ vi.mock("@google/genai", async (importOriginal) => {
}
return {
- ...actual,
+ ...googleGenAiActual,
GoogleGenAI,
ResourceScope: { COLLECTION: "COLLECTION" },
ThinkingLevel: {
@@ -62,12 +63,14 @@ async function captureMinimalReasoningPayload(
}
describe("Google Vertex thinking budget payload", () => {
- it.each(flashLiteModels)("uses the supported minimal budget for $id", async (model) => {
- const payload = await captureMinimalReasoningPayload(model);
+ for (const model of flashLiteModels) {
+ it(`uses the supported minimal budget for ${model.id}`, async () => {
+ const payload = await captureMinimalReasoningPayload(model);
- expect(payload.config?.thinkingConfig).toEqual({
- includeThoughts: true,
- thinkingBudget: 512,
+ expect(payload.config?.thinkingConfig).toEqual({
+ includeThoughts: true,
+ thinkingBudget: 512,
+ });
});
- });
+ }
});
diff --git a/packages/ai/test/lazy-module-load.test.ts b/packages/ai/test/lazy-module-load.test.ts
index c94669abfb..7b2a3e7db4 100644
--- a/packages/ai/test/lazy-module-load.test.ts
+++ b/packages/ai/test/lazy-module-load.test.ts
@@ -1,13 +1,13 @@
import { spawnSync } from "node:child_process";
-import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
-const require = createRequire(import.meta.url);
-const tsxLoader = require.resolve("tsx/esm");
-const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const packageRoot = resolve(__dirname, "..");
const aiEntryUrl = new URL("../src/index.ts", import.meta.url).href;
+const bedrockProviderEntryUrl = new URL("../src/bedrock-provider.ts", import.meta.url).href;
const SDK_SPECIFIERS = [
"@anthropic-ai/sdk",
@@ -22,27 +22,17 @@ type ProbeResult = {
};
function runProbe(action: string): ProbeResult {
- const script = `
- import { registerHooks } from "node:module";
-
- const targets = new Set(${JSON.stringify(SDK_SPECIFIERS)});
- const loaded = [];
-
- registerHooks({
- resolve(specifier, context, nextResolve) {
- if (targets.has(specifier)) {
- loaded.push(specifier);
- }
- return nextResolve(specifier, context);
- },
- });
-
- const mod = await import(${JSON.stringify(aiEntryUrl)});
- ${action}
- console.log(JSON.stringify({ loadedSpecifiers: [...new Set(loaded)] }));
- `;
-
- const result = spawnSync(process.execPath, ["--import", tsxLoader, "--input-type=module", "--eval", script], {
+ const script = [
+ `const targets = ${JSON.stringify([...SDK_SPECIFIERS])};`,
+ `const mod = await import("${aiEntryUrl}");`,
+ action,
+ `const Module = require("module");`,
+ `const cacheKeys = Object.keys(Module._cache);`,
+ `const loaded = targets.filter((spec) => cacheKeys.some((k) => k.includes(spec)));`,
+ `console.log(JSON.stringify({ loadedSpecifiers: loaded }));`,
+ ].join("\n");
+
+ const result = spawnSync(process.execPath, ["--eval", script], {
cwd: packageRoot,
encoding: "utf8",
});
@@ -69,33 +59,70 @@ describe("lazy provider module loading", () => {
expect(result.loadedSpecifiers).toEqual([]);
});
+ it("does not load the Bedrock SDK when registering its compiled-binary loader", () => {
+ const result = runProbe(
+ `mod.setBedrockProviderModuleLoader(async () => (await import("${bedrockProviderEntryUrl}")).bedrockProviderModule);`,
+ );
+ expect(result.loadedSpecifiers).toEqual([]);
+ });
+
+ it("loads the Bedrock SDK when the registered loader is first used", () => {
+ const result = runProbe(
+ [
+ `mod.setBedrockProviderModuleLoader(async () => {`,
+ ` await import("${bedrockProviderEntryUrl}");`,
+ ` const fail = () => { throw new Error("probe"); };`,
+ ` return { streamBedrock: fail, streamSimpleBedrock: fail };`,
+ `});`,
+ `const model = {`,
+ ` id: "anthropic.claude-3-5-sonnet-20241022-v2:0",`,
+ ` api: "bedrock-converse-stream",`,
+ ` provider: "amazon-bedrock",`,
+ ` baseUrl: "http://127.0.0.1:9",`,
+ ` reasoning: false,`,
+ ` input: ["text"],`,
+ ` cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },`,
+ ` contextWindow: 200000,`,
+ ` maxTokens: 8192,`,
+ `};`,
+ `const context = { messages: [{ role: "user", content: "hi", timestamp: Date.now() }] };`,
+ `try { await mod.streamSimple(model, context).result(); } catch (_e) {}`,
+ ].join("\n"),
+ );
+
+ expect(result.loadedSpecifiers).toEqual(["@aws-sdk/client-bedrock-runtime"]);
+ });
+
it("loads only the Anthropic SDK when calling the root lazy wrapper", () => {
- const result = runProbe(`
- const model = {
- id: "claude-sonnet-4-6",
- name: "Claude Sonnet 4",
- api: "anthropic-messages",
- provider: "anthropic",
- baseUrl: "https://api.anthropic.com",
- reasoning: true,
- input: ["text"],
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
- contextWindow: 200000,
- maxTokens: 8192,
- };
- const context = { messages: [{ role: "user", content: "hi" }] };
- await mod.streamSimpleAnthropic(model, context).result();
- `);
+ const result = runProbe(
+ [
+ `const model = {`,
+ ` id: "claude-sonnet-4-6",`,
+ ` api: "anthropic-messages",`,
+ ` provider: "anthropic",`,
+ ` baseUrl: "http://127.0.0.1:9",`,
+ ` reasoning: true,`,
+ ` input: ["text"],`,
+ ` cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },`,
+ ` contextWindow: 200000,`,
+ ` maxTokens: 8192,`,
+ `};`,
+ `const context = { messages: [{ role: "user", content: "hi", timestamp: Date.now() }] };`,
+ `try { await mod.streamSimpleAnthropic(model, context).result(); } catch (_e) {}`,
+ ].join("\n"),
+ );
expect(result.loadedSpecifiers).toEqual(["@anthropic-ai/sdk"]);
});
it("loads only the Anthropic SDK when dispatching through streamSimple", () => {
- const result = runProbe(`
- const model = mod.getModel("anthropic", "claude-sonnet-4-6");
- const context = { messages: [{ role: "user", content: "hi" }] };
- await mod.streamSimple(model, context).result();
- `);
+ const result = runProbe(
+ [
+ `const model = mod.getModel("anthropic", "claude-sonnet-4-6");`,
+ `const context = { messages: [{ role: "user", content: "hi", timestamp: Date.now() }] };`,
+ `try { await mod.streamSimple(model, context).result(); } catch (_e) {}`,
+ ].join("\n"),
+ );
expect(result.loadedSpecifiers).toEqual(["@anthropic-ai/sdk"]);
});
diff --git a/packages/ai/test/mcp-oauth.test.ts b/packages/ai/test/mcp-oauth.test.ts
index e35f2a009d..7d222c7a37 100644
--- a/packages/ai/test/mcp-oauth.test.ts
+++ b/packages/ai/test/mcp-oauth.test.ts
@@ -1,5 +1,5 @@
+import { afterEach, describe, expect, it, vi } from "bun:test";
import { createServer } from "node:http";
-import { afterEach, describe, expect, it, vi } from "vitest";
import { createMcpOAuthProvider } from "../src/mcp/oauth.js";
function jsonResponse(body: unknown, status = 200, headers?: Record): Response {
@@ -58,9 +58,13 @@ async function loginWithManualCode(
return { creds, authUrl };
}
-describe.sequential("MCP OAuth provider", () => {
+describe("MCP OAuth provider", () => {
+ let origFetchMCP: typeof globalThis.fetch | undefined;
afterEach(() => {
- vi.unstubAllGlobals();
+ if (origFetchMCP !== undefined) {
+ globalThis.fetch = origFetchMCP;
+ origFetchMCP = undefined;
+ }
});
it("has a namespaced id and label", () => {
@@ -92,7 +96,8 @@ describe.sequential("MCP OAuth provider", () => {
}
throw new Error(`unexpected fetch: ${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = fetchMock;
const { creds, authUrl } = await loginWithManualCode(createMcpOAuthProvider({ server: "plane", url: RESOURCE }));
expect(creds).toMatchObject({
@@ -119,45 +124,40 @@ describe.sequential("MCP OAuth provider", () => {
token_endpoint: "https://login.example/tenant/token",
registration_endpoint: "https://login.example/tenant/register",
};
- vi.stubGlobal(
- "fetch",
- vi.fn(async (input: unknown): Promise => {
- const url = urlOf(input);
- if (url === RESOURCE) return new Response("", { status: 404 });
- if (url === PLANE_PRM_URL) return jsonResponse({ resource: RESOURCE, authorization_servers: [issuer] });
- if (url === "https://login.example/.well-known/oauth-authorization-server/tenant")
- return new Response("not metadata", {
- status: 200,
- headers: { "Content-Type": "text/html" },
- });
- if (url === oidcMeta) return jsonResponse(metadata);
- if (url === metadata.registration_endpoint) return jsonResponse({ client_id: "c" });
- if (url === metadata.token_endpoint) return jsonResponse({ access_token: "a", expires_in: 60 });
- throw new Error(`unexpected fetch: ${url}`);
- }),
- );
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (input: unknown): Promise => {
+ const url = urlOf(input);
+ if (url === RESOURCE) return new Response("", { status: 404 });
+ if (url === PLANE_PRM_URL) return jsonResponse({ resource: RESOURCE, authorization_servers: [issuer] });
+ if (url === "https://login.example/.well-known/oauth-authorization-server/tenant")
+ return new Response("not metadata", {
+ status: 200,
+ headers: { "Content-Type": "text/html" },
+ });
+ if (url === oidcMeta) return jsonResponse(metadata);
+ if (url === metadata.registration_endpoint) return jsonResponse({ client_id: "c" });
+ if (url === metadata.token_endpoint) return jsonResponse({ access_token: "a", expires_in: 60 });
+ throw new Error(`unexpected fetch: ${url}`);
+ });
const { creds } = await loginWithManualCode(createMcpOAuthProvider({ server: "plane", url: RESOURCE }));
expect(creds).toMatchObject({ resource: RESOURCE, issuer });
});
it("fails closed after protected-resource metadata selects an issuer", async () => {
- vi.stubGlobal(
- "fetch",
- vi.fn(async (input: unknown): Promise => {
- const url = urlOf(input);
- if (url === RESOURCE)
- return new Response("", {
- status: 401,
- headers: { "WWW-Authenticate": `Bearer resource_metadata="${PLANE_PRM_URL}"` },
- });
- if (url === PLANE_PRM_URL)
- return jsonResponse({ resource: RESOURCE, authorization_servers: [PLANE_ISSUER] });
- if (url === PLANE_META_URL) return jsonResponse({ ...PLANE_META, issuer: "https://wrong.example" });
- if (url === "https://mcp.plane.so/http/.well-known/openid-configuration")
- return new Response("", { status: 404 });
- throw new Error(`unexpected fetch: ${url}`);
- }),
- );
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (input: unknown): Promise => {
+ const url = urlOf(input);
+ if (url === RESOURCE)
+ return new Response("", {
+ status: 401,
+ headers: { "WWW-Authenticate": `Bearer resource_metadata="${PLANE_PRM_URL}"` },
+ });
+ if (url === PLANE_PRM_URL) return jsonResponse({ resource: RESOURCE, authorization_servers: [PLANE_ISSUER] });
+ if (url === PLANE_META_URL) return jsonResponse({ ...PLANE_META, issuer: "https://wrong.example" });
+ if (url === "https://mcp.plane.so/http/.well-known/openid-configuration")
+ return new Response("", { status: 404 });
+ throw new Error(`unexpected fetch: ${url}`);
+ });
await expect(
createMcpOAuthProvider({ server: "plane", url: RESOURCE }).login({
onAuth: () => {},
@@ -180,7 +180,8 @@ describe.sequential("MCP OAuth provider", () => {
}
throw new Error(`unexpected fetch: ${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = fetchMock;
const { creds, authUrl } = await loginWithManualCode(
createMcpOAuthProvider({ server: "origin", url: ORIGIN_URL }),
);
@@ -213,7 +214,8 @@ describe.sequential("MCP OAuth provider", () => {
}
throw new Error(`unexpected fetch: ${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = fetchMock;
const provider = createMcpOAuthProvider({ server: "plane", url: RESOURCE });
const refreshed = await provider.refreshToken({
access: "access-1",
@@ -260,17 +262,15 @@ describe.sequential("MCP OAuth provider", () => {
authorization_endpoint: "https://root.example/authorize",
token_endpoint: "https://root.example/token",
};
- vi.stubGlobal(
- "fetch",
- vi.fn(async (input: unknown): Promise => {
- const url = urlOf(input);
- if (url === "https://root.example/") return new Response("", { status: 401 });
- if (url === prm) return jsonResponse({ resource, authorization_servers: [issuer] });
- if (url === asMetadata) return jsonResponse(metadata);
- if (url === metadata.token_endpoint) return jsonResponse({ access_token: "root-access" });
- throw new Error(`unexpected fetch: ${url}`);
- }),
- );
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (input: unknown): Promise => {
+ const url = urlOf(input);
+ if (url === "https://root.example/") return new Response("", { status: 401 });
+ if (url === prm) return jsonResponse({ resource, authorization_servers: [issuer] });
+ if (url === asMetadata) return jsonResponse(metadata);
+ if (url === metadata.token_endpoint) return jsonResponse({ access_token: "root-access" });
+ throw new Error(`unexpected fetch: ${url}`);
+ });
const { creds, authUrl } = await loginWithManualCode(
createMcpOAuthProvider({ server: "root", url: resource, clientId: "root-client" }),
);
@@ -296,7 +296,8 @@ describe.sequential("MCP OAuth provider", () => {
if (url === metadata.token_endpoint) return jsonResponse({ access_token: "query-access" });
throw new Error(`unexpected fetch: ${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = fetchMock;
const { creds } = await loginWithManualCode(
createMcpOAuthProvider({ server: "query", url: resource, clientId: "query-client" }),
);
@@ -314,7 +315,8 @@ describe.sequential("MCP OAuth provider", () => {
if (url === PLANE_META_URL) return jsonResponse(PLANE_META);
throw new Error(`unexpected fetch: ${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = fetchMock;
await expect(
createMcpOAuthProvider({ server: "plane", url: RESOURCE }).refreshToken({
access: "origin-access",
@@ -329,20 +331,18 @@ describe.sequential("MCP OAuth provider", () => {
});
it("rejects a redirected token POST", async () => {
- vi.stubGlobal(
- "fetch",
- vi.fn(async (input: unknown, init?: RequestInit): Promise => {
- const missing = absentPrm(input);
- if (missing) return missing;
- const url = urlOf(input);
- if (url === "https://srv.test/.well-known/oauth-authorization-server") return jsonResponse(ORIGIN_META);
- if (url === ORIGIN_META.token_endpoint) {
- expect(init?.redirect).toBe("error");
- return new Response("redirect", { status: 302, headers: { Location: "https://evil.test/token" } });
- }
- throw new Error(`unexpected fetch: ${url}`);
- }),
- );
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (input: unknown, init?: RequestInit): Promise => {
+ const missing = absentPrm(input);
+ if (missing) return missing;
+ const url = urlOf(input);
+ if (url === "https://srv.test/.well-known/oauth-authorization-server") return jsonResponse(ORIGIN_META);
+ if (url === ORIGIN_META.token_endpoint) {
+ expect(init?.redirect).toBe("error");
+ return new Response("redirect", { status: 302, headers: { Location: "https://evil.test/token" } });
+ }
+ throw new Error(`unexpected fetch: ${url}`);
+ });
const provider = createMcpOAuthProvider({ server: "origin", url: ORIGIN_URL, clientId: "c" });
await expect(
provider.refreshToken({
@@ -370,7 +370,8 @@ describe.sequential("MCP OAuth provider", () => {
if (url === PLANE_META.token_endpoint) return jsonResponse({ access_token: "pointer-access" });
throw new Error(`unexpected fetch: ${url}`);
});
- vi.stubGlobal("fetch", fetchMock);
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = fetchMock;
const { creds } = await loginWithManualCode(createMcpOAuthProvider({ server: "plane", url: RESOURCE }));
expect(creds).toMatchObject({ access: "pointer-access", resource: RESOURCE, issuer: PLANE_ISSUER });
@@ -378,16 +379,14 @@ describe.sequential("MCP OAuth provider", () => {
});
it("rejects protected-resource metadata for a different resource", async () => {
- vi.stubGlobal(
- "fetch",
- vi.fn(async (input: unknown): Promise => {
- const url = urlOf(input);
- if (url === RESOURCE) return new Response("", { status: 401 });
- if (url === PLANE_PRM_URL)
- return jsonResponse({ resource: "https://attacker.example/mcp", authorization_servers: [PLANE_ISSUER] });
- throw new Error(`unexpected fetch: ${url}`);
- }),
- );
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (input: unknown): Promise => {
+ const url = urlOf(input);
+ if (url === RESOURCE) return new Response("", { status: 401 });
+ if (url === PLANE_PRM_URL)
+ return jsonResponse({ resource: "https://attacker.example/mcp", authorization_servers: [PLANE_ISSUER] });
+ throw new Error(`unexpected fetch: ${url}`);
+ });
await expect(
createMcpOAuthProvider({ server: "plane", url: RESOURCE }).login({
@@ -404,18 +403,16 @@ describe.sequential("MCP OAuth provider", () => {
blocker.listen(53700, "127.0.0.1", () => resolve(true));
});
try {
- vi.stubGlobal(
- "fetch",
- vi.fn(async (input: unknown): Promise => {
- const missing = absentPrm(input);
- if (missing) return missing;
- const url = urlOf(input);
- if (url === "https://srv.test/.well-known/oauth-authorization-server") return jsonResponse(ORIGIN_META);
- if (url === ORIGIN_META.registration_endpoint) return jsonResponse({ client_id: "c" });
- if (url === ORIGIN_META.token_endpoint) return jsonResponse({ access_token: "a", expires_in: 60 });
- throw new Error(`unexpected fetch: ${url}`);
- }),
- );
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (input: unknown): Promise => {
+ const missing = absentPrm(input);
+ if (missing) return missing;
+ const url = urlOf(input);
+ if (url === "https://srv.test/.well-known/oauth-authorization-server") return jsonResponse(ORIGIN_META);
+ if (url === ORIGIN_META.registration_endpoint) return jsonResponse({ client_id: "c" });
+ if (url === ORIGIN_META.token_endpoint) return jsonResponse({ access_token: "a", expires_in: 60 });
+ throw new Error(`unexpected fetch: ${url}`);
+ });
const { authUrl } = await loginWithManualCode(createMcpOAuthProvider({ server: "demo", url: ORIGIN_URL }));
const redirect = new URL(authUrl).searchParams.get("redirect_uri") ?? "";
expect(redirect).not.toContain(":53700/");
@@ -426,17 +423,15 @@ describe.sequential("MCP OAuth provider", () => {
});
it("fails clearly when dynamic client registration is unavailable", async () => {
- vi.stubGlobal(
- "fetch",
- vi.fn(async (input: unknown): Promise => {
- const missing = absentPrm(input);
- if (missing) return missing;
- const url = urlOf(input);
- if (url === "https://srv.test/.well-known/oauth-authorization-server")
- return jsonResponse({ ...ORIGIN_META, registration_endpoint: undefined });
- throw new Error(`unexpected fetch: ${url}`);
- }),
- );
+ origFetchMCP = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (input: unknown): Promise => {
+ const missing = absentPrm(input);
+ if (missing) return missing;
+ const url = urlOf(input);
+ if (url === "https://srv.test/.well-known/oauth-authorization-server")
+ return jsonResponse({ ...ORIGIN_META, registration_endpoint: undefined });
+ throw new Error(`unexpected fetch: ${url}`);
+ });
await expect(
createMcpOAuthProvider({ server: "slackish", url: ORIGIN_URL }).login({
onAuth: () => {},
diff --git a/packages/ai/test/openai-codex-oauth.test.ts b/packages/ai/test/openai-codex-oauth.test.ts
index 276abe1bfe..6e86f5e982 100644
--- a/packages/ai/test/openai-codex-oauth.test.ts
+++ b/packages/ai/test/openai-codex-oauth.test.ts
@@ -1,28 +1,30 @@
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it, vi } from "bun:test";
import { refreshOpenAICodexToken } from "../src/utils/oauth/openai-codex.js";
describe("OpenAI Codex OAuth", () => {
+ let origFetchCodex: typeof globalThis.fetch | undefined;
afterEach(() => {
vi.restoreAllMocks();
- vi.unstubAllGlobals();
+ if (origFetchCodex !== undefined) {
+ globalThis.fetch = origFetchCodex;
+ origFetchCodex = undefined;
+ }
});
it("does not write token refresh failures to stderr", async () => {
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
- vi.stubGlobal(
- "fetch",
- vi.fn(async (): Promise => {
- return new Response(
- JSON.stringify({
- error: {
- message: "Could not validate your token. Please try signing in again.",
- type: "invalid_request_error",
- },
- }),
- { status: 401, statusText: "Unauthorized", headers: { "Content-Type": "application/json" } },
- );
- }),
- );
+ origFetchCodex = globalThis.fetch;
+ globalThis.fetch = vi.fn(async (): Promise => {
+ return new Response(
+ JSON.stringify({
+ error: {
+ message: "Could not validate your token. Please try signing in again.",
+ type: "invalid_request_error",
+ },
+ }),
+ { status: 401, statusText: "Unauthorized", headers: { "Content-Type": "application/json" } },
+ );
+ });
await expect(refreshOpenAICodexToken("invalid-refresh-token")).rejects.toThrow(
/OpenAI Codex token refresh failed \(401\).*Could not validate your token/,
diff --git a/packages/ai/test/openai-codex-stream.test.ts b/packages/ai/test/openai-codex-stream.test.ts
index e05b9d9831..4a6c84b583 100644
--- a/packages/ai/test/openai-codex-stream.test.ts
+++ b/packages/ai/test/openai-codex-stream.test.ts
@@ -543,6 +543,7 @@ describe("openai-codex streaming", () => {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
+ thinkingLevelMap: { minimal: "low" },
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 400000,
@@ -554,9 +555,9 @@ describe("openai-codex streaming", () => {
messages: [{ role: "user", content: "Say hello", timestamp: Date.now() }],
};
- const streamResult = streamOpenAICodexResponses(model, context, {
+ const streamResult = streamSimpleOpenAICodexResponses(model, context, {
apiKey: token,
- reasoningEffort: "minimal",
+ reasoning: "minimal",
});
await streamResult.result();
});
diff --git a/packages/ai/test/openai-completions-cache-control-format.test.ts b/packages/ai/test/openai-completions-cache-control-format.test.ts
index 9b6f66c46a..66ac4ed9b8 100644
--- a/packages/ai/test/openai-completions-cache-control-format.test.ts
+++ b/packages/ai/test/openai-completions-cache-control-format.test.ts
@@ -28,9 +28,9 @@ interface CapturedParams {
tools?: ToolWithCacheControl[];
}
-const mockState = vi.hoisted(() => ({
- lastParams: undefined as CapturedParams | undefined,
-}));
+const mockState: { lastParams: CapturedParams | undefined } = {
+ lastParams: undefined,
+};
const emptyUsage: Usage = {
input: 0,
diff --git a/packages/ai/test/openai-completions-empty-tools.test.ts b/packages/ai/test/openai-completions-empty-tools.test.ts
index 61a65c1df4..9dbb733905 100644
--- a/packages/ai/test/openai-completions-empty-tools.test.ts
+++ b/packages/ai/test/openai-completions-empty-tools.test.ts
@@ -4,10 +4,10 @@ import { CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL } from "../src/providers/cloudfla
import { streamSimple } from "../src/stream.js";
import type { Model } from "../src/types.js";
-const mockState = vi.hoisted(() => ({
- lastParams: undefined as unknown,
- lastClientOptions: undefined as unknown,
-}));
+const mockState: { lastParams: unknown; lastClientOptions: unknown } = {
+ lastParams: undefined,
+ lastClientOptions: undefined,
+};
vi.mock("openai", () => {
class FakeOpenAI {
diff --git a/packages/ai/test/openai-completions-prompt-cache.test.ts b/packages/ai/test/openai-completions-prompt-cache.test.ts
index 147097efc4..d2c54f2fff 100644
--- a/packages/ai/test/openai-completions-prompt-cache.test.ts
+++ b/packages/ai/test/openai-completions-prompt-cache.test.ts
@@ -15,10 +15,13 @@ interface CapturedCompletionsPayload {
prompt_cache_retention?: "24h" | "in-memory" | null;
}
-const mockState = vi.hoisted(() => ({
- lastParams: undefined as CapturedCompletionsPayload | undefined,
- lastClientOptions: undefined as FakeOpenAIClientOptions | undefined,
-}));
+const mockState: {
+ lastParams: CapturedCompletionsPayload | undefined;
+ lastClientOptions: FakeOpenAIClientOptions | undefined;
+} = {
+ lastParams: undefined,
+ lastClientOptions: undefined,
+};
vi.mock("openai", () => {
class FakeOpenAI {
diff --git a/packages/ai/test/openai-completions-response-model.test.ts b/packages/ai/test/openai-completions-response-model.test.ts
index a7592b0eb0..a85f14fec7 100644
--- a/packages/ai/test/openai-completions-response-model.test.ts
+++ b/packages/ai/test/openai-completions-response-model.test.ts
@@ -2,9 +2,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { complete } from "../src/stream.js";
import type { Model } from "../src/types.js";
-const mockState = vi.hoisted(() => ({
- chunks: [] as unknown[],
-}));
+const mockState: { chunks: unknown[] } = {
+ chunks: [],
+};
vi.mock("openai", () => {
class FakeOpenAI {
diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts
index 64dd360b75..4f305bacf8 100644
--- a/packages/ai/test/openai-completions-tool-choice.test.ts
+++ b/packages/ai/test/openai-completions-tool-choice.test.ts
@@ -5,21 +5,20 @@ import { streamSimple } from "../src/stream.js";
import type { Tool } from "../src/types.js";
import { getZaiTestModel } from "./zai-test-model.js";
-const mockState = vi.hoisted(() => ({
- lastParams: undefined as unknown,
- chunks: undefined as
- | Array; finish_reason: string | null; usage?: unknown }>;
- usage?: {
- prompt_tokens: number;
- completion_tokens: number;
- prompt_tokens_details: { cached_tokens: number; cache_write_tokens?: number };
- completion_tokens_details: { reasoning_tokens: number };
- };
- }>
- | undefined,
-}));
+interface ChunkItem {
+ id?: string;
+ choices?: Array<{ delta: Record; finish_reason: string | null; usage?: unknown }>;
+ usage?: {
+ prompt_tokens: number;
+ completion_tokens: number;
+ prompt_tokens_details: { cached_tokens: number; cache_write_tokens?: number };
+ completion_tokens_details: { reasoning_tokens: number };
+ };
+}
+const mockState: { lastParams: unknown; chunks: Array | undefined } = {
+ lastParams: undefined,
+ chunks: undefined,
+};
vi.mock("openai", () => {
class FakeOpenAI {
diff --git a/packages/ai/vitest.config.ts b/packages/ai/vitest.config.ts
deleted file mode 100644
index 1b07c96f24..0000000000
--- a/packages/ai/vitest.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineConfig } from 'vitest/config';
-
-export default defineConfig({
- test: {
- globals: true,
- environment: 'node',
- testTimeout: 30000, // 30 seconds for API calls
- }
-});
\ No newline at end of file
diff --git a/packages/coding-agent/.changes/bun-primary-runtime.md b/packages/coding-agent/.changes/bun-primary-runtime.md
new file mode 100644
index 0000000000..cc8bff96cf
--- /dev/null
+++ b/packages/coding-agent/.changes/bun-primary-runtime.md
@@ -0,0 +1 @@
+- Breaking: made compiled Bun binaries the only supported Prime Agent install and update path; removed the Node/npm installer compatibility path.
diff --git a/packages/coding-agent/.changes/windows-native-support.md b/packages/coding-agent/.changes/windows-native-support.md
new file mode 100644
index 0000000000..64c4909d3b
--- /dev/null
+++ b/packages/coding-agent/.changes/windows-native-support.md
@@ -0,0 +1 @@
+- Added native Windows installation, release archives, process cleanup, daemon named-pipe coordination, CPython bootstrap, and CI coverage without requiring WSL.
diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md
index 5f6bd39454..0fdd4b2417 100644
--- a/packages/coding-agent/README.md
+++ b/packages/coding-agent/README.md
@@ -13,7 +13,7 @@
RLM-native terminal coding and research harness.
-Prime Agent began as a hard fork of [pi-mono](https://github.com/badlogic/pi-mono), but it is now developed and distributed independently. This workspace retains inherited `@earendil-works/pi-*` source package identifiers, the `pi` package manifest key, and a source-package `pi` bin entry for internal compatibility. Public releases are currently versioned tarball artifacts installed by the scripts below; release packaging rewrites the application package and command to `prime-agent`. Do not use the inherited npm package as the Prime Agent install path.
+Prime Agent began as a hard fork of [pi-mono](https://github.com/badlogic/pi-mono), but it is now developed and distributed independently. This workspace retains inherited `@earendil-works/pi-*` source package identifiers, the `pi` package manifest key, and a source-package `pi` bin entry for internal compatibility. Public releases are compiled Bun binary archives installed by the scripts below; release packaging ships the application and command as `prime-agent`. Do not use the inherited npm package as the Prime Agent install path.
## Table of Contents
@@ -46,6 +46,12 @@ Prime Agent began as a hard fork of [pi-mono](https://github.com/badlogic/pi-mon
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh
```
+On Windows, install natively from PowerShell. WSL is not required; Git Bash is required for shell commands.
+
+```powershell
+irm https://app.primeintellect.ai/prime-agent/install.ps1 | iex
+```
+
To install the beta built from the latest commit on `main`:
```bash
diff --git a/packages/coding-agent/bunfig.toml b/packages/coding-agent/bunfig.toml
new file mode 100644
index 0000000000..03378bb026
--- /dev/null
+++ b/packages/coding-agent/bunfig.toml
@@ -0,0 +1,3 @@
+[test]
+preload = ["./test/bun-test-preload.ts"]
+timeout = 30000
diff --git a/packages/coding-agent/docs/daemon.md b/packages/coding-agent/docs/daemon.md
index 35e4ce9dd9..ba1cce031e 100644
--- a/packages/coding-agent/docs/daemon.md
+++ b/packages/coding-agent/docs/daemon.md
@@ -156,11 +156,11 @@ If preparation or manifest validation fails, prepared workers are released and a
From `packages/coding-agent`:
```sh
-npx tsx test/daemon-multiclient-bench.ts
-npx tsx test/daemon-multiclient-bench.ts --generated-session-mib 100
-npx tsx test/daemon-multiclient-bench.ts --generated-session-mib 500
-npx tsx test/daemon-multiclient-bench.ts --session-file /path/to/session.jsonl
-PRIME_AGENT_STRESS_WORKERS=50 npx tsx ../../node_modules/vitest/dist/cli.js --run test/daemon-supervisor-process.test.ts -t "hosts resident roots"
+bun test/daemon-multiclient-bench.ts
+bun test/daemon-multiclient-bench.ts --generated-session-mib 100
+bun test/daemon-multiclient-bench.ts --generated-session-mib 500
+bun test/daemon-multiclient-bench.ts --session-file /path/to/session.jsonl
+PRIME_AGENT_STRESS_WORKERS=50 PRIME_AGENT_TEST_TAGS=process-stress bun test --isolate test/daemon-supervisor-process.test.ts -t "hosts resident roots"
```
The benchmark compares fanout and attach paths, including serialization count, throughput, elapsed time, and sampled RSS. The stress case starts many resident roots and verifies that their schedules advance independently while sessions are busy.
diff --git a/packages/coding-agent/docs/development.md b/packages/coding-agent/docs/development.md
index 56aa1a74a4..8a702daea8 100644
--- a/packages/coding-agent/docs/development.md
+++ b/packages/coding-agent/docs/development.md
@@ -9,7 +9,7 @@ Prime Agent requires Node.js 22.8.0 or newer.
```bash
git clone https://github.com/PrimeIntellect-ai/prime-agent
cd prime-agent
-npm ci
+bun install --frozen-lockfile
```
Run from source:
@@ -24,7 +24,7 @@ The script can be called from any directory and preserves the caller's working d
Prime Agent is the product, public CLI, release artifact, and repository name. The monorepo still retains inherited `@earendil-works/pi-*` npm workspace names, a source-package `pi` bin entry, the `pi` package manifest key, and some `PI_*` compatibility environment variables. These names are source and compatibility details, not a signal that contributors should install or develop against pi-mono.
-Public releases are currently versioned tarball artifacts installed by the stable and beta installer scripts. `scripts/pack-prime-agent-release.mjs` rewrites the coding-agent package name, executable, config metadata, and internal dependency URLs for that distribution. Do not document the inherited npm workspace package as the public Prime Agent install path.
+Public releases are compiled Bun binary archives installed by the stable and beta installer scripts. `scripts/pack-prime-agent-release.mjs` assembles the platform archives and metadata. Do not document a package-manager install path for the public Prime Agent CLI.
## Local Configuration
@@ -68,7 +68,7 @@ prime-agent shutdown
After code changes, run the repository check from the root:
```bash
-npm run check
+bun run check
```
This performs formatting, linting, type checking, installer rendering checks, and the browser smoke check. It does not run the test suite.
@@ -77,7 +77,7 @@ Run focused tests from the package root. For example:
```bash
cd packages/coding-agent
-npx tsx ../../node_modules/vitest/dist/cli.js --run test/specific.test.ts
+bun test test/specific.test.ts
```
If you create or modify a test file, run that file and iterate until it passes. Coding-agent suite regressions belong under `test/suite/regressions/` and use the suite harness and faux provider rather than live provider credentials.
diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md
index 4fd2912808..a16b06bbb5 100644
--- a/packages/coding-agent/docs/extensions.md
+++ b/packages/coding-agent/docs/extensions.md
@@ -144,7 +144,7 @@ To share extensions via npm or git as Prime Agent packages, see [packages.md](pa
| `@earendil-works/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
| `@earendil-works/pi-tui` | TUI components for custom rendering |
-npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `npm install`, and imports from `node_modules/` are resolved automatically.
+npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `bun install`, and imports from `node_modules/` are resolved automatically.
For distributed Prime Agent packages installed with `prime-agent package install` (npm or git), runtime dependencies must be in `dependencies`. Package installation uses production installs (`npm install --omit=dev`) by default, so `devDependencies` are not available at runtime; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers.
@@ -242,7 +242,7 @@ This pattern makes the fetched models available during normal startup and to `pr
└── my-extension/
├── package.json # Declares dependencies and entry points
├── package-lock.json
- ├── node_modules/ # After npm install
+ ├── node_modules/ # After bun install
└── src/
└── index.ts
```
@@ -261,7 +261,7 @@ This pattern makes the fetched models available during normal startup and to `pr
}
```
-Run `npm install` in the extension directory, then imports from `node_modules/` work automatically.
+Run `bun install` in the extension directory, then imports from `node_modules/` work automatically.
## Events
diff --git a/packages/coding-agent/docs/long-running-agents.md b/packages/coding-agent/docs/long-running-agents.md
index 436a40d447..1ac2f9d1ed 100644
--- a/packages/coding-agent/docs/long-running-agents.md
+++ b/packages/coding-agent/docs/long-running-agents.md
@@ -213,7 +213,7 @@ Or configure a run from the CLI:
```bash
prime-agent \
--autonomous \
- --autonomous-gate "npm run check" \
+ --autonomous-gate "bun run check" \
--autonomous-max-turns 20 \
"Implement and verify the requested change"
```
diff --git a/packages/coding-agent/docs/quickstart.md b/packages/coding-agent/docs/quickstart.md
index e55a8375b5..d6fb8d0f18 100644
--- a/packages/coding-agent/docs/quickstart.md
+++ b/packages/coding-agent/docs/quickstart.md
@@ -10,6 +10,12 @@ Install the latest stable release on Linux or macOS:
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh
```
+On Windows, use PowerShell. This is a native install and does not require WSL. Install Git Bash for shell commands.
+
+```powershell
+irm https://app.primeintellect.ai/prime-agent/install.ps1 | iex
+```
+
To try the latest beta built from `main`:
```bash
@@ -25,12 +31,12 @@ cd /path/to/project
prime-agent
```
-To run a source checkout instead, use Node.js 22.8.0 or newer:
+To run a source checkout instead, use Bun 1.4.0:
```bash
git clone https://github.com/PrimeIntellect-ai/prime-agent
cd prime-agent
-npm ci
+bun install --frozen-lockfile
./prime-agent.sh
```
@@ -94,7 +100,7 @@ Prime Agent loads context files at startup. Add an `AGENTS.md` file to tell it h
```markdown
# Project Instructions
-- Run `npm run check` after code changes.
+- Run `bun run check` after code changes.
- Do not run production migrations locally.
- Keep responses concise.
```
@@ -124,7 +130,7 @@ Images can be pasted with Ctrl+V (Alt+V on Windows) or dragged into supported te
In interactive mode:
```text
-!npm run lint
+!bun run lint
```
The command output is sent to the model. Use `!!command` to run a command without adding its output to model context. During agent work, the model normally runs project commands from the Python REPL with `bash()`.
diff --git a/packages/coding-agent/docs/rlm.md b/packages/coding-agent/docs/rlm.md
index 7d558073b4..4be00ec26c 100644
--- a/packages/coding-agent/docs/rlm.md
+++ b/packages/coding-agent/docs/rlm.md
@@ -44,7 +44,7 @@ large_files = [path for path in config_files if path.stat().st_size > 10_000]
Run a project's normal commands through its own environment with `bash()`:
```python
-result = await bash("npm run check")
+result = await bash("bun run check")
print(result.output)
```
diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md
index a8ee9aa372..e50f199584 100644
--- a/packages/coding-agent/docs/sdk.md
+++ b/packages/coding-agent/docs/sdk.md
@@ -40,7 +40,7 @@ await session.prompt("What files are in the current directory?");
## Installation
```bash
-npm install @earendil-works/pi-coding-agent
+bun add @earendil-works/pi-coding-agent
```
The SDK is included in the main package. No separate installation needed.
diff --git a/packages/coding-agent/docs/skills.md b/packages/coding-agent/docs/skills.md
index ef41c05f27..8b156f0b41 100644
--- a/packages/coding-agent/docs/skills.md
+++ b/packages/coding-agent/docs/skills.md
@@ -278,7 +278,7 @@ description: What this skill does and when to use it. Be specific.
Run once before first use:
```bash
-cd /path/to/skill && npm install
+cd /path/to/skill && bun install
```
## Usage
@@ -369,7 +369,7 @@ description: Web search and content extraction via Brave Search API. Use for sea
## Setup
```bash
-cd /path/to/brave-search && npm install
+cd /path/to/brave-search && bun install
```
## Search
diff --git a/packages/coding-agent/docs/termux.md b/packages/coding-agent/docs/termux.md
index c43eab4122..fd127ac056 100644
--- a/packages/coding-agent/docs/termux.md
+++ b/packages/coding-agent/docs/termux.md
@@ -14,12 +14,12 @@ Prime Agent runs on Android via [Termux](https://termux.dev/), a terminal emulat
pkg update && pkg upgrade
# Install dependencies
-pkg install nodejs termux-api git ripgrep
+pkg install bun termux-api git ripgrep
# Clone and install Prime Agent from source
git clone https://github.com/PrimeIntellect-ai/prime-agent.git
cd prime-agent
-npm ci
+bun install --frozen-lockfile
# Run Prime Agent
./prime-agent.sh
diff --git a/packages/coding-agent/docs/tui.md b/packages/coding-agent/docs/tui.md
index 5e9c371255..2d25174997 100644
--- a/packages/coding-agent/docs/tui.md
+++ b/packages/coding-agent/docs/tui.md
@@ -448,7 +448,7 @@ interface MyTheme {
Set `PI_TUI_WRITE_LOG` to capture the raw ANSI stream written to stdout.
```bash
-PI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx packages/tui/test/chat-simple.ts
+PI_TUI_WRITE_LOG=/tmp/tui-ansi.log bun packages/tui/test/chat-simple.ts
```
## Performance
diff --git a/packages/coding-agent/docs/usage.md b/packages/coding-agent/docs/usage.md
index 3d68af75bd..087bbf196a 100644
--- a/packages/coding-agent/docs/usage.md
+++ b/packages/coding-agent/docs/usage.md
@@ -279,7 +279,7 @@ For example, this noninteractive run uses a locally available model configuratio
```bash
prime-agent -p \
--autonomous \
- --autonomous-gate "npm run check" \
+ --autonomous-gate "bun run check" \
--autonomous-gate-retries 2 \
--autonomous-gate-timeout-ms 300000 \
--autonomous-max-continuations 3 \
diff --git a/packages/coding-agent/docs/windows.md b/packages/coding-agent/docs/windows.md
index 3f7da6c61d..33d70d9171 100644
--- a/packages/coding-agent/docs/windows.md
+++ b/packages/coding-agent/docs/windows.md
@@ -1,17 +1,97 @@
# Windows Setup
-Prime Agent requires a bash shell on Windows. Checked locations (in order):
+Prime Agent supports native Windows 10 and 11 on x64 and Arm64. WSL is not required.
-1. Custom path from `~/.prime/agent/settings.json`
-2. Git Bash (`C:\Program Files\Git\bin\bash.exe`)
-3. `bash.exe` on PATH (Cygwin, MSYS2, WSL)
+## Requirements
-For most users, [Git for Windows](https://git-scm.com/download/win) is sufficient.
+- PowerShell 5.1 or newer for installation and updates
+- [Git for Windows](https://git-scm.com/download/win), including Git Bash, for shell commands
+- Windows Terminal is recommended
-## Custom Shell Path
+Prime Agent itself runs as a native Bun-compiled Windows executable. Git Bash is the command shell used by the `bash` tool; it does not run Prime Agent under WSL.
+
+## Install
+
+Open PowerShell and run:
+
+```powershell
+irm https://app.primeintellect.ai/prime-agent/install.ps1 | iex
+```
+
+The installer downloads the x64 or Arm64 ZIP archive, requires a matching SHA-256 entry, installs versioned files under `%LOCALAPPDATA%\PrimeAgent`, and adds `%LOCALAPPDATA%\PrimeAgent\bin` to your user `PATH`.
+
+Open a new terminal after the first install, then run:
+
+```powershell
+prime-agent
+```
+
+To install the beta channel:
+
+```powershell
+irm https://app.primeintellect.ai/prime-agent/install-beta.ps1 | iex
+```
+
+## Update or select a version
+
+```powershell
+& ([scriptblock]::Create((irm https://app.primeintellect.ai/prime-agent/install.ps1))) -Update
+& ([scriptblock]::Create((irm https://app.primeintellect.ai/prime-agent/install.ps1))) -Version 0.9.1
+```
+
+Downloads and extraction finish before the command shim changes. A failed download, checksum, extraction, or activation leaves the previous version active.
+
+## Uninstall
+
+```powershell
+& ([scriptblock]::Create((irm https://app.primeintellect.ai/prime-agent/install.ps1))) -Uninstall
+```
+
+This removes `%LOCALAPPDATA%\PrimeAgent` and its entry from your user `PATH`. Open a new terminal to see the updated `PATH`.
+
+## Git Bash discovery
+
+Prime Agent checks shell locations in this order:
+
+1. `shellPath` in `~/.prime/agent/settings.json`
+2. `C:\Program Files\Git\bin\bash.exe`
+3. `C:\Program Files (x86)\Git\bin\bash.exe`
+4. `bash.exe` on `PATH`
+
+A custom shell can be configured as follows:
```json
{
- "shellPath": "C:\\cygwin64\\bin\\bash.exe"
+ "shellPath": "C:\\Program Files\\Git\\bin\\bash.exe"
}
```
+
+Git Bash is the supported default. Cygwin and MSYS2 can be selected explicitly, but they are not part of the primary Windows validation path. WSL paths are not used by the native installer.
+
+## Python kernel
+
+The persistent CPython kernel is prepared automatically with `uv`. The Windows bootstrap uses PowerShell and a virtual environment at `~/.prime/agent/kernel-venv`, whose interpreter is under `Scripts\python.exe`.
+
+On managed systems that block the uv installer, install uv separately or set `PRIME_AGENT_KERNEL_PYTHON` to a CPython environment that already contains `prime-agent-runtime`.
+
+## Troubleshooting
+
+### `prime-agent` is not recognized
+
+Open a new terminal. Confirm that `%LOCALAPPDATA%\PrimeAgent\bin` appears in your user `PATH` and that `prime-agent.cmd` exists there.
+
+### No bash shell found
+
+Install Git for Windows with Git Bash. If it is installed in a custom directory, set `shellPath` in `~/.prime/agent/settings.json`.
+
+### PowerShell blocks the installer
+
+Run the command in a normal interactive PowerShell session. Organization policy can block downloaded scripts or remote content. In that case, download `install.ps1`, review it, and run it according to your organization's policy.
+
+### A child process survives cancellation
+
+Run `prime-agent shutdown --force`. Prime Agent uses Windows process-tree termination and CPython Job Objects, but a process moved into a separately managed Windows service or job can require manual termination.
+
+### Terminal input or colors are incorrect
+
+Use an updated Windows Terminal profile. See [Terminal Setup](terminal-setup.md) for the recommended key mappings.
diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts b/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts
index 69267f4006..73318cd2b5 100644
--- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts
+++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts
@@ -10,7 +10,7 @@
*
* Usage:
* # First install dependencies
- * cd packages/coding-agent/examples/extensions/custom-provider && npm install
+ * cd packages/coding-agent/examples/extensions/custom-provider && bun install
*
* # With OAuth (run /login custom-anthropic first)
* pi -e ./packages/coding-agent/examples/extensions/custom-provider
diff --git a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts
index c796373258..88f8a543e7 100644
--- a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts
+++ b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/test.ts
@@ -1,11 +1,11 @@
/**
* Test script for GitLab Duo extension
- * Run: npx tsx test.ts [model-id] [--thinking]
+ * Run: bun test.ts [model-id] [--thinking]
*
* Examples:
- * npx tsx test.ts # Test default (claude-sonnet-4-5-20250929)
- * npx tsx test.ts gpt-5-codex # Test GPT-5 Codex
- * npx tsx test.ts claude-sonnet-4-5-20250929 --thinking
+ * bun test.ts # Test default (claude-sonnet-4-5-20250929)
+ * bun test.ts gpt-5-codex # Test GPT-5 Codex
+ * bun test.ts claude-sonnet-4-5-20250929 --thinking
*/
import { type Api, type Context, type Model, registerApiProvider, streamSimple } from "@earendil-works/pi-ai";
diff --git a/packages/coding-agent/examples/extensions/plan-mode/README.md b/packages/coding-agent/examples/extensions/plan-mode/README.md
index 6618c80860..0783559388 100644
--- a/packages/coding-agent/examples/extensions/plan-mode/README.md
+++ b/packages/coding-agent/examples/extensions/plan-mode/README.md
@@ -60,6 +60,6 @@ Safe commands (allowed):
Blocked commands:
- File modification: `rm`, `mv`, `cp`, `mkdir`, `touch`
- Git write: `git add`, `git commit`, `git push`
-- Package install: `npm install`, `yarn add`, `pip install`
+- Package install: `bun install`, `npm install`, `yarn add`, `pip install`
- System: `sudo`, `kill`, `reboot`
- Editors: `vim`, `nano`, `code`
diff --git a/packages/coding-agent/examples/extensions/sandbox/index.ts b/packages/coding-agent/examples/extensions/sandbox/index.ts
index 55524872cc..ebe6dede8a 100644
--- a/packages/coding-agent/examples/extensions/sandbox/index.ts
+++ b/packages/coding-agent/examples/extensions/sandbox/index.ts
@@ -36,7 +36,7 @@
*
* Setup:
* 1. Copy sandbox/ directory to ~/.prime/agent/extensions/
- * 2. Run `npm install` in ~/.prime/agent/extensions/sandbox/
+ * 2. Run `bun install` in ~/.prime/agent/extensions/sandbox/
*
* Linux also requires: bubblewrap, socat, ripgrep
*/
diff --git a/packages/coding-agent/examples/extensions/with-deps/index.ts b/packages/coding-agent/examples/extensions/with-deps/index.ts
index 17be7da048..6008d65e55 100644
--- a/packages/coding-agent/examples/extensions/with-deps/index.ts
+++ b/packages/coding-agent/examples/extensions/with-deps/index.ts
@@ -2,7 +2,7 @@
* Example extension with its own npm dependencies.
* Tests that jiti resolves modules from the extension's own node_modules.
*
- * Requires: npm install in this directory
+ * Requires: bun install in this directory
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
diff --git a/packages/coding-agent/examples/rpc-extension-ui.ts b/packages/coding-agent/examples/rpc-extension-ui.ts
index bcbb6ec898..9816e8b2bf 100644
--- a/packages/coding-agent/examples/rpc-extension-ui.ts
+++ b/packages/coding-agent/examples/rpc-extension-ui.ts
@@ -5,7 +5,7 @@
* Demonstrates how to build a custom UI on top of the RPC protocol,
* including handling extension UI requests (select, confirm, input, editor).
*
- * Usage: npx tsx examples/rpc-extension-ui.ts
+ * Usage: bun examples/rpc-extension-ui.ts
*
* Slash commands:
* /select - demo select dialog
diff --git a/packages/coding-agent/examples/sdk/README.md b/packages/coding-agent/examples/sdk/README.md
index aac8b4ea6b..21d05192cd 100644
--- a/packages/coding-agent/examples/sdk/README.md
+++ b/packages/coding-agent/examples/sdk/README.md
@@ -30,7 +30,7 @@ The runtime example shows how to build a recreate function that closes over proc
```bash
cd packages/coding-agent
-npx tsx examples/sdk/01-minimal.ts
+bun examples/sdk/01-minimal.ts
```
## Quick Reference
diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json
index b9dafc7990..aee7466abe 100644
--- a/packages/coding-agent/package.json
+++ b/packages/coding-agent/package.json
@@ -31,20 +31,20 @@
"CHANGELOG.md"
],
"scripts": {
- "clean": "shx rm -rf dist",
- "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
- "build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && npm run copy-assets && npm run bundle",
- "build:binary": "npm --prefix ../tui run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm run build && bun build --compile ./dist/bun/cli.js --outfile dist/pi && npm run copy-binary-assets",
- "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && shx rm -rf dist/prime-agent-runtime && shx cp -r ../../prime-agent-runtime dist/prime-agent-runtime && shx rm -rf dist/skills && shx cp -r skills dist/skills",
- "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/ && shx rm -rf dist/skills && shx cp -r skills dist/skills",
- "test": "vitest --run",
- "test:ci": "tsx src/core/kernel/bootstrap-cli.ts && vitest --run --exclude test/daemon-supervisor-process.test.ts",
- "test:process": "vitest --run test/daemon-supervisor-process.test.ts",
- "test:process-stress": "vitest --run --tagsFilter process-stress test/daemon-supervisor-process.test.ts",
- "postinstall": "node postinstall.cjs",
- "prepublishOnly": "npm run clean && npm run build",
- "bundle": "node scripts/bundle.mjs",
- "test:kernel": "vitest --run --no-file-parallelism --tagsFilter kernel-heavy test/acp-kernel-features.test.ts test/acp-cold-cli.test.ts test/kernel-goal-skill.test.ts test/repl-kernel-state-roundtrip.test.ts test/repl-kernel-mcp-shutdown.test.ts"
+ "clean": "bun ../../scripts/remove-paths.ts dist",
+ "dev": "bun --bun tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
+ "build": "bun --bun tsgo -p tsconfig.build.json && bun scripts/copy-assets.ts package && bun run bundle",
+ "build:binary": "bun run --cwd ../tui build && bun run --cwd ../ai build && bun run --cwd ../agent build && bun run build && bun build --compile --minify --keep-names --bytecode --format=esm ./dist/bun/cli.js --outfile dist/pi && bun scripts/copy-assets.ts binary",
+ "copy-assets": "bun scripts/copy-assets.ts package",
+ "copy-binary-assets": "bun scripts/copy-assets.ts binary",
+ "test": "bun ../../scripts/run-with-clean-env.ts bun scripts/run-tests.ts",
+ "test:ci": "bun ../../scripts/run-with-clean-env.ts bun src/core/kernel/bootstrap-cli.ts && bun ../../scripts/run-with-clean-env.ts bun scripts/run-tests.ts",
+ "test:process": "bun ../../scripts/run-with-clean-env.ts bun test --isolate --timeout 30000 test/daemon-supervisor-process.test.ts",
+ "test:process-stress": "PRIME_AGENT_TEST_TAGS=process-stress bun ../../scripts/run-with-clean-env.ts bun test --isolate --timeout 30000 test/daemon-supervisor-process.test.ts",
+ "postinstall": "bun postinstall.cjs",
+ "prepublishOnly": "bun run clean && bun run build",
+ "bundle": "bun scripts/bundle.mjs",
+ "test:kernel": "PRIME_AGENT_TEST_TAGS=kernel-heavy bun ../../scripts/run-with-clean-env.ts bun test --isolate --timeout 30000 test/acp-kernel-features.test.ts test/acp-cold-cli.test.ts test/kernel-goal-skill.test.ts test/repl-kernel-state-roundtrip.test.ts test/repl-kernel-mcp-shutdown.test.ts test/repl-kernel-parent-watchdog.test.ts"
},
"dependencies": {
"@agentclientprotocol/sdk": "^1.3.0",
@@ -72,10 +72,7 @@
"yaml": "^2.9.0"
},
"overrides": {
- "rimraf": "6.1.2",
- "gaxios": {
- "rimraf": "6.1.2"
- }
+ "rimraf": "6.1.2"
},
"optionalDependencies": {
"@mariozechner/clipboard": "^0.3.9"
@@ -86,10 +83,7 @@
"@types/ms": "^2.1.0",
"@types/node": "^24.3.0",
"@types/proper-lockfile": "^4.1.4",
- "esbuild": "^0.28.1",
- "shx": "^0.4.0",
- "typescript": "^7.0.2",
- "vitest": "^4.1.10"
+ "typescript": "^7.0.2"
},
"keywords": [
"coding-agent",
@@ -107,6 +101,7 @@
"directory": "packages/coding-agent"
},
"engines": {
- "node": ">=22.8.0"
+ "node": ">=22.8.0",
+ "bun": "1.4.0"
}
}
diff --git a/packages/coding-agent/scripts/bundle.mjs b/packages/coding-agent/scripts/bundle.mjs
index c14d432123..ae780849c5 100644
--- a/packages/coding-agent/scripts/bundle.mjs
+++ b/packages/coding-agent/scripts/bundle.mjs
@@ -1,6 +1,6 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
/**
- * Bundles the compiled CLI entry (dist/cli.js) into dist/bundle/ with esbuild.
+ * Bundles the compiled CLI entry (dist/cli.js) into dist/bundle/ with Bun.
*
* Why: the unbundled module graph is ~2,500 files; resolving and reading them
* dominates startup (~1.5s on slow filesystems). The bundle loads the same code
@@ -15,7 +15,6 @@ import { chmodSync, readFileSync, rmSync } from "node:fs";
import { execFileSync } from "node:child_process";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
-import { build } from "esbuild";
const packageDir = dirname(dirname(fileURLToPath(import.meta.url)));
const outdir = join(packageDir, "dist", "bundle");
@@ -31,22 +30,34 @@ try {
rmSync(outdir, { recursive: true, force: true });
-await build({
- entryPoints: [join(packageDir, "dist", "cli.js")],
+const result = await Bun.build({
+ entrypoints: [join(packageDir, "dist", "cli.js")],
outdir,
- bundle: true,
splitting: true,
format: "esm",
- platform: "node",
+ target: "node",
+ sourcemap: "linked",
// Native or interop-sensitive packages stay external; they resolve from
// node_modules at runtime (and are loaded via createRequire/lazily anyway).
external: ["koffi", "undici", "@silvia-odwyer/photon-node", "@mariozechner/clipboard"],
- define: { __PI_BUNDLED__: "true", __PI_BUILD_ID__: JSON.stringify(buildId) },
- banner: {
- js: "import { createRequire as __piBundleCreateRequire } from 'node:module'; const require = __piBundleCreateRequire(import.meta.url);",
+ define: {
+ __PI_BUNDLED__: "true",
+ __PI_BUILD_ID__: JSON.stringify(buildId),
},
- logLevel: "warning",
+ banner: `import { createRequire as __piBundleCreateRequire } from 'node:module'; const require = __piBundleCreateRequire(import.meta.url);`,
+ naming: {
+ entry: "[name].js",
+ chunk: "[name]-[hash].js",
+ },
+ throw: false,
});
+if (!result.success) {
+ for (const log of result.logs) {
+ console.error(log);
+ }
+ process.exit(1);
+}
+
chmodSync(join(outdir, "cli.js"), 0o755);
console.log("bundled dist/cli.js -> dist/bundle/");
diff --git a/packages/coding-agent/scripts/copy-assets.ts b/packages/coding-agent/scripts/copy-assets.ts
new file mode 100644
index 0000000000..b0be2b34af
--- /dev/null
+++ b/packages/coding-agent/scripts/copy-assets.ts
@@ -0,0 +1,74 @@
+import { chmod, cp, mkdir, readdir, rm } from "node:fs/promises";
+import { basename, dirname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const repoDir = resolve(packageDir, "../..");
+const distDir = join(packageDir, "dist");
+const mode = process.argv[2];
+
+async function copyFiles(sourceDir: string, targetDir: string, suffix: string): Promise {
+ await mkdir(targetDir, { recursive: true });
+ for (const entry of await readdir(sourceDir, { withFileTypes: true })) {
+ if (entry.isFile() && entry.name.endsWith(suffix)) {
+ await cp(join(sourceDir, entry.name), join(targetDir, entry.name));
+ }
+ }
+}
+
+const excludedReleaseDirectoryNames = new Set(["node_modules", ".venv", "__pycache__", ".pytest_cache"]);
+
+function includeReleasePath(source: string): boolean {
+ return !source.split(/[\\/]/).some((part) => excludedReleaseDirectoryNames.has(part));
+}
+
+async function replaceDirectory(source: string, target: string): Promise {
+ await rm(target, { recursive: true, force: true });
+ await cp(source, target, { recursive: true, filter: includeReleasePath });
+}
+
+async function copyFile(source: string, targetDir: string): Promise {
+ await mkdir(targetDir, { recursive: true });
+ await cp(source, join(targetDir, basename(source)));
+}
+
+async function copyPackageAssets(): Promise {
+ await chmod(join(distDir, "cli.js"), 0o755);
+ await copyFiles(join(packageDir, "src/modes/interactive/theme"), join(distDir, "modes/interactive/theme"), ".json");
+ await copyFiles(join(packageDir, "src/modes/interactive/assets"), join(distDir, "modes/interactive/assets"), ".png");
+ const exportDir = join(distDir, "core/export-html");
+ for (const name of ["template.html", "template.css", "template.js"]) {
+ await copyFile(join(packageDir, "src/core/export-html", name), exportDir);
+ }
+ await copyFiles(join(packageDir, "src/core/export-html/vendor"), join(exportDir, "vendor"), ".js");
+ await replaceDirectory(join(repoDir, "prime-agent-runtime"), join(distDir, "prime-agent-runtime"));
+ await replaceDirectory(join(packageDir, "skills"), join(distDir, "skills"));
+}
+
+async function copyBinaryAssets(): Promise {
+ for (const name of ["package.json", "README.md", "CHANGELOG.md"]) {
+ await copyFile(join(packageDir, name), distDir);
+ }
+ await copyFile(join(repoDir, "install.sh"), distDir);
+ await copyFile(join(repoDir, "install.ps1"), distDir);
+ await copyFiles(join(packageDir, "src/modes/interactive/theme"), join(distDir, "theme"), ".json");
+ await copyFiles(join(packageDir, "src/modes/interactive/assets"), join(distDir, "assets"), ".png");
+ for (const name of ["template.html", "template.css", "template.js"]) {
+ await copyFile(join(packageDir, "src/core/export-html", name), join(distDir, "export-html"));
+ }
+ await copyFiles(join(packageDir, "src/core/export-html/vendor"), join(distDir, "export-html/vendor"), ".js");
+ await replaceDirectory(join(packageDir, "docs"), join(distDir, "docs"));
+ await replaceDirectory(join(packageDir, "examples"), join(distDir, "examples"));
+ await copyFile(join(repoDir, "node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm"), distDir);
+ await replaceDirectory(join(repoDir, "prime-agent-runtime"), join(distDir, "prime-agent-runtime"));
+ await replaceDirectory(join(packageDir, "skills"), join(distDir, "skills"));
+}
+
+if (mode === "package") {
+ await copyPackageAssets();
+} else if (mode === "binary") {
+ await copyBinaryAssets();
+} else {
+ console.error("Usage: bun scripts/copy-assets.ts ");
+ process.exit(2);
+}
diff --git a/packages/coding-agent/scripts/run-tests.ts b/packages/coding-agent/scripts/run-tests.ts
new file mode 100644
index 0000000000..caaf9d42ac
--- /dev/null
+++ b/packages/coding-agent/scripts/run-tests.ts
@@ -0,0 +1,61 @@
+#!/usr/bin/env bun
+
+interface Shard {
+ index: number;
+ total: number;
+}
+
+function parseShard(args: string[]): { shard?: Shard; forwarded: string[] } {
+ let shard: Shard | undefined;
+ const forwarded: string[] = [];
+ for (const arg of args) {
+ if (!arg.startsWith("--shard=")) {
+ forwarded.push(arg);
+ continue;
+ }
+ const match = arg.slice("--shard=".length).match(/^(\d+)\/(\d+)$/);
+ if (!match) throw new Error(`Invalid shard: ${arg}`);
+ const index = Number(match[1]);
+ const total = Number(match[2]);
+ if (!Number.isInteger(index) || !Number.isInteger(total) || index < 1 || index > total) {
+ throw new Error(`Invalid shard: ${arg}`);
+ }
+ shard = { index, total };
+ }
+ return { shard, forwarded };
+}
+
+const excluded = new Set(["test/compiled-artifact-smoke.test.ts", "test/daemon-supervisor-process.test.ts"]);
+const { shard, forwarded } = parseShard(process.argv.slice(2));
+const discovered: string[] = [];
+for await (const file of new Bun.Glob("test/**/*.test.ts").scan({ cwd: process.cwd(), onlyFiles: true })) {
+ if (!excluded.has(file)) discovered.push(file);
+}
+discovered.sort();
+const selected = shard
+ ? discovered.filter((_file, position) => position % shard.total === shard.index - 1)
+ : discovered;
+
+console.log(
+ `Running ${selected.length}/${discovered.length} coding-agent test files${shard ? ` (shard ${shard.index}/${shard.total})` : ""}`,
+);
+
+const failures: Array<{ file: string; exitCode: number }> = [];
+for (const file of selected) {
+ const child = Bun.spawn([process.execPath, "test", "--isolate", "--timeout", "30000", ...forwarded, file], {
+ cwd: process.cwd(),
+ env: process.env,
+ stdin: "ignore",
+ stdout: "inherit",
+ stderr: "inherit",
+ });
+ const exitCode = await child.exited;
+ if (exitCode !== 0) failures.push({ file, exitCode });
+}
+
+if (failures.length > 0) {
+ console.error(`Failed coding-agent test files (${failures.length}/${selected.length}):`);
+ for (const failure of failures) console.error(`- ${failure.file} (exit ${failure.exitCode})`);
+ process.exit(1);
+}
+console.log(`Passed ${selected.length} coding-agent test files.`);
diff --git a/packages/coding-agent/src/bun/cli.ts b/packages/coding-agent/src/bun/cli.ts
index 0aacb95b08..fb9318f941 100644
--- a/packages/coding-agent/src/bun/cli.ts
+++ b/packages/coding-agent/src/bun/cli.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
import { APP_NAME } from "../config.js";
process.title = APP_NAME;
diff --git a/packages/coding-agent/src/bun/register-bedrock.ts b/packages/coding-agent/src/bun/register-bedrock.ts
index 92af0dde28..839cbcdb2b 100644
--- a/packages/coding-agent/src/bun/register-bedrock.ts
+++ b/packages/coding-agent/src/bun/register-bedrock.ts
@@ -1,4 +1,6 @@
-import { setBedrockProviderModule } from "@earendil-works/pi-ai";
-import { bedrockProviderModule } from "@earendil-works/pi-ai/bedrock-provider";
+import { setBedrockProviderModuleLoader } from "@earendil-works/pi-ai";
-setBedrockProviderModule(bedrockProviderModule);
+setBedrockProviderModuleLoader(async () => {
+ const { bedrockProviderModule } = await import("@earendil-works/pi-ai/bedrock-provider");
+ return bedrockProviderModule;
+});
diff --git a/packages/coding-agent/src/cli.ts b/packages/coding-agent/src/cli.ts
index 07d1b9b3d0..38b8024c7a 100644
--- a/packages/coding-agent/src/cli.ts
+++ b/packages/coding-agent/src/cli.ts
@@ -3,11 +3,13 @@
// behind the dynamic import, after the dependency-free guard runs.
import { assertNodeVersion } from "./cli/node-version-check.js";
-const supported = assertNodeVersion({
- version: process.versions.node,
- log: console.error,
- exit: (code) => process.exit(code),
-});
+const supported = process.versions.bun
+ ? true
+ : assertNodeVersion({
+ version: process.versions.node,
+ log: console.error,
+ exit: (code) => process.exit(code),
+ });
if (supported) {
const { runCli } = await import("./cli-main.js");
diff --git a/packages/coding-agent/src/cli/daemon-launch.ts b/packages/coding-agent/src/cli/daemon-launch.ts
index 0d57425064..37073959a7 100644
--- a/packages/coding-agent/src/cli/daemon-launch.ts
+++ b/packages/coding-agent/src/cli/daemon-launch.ts
@@ -346,11 +346,22 @@ async function shutdownStaleDaemonIfNotBusy(socketPath: string): Promise {
+export interface DaemonLaunchTiming {
+ startupTimeoutMs?: number;
+ initialHelloTimeoutMs?: number;
+}
+
+async function ensureDaemonRunning(
+ socketPath: string,
+ spawnCwd?: string,
+ timing: DaemonLaunchTiming = {},
+): Promise {
+ const startupTimeoutMs = timing.startupTimeoutMs ?? DAEMON_STARTUP_TIMEOUT_MS;
+ const initialHelloTimeoutMs = timing.initialHelloTimeoutMs ?? 2000;
const probeStartedAt = Date.now();
- let probe = await probeDaemonVersion(socketPath);
+ let probe = await probeDaemonVersion(socketPath, initialHelloTimeoutMs);
if (probe.status === "unresponsive") {
- const remainingStartupMs = Math.max(1, DAEMON_STARTUP_TIMEOUT_MS - (Date.now() - probeStartedAt));
+ const remainingStartupMs = Math.max(1, startupTimeoutMs - (Date.now() - probeStartedAt));
probe = await probeDaemonVersion(socketPath, remainingStartupMs);
}
if (probe.status === "current") {
@@ -358,7 +369,7 @@ async function ensureDaemonRunning(socketPath: string, spawnCwd?: string): Promi
}
if (probe.status === "unresponsive") {
throw new Error(
- `Prime Agent daemon on ${socketPath} accepted connections but did not finish startup within ${DAEMON_STARTUP_TIMEOUT_MS / 1000} seconds. ` +
+ `Prime Agent daemon on ${socketPath} accepted connections but did not finish startup within ${startupTimeoutMs / 1000} seconds. ` +
`It was left running to avoid interrupting active work.
Run:
@@ -436,7 +447,7 @@ Then retry the original command.`,
// A child exit is not immediately fatal: it may have lost the socket to a
// concurrent launcher whose daemon is still booting. Keep probing for a
// short grace window before attributing the failure to the exit.
- const deadline = Date.now() + DAEMON_STARTUP_TIMEOUT_MS;
+ const deadline = Date.now() + startupTimeoutMs;
let exitDeadline: number | undefined;
while (Date.now() < Math.min(deadline, exitDeadline ?? Number.POSITIVE_INFINITY)) {
const started = await probeDaemonVersion(socketPath);
@@ -489,10 +500,14 @@ const ensurePromises = new Map>();
* main.ts share one probe/spawn; failed attempts are forgotten so a later call
* retries (and surfaces the real error at its await site).
*/
-export function ensureInteractiveDaemonRunning(socketPath: string, spawnCwd?: string): Promise {
+export function ensureInteractiveDaemonRunning(
+ socketPath: string,
+ spawnCwd?: string,
+ timing?: DaemonLaunchTiming,
+): Promise {
let promise = ensurePromises.get(socketPath);
if (!promise) {
- promise = ensureDaemonRunning(socketPath, spawnCwd);
+ promise = ensureDaemonRunning(socketPath, spawnCwd, timing);
ensurePromises.set(socketPath, promise);
const clear = () => {
if (ensurePromises.get(socketPath) === promise) {
diff --git a/packages/coding-agent/src/cli/daemon-ps.ts b/packages/coding-agent/src/cli/daemon-ps.ts
index 42c05f9a73..17dbf005a7 100644
--- a/packages/coding-agent/src/cli/daemon-ps.ts
+++ b/packages/coding-agent/src/cli/daemon-ps.ts
@@ -120,7 +120,9 @@ export function parseLsofListeners(stdout: string): DiscoveredDaemonProcess[] {
if (field === "p") {
pid = Number.parseInt(value, 10);
} else if (field === "n" && pid !== undefined && value.startsWith("/")) {
- const socketPath = normalizeSocketPath(value);
+ // Linux lsof appends metadata such as " type=STREAM" to Unix socket
+ // names, while macOS reports only the path.
+ const socketPath = normalizeSocketPath(value.replace(/\s+type=\w+(?:\s.*)?$/, ""));
const key = `${pid}:${socketPath}`;
if (!seen.has(key)) {
seen.add(key);
diff --git a/packages/coding-agent/src/cli/daemon-update-restart.ts b/packages/coding-agent/src/cli/daemon-update-restart.ts
index f436f2f6d1..3d6d251ee7 100644
--- a/packages/coding-agent/src/cli/daemon-update-restart.ts
+++ b/packages/coding-agent/src/cli/daemon-update-restart.ts
@@ -250,8 +250,9 @@ export class DaemonUpdateRestartStatusWriter {
private readonly path: string,
requestId: string,
socketPath: string,
+ private readonly clock: () => Date = () => new Date(),
) {
- const now = new Date().toISOString();
+ const now = this.clock().toISOString();
const processStartId = getProcessStartId(process.pid);
this.status = {
version: 1,
@@ -270,7 +271,7 @@ export class DaemonUpdateRestartStatusWriter {
update(
update: Partial>,
): void {
- const now = new Date().toISOString();
+ const now = this.clock().toISOString();
this.status = {
...this.status,
...update,
@@ -293,7 +294,7 @@ export class DaemonUpdateRestartStatusWriter {
}
touch(): void {
- this.status = { ...this.status, heartbeatAt: new Date().toISOString() };
+ this.status = { ...this.status, heartbeatAt: this.clock().toISOString() };
this.persist();
}
diff --git a/packages/coding-agent/src/cli/node-version-check.ts b/packages/coding-agent/src/cli/node-version-check.ts
index 24eef4eb8d..8e53590508 100644
--- a/packages/coding-agent/src/cli/node-version-check.ts
+++ b/packages/coding-agent/src/cli/node-version-check.ts
@@ -38,11 +38,6 @@ function isSupportedNodeVersion(version: ParsedNodeVersion): boolean {
}
export function assertNodeVersion(io: NodeVersionGuardIO): boolean {
- // Bun ships its own runtime; its node-compat version is unrelated to the user's Node.
- if (process.versions.bun) {
- return true;
- }
-
const version = parseVersion(io.version);
if (!version || isSupportedNodeVersion(version)) {
return true;
diff --git a/packages/coding-agent/src/cli/subprocess-launch.ts b/packages/coding-agent/src/cli/subprocess-launch.ts
index f5843b48d1..d634bdb81a 100644
--- a/packages/coding-agent/src/cli/subprocess-launch.ts
+++ b/packages/coding-agent/src/cli/subprocess-launch.ts
@@ -1,5 +1,4 @@
-import { existsSync } from "node:fs";
-import { dirname, isAbsolute, join, resolve } from "node:path";
+import { isAbsolute, resolve } from "node:path";
import { isBunBinary } from "../config.js";
export interface CliSubprocessLaunchSpec {
@@ -7,28 +6,8 @@ export interface CliSubprocessLaunchSpec {
args: string[];
}
-export function createCliSubprocessEnv(
- source: NodeJS.ProcessEnv = process.env,
- entrypoint = process.argv[1],
- execArgs: readonly string[] = process.execArgv,
-): NodeJS.ProcessEnv {
- const environment = { ...source };
- if (environment.TSX_TSCONFIG_PATH !== undefined || !entrypoint || !execArgs.some((arg) => arg.includes("tsx"))) {
- return environment;
- }
- let directory = dirname(resolve(entrypoint));
- while (true) {
- const tsconfigPath = join(directory, "tsconfig.json");
- if (existsSync(tsconfigPath) && existsSync(join(directory, "node_modules", "tsx", "package.json"))) {
- environment.TSX_TSCONFIG_PATH = tsconfigPath;
- return environment;
- }
- const parent = dirname(directory);
- if (parent === directory) {
- return environment;
- }
- directory = parent;
- }
+export function createCliSubprocessEnv(source: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
+ return { ...source };
}
function quoteCommandArgument(value: string): string {
diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts
index 596d468d1a..1d6fbbc8c7 100644
--- a/packages/coding-agent/src/config.ts
+++ b/packages/coding-agent/src/config.ts
@@ -98,7 +98,7 @@ export function detectInstallMethod(): InstallMethod {
if (resolvedPath.includes("/yarn/") || resolvedPath.includes("/.yarn/")) {
return "yarn";
}
- if (isBunRuntime || resolvedPath.includes("/install/global/node_modules/")) {
+ if (resolvedPath.includes("/install/global/node_modules/")) {
return "bun";
}
if (resolvedPath.includes("/npm/") || resolvedPath.includes("/node_modules/")) {
@@ -159,7 +159,20 @@ function getSelfUpdateCommandForMethod(
): SelfUpdateCommand | undefined {
const uninstallAfterInstall = isDirectPackageArtifactSpec(updateSpec);
switch (method) {
- case "bun-binary":
+ case "bun-binary": {
+ // Bun-compiled binary: use install.sh sidecar for self-update
+ const installScript = join(getPackageDir(), "install.sh");
+ if (existsSync(installScript)) {
+ const updateCmd = makeSelfUpdateCommandStep(installScript, ["--update"]);
+ return {
+ ...updateCmd,
+ steps: [updateCmd],
+ };
+ }
+ // install.sh not bundled; self-update not available via command.
+ // The caller shows a download link as fallback.
+ return undefined;
+ }
case "homebrew":
return undefined;
case "pnpm":
@@ -211,6 +224,7 @@ function readCommandOutput(
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
shell: shouldUseWindowsShell(command),
+ env: process.env,
});
if (result.status === 0) return result.stdout.trim() || undefined;
if (options.requireSuccess) {
@@ -314,8 +328,12 @@ export function getSelfUpdateCommand(
): SelfUpdateCommand | undefined {
const method = detectInstallMethod();
const command = getSelfUpdateCommandForMethod(method, packageName, updateSpec, npmCommand, updatePackageName);
- if (!command || !isManagedByGlobalPackageManager(method, packageName, npmCommand) || !isSelfUpdatePathWritable()) {
- return undefined;
+ if (!command) return undefined;
+ // Compiled binary installs are managed by their versioned installer directory.
+ if (method !== "bun-binary") {
+ if (!isManagedByGlobalPackageManager(method, packageName, npmCommand) || !isSelfUpdatePathWritable()) {
+ return undefined;
+ }
}
return command;
}
@@ -328,6 +346,10 @@ export function getSelfUpdateUnavailableInstruction(
): string {
const method = detectInstallMethod();
if (method === "bun-binary") {
+ const installScript = join(getPackageDir(), "install.sh");
+ if (existsSync(installScript)) {
+ return `Update with: ${installScript} --update`;
+ }
return `Download from: https://github.com/PrimeIntellect-ai/prime-agent/releases/latest`;
}
if (method === "homebrew") {
@@ -360,7 +382,7 @@ export function getUpdateInstruction(packageName: string): string {
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
* - For Bun binary: returns the directory containing the executable
* - For Node.js (dist/): returns __dirname (the dist/ directory)
- * - For tsx (src/): returns parent directory (the package root)
+ * - For direct source execution (src/): returns parent directory (the package root)
*/
export function getPackageDir(): string {
// Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
@@ -391,7 +413,7 @@ export function getPackageDir(): string {
* Get path to built-in themes directory (shipped with package)
* - For Bun binary: theme/ next to executable
* - For Node.js (dist/): dist/modes/interactive/theme/
- * - For tsx (src/): src/modes/interactive/theme/
+ * - For direct source execution (src/): src/modes/interactive/theme/
*/
export function getThemesDir(): string {
if (isBunBinary) {
@@ -407,7 +429,7 @@ export function getThemesDir(): string {
* Get path to HTML export template directory (shipped with package)
* - For Bun binary: export-html/ next to executable
* - For Node.js (dist/): dist/core/export-html/
- * - For tsx (src/): src/core/export-html/
+ * - For direct source execution (src/): src/core/export-html/
*/
export function getExportTemplateDir(): string {
if (isBunBinary) {
@@ -437,7 +459,7 @@ export function getChangelogPath(): string {
* Get path to built-in interactive assets directory.
* - For Bun binary: assets/ next to executable
* - For Node.js (dist/): dist/modes/interactive/assets/
- * - For tsx (src/): src/modes/interactive/assets/
+ * - For direct source execution (src/): src/modes/interactive/assets/
*/
export function getInteractiveAssetsDir(): string {
if (isBunBinary) {
@@ -457,7 +479,7 @@ export function getBundledInteractiveAssetPath(name: string): string {
* Get the directory containing built-in skills shipped with the package.
* - For Bun binary: skills/ next to executable
* - For Node.js (dist/): dist/skills/
- * - For tsx (src/): skills/ at the package root
+ * - For direct source execution (src/): skills/ at the package root
*/
export function getBundledSkillsDir(): string {
if (isBunBinary) {
diff --git a/packages/coding-agent/src/core/exec.ts b/packages/coding-agent/src/core/exec.ts
index c3cc408068..47fb0597bb 100644
--- a/packages/coding-agent/src/core/exec.ts
+++ b/packages/coding-agent/src/core/exec.ts
@@ -3,7 +3,7 @@
*/
import { spawn } from "node:child_process";
-import { waitForChildProcess } from "../utils/child-process.js";
+import { signalProcessGroupOrProcess, waitForChildProcess } from "../utils/child-process.js";
/**
* Options for executing shell commands.
@@ -80,7 +80,9 @@ export async function execCommand(
forceKillTimeoutId = setTimeout(() => {
forceKillTimeoutId = undefined;
if (proc.exitCode === null && proc.signalCode === null) {
- proc.kill("SIGKILL");
+ if (proc.pid) {
+ signalProcessGroupOrProcess(proc.pid, "SIGKILL");
+ }
}
}, 5000);
}
diff --git a/packages/coding-agent/src/core/extensions/loader.ts b/packages/coding-agent/src/core/extensions/loader.ts
index 2c7e81ad6b..ff978c8e8c 100644
--- a/packages/coding-agent/src/core/extensions/loader.ts
+++ b/packages/coding-agent/src/core/extensions/loader.ts
@@ -320,8 +320,8 @@ function createExtensionAPI(
return api;
}
-// Replaced with `true` by the esbuild CLI bundle (scripts/bundle.mjs); stays
-// undefined in unbundled dist/ and under tsx.
+// Replaced with `true` by the Bun CLI bundle (scripts/bundle.mjs); stays
+// undefined in unbundled dist/ and direct source execution.
declare const __PI_BUNDLED__: boolean | undefined;
const isBundledCli = typeof __PI_BUNDLED__ !== "undefined" && __PI_BUNDLED__ === true;
@@ -333,11 +333,11 @@ async function loadExtensionModule(extensionPath: string) {
const { createJiti } = await import("jiti/static");
const jiti = createJiti(import.meta.url, {
moduleCache: false,
- // In the Bun binary and the esbuild CLI bundle: serve pi packages from
+ // In the Bun binary and the Bun CLI bundle: serve pi packages from
// virtualModules so extensions share the bundle's module instances
// (file-path aliases would load a second, divergent copy of each package).
// Also disable tryNative so jiti handles ALL imports (not just the entry point)
- // In Node.js/dev: use aliases to resolve to node_modules paths
+ // In direct source execution: use aliases to resolve to node_modules paths
...(isBunBinary || isBundledCli
? { virtualModules: (await import("./bundled-modules.js")).VIRTUAL_MODULES, tryNative: false }
: { alias: getAliases() }),
@@ -522,7 +522,7 @@ function discoverExtensionsInDir(dir: string): string[] {
const discovered: string[] = [];
try {
- const entries = fs.readdirSync(dir, { withFileTypes: true });
+ const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
for (const entry of entries) {
const entryPath = path.join(dir, entry.name);
diff --git a/packages/coding-agent/src/core/kernel/bootstrap.ts b/packages/coding-agent/src/core/kernel/bootstrap.ts
index 2390749308..a2533875c1 100644
--- a/packages/coding-agent/src/core/kernel/bootstrap.ts
+++ b/packages/coding-agent/src/core/kernel/bootstrap.ts
@@ -34,7 +34,10 @@ const DEFAULT_RLM_EXTRA_PACKAGES = [
export const DEFAULT_RLM_EXTRA_UV_ARGS = DEFAULT_RLM_EXTRA_PACKAGES.map((pkg) => pkg.uvArg);
export const DEFAULT_RLM_EXTRA_IMPORT_NAMES = DEFAULT_RLM_EXTRA_PACKAGES.map((pkg) => pkg.importName);
export const DEFAULT_RLM_EXTRA_IMPORT_LABELS = DEFAULT_RLM_EXTRA_PACKAGES.map((pkg) => pkg.promptLabel);
-const UV_INSTALL_COMMAND = "curl -LsSf https://astral.sh/uv/install.sh | sh";
+const IS_WINDOWS = process.platform === "win32";
+const UV_INSTALL_COMMAND_POSIX = "curl -LsSf https://astral.sh/uv/install.sh | sh";
+const UV_INSTALL_COMMAND_WINDOWS =
+ 'powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://astral.sh/uv/install.ps1 | iex"';
const REQUIRED_HARNESS_METHODS = [
"create_memory",
"update_memory",
@@ -340,6 +343,11 @@ export function getKernelVenvDir(): string {
return path.join(os.homedir(), ".prime", "agent", "kernel-venv");
}
+/** Python executable path inside a venv directory, respecting Windows layout. */
+function venvPythonPath(venv: string): string {
+ return IS_WINDOWS ? path.join(venv, "Scripts", "python.exe") : path.join(venv, "bin", "python");
+}
+
function getXdgKernelVenvDir(): string {
const dataHome = process.env.XDG_DATA_HOME
? path.resolve(expandHome(process.env.XDG_DATA_HOME))
@@ -515,25 +523,35 @@ async function ensureUv(options: EnsureKernelPythonOptions): Promise {
const shouldInstallUv =
process.env.PRIME_AGENT_INSTALL_UV === "1" || (!options.onProgress && (await confirmUvInstall()));
if (!shouldInstallUv) {
+ const command = IS_WINDOWS ? UV_INSTALL_COMMAND_WINDOWS : UV_INSTALL_COMMAND_POSIX;
throw new Error(
- `uv is required to set up the Python kernel. Install uv yourself: ${UV_INSTALL_COMMAND}, ` +
+ `uv is required to set up the Python kernel. Install uv yourself: ${command}, ` +
"or set PRIME_AGENT_INSTALL_UV=1 to let prime-agent run that installer.",
);
}
reportProgress(options, "› installing uv (one-time)…");
try {
- await run("sh", ["-c", UV_INSTALL_COMMAND], { stdio: options.onProgress ? "ignore" : "inherit" });
+ if (IS_WINDOWS) {
+ await run(
+ "powershell",
+ ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "irm https://astral.sh/uv/install.ps1 | iex"],
+ { stdio: options.onProgress ? "ignore" : "inherit" },
+ );
+ } else {
+ await run("sh", ["-c", UV_INSTALL_COMMAND_POSIX], { stdio: options.onProgress ? "ignore" : "inherit" });
+ }
} catch (error) {
+ const command = IS_WINDOWS ? UV_INSTALL_COMMAND_WINDOWS : UV_INSTALL_COMMAND_POSIX;
throw new Error(
- `couldn't install uv from astral.sh; install it yourself: ${UV_INSTALL_COMMAND}, then re-run prime-agent. ${errorMessage(error)}`,
+ `couldn't install uv from astral.sh; install it yourself: ${command}, then re-run prime-agent. ${errorMessage(error)}`,
);
}
if (await isExecutable(localUv)) return localUv;
const installedFromPath = await findExecutable("uv");
if (installedFromPath) return installedFromPath;
- throw new Error("uv install completed but binary not found at ~/.local/bin/uv");
+ throw new Error(`uv install completed but binary not found at ${localUv}`);
}
async function confirmUvInstall(): Promise {
@@ -649,12 +667,12 @@ async function writeBootstrapVersion(
function runtimeCandidateDirs(): string[] {
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
- // dist/prime-agent-runtime is listed first deliberately: it is the only path stable
- // across every shipped layout (dist/, dist/bundle/, bun), where import.meta.url-relative
- // resolution breaks. `npm run build` rebuilds it from live source (copy-assets does
- // rm -rf + cp), so the staleness hash still refreshes on every build. The relative
- // paths below cover running from source (tsx) where dist/ hasn't been built.
+ // Compiled binary releases place the runtime beside the executable. npm bundles
+ // place it under dist/. `bun run build` refreshes both layouts from live source,
+ // so the staleness hash still changes with runtime code. The relative paths below
+ // cover direct source execution where dist/ has not been built.
return [
+ path.join(getPackageDir(), "prime-agent-runtime"),
path.join(getPackageDir(), "dist", "prime-agent-runtime"),
path.resolve(moduleDir, "..", "..", "prime-agent-runtime"),
path.resolve(moduleDir, "..", "..", "..", "..", "..", "prime-agent-runtime"),
@@ -716,7 +734,7 @@ async function bootstrapVenv(
): Promise {
await mkdir(path.dirname(venv), { recursive: true });
const uv = await ensureUv(options);
- const python = path.join(venv, "bin", "python");
+ const python = venvPythonPath(venv);
const sourceDir = await resolveRuntimeSourceDir();
const runtimeRequirement = sourceDir ?? RUNTIME_REQUIREMENT;
const runtimeIdentity = await resolveRuntimeIdentity();
@@ -873,7 +891,7 @@ async function ensureKernelPythonUncached(
}
const venv = await resolveWritableKernelVenvDir();
- const python = path.join(venv, "bin", "python");
+ const python = venvPythonPath(venv);
const runtimeIdentity = await resolveRuntimeIdentity();
if (await kernelReady(python, venv, runtimeIdentity, pythonSkills)) return python;
diff --git a/packages/coding-agent/src/core/output-guard.ts b/packages/coding-agent/src/core/output-guard.ts
index c078376046..59c1465566 100644
--- a/packages/coding-agent/src/core/output-guard.ts
+++ b/packages/coding-agent/src/core/output-guard.ts
@@ -2,6 +2,9 @@ interface StdoutTakeoverState {
rawStdoutWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;
rawStderrWrite: (chunk: string, callback?: (error?: Error | null) => void) => boolean;
originalStdoutWrite: typeof process.stdout.write;
+ originalConsoleLog: typeof console.log;
+ originalConsoleInfo: typeof console.info;
+ originalConsoleDebug: typeof console.debug;
}
let stdoutTakeoverState: StdoutTakeoverState | undefined;
@@ -14,6 +17,9 @@ export function takeOverStdout(): void {
const rawStdoutWrite = process.stdout.write.bind(process.stdout) as StdoutTakeoverState["rawStdoutWrite"];
const rawStderrWrite = process.stderr.write.bind(process.stderr) as StdoutTakeoverState["rawStderrWrite"];
const originalStdoutWrite = process.stdout.write;
+ const originalConsoleLog = console.log;
+ const originalConsoleInfo = console.info;
+ const originalConsoleDebug = console.debug;
process.stdout.write = ((
chunk: string | Uint8Array,
@@ -25,11 +31,19 @@ export function takeOverStdout(): void {
}
return rawStderrWrite(String(chunk), callback);
}) as typeof process.stdout.write;
+ // Bun's console methods write through native bindings instead of the mutable
+ // process.stdout.write property. Redirect them explicitly as well.
+ console.log = (...args: unknown[]) => console.error(...args);
+ console.info = (...args: unknown[]) => console.error(...args);
+ console.debug = (...args: unknown[]) => console.error(...args);
stdoutTakeoverState = {
rawStdoutWrite,
rawStderrWrite,
originalStdoutWrite,
+ originalConsoleLog,
+ originalConsoleInfo,
+ originalConsoleDebug,
};
}
@@ -39,6 +53,9 @@ export function restoreStdout(): void {
}
process.stdout.write = stdoutTakeoverState.originalStdoutWrite;
+ console.log = stdoutTakeoverState.originalConsoleLog;
+ console.info = stdoutTakeoverState.originalConsoleInfo;
+ console.debug = stdoutTakeoverState.originalConsoleDebug;
stdoutTakeoverState = undefined;
}
diff --git a/packages/coding-agent/src/core/prime-inference-auth.ts b/packages/coding-agent/src/core/prime-inference-auth.ts
index 3331ae3605..f761489ec6 100644
--- a/packages/coding-agent/src/core/prime-inference-auth.ts
+++ b/packages/coding-agent/src/core/prime-inference-auth.ts
@@ -87,7 +87,7 @@ export type PrimeTeam = {
};
function defaultPrimeCliConfigPath(): string {
- return join(homedir(), ".prime", "config.json");
+ return join(process.env.HOME || homedir(), ".prime", "config.json");
}
export function getPrimeCliConfigPath(configPath?: string): string {
diff --git a/packages/coding-agent/src/core/prompts/rlm.ts b/packages/coding-agent/src/core/prompts/rlm.ts
index ef610268e6..735f96a9f5 100644
--- a/packages/coding-agent/src/core/prompts/rlm.ts
+++ b/packages/coding-agent/src/core/prompts/rlm.ts
@@ -34,7 +34,7 @@ const REPL_CONTROL_PROMPT = [
"",
"Do not assume the REPL is the native runtime of the external thing being investigated. A repository, package, service, dataset, paper, website, benchmark, or API may have its own environment and normal interface. Evaluate external systems through their own interface, then use the REPL to coordinate the process and analyze what comes back.",
"",
- "`bash(command)` starts a shell command in the background and returns a handle immediately: `h = bash('npm test')`. Use `h.pid` / `h.running` for liveness, `h.tail(n)` / `h.output()` for combined stdout+stderr so far, `h.poll()` for a non-blocking result, `h.kill()` to terminate (SIGTERM, escalating to SIGKILL; on Windows kill() uses taskkill /T and detached or reparented descendants may survive), and `await h` (or `await bash('cmd')`) for the completed result with exit_code, output, and duration. Prefer bash() for long-running commands so the turn keeps working. Run shell commands with `bash()`, not `subprocess`/`os.system`: subprocess calls block the kernel, show the user nothing while they run, and spawn processes the harness cannot see or stop.",
+ "`bash(command)` starts a shell command in the background and returns a handle immediately: `h = bash('bun test')`. Use `h.pid` / `h.running` for liveness, `h.tail(n)` / `h.output()` for combined stdout+stderr so far, `h.poll()` for a non-blocking result, `h.kill()` to terminate (SIGTERM, escalating to SIGKILL; on Windows kill() uses taskkill /T and detached or reparented descendants may survive), and `await h` (or `await bash('cmd')`) for the completed result with exit_code, output, and duration. Prefer bash() for long-running commands so the turn keeps working. Run shell commands with `bash()`, not `subprocess`/`os.system`: subprocess calls block the kernel, show the user nothing while they run, and spawn processes the harness cannot see or stop.",
"",
"Important: do not install dependencies into the kernel just to make an external project import or run there. If a project import, test, script, CLI, or dependency check is needed, run it through that project's own environment and normal command interface. For example, in a Python repo use its documented commands, `uv run ...`, `.venv/bin/python ...`, or the active project interpreter from the repo root. Treat failures from that native environment as the relevant result.",
"",
diff --git a/packages/coding-agent/src/core/skills.ts b/packages/coding-agent/src/core/skills.ts
index d8b2bd28cf..85316240fa 100644
--- a/packages/coding-agent/src/core/skills.ts
+++ b/packages/coding-agent/src/core/skills.ts
@@ -550,9 +550,10 @@ export function loadSkills(options: LoadSkillsOptions): LoadSkillsResult {
if (skill.kind === "python") {
const existingPythonSkill = pythonImportMap.get(skill.python.importName);
if (existingPythonSkill) {
+ const [firstName, secondName] = [existingPythonSkill.name, skill.name].sort();
pythonImportDiagnostics.push({
type: "warning",
- message: `python import name "${skill.python.importName}" is shared by skills "${existingPythonSkill.name}" and "${skill.name}"`,
+ message: `python import name "${skill.python.importName}" is shared by skills "${firstName}" and "${secondName}"`,
path: skill.filePath,
});
} else {
diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts
index e90b2e5dcc..b4968d66c2 100644
--- a/packages/coding-agent/src/main.ts
+++ b/packages/coding-agent/src/main.ts
@@ -59,7 +59,7 @@ import { KeybindingsManager } from "./core/keybindings.js";
import { installFileLogSink, setLogContext } from "./core/logging.js";
import type { ModelRegistry } from "./core/model-registry.js";
import { findInitialModel, resolveCliModel, resolveModelScope, type ScopedModel } from "./core/model-resolver.js";
-import { restoreStdout, takeOverStdout } from "./core/output-guard.js";
+import { restoreStdout, takeOverStdout, writeRawStdout } from "./core/output-guard.js";
import type { CreateAgentSessionOptions } from "./core/sdk.js";
import {
formatMissingSessionCwdPrompt,
@@ -1095,12 +1095,17 @@ export async function main(args: string[], options?: MainOptions) {
takeOverStdout();
}
+ const writeCliMetadata = (text: string): void => {
+ const explicitMachineMode = parsed.print || parsed.mode !== undefined;
+ if (explicitMachineMode) console.error(text);
+ else writeRawStdout(`${text}\n`);
+ };
if (parsed.version) {
- console.log(VERSION);
+ writeCliMetadata(VERSION);
process.exit(0);
}
if (parsed.help) {
- console.log(formatTopLevelHelp());
+ writeCliMetadata(formatTopLevelHelp());
process.exit(0);
}
diff --git a/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts b/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts
index 17453aaabb..670cb2abb4 100644
--- a/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts
+++ b/packages/coding-agent/src/modes/daemon/daemon-catalog-process.ts
@@ -1,7 +1,6 @@
import { type ChildProcess, spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
-import { createRequire } from "node:module";
import { join, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { createCliSubprocessEnv, createCliSubprocessLaunchSpec } from "../../cli/subprocess-launch.js";
@@ -344,20 +343,16 @@ export class DaemonCatalogClient {
private async spawnCatalog(): Promise {
let command: string;
let args: string[];
- let environment = createCliSubprocessEnv({ ...process.env, [DAEMON_CATALOG_ROLE_ENV]: "1" });
+ const environment = createCliSubprocessEnv({ ...process.env, [DAEMON_CATALOG_ROLE_ENV]: "1" });
if (isBunBinary) {
const launch = createCliSubprocessLaunchSpec(["--version"]);
command = launch.command;
args = launch.args;
} else {
const catalogEntry = resolveDaemonCatalogEntrypoint();
- const execArgs = catalogEntry.endsWith(".ts")
- ? [...process.execArgv, "--import", createRequire(import.meta.url).resolve("tsx")]
- : process.execArgv;
- const launch = createCliSubprocessLaunchSpec([], undefined, execArgs, catalogEntry);
+ const launch = createCliSubprocessLaunchSpec([], undefined, process.execArgv, catalogEntry);
command = launch.command;
args = launch.args;
- environment = createCliSubprocessEnv(environment, catalogEntry, execArgs);
}
const child = spawn(command, args, {
cwd: process.cwd(),
diff --git a/packages/coding-agent/src/modes/daemon/daemon-client.ts b/packages/coding-agent/src/modes/daemon/daemon-client.ts
index b5ded67982..6af47df0ac 100644
--- a/packages/coding-agent/src/modes/daemon/daemon-client.ts
+++ b/packages/coding-agent/src/modes/daemon/daemon-client.ts
@@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto";
-import { createConnection, type Socket } from "node:net";
+import { Socket } from "node:net";
import { getDaemonLogPath } from "../../config.js";
import { attachJsonlLineReader, serializeJsonLine } from "../rpc/jsonl.js";
import {
@@ -200,7 +200,7 @@ export class DaemonClient {
}
this.helloMessage = undefined;
this.daemonClosingReason = undefined;
- const socket = createConnection(this.socketPath);
+ const socket = new Socket();
this.socket = socket;
this.detachReader = attachJsonlLineReader(socket, (line) => this.handleLine(line));
@@ -235,6 +235,7 @@ export class DaemonClient {
};
socket.once("connect", onConnect);
socket.once("error", onError);
+ socket.connect(this.socketPath);
});
socket.on("error", (error) =>
diff --git a/packages/coding-agent/src/modes/daemon/daemon-mode.ts b/packages/coding-agent/src/modes/daemon/daemon-mode.ts
index b99c047ccf..e9d0b1f4d3 100644
--- a/packages/coding-agent/src/modes/daemon/daemon-mode.ts
+++ b/packages/coding-agent/src/modes/daemon/daemon-mode.ts
@@ -183,6 +183,7 @@ import {
cleanupDaemonSocketPath,
type DaemonSocketIdentity,
defaultDaemonSocketPath,
+ endDaemonSocketAfterFlush,
getDaemonSocketIdentity,
normalizeSocketPath,
prepareDaemonSocketPath,
@@ -7313,7 +7314,7 @@ export class AgentDaemon {
}
for (const client of this.clients) {
client.detachInput();
- client.socket.end();
+ endDaemonSocketAfterFlush(client.socket);
}
await new Promise((resolveClose) => {
if (!this.server) {
diff --git a/packages/coding-agent/src/modes/daemon/daemon-socket.ts b/packages/coding-agent/src/modes/daemon/daemon-socket.ts
index 99a3a15105..4e18f219f0 100644
--- a/packages/coding-agent/src/modes/daemon/daemon-socket.ts
+++ b/packages/coding-agent/src/modes/daemon/daemon-socket.ts
@@ -1,6 +1,7 @@
+import { createHash } from "node:crypto";
import { chmodSync, existsSync, lstatSync, mkdirSync, unlinkSync } from "node:fs";
-import { createConnection } from "node:net";
-import { tmpdir } from "node:os";
+import { createConnection, type Socket } from "node:net";
+import { tmpdir, userInfo } from "node:os";
import { dirname, join } from "node:path";
import lockfile from "proper-lockfile";
@@ -12,6 +13,44 @@ const DAEMON_SOCKET_RELEASE_GRACE_MS = 1000;
const DAEMON_SOCKET_RELEASE_POLL_MS = 25;
const DAEMON_SOCKET_LOCK_STALE_MS = 5000;
const DAEMON_SOCKET_LOCK_UPDATE_MS = 1000;
+const DAEMON_SOCKET_FLUSH_TIMEOUT_MS = 1000;
+
+/** Map a Windows named pipe to a per-user filesystem lock target. */
+function windowsPipeLockPath(socketPath: string): string {
+ const pipeHash = createHash("sha256").update(socketPath.toLowerCase()).digest("hex").slice(0, 24);
+ return join(defaultDaemonSocketDir(), `pipe-${pipeHash}`);
+}
+
+/** Probe a Windows named pipe by attempting a short-lived connection. */
+function canConnectToWindowsPipe(pipePath: string): Promise {
+ return new Promise((resolveConnect) => {
+ const socket = createConnection(pipePath);
+ let settled = false;
+ const finish = (canConnect: boolean) => {
+ if (settled) return;
+ settled = true;
+ clearTimeout(timeoutId);
+ socket.removeAllListeners();
+ socket.destroy();
+ resolveConnect(canConnect);
+ };
+ const timeoutId = setTimeout(() => finish(false), 250);
+ socket.once("connect", () => finish(true));
+ socket.once("error", () => finish(false));
+ });
+}
+
+export function endDaemonSocketAfterFlush(socket: Socket): void {
+ if (socket.destroyed) return;
+ const forceCloseTimer = setTimeout(() => socket.destroy(), DAEMON_SOCKET_FLUSH_TIMEOUT_MS);
+ forceCloseTimer.unref();
+ const clearForceClose = () => clearTimeout(forceCloseTimer);
+ socket.once("close", clearForceClose);
+ socket.end(() => {
+ clearForceClose();
+ if (!socket.destroyed) socket.destroy();
+ });
+}
type DaemonSocketCompromiseListener = (error: Error) => void;
@@ -67,9 +106,14 @@ export interface DaemonSocketIdentity {
ino: number;
}
+export function windowsNamedPipeUserScope(): string {
+ const user = userInfo();
+ return createHash("sha256").update(`${user.username}\0${user.homedir}`).digest("hex").slice(0, 16);
+}
+
export function defaultDaemonSocketPath(): string {
if (process.platform === "win32") {
- return "\\\\.\\pipe\\prime-agent-daemon";
+ return `\\\\.\\pipe\\prime-agent-daemon-${windowsNamedPipeUserScope()}`;
}
return join(defaultDaemonSocketDir(), "daemon.sock");
}
@@ -77,7 +121,29 @@ export function defaultDaemonSocketPath(): string {
export async function acquireDaemonSocketPathLease(socketPath: string): Promise {
ensureDefaultDaemonSocketDir(socketPath);
if (process.platform === "win32") {
- return undefined;
+ // Use a file-based lock for Windows named pipes since proper-lockfile
+ // requires a filesystem path.
+ const lockPath = windowsPipeLockPath(socketPath);
+ let lease: DaemonSocketPathLease | undefined;
+ let pendingCompromise: Error | undefined;
+ const releaseLock = await lockfile.lock(lockPath, {
+ realpath: false,
+ stale: DAEMON_SOCKET_LOCK_STALE_MS,
+ update: DAEMON_SOCKET_LOCK_UPDATE_MS,
+ onCompromised: (error) => {
+ if (lease) lease.recordCompromise(error);
+ else pendingCompromise = error;
+ },
+ retries: {
+ retries: 600,
+ factor: 1,
+ minTimeout: DAEMON_SOCKET_RELEASE_POLL_MS,
+ maxTimeout: DAEMON_SOCKET_RELEASE_POLL_MS,
+ },
+ });
+ lease = new DaemonSocketPathLease(socketPath, releaseLock);
+ if (pendingCompromise) lease.recordCompromise(pendingCompromise);
+ return lease;
}
let lease: DaemonSocketPathLease | undefined;
let pendingCompromise: Error | undefined;
@@ -105,6 +171,11 @@ export async function prepareDaemonSocketPath(socketPath: string, lease?: Daemon
ensureDefaultDaemonSocketDir(socketPath);
if (process.platform === "win32") {
+ // Windows named pipes have no filesystem artifact. Check if a server
+ // is already listening on this pipe name; reject if so.
+ if (await canConnectToWindowsPipe(socketPath)) {
+ throw new Error(`Daemon socket already in use: ${socketPath}`);
+ }
return;
}
if (lease) {
@@ -178,6 +249,7 @@ async function prepareUnixDaemonSocketPath(socketPath: string, lease?: DaemonSoc
export function restrictDaemonSocketPath(socketPath: string): void {
if (process.platform === "win32") {
+ // Named pipes have no chmod-equivalent filesystem mode.
return;
}
chmodSync(socketPath, DAEMON_SOCKET_MODE);
@@ -185,6 +257,7 @@ export function restrictDaemonSocketPath(socketPath: string): void {
export function getDaemonSocketIdentity(socketPath: string): DaemonSocketIdentity | undefined {
if (process.platform === "win32") {
+ // Windows named pipes do not expose inode identity.
return undefined;
}
const stat = lstatSync(socketPath);
@@ -197,6 +270,8 @@ export function cleanupDaemonSocketPath(
lease?: DaemonSocketPathLease,
): void {
if (process.platform === "win32") {
+ // Named pipes have no filesystem artifact to clean up. The lock file
+ // is released when the lease is released.
return;
}
if (lease) {
@@ -277,29 +352,37 @@ function assertSocketLeaseHeld(socketPath: string, lease: DaemonSocketPathLease)
}
export function defaultDaemonSocketDir(): string {
- const suffix = typeof process.getuid === "function" ? String(process.getuid()) : "user";
- return join(tmpdir(), `prime-agent-${suffix}`);
+ const suffix =
+ process.platform === "win32"
+ ? windowsNamedPipeUserScope()
+ : typeof process.getuid === "function"
+ ? String(process.getuid())
+ : "user";
+ // Bun caches os.tmpdir() at startup. Read the standard overrides here so
+ // isolated processes and tests can select a runtime temporary directory.
+ const runtimeTmpDir = process.env.TMPDIR || process.env.TMP || process.env.TEMP || tmpdir();
+ return join(runtimeTmpDir, `prime-agent-${suffix}`);
}
function ensureDefaultDaemonSocketDir(socketPath: string): void {
- if (process.platform === "win32" || dirname(socketPath) !== defaultDaemonSocketDir()) {
- return;
- }
+ const socketDir = defaultDaemonSocketDir();
+ if (process.platform !== "win32" && dirname(socketPath) !== socketDir) return;
- if (!existsSync(defaultDaemonSocketDir())) {
- mkdirSync(defaultDaemonSocketDir(), { recursive: true, mode: DAEMON_SOCKET_DIR_MODE });
+ if (!existsSync(socketDir)) {
+ mkdirSync(socketDir, { recursive: true, mode: DAEMON_SOCKET_DIR_MODE });
}
- const stat = lstatSync(defaultDaemonSocketDir());
+ const stat = lstatSync(socketDir);
if (!stat.isDirectory()) {
- throw new Error(`Daemon socket directory exists and is not a directory: ${defaultDaemonSocketDir()}`);
+ throw new Error(`Daemon socket directory exists and is not a directory: ${socketDir}`);
}
- if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
- throw new Error(`Daemon socket directory is not owned by the current user: ${defaultDaemonSocketDir()}`);
+ if (process.platform !== "win32") {
+ if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
+ throw new Error(`Daemon socket directory is not owned by the current user: ${socketDir}`);
+ }
+ chmodSync(socketDir, DAEMON_SOCKET_DIR_MODE);
}
-
- chmodSync(defaultDaemonSocketDir(), DAEMON_SOCKET_DIR_MODE);
}
function canConnectToUnixSocket(socketPath: string): Promise {
diff --git a/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts b/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
index b5afde5da3..3b9fd058d5 100644
--- a/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
+++ b/packages/coding-agent/src/modes/daemon/daemon-supervisor.ts
@@ -113,10 +113,12 @@ import {
type DaemonSocketPathLease,
defaultDaemonSocketDir,
defaultDaemonSocketPath,
+ endDaemonSocketAfterFlush,
getDaemonSocketIdentity,
normalizeSocketPath,
prepareDaemonSocketPath,
restrictDaemonSocketPath,
+ windowsNamedPipeUserScope,
} from "./daemon-socket.js";
import {
acquireDaemonSupervisorOwnership,
@@ -481,20 +483,29 @@ function unrefDelay(ms: number): Promise {
function commitWorkerStartupGate(gate: Writable): Promise {
return new Promise((resolveCommit, rejectCommit) => {
let settled = false;
+ let onError: (error: Error) => void;
+ let onClose: () => void;
const finish = (error?: Error | null) => {
if (settled) {
return;
}
settled = true;
+ gate.off("error", onError);
+ gate.off("close", onClose);
if (error) {
rejectCommit(error);
} else {
resolveCommit();
}
};
- const onError = (error: Error) => finish(error);
- gate.on("error", onError);
- gate.once("close", () => gate.off("error", onError));
+ onError = (error: Error) => finish(error);
+ onClose = () => finish(new Error("Daemon session worker startup gate closed before commit"));
+ gate.once("error", onError);
+ gate.once("close", onClose);
+ if (gate.destroyed || gate.closed || !gate.writable) {
+ onClose();
+ return;
+ }
gate.end(DAEMON_WORKER_STARTUP_GATE_COMMIT, (error?: Error | null) => finish(error));
});
}
@@ -636,7 +647,7 @@ export function idleEvictionSweepIntervalMs(idleEvictionMinutes: IdleEvictionMin
function workerSocketPath(supervisorSocketPath: string, workerId: string): string {
const key = descriptorKey(supervisorSocketPath);
if (process.platform === "win32") {
- return `\\\\.\\pipe\\prime-agent-worker-${key}-${workerId.slice(0, 12)}`;
+ return `\\\\.\\pipe\\prime-agent-worker-${windowsNamedPipeUserScope()}-${key}-${workerId.slice(0, 12)}`;
}
return join(defaultDaemonSocketDir(), `worker-${key}-${workerId.slice(0, 12)}.sock`);
}
@@ -2881,7 +2892,10 @@ export class DaemonSupervisor {
})
: () => {};
child.once("close", detachWorkerStderr);
- const childClosed = new Promise((resolveClose) => child.once("close", () => resolveClose()));
+ const childClosed =
+ child.exitCode !== null || child.signalCode !== null
+ ? Promise.resolve()
+ : new Promise((resolveClose) => child.once("close", () => resolveClose()));
let spawnFailure: Error | undefined;
const spawnSettled = new Promise((resolveSpawn) => {
child.once("spawn", () => resolveSpawn());
@@ -2889,6 +2903,9 @@ export class DaemonSupervisor {
spawnFailure = error instanceof Error ? error : new Error(String(error));
resolveSpawn();
});
+ // Bun can emit "spawn" while a test or instrumentation wrapper is still
+ // returning the ChildProcess. A pid already proves successful admission.
+ if (child.pid) resolveSpawn();
});
child.on("error", (error) => {
this.log(
@@ -2983,7 +3000,12 @@ export class DaemonSupervisor {
try {
try {
- await commitWorkerStartupGate(startupGate);
+ await Promise.race([
+ commitWorkerStartupGate(startupGate),
+ childClosed.then(() => {
+ throw new Error("Daemon session worker exited before startup gate commit");
+ }),
+ ]);
} catch (error) {
startupGate.destroy();
await childClosed;
@@ -6534,7 +6556,7 @@ export class DaemonSupervisor {
await this.catalog.stop();
for (const client of this.clients) {
client.detachInput();
- client.socket.end();
+ endDaemonSocketAfterFlush(client.socket);
}
await new Promise((resolveClose) => this.server?.close(() => resolveClose()) ?? resolveClose());
await this.runCleanupStep("daemon socket", () => this.cleanupSocket());
diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts
index 53cc14670c..9c81ae25e8 100644
--- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts
+++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts
@@ -3348,11 +3348,11 @@ export class InteractiveMode {
}
private clearFeatureHintPresentation(): void {
- if (this.featureHintTimer) {
+ if (this.featureHintTimer !== undefined) {
clearTimeout(this.featureHintTimer);
this.featureHintTimer = undefined;
}
- if (this.featureHintAnimationTimer) {
+ if (this.featureHintAnimationTimer !== undefined) {
clearInterval(this.featureHintAnimationTimer);
this.featureHintAnimationTimer = undefined;
}
diff --git a/packages/coding-agent/src/modes/interactive/theme/theme.ts b/packages/coding-agent/src/modes/interactive/theme/theme.ts
index dd4a48f7f7..40424a81aa 100644
--- a/packages/coding-agent/src/modes/interactive/theme/theme.ts
+++ b/packages/coding-agent/src/modes/interactive/theme/theme.ts
@@ -820,7 +820,7 @@ function getDefaultTheme(): string {
// Global Theme Instance
// ============================================================================
-// Use globalThis to share theme across module loaders (tsx + jiti in dev mode)
+// Use globalThis to share theme across module loaders (direct Bun source execution + jiti in dev mode)
const THEME_KEY = Symbol.for("@earendil-works/pi-coding-agent:theme");
// Export theme as a getter that reads from globalThis
@@ -1026,6 +1026,10 @@ function startThemeWatcher(): void {
) ?? undefined;
}
+export function getActiveThemeWatcher(): fs.FSWatcher | undefined {
+ return themeWatcher;
+}
+
export function stopThemeWatcher(): void {
if (themeReloadTimer) {
clearTimeout(themeReloadTimer);
diff --git a/packages/coding-agent/src/utils/child-process.ts b/packages/coding-agent/src/utils/child-process.ts
index 3f587327f8..cd4cfbec39 100644
--- a/packages/coding-agent/src/utils/child-process.ts
+++ b/packages/coding-agent/src/utils/child-process.ts
@@ -1,4 +1,4 @@
-import { type ChildProcess, execFileSync } from "node:child_process";
+import { type ChildProcess, execFileSync, spawn } from "node:child_process";
import { readFileSync } from "node:fs";
import { constants } from "node:os";
import { basename } from "node:path";
@@ -52,6 +52,29 @@ export function isProcessAlive(pid: number): boolean {
}
export function signalProcessGroupOrProcess(pid: number, signal: NodeJS.Signals): void {
+ if (process.platform === "win32") {
+ const fallbackSignal = signal === "SIGKILL" ? "SIGTERM" : signal;
+ const fallback = () => {
+ try {
+ process.kill(pid, fallbackSignal);
+ } catch {
+ // Process may already be dead.
+ }
+ };
+ try {
+ const args = ["/PID", String(pid), "/T"];
+ if (signal === "SIGKILL") args.push("/F");
+ const taskkill = spawn("taskkill", args, { stdio: "ignore", windowsHide: true });
+ taskkill.once("error", fallback);
+ taskkill.once("exit", (code) => {
+ if (code !== 0 && isProcessAlive(pid)) fallback();
+ });
+ taskkill.unref();
+ } catch {
+ fallback();
+ }
+ return;
+ }
try {
process.kill(-pid, signal);
return;
diff --git a/packages/coding-agent/src/utils/photon.ts b/packages/coding-agent/src/utils/photon.ts
index 6c320705eb..ed6d3e24c2 100644
--- a/packages/coding-agent/src/utils/photon.ts
+++ b/packages/coding-agent/src/utils/photon.ts
@@ -2,7 +2,7 @@
* Photon image processing wrapper.
*
* This module provides a unified interface to @silvia-odwyer/photon-node that works in:
- * 1. Node.js (development, npm run build)
+ * 1. Bun (development and build)
* 2. Bun compiled binaries (standalone distribution)
*
* The challenge: photon-node's CJS entry uses fs.readFileSync(__dirname + '/photon_rs_bg.wasm')
diff --git a/packages/coding-agent/src/utils/shell.ts b/packages/coding-agent/src/utils/shell.ts
index 3a85440011..88ff899c47 100644
--- a/packages/coding-agent/src/utils/shell.ts
+++ b/packages/coding-agent/src/utils/shell.ts
@@ -1,8 +1,9 @@
import { existsSync } from "node:fs";
import { delimiter } from "node:path";
-import { spawn, spawnSync } from "child_process";
+import { spawnSync } from "child_process";
import { getBinDir } from "../config.js";
import { recordOrphanProcessState } from "../core/orphan-process-journal.js";
+import { signalProcessGroupOrProcess } from "./child-process.js";
export interface ShellConfig {
shell: string;
@@ -216,27 +217,5 @@ export function killTrackedDetachedChildren(): void {
* Kill a process and all its children (cross-platform)
*/
export function killProcessTree(pid: number): void {
- if (process.platform === "win32") {
- // Use taskkill on Windows to kill process tree
- try {
- spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
- stdio: "ignore",
- detached: true,
- });
- } catch {
- // Ignore errors if taskkill fails
- }
- } else {
- // Use SIGKILL on Unix/Linux/Mac
- try {
- process.kill(-pid, "SIGKILL");
- } catch {
- // Fallback to killing just the child if process group kill fails
- try {
- process.kill(pid, "SIGKILL");
- } catch {
- // Process already dead
- }
- }
- }
+ signalProcessGroupOrProcess(pid, "SIGKILL");
}
diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-agent/src/utils/tools-manager.ts
index c3da7045ec..414c5cb50c 100644
--- a/packages/coding-agent/src/utils/tools-manager.ts
+++ b/packages/coding-agent/src/utils/tools-manager.ts
@@ -8,7 +8,6 @@ import { Readable } from "stream";
import { pipeline } from "stream/promises";
import { APP_NAME, getBinDir } from "../config.js";
-const TOOLS_DIR = getBinDir();
const NETWORK_TIMEOUT_MS = 10_000;
const DOWNLOAD_TIMEOUT_MS = 120_000;
const COMMAND_TIMEOUT_MS = 5_000;
@@ -100,7 +99,7 @@ const TOOLS: Record = {
// Check that a command both launches and reports a successful version.
function commandWorks(cmd: string): boolean {
try {
- const result = spawnSync(cmd, ["--version"], { stdio: "pipe", timeout: COMMAND_TIMEOUT_MS });
+ const result = spawnSync(cmd, ["--version"], { env: process.env, stdio: "pipe", timeout: COMMAND_TIMEOUT_MS });
return !result.error && result.status === 0;
} catch {
return false;
@@ -113,7 +112,7 @@ export function getToolPath(tool: ManagedTool): string | null {
if (!config) return null;
// Check our tools directory first
- const localPath = join(TOOLS_DIR, config.binaryName + (platform() === "win32" ? ".exe" : ""));
+ const localPath = join(getBinDir(), config.binaryName + (platform() === "win32" ? ".exe" : ""));
if (existsSync(localPath) && commandWorks(localPath)) {
return localPath;
}
@@ -190,6 +189,7 @@ class UnsupportedToolPlatformError extends Error {}
async function downloadTool(tool: ManagedTool): Promise {
const config = TOOLS[tool];
if (!config) throw new Error(`Unknown tool: ${tool}`);
+ const toolsDir = getBinDir();
const plat = platform();
const architecture = arch();
@@ -204,12 +204,12 @@ async function downloadTool(tool: ManagedTool): Promise {
if (!assetName) throw new UnsupportedToolPlatformError(`Unsupported platform: ${plat}/${architecture}`);
// Create tools directory
- mkdirSync(TOOLS_DIR, { recursive: true });
+ mkdirSync(toolsDir, { recursive: true });
const downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;
- const archivePath = join(TOOLS_DIR, assetName);
+ const archivePath = join(toolsDir, assetName);
const binaryExt = plat === "win32" ? ".exe" : "";
- const binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);
+ const binaryPath = join(toolsDir, config.binaryName + binaryExt);
// Download
await downloadFile(downloadUrl, archivePath);
@@ -217,7 +217,7 @@ async function downloadTool(tool: ManagedTool): Promise {
// Extract into a unique temp directory. fd and rg downloads can run concurrently
// during startup, so sharing a fixed directory causes races.
const extractDir = join(
- TOOLS_DIR,
+ toolsDir,
`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
);
mkdirSync(extractDir, { recursive: true });
diff --git a/packages/coding-agent/test/acp-cold-cli.test.ts b/packages/coding-agent/test/acp-cold-cli.test.ts
index 38a3760e1a..d20c9c57b7 100644
--- a/packages/coding-agent/test/acp-cold-cli.test.ts
+++ b/packages/coding-agent/test/acp-cold-cli.test.ts
@@ -5,6 +5,8 @@ import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { ENV_AGENT_DIR } from "../src/config.js";
+import { signalProcessGroupOrProcess } from "../src/utils/child-process.js";
+import { isTestTagEnabled } from "./test-tags.js";
/**
* Cold real-CLI ACP coverage.
@@ -17,7 +19,6 @@ import { ENV_AGENT_DIR } from "../src/config.js";
*/
const cliPath = resolve(__dirname, "../src/cli.ts");
-const tsxPath = resolve(__dirname, "../../../node_modules/tsx/dist/cli.mjs");
const tempDirs: string[] = [];
const servers: Server[] = [];
@@ -74,7 +75,6 @@ async function driveAcpTurn(baseUrl: string): Promise {
const child = spawn(
process.execPath,
[
- tsxPath,
cliPath,
"--mode",
"acp",
@@ -93,7 +93,6 @@ async function driveAcpTurn(baseUrl: string): Promise {
...process.env,
[ENV_AGENT_DIR]: agentDir,
HOME: agentDir,
- TSX_TSCONFIG_PATH: resolve(__dirname, "../../../tsconfig.json"),
},
stdio: ["pipe", "pipe", "pipe"],
},
@@ -173,23 +172,24 @@ async function driveAcpTurn(baseUrl: string): Promise {
exited.then(() => true),
new Promise((resolveTimeout) => setTimeout(() => resolveTimeout(false), 10_000)),
]);
- if (!exitedInTime) {
- child.kill("SIGTERM");
+ if (!exitedInTime && child.pid !== undefined) {
+ signalProcessGroupOrProcess(child.pid, "SIGTERM");
const stoppedInTime = await Promise.race([
exited.then(() => true),
new Promise((resolveTimeout) => setTimeout(() => resolveTimeout(false), 5_000)),
]);
- if (!stoppedInTime) child.kill("SIGKILL");
+ if (!stoppedInTime) {
+ signalProcessGroupOrProcess(child.pid, "SIGKILL");
+ await Promise.race([exited, new Promise((resolveTimeout) => setTimeout(resolveTimeout, 5_000))]);
+ }
}
}
return { responses, updates };
}
describe("ACP mode over a cold real CLI process", () => {
- it("reports a provider failure instead of a silent end_turn", {
- tags: ["kernel-heavy"],
- timeout: 180_000,
- }, async () => {
+ const kernelHeavyIt = isTestTagEnabled("kernel-heavy") ? it : it.skip;
+ kernelHeavyIt("reports a provider failure instead of a silent end_turn", { timeout: 180_000 }, async () => {
const baseUrl = await startRejectingProvider();
const { responses, updates } = await driveAcpTurn(baseUrl);
diff --git a/packages/coding-agent/test/acp-kernel-features.test.ts b/packages/coding-agent/test/acp-kernel-features.test.ts
index e333d382e6..78303ff199 100644
--- a/packages/coding-agent/test/acp-kernel-features.test.ts
+++ b/packages/coding-agent/test/acp-kernel-features.test.ts
@@ -9,6 +9,7 @@ import { IpythonKernelProvisioner } from "../src/core/tools/ipython.js";
import { acpUpdatesForSessionEvent } from "../src/modes/acp/acp-events.js";
import { PRIME_AGENT_META_NAMESPACE } from "../src/modes/acp/acp-meta.js";
import type { AgentConnectionSessionEvent } from "../src/modes/agent-connection/types.js";
+import { isTestTagEnabled } from "./test-tags.js";
/**
* Real-kernel verification for ACP mode.
@@ -43,6 +44,7 @@ function toolEndEvent(toolCallId: string, output: string, isError = false): Agen
}
describe("ACP mode over a real Python kernel", () => {
+ const kernelHeavyIt = isTestTagEnabled("kernel-heavy") ? it : it.skip;
let tempDir: string;
let provisioner: IpythonKernelProvisioner | undefined;
@@ -57,43 +59,44 @@ describe("ACP mode over a real Python kernel", () => {
rmSync(tempDir, { recursive: true, force: true });
});
- it("keeps Python state across cells and represents each cell as an ACP execute call", {
- tags: ["kernel-heavy"],
- timeout: 180_000,
- }, async () => {
- // Every provisioner in this file requests the same skill set: the kernel venv
- // is shared, and a skill-less kernel here can leave a later skill-dependent
- // test with an unsynced venv when files run concurrently.
- provisioner = new IpythonKernelProvisioner(tempDir, { pythonSkills: [AGENT_MESSAGE_SKILL] });
- const manager: KernelClient = await provisioner.ensure();
+ kernelHeavyIt(
+ "keeps Python state across cells and represents each cell as an ACP execute call",
+ { timeout: 180_000 },
+ async () => {
+ // Every provisioner in this file requests the same skill set: the kernel venv
+ // is shared, and a skill-less kernel here can leave a later skill-dependent
+ // test with an unsynced venv when files run concurrently.
+ provisioner = new IpythonKernelProvisioner(tempDir, { pythonSkills: [AGENT_MESSAGE_SKILL] });
+ const manager: KernelClient = await provisioner.ensure();
- const first = await manager.execute("acp_state = 41\nprint('set')");
- expect(first.status).toBe("ok");
- // Persistence is the whole point of the kernel: a second cell must see it.
- const second = await manager.execute("print(acp_state + 1)");
- expect(second.status).toBe("ok");
- expect(second.stdout.trim()).toBe("42");
+ const first = await manager.execute("acp_state = 41\nprint('set')");
+ expect(first.status).toBe("ok");
+ // Persistence is the whole point of the kernel: a second cell must see it.
+ const second = await manager.execute("print(acp_state + 1)");
+ expect(second.status).toBe("ok");
+ expect(second.stdout.trim()).toBe("42");
- const updates = acpUpdatesForSessionEvent(toolEndEvent("cell-2", second.stdout));
- expect(updates[0]).toMatchObject({
- sessionUpdate: "tool_call_update",
- toolCallId: "cell-2",
- status: "completed",
- });
- expect(JSON.stringify(updates[0]?.content)).toContain("42");
- });
+ const updates = acpUpdatesForSessionEvent(toolEndEvent("cell-2", second.stdout));
+ expect(updates[0]).toMatchObject({
+ sessionUpdate: "tool_call_update",
+ toolCallId: "cell-2",
+ status: "completed",
+ });
+ expect(JSON.stringify(updates[0]?.content)).toContain("42");
+ },
+ );
- it("runs continual-harness CRUD in the kernel and can represent the result over ACP", {
- tags: ["kernel-heavy"],
- timeout: 180_000,
- }, async () => {
- provisioner = new IpythonKernelProvisioner(tempDir, {
- pythonSkills: [AGENT_MESSAGE_SKILL],
- env: { RLM_GLOBAL_HARNESS_STATE_DIR: join(tempDir, "harness") },
- });
- const manager = await provisioner.ensure();
+ kernelHeavyIt(
+ "runs continual-harness CRUD in the kernel and can represent the result over ACP",
+ { timeout: 180_000 },
+ async () => {
+ provisioner = new IpythonKernelProvisioner(tempDir, {
+ pythonSkills: [AGENT_MESSAGE_SKILL],
+ env: { RLM_GLOBAL_HARNESS_STATE_DIR: join(tempDir, "harness") },
+ });
+ const manager = await provisioner.ensure();
- const allKinds = await manager.execute(`
+ const allKinds = await manager.execute(`
import json
mem = rlm.harness.create_memory(title="m", content="memory content", global_=True)
note = rlm.harness.create_prompt_note(title="n", content="prompt note content", global_=True)
@@ -110,15 +113,15 @@ print(json.dumps({
"skill_ref": skill.reference.get("import"),
}, sort_keys=True))
`);
- expect(allKinds.status, why(allKinds)).toBe("ok");
- // Every editable harness kind, not just memory: skills additionally require
- // a python reference and an argument contract.
- expect(JSON.parse(allKinds.stdout.trim())).toMatchObject({
- kinds: ["memory", "prompt", "skill", "subagent"],
- skill_ref: "pkg.mod",
- });
+ expect(allKinds.status, why(allKinds)).toBe("ok");
+ // Every editable harness kind, not just memory: skills additionally require
+ // a python reference and an argument contract.
+ expect(JSON.parse(allKinds.stdout.trim())).toMatchObject({
+ kinds: ["memory", "prompt", "skill", "subagent"],
+ skill_ref: "pkg.mod",
+ });
- const result = await manager.execute(`
+ const result = await manager.execute(`
import json
entry = rlm.harness.create_memory(
title="ACP verification memory",
@@ -137,61 +140,62 @@ print(json.dumps({
"after": after.title if after else None,
}, sort_keys=True))
`);
- expect(result.status, why(result)).toBe("ok");
- const payload = JSON.parse(result.stdout.trim());
- expect(payload.found).toBe("ACP verification memory");
- expect(payload.listed).toContain(payload.created);
- expect(payload.deleted).toBe(true);
- expect(payload.after).toBeNull();
+ expect(result.status, why(result)).toBe("ok");
+ const payload = JSON.parse(result.stdout.trim());
+ expect(payload.found).toBe("ACP verification memory");
+ expect(payload.listed).toContain(payload.created);
+ expect(payload.deleted).toBe(true);
+ expect(payload.after).toBeNull();
- // A refinement outcome for that CRUD is expressible as namespaced metadata.
- const refined = acpUpdatesForSessionEvent({
- type: "refine_complete",
- result: {
- summary: "persisted ACP verification memory",
- appliedEdits: [{ applied: true, action: "create", kind: "memory", id: payload.created }],
- },
- } as AgentConnectionSessionEvent);
- expect(refined[0]?._meta).toMatchObject({
- [PRIME_AGENT_META_NAMESPACE]: { refinement: { status: "complete" } },
- });
- });
+ // A refinement outcome for that CRUD is expressible as namespaced metadata.
+ const refined = acpUpdatesForSessionEvent({
+ type: "refine_complete",
+ result: {
+ summary: "persisted ACP verification memory",
+ appliedEdits: [{ applied: true, action: "create", kind: "memory", id: payload.created }],
+ },
+ } as AgentConnectionSessionEvent);
+ expect(refined[0]?._meta).toMatchObject({
+ [PRIME_AGENT_META_NAMESPACE]: { refinement: { status: "complete" } },
+ });
+ },
+ );
- it("exposes rlm depth and subagent APIs to the kernel behind the ACP front end", {
- tags: ["kernel-heavy"],
- timeout: 180_000,
- }, async () => {
- provisioner = new IpythonKernelProvisioner(tempDir, {
- pythonSkills: [AGENT_MESSAGE_SKILL],
- env: { RLM_DEPTH: "0", RLM_MAX_DEPTH: "1" },
- hostHandlers: {
- "rlm.list_subagents": async () => ({
- subagents: [
- {
- rlm_child_id: "child-1",
- active_session_id: "active-1",
+ kernelHeavyIt(
+ "exposes rlm depth and subagent APIs to the kernel behind the ACP front end",
+ { timeout: 180_000 },
+ async () => {
+ provisioner = new IpythonKernelProvisioner(tempDir, {
+ pythonSkills: [AGENT_MESSAGE_SKILL],
+ env: { RLM_DEPTH: "0", RLM_MAX_DEPTH: "1" },
+ hostHandlers: {
+ "rlm.list_subagents": async () => ({
+ subagents: [
+ {
+ rlm_child_id: "child-1",
+ active_session_id: "active-1",
+ session_id: "session-1",
+ session_name: "reviewer",
+ session_dir: tempDir,
+ status: "completed",
+ },
+ ],
+ }),
+ "rlm.delete_subagent": async (payload) => ({
+ subagent: {
+ rlm_child_id: String(payload.target),
+ active_session_id: null,
session_id: "session-1",
session_name: "reviewer",
session_dir: tempDir,
status: "completed",
},
- ],
- }),
- "rlm.delete_subagent": async (payload) => ({
- subagent: {
- rlm_child_id: String(payload.target),
- active_session_id: null,
- session_id: "session-1",
- session_name: "reviewer",
- session_dir: tempDir,
- status: "completed",
- },
- }),
- },
- });
- const manager = await provisioner.ensure();
+ }),
+ },
+ });
+ const manager = await provisioner.ensure();
- const result = await manager.execute(`
+ const result = await manager.execute(`
import json, os
children = await rlm.list_subagents()
removed = await rlm.delete_subagent(children[0])
@@ -202,50 +206,53 @@ print(json.dumps({
"removed": removed.session_name,
}, sort_keys=True))
`);
- expect(result.status, why(result)).toBe("ok");
- const payload = JSON.parse(result.stdout.trim());
- expect(payload.names).toEqual(["reviewer"]);
- expect(payload.removed).toBe("reviewer");
- expect(payload.depth).toBe("0");
- expect(payload.max_depth).toBe("1");
- });
+ expect(result.status, why(result)).toBe("ok");
+ const payload = JSON.parse(result.stdout.trim());
+ expect(payload.names).toEqual(["reviewer"]);
+ expect(payload.removed).toBe("reviewer");
+ expect(payload.depth).toBe("0");
+ expect(payload.max_depth).toBe("1");
+ },
+ );
- it("sends an agent-to-agent message from the kernel and surfaces it over ACP", {
- tags: ["kernel-heavy"],
- timeout: 180_000,
- }, async () => {
- provisioner = new IpythonKernelProvisioner(tempDir, {
- pythonSkills: [AGENT_MESSAGE_SKILL],
- hostHandlers: {
- // The family roster: parent, siblings, and children of this agent.
- "agent_message.list_agents": async () => ({
- current: { name: "root", id: "session-alpha", depth: 0 },
- entries: [{ relationship: "child", name: "reviewer", id: "session-beta", depth: 1, status: "idle" }],
- }),
- "agent_message.send": async (payload) => ({
- id: "agentmsg-acp",
- source: "agent_message",
- target: { activeSessionId: "beta", sessionId: "session-beta", sessionName: "reviewer" },
- message: payload.message,
- deliveryStatus: "queued",
- queuedAt: "2026-08-04T00:00:00.000Z",
- deliveryMode: payload.mode ?? "auto",
- }),
- },
- });
- const manager = await provisioner.ensure();
+ kernelHeavyIt(
+ "sends an agent-to-agent message from the kernel and surfaces it over ACP",
+ { timeout: 180_000 },
+ async () => {
+ provisioner = new IpythonKernelProvisioner(tempDir, {
+ pythonSkills: [AGENT_MESSAGE_SKILL],
+ hostHandlers: {
+ // The family roster: parent, siblings, and children of this agent.
+ "agent_message.list_agents": async () => ({
+ current: { name: "root", id: "session-alpha", depth: 0 },
+ entries: [{ relationship: "child", name: "reviewer", id: "session-beta", depth: 1, status: "idle" }],
+ }),
+ "agent_message.send": async (payload) => ({
+ id: "agentmsg-acp",
+ source: "agent_message",
+ target: { activeSessionId: "beta", sessionId: "session-beta", sessionName: "reviewer" },
+ message: payload.message,
+ deliveryStatus: "queued",
+ queuedAt: "2026-08-04T00:00:00.000Z",
+ deliveryMode: payload.mode ?? "auto",
+ }),
+ },
+ });
+ const manager = await provisioner.ensure();
- // The kernel venv is shared across test files. If a concurrently running
- // file rebuilt it without this skill, say so plainly rather than failing
- // later with an opaque AttributeError on the stub object.
- const available = await manager.execute("import json; print(json.dumps({'kind': type(agent_message).__name__}))");
- expect(available.status, why(available)).toBe("ok");
- expect(
- JSON.parse(available.stdout.trim()).kind,
- "agent_message skill was not installed into the shared kernel venv",
- ).not.toBe("_PrimeAgentUnavailableSkill");
+ // The kernel venv is shared across test files. If a concurrently running
+ // file rebuilt it without this skill, say so plainly rather than failing
+ // later with an opaque AttributeError on the stub object.
+ const available = await manager.execute(
+ "import json; print(json.dumps({'kind': type(agent_message).__name__}))",
+ );
+ expect(available.status, why(available)).toBe("ok");
+ expect(
+ JSON.parse(available.stdout.trim()).kind,
+ "agent_message skill was not installed into the shared kernel venv",
+ ).not.toBe("_PrimeAgentUnavailableSkill");
- const result = await manager.execute(`
+ const result = await manager.execute(`
import json
roster = await agent_message.list_agents()
receipt = await agent_message.send("status update", receiver_role="child", receiver_name="reviewer")
@@ -254,22 +261,23 @@ print(json.dumps({
"status": receipt["deliveryStatus"],
}))
`);
- expect(result.status, why(result)).toBe("ok");
- const payload = JSON.parse(result.stdout.trim());
- expect(payload.roster).toEqual(["reviewer"]);
- expect(payload.status).toBe("queued");
+ expect(result.status, why(result)).toBe("ok");
+ const payload = JSON.parse(result.stdout.trim());
+ expect(payload.roster).toEqual(["reviewer"]);
+ expect(payload.status).toBe("queued");
- // The kernel reports the send; ACP carries it as namespaced metadata.
- const sentMessages = result.sentAgentMessages ?? [];
- expect(sentMessages.length).toBeGreaterThan(0);
- const sent = sentMessages[0];
- const updates = acpUpdatesForSessionEvent({
- type: "ipython_sent_agent_message",
- toolCallId: "cell-msg",
- message: sent,
- } as AgentConnectionSessionEvent);
- expect(updates[0]?._meta).toMatchObject({
- [PRIME_AGENT_META_NAMESPACE]: { agentMessage: { toolCallId: "cell-msg", deliveryStatus: "queued" } },
- });
- });
+ // The kernel reports the send; ACP carries it as namespaced metadata.
+ const sentMessages = result.sentAgentMessages ?? [];
+ expect(sentMessages.length).toBeGreaterThan(0);
+ const sent = sentMessages[0];
+ const updates = acpUpdatesForSessionEvent({
+ type: "ipython_sent_agent_message",
+ toolCallId: "cell-msg",
+ message: sent,
+ } as AgentConnectionSessionEvent);
+ expect(updates[0]?._meta).toMatchObject({
+ [PRIME_AGENT_META_NAMESPACE]: { agentMessage: { toolCallId: "cell-msg", deliveryStatus: "queued" } },
+ });
+ },
+ );
});
diff --git a/packages/coding-agent/test/agent-connection-daemon.test.ts b/packages/coding-agent/test/agent-connection-daemon.test.ts
index a0ebd66557..5c2ec6e500 100644
--- a/packages/coding-agent/test/agent-connection-daemon.test.ts
+++ b/packages/coding-agent/test/agent-connection-daemon.test.ts
@@ -1958,11 +1958,9 @@ describe("DaemonAgentConnection", () => {
expect(fakeClient.reconnectCount).toBe(0);
expect(closedEvents).toHaveLength(1);
- expect(closedEvents[0]).toMatchObject({
- type: "closed",
- error: expect.stringContaining("The Prime Agent daemon shut down while this window was attached."),
- });
+ expect(closedEvents[0]?.type).toBe("closed");
const closedError = closedEvents[0]?.type === "closed" ? closedEvents[0].error : undefined;
+ expect(closedError).toContain("The Prime Agent daemon shut down while this window was attached.");
expect(closedError).toContain("Session ID: session-current.");
expect(closedError).toContain("Session file: /tmp/session-current.jsonl.");
expect(closedError).toContain("Diagnostic log:");
diff --git a/packages/coding-agent/test/agent-session-concurrent.test.ts b/packages/coding-agent/test/agent-session-concurrent.test.ts
index 1c8a6f950f..2e8db829a6 100644
--- a/packages/coding-agent/test/agent-session-concurrent.test.ts
+++ b/packages/coding-agent/test/agent-session-concurrent.test.ts
@@ -817,7 +817,7 @@ describe("AgentSession concurrent prompt guard", () => {
await session.prompt("First message");
expect(session.isStreaming).toBe(false);
- await expect(session.prompt("Second message")).resolves.not.toThrow();
+ await expect(session.prompt("Second message")).resolves.toBeUndefined();
});
it("should wait for queued agent events before emitting tool_call", async () => {
diff --git a/packages/coding-agent/test/agent-session-services.test.ts b/packages/coding-agent/test/agent-session-services.test.ts
index 7c1eec6c7c..244b4866c4 100644
--- a/packages/coding-agent/test/agent-session-services.test.ts
+++ b/packages/coding-agent/test/agent-session-services.test.ts
@@ -298,7 +298,7 @@ describe("createAgentSessionFromServices", () => {
_createKernelHostHandlers(): Record;
}
)._createKernelHostHandlers(),
- ).not.toHaveProperty("agent_message.send");
+ ).not.toHaveProperty(["agent_message.send"]);
} finally {
session.dispose();
}
@@ -403,7 +403,7 @@ describe("createAgentSessionFromServices", () => {
});
try {
expect(visibleSkillNames(withMessageController)).toContain(AGENT_MESSAGE_SKILL_NAME);
- expect(kernelHostHandlers(withMessageController)).toHaveProperty("agent_message.send");
+ expect(kernelHostHandlers(withMessageController)).toHaveProperty(["agent_message.send"]);
} finally {
withMessageController.dispose();
}
diff --git a/packages/coding-agent/test/agents-view-mode.test.ts b/packages/coding-agent/test/agents-view-mode.test.ts
index d471269723..1e5ef8a03e 100644
--- a/packages/coding-agent/test/agents-view-mode.test.ts
+++ b/packages/coding-agent/test/agents-view-mode.test.ts
@@ -1,3 +1,4 @@
+import { createRequire } from "node:module";
import { setKeybindings } from "@earendil-works/pi-tui";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { KeybindingsManager } from "../src/core/keybindings.js";
@@ -29,10 +30,8 @@ const modeMocks = vi.hoisted(() => ({
clientRequest: vi.fn<() => Promise>(),
}));
-vi.mock("../src/config.js", async (importOriginal) => {
- const actual = await importOriginal();
- return { ...actual, appendRotatingLog: vi.fn() };
-});
+const __configJs = createRequire(import.meta.url)("../src/config.js");
+vi.mock("../src/config.js", () => ({ ...__configJs, appendRotatingLog: vi.fn() }));
vi.mock("../src/modes/daemon/daemon-client.js", () => ({
DaemonClient: class {
@@ -49,16 +48,14 @@ vi.mock("../src/modes/agent-connection/daemon-agent-connection.js", () => ({
}),
}));
-vi.mock("../src/modes/interactive/interactive-mode.js", async (importOriginal) => {
- const actual = await importOriginal();
- return {
- ...actual,
- InteractiveMode: class {
- run = modeMocks.interactiveRun;
- teardownSessionUi = modeMocks.teardownSessionUi;
- },
- };
-});
+const __interactiveMode = createRequire(import.meta.url)("../src/modes/interactive/interactive-mode.js");
+vi.mock("../src/modes/interactive/interactive-mode.js", () => ({
+ ...__interactiveMode,
+ InteractiveMode: class {
+ run = modeMocks.interactiveRun;
+ teardownSessionUi = modeMocks.teardownSessionUi;
+ },
+}));
function summary(overrides: Partial = {}): SessionSummary {
return {
diff --git a/packages/coding-agent/test/assistant-message-streaming-bench.ts b/packages/coding-agent/test/assistant-message-streaming-bench.ts
index 1597ca6dec..b22d70b351 100644
--- a/packages/coding-agent/test/assistant-message-streaming-bench.ts
+++ b/packages/coding-agent/test/assistant-message-streaming-bench.ts
@@ -4,7 +4,7 @@
* Mimics the interactive-mode `message_update` handler: for each token chunk,
* call updateContent() with the grown message and render(). Run with:
*
- * npx tsx test/assistant-message-streaming-bench.ts
+ * bun test/assistant-message-streaming-bench.ts
*/
import { performance } from "node:perf_hooks";
import type { AssistantMessage } from "@earendil-works/pi-ai";
diff --git a/packages/coding-agent/test/builtin-skills.test.ts b/packages/coding-agent/test/builtin-skills.test.ts
index 8272ff0ca1..a94f81b146 100644
--- a/packages/coding-agent/test/builtin-skills.test.ts
+++ b/packages/coding-agent/test/builtin-skills.test.ts
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { REQUIRED_BINARY_SIDECARS } from "../../../scripts/compiled-artifact-smoke.js";
import { getBundledSkillsDir } from "../src/config.js";
import { DefaultPackageManager } from "../src/core/package-manager.js";
import { DefaultResourceLoader } from "../src/core/resource-loader.js";
@@ -297,26 +298,16 @@ describe("builtin skills", () => {
// Verify every shipping path includes bundled skills; source-only success would hide a release packaging regression.
describe("packaging ships bundled skills", () => {
const packageRoot = join(__dirname, "..");
- const repoRoot = join(packageRoot, "..", "..");
- it("npm build (copy-assets) copies skills into dist", () => {
+ it("package and binary builds require bundled skills", () => {
const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf-8")) as {
files?: string[];
scripts?: Record;
};
- expect(pkg.scripts?.["copy-assets"]).toContain("skills dist/skills");
- // npm publish ships the source skills/ dir via the files allowlist too.
+ expect(pkg.scripts?.["copy-assets"]).toBe("bun scripts/copy-assets.ts package");
+ expect(pkg.scripts?.["copy-binary-assets"]).toBe("bun scripts/copy-assets.ts binary");
expect(pkg.files).toContain("skills");
- });
-
- it("binary release script copies skills next to the executable", () => {
- const script = readFileSync(join(repoRoot, "scripts", "build-binaries.sh"), "utf-8");
- expect(script).toMatch(/cp -r skills binaries\/\$platform\//);
- });
-
- it("release packer includes skills in the packed package", () => {
- const script = readFileSync(join(repoRoot, "scripts", "pack-prime-agent-release.mjs"), "utf-8");
- expect(script).toContain('"skills"');
+ expect(REQUIRED_BINARY_SIDECARS).toContain("skills");
});
});
});
diff --git a/packages/coding-agent/test/bun-bundle.test.ts b/packages/coding-agent/test/bun-bundle.test.ts
new file mode 100644
index 0000000000..24aaa3689b
--- /dev/null
+++ b/packages/coding-agent/test/bun-bundle.test.ts
@@ -0,0 +1,122 @@
+import { execFileSync } from "node:child_process";
+import { existsSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
+import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
+
+const bundleDir = join(process.cwd(), "dist", "bundle");
+const bundleEntry = join(bundleDir, "cli.js");
+const distEntry = join(process.cwd(), "dist", "cli.js");
+const bundleScript = join(process.cwd(), "scripts", "bundle.mjs");
+
+beforeAll(() => {
+ if (!existsSync(distEntry)) {
+ for (const packageDir of ["../tui", "../ai", "../agent", "."]) {
+ execFileSync("bun", ["--bun", "tsgo", "-p", "tsconfig.build.json"], {
+ cwd: join(process.cwd(), packageDir),
+ });
+ }
+ }
+ if (existsSync(bundleDir)) {
+ rmSync(bundleDir, { recursive: true, force: true });
+ }
+ execFileSync("bun", [bundleScript], {
+ cwd: process.cwd(),
+ encoding: "utf8",
+ });
+});
+
+function readEntry(): string {
+ return readFileSync(bundleEntry, "utf8");
+}
+
+function listDir(): string[] {
+ return readdirSync(bundleDir);
+}
+
+describe("bun-bundle build output", () => {
+ it("creates dist/bundle/ directory", () => {
+ expect(existsSync(bundleDir)).toBe(true);
+ });
+
+ it("creates entry point cli.js", () => {
+ expect(existsSync(bundleEntry)).toBe(true);
+ });
+
+ it("sets cli.js executable", () => {
+ const mode = statSync(bundleEntry).mode;
+ expect(mode & 0o100).toBeTruthy();
+ });
+});
+
+describe("bun-bundle entry content", () => {
+ it("preserves shebang", () => {
+ expect(readEntry().startsWith("#!/usr/bin/env node")).toBe(true);
+ });
+
+ it("injects require polyfill banner (createRequire)", () => {
+ const text = readEntry();
+ expect(text).toContain("createRequire as __piBundleCreateRequire");
+ expect(text).toContain("node:module");
+ });
+
+ it("inlines __PI_BUNDLED__ define", () => {
+ expect(readEntry()).not.toContain("__PI_BUNDLED__");
+ });
+
+ it("inlines __PI_BUILD_ID__ define", () => {
+ expect(readEntry()).not.toContain("__PI_BUILD_ID__");
+ });
+});
+
+describe("bun-bundle externals", () => {
+ const packages = ["koffi", "undici", "@silvia-odwyer/photon-node", "@mariozechner/clipboard"];
+ for (const pkg of packages) {
+ it(`keeps ${pkg} external (not inlined)`, () => {
+ expect(readEntry()).not.toContain(pkg);
+ });
+ }
+});
+
+describe("bun-bundle structure", () => {
+ it("produces at least 20 chunk files", () => {
+ const chunks = listDir().filter((f) => f !== "cli.js");
+ expect(chunks.length).toBeGreaterThanOrEqual(20);
+ });
+
+ it("produces named provider chunks", () => {
+ const named = listDir().filter(
+ (f) =>
+ f.startsWith("anthropic-") ||
+ f.startsWith("google-") ||
+ f.startsWith("azure-") ||
+ f.startsWith("code-highlighter-"),
+ );
+ expect(named.length).toBeGreaterThanOrEqual(3);
+ });
+
+ it("entry imports relative chunks", () => {
+ const relativeImports = readEntry().match(/from\s+["']\.\//g) || [];
+ expect(relativeImports.length).toBeGreaterThanOrEqual(1);
+ });
+
+ it("writes linked source maps for the entry and chunks", () => {
+ const files = listDir();
+ const scripts = files.filter((file) => file.endsWith(".js"));
+ const maps = files.filter((file) => file.endsWith(".js.map"));
+ expect(maps.length).toBe(scripts.length);
+ expect(readEntry()).toContain("sourceMappingURL=cli.js.map");
+ });
+});
+
+describe("bun-bundle banner in chunks", () => {
+ it("injects require polyfill into chunk files too", () => {
+ const chunks = listDir()
+ .filter((file) => file.endsWith(".js") && file !== "cli.js")
+ .slice(0, 3);
+ for (const chunk of chunks) {
+ const content = readFileSync(join(bundleDir, chunk), "utf8");
+ expect(content).toContain("createRequire");
+ expect(content).toContain("node:module");
+ }
+ });
+});
diff --git a/packages/coding-agent/test/bun-installer-e2e.test.ts b/packages/coding-agent/test/bun-installer-e2e.test.ts
new file mode 100644
index 0000000000..e70ac65d27
--- /dev/null
+++ b/packages/coding-agent/test/bun-installer-e2e.test.ts
@@ -0,0 +1,437 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { spawnSync } from "node:child_process";
+import { createHash } from "node:crypto";
+import {
+ chmodSync,
+ mkdirSync,
+ mkdtempSync,
+ readdirSync,
+ readFileSync,
+ readlinkSync,
+ realpathSync,
+ rmSync,
+ symlinkSync,
+ writeFileSync,
+} from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const installer = join(dirname(fileURLToPath(import.meta.url)), "../../../install.sh");
+const temporaryRoots: string[] = [];
+
+afterEach(() => {
+ for (const root of temporaryRoots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+function makeRelease(
+ root: string,
+ version: string,
+ executable: string,
+ checksumIsValid = true,
+ omitSidecar?: string,
+): void {
+ const platform = `${process.platform === "darwin" ? "darwin" : "linux"}-${process.arch === "arm64" ? "arm64" : "x64"}`;
+ const releaseDir = join(root, "server", "releases", `v${version}`);
+ const stage = join(root, `stage-${version}`);
+ mkdirSync(releaseDir, { recursive: true });
+ mkdirSync(stage, { recursive: true });
+ writeFileSync(join(stage, "prime-agent"), executable);
+ chmodSync(join(stage, "prime-agent"), 0o755);
+ const requiredFiles = [
+ "package.json",
+ "README.md",
+ "CHANGELOG.md",
+ "install.sh",
+ "photon_rs_bg.wasm",
+ "prime-agent-runtime/pyproject.toml",
+ "theme/prime.json",
+ "theme/dark.json",
+ "theme/light.json",
+ "theme/theme-schema.json",
+ "export-html/template.html",
+ "export-html/template.css",
+ "export-html/template.js",
+ ];
+ for (const relative of requiredFiles) {
+ mkdirSync(dirname(join(stage, relative)), { recursive: true });
+ writeFileSync(
+ join(stage, relative),
+ relative === "package.json" ? JSON.stringify({ name: "prime-agent", version }) : "fixture",
+ );
+ }
+ for (const relative of ["skills", "assets", "docs", "examples", "export-html/vendor"]) {
+ mkdirSync(join(stage, relative), { recursive: true });
+ writeFileSync(join(stage, relative, ".keep"), "fixture");
+ }
+ if (omitSidecar) rmSync(join(stage, omitSidecar), { recursive: true, force: true });
+ chmodSync(join(stage, "install.sh"), 0o755);
+ const archiveName = `prime-agent-${version}-${platform}.tar.gz`;
+ const archive = join(releaseDir, archiveName);
+ const packed = spawnSync("tar", ["-czf", archive, "-C", stage, "."], { encoding: "utf8" });
+ if (packed.status !== 0) throw new Error(packed.stderr || "tar failed");
+ const checksum = checksumIsValid ? createHash("sha256").update(readFileSync(archive)).digest("hex") : "0".repeat(64);
+ writeFileSync(join(releaseDir, "SHA256SUMS"), `${checksum} ${archiveName}\n`);
+}
+
+function installerEnv(root: string, overrides: Record = {}): NodeJS.ProcessEnv {
+ const home = join(root, "home");
+ mkdirSync(join(home, ".prime"), { recursive: true });
+ writeFileSync(join(home, ".prime", "sentinel"), "user data");
+ return {
+ ...process.env,
+ HOME: home,
+ PRIME_AGENT_DOWNLOAD_BASE_URL: `file://${join(root, "server")}`,
+ PRIME_AGENT_VERSIONS_DIR: join(root, "apps", "versions"),
+ PRIME_AGENT_BIN_DIR: join(root, "bin"),
+ TERM: "dumb",
+ ...overrides,
+ };
+}
+
+function runInstaller(
+ root: string,
+ args: string[],
+ env: Record = {},
+): { exitCode: number; stdout: string; stderr: string } {
+ const result = spawnSync("sh", [installer, ...args], {
+ cwd: root,
+ env: installerEnv(root, env),
+ encoding: "utf8",
+ });
+ return {
+ exitCode: result.status ?? 1,
+ stdout: result.stdout ?? "",
+ stderr: result.stderr ?? result.error?.message ?? "",
+ };
+}
+
+function goodExecutable(version: string): string {
+ return `#!/bin/sh\nif [ "${"$"}1" = "--version" ]; then echo "prime-agent ${version}"; exit 0; fi\nexit 0\n`;
+}
+
+describe("compiled binary installer", () => {
+ test("installs a flat archive into a versioned app directory and preserves user data", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+
+ const result = runInstaller(root, ["1.2.3"]);
+ expect(result.exitCode, result.stderr).toBe(0);
+ const target = join(realpathSync(root), "apps", "versions", "v1.2.3", "prime-agent");
+ expect(readlinkSync(join(root, "bin", "prime-agent"))).toBe(target);
+ expect(readFileSync(join(root, "apps", "versions", "v1.2.3", "package.json"), "utf8")).toContain('"1.2.3"');
+ expect(readFileSync(join(root, "home", ".prime", "sentinel"), "utf8")).toBe("user data");
+
+ const secondInstall = runInstaller(root, ["1.2.3"]);
+ expect(secondInstall.exitCode, secondInstall.stderr).toBe(0);
+ expect(readlinkSync(join(root, "bin", "prime-agent"))).toBe(target);
+ });
+
+ test("links a custom command name to the canonical archive executable", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+
+ const result = runInstaller(root, ["1.2.3"], { PRIME_AGENT_CMD: "pa" });
+ expect(result.exitCode, result.stderr).toBe(0);
+ const command = join(root, "bin", "pa");
+ expect(readlinkSync(command)).toContain("v1.2.3/prime-agent");
+ expect(spawnSync(command, ["--version"], { encoding: "utf8" }).status).toBe(0);
+ });
+
+ test("does not trust install metadata from the working directory in a piped install", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ const attackerDir = join(root, "attacker");
+ mkdirSync(attackerDir);
+ writeFileSync(
+ join(attackerDir, ".install-paths"),
+ `${join(root, "attacker-versions")}\n${join(root, "attacker-bin", "prime-agent")}\nprime-agent\n`,
+ );
+ const home = join(root, "piped-home");
+ mkdirSync(home);
+
+ const result = spawnSync("sh", ["-s", "--", "1.2.3"], {
+ cwd: attackerDir,
+ input: readFileSync(installer),
+ env: {
+ ...process.env,
+ HOME: home,
+ PRIME_AGENT_DOWNLOAD_BASE_URL: `file://${join(root, "server")}`,
+ PRIME_AGENT_VERSIONS_DIR: undefined,
+ PRIME_AGENT_BIN_DIR: undefined,
+ XDG_DATA_HOME: undefined,
+ XDG_BIN_HOME: undefined,
+ TERM: "dumb",
+ },
+ encoding: "utf8",
+ });
+ expect(result.status, result.stderr).toBe(0);
+ expect(readlinkSync(join(home, ".local", "bin", "prime-agent"))).toContain("v1.2.3/prime-agent");
+ expect(() => readlinkSync(join(root, "attacker-bin", "prime-agent"))).toThrow();
+ });
+
+ test("self-updates the persisted custom install paths without exported overrides", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ const v1Sidecar = join(root, "apps", "versions", "v1.2.3", "install.sh");
+ writeFileSync(v1Sidecar, readFileSync(installer));
+ chmodSync(v1Sidecar, 0o755);
+ makeRelease(root, "2.0.0", goodExecutable("2.0.0"));
+ const isolatedHome = join(root, "isolated-home");
+ mkdirSync(isolatedHome);
+ const result = spawnSync("sh", [v1Sidecar, "--update", "2.0.0"], {
+ cwd: root,
+ env: {
+ ...process.env,
+ HOME: isolatedHome,
+ PRIME_AGENT_DOWNLOAD_BASE_URL: `file://${join(root, "server")}`,
+ PRIME_AGENT_VERSIONS_DIR: undefined,
+ PRIME_AGENT_BIN_DIR: undefined,
+ XDG_DATA_HOME: undefined,
+ XDG_BIN_HOME: undefined,
+ TERM: "dumb",
+ },
+ encoding: "utf8",
+ });
+ expect(result.status, result.stderr).toBe(0);
+ expect(readlinkSync(join(root, "bin", "prime-agent"))).toContain("v2.0.0/prime-agent");
+ expect(() => readlinkSync(join(isolatedHome, ".local", "bin", "prime-agent"))).toThrow();
+ });
+
+ test("rejects a fresh install that fails through the activated command symlink", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(
+ root,
+ "1.2.3",
+ '#!/bin/sh\ncase "$0" in */bin/prime-agent) exit 1 ;; esac\nif [ "$1" = "--version" ]; then exit 0; fi\nexit 0\n',
+ );
+
+ const result = runInstaller(root, ["1.2.3"]);
+ expect(result.exitCode).not.toBe(0);
+ expect(result.stderr).toContain("installed Prime Agent command did not run correctly");
+ expect(() => readlinkSync(join(root, "bin", "prime-agent"))).toThrow();
+ });
+
+ test("rejects a bad checksum before changing the active version", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"), false);
+
+ const result = runInstaller(root, ["1.2.3"]);
+ expect(result.exitCode).not.toBe(0);
+ expect(() => readlinkSync(join(root, "bin", "prime-agent"))).toThrow();
+ });
+
+ test("rejects a pre-existing directory at the command path", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ const commandPath = join(root, "bin", "prime-agent");
+ mkdirSync(commandPath, { recursive: true });
+
+ const result = runInstaller(root, ["1.2.3"]);
+ expect(result.exitCode).not.toBe(0);
+ expect(result.stderr).toContain("command path is a directory");
+ expect(readFileSync(join(root, "home", ".prime", "sentinel"), "utf8")).toBe("user data");
+ });
+
+ test("repairs a broken active command when updating to the same version", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ const link = join(root, "bin", "prime-agent");
+ const target = readlinkSync(link);
+ writeFileSync(target, "#!/bin/sh\nexit 1\n");
+ chmodSync(target, 0o755);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+
+ const result = runInstaller(root, ["--update", "1.2.3"]);
+ expect(result.exitCode, result.stderr).toBe(0);
+ const smoke = spawnSync(link, ["--version"], { encoding: "utf8" });
+ expect(smoke.status).toBe(0);
+ expect(smoke.stdout).toContain("1.2.3");
+ });
+
+ test("does not use the repaired version as its own rollback target", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ const link = join(root, "bin", "prime-agent");
+ const target = readlinkSync(link);
+ const publicPathFailure =
+ '#!/bin/sh\ncase "$0" in */bin/prime-agent) exit 1 ;; esac\nif [ "$1" = "--version" ]; then exit 0; fi\nexit 0\n';
+ writeFileSync(target, publicPathFailure);
+ chmodSync(target, 0o755);
+ makeRelease(root, "1.2.3", publicPathFailure);
+
+ const result = runInstaller(root, ["--update", "1.2.3"]);
+ expect(result.exitCode).not.toBe(0);
+ expect(result.stderr).toContain("no healthy rollback version was available");
+ expect(() => readlinkSync(link)).toThrow();
+ expect(() => readFileSync(join(root, "apps", "versions", "v1.2.3", "package.json"))).toThrow();
+ });
+
+ test("does not self-rollback through an aliased versions directory", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ const physicalVersions = join(root, "physical-versions");
+ const aliasedVersions = join(root, "aliased-versions");
+ mkdirSync(physicalVersions);
+ symlinkSync(physicalVersions, aliasedVersions, "dir");
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ const pathEnv = { PRIME_AGENT_VERSIONS_DIR: aliasedVersions };
+ expect(runInstaller(root, ["1.2.3"], pathEnv).exitCode).toBe(0);
+ const link = join(root, "bin", "prime-agent");
+ const target = readlinkSync(link);
+ writeFileSync(target, "#!/bin/sh\nexit 1\n");
+ chmodSync(target, 0o755);
+ const publicPathFailure =
+ '#!/bin/sh\ncase "$0" in */bin/prime-agent) exit 1 ;; esac\nif [ "$1" = "--version" ]; then exit 0; fi\nexit 0\n';
+ makeRelease(root, "1.2.3", publicPathFailure);
+
+ const result = runInstaller(root, ["1.2.3"], pathEnv);
+ expect(result.exitCode).not.toBe(0);
+ expect(() => readlinkSync(link)).toThrow();
+ expect(() => readFileSync(join(physicalVersions, "v1.2.3", "package.json"))).toThrow();
+ });
+
+ test("keeps the previous symlink when an update fails its smoke test", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ const link = join(root, "bin", "prime-agent");
+ const oldTarget = readlinkSync(link);
+
+ makeRelease(root, "2.0.0", "#!/bin/sh\nexit 1\n");
+ const result = runInstaller(root, ["--update", "2.0.0"]);
+ expect(result.exitCode).not.toBe(0);
+ expect(readlinkSync(link)).toBe(oldTarget);
+ expect(readFileSync(join(root, "home", ".prime", "sentinel"), "utf8")).toBe("user data");
+ });
+
+ test("serializes concurrent updates without deleting an activated version", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ makeRelease(root, "2.0.0", goodExecutable("2.0.0").replace("then echo", "then sleep 1; echo"));
+ const staleRoot = join(root, "apps", "versions", ".install-locks");
+ mkdirSync(join(staleRoot, "1-99999991"), { recursive: true });
+
+ const result = spawnSync(
+ "sh",
+ [
+ "-c",
+ 'sh "$1" --update 2.0.0 & first=$!; sh "$1" --update 2.0.0 & second=$!; wait "$first"; a=$?; wait "$second"; b=$?; [ "$a" -eq 0 ] && [ "$b" -eq 0 ]',
+ "--",
+ installer,
+ ],
+ { cwd: root, env: installerEnv(root), encoding: "utf8", timeout: 20_000 },
+ );
+ expect(result.status, result.stderr).toBe(0);
+ const link = join(root, "bin", "prime-agent");
+ expect(readlinkSync(link)).toContain("v2.0.0/prime-agent");
+ expect(spawnSync(link, ["--version"], { encoding: "utf8" }).status).toBe(0);
+ expect(readdirSync(staleRoot)).toEqual([]);
+ });
+
+ test("recovers an install lock whose recorded process is gone", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ makeRelease(root, "2.0.0", goodExecutable("2.0.0"));
+ const lockRoot = join(root, "apps", "versions", ".install-locks");
+ const staleContender = join(lockRoot, "1-99999999");
+ mkdirSync(staleContender, { recursive: true });
+
+ const result = runInstaller(root, ["--update", "2.0.0"], {
+ PRIME_AGENT_INSTALL_LOCK_TIMEOUT_SECONDS: "1",
+ });
+ expect(result.exitCode, result.stderr).toBe(0);
+ expect(readlinkSync(join(root, "bin", "prime-agent"))).toContain("v2.0.0/prime-agent");
+ expect(() => readFileSync(join(staleContender, "pid"))).toThrow();
+ });
+
+ test("keeps the previous version when an update is missing a required sidecar", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ const link = join(root, "bin", "prime-agent");
+ const oldTarget = readlinkSync(link);
+
+ makeRelease(root, "2.0.0", goodExecutable("2.0.0"), true, "theme/prime.json");
+ const result = runInstaller(root, ["--update", "2.0.0"]);
+ expect(result.exitCode).not.toBe(0);
+ expect(result.stderr).toContain("missing required sidecar: theme/prime.json");
+ expect(readlinkSync(link)).toBe(oldTarget);
+ });
+
+ test("rolls back when the activated symlink fails its smoke test", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ expect(runInstaller(root, ["1.2.3"]).exitCode).toBe(0);
+ const link = join(root, "bin", "prime-agent");
+ const oldTarget = readlinkSync(link);
+
+ makeRelease(
+ root,
+ "2.0.0",
+ '#!/bin/sh\ncase "$0" in */bin/prime-agent) exit 1 ;; esac\nif [ "$1" = "--version" ]; then echo "prime-agent 2.0.0"; exit 0; fi\nexit 0\n',
+ );
+ const result = runInstaller(root, ["--update", "2.0.0"]);
+ expect(result.exitCode).not.toBe(0);
+ expect(result.stderr).toContain("restored the previous Prime Agent version");
+ expect(readlinkSync(link)).toBe(oldTarget);
+ });
+
+ test("rejects glibc binaries on musl Linux before downloading", () => {
+ if (process.platform !== "linux") return;
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ const tools = join(root, "tools");
+ mkdirSync(tools);
+ writeFileSync(join(tools, "ldd"), '#!/bin/sh\necho "musl libc"\n');
+ chmodSync(join(tools, "ldd"), 0o755);
+ const result = runInstaller(root, ["1.2.3"], { PATH: `${tools}:${process.env.PATH}` });
+ expect(result.exitCode).not.toBe(0);
+ expect(result.stderr).toContain("require glibc Linux");
+ });
+
+ test("warns when an older command shadows the installed binary", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+ const tools = join(root, "tools");
+ mkdirSync(tools);
+ writeFileSync(join(tools, "prime-agent"), '#!/bin/sh\necho "old"\n');
+ chmodSync(join(tools, "prime-agent"), 0o755);
+
+ const result = runInstaller(root, ["1.2.3"], { PATH: `${tools}:${process.env.PATH}` });
+ expect(result.exitCode, result.stderr).toBe(0);
+ expect(result.stderr).toContain("currently shadows the new binary");
+ expect(result.stdout).toContain("export PATH='");
+ });
+ test("canonicalizes relative version directories before linking", () => {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-installer-"));
+ temporaryRoots.push(root);
+ makeRelease(root, "1.2.3", goodExecutable("1.2.3"));
+
+ const result = runInstaller(root, ["1.2.3"], { PRIME_AGENT_VERSIONS_DIR: "relative/versions" });
+ expect(result.exitCode, result.stderr).toBe(0);
+ expect(readlinkSync(join(root, "bin", "prime-agent"))).toBe(
+ join(realpathSync(root), "relative", "versions", "v1.2.3", "prime-agent"),
+ );
+ });
+});
diff --git a/packages/coding-agent/test/bun-installer.test.ts b/packages/coding-agent/test/bun-installer.test.ts
new file mode 100644
index 0000000000..438d17ce93
--- /dev/null
+++ b/packages/coding-agent/test/bun-installer.test.ts
@@ -0,0 +1,226 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { execFileSync, spawnSync } from "node:child_process";
+import { mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
+const installer = join(repoRoot, "install.sh");
+const installerText = readFileSync(installer, "utf-8");
+
+let tempDirs: string[] = [];
+afterEach(() => {
+ for (const d of tempDirs) {
+ try {
+ rmSync(d, { recursive: true, force: true });
+ } catch {}
+ }
+ tempDirs = [];
+});
+function mkTemp(): string {
+ const d = mkdtempSync(join(tmpdir(), "pi-installer-"));
+ tempDirs.push(d);
+ return d;
+}
+
+function sourceVar(v: string): string {
+ // Source installer with main call disabled via sed, capture variable
+ const result = execFileSync(
+ "sh",
+ ["-c", `eval "$(sed 's/^main "$@"/# &/' "$1")" 2>/dev/null; printf '%s' "$${v}"`, "--", installer],
+ { encoding: "utf-8", env: process.env as any },
+ );
+ return result.trim();
+}
+
+// ============================================================================
+describe("install.sh shell syntax", () => {
+ it("passes POSIX shell syntax check", () => {
+ expect(() => execFileSync("sh", ["-n", installer], { stdio: "pipe" })).not.toThrow();
+ });
+
+ it("supports compiled binary installs and updates only", () => {
+ expect(installerText).toContain("prime_agent_binary_fresh_install");
+ expect(installerText).toContain("prime_agent_binary_update");
+ expect(installerText).toContain("--update");
+ expect(installerText).not.toContain("prime_agent_npm_install");
+ expect(installerText).not.toContain("install_node_npm");
+ expect(installerText).not.toContain("npm install");
+ expect(installerText).not.toContain("NPM Install Path");
+ expect(installerText).not.toContain("prime_agent_package");
+ expect(installerText).not.toContain("prime_agent_original_path");
+ expect(installerText).not.toContain("prime_agent_bootstrap_kernel_on_install");
+ expect(installerText).not.toContain("prime_agent_screen_question");
+ expect(installerText).toContain("prime_agent_binary_acquire_lock");
+ expect(installerText).toContain('grep -F -q -- "$_bin_dir"');
+ });
+
+ it("rejects removed package-manager method flags", () => {
+ expect(installerText).toContain("--method is no longer supported");
+ });
+
+ it("reports an actionable error when HOME and explicit install paths are absent", () => {
+ const result = spawnSync("sh", [installer, "1.2.3"], {
+ encoding: "utf8",
+ env: {
+ PATH: process.env.PATH,
+ PRIME_AGENT_DOWNLOAD_BASE_URL: "https://downloads.example.test",
+ TERM: "dumb",
+ },
+ });
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain("HOME is not set");
+ expect(result.stderr).not.toContain("parameter not set");
+ });
+});
+
+// ============================================================================
+describe("versioned-dir variables", () => {
+ it("uses XDG_DATA_HOME/prime-agent/versions for versions dir", () => {
+ const dir = sourceVar("prime_agent_binary_versions_dir");
+ expect(dir).toMatch(/prime-agent.versions$/);
+ expect(dir).not.toContain(".prime");
+ });
+
+ it("sets symlink to XDG_BIN_HOME/prime-agent", () => {
+ const link = sourceVar("prime_agent_binary_symlink");
+ expect(link).toMatch(/prime-agent$/);
+ expect(link).not.toContain(".prime");
+ });
+
+ it("respects PRIME_AGENT_VERSIONS_DIR env var", () => {
+ const result = execFileSync(
+ "sh",
+ [
+ "-c",
+ "PRIME_AGENT_VERSIONS_DIR=/custom/versions; " +
+ 'eval "$(sed \'s/^main "$@"$/# &/\' "$1")" 2>/dev/null; ' +
+ "printf '%s' $prime_agent_binary_versions_dir",
+ "--",
+ installer,
+ ],
+ { encoding: "utf-8", env: process.env as any },
+ ).trim();
+ expect(result).toBe("/custom/versions");
+ });
+
+ it("respects PRIME_AGENT_BIN_DIR env var for symlink path", () => {
+ const result = execFileSync(
+ "sh",
+ [
+ "-c",
+ "PRIME_AGENT_BIN_DIR=/custom/bin; " +
+ 'eval "$(sed \'s/^main "$@"$/# &/\' "$1")" 2>/dev/null; ' +
+ "printf '%s' $prime_agent_binary_symlink",
+ "--",
+ installer,
+ ],
+ { encoding: "utf-8", env: process.env as any },
+ ).trim();
+ expect(result).toBe("/custom/bin/prime-agent");
+ });
+});
+
+// ============================================================================
+describe("platform detection", () => {
+ it("detects current platform as a valid binary platform", () => {
+ const platform = execFileSync(
+ "sh",
+ [
+ "-c",
+ 'eval "$(sed \'s/^main "$@"$/# &/\' "$1")" 2>/dev/null; prime_agent_detect_binary_platform',
+ "--",
+ installer,
+ ],
+ { encoding: "utf-8", env: process.env as any },
+ ).trim();
+ expect(["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64"]).toContain(platform);
+ });
+});
+
+// ============================================================================
+describe("atomic symlink function", () => {
+ it("creates and replaces symlink atomically", () => {
+ const tmp = mkTemp();
+ const v1 = `${tmp}/versions/v1`;
+ const v2 = `${tmp}/versions/v2`;
+ const link = `${tmp}/bin/prime-agent`;
+ const expectedTarget = `${realpathSync(tmp)}/versions/v2/pi`;
+ const cmd =
+ 'eval "$(sed \'s/^main "$@"$/# &/\' "$1")" 2>/dev/null' +
+ "; mkdir -p " +
+ v1 +
+ " " +
+ v2 +
+ "; touch " +
+ v1 +
+ "/pi " +
+ v2 +
+ "/pi" +
+ "; chmod +x " +
+ v1 +
+ "/pi " +
+ v2 +
+ "/pi" +
+ "; mkdir -p " +
+ tmp +
+ "/bin" +
+ "; prime_agent_binary_symlink=" +
+ link +
+ "; prime_agent_binary_atomic_symlink " +
+ v1 +
+ "/pi " +
+ link +
+ "; prime_agent_binary_atomic_symlink " +
+ v2 +
+ "/pi " +
+ link +
+ '; [ "$(readlink ' +
+ link +
+ ')" = "' +
+ expectedTarget +
+ "\" ] && printf 'OK'";
+ const result = execFileSync("sh", ["-c", cmd, "--", installer], {
+ encoding: "utf-8",
+ env: process.env as any,
+ }).trim();
+ expect(result).toBe("OK");
+ });
+});
+
+// ============================================================================
+describe("config dir isolation", () => {
+ it("does not use ~/.prime for binary install paths", () => {
+ const versionsLine = installerText.match(/^prime_agent_binary_versions_dir=.*$/m);
+ const symlinkLine = installerText.match(/^prime_agent_binary_symlink=.*$/m);
+ if (versionsLine) expect(versionsLine[0]).not.toContain(".prime");
+ if (symlinkLine) expect(symlinkLine[0]).not.toContain(".prime");
+ });
+
+ it("uses XDG data dir for versions", () => {
+ expect(installerText).toContain("XDG_DATA_HOME");
+ });
+
+ it("uses separate bin dir for symlink", () => {
+ expect(installerText).toContain("prime_agent_binary_symlink");
+ });
+
+ it("does not copy sidecars into ~/.prime", () => {
+ const freshSection = installerText.match(/prime_agent_binary_fresh_install[^}]*}/s)?.[0] || "";
+ expect(freshSection).not.toContain(".prime");
+ });
+});
+
+// ============================================================================
+describe("install.sh sidecar", () => {
+ it("ensures install.sh is made executable in versioned dir", () => {
+ expect(installerText).toContain('chmod +x "$_version_dir/install.sh"');
+ });
+
+ it("appears in both fresh_install and update paths", () => {
+ const matches = installerText.match(/chmod \+x "\$_version_dir\/install\.sh"/g);
+ expect(matches).not.toBeNull();
+ expect(matches!.length).toBe(2);
+ });
+});
diff --git a/packages/coding-agent/test/bun-release-archives.test.ts b/packages/coding-agent/test/bun-release-archives.test.ts
new file mode 100644
index 0000000000..837c3cbe10
--- /dev/null
+++ b/packages/coding-agent/test/bun-release-archives.test.ts
@@ -0,0 +1,250 @@
+import { afterEach, describe, expect, test } from "bun:test";
+import { spawnSync } from "node:child_process";
+import {
+ chmodSync,
+ cpSync,
+ existsSync,
+ mkdirSync,
+ mkdtempSync,
+ readdirSync,
+ readFileSync,
+ rmSync,
+ symlinkSync,
+ writeFileSync,
+} from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const packageDir = join(dirname(fileURLToPath(import.meta.url)), "..");
+const repoRoot = join(packageDir, "..", "..");
+const packScript = join(repoRoot, "scripts", "pack-prime-agent-release.mjs");
+const releaseRoot = join(packageDir, "release");
+const temporaryRoots: string[] = [];
+const outputDirs: string[] = [];
+const platforms = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "windows-x64", "windows-arm64"];
+
+function fixture(): { root: string; binaries: string; sidecars: string; output: string } {
+ const root = mkdtempSync(join(tmpdir(), "prime-agent-release-"));
+ temporaryRoots.push(root);
+ const binaries = join(root, "binaries");
+ const sidecars = join(root, "sidecars");
+ for (const platform of platforms) {
+ const dir = join(binaries, platform);
+ mkdirSync(dir, { recursive: true });
+ const binaryName = platform.startsWith("windows") ? "pi.exe" : "pi";
+ const binaryPath = join(dir, binaryName);
+ cpSync("/bin/echo", binaryPath);
+ chmodSync(binaryPath, 0o755);
+ }
+ mkdirSync(sidecars, { recursive: true });
+ for (const name of ["prime-agent-runtime", "skills", "theme", "assets", "export-html", "docs", "examples"]) {
+ mkdirSync(join(sidecars, name));
+ writeFileSync(join(sidecars, name, ".keep"), "fixture");
+ }
+ writeFileSync(join(sidecars, "package.json"), JSON.stringify({ name: "prime-agent", version: "1.2.3" }));
+ writeFileSync(join(sidecars, "README.md"), "readme");
+ writeFileSync(join(sidecars, "CHANGELOG.md"), "changelog");
+ writeFileSync(
+ join(sidecars, "install.sh"),
+ '#!/bin/sh\nbase="__PRIME_AGENT_DOWNLOAD_BASE_URL__"\nchannel="__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__"\n',
+ );
+ writeFileSync(join(sidecars, "photon_rs_bg.wasm"), "wasm");
+ writeFileSync(
+ join(sidecars, "install.ps1"),
+ '#!/usr/bin/env pwsh\n# Prime Agent Windows installer\n$Script:PrimeAgentBaseUrl = "__PRIME_AGENT_DOWNLOAD_BASE_URL__"\n$Script:DefaultReleaseChannel = "__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__"\n',
+ );
+ const output = join(releaseRoot, `test-${process.pid}-${Math.random().toString(36).slice(2)}`);
+ outputDirs.push(output);
+ return { root, binaries, sidecars, output };
+}
+
+function pack(f: ReturnType, extra: string[] = []) {
+ return spawnSync(
+ process.execPath,
+ [
+ packScript,
+ "--base-url",
+ "https://downloads.example.test",
+ "--channel",
+ "stable",
+ "--version",
+ "1.2.3",
+ "--binary-base-dir",
+ f.binaries,
+ "--sidecar-dir",
+ f.sidecars,
+ "--out-dir",
+ f.output,
+ ...extra,
+ ],
+ { encoding: "utf8" },
+ );
+}
+
+afterEach(() => {
+ for (const root of temporaryRoots.splice(0)) rmSync(root, { recursive: true, force: true });
+ for (const output of outputDirs.splice(0)) rmSync(output, { recursive: true, force: true });
+});
+
+describe("compiled release archives", () => {
+ test("creates all platform archives with flat required sidecars and rendered installer", () => {
+ const f = fixture();
+ const result = pack(f);
+ expect(result.status, result.stderr).toBe(0);
+ const artifacts = join(f.output, "artifacts");
+ const archives = readdirSync(artifacts).filter((name) => name.endsWith(".tar.gz") || name.endsWith(".zip"));
+ const expectedArchives = [
+ ...["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64"].map((p) => `prime-agent-1.2.3-${p}.tar.gz`),
+ ...["windows-x64", "windows-arm64"].map((p) => `prime-agent-1.2.3-${p}.zip`),
+ ];
+ expect(archives.sort()).toEqual(expectedArchives.sort());
+ expect(readFileSync(join(artifacts, "stable"), "utf8")).toBe("v1.2.3\n");
+ expect(readFileSync(join(artifacts, "SHA256SUMS"), "utf8").trim().split("\n")).toHaveLength(6);
+
+ const extracted = join(f.root, "extracted");
+ mkdirSync(extracted);
+ const archive = join(artifacts, "prime-agent-1.2.3-darwin-arm64.tar.gz");
+ expect(spawnSync("tar", ["-xzf", archive, "-C", extracted]).status).toBe(0);
+
+ const winExtracted = join(f.root, "win-extracted");
+ mkdirSync(winExtracted);
+ const winArchive = join(artifacts, "prime-agent-1.2.3-windows-x64.zip");
+ expect(spawnSync("unzip", ["-q", winArchive, "-d", winExtracted]).status).toBe(0);
+ expect(existsSync(join(winExtracted, "prime-agent.exe"))).toBe(true);
+ expect(readFileSync(join(extracted, "install.sh"), "utf8")).toContain('base="https://downloads.example.test"');
+ expect(readFileSync(join(extracted, "install.sh"), "utf8")).toContain('channel="stable"');
+ const powershellInstaller = readFileSync(join(winExtracted, "install.ps1"), "utf8");
+ expect(powershellInstaller).toContain('$Script:PrimeAgentBaseUrl = "https://downloads.example.test"');
+ expect(powershellInstaller).toContain('$Script:DefaultReleaseChannel = "stable"');
+ expect(powershellInstaller).not.toContain("__PRIME_AGENT_DOWNLOAD_BASE_URL__");
+ expect(powershellInstaller).not.toContain("__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__");
+ const windowsManifest = JSON.parse(readFileSync(join(winExtracted, "package.json"), "utf8"));
+ expect(windowsManifest.bin).toEqual({ "prime-agent": "./prime-agent.exe" });
+ const manifest = JSON.parse(readFileSync(join(extracted, "package.json"), "utf8"));
+ expect(manifest).toMatchObject({
+ name: "prime-agent",
+ version: "1.2.3",
+ bin: { "prime-agent": "./prime-agent" },
+ packageManager: "bun@1.4.0",
+ });
+ for (const name of [
+ "prime-agent",
+ "package.json",
+ "README.md",
+ "CHANGELOG.md",
+ "install.sh",
+ "prime-agent-runtime",
+ "skills",
+ "theme",
+ "assets",
+ "export-html",
+ "docs",
+ "examples",
+ "photon_rs_bg.wasm",
+ "install.ps1",
+ ]) {
+ expect(existsSync(join(extracted, name))).toBe(true);
+ }
+ });
+
+ test("supports a single-platform local archive", () => {
+ const f = fixture();
+ const result = pack(f, ["--platform", "linux-x64"]);
+ expect(result.status, result.stderr).toBe(0);
+ expect(readdirSync(join(f.output, "artifacts")).filter((name) => name.endsWith(".tar.gz"))).toEqual([
+ "prime-agent-1.2.3-linux-x64.tar.gz",
+ ]);
+ });
+
+ test("fails closed when a required sidecar is missing", () => {
+ const f = fixture();
+ rmSync(join(f.sidecars, "theme"), { recursive: true });
+ const result = pack(f);
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain("Missing required sidecars");
+ });
+
+ test("rejects dependency directories in release sidecars", () => {
+ const f = fixture();
+ mkdirSync(join(f.sidecars, "examples", "node_modules"));
+ const result = pack(f);
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain("forbidden dependency or cache directory");
+ });
+ test("normalizes v-prefixed versions for binary archive metadata", () => {
+ const f = fixture();
+ const result = pack(f, ["--version", "v1.2.3"]);
+ expect(result.status, result.stderr).toBe(0);
+ const manifest = JSON.parse(readFileSync(join(f.output, "artifacts", "latest.json"), "utf8"));
+ expect(manifest.version).toBe("v1.2.3");
+ expect(manifest.baseUrl).toBe("https://downloads.example.test/releases/v1.2.3");
+ expect(manifest.platforms.length).toBe(6);
+ expect(manifest.platforms.map((p: any) => p.platform).sort()).toEqual([
+ "darwin-arm64",
+ "darwin-x64",
+ "linux-arm64",
+ "linux-x64",
+ "windows-arm64",
+ "windows-x64",
+ ]);
+ expect(existsSync(join(f.output, "artifacts", "prime-agent-1.2.3-darwin-arm64.tar.gz"))).toBe(true);
+ });
+
+ test("trims release base URLs before writing binary metadata", () => {
+ const f = fixture();
+ const result = pack(f, ["--base-url", " https://downloads.example.test/ "]);
+ expect(result.status, result.stderr).toBe(0);
+ const manifest = JSON.parse(readFileSync(join(f.output, "artifacts", "latest.json"), "utf8"));
+ expect(manifest.baseUrl).toBe("https://downloads.example.test/releases/v1.2.3");
+ });
+
+ test("rejects insecure release base URLs", () => {
+ const f = fixture();
+ const result = pack(f, ["--base-url", "http://downloads.example.test"]);
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain("must use HTTPS");
+ });
+
+ test("rejects empty query and fragment delimiters in release base URLs", () => {
+ for (const delimiter of ["?", "#"]) {
+ const f = fixture();
+ const result = pack(f, ["--base-url", `https://downloads.example.test/${delimiter}`]);
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain("must not contain a query or fragment");
+ }
+ });
+
+ test("rejects shell-active release base URLs", () => {
+ const f = fixture();
+ const result = pack(f, ["--base-url", "https://downloads.example.test/$(touch-danger)"]);
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain("unsafe shell characters");
+ });
+
+ test("preserves existing artifacts when preflight validation fails", () => {
+ const f = fixture();
+ const sentinel = join(f.output, "artifacts", "keep.txt");
+ mkdirSync(dirname(sentinel), { recursive: true });
+ writeFileSync(sentinel, "keep");
+ rmSync(join(f.sidecars, "theme"), { recursive: true });
+ const result = pack(f);
+ expect(result.status).not.toBe(0);
+ expect(readFileSync(sentinel, "utf8")).toBe("keep");
+ });
+
+ test("rejects output paths that traverse a symlink", () => {
+ const f = fixture();
+ const outside = join(f.root, "outside");
+ const sentinel = join(outside, "old", "keep.txt");
+ mkdirSync(dirname(sentinel), { recursive: true });
+ writeFileSync(sentinel, "keep");
+ const link = join(releaseRoot, `link-${process.pid}-${Math.random().toString(36).slice(2)}`);
+ outputDirs.push(link);
+ symlinkSync(outside, link, "dir");
+ const result = pack(f, ["--out-dir", join(link, "old")]);
+ expect(result.status).not.toBe(0);
+ expect(result.stderr).toContain("symlinked output path");
+ expect(readFileSync(sentinel, "utf8")).toBe("keep");
+ });
+});
diff --git a/packages/coding-agent/test/bun-test-preload.ts b/packages/coding-agent/test/bun-test-preload.ts
new file mode 100644
index 0000000000..aa313ca953
--- /dev/null
+++ b/packages/coding-agent/test/bun-test-preload.ts
@@ -0,0 +1,142 @@
+import { jest } from "bun:test";
+import { readFile } from "node:fs/promises";
+import { describe, expect, vi } from "vitest";
+
+type AnyFunction = (...args: never[]) => unknown;
+type ViCompat = Record;
+
+const compat = vi as unknown as ViCompat;
+const nativeSetTimeout = globalThis.setTimeout;
+process.env.DO_NOT_TRACK ??= "1";
+// Do not let the parent Prime Agent daemon make CLI tests operate on live sessions.
+for (const name of Object.keys(process.env)) {
+ if (name.startsWith("PRIME_AGENT_INTERNAL_") || name.startsWith("RLM_")) {
+ delete process.env[name];
+ }
+}
+delete process.env.PRIME_AGENT_CODING_AGENT_DIR;
+delete process.env.PRIME_AGENT_KERNEL_OWNER_PID;
+const gitConfigCount = Number.parseInt(process.env.GIT_CONFIG_COUNT ?? "0", 10) || 0;
+process.env[`GIT_CONFIG_KEY_${gitConfigCount}`] = "commit.gpgsign";
+process.env[`GIT_CONFIG_VALUE_${gitConfigCount}`] = "false";
+process.env.GIT_CONFIG_COUNT = String(gitConfigCount + 1);
+const originalEnv = new Map();
+const originalGlobals = new Map();
+
+compat.hoisted ??= (factory: AnyFunction) => factory();
+compat.mocked ??= (value: unknown) => value;
+compat.setSystemTime ??= (value: Date | number) => jest.setSystemTime(value);
+compat.advanceTimersByTime = (milliseconds: number) => {
+ const targetTime = Date.now() + milliseconds;
+ jest.advanceTimersByTime(milliseconds);
+ jest.setSystemTime(targetTime);
+ return vi;
+};
+const flushMicrotasks = async (): Promise => {
+ // Timer callbacks in these tests often cross several awaited boundaries before
+ // scheduling the next timer. Keep fake time still while those jobs settle.
+ for (let index = 0; index < 8; index++) await Promise.resolve();
+};
+
+compat.advanceTimersByTimeAsync ??= async (milliseconds: number) => {
+ await flushMicrotasks();
+ let remaining = milliseconds;
+ while (remaining > 0) {
+ const step = Math.min(remaining, 10);
+ const targetTime = Date.now() + step;
+ jest.advanceTimersByTime(step);
+ jest.setSystemTime(targetTime);
+ remaining -= step;
+ await flushMicrotasks();
+ }
+ if (milliseconds === 0) {
+ jest.advanceTimersByTime(0);
+ await flushMicrotasks();
+ }
+};
+compat.runAllTimersAsync ??= async () => {
+ await flushMicrotasks();
+ for (let pass = 0; pass < 10_000 && jest.getTimerCount() > 0; pass++) {
+ jest.runAllTimers();
+ await flushMicrotasks();
+ }
+};
+compat.advanceTimersToNextTimerAsync ??= async () => {
+ await flushMicrotasks();
+ jest.advanceTimersToNextTimer();
+ await flushMicrotasks();
+};
+compat.runOnlyPendingTimersAsync ??= async () => {
+ await flushMicrotasks();
+ jest.runOnlyPendingTimers();
+ await flushMicrotasks();
+};
+compat.waitFor ??= async (assertion: AnyFunction, options: { timeout?: number; interval?: number } = {}) => {
+ const timeout = options.timeout ?? 1_000;
+ const interval = options.interval ?? 20;
+ const deadline = Date.now() + timeout;
+ let lastError: unknown;
+ while (Date.now() <= deadline) {
+ try {
+ return await assertion();
+ } catch (error) {
+ lastError = error;
+ }
+ if (jest.isFakeTimers()) {
+ const targetTime = Date.now() + interval;
+ jest.advanceTimersByTime(interval);
+ jest.setSystemTime(targetTime);
+ await flushMicrotasks();
+ // Promise jobs alone do not let spawned-process and socket events run.
+ await readFile("/dev/null");
+ } else {
+ await new Promise((resolve) => nativeSetTimeout(resolve, interval));
+ }
+ }
+ throw lastError ?? new Error(`waitFor timed out after ${timeout} ms`);
+};
+
+const expectCompat = expect as typeof expect & {
+ poll?: (actual: () => unknown | Promise, options?: { timeout?: number; interval?: number }) => unknown;
+};
+expectCompat.poll ??= (actual: () => unknown | Promise, options?: { timeout?: number; interval?: number }) =>
+ new Proxy(
+ {},
+ {
+ get:
+ (_target, matcher: string) =>
+ async (...args: unknown[]) =>
+ vi.waitFor(async () => {
+ const received = await actual();
+ return (expect(received) as unknown as Record unknown>)[matcher]?.(
+ ...args,
+ );
+ }, options),
+ },
+ );
+
+compat.stubEnv ??= (name: string, value: string | undefined) => {
+ if (!originalEnv.has(name)) originalEnv.set(name, process.env[name]);
+ if (value === undefined) delete process.env[name];
+ else process.env[name] = value;
+};
+compat.unstubAllEnvs ??= () => {
+ for (const [name, value] of originalEnv) {
+ if (value === undefined) delete process.env[name];
+ else process.env[name] = value;
+ }
+ originalEnv.clear();
+};
+compat.stubGlobal ??= (name: PropertyKey, value: unknown) => {
+ if (!originalGlobals.has(name)) originalGlobals.set(name, Object.getOwnPropertyDescriptor(globalThis, name));
+ Object.defineProperty(globalThis, name, { configurable: true, writable: true, value });
+};
+compat.unstubAllGlobals ??= () => {
+ for (const [name, descriptor] of originalGlobals) {
+ if (descriptor) Object.defineProperty(globalThis, name, descriptor);
+ else Reflect.deleteProperty(globalThis, name);
+ }
+ originalGlobals.clear();
+};
+
+(describe as typeof describe & { sequential?: typeof describe }).sequential ??= describe;
diff --git a/packages/coding-agent/test/bun-test-runtime-isolation.test.ts b/packages/coding-agent/test/bun-test-runtime-isolation.test.ts
new file mode 100644
index 0000000000..07d9fdd1ec
--- /dev/null
+++ b/packages/coding-agent/test/bun-test-runtime-isolation.test.ts
@@ -0,0 +1,33 @@
+import { spawnSync } from "node:child_process";
+import { resolve } from "node:path";
+import { describe, expect, it } from "vitest";
+
+const repoRoot = resolve(import.meta.dirname, "../../..");
+const launcher = resolve(repoRoot, "scripts/run-with-clean-env.ts");
+
+describe("Bun test runtime isolation", () => {
+ it("removes live Prime Agent orchestration state before Bun and its descendants start", () => {
+ const probe = `console.log(JSON.stringify({
+ internal: process.env.PRIME_AGENT_INTERNAL_ORPHAN_PROCESS_JOURNAL,
+ rlm: process.env.RLM_SESSION_DIR,
+ agentDir: process.env.PRIME_AGENT_CODING_AGENT_DIR,
+ owner: process.env.PRIME_AGENT_KERNEL_OWNER_PID,
+ tags: process.env.PRIME_AGENT_TEST_TAGS,
+ }))`;
+ const result = spawnSync(process.execPath, [launcher, "bun", "-e", probe], {
+ cwd: repoRoot,
+ encoding: "utf8",
+ env: {
+ ...process.env,
+ PRIME_AGENT_INTERNAL_ORPHAN_PROCESS_JOURNAL: "/live/orphans.jsonl",
+ RLM_SESSION_DIR: "/live/session",
+ PRIME_AGENT_CODING_AGENT_DIR: "/live/agent",
+ PRIME_AGENT_KERNEL_OWNER_PID: "1234",
+ PRIME_AGENT_TEST_TAGS: "kernel-heavy",
+ },
+ });
+
+ expect(result.status, result.stderr).toBe(0);
+ expect(JSON.parse(result.stdout)).toEqual({ tags: "kernel-heavy" });
+ });
+});
diff --git a/packages/coding-agent/test/child-process.test.ts b/packages/coding-agent/test/child-process.test.ts
index 08a87793f2..93ff6cc141 100644
--- a/packages/coding-agent/test/child-process.test.ts
+++ b/packages/coding-agent/test/child-process.test.ts
@@ -1,7 +1,12 @@
import { type ChildProcess, spawn } from "node:child_process";
import { EventEmitter } from "node:events";
import { describe, expect, it } from "vitest";
-import { isProcessAlive, isZombieProcess, waitForChildProcess } from "../src/utils/child-process.js";
+import {
+ isProcessAlive,
+ isZombieProcess,
+ signalProcessGroupOrProcess,
+ waitForChildProcess,
+} from "../src/utils/child-process.js";
describe("waitForChildProcess", () => {
it("reports signaled already-exited children as failures", async () => {
@@ -16,6 +21,19 @@ describe("waitForChildProcess", () => {
});
});
+describe("signalProcessGroupOrProcess", () => {
+ it("does not throw for a running child pid", async () => {
+ const child = spawn(process.execPath, ["--eval", "setTimeout(() => {}, 1000)"], { stdio: "ignore" });
+ await new Promise((resolve) => child.once("spawn", () => resolve()));
+ expect(() => signalProcessGroupOrProcess(child.pid!, "SIGTERM")).not.toThrow();
+ await new Promise((resolve) => child.once("exit", () => resolve()));
+ });
+
+ it("does not throw for a nonexistent pid", () => {
+ expect(() => signalProcessGroupOrProcess(999999999, "SIGKILL")).not.toThrow();
+ });
+});
+
describe("process liveness", () => {
it("treats the current process as alive and not a zombie", () => {
expect(isProcessAlive(process.pid)).toBe(true);
diff --git a/packages/coding-agent/test/clipboard-image-bmp-conversion.test.ts b/packages/coding-agent/test/clipboard-image-bmp-conversion.test.ts
index 95107506b5..87a7e0e166 100644
--- a/packages/coding-agent/test/clipboard-image-bmp-conversion.test.ts
+++ b/packages/coding-agent/test/clipboard-image-bmp-conversion.test.ts
@@ -5,6 +5,7 @@
* This tests the fix for WSL2/WSLg where clipboard often provides image/bmp
* instead of image/png.
*/
+import * as childProcess from "node:child_process";
import { describe, expect, test, vi } from "vitest";
function createTinyBmp1x1Red24bpp(): Uint8Array {
@@ -42,10 +43,9 @@ function createTinyBmp1x1Red24bpp(): Uint8Array {
}
// Mock wl-paste to return BMP
-vi.mock("child_process", async () => {
- const actual = await vi.importActual("child_process");
+vi.mock("child_process", () => {
return {
- ...actual,
+ ...childProcess,
spawnSync: vi.fn((command: string, args: string[]) => {
if (command === "wl-paste" && args.includes("--list-types")) {
return { status: 0, stdout: Buffer.from("image/bmp\n"), error: null };
diff --git a/packages/coding-agent/test/clipboard-image.test.ts b/packages/coding-agent/test/clipboard-image.test.ts
index eda61a29e5..c99a03387c 100644
--- a/packages/coding-agent/test/clipboard-image.test.ts
+++ b/packages/coding-agent/test/clipboard-image.test.ts
@@ -49,7 +49,6 @@ function spawnError(error: Error): SpawnSyncReturns {
describe("readClipboardImage", () => {
beforeEach(() => {
- vi.resetModules();
mocks.spawnSync.mockReset();
mocks.clipboard.hasImage.mockReset();
mocks.clipboard.getImageBinary.mockReset();
diff --git a/packages/coding-agent/test/compaction-summary-reasoning.test.ts b/packages/coding-agent/test/compaction-summary-reasoning.test.ts
index c674ef1a7d..886760bc4f 100644
--- a/packages/coding-agent/test/compaction-summary-reasoning.test.ts
+++ b/packages/coding-agent/test/compaction-summary-reasoning.test.ts
@@ -1,3 +1,4 @@
+import { createRequire } from "node:module";
import type { AgentMessage } from "@earendil-works/pi-agent-core";
import type { AssistantMessage, Model } from "@earendil-works/pi-ai";
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -7,13 +8,11 @@ const { completeSimpleMock } = vi.hoisted(() => ({
completeSimpleMock: vi.fn(),
}));
-vi.mock("@earendil-works/pi-ai", async (importOriginal) => {
- const actual = await importOriginal();
- return {
- ...actual,
- completeSimple: completeSimpleMock,
- };
-});
+const __piAi = createRequire(import.meta.url)("@earendil-works/pi-ai");
+vi.mock("@earendil-works/pi-ai", () => ({
+ ...__piAi,
+ completeSimple: completeSimpleMock,
+}));
function createModel(reasoning: boolean): Model<"anthropic-messages"> {
return {
diff --git a/packages/coding-agent/test/compiled-artifact-smoke.test.ts b/packages/coding-agent/test/compiled-artifact-smoke.test.ts
new file mode 100644
index 0000000000..5bce3bb746
--- /dev/null
+++ b/packages/coding-agent/test/compiled-artifact-smoke.test.ts
@@ -0,0 +1,65 @@
+import { describe, expect, it } from "bun:test";
+import { existsSync, readdirSync, statSync } from "node:fs";
+import { dirname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+import {
+ inspectNativeExecutable,
+ REQUIRED_BINARY_SIDECARS,
+ runEmptyDiagnostic,
+ runPackagedSmoke,
+} from "../../../scripts/compiled-artifact-smoke.js";
+
+const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const distDir = join(packageDir, "dist");
+const binary = join(distDir, "pi");
+
+function dependencyDirectories(root: string): string[] {
+ const found: string[] = [];
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
+ if (!entry.isDirectory()) continue;
+ const path = join(root, entry.name);
+ if (["node_modules", ".venv", "__pycache__", ".pytest_cache"].includes(entry.name)) found.push(path);
+ else found.push(...dependencyDirectories(path));
+ }
+ return found;
+}
+
+describe("compiled Bun artifact", () => {
+ it("is a native executable", () => {
+ expect(existsSync(binary)).toBe(true);
+ expect(statSync(binary).size).toBeGreaterThan(10 * 1024 * 1024);
+ expect(["elf", "mach-o"]).toContain(inspectNativeExecutable(binary));
+ });
+
+ it("documents the unsupported binary-only layout", () => {
+ const result = runEmptyDiagnostic(binary);
+ expect(result.detectedMissingPackageJson).toBe(true);
+ expect(result.run.exitCode).not.toBe(0);
+ });
+
+ it("contains the complete supported sidecar set", () => {
+ for (const name of REQUIRED_BINARY_SIDECARS) {
+ expect(existsSync(join(distDir, name)), name).toBe(true);
+ }
+ for (const name of ["template.html", "template.css", "template.js"]) {
+ expect(existsSync(join(distDir, "export-html", name)), name).toBe(true);
+ }
+ expect(existsSync(join(distDir, "prime-agent-runtime", "pyproject.toml"))).toBe(true);
+ });
+
+ it("contains no installed dependency or cache directories", () => {
+ expect(dependencyDirectories(distDir)).toEqual([]);
+ });
+
+ it("runs --version and --help with no Node or npm on PATH", () => {
+ const result = runPackagedSmoke(binary, distDir);
+ expect(result.nodeAvailable).toBe(false);
+ expect(result.npmAvailable).toBe(false);
+ expect(result.passed).toBe(true);
+ for (const run of result.runs) {
+ expect(run.exitCode).toBe(0);
+ expect(run.stdout.trim().length).toBeGreaterThan(0);
+ expect(run.stderr).not.toContain("ENOENT");
+ }
+ });
+});
diff --git a/packages/coding-agent/test/daemon-client.test.ts b/packages/coding-agent/test/daemon-client.test.ts
index f7614733fa..42d75a3caf 100644
--- a/packages/coding-agent/test/daemon-client.test.ts
+++ b/packages/coding-agent/test/daemon-client.test.ts
@@ -15,7 +15,17 @@ const netMock = vi.hoisted(() => {
readonly writes: string[] = [];
destroyed = false;
- constructor(readonly path: string) {}
+ path = "";
+
+ constructor(path = "") {
+ this.path = path;
+ }
+
+ connect(path: string): this {
+ this.path = path;
+ sockets.push(this);
+ return this;
+ }
on(event: string, listener: Listener): this {
const listeners = this.listeners.get(event) ?? new Set();
@@ -83,11 +93,12 @@ const netMock = vi.hoisted(() => {
return socket;
});
- return { createConnection, sockets };
+ return { createConnection, MockSocket, sockets };
});
vi.mock("node:net", () => ({
createConnection: netMock.createConnection,
+ Socket: netMock.MockSocket,
}));
function emitHello(
@@ -155,11 +166,9 @@ describe("DaemonClient", () => {
expect(netMock.sockets).toHaveLength(1);
const firstSocket = netMock.sockets[0]!;
- const timeoutRejection = expect(firstAttempt).resolves.toMatchObject({
- message: expect.stringContaining("Timed out after 5ms connecting to the Prime Agent daemon."),
- });
await vi.advanceTimersByTimeAsync(5);
- await timeoutRejection;
+ const timeoutError = await firstAttempt;
+ expect(timeoutError.message).toContain("Timed out after 5ms connecting to the Prime Agent daemon.");
expect(firstSocket.destroyed).toBe(true);
expect(firstSocket.listenerCount("data")).toBe(0);
@@ -419,17 +428,12 @@ describe("DaemonClient", () => {
const request = client.request({ type: "list", all: true });
- await expect(request).rejects.toMatchObject({
- message: expect.stringContaining(
- 'Cannot send daemon command "list" because the Prime Agent daemon is not connected.',
- ),
- });
- await expect(request).rejects.toMatchObject({
- message: expect.stringContaining("Socket: /tmp/prime-agent.sock."),
- });
- await expect(request).rejects.toMatchObject({
- message: expect.stringContaining("Daemon log:"),
- });
+ const requestError = await captureRejection(request);
+ expect(requestError.message).toContain(
+ 'Cannot send daemon command "list" because the Prime Agent daemon is not connected.',
+ );
+ expect(requestError.message).toContain("Socket: /tmp/prime-agent.sock.");
+ expect(requestError.message).toContain("Daemon log:");
});
it("keeps durable command envelopes on the session-action protocol", async () => {
@@ -931,7 +935,7 @@ describe("DaemonClient", () => {
});
});
-async function captureRejection(promise: Promise): Promise {
+async function captureRejection(promise: Promise): Promise {
try {
await promise;
} catch (error) {
diff --git a/packages/coding-agent/test/daemon-command.test.ts b/packages/coding-agent/test/daemon-command.test.ts
index 65866521c9..43e0e0ef4e 100644
--- a/packages/coding-agent/test/daemon-command.test.ts
+++ b/packages/coding-agent/test/daemon-command.test.ts
@@ -1,3 +1,4 @@
+import { createRequire } from "node:module";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const daemonClientMock = vi.hoisted(() => {
@@ -121,10 +122,8 @@ const spawnMock = vi.hoisted(() => {
};
});
-vi.mock("node:child_process", async (importOriginal) => {
- const original = (await importOriginal()) as Record;
- return { ...original, spawn: spawnMock.mockSpawn as never };
-});
+const __childProcess = createRequire(import.meta.url)("node:child_process");
+vi.mock("node:child_process", () => ({ ...__childProcess, spawn: spawnMock.mockSpawn as never }));
import { handleDaemonCommand } from "../src/cli/daemon-command.js";
@@ -132,7 +131,7 @@ describe("daemon command", () => {
let consoleErrorMessages: unknown[];
beforeEach(() => {
- process.exitCode = undefined;
+ process.exitCode = 0;
daemonClientMock.instances.length = 0;
daemonClientMock.behavior.promptSucceeds = false;
daemonClientMock.behavior.emitStaleAgentEndOnAttach = false;
@@ -149,7 +148,7 @@ describe("daemon command", () => {
});
afterEach(() => {
- process.exitCode = undefined;
+ process.exitCode = 0;
vi.restoreAllMocks();
});
diff --git a/packages/coding-agent/test/daemon-launch.test.ts b/packages/coding-agent/test/daemon-launch.test.ts
index 26cc049ef3..3a12012b31 100644
--- a/packages/coding-agent/test/daemon-launch.test.ts
+++ b/packages/coding-agent/test/daemon-launch.test.ts
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:
import { createServer, type Server, type Socket } from "node:net";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it } from "vitest";
import {
ensureInteractiveDaemonRunning,
probeDaemonVersion,
@@ -292,7 +292,6 @@ describe("ensureInteractiveDaemonRunning", () => {
});
it("reuses a daemon that becomes current inside the startup window", async () => {
- vi.useFakeTimers();
let resolveFirstConnection = () => {};
let resolveSecondConnection = () => {};
const firstConnection = new Promise((resolve) => {
@@ -311,19 +310,16 @@ describe("ensureInteractiveDaemonRunning", () => {
});
cleanups.push(daemon.close);
- try {
- const ensuring = ensureInteractiveDaemonRunning(daemon.socketPath);
- await firstConnection;
- await vi.advanceTimersByTimeAsync(2000);
- await secondConnection;
- await expect(ensuring).resolves.toBeUndefined();
- } finally {
- vi.useRealTimers();
- }
+ const ensuring = ensureInteractiveDaemonRunning(daemon.socketPath, undefined, {
+ initialHelloTimeoutMs: 10,
+ startupTimeoutMs: 100,
+ });
+ await firstConnection;
+ await secondConnection;
+ await expect(ensuring).resolves.toBeUndefined();
});
it("leaves a connected unresponsive daemon running after the startup window", async () => {
- vi.useFakeTimers();
const connections: Array<() => void> = [];
const connected = [0, 1].map(
(index) =>
@@ -339,19 +335,16 @@ describe("ensureInteractiveDaemonRunning", () => {
});
cleanups.push(daemon.close);
- try {
- const ensuring = ensureInteractiveDaemonRunning(daemon.socketPath);
- const rejected = expect(ensuring).rejects.toThrow(/accepted connections but did not finish startup/);
- await connected[0];
- await vi.advanceTimersByTimeAsync(2000);
- await connected[1];
- await vi.advanceTimersByTimeAsync(30_000);
- await rejected;
- expect(commands).not.toContain("list");
- expect(commands).not.toContain("shutdown");
- } finally {
- vi.useRealTimers();
- }
+ const ensuring = ensureInteractiveDaemonRunning(daemon.socketPath, undefined, {
+ initialHelloTimeoutMs: 10,
+ startupTimeoutMs: 100,
+ });
+ const rejected = expect(ensuring).rejects.toThrow(/accepted connections but did not finish startup/);
+ await connected[0];
+ await connected[1];
+ await rejected;
+ expect(commands).not.toContain("list");
+ expect(commands).not.toContain("shutdown");
});
it("cancels replacement when a stale-looking daemon becomes current", async () => {
diff --git a/packages/coding-agent/test/daemon-mode.test.ts b/packages/coding-agent/test/daemon-mode.test.ts
index 2fe4077d06..e2705dbf53 100644
--- a/packages/coding-agent/test/daemon-mode.test.ts
+++ b/packages/coding-agent/test/daemon-mode.test.ts
@@ -1837,8 +1837,11 @@ describe("daemon mode helpers", () => {
const tempDir = mkdtempSync(join(tmpdir(), "pa-msg-"));
const socketPath = join(tempDir, "d.sock");
let connectionCount = 0;
+ const sockets = new Set();
const server: Server = createServer((socket) => {
connectionCount++;
+ sockets.add(socket);
+ socket.once("close", () => sockets.delete(socket));
socket.on("error", () => undefined);
socket.write(
`${JSON.stringify({
@@ -1893,9 +1896,14 @@ describe("daemon mode helpers", () => {
}
).sendRemoteAgentSessionMessage.bind(daemon);
- await expect(sendRemoteAgentSessionMessage(makeState("source"), "remote", "continue")).rejects.toThrow(
- "Target session has too many pending messages",
- );
+ let rejection: unknown;
+ try {
+ await sendRemoteAgentSessionMessage(makeState("source"), "remote", "continue");
+ } catch (error) {
+ rejection = error;
+ }
+ expect(rejection).toBeInstanceOf(Error);
+ expect((rejection as Error).message).toContain("Target session has too many pending messages");
expect(connectionCount).toBe(1);
} finally {
if (previousSupervisorSocket === undefined) {
@@ -1903,6 +1911,7 @@ describe("daemon mode helpers", () => {
} else {
process.env[DAEMON_WORKER_SUPERVISOR_SOCKET_ENV] = previousSupervisorSocket;
}
+ for (const socket of sockets) socket.destroy();
await new Promise((resolve) => server.close(() => resolve()));
rmSync(tempDir, { recursive: true, force: true });
}
diff --git a/packages/coding-agent/test/daemon-multiclient-bench.ts b/packages/coding-agent/test/daemon-multiclient-bench.ts
index 998e4b48c1..91a5bf1822 100644
--- a/packages/coding-agent/test/daemon-multiclient-bench.ts
+++ b/packages/coding-agent/test/daemon-multiclient-bench.ts
@@ -7,10 +7,10 @@
*
* Run from packages/coding-agent:
*
- * npx tsx test/daemon-multiclient-bench.ts
- * npx tsx test/daemon-multiclient-bench.ts --session-file /path/to/session.jsonl
- * npx tsx test/daemon-multiclient-bench.ts --generated-session-mib 100
- * npx tsx test/daemon-multiclient-bench.ts --generated-session-mib 500
+ * bun test/daemon-multiclient-bench.ts
+ * bun test/daemon-multiclient-bench.ts --session-file /path/to/session.jsonl
+ * bun test/daemon-multiclient-bench.ts --generated-session-mib 100
+ * bun test/daemon-multiclient-bench.ts --generated-session-mib 500
*/
import { randomUUID } from "node:crypto";
import { createWriteStream } from "node:fs";
diff --git a/packages/coding-agent/test/daemon-ps.test.ts b/packages/coding-agent/test/daemon-ps.test.ts
index 6233a9353e..bf2dce7d09 100644
--- a/packages/coding-agent/test/daemon-ps.test.ts
+++ b/packages/coding-agent/test/daemon-ps.test.ts
@@ -19,7 +19,7 @@ import { getProcessStartId } from "../src/core/session-lease.js";
import { defaultDaemonSocketDir } from "../src/modes/daemon/daemon-socket.js";
describe("worker socket classification", () => {
- it.runIf(process.platform !== "win32")("recognizes only worker sockets in the default service directory", () => {
+ it.skipIf(process.platform === "win32")("recognizes only worker sockets in the default service directory", () => {
expect(isWorkerSocketPath(join(defaultDaemonSocketDir(), "worker-abc.sock"))).toBe(true);
expect(isWorkerSocketPath(join(defaultDaemonSocketDir(), "daemon.sock"))).toBe(false);
expect(isWorkerSocketPath("/tmp/worker-abc.sock")).toBe(false);
@@ -57,7 +57,9 @@ describe("parseSsListeners", () => {
describe("parseLsofListeners", () => {
it("pairs each pid with its listening unix socket paths", () => {
- const stdout = ["p1234", "fu", "n/tmp/a.sock", "p5678", "n/tmp/b.sock", "n0x0 (not a path)", ""].join("\n");
+ const stdout = ["p1234", "fu", "n/tmp/a.sock type=STREAM", "p5678", "n/tmp/b.sock", "n0x0 (not a path)", ""].join(
+ "\n",
+ );
expect(parseLsofListeners(stdout)).toEqual([
{ pid: 1234, socketPath: "/tmp/a.sock" },
{ pid: 5678, socketPath: "/tmp/b.sock" },
diff --git a/packages/coding-agent/test/daemon-socket.test.ts b/packages/coding-agent/test/daemon-socket.test.ts
index 8237d68f23..d36c22746d 100644
--- a/packages/coding-agent/test/daemon-socket.test.ts
+++ b/packages/coding-agent/test/daemon-socket.test.ts
@@ -9,11 +9,37 @@ import {
cleanupDaemonSocketPath,
DaemonSocketPathLease,
defaultDaemonSocketPath,
+ endDaemonSocketAfterFlush,
getDaemonSocketIdentity,
normalizeSocketPath,
prepareDaemonSocketPath,
+ windowsNamedPipeUserScope,
} from "../src/modes/daemon/daemon-socket.js";
+describe("endDaemonSocketAfterFlush", () => {
+ it("delivers queued bytes before closing the socket", async () => {
+ const server = createServer((socket) => {
+ socket.write("daemon_closing\n");
+ endDaemonSocketAfterFlush(socket);
+ });
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
+ const address = server.address();
+ if (!address || typeof address === "string") throw new Error("Expected TCP server address");
+ const received = await new Promise((resolve, reject) => {
+ let data = "";
+ const socket = createConnection({ host: "127.0.0.1", port: address.port });
+ socket.setEncoding("utf8");
+ socket.on("data", (chunk) => {
+ data += chunk;
+ });
+ socket.on("end", () => resolve(data));
+ socket.on("error", reject);
+ });
+ await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
+ expect(received).toBe("daemon_closing\n");
+ });
+});
+
describe("normalizeSocketPath", () => {
it("normalizes equivalent Unix spellings", () => {
if (process.platform === "win32") return;
@@ -22,12 +48,31 @@ describe("normalizeSocketPath", () => {
});
describe("defaultDaemonSocketPath", () => {
- it("uses a fixed Windows named pipe path", () => {
+ it("uses a per-user Windows named pipe path", () => {
if (process.platform !== "win32") {
return;
}
- expect(defaultDaemonSocketPath()).toBe("\\\\.\\pipe\\prime-agent-daemon");
+ expect(defaultDaemonSocketPath()).toBe(`\\\\.\\pipe\\prime-agent-daemon-${windowsNamedPipeUserScope()}`);
+ });
+
+ it("uses a stable opaque Windows user scope", () => {
+ expect(windowsNamedPipeUserScope()).toMatch(/^[0-9a-f]{16}$/);
+ expect(windowsNamedPipeUserScope()).toBe(windowsNamedPipeUserScope());
+ });
+
+ it("detects a live Windows named pipe", async () => {
+ if (process.platform !== "win32") return;
+ const pipePath = `\\\\.\\pipe\\prime-agent-test-${process.pid}-${Date.now()}`;
+ const server = createServer();
+ await new Promise((resolveListen) => server.listen(pipePath, resolveListen));
+ try {
+ await expect(prepareDaemonSocketPath(pipePath)).rejects.toThrow("already in use");
+ } finally {
+ await new Promise((resolveClose, rejectClose) =>
+ server.close((error) => (error ? rejectClose(error) : resolveClose())),
+ );
+ }
});
it("uses a per-user Unix socket directory", () => {
diff --git a/packages/coding-agent/test/daemon-supervisor-lazy-subagents.test.ts b/packages/coding-agent/test/daemon-supervisor-lazy-subagents.test.ts
index becf9a98f3..7d6a030634 100644
--- a/packages/coding-agent/test/daemon-supervisor-lazy-subagents.test.ts
+++ b/packages/coding-agent/test/daemon-supervisor-lazy-subagents.test.ts
@@ -19,6 +19,9 @@ interface SupervisorInternals {
workers: Map;
start(): Promise;
cleanupSupervisorResources(): Promise;
+ clearIdleEvictionTimer(): void;
+ clearRosterWatchdogTimer(): void;
+ catalog: { stop?: () => Promise };
refreshWorkerSummaries(worker: WorkerFixture): Promise;
findSummaryInWorker(worker: WorkerFixture, selector: string): SessionSummary | undefined;
createOrReuseWorker(
@@ -55,11 +58,33 @@ interface WorkerFixture {
}
const tempDirs: string[] = [];
-
-afterEach(() => {
+const supervisors: SupervisorInternals[] = [];
+
+afterEach(async () => {
+ for (const supervisor of supervisors.splice(0)) {
+ supervisor.clearIdleEvictionTimer();
+ supervisor.clearRosterWatchdogTimer();
+ await supervisor.catalog.stop?.();
+ }
for (const directory of tempDirs.splice(0)) rmSync(directory, { recursive: true, force: true });
});
+function createSupervisor(directory: string): SupervisorInternals {
+ const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
+ defaultSessionConfig: { agentDir: directory, cwd: directory },
+ descriptorDir: join(directory, "workers"),
+ }) as unknown as SupervisorInternals;
+ supervisors.push(supervisor);
+ return supervisor;
+}
+
+function capturePromise(promise: Promise): Promise<{ value?: T; error?: unknown }> {
+ return promise.then(
+ (value) => ({ value }),
+ (error: unknown) => ({ error }),
+ );
+}
+
function summary(overrides: Partial & Pick): SessionSummary {
return {
lifecycle: "live",
@@ -98,10 +123,7 @@ describe("daemon supervisor passive subagent topology", () => {
it("finds a child summary by its displayed session ID suffix", () => {
const directory = mkdtempSync(join(tmpdir(), "prime-supervisor-child-suffix-"));
tempDirs.push(directory);
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const child = summary({
id: "bbbb6666777788889999cccc",
activeSessionId: "bbbb6666777788889999cccc",
@@ -116,10 +138,7 @@ describe("daemon supervisor passive subagent topology", () => {
it("rejects an explicit root name that collides with a saved root", async () => {
const directory = mkdtempSync(join(tmpdir(), "prime-supervisor-root-name-"));
tempDirs.push(directory);
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const launchWorker = vi.fn();
Object.assign(supervisor, {
catalog: {
@@ -160,10 +179,7 @@ describe("daemon supervisor passive subagent topology", () => {
if (!forkedPath) throw new Error("Missing forked session path");
const forkedInfo = await readSessionInfo(forkedPath);
if (!forkedInfo) throw new Error("Missing forked session info");
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
Object.assign(supervisor, {
catalog: {
siblings: vi.fn(async () => [forkedInfo]),
@@ -193,10 +209,7 @@ describe("daemon supervisor passive subagent topology", () => {
it("normalizes explicit root names before supervisor validation and launch", async () => {
const directory = mkdtempSync(join(tmpdir(), "prime-supervisor-normalized-root-name-"));
tempDirs.push(directory);
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const launchWorker = vi.fn();
Object.assign(supervisor, {
catalog: {
@@ -243,10 +256,7 @@ describe("daemon supervisor passive subagent topology", () => {
allMessagesText: "",
};
const duplicate = { ...target, id: "duplicate", path: duplicatePath, name: "taken" };
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
Object.assign(supervisor, {
rlmLedgerSiblings: vi.fn(async () => [target]),
catalog: {
@@ -263,10 +273,7 @@ describe("daemon supervisor passive subagent topology", () => {
it("retains a legacy child's parent edge when its depth is unknown", () => {
const directory = mkdtempSync(join(tmpdir(), "prime-supervisor-legacy-family-"));
tempDirs.push(directory);
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const parentPath = join(directory, "parent.jsonl");
const child = supervisor.familyCatalogEntry(
summary({
@@ -312,10 +319,7 @@ describe("daemon supervisor passive subagent topology", () => {
};
const target = { ...base, id: "target", path: join(directory, "target.jsonl"), parentSessionPath, rlmDepth: 1 };
const legacy = { ...base, id: "legacy", path: join(directory, "legacy.jsonl"), parentSessionPath, name: "taken" };
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
expect(() => supervisor.assertSavedSiblingNameAvailable([target, legacy], target, "taken")).toThrow(
"an agent of that name already exists at depth 1 under this parent",
@@ -330,10 +334,7 @@ describe("daemon supervisor passive subagent topology", () => {
const siblingGate = new Promise((resolve) => {
releaseSiblings = resolve;
});
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const resident = worker("opened");
const launchWorker = vi.fn(async () => resident);
Object.assign(supervisor, {
@@ -367,10 +368,7 @@ describe("daemon supervisor passive subagent topology", () => {
const launchGate = new Promise((resolve) => {
releaseLaunch = resolve;
});
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const resident = worker("opened");
resident.descriptor.ownerClientId = "owner";
const launchWorker = vi.fn(async () => {
@@ -383,13 +381,13 @@ describe("daemon supervisor passive subagent topology", () => {
const first = supervisor.createOrReuseWorker("owner", create);
const sameOwner = supervisor.createOrReuseWorker("owner", create);
const otherClient = supervisor.createOrReuseWorker("intruder", create);
- const expectations = Promise.all([
- expect(first).resolves.toBe(resident),
- expect(sameOwner).resolves.toBe(resident),
- expect(otherClient).rejects.toMatchObject({ code: "session_already_active" }),
- ]);
+ const firstResult = capturePromise(first);
+ const sameOwnerResult = capturePromise(sameOwner);
+ const otherClientResult = capturePromise(otherClient);
releaseLaunch();
- await expectations;
+ expect(await firstResult).toEqual({ value: resident });
+ expect(await sameOwnerResult).toEqual({ value: resident });
+ expect((await otherClientResult).error).toHaveProperty("code", "session_already_active");
expect(launchWorker).toHaveBeenCalledOnce();
});
@@ -401,10 +399,7 @@ describe("daemon supervisor passive subagent topology", () => {
const reclaimGate = new Promise((resolve) => {
releaseReclaim = resolve;
});
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const stale = worker("stale");
stale.descriptor.createCommand = { config: { cwd: directory }, sessionPath };
supervisor.workers.set(stale.descriptor.workerId, stale);
@@ -422,13 +417,13 @@ describe("daemon supervisor passive subagent topology", () => {
const first = supervisor.createOrReuseWorker("owner", create);
const second = supervisor.createOrReuseWorker("owner", create);
const intruder = supervisor.createOrReuseWorker("intruder", create);
- const expectations = Promise.all([
- expect(first).resolves.toBe(resident),
- expect(second).resolves.toBe(resident),
- expect(intruder).rejects.toMatchObject({ code: "session_already_active" }),
- ]);
+ const firstResult = capturePromise(first);
+ const secondResult = capturePromise(second);
+ const intruderResult = capturePromise(intruder);
releaseReclaim();
- await expectations;
+ expect(await firstResult).toEqual({ value: resident });
+ expect(await secondResult).toEqual({ value: resident });
+ expect((await intruderResult).error).toHaveProperty("code", "session_already_active");
expect(launchWorker).toHaveBeenCalledOnce();
});
@@ -440,10 +435,7 @@ describe("daemon supervisor passive subagent topology", () => {
const launchGate = new Promise((resolve) => {
releaseLaunch = resolve;
});
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
const launchWorker = vi.fn(async () => {
await launchGate;
throw new Error("launch exploded");
@@ -453,12 +445,13 @@ describe("daemon supervisor passive subagent topology", () => {
const create = { type: "create" as const, sessionPath, lifecycle: "client_owned" as const };
const first = supervisor.createOrReuseWorker("owner", create);
const joiner = supervisor.createOrReuseWorker("intruder", create);
- const expectations = Promise.all([
- expect(first).rejects.toThrow("launch exploded"),
- expect(joiner).rejects.toThrow("launch exploded"),
- ]);
+ const firstResult = capturePromise(first);
+ const joinerResult = capturePromise(joiner);
releaseLaunch();
- await expectations;
+ expect((await firstResult).error).toBeInstanceOf(Error);
+ expect((await firstResult).error).toHaveProperty("message", "launch exploded");
+ expect((await joinerResult).error).toBeInstanceOf(Error);
+ expect((await joinerResult).error).toHaveProperty("message", "launch exploded");
});
it("uses injective structural session name reservation keys", () => {
@@ -497,10 +490,7 @@ describe("daemon supervisor passive subagent topology", () => {
});
const secondWorker = worker("second", [secondSummary]);
secondWorker.client.request.mockResolvedValue(success(undefined, "rename", secondSummary));
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
supervisor.workers.set("first", firstWorker);
supervisor.workers.set("second", secondWorker);
seedSupervisorRoster(supervisor, firstWorker, secondWorker);
@@ -542,10 +532,7 @@ describe("daemon supervisor passive subagent topology", () => {
const ownedWorker = worker("owned", [ownedSummary]);
ownedWorker.descriptor.ownerClientId = "interactive-client";
ownedWorker.client.request.mockResolvedValue(success(undefined, "set_session_name"));
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
supervisor.workers.set("owned", ownedWorker);
seedSupervisorRoster(supervisor, ownedWorker);
Object.assign(supervisor, { catalog: { list: vi.fn(async () => []) } });
@@ -614,10 +601,7 @@ describe("daemon supervisor passive subagent topology", () => {
});
const secondWorker = worker("second", [secondSummary]);
secondWorker.client.request.mockResolvedValue(success(undefined, "rename_saved_session", secondSummary));
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
supervisor.workers.set("first", firstWorker);
supervisor.workers.set("second", secondWorker);
seedSupervisorRoster(supervisor, firstWorker, secondWorker);
@@ -677,10 +661,7 @@ describe("daemon supervisor passive subagent topology", () => {
releaseRename = resolve;
});
const rename = vi.fn(async () => renameGate);
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
Object.assign(supervisor, {
rlmLedgerSiblings: vi.fn(async () => saved),
rlmSpawnLedger: vi.fn(() => ({ appendRenameByChildPath: vi.fn(async () => {}) })),
@@ -734,10 +715,7 @@ describe("daemon supervisor passive subagent topology", () => {
await launchGate;
return launched;
});
- const supervisor = new DaemonSupervisor(join(directory, "daemon.sock"), {
- defaultSessionConfig: { agentDir: directory, cwd: directory },
- descriptorDir: join(directory, "workers"),
- }) as unknown as SupervisorInternals;
+ const supervisor = createSupervisor(directory);
Object.assign(supervisor, {
rlmLedgerSiblings: vi.fn(async (path: string) => [path === firstChild.path ? firstChild : secondChild]),
catalog: {
diff --git a/packages/coding-agent/test/daemon-supervisor-monitor.test.ts b/packages/coding-agent/test/daemon-supervisor-monitor.test.ts
index 8d6954ffde..e017e6f195 100644
--- a/packages/coding-agent/test/daemon-supervisor-monitor.test.ts
+++ b/packages/coding-agent/test/daemon-supervisor-monitor.test.ts
@@ -1,6 +1,7 @@
import type { ChildProcess, SpawnOptions } from "node:child_process";
import { EventEmitter } from "node:events";
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { createRequire } from "node:module";
import type { Socket } from "node:net";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -50,96 +51,92 @@ const workerLaunchTestState = vi.hoisted(() => ({
spawned: [] as Array<{ child: ChildProcess; args: readonly string[] }>,
}));
-vi.mock("node:child_process", async (importOriginal) => {
- const actual = (await importOriginal()) as Record & {
- spawn(command: string, args: readonly string[], options: SpawnOptions): ChildProcess;
- };
- return {
- ...actual,
- spawn(command: string, args: readonly string[], options: SpawnOptions): ChildProcess {
- const failureCode = workerLaunchTestState.spawnFailureCode;
- if (failureCode) {
- // Node's failed-spawn shape: no pid, stdio undefined, "error" then "close".
- const failing = Object.assign(new EventEmitter(), {
- pid: undefined,
- stdio: undefined,
- stderr: undefined,
- unref: () => {},
- }) as unknown as ChildProcess;
- process.nextTick(() => {
- failing.emit(
- "error",
- Object.assign(new Error(`spawn ${command} ${failureCode}`), { code: failureCode }),
- );
- failing.emit("close", null, null);
- });
- return failing;
- }
- const child = actual.spawn(command, args, options);
- if (workerLaunchTestState.capture) {
- workerLaunchTestState.spawned.push({ child, args });
- }
- return child;
- },
- };
-});
+const __childProcess = createRequire(import.meta.url)("node:child_process") as Record & {
+ spawn(command: string, args: readonly string[], options: SpawnOptions): ChildProcess;
+};
+const spawnChildProcess = __childProcess.spawn;
+vi.mock("node:child_process", () => ({
+ ...__childProcess,
+ spawn(command: string, args: readonly string[], options: SpawnOptions): ChildProcess {
+ const failureCode = workerLaunchTestState.spawnFailureCode;
+ if (failureCode) {
+ // Node's failed-spawn shape: no pid, stdio undefined, "error" then "close".
+ const failing = Object.assign(new EventEmitter(), {
+ pid: undefined,
+ stdio: undefined,
+ stderr: undefined,
+ unref: () => {},
+ }) as unknown as ChildProcess;
+ queueMicrotask(() => {
+ failing.emit("error", Object.assign(new Error(`spawn ${command} ${failureCode}`), { code: failureCode }));
+ failing.emit("close", null, null);
+ });
+ return failing;
+ }
+ const child = spawnChildProcess(command, args, options);
+ if (workerLaunchTestState.capture) {
+ workerLaunchTestState.spawned.push({ child, args });
+ }
+ return child;
+ },
+}));
-vi.mock("../src/cli/subprocess-launch.js", async (importOriginal) => {
- const actual = (await importOriginal()) as Record;
- return {
- ...actual,
- createCliSubprocessLaunchSpec(args: readonly string[]) {
- if (!workerLaunchTestState.capture) {
- return (actual.createCliSubprocessLaunchSpec as (args: readonly string[]) => unknown)(args);
- }
- if (workerLaunchTestState.fixtureMode === "rollback-gate") {
- const markerPath = JSON.stringify(workerLaunchTestState.gateMarkerPath);
- const commitMarker = JSON.stringify(DAEMON_WORKER_STARTUP_GATE_COMMIT);
- return {
- command: process.execPath,
- args: [
- "--eval",
- `const fs = require("node:fs"); const marker = fs.readFileSync(3, "utf8"); if (marker === ${commitMarker}) { fs.writeFileSync(${markerPath}, marker); setInterval(() => {}, 1000); }`,
- "--",
- ...args,
- ],
- };
- }
- if (workerLaunchTestState.fixtureMode === "close-gate") {
- return {
- command: process.execPath,
- args: ["--eval", 'require("node:fs").closeSync(3)'],
- };
- }
- if (workerLaunchTestState.fixtureMode === "successful-gate") {
- const markerPath = JSON.stringify(workerLaunchTestState.gateMarkerPath);
- return {
- command: process.execPath,
- args: [
- "--eval",
- `const fs = require("node:fs"); const marker = fs.readFileSync(3, "utf8"); fs.writeFileSync(${markerPath}, marker); setInterval(() => {}, 1000);`,
- ],
- };
- }
+const __subprocessLaunch = createRequire(import.meta.url)("../src/cli/subprocess-launch.js") as Record;
+const createSubprocessLaunchSpec = __subprocessLaunch.createCliSubprocessLaunchSpec as (
+ args: readonly string[],
+) => unknown;
+vi.mock("../src/cli/subprocess-launch.js", () => ({
+ ...__subprocessLaunch,
+ createCliSubprocessLaunchSpec(args: readonly string[]) {
+ if (!workerLaunchTestState.capture) {
+ return createSubprocessLaunchSpec(args);
+ }
+ if (workerLaunchTestState.fixtureMode === "rollback-gate") {
+ const markerPath = JSON.stringify(workerLaunchTestState.gateMarkerPath);
+ const commitMarker = JSON.stringify(DAEMON_WORKER_STARTUP_GATE_COMMIT);
return {
command: process.execPath,
- args: [workerLaunchTestState.tsxCliPath, workerLaunchTestState.cliEntrypoint, ...args],
+ args: [
+ "--eval",
+ `const fs = require("node:fs"); const marker = fs.readFileSync(3, "utf8"); if (marker === ${commitMarker}) { fs.writeFileSync(${markerPath}, marker); setInterval(() => {}, 1000); }`,
+ "--",
+ ...args,
+ ],
};
- },
- };
-});
+ }
+ if (workerLaunchTestState.fixtureMode === "close-gate") {
+ return {
+ command: process.execPath,
+ args: ["--eval", 'require("node:fs").closeSync(3); process.reallyExit(0)'],
+ };
+ }
+ if (workerLaunchTestState.fixtureMode === "successful-gate") {
+ const markerPath = JSON.stringify(workerLaunchTestState.gateMarkerPath);
+ return {
+ command: process.execPath,
+ args: [
+ "--eval",
+ `const fs = require("node:fs"); const marker = fs.readFileSync(3, "utf8"); fs.writeFileSync(${markerPath}, marker); setInterval(() => {}, 1000);`,
+ ],
+ };
+ }
+ return {
+ command: process.execPath,
+ args: [workerLaunchTestState.tsxCliPath, workerLaunchTestState.cliEntrypoint, ...args],
+ };
+ },
+}));
-vi.mock("../src/core/session-lease.js", async (importOriginal) => {
- const actual = (await importOriginal()) as Record & {
- getProcessStartId(pid: number): string | undefined;
- };
- return {
- ...actual,
- getProcessStartId(pid: number): string | undefined {
- return workerLaunchTestState.forceMissingProcessStartId ? undefined : actual.getProcessStartId(pid);
- },
- };
-});
+const __sessionLease = createRequire(import.meta.url)("../src/core/session-lease.js") as Record & {
+ getProcessStartId(pid: number): string | undefined;
+};
+const readProcessStartId = __sessionLease.getProcessStartId;
+vi.mock("../src/core/session-lease.js", () => ({
+ ...__sessionLease,
+ getProcessStartId(pid: number): string | undefined {
+ return workerLaunchTestState.forceMissingProcessStartId ? undefined : readProcessStartId(pid);
+ },
+}));
const supervisorRegistryDirEnv = "PRIME_AGENT_INTERNAL_DAEMON_SUPERVISOR_REGISTRY_DIR";
const previousSupervisorRegistryDir = process.env[supervisorRegistryDirEnv];
@@ -699,9 +696,12 @@ describe("daemon worker supervisor monitoring", () => {
launchWorker(command: { type: "create"; config: { cwd: string; agentDir: string } }): Promise;
};
- await expect(supervisor.launchWorker({ type: "create", config: { cwd: root, agentDir: root } })).rejects.toThrow(
- /EMFILE.*resident session workers.*ulimit -n/s,
- );
+ const emfileFailure = await supervisor
+ .launchWorker({ type: "create", config: { cwd: root, agentDir: root } })
+ .then(() => undefined)
+ .catch((error: Error) => error);
+ expect(emfileFailure).toBeInstanceOf(Error);
+ expect(emfileFailure?.message).toMatch(/EMFILE.*resident session workers.*ulimit -n/s);
expect(workers.size).toBe(0);
workerLaunchTestState.spawnFailureCode = "ENOENT";
diff --git a/packages/coding-agent/test/daemon-supervisor-process.test.ts b/packages/coding-agent/test/daemon-supervisor-process.test.ts
index 765a9edc26..7fb14bc803 100644
--- a/packages/coding-agent/test/daemon-supervisor-process.test.ts
+++ b/packages/coding-agent/test/daemon-supervisor-process.test.ts
@@ -23,9 +23,9 @@ import {
isDaemonWorkerFrameHeader,
} from "../src/modes/daemon/daemon-worker-protocol.js";
import { encodePrivateFrame, PrivateFrameDecoder } from "../src/modes/session-worker/private-framing.js";
+import { isTestTagEnabled } from "./test-tags.js";
const cliPath = resolve(__dirname, "../src/cli.ts");
-const tsxPath = resolve(__dirname, "../../../node_modules/tsx/dist/cli.mjs");
const blockingProcessPath = resolve(__dirname, "fixtures/blocking-process.mjs");
const tempDirs: string[] = [];
const children = new Set();
@@ -33,6 +33,7 @@ const workerPids = new Set();
const daemonSockets = new Set();
const childDiagnostics = new WeakMap();
const PROCESS_STRESS_WORKERS = Number.parseInt(process.env.PRIME_AGENT_STRESS_WORKERS ?? "10", 10);
+const processStressIt = isTestTagEnabled("process-stress") ? it : it.skip;
afterEach(async () => {
for (const socketPath of daemonSockets) {
@@ -94,7 +95,7 @@ function spawnSupervisor(
daemonSockets.add(socketPath);
const child = spawn(
process.execPath,
- [tsxPath, cliPath, "--mode", "daemon", "--daemon-socket", socketPath, "--offline", ...extraArgs],
+ [cliPath, "--mode", "daemon", "--daemon-socket", socketPath, "--offline", ...extraArgs],
{
cwd,
env: {
@@ -102,7 +103,6 @@ function spawnSupervisor(
...extraEnv,
[ENV_AGENT_DIR]: agentDir,
PI_OFFLINE: "1",
- TSX_TSCONFIG_PATH: resolve(__dirname, "../../../tsconfig.json"),
},
stdio: ["ignore", "pipe", "pipe"],
},
@@ -697,7 +697,7 @@ describe("daemon supervisor resident workers", () => {
type: "create",
lifecycle: "client_owned",
noSession: true,
- launchEnv: { TSX_TSCONFIG_PATH: resolve(__dirname, "../../../tsconfig.json") },
+ launchEnv: {},
config: { cwd: projectDir, agentDir, noTools: true, noExtensions: true },
});
if (!created.success) {
@@ -782,7 +782,7 @@ describe("daemon supervisor resident workers", () => {
await waitForSocketGone(socketPath);
}, 30_000);
- it("cancels an archived session heartbeat without spawning a worker", { tags: ["process-stress"] }, async () => {
+ processStressIt("cancels an archived session heartbeat without spawning a worker", async () => {
const root = tempDir();
const agentDir = join(root, "agent");
const projectDir = join(root, "project");
@@ -835,9 +835,7 @@ describe("daemon supervisor resident workers", () => {
await waitForSocketGone(socketPath);
});
- it("cancels an orphan heartbeat instead of recreating a descriptorless active session", {
- tags: ["process-stress"],
- }, async () => {
+ processStressIt("cancels an orphan heartbeat instead of recreating a descriptorless active session", async () => {
const root = tempDir();
const agentDir = join(root, "agent");
const projectDir = join(root, "project");
@@ -912,7 +910,7 @@ describe("daemon supervisor resident workers", () => {
await waitForSocketGone(socketPath);
});
- it("survives a worker process spawn error", { tags: ["process-stress"] }, async () => {
+ processStressIt("survives a worker process spawn error", async () => {
const root = tempDir();
const agentDir = join(root, "agent");
const projectDir = join(root, "project");
@@ -1008,233 +1006,244 @@ describe("daemon supervisor resident workers", () => {
await waitForSocketGone(socketPath);
}, 30_000);
- it("finalizes a timed-out worker stop by force-stopping the process and removing its registration", {
- tags: ["process-stress"],
- timeout: 45_000,
- }, async () => {
- const root = tempDir();
- const agentDir = join(root, "agent");
- const projectDir = join(root, "project");
- const sessionDir = join(agentDir, "sessions");
- const socketPath = join(
- tmpdir(),
- `prime-supervisor-stop-finalize-${process.pid}-${randomUUID().slice(0, 8)}.sock`,
- );
- mkdirSync(projectDir, { recursive: true });
- const sessionManager = SessionManager.create(projectDir, sessionDir);
- sessionManager.appendMessage({ role: "user", content: "finalize me", timestamp: 1 });
- const sessionFile = sessionManager.getSessionFile();
- if (!sessionFile) {
- throw new Error("Fixture session did not persist");
- }
-
- const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
- const client = await connectEventually(socketPath, supervisor);
- const created = await client.request({
- type: "create",
- sessionPath: sessionFile,
- lifecycle: "client_owned",
- config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
- });
- if (!created.success) {
- throw new Error(created.error);
- }
- const summary = requireSummary(created.data);
- if (!summary.workerPid) {
- throw new Error("Resident worker did not expose its pid");
- }
- workerPids.add(summary.workerPid);
- const activeSessionId = summary.activeSessionId ?? summary.id;
-
- // A suspended worker cannot exit within the stop deadline, so the stop
- // times out and used to leave a tombstoned registration behind forever.
- process.kill(summary.workerPid, "SIGSTOP");
- const stopResult = await client.request({ type: "complete_owned_session", activeSessionId }, 30_000);
- expect(stopResult).toMatchObject({
- success: false,
- error: expect.stringContaining("did not stop"),
- });
- const tombstone = readWorkerDescriptor(agentDir);
- expect(tombstone.stopRequestedAt).toEqual(expect.any(String));
-
- // The supervisor finishes the interrupted stop on its own: it escalates
- // to SIGKILL, waits for the process to die, and removes the registration.
- await waitForProcessGone(summary.workerPid);
- workerPids.delete(summary.workerPid);
- await waitForCondition(
- () => countWorkerDescriptors(agentDir) === 0,
- "Timed-out worker stop was not finalized",
- 20_000,
- );
-
- await client.request({ type: "shutdown" });
- client.close();
- await waitForSocketGone(socketPath);
- });
-
- it("resumes a saved session immediately after a worker stop fails and the process dies", {
- tags: ["process-stress"],
- timeout: 45_000,
- }, async () => {
- const root = tempDir();
- const agentDir = join(root, "agent");
- const projectDir = join(root, "project");
- const sessionDir = join(agentDir, "sessions");
- const socketPath = join(tmpdir(), `prime-supervisor-resume-heal-${process.pid}-${randomUUID().slice(0, 8)}.sock`);
- mkdirSync(projectDir, { recursive: true });
- const sessionManager = SessionManager.create(projectDir, sessionDir);
- sessionManager.appendMessage({ role: "user", content: "resume me", timestamp: 1 });
- const sessionFile = sessionManager.getSessionFile();
- if (!sessionFile) {
- throw new Error("Fixture session did not persist");
- }
-
- const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
- const client = await connectEventually(socketPath, supervisor);
- const created = await client.request({
- type: "create",
- sessionPath: sessionFile,
- lifecycle: "client_owned",
- config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
- });
- if (!created.success) {
- throw new Error(created.error);
- }
- const summary = requireSummary(created.data);
- if (!summary.workerPid) {
- throw new Error("Resident worker did not expose its pid");
- }
- workerPids.add(summary.workerPid);
- const activeSessionId = summary.activeSessionId ?? summary.id;
+ processStressIt(
+ "finalizes a timed-out worker stop by force-stopping the process and removing its registration",
+ { timeout: 45_000 },
+ async () => {
+ const root = tempDir();
+ const agentDir = join(root, "agent");
+ const projectDir = join(root, "project");
+ const sessionDir = join(agentDir, "sessions");
+ const socketPath = join(
+ tmpdir(),
+ `prime-supervisor-stop-finalize-${process.pid}-${randomUUID().slice(0, 8)}.sock`,
+ );
+ mkdirSync(projectDir, { recursive: true });
+ const sessionManager = SessionManager.create(projectDir, sessionDir);
+ sessionManager.appendMessage({ role: "user", content: "finalize me", timestamp: 1 });
+ const sessionFile = sessionManager.getSessionFile();
+ if (!sessionFile) {
+ throw new Error("Fixture session did not persist");
+ }
- // A suspended worker forces the stop past its deadline, leaving a
- // tombstoned registration for a process that dies moments later.
- process.kill(summary.workerPid, "SIGSTOP");
- const stopResult = await client.request({ type: "complete_owned_session", activeSessionId }, 30_000);
- expect(stopResult).toMatchObject({
- success: false,
- error: expect.stringContaining("did not stop"),
- });
- process.kill(summary.workerPid, "SIGKILL");
- await waitForProcessGone(summary.workerPid);
- workerPids.delete(summary.workerPid);
+ const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
+ const client = await connectEventually(socketPath, supervisor);
+ const created = await client.request({
+ type: "create",
+ sessionPath: sessionFile,
+ lifecycle: "client_owned",
+ config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
+ });
+ if (!created.success) {
+ throw new Error(created.error);
+ }
+ const summary = requireSummary(created.data);
+ if (!summary.workerPid) {
+ throw new Error("Resident worker did not expose its pid");
+ }
+ workerPids.add(summary.workerPid);
+ const activeSessionId = summary.activeSessionId ?? summary.id;
+
+ // A suspended worker cannot exit within the stop deadline, so the stop
+ // times out and used to leave a tombstoned registration behind forever.
+ process.kill(summary.workerPid, "SIGSTOP");
+ const stopResult = await client.request({ type: "complete_owned_session", activeSessionId }, 30_000);
+ expect(stopResult).toMatchObject({
+ success: false,
+ error: expect.stringContaining("did not stop"),
+ });
+ const tombstone = readWorkerDescriptor(agentDir);
+ expect(tombstone.stopRequestedAt).toEqual(expect.any(String));
+
+ // The supervisor finishes the interrupted stop on its own: it escalates
+ // to SIGKILL, waits for the process to die, and removes the registration.
+ await waitForProcessGone(summary.workerPid);
+ workerPids.delete(summary.workerPid);
+ await waitForCondition(
+ () => countWorkerDescriptors(agentDir) === 0,
+ "Timed-out worker stop was not finalized",
+ 20_000,
+ );
- // Resuming the saved transcript must not be blocked by the stale
- // registration. Whichever cleanup wins the race — the background stop
- // finalizer or the resume-time reclaim (each covered deterministically
- // by unit tests) — the user-visible guarantee is the same: the resume
- // below must succeed with a fresh worker.
- const resumed = await client.request({
- type: "create",
- sessionPath: sessionFile,
- config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
- });
- expect(resumed.success).toBe(true);
- const resumedSummary = requireSummary(resumed.success ? resumed.data : undefined);
- expect(resumedSummary.sessionId).toBe(summary.sessionId);
- expect(resumedSummary.workerPid).not.toBe(summary.workerPid);
- expect(resumedSummary.workerState).toBe("ready");
- if (resumedSummary.workerPid) {
- workerPids.add(resumedSummary.workerPid);
- }
+ await client.request({ type: "shutdown" });
+ client.close();
+ await waitForSocketGone(socketPath);
+ },
+ );
- const attached = await client.request({
- type: "attach",
- activeSessionId: resumedSummary.activeSessionId ?? resumedSummary.id,
- });
- expect(attached.success).toBe(true);
+ processStressIt(
+ "resumes a saved session immediately after a worker stop fails and the process dies",
+ { timeout: 45_000 },
+ async () => {
+ const root = tempDir();
+ const agentDir = join(root, "agent");
+ const projectDir = join(root, "project");
+ const sessionDir = join(agentDir, "sessions");
+ const socketPath = join(
+ tmpdir(),
+ `prime-supervisor-resume-heal-${process.pid}-${randomUUID().slice(0, 8)}.sock`,
+ );
+ mkdirSync(projectDir, { recursive: true });
+ const sessionManager = SessionManager.create(projectDir, sessionDir);
+ sessionManager.appendMessage({ role: "user", content: "resume me", timestamp: 1 });
+ const sessionFile = sessionManager.getSessionFile();
+ if (!sessionFile) {
+ throw new Error("Fixture session did not persist");
+ }
- await client.request({ type: "shutdown" });
- client.close();
- await waitForSocketGone(socketPath);
- if (resumedSummary.workerPid) {
- await waitForProcessGone(resumedSummary.workerPid);
- workerPids.delete(resumedSummary.workerPid);
- }
- });
+ const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
+ const client = await connectEventually(socketPath, supervisor);
+ const created = await client.request({
+ type: "create",
+ sessionPath: sessionFile,
+ lifecycle: "client_owned",
+ config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
+ });
+ if (!created.success) {
+ throw new Error(created.error);
+ }
+ const summary = requireSummary(created.data);
+ if (!summary.workerPid) {
+ throw new Error("Resident worker did not expose its pid");
+ }
+ workerPids.add(summary.workerPid);
+ const activeSessionId = summary.activeSessionId ?? summary.id;
+
+ // A suspended worker forces the stop past its deadline, leaving a
+ // tombstoned registration for a process that dies moments later.
+ process.kill(summary.workerPid, "SIGSTOP");
+ const stopResult = await client.request({ type: "complete_owned_session", activeSessionId }, 30_000);
+ expect(stopResult).toMatchObject({
+ success: false,
+ error: expect.stringContaining("did not stop"),
+ });
+ process.kill(summary.workerPid, "SIGKILL");
+ await waitForProcessGone(summary.workerPid);
+ workerPids.delete(summary.workerPid);
+
+ // Resuming the saved transcript must not be blocked by the stale
+ // registration. Whichever cleanup wins the race — the background stop
+ // finalizer or the resume-time reclaim (each covered deterministically
+ // by unit tests) — the user-visible guarantee is the same: the resume
+ // below must succeed with a fresh worker.
+ const resumed = await client.request({
+ type: "create",
+ sessionPath: sessionFile,
+ config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
+ });
+ expect(resumed.success).toBe(true);
+ const resumedSummary = requireSummary(resumed.success ? resumed.data : undefined);
+ expect(resumedSummary.sessionId).toBe(summary.sessionId);
+ expect(resumedSummary.workerPid).not.toBe(summary.workerPid);
+ expect(resumedSummary.workerState).toBe("ready");
+ if (resumedSummary.workerPid) {
+ workerPids.add(resumedSummary.workerPid);
+ }
- it("does not resurrect an intentionally stopped root when the supervisor dies during kill", {
- tags: ["process-stress"],
- timeout: 30_000,
- }, async () => {
- const root = tempDir();
- const agentDir = join(root, "agent");
- const projectDir = join(root, "project");
- const sessionDir = join(agentDir, "sessions");
- const socketPath = join(tmpdir(), `prime-supervisor-stop-race-${process.pid}-${randomUUID().slice(0, 8)}.sock`);
- mkdirSync(projectDir, { recursive: true });
- const sessionManager = SessionManager.create(projectDir, sessionDir);
- sessionManager.appendMessage({ role: "user", content: "stop me", timestamp: 1 });
- sessionManager.appendSessionState({ status: "active" });
- const sessionFile = sessionManager.getSessionFile();
- if (!sessionFile) {
- throw new Error("Fixture session did not persist");
- }
+ const attached = await client.request({
+ type: "attach",
+ activeSessionId: resumedSummary.activeSessionId ?? resumedSummary.id,
+ });
+ expect(attached.success).toBe(true);
- const firstSupervisor = spawnSupervisor(agentDir, socketPath, projectDir);
- const client = await connectEventually(socketPath, firstSupervisor);
- const firstSupervisorPid = client.hello?.supervisorPid;
- if (!firstSupervisorPid) {
- throw new Error("Daemon hello did not expose its supervisor pid");
- }
- const created = await client.request({
- type: "create",
- sessionPath: sessionFile,
- config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
- });
- if (!created.success) {
- throw new Error(created.error);
- }
- const summary = requireSummary(created.data);
- if (!summary.workerPid) {
- throw new Error("Resident worker did not expose its pid");
- }
- workerPids.add(summary.workerPid);
- const activeSessionId = summary.activeSessionId ?? summary.id;
- const heartbeatResponse = await client.request({
- type: "heartbeat_set",
- activeSessionId,
- schedule: "every 1h",
- prompt: "continue old work",
- });
- if (!heartbeatResponse.success || !heartbeatResponse.data || typeof heartbeatResponse.data !== "object") {
- throw new Error(heartbeatResponse.success ? "Heartbeat response was missing data" : heartbeatResponse.error);
- }
- const heartbeat = (heartbeatResponse.data as { heartbeat: { id: string } }).heartbeat;
- const cronStore = AgentCronJobStore.forSessionArtifacts();
- cronStore.registerSessionArtifact(summary.sessionId, sessionManager.getSessionArtifactDir()!);
- process.kill(summary.workerPid, "SIGSTOP");
- const killResult = client.request({ type: "kill", activeSessionId }).catch((error: unknown) => error);
- const tombstone = await waitForWorkerStopTombstone(agentDir);
- expect(tombstone.stopRequestedAt).toEqual(expect.any(String));
- expect(tombstone.archiveOnStop).toBe(true);
+ await client.request({ type: "shutdown" });
+ client.close();
+ await waitForSocketGone(socketPath);
+ if (resumedSummary.workerPid) {
+ await waitForProcessGone(resumedSummary.workerPid);
+ workerPids.delete(resumedSummary.workerPid);
+ }
+ },
+ );
- process.kill(firstSupervisorPid, "SIGKILL");
- await waitForExit(firstSupervisor);
- children.delete(firstSupervisor);
- client.close();
- await expect(killResult).resolves.toBeInstanceOf(Error);
+ processStressIt(
+ "does not resurrect an intentionally stopped root when the supervisor dies during kill",
+ { timeout: 30_000 },
+ async () => {
+ const root = tempDir();
+ const agentDir = join(root, "agent");
+ const projectDir = join(root, "project");
+ const sessionDir = join(agentDir, "sessions");
+ const socketPath = join(
+ tmpdir(),
+ `prime-supervisor-stop-race-${process.pid}-${randomUUID().slice(0, 8)}.sock`,
+ );
+ mkdirSync(projectDir, { recursive: true });
+ const sessionManager = SessionManager.create(projectDir, sessionDir);
+ sessionManager.appendMessage({ role: "user", content: "stop me", timestamp: 1 });
+ sessionManager.appendSessionState({ status: "active" });
+ const sessionFile = sessionManager.getSessionFile();
+ if (!sessionFile) {
+ throw new Error("Fixture session did not persist");
+ }
- const replacementSupervisor = spawnSupervisor(agentDir, socketPath, projectDir);
- const replacementClient = await connectEventually(socketPath, replacementSupervisor);
- const listed = await replacementClient.request({ type: "list" });
- expect(listed.success).toBe(true);
- const sessions = requireSessionList(listed.success ? listed.data : undefined);
- expect(sessions.filter((session) => session.activeSessionId || session.workerPid)).toEqual([]);
- await waitForProcessGone(summary.workerPid);
- workerPids.delete(summary.workerPid);
- await waitForCondition(
- () => countWorkerDescriptors(agentDir) === 0,
- "Intentional worker stop descriptor was not removed",
- );
- expect(countWorkerDescriptors(agentDir)).toBe(0);
- expect((await readSessionInfo(sessionFile))?.state).toEqual({ status: "archived" });
- expect(cronStore.list().find((job) => job.id === heartbeat.id)).toMatchObject({ status: "cancelled" });
+ const firstSupervisor = spawnSupervisor(agentDir, socketPath, projectDir);
+ const client = await connectEventually(socketPath, firstSupervisor);
+ const firstSupervisorPid = client.hello?.supervisorPid;
+ if (!firstSupervisorPid) {
+ throw new Error("Daemon hello did not expose its supervisor pid");
+ }
+ const created = await client.request({
+ type: "create",
+ sessionPath: sessionFile,
+ config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
+ });
+ if (!created.success) {
+ throw new Error(created.error);
+ }
+ const summary = requireSummary(created.data);
+ if (!summary.workerPid) {
+ throw new Error("Resident worker did not expose its pid");
+ }
+ workerPids.add(summary.workerPid);
+ const activeSessionId = summary.activeSessionId ?? summary.id;
+ const heartbeatResponse = await client.request({
+ type: "heartbeat_set",
+ activeSessionId,
+ schedule: "every 1h",
+ prompt: "continue old work",
+ });
+ if (!heartbeatResponse.success || !heartbeatResponse.data || typeof heartbeatResponse.data !== "object") {
+ throw new Error(
+ heartbeatResponse.success ? "Heartbeat response was missing data" : heartbeatResponse.error,
+ );
+ }
+ const heartbeat = (heartbeatResponse.data as { heartbeat: { id: string } }).heartbeat;
+ const cronStore = AgentCronJobStore.forSessionArtifacts();
+ cronStore.registerSessionArtifact(summary.sessionId, sessionManager.getSessionArtifactDir()!);
+ process.kill(summary.workerPid, "SIGSTOP");
+ const killResult = client.request({ type: "kill", activeSessionId }).catch((error: unknown) => error);
+ const tombstone = await waitForWorkerStopTombstone(agentDir);
+ expect(tombstone.stopRequestedAt).toEqual(expect.any(String));
+ expect(tombstone.archiveOnStop).toBe(true);
+
+ process.kill(firstSupervisorPid, "SIGKILL");
+ await waitForExit(firstSupervisor);
+ children.delete(firstSupervisor);
+ client.close();
+ await expect(killResult).resolves.toBeInstanceOf(Error);
+
+ const replacementSupervisor = spawnSupervisor(agentDir, socketPath, projectDir);
+ const replacementClient = await connectEventually(socketPath, replacementSupervisor);
+ const listed = await replacementClient.request({ type: "list" });
+ expect(listed.success).toBe(true);
+ const sessions = requireSessionList(listed.success ? listed.data : undefined);
+ expect(sessions.filter((session) => session.activeSessionId || session.workerPid)).toEqual([]);
+ await waitForProcessGone(summary.workerPid);
+ workerPids.delete(summary.workerPid);
+ await waitForCondition(
+ () => countWorkerDescriptors(agentDir) === 0,
+ "Intentional worker stop descriptor was not removed",
+ );
+ expect(countWorkerDescriptors(agentDir)).toBe(0);
+ expect((await readSessionInfo(sessionFile))?.state).toEqual({ status: "archived" });
+ expect(cronStore.list().find((job) => job.id === heartbeat.id)).toMatchObject({ status: "cancelled" });
- await replacementClient.request({ type: "shutdown" });
- replacementClient.close();
- await waitForSocketGone(socketPath);
- });
+ await replacementClient.request({ type: "shutdown" });
+ replacementClient.close();
+ await waitForSocketGone(socketPath);
+ },
+ );
it("hosts and adopts isolated worker processes", async () => {
const root = tempDir();
@@ -1307,142 +1316,146 @@ describe("daemon supervisor resident workers", () => {
);
}, 60_000);
- it("hosts resident roots in isolated worker processes without a session cap", {
- tags: ["process-stress"],
- timeout: 180_000,
- }, async () => {
- const root = tempDir();
- const agentDir = join(root, "agent");
- const projectDir = join(root, "project");
- const sessionDir = join(agentDir, "sessions");
- const socketPath = join(tmpdir(), `prime-supervisor-many-roots-${process.pid}-${randomUUID().slice(0, 8)}.sock`);
- mkdirSync(projectDir, { recursive: true });
- const sessionFiles = Array.from({ length: PROCESS_STRESS_WORKERS }, (_, index) => {
- const manager = SessionManager.create(projectDir, sessionDir);
- manager.appendMessage({ role: "user", content: `root ${index}`, timestamp: index + 1 });
- const sessionFile = manager.getSessionFile();
- if (!sessionFile) {
- throw new Error("Fixture session did not persist");
- }
- return sessionFile;
- });
+ processStressIt(
+ "hosts resident roots in isolated worker processes without a session cap",
+ { timeout: 180_000 },
+ async () => {
+ const root = tempDir();
+ const agentDir = join(root, "agent");
+ const projectDir = join(root, "project");
+ const sessionDir = join(agentDir, "sessions");
+ const socketPath = join(
+ tmpdir(),
+ `prime-supervisor-many-roots-${process.pid}-${randomUUID().slice(0, 8)}.sock`,
+ );
+ mkdirSync(projectDir, { recursive: true });
+ const sessionFiles = Array.from({ length: PROCESS_STRESS_WORKERS }, (_, index) => {
+ const manager = SessionManager.create(projectDir, sessionDir);
+ manager.appendMessage({ role: "user", content: `root ${index}`, timestamp: index + 1 });
+ const sessionFile = manager.getSessionFile();
+ if (!sessionFile) {
+ throw new Error("Fixture session did not persist");
+ }
+ return sessionFile;
+ });
- const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
- const client = await connectEventually(socketPath, supervisor);
- const externalLease = acquireSessionLease(sessionFiles[0], agentDir, {
- [SESSION_LEASES_ENABLED_ENV]: "1",
- [SESSION_LEASE_OWNER_ID_ENV]: "external-owner",
- });
- const conflict = await client.request({
- type: "create",
- sessionPath: sessionFiles[0],
- config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
- });
- expect(conflict).toMatchObject({
- success: false,
- errorInfo: { code: "session_already_active", activeSessionId: "external-owner" },
- });
- const emptyAfterConflict = await client.request({ type: "list" });
- expect(requireSessionList(emptyAfterConflict.success ? emptyAfterConflict.data : undefined)).toHaveLength(0);
- externalLease?.release();
- const created = await Promise.all(
- sessionFiles.map((sessionPath) =>
- client.request({
- type: "create",
- sessionPath,
- config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
- }),
- ),
- );
- const summaries = created.map((response) => {
- if (!response.success) {
- throw new Error(response.error);
+ const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
+ const client = await connectEventually(socketPath, supervisor);
+ const externalLease = acquireSessionLease(sessionFiles[0], agentDir, {
+ [SESSION_LEASES_ENABLED_ENV]: "1",
+ [SESSION_LEASE_OWNER_ID_ENV]: "external-owner",
+ });
+ const conflict = await client.request({
+ type: "create",
+ sessionPath: sessionFiles[0],
+ config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
+ });
+ expect(conflict).toMatchObject({
+ success: false,
+ errorInfo: { code: "session_already_active", activeSessionId: "external-owner" },
+ });
+ const emptyAfterConflict = await client.request({ type: "list" });
+ expect(requireSessionList(emptyAfterConflict.success ? emptyAfterConflict.data : undefined)).toHaveLength(0);
+ externalLease?.release();
+ const created = await Promise.all(
+ sessionFiles.map((sessionPath) =>
+ client.request({
+ type: "create",
+ sessionPath,
+ config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
+ }),
+ ),
+ );
+ const summaries = created.map((response) => {
+ if (!response.success) {
+ throw new Error(response.error);
+ }
+ return requireSummary(response.data);
+ });
+ const pids = summaries.map((summary) => summary.workerPid);
+ expect(new Set(pids).size).toBe(PROCESS_STRESS_WORKERS);
+ expect(pids).not.toContain(supervisor.pid);
+ for (const pid of pids) {
+ if (!pid) {
+ throw new Error("Resident root did not expose a worker pid");
+ }
+ workerPids.add(pid);
}
- return requireSummary(response.data);
- });
- const pids = summaries.map((summary) => summary.workerPid);
- expect(new Set(pids).size).toBe(PROCESS_STRESS_WORKERS);
- expect(pids).not.toContain(supervisor.pid);
- for (const pid of pids) {
- if (!pid) {
- throw new Error("Resident root did not expose a worker pid");
+ const firstActiveSessionId = summaries[0]!.activeSessionId ?? summaries[0]!.id;
+ const addedCron = await client.request({
+ type: "cron_add",
+ activeSessionId: firstActiveSessionId,
+ schedule: "every 1h",
+ prompt: "check status",
+ });
+ expect(addedCron.success).toBe(true);
+ const cronJob = (addedCron.success ? addedCron.data : undefined) as { job?: { id?: string } } | undefined;
+ if (!cronJob?.job?.id) {
+ throw new Error("Supervisor did not persist the cron job");
}
- workerPids.add(pid);
- }
- const firstActiveSessionId = summaries[0]!.activeSessionId ?? summaries[0]!.id;
- const addedCron = await client.request({
- type: "cron_add",
- activeSessionId: firstActiveSessionId,
- schedule: "every 1h",
- prompt: "check status",
- });
- expect(addedCron.success).toBe(true);
- const cronJob = (addedCron.success ? addedCron.data : undefined) as { job?: { id?: string } } | undefined;
- if (!cronJob?.job?.id) {
- throw new Error("Supervisor did not persist the cron job");
- }
- const listedCron = await client.request({ type: "cron_list", activeSessionId: firstActiveSessionId });
- expect(listedCron).toMatchObject({ success: true, data: { jobs: [{ id: cronJob.job.id }] } });
- const cancelledCron = await client.request({ type: "cron_cancel", jobId: cronJob.job.id });
- expect(cancelledCron.success).toBe(true);
- const activeSessionIds = summaries.map((summary) => summary.activeSessionId ?? summary.id);
- await Promise.all(
- activeSessionIds.map((activeSessionId, index) =>
- startBlockingBash(client, activeSessionId, join(root, `stress-blocker-${index}.ready`)),
- ),
- );
- const heartbeats = await Promise.all(
- activeSessionIds.map((activeSessionId, index) =>
- client.request({
- type: "heartbeat_set",
- activeSessionId,
- schedule: "every 10s",
- prompt: `heartbeat ${index}`,
- }),
- ),
- );
- expect(heartbeats.every((response) => response.success)).toBe(true);
- await waitForCondition(
- () => {
- const stores = summaries.map((summary, index) => {
- const store = AgentCronJobStore.forSessionArtifacts();
- store.registerSessionArtifact(
- summary.sessionId,
- join(dirname(dirname(sessionFiles[index]!)), "session-artifacts", summary.sessionId),
- );
- return store;
- });
- return stores.every((store) => store.list().some((job) => job.lastSkippedAt !== undefined));
- },
- "Session workers did not advance their heartbeats independently",
- 15_000,
- );
+ const listedCron = await client.request({ type: "cron_list", activeSessionId: firstActiveSessionId });
+ expect(listedCron).toMatchObject({ success: true, data: { jobs: [{ id: cronJob.job.id }] } });
+ const cancelledCron = await client.request({ type: "cron_cancel", jobId: cronJob.job.id });
+ expect(cancelledCron.success).toBe(true);
+ const activeSessionIds = summaries.map((summary) => summary.activeSessionId ?? summary.id);
+ await Promise.all(
+ activeSessionIds.map((activeSessionId, index) =>
+ startBlockingBash(client, activeSessionId, join(root, `stress-blocker-${index}.ready`)),
+ ),
+ );
+ const heartbeats = await Promise.all(
+ activeSessionIds.map((activeSessionId, index) =>
+ client.request({
+ type: "heartbeat_set",
+ activeSessionId,
+ schedule: "every 10s",
+ prompt: `heartbeat ${index}`,
+ }),
+ ),
+ );
+ expect(heartbeats.every((response) => response.success)).toBe(true);
+ await waitForCondition(
+ () => {
+ const stores = summaries.map((summary, index) => {
+ const store = AgentCronJobStore.forSessionArtifacts();
+ store.registerSessionArtifact(
+ summary.sessionId,
+ join(dirname(dirname(sessionFiles[index]!)), "session-artifacts", summary.sessionId),
+ );
+ return store;
+ });
+ return stores.every((store) => store.list().some((job) => job.lastSkippedAt !== undefined));
+ },
+ "Session workers did not advance their heartbeats independently",
+ 15_000,
+ );
- const listed = await client.request({ type: "list" });
- expect(listed.success).toBe(true);
- expect(requireSessionList(listed.success ? listed.data : undefined)).toHaveLength(PROCESS_STRESS_WORKERS);
- supervisor.kill("SIGTERM");
- await waitForExit(supervisor);
- children.delete(supervisor);
- client.close();
- const replacementClient = await connectEventually(socketPath);
- const adopted = await replacementClient.request({ type: "list" });
- expect(adopted.success).toBe(true);
- expect(
- new Set(requireSessionList(adopted.success ? adopted.data : undefined).map((summary) => summary.workerPid)),
- ).toEqual(new Set(pids));
- await replacementClient.request({ type: "shutdown" });
- replacementClient.close();
- await waitForSocketGone(socketPath);
- await Promise.all(
- pids.map(async (pid) => {
- if (pid) {
- await waitForProcessGone(pid);
- workerPids.delete(pid);
- }
- }),
- );
- });
+ const listed = await client.request({ type: "list" });
+ expect(listed.success).toBe(true);
+ expect(requireSessionList(listed.success ? listed.data : undefined)).toHaveLength(PROCESS_STRESS_WORKERS);
+ supervisor.kill("SIGTERM");
+ await waitForExit(supervisor);
+ children.delete(supervisor);
+ client.close();
+ const replacementClient = await connectEventually(socketPath);
+ const adopted = await replacementClient.request({ type: "list" });
+ expect(adopted.success).toBe(true);
+ expect(
+ new Set(requireSessionList(adopted.success ? adopted.data : undefined).map((summary) => summary.workerPid)),
+ ).toEqual(new Set(pids));
+ await replacementClient.request({ type: "shutdown" });
+ replacementClient.close();
+ await waitForSocketGone(socketPath);
+ await Promise.all(
+ pids.map(async (pid) => {
+ if (pid) {
+ await waitForProcessGone(pid);
+ workerPids.delete(pid);
+ }
+ }),
+ );
+ },
+ );
it("isolates a root, streams a chunked snapshot, and adopts the same worker after restart", async () => {
const root = tempDir();
@@ -1643,71 +1656,72 @@ describe("daemon supervisor resident workers", () => {
workerPids.delete(recovered.workerPid);
});
- it("runs a session-artifact cron job while the supervisor is being replaced", {
- tags: ["process-stress"],
- timeout: 30_000,
- }, async () => {
- const root = tempDir();
- const agentDir = join(root, "agent");
- const projectDir = join(root, "project");
- const sessionDir = join(agentDir, "sessions");
- const socketPath = join(tmpdir(), `prime-worker-cron-${process.pid}-${randomUUID().slice(0, 8)}.sock`);
- mkdirSync(projectDir, { recursive: true });
- const sessionManager = SessionManager.create(projectDir, sessionDir);
- sessionManager.appendMessage({ role: "user", content: "scheduled work", timestamp: 1 });
- sessionManager.appendSessionState({ status: "active" });
- const sessionFile = sessionManager.getSessionFile();
- const artifactDir = sessionManager.getSessionArtifactDir();
- if (!sessionFile || !artifactDir) {
- throw new Error("Fixture session did not persist");
- }
-
- const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
- const client = await connectEventually(socketPath, supervisor);
- const created = await client.request({
- type: "create",
- sessionPath: sessionFile,
- config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
- });
- if (!created.success) {
- throw new Error(created.error);
- }
- const summary = requireSummary(created.data);
- if (!summary.workerPid) {
- throw new Error("Resident worker did not expose its pid");
- }
- workerPids.add(summary.workerPid);
- await startBlockingBash(client, summary.activeSessionId ?? summary.id, join(root, "heartbeat-blocker.ready"));
- const scheduled = await client.request({
- type: "heartbeat_set",
- activeSessionId: summary.activeSessionId ?? summary.id,
- schedule: "every 10s",
- prompt: "continue without the supervisor",
- });
- if (!scheduled.success || !scheduled.data || typeof scheduled.data !== "object") {
- throw new Error(scheduled.success ? "Heartbeat response was missing its job" : scheduled.error);
- }
- const job = (scheduled.data as { heartbeat: { id: string } }).heartbeat;
-
- client.close();
- supervisor.kill("SIGKILL");
- await waitForExit(supervisor);
- children.delete(supervisor);
+ processStressIt(
+ "runs a session-artifact cron job while the supervisor is being replaced",
+ { timeout: 30_000 },
+ async () => {
+ const root = tempDir();
+ const agentDir = join(root, "agent");
+ const projectDir = join(root, "project");
+ const sessionDir = join(agentDir, "sessions");
+ const socketPath = join(tmpdir(), `prime-worker-cron-${process.pid}-${randomUUID().slice(0, 8)}.sock`);
+ mkdirSync(projectDir, { recursive: true });
+ const sessionManager = SessionManager.create(projectDir, sessionDir);
+ sessionManager.appendMessage({ role: "user", content: "scheduled work", timestamp: 1 });
+ sessionManager.appendSessionState({ status: "active" });
+ const sessionFile = sessionManager.getSessionFile();
+ const artifactDir = sessionManager.getSessionArtifactDir();
+ if (!sessionFile || !artifactDir) {
+ throw new Error("Fixture session did not persist");
+ }
- const store = AgentCronJobStore.forSessionArtifacts();
- store.registerSessionArtifact(sessionManager.getSessionId(), artifactDir);
- await waitForCondition(
- () => store.list().find((candidate) => candidate.id === job.id)?.lastSkippedAt !== undefined,
- "Resident worker did not advance its heartbeat without the supervisor",
- 15_000,
- );
- expect(store.list().find((candidate) => candidate.id === job.id)).toBeDefined();
+ const supervisor = spawnSupervisor(agentDir, socketPath, projectDir);
+ const client = await connectEventually(socketPath, supervisor);
+ const created = await client.request({
+ type: "create",
+ sessionPath: sessionFile,
+ config: { cwd: projectDir, agentDir, sessionDir, noTools: true, noExtensions: true },
+ });
+ if (!created.success) {
+ throw new Error(created.error);
+ }
+ const summary = requireSummary(created.data);
+ if (!summary.workerPid) {
+ throw new Error("Resident worker did not expose its pid");
+ }
+ workerPids.add(summary.workerPid);
+ await startBlockingBash(client, summary.activeSessionId ?? summary.id, join(root, "heartbeat-blocker.ready"));
+ const scheduled = await client.request({
+ type: "heartbeat_set",
+ activeSessionId: summary.activeSessionId ?? summary.id,
+ schedule: "every 10s",
+ prompt: "continue without the supervisor",
+ });
+ if (!scheduled.success || !scheduled.data || typeof scheduled.data !== "object") {
+ throw new Error(scheduled.success ? "Heartbeat response was missing its job" : scheduled.error);
+ }
+ const job = (scheduled.data as { heartbeat: { id: string } }).heartbeat;
- const replacement = await connectEventually(socketPath);
- await replacement.request({ type: "shutdown" });
- replacement.close();
- await waitForSocketGone(socketPath);
- await waitForProcessGone(summary.workerPid);
- workerPids.delete(summary.workerPid);
- });
+ client.close();
+ supervisor.kill("SIGKILL");
+ await waitForExit(supervisor);
+ children.delete(supervisor);
+
+ const store = AgentCronJobStore.forSessionArtifacts();
+ store.registerSessionArtifact(sessionManager.getSessionId(), artifactDir);
+ await waitForCondition(
+ () => store.list().find((candidate) => candidate.id === job.id)?.lastSkippedAt !== undefined,
+ "Resident worker did not advance its heartbeat without the supervisor",
+ 15_000,
+ );
+ expect(store.list().find((candidate) => candidate.id === job.id)).toBeDefined();
+
+ const replacement = await connectEventually(socketPath);
+ await replacement.request({ type: "shutdown" });
+ replacement.close();
+ await waitForSocketGone(socketPath);
+ await waitForProcessGone(summary.workerPid);
+ workerPids.delete(summary.workerPid);
+ },
+ );
});
diff --git a/packages/coding-agent/test/exec.test.ts b/packages/coding-agent/test/exec.test.ts
index 2f2ab6ea02..6c52abc48e 100644
--- a/packages/coding-agent/test/exec.test.ts
+++ b/packages/coding-agent/test/exec.test.ts
@@ -50,3 +50,28 @@ describe.skipIf(process.platform === "win32")("execCommand", () => {
}
});
});
+
+describe.skipIf(process.platform !== "win32")("execCommand on Windows", () => {
+ it("cancels a running process tree", async () => {
+ const testDir = mkdtempSync(join(tmpdir(), "prime-agent-exec-windows-test-"));
+ const readyFile = join(testDir, "ready");
+ const controller = new AbortController();
+ let resultPromise: Promise>> | undefined;
+ try {
+ resultPromise = execCommand(
+ process.execPath,
+ ["-e", `require("node:fs").writeFileSync(process.argv[1], ""); setInterval(() => {}, 1000);`, readyFile],
+ process.cwd(),
+ { signal: controller.signal },
+ );
+ await waitForFile(readyFile);
+ controller.abort();
+ const result = await resultPromise;
+ expect(result.killed).toBe(true);
+ } finally {
+ controller.abort();
+ await resultPromise;
+ rmSync(testDir, { recursive: true, force: true });
+ }
+ });
+});
diff --git a/packages/coding-agent/test/file-lines.test.ts b/packages/coding-agent/test/file-lines.test.ts
index 92421e9f99..d78fdad463 100644
--- a/packages/coding-agent/test/file-lines.test.ts
+++ b/packages/coding-agent/test/file-lines.test.ts
@@ -1,3 +1,4 @@
+import { createRequire } from "node:module";
import { Readable } from "node:stream";
import { afterEach, describe, expect, it, vi } from "vitest";
@@ -5,13 +6,11 @@ const fsMocks = vi.hoisted(() => ({
createReadStream: vi.fn(),
}));
-vi.mock("node:fs", async (importOriginal) => {
- const actual = await importOriginal();
- return {
- ...actual,
- createReadStream: fsMocks.createReadStream,
- };
-});
+const __fs = createRequire(import.meta.url)("node:fs");
+vi.mock("node:fs", () => ({
+ ...__fs,
+ createReadStream: fsMocks.createReadStream,
+}));
const { readLinesAsBuffers } = await import("../src/utils/file-lines.js");
diff --git a/packages/coding-agent/test/fixtures/eng-4600-supervisor-fixture.ts b/packages/coding-agent/test/fixtures/eng-4600-supervisor-fixture.ts
index 11adf1bc85..f44431ad4a 100644
--- a/packages/coding-agent/test/fixtures/eng-4600-supervisor-fixture.ts
+++ b/packages/coding-agent/test/fixtures/eng-4600-supervisor-fixture.ts
@@ -20,16 +20,43 @@ function send(message: Record): void {
process.send?.(message);
}
+function exitFixture(code: number): never {
+ process.exitCode = code;
+ if (process.connected) process.disconnect();
+ const reallyExit = (process as NodeJS.Process & { reallyExit?: (exitCode?: number) => never }).reallyExit;
+ if (reallyExit) return reallyExit.call(process, code);
+ process.exit(code);
+}
+
+function sendAndExit(message: Record, code: number): void {
+ send(message);
+ setTimeout(() => exitFixture(code), 10);
+}
+
+const pendingControls = new Map void>>();
+const controlBacklog: ControlMessage["type"][] = [];
+
+process.on("message", (message: unknown) => {
+ if (!message || typeof message !== "object" || typeof (message as Partial).type !== "string") {
+ return;
+ }
+ const type = (message as ControlMessage).type;
+ const waiters = pendingControls.get(type);
+ const waiter = waiters?.shift();
+ if (waiter) waiter();
+ else controlBacklog.push(type);
+});
+
function waitForControl(type: ControlMessage["type"]): Promise {
+ const queuedIndex = controlBacklog.indexOf(type);
+ if (queuedIndex !== -1) {
+ controlBacklog.splice(queuedIndex, 1);
+ return Promise.resolve();
+ }
return new Promise((resolve) => {
- const onMessage = (message: unknown) => {
- if (!message || typeof message !== "object" || (message as Partial).type !== type) {
- return;
- }
- process.off("message", onMessage);
- resolve();
- };
- process.on("message", onMessage);
+ const waiters = pendingControls.get(type) ?? [];
+ waiters.push(resolve);
+ pendingControls.set(type, waiters);
});
}
@@ -47,7 +74,7 @@ async function runOwnershipHolder(): Promise {
await ownership.release();
send({ type: "owner_released" });
await waitForControl("shutdown");
- process.exit(0);
+ exitFixture(0);
}
async function runSupervisor(): Promise {
@@ -82,8 +109,8 @@ async function runSupervisor(): Promise {
});
return await new Promise(() => {});
} catch (error) {
- send({ type: "failed", error: error instanceof Error ? error.message : String(error) });
- process.exit(0);
+ sendAndExit({ type: "failed", error: error instanceof Error ? error.message : String(error) }, 0);
+ return await new Promise(() => {});
}
}
@@ -129,8 +156,8 @@ async function runLegacyCleanup(): Promise {
}
}
}
- send({ type: "cleanup_complete", skipped });
- process.exit(0);
+ sendAndExit({ type: "cleanup_complete", skipped }, 0);
+ return await new Promise(() => {});
}
async function main(): Promise {
@@ -158,6 +185,5 @@ async function main(): Promise {
}
void main().catch((error) => {
- send({ type: "failed", error: error instanceof Error ? error.message : String(error) });
- process.exit(1);
+ sendAndExit({ type: "failed", error: error instanceof Error ? error.message : String(error) }, 1);
});
diff --git a/packages/coding-agent/test/fixtures/eng-4606-update-launcher.ts b/packages/coding-agent/test/fixtures/eng-4606-update-launcher.ts
index 1e7b693882..8cacf3a74b 100644
--- a/packages/coding-agent/test/fixtures/eng-4606-update-launcher.ts
+++ b/packages/coding-agent/test/fixtures/eng-4606-update-launcher.ts
@@ -14,11 +14,10 @@ const cliPath = requireEnvironment("ENG_4606_CLI_PATH");
const completionPath = requireEnvironment("ENG_4606_COMPLETION_PATH");
const pidPath = requireEnvironment("ENG_4606_PID_PATH");
const socketPath = requireEnvironment("ENG_4606_SOCKET_PATH");
-const tsxPath = requireEnvironment("ENG_4606_TSX_PATH");
const originActiveSessionId = requireEnvironment("PRIME_AGENT_INTERNAL_DAEMON_WORKER_ACTIVE_SESSION_ID");
process.argv[1] = cliPath;
-process.execArgv.splice(0, process.execArgv.length, tsxPath);
+process.execArgv.splice(0, process.execArgv.length);
writeFileSync(pidPath, `${process.pid}\n`);
await launchDaemonUpdateRestartCoordinator({
diff --git a/packages/coding-agent/test/git-context.test.ts b/packages/coding-agent/test/git-context.test.ts
index 07b61ddc05..ea61ff707a 100644
--- a/packages/coding-agent/test/git-context.test.ts
+++ b/packages/coding-agent/test/git-context.test.ts
@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { captureGitContext, gitContextsEqual } from "../src/utils/git.js";
function git(cwd: string, ...args: string[]): string {
- return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
+ return execFileSync("git", args, { cwd, encoding: "utf8", env: process.env }).trim();
}
function initRepo(dir: string): void {
diff --git a/packages/coding-agent/test/git-update.test.ts b/packages/coding-agent/test/git-update.test.ts
index eb50d6003c..e7df416356 100644
--- a/packages/coding-agent/test/git-update.test.ts
+++ b/packages/coding-agent/test/git-update.test.ts
@@ -11,6 +11,7 @@ function git(args: string[], cwd: string): string {
const result = spawnSync("git", args, {
cwd,
encoding: "utf-8",
+ env: process.env,
});
if (result.status !== 0) {
throw new Error(`Command failed: git ${args.join(" ")}\n${result.stderr}`);
diff --git a/packages/coding-agent/test/interactive-mode-status.test.ts b/packages/coding-agent/test/interactive-mode-status.test.ts
index 2ffdd91f7c..dfdefce78a 100644
--- a/packages/coding-agent/test/interactive-mode-status.test.ts
+++ b/packages/coding-agent/test/interactive-mode-status.test.ts
@@ -5026,9 +5026,9 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- @scope/pi-scoped, answer.ts, cli-extension.ts, HazAT/pi-interactive-subagents, HazAT/pi-interactive-subagents:subagents, local-index, pi-markdown-preview, user-index"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe(
+ "[Extensions]\n @scope/pi-scoped, answer.ts, cli-extension.ts, HazAT/pi-interactive-subagents, HazAT/pi-interactive-subagents:subagents, local-index, pi-markdown-preview, user-index",
+ );
});
test("adds more parent folders until local extension labels are unique", () => {
@@ -5072,9 +5072,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- alpha/one, beta/one, gamma/one"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n alpha/one, beta/one, gamma/one");
});
test("strips index.ts from local extension label, showing parent dir", () => {
@@ -5100,9 +5098,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- plan-mode"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n plan-mode");
});
test("strips index.js from local extension label, showing parent dir", () => {
@@ -5128,9 +5124,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- plan-mode"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n plan-mode");
});
test("mixed single-file and subdirectory index.ts extensions strip index.ts", () => {
@@ -5165,9 +5159,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- plan-mode, webfetch.ts"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n plan-mode, webfetch.ts");
});
test("multiple index.ts with unique parent dirs need no disambiguation", () => {
@@ -5202,9 +5194,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- bar, foo"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n bar, foo");
});
test("multiple index.ts with same parent dir name disambiguated with grandparent", () => {
@@ -5239,9 +5229,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- alpha/tools, beta/tools"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n alpha/tools, beta/tools");
});
test("non-index file in subdirectory stays as filename", () => {
@@ -5267,9 +5255,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- main.ts"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n main.ts");
});
test("package extensions still strip index.ts correctly (regression guard)", () => {
@@ -5295,9 +5281,7 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- pi-markdown-preview"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe("[Extensions]\n pi-markdown-preview");
});
test("captures mixed extension layouts in expanded output", () => {
const fakeThis = createShowLoadedResourcesThis({
@@ -5311,22 +5295,9 @@ describe("InteractiveMode.showLoadedResources", () => {
force: true,
});
- expect(normalizeRenderedOutput(fakeThis.chatContainer)).toMatchInlineSnapshot(`
-"[Extensions]
- project
- /tmp/project/.pi/extensions/answer.ts
- /tmp/project/.pi/extensions/local-index
- git:github.com/HazAT/pi-interactive-subagents
- extensions
- extensions/subagents
- npm:@scope/pi-scoped
- extensions
- npm:pi-markdown-preview
- extensions
- user
- /tmp/agent/extensions/user-index
- path
- /tmp/temp/cli-extension.ts"`);
+ expect(normalizeRenderedOutput(fakeThis.chatContainer)).toBe(
+ "[Extensions]\n project\n /tmp/project/.pi/extensions/answer.ts\n /tmp/project/.pi/extensions/local-index\n git:github.com/HazAT/pi-interactive-subagents\n extensions\n extensions/subagents\n npm:@scope/pi-scoped\n extensions\n npm:pi-markdown-preview\n extensions\n user\n /tmp/agent/extensions/user-index\n path\n /tmp/temp/cli-extension.ts",
+ );
});
test("shows context paths relative to cwd while preserving full external paths", () => {
@@ -5419,13 +5390,9 @@ describe("InteractiveMode.showLoadedResources", () => {
});
const output = normalizeRenderedOutput(fakeThis.chatContainer, 100);
- expect(output).toMatchInlineSnapshot(`
-"[Skill warning]
- first line of the warning.
- second line with guidance.
-
- indented detail"
-`);
+ expect(output).toBe(
+ "[Skill warning]\n first line of the warning.\n second line with guidance.\n\n indented detail",
+ );
expect(output).not.toContain("[Skill conflicts]");
});
});
diff --git a/packages/coding-agent/test/interactive-update-relaunch.test.ts b/packages/coding-agent/test/interactive-update-relaunch.test.ts
index b13e6b216d..8a215bbe07 100644
--- a/packages/coding-agent/test/interactive-update-relaunch.test.ts
+++ b/packages/coding-agent/test/interactive-update-relaunch.test.ts
@@ -1,19 +1,21 @@
-import type * as ChildProcessModule from "child_process";
+import { createRequire } from "node:module";
import { describe, expect, it, vi } from "vitest";
-import type * as DaemonUpdateRestartModule from "../src/cli/daemon-update-restart.js";
const updateMocks = vi.hoisted(() => ({
spawnSync: vi.fn(),
launchCoordinator: vi.fn(),
}));
-vi.mock("child_process", async (importOriginal) => ({
- ...(await importOriginal()),
+const __childProcess = createRequire(import.meta.url)("child_process");
+const __daemonUpdateRestart = createRequire(import.meta.url)("../src/cli/daemon-update-restart.js");
+
+vi.mock("child_process", () => ({
+ ...__childProcess,
spawnSync: updateMocks.spawnSync,
}));
-vi.mock("../src/cli/daemon-update-restart.js", async (importOriginal) => ({
- ...(await importOriginal()),
+vi.mock("../src/cli/daemon-update-restart.js", () => ({
+ ...__daemonUpdateRestart,
launchDaemonUpdateRestartCoordinator: updateMocks.launchCoordinator,
}));
diff --git a/packages/coding-agent/test/kernel-bash-shell.test.ts b/packages/coding-agent/test/kernel-bash-shell.test.ts
index bced79ae99..80302b7b86 100644
--- a/packages/coding-agent/test/kernel-bash-shell.test.ts
+++ b/packages/coding-agent/test/kernel-bash-shell.test.ts
@@ -1,3 +1,4 @@
+import { createRequire } from "node:module";
import { afterEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
@@ -5,17 +6,15 @@ const mocks = vi.hoisted(() => ({
spawnSync: vi.fn(),
}));
-vi.mock("node:fs", async (importOriginal) => {
- const actual = await importOriginal();
+const __fs = createRequire(import.meta.url)("node:fs") as typeof import("node:fs");
+vi.mock("node:fs", () => {
// Module-load reads (config.ts) must see the real fs; tests override per case.
- mocks.existsSync.mockImplementation(actual.existsSync);
- return { ...actual, existsSync: mocks.existsSync };
+ mocks.existsSync.mockImplementation(__fs.existsSync);
+ return { ...__fs, existsSync: mocks.existsSync };
});
-vi.mock("child_process", async (importOriginal) => {
- const actual = await importOriginal();
- return { ...actual, spawnSync: mocks.spawnSync };
-});
+const __childProcess = createRequire(import.meta.url)("child_process") as typeof import("child_process");
+vi.mock("child_process", () => ({ ...__childProcess, spawnSync: mocks.spawnSync }));
import { resolveKernelBashShell } from "../src/utils/shell.js";
diff --git a/packages/coding-agent/test/kernel-bootstrap.test.ts b/packages/coding-agent/test/kernel-bootstrap.test.ts
index cda17807c4..0aff453877 100644
--- a/packages/coding-agent/test/kernel-bootstrap.test.ts
+++ b/packages/coding-agent/test/kernel-bootstrap.test.ts
@@ -173,6 +173,17 @@ describe("kernel bootstrap", () => {
expect(getKernelVenvDir()).toBe(venv);
});
+ it("resolves a runtime sidecar beside a compiled binary", async () => {
+ const packageDir = join(tempDir, "binary-layout");
+ const runtimeDir = join(packageDir, "prime-agent-runtime");
+ mkdirSync(join(runtimeDir, "src", "rlm"), { recursive: true });
+ writeFileSync(join(runtimeDir, "pyproject.toml"), '[project]\nname = "prime-agent-runtime"\n');
+ writeFileSync(join(runtimeDir, "src", "rlm", "__init__.py"), "VALUE = 1\n");
+ process.env.PI_PACKAGE_DIR = packageDir;
+
+ expect(await resolveRuntimeIdentity()).toMatch(/^sha256:[0-9a-f]{64}$/);
+ });
+
it("bootstraps a missing venv with uv, prime-agent-runtime, and default extra packages", async () => {
const logPath = installFakeUv();
const venv = join(tempDir, "kernel-venv");
diff --git a/packages/coding-agent/test/kernel-goal-skill.test.ts b/packages/coding-agent/test/kernel-goal-skill.test.ts
index 71a544b640..76fded711b 100644
--- a/packages/coding-agent/test/kernel-goal-skill.test.ts
+++ b/packages/coding-agent/test/kernel-goal-skill.test.ts
@@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { getBundledSkillsDir } from "../src/config.js";
import type { PythonSkillRuntimeInfo } from "../src/core/skills.js";
import { IpythonKernelProvisioner } from "../src/core/tools/ipython.js";
+import { isTestTagEnabled } from "./test-tags.js";
function bundledGoalSkill(): PythonSkillRuntimeInfo {
const packagePath = join(getBundledSkillsDir(), "goal");
@@ -16,7 +17,9 @@ function bundledGoalSkill(): PythonSkillRuntimeInfo {
};
}
-describe("goal skill over the kernel host bridge", { tags: ["kernel-heavy"] }, () => {
+const kernelHeavyDescribe = isTestTagEnabled("kernel-heavy") ? describe : describe.skip;
+
+kernelHeavyDescribe("goal skill over the kernel host bridge", () => {
let tempDir: string;
let provisioner: IpythonKernelProvisioner | undefined;
diff --git a/packages/coding-agent/test/mcp-command.test.ts b/packages/coding-agent/test/mcp-command.test.ts
index 81f19581b5..21ce779296 100644
--- a/packages/coding-agent/test/mcp-command.test.ts
+++ b/packages/coding-agent/test/mcp-command.test.ts
@@ -43,10 +43,12 @@ describe("MCP management commands", () => {
"--",
"node",
]);
- expect(config).toMatchObject({
- type: "stdio",
- env: { __proto__: { env: "PROTO_SOURCE" }, constructor: { env: "CONSTRUCTOR_SOURCE" } },
- });
+ expect(config.type).toBe("stdio");
+ if (config.type !== "stdio") throw new Error("Expected stdio MCP config");
+ expect(Object.hasOwn(config.env ?? {}, "__proto__")).toBe(true);
+ expect(Reflect.get(config.env ?? {}, "__proto__")).toEqual({ env: "PROTO_SOURCE" });
+ expect(Object.hasOwn(config.env ?? {}, "constructor")).toBe(true);
+ expect(config.env?.constructor).toEqual({ env: "CONSTRUCTOR_SOURCE" });
});
it("validates transport, URL, names, auth, and stdio environment syntax", () => {
diff --git a/packages/coding-agent/test/owned-session-worker-process.test.ts b/packages/coding-agent/test/owned-session-worker-process.test.ts
index 0b661ff631..d19cc8f01c 100644
--- a/packages/coding-agent/test/owned-session-worker-process.test.ts
+++ b/packages/coding-agent/test/owned-session-worker-process.test.ts
@@ -5,7 +5,6 @@ import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
const fixturePath = resolve(__dirname, "fixtures/owned-session-worker-fixture.ts");
-const tsxPath = resolve(__dirname, "../../../node_modules/tsx/dist/cli.mjs");
const tempDirs: string[] = [];
const children = new Set();
const workerPids = new Set();
@@ -45,14 +44,13 @@ function spawnFrontend(
keepAlive = false,
environment: NodeJS.ProcessEnv = {},
): ChildProcess {
- const child = spawn(process.execPath, [tsxPath, fixturePath, ...args], {
+ const child = spawn(process.execPath, [fixturePath, ...args], {
env: {
...process.env,
...environment,
PRIME_AGENT_INTERNAL_LEGACY_OWNED_WORKER_FRONTEND: "1",
PRIME_AGENT_TEST_OWNED_PID_PATH: pidPath,
...(keepAlive ? { PRIME_AGENT_TEST_KEEP_ALIVE: "1" } : {}),
- TSX_TSCONFIG_PATH: resolve(__dirname, "../../../tsconfig.json"),
},
stdio: ["pipe", "pipe", "pipe"],
});
diff --git a/packages/coding-agent/test/package-command-paths.test.ts b/packages/coding-agent/test/package-command-paths.test.ts
index 3ddb7055a4..980da26b1d 100644
--- a/packages/coding-agent/test/package-command-paths.test.ts
+++ b/packages/coding-agent/test/package-command-paths.test.ts
@@ -57,7 +57,7 @@ describe("package commands", () => {
originalTmpDir = process.env.TMPDIR;
originalExitCode = process.exitCode;
originalExecPath = process.execPath;
- process.exitCode = undefined;
+ process.exitCode = 0;
process.env[ENV_AGENT_DIR] = agentDir;
process.env.TMPDIR = tempDir;
process.chdir(projectDir);
@@ -66,7 +66,7 @@ describe("package commands", () => {
afterEach(() => {
vi.unstubAllGlobals();
process.chdir(originalCwd);
- process.exitCode = originalExitCode;
+ process.exitCode = originalExitCode ?? 0;
restoreEnv(ENV_AGENT_DIR, originalAgentDir);
restoreEnv("PI_PACKAGE_DIR", originalPiPackageDir);
restoreEnv("PRIME_AGENT_DOWNLOAD_BASE_URL", originalPrimeAgentDownloadBaseUrl);
@@ -113,7 +113,7 @@ describe("package commands", () => {
expect(stdout).toContain("Usage:");
expect(stdout).toContain(`${APP_NAME} package install [--local]`);
expect(errorSpy).not.toHaveBeenCalled();
- expect(process.exitCode).toBeUndefined();
+ expect(process.exitCode ?? 0).toBe(0);
} finally {
logSpy.mockRestore();
errorSpy.mockRestore();
@@ -219,7 +219,7 @@ else fs.writeFileSync(${JSON.stringify(recordPath)},JSON.stringify(args));
try {
await expect(runSelfUpdateInstallChild(["update", "--self", "--force"])).resolves.toBeUndefined();
- expect(process.exitCode).toBeUndefined();
+ expect(process.exitCode ?? 0).toBe(0);
expect(errorSpy).not.toHaveBeenCalled();
expect(fetchMock).toHaveBeenCalledOnce();
const recordedArgs = JSON.parse(readFileSync(recordPath, "utf-8")) as string[];
@@ -263,7 +263,7 @@ else fs.writeFileSync(${JSON.stringify(recordPath)},JSON.stringify(args));
try {
await expect(runSelfUpdateInstallChild(["update", "--self"])).resolves.toBeUndefined();
- expect(process.exitCode).toBeUndefined();
+ expect(process.exitCode ?? 0).toBe(0);
expect(errorSpy).not.toHaveBeenCalled();
expect(fetchMock).toHaveBeenCalledOnce();
const recordedArgs = JSON.parse(readFileSync(recordPath, "utf-8")) as string[];
@@ -312,7 +312,7 @@ else {
try {
await expect(runSelfUpdateInstallChild(["update", "--self"])).resolves.toBeUndefined();
- expect(process.exitCode).toBeUndefined();
+ expect(process.exitCode ?? 0).toBe(0);
expect(errorSpy).not.toHaveBeenCalled();
const recordedCalls = JSON.parse(readFileSync(recordPath, "utf-8")) as string[][];
expect(recordedCalls).toEqual([
@@ -365,7 +365,7 @@ else {
try {
await expect(runSelfUpdateInstallChild(["update", "--self"])).resolves.toBeUndefined();
- expect(process.exitCode).toBeUndefined();
+ expect(process.exitCode ?? 0).toBe(0);
expect(errorSpy).not.toHaveBeenCalled();
const recordedCalls = JSON.parse(readFileSync(recordPath, "utf-8")) as string[][];
expect(recordedCalls).toEqual([
@@ -419,7 +419,7 @@ else fs.writeFileSync(${JSON.stringify(recordPath)},JSON.stringify(args));
try {
await expect(main(["update"])).resolves.toBeUndefined();
- expect(process.exitCode).toBeUndefined();
+ expect(process.exitCode ?? 0).toBe(0);
expect(errorSpy).not.toHaveBeenCalled();
expect(logSpy.mock.calls.map(([message]) => String(message)).join("\n")).toContain("is already up to date");
expect(existsSync(recordPath)).toBe(false);
diff --git a/packages/coding-agent/test/package-self-update-daemon.test.ts b/packages/coding-agent/test/package-self-update-daemon.test.ts
index af8ebf000d..0245271506 100644
--- a/packages/coding-agent/test/package-self-update-daemon.test.ts
+++ b/packages/coding-agent/test/package-self-update-daemon.test.ts
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import type * as DaemonUpdateRestartModule from "../src/cli/daemon-update-restart.js";
import {
acquireDaemonUpdateRestartCoordinator,
type DaemonUpdateRestartStatus,
@@ -20,7 +19,6 @@ import {
} from "../src/config.js";
import type { AgentSessionRuntimeMetadata } from "../src/core/agent-session-runtime.js";
import { DAEMON_PROTOCOL_VERSION, DAEMON_SCHEMA_ID } from "../src/modes/daemon/daemon-protocol.js";
-import type * as DaemonSocketModule from "../src/modes/daemon/daemon-socket.js";
import {
handlePackageCommand,
prepareDaemonUpdateRestart,
@@ -198,8 +196,8 @@ vi.mock("child_process", () => ({
})),
}));
-vi.mock("../src/cli/daemon-update-restart.js", async (importOriginal) => {
- const original = await importOriginal();
+vi.mock("../src/cli/daemon-update-restart.js", () => {
+ const original = require("../src/cli/daemon-update-restart.js");
return {
...original,
launchDaemonUpdateRestartCoordinator: vi.fn(async (options: { socketPath: string }) => {
@@ -218,10 +216,10 @@ vi.mock("../src/cli/daemon-update-restart.js", async (importOriginal) => {
};
});
-vi.mock("../src/modes/daemon/daemon-socket.js", async (importOriginal) => ({
- ...(await importOriginal()),
- defaultDaemonSocketPath: () => mockState.socketPath,
-}));
+vi.mock("../src/modes/daemon/daemon-socket.js", () => {
+ const actual = require("../src/modes/daemon/daemon-socket.js");
+ return { ...actual, defaultDaemonSocketPath: () => mockState.socketPath };
+});
vi.mock("../src/modes/daemon/daemon-supervisor-ownership.js", () => ({
acquireDaemonShutdownAdmission: vi.fn(async () => {
@@ -511,7 +509,7 @@ describe("self-update daemon restart", () => {
originalCwd = process.cwd();
originalExecPath = process.execPath;
originalExitCode = process.exitCode;
- process.exitCode = undefined;
+ process.exitCode = 0;
process.env[ENV_AGENT_DIR] = agentDir;
process.env.PI_PACKAGE_DIR = packageDir;
process.chdir(projectDir);
@@ -529,7 +527,7 @@ describe("self-update daemon restart", () => {
afterEach(() => {
vi.unstubAllGlobals();
process.chdir(originalCwd);
- process.exitCode = originalExitCode;
+ process.exitCode = originalExitCode ?? 0;
if (originalAgentDir === undefined) {
delete process.env[ENV_AGENT_DIR];
} else {
@@ -884,7 +882,7 @@ describe("self-update daemon restart", () => {
try {
await expect(performUpdateAndRunCoordinator()).resolves.toBeUndefined();
- expect(process.exitCode).toBeUndefined();
+ expect(process.exitCode ?? 0).toBe(0);
const spawnIndex = mockState.calls.findIndex((call) => call.startsWith("spawn:npm "));
const launchIndex = mockState.calls.indexOf(`launch-coordinator:${mockState.socketPath}`);
const fenceIndex = mockState.calls.indexOf("persist-daemon-startup-fence");
diff --git a/packages/coding-agent/test/print-mode.test.ts b/packages/coding-agent/test/print-mode.test.ts
index 065d5d04ae..e75b48e3ec 100644
--- a/packages/coding-agent/test/print-mode.test.ts
+++ b/packages/coding-agent/test/print-mode.test.ts
@@ -754,7 +754,7 @@ describe("runPrintMode", () => {
});
expect(exitCode).toBe(0);
- expect(session.waitForIdle).toHaveBeenCalledBefore(session.prompt);
+ expect(session.waitForIdle.mock.invocationCallOrder[0]).toBeLessThan(session.prompt.mock.invocationCallOrder[0]!);
expect(session.prompt).toHaveBeenCalledTimes(2);
expect(session.prompt.mock.calls[0][0]).toContain("Autonomous quality gate failed (attempt 1/3)");
expect(session.prompt.mock.calls[0][0]).toContain("0/9");
diff --git a/packages/coding-agent/test/public-command.test.ts b/packages/coding-agent/test/public-command.test.ts
index 5fc4ae676e..d0bca2b58b 100644
--- a/packages/coding-agent/test/public-command.test.ts
+++ b/packages/coding-agent/test/public-command.test.ts
@@ -63,13 +63,13 @@ describe("public command routing", () => {
mocks.reapCalls.length = 0;
mocks.shutdownCalls.length = 0;
mocks.mcpCommands.length = 0;
- process.exitCode = undefined;
+ process.exitCode = 0;
vi.spyOn(console, "log").mockImplementation(() => {});
vi.spyOn(console, "error").mockImplementation(() => {});
});
afterEach(() => {
- process.exitCode = undefined;
+ process.exitCode = 0;
vi.restoreAllMocks();
});
diff --git a/packages/coding-agent/test/refinement.test.ts b/packages/coding-agent/test/refinement.test.ts
index 19a7313975..bc7dbbe86d 100644
--- a/packages/coding-agent/test/refinement.test.ts
+++ b/packages/coding-agent/test/refinement.test.ts
@@ -2,7 +2,6 @@ import { appendFileSync, chmodSync, mkdtempSync, readdirSync, rmSync, statSync,
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { AgentMessage } from "@earendil-works/pi-agent-core";
-import type * as PiAi from "@earendil-works/pi-ai";
import type { AssistantMessage, Model } from "@earendil-works/pi-ai";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
@@ -34,8 +33,8 @@ const { completeSimpleMock } = vi.hoisted(() => ({
completeSimpleMock: vi.fn(),
}));
-vi.mock("@earendil-works/pi-ai", async (importOriginal) => {
- const actual = await importOriginal();
+vi.mock("@earendil-works/pi-ai", () => {
+ const actual = require("@earendil-works/pi-ai");
return {
...actual,
completeSimple: completeSimpleMock,
@@ -1015,20 +1014,13 @@ describe("harness refinement", () => {
);
expect(completeSimpleMock).toHaveBeenCalledTimes(1);
- expect(completeSimpleMock.mock.calls[0][1]).toMatchObject({
- systemPrompt: expect.stringContaining("The default editable continual harness store is local"),
- });
- expect(completeSimpleMock.mock.calls[0][1]).toMatchObject({
- systemPrompt: expect.stringContaining("A caller may explicitly request global refinement"),
- });
- expect(completeSimpleMock.mock.calls[0][1]).toMatchObject({
- systemPrompt: expect.stringContaining("Always use the bare id (no prefix) in edits"),
- });
- expect(completeSimpleMock.mock.calls[0][1]).toMatchObject({
- systemPrompt: expect.stringContaining(
- "During a local refinement, global entries are read-only context: never propose update or delete edits for them",
- ),
- });
+ const refinementSystemPrompt = completeSimpleMock.mock.calls[0][1]?.systemPrompt;
+ expect(refinementSystemPrompt).toContain("The default editable continual harness store is local");
+ expect(refinementSystemPrompt).toContain("A caller may explicitly request global refinement");
+ expect(refinementSystemPrompt).toContain("Always use the bare id (no prefix) in edits");
+ expect(refinementSystemPrompt).toContain(
+ "During a local refinement, global entries are read-only context: never propose update or delete edits for them",
+ );
// Budget is derived from the model (8192) rather than a fixed literal.
expect(completeSimpleMock.mock.calls[0][2]).toMatchObject({
maxTokens: 8192,
diff --git a/packages/coding-agent/test/repl-kernel-abort.test.ts b/packages/coding-agent/test/repl-kernel-abort.test.ts
index b3283a4191..dbfdfd54a8 100644
--- a/packages/coding-agent/test/repl-kernel-abort.test.ts
+++ b/packages/coding-agent/test/repl-kernel-abort.test.ts
@@ -204,13 +204,12 @@ describe("ReplKernelManager abort handling", () => {
await expect(executePromise).resolves.toMatchObject({ status: "aborted" });
const secondExecutePromise = manager.execute("x = 1");
- const secondExecuteExpectation = expect(secondExecutePromise).rejects.toThrow(
- "The Python kernel is still running the previously interrupted cell",
- );
await Promise.resolve();
await vi.advanceTimersByTimeAsync(5000);
- await secondExecuteExpectation;
+ await expect(secondExecutePromise).rejects.toThrow(
+ "The Python kernel is still running the previously interrupted cell",
+ );
expect(writeLine.mock.calls.filter((call) => (call[0] as { type?: string }).type === "execute")).toHaveLength(1);
expect(writeLine.mock.calls.some((call) => (call[0] as { type?: string }).type === "interrupt")).toBe(true);
manager.disposeSync();
diff --git a/packages/coding-agent/test/repl-kernel-execute.test.ts b/packages/coding-agent/test/repl-kernel-execute.test.ts
index c653bce98f..eaca159a0a 100644
--- a/packages/coding-agent/test/repl-kernel-execute.test.ts
+++ b/packages/coding-agent/test/repl-kernel-execute.test.ts
@@ -12,10 +12,13 @@ import {
} from "../src/core/kernel/index.js";
function resolveReplPython(): string | null {
+ const venvPython = (...parts: string[]) =>
+ process.platform === "win32" ? join(...parts, "Scripts", "python.exe") : join(...parts, "bin", "python");
const candidates = [
process.env.PRIME_AGENT_KERNEL_PYTHON,
- resolve(__dirname, "..", "..", "..", "prime-agent-runtime", ".venv", "bin", "python"),
- join(homedir(), ".prime", "agent", "kernel-venv", "bin", "python"),
+ process.env.PRIME_AGENT_KERNEL_VENV ? venvPython(process.env.PRIME_AGENT_KERNEL_VENV) : undefined,
+ venvPython(resolve(__dirname, "..", "..", "..", "prime-agent-runtime", ".venv")),
+ venvPython(homedir(), ".prime", "agent", "kernel-venv"),
].filter((p): p is string => Boolean(p));
for (const python of candidates) {
if (!existsSync(python)) continue;
diff --git a/packages/coding-agent/test/repl-kernel-mcp-shutdown.test.ts b/packages/coding-agent/test/repl-kernel-mcp-shutdown.test.ts
index ca5545c7b2..dca42a4e2b 100644
--- a/packages/coding-agent/test/repl-kernel-mcp-shutdown.test.ts
+++ b/packages/coding-agent/test/repl-kernel-mcp-shutdown.test.ts
@@ -4,6 +4,7 @@ import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { ReplKernelManager } from "../src/core/kernel/index.js";
+import { isTestTagEnabled } from "./test-tags.js";
const runtimePython = resolve("../../prime-agent-runtime/.venv/bin/python");
const fallbackPython = join(homedir(), ".prime", "agent", "kernel-venv", "bin", "python");
@@ -18,7 +19,7 @@ function resolveKernelPython(): string | null {
}
const python = resolveKernelPython();
-const describeIfKernel = python ? describe : describe.skip;
+const describeIfKernel = python && isTestTagEnabled("kernel-heavy") ? describe : describe.skip;
const MCP_SERVER = `import asyncio, json, os, sys
from pathlib import Path
@@ -56,7 +57,7 @@ async function waitForExit(pid: number, timeoutMs: number): Promise {
return !pidExists(pid);
}
-describeIfKernel("real REPL kernel MCP shutdown", { tags: ["kernel-heavy"] }, () => {
+describeIfKernel("real REPL kernel MCP shutdown", () => {
let dir = "";
let fixture = "";
let pidFile = "";
diff --git a/packages/coding-agent/test/repl-kernel-parent-watchdog.test.ts b/packages/coding-agent/test/repl-kernel-parent-watchdog.test.ts
index 5924881fce..431a8214a9 100644
--- a/packages/coding-agent/test/repl-kernel-parent-watchdog.test.ts
+++ b/packages/coding-agent/test/repl-kernel-parent-watchdog.test.ts
@@ -1,28 +1,49 @@
import { spawn, spawnSync } from "node:child_process";
import { EventEmitter } from "node:events";
-import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import type * as kernelBootstrap from "../src/core/kernel/bootstrap.js";
import { ReplKernelManager } from "../src/core/kernel/index.js";
import { ORPHAN_PROCESS_JOURNAL_ENV } from "../src/core/orphan-process-journal.js";
+import { isTestTagEnabled } from "./test-tags.js";
const ensureKernelPythonMock = vi.hoisted(() => vi.fn());
-vi.mock("../src/core/kernel/bootstrap.js", async (importOriginal) => {
- const original = await importOriginal();
+vi.mock("../src/core/kernel/bootstrap.js", () => {
+ const original = require("../src/core/kernel/bootstrap.js");
return { ...original, ensureKernelPython: ensureKernelPythonMock };
});
let tempDir = "";
const savedJournalPath = process.env[ORPHAN_PROCESS_JOURNAL_ENV];
-function writeFakePython(script: string[]): string {
+function writeEarlyExitKernel(envDump: string): { python: string; env?: Record } {
+ if (process.platform === "win32") {
+ const venv = process.env.PRIME_AGENT_KERNEL_VENV;
+ if (!venv) throw new Error("PRIME_AGENT_KERNEL_VENV is required for this Windows kernel test");
+ const python = join(venv, "Scripts", "python.exe");
+ const runtimeRoot = join(tempDir, "early-exit-runtime");
+ const rlmDir = join(runtimeRoot, "rlm");
+ mkdirSync(rlmDir, { recursive: true });
+ writeFileSync(join(rlmDir, "__init__.py"), "");
+ writeFileSync(
+ join(rlmDir, "repl.py"),
+ [
+ "import os",
+ "from pathlib import Path",
+ `Path(${JSON.stringify(envDump)}).write_text("\\n".join(f"{key}={value}" for key, value in os.environ.items()))`,
+ "raise SystemExit(42)",
+ "",
+ ].join("\n"),
+ );
+ return { python, env: { PYTHONPATH: runtimeRoot } };
+ }
+
const python = join(tempDir, "python");
- writeFileSync(python, script.join("\n"));
+ writeFileSync(python, ["#!/bin/sh", `env > "${envDump}"`, "exit 42", ""].join("\n"));
chmodSync(python, 0o755);
- return python;
+ return { python };
}
interface JournalRecord {
@@ -55,11 +76,11 @@ describe("repl kernel parent watchdog", () => {
it("spawn sets PRIME_AGENT_KERNEL_OWNER_PID and journals the kernel pid", async () => {
const envDump = join(tempDir, "kernel-env");
- const python = writeFakePython(["#!/bin/sh", `env > "${envDump}"`, "exit 42", ""]);
+ const { python, env } = writeEarlyExitKernel(envDump);
const journalPath = join(tempDir, "orphans.jsonl");
process.env[ORPHAN_PROCESS_JOURNAL_ENV] = journalPath;
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
- const manager = new ReplKernelManager({ python, cwd: tempDir });
+ const manager = new ReplKernelManager({ python, cwd: tempDir, env });
try {
await expect(manager.execute("x")).rejects.toThrow(/Kernel exited before ready/);
@@ -70,12 +91,17 @@ describe("repl kernel parent watchdog", () => {
expect(readFileSync(envDump, "utf8")).toMatch(new RegExp(`^PRIME_AGENT_KERNEL_OWNER_PID=${process.pid}$`, "m"));
- // Self-exited child: the kill signals nothing, so the record must stay active.
+ // The append-only journal starts with an active ownership record. Bun can
+ // observe the child exit soon enough to append its inactive tombstone too.
await vi.waitFor(() => {
const records = readJournalRecords(journalPath);
- expect(records).toHaveLength(1);
+ expect(records.length).toBeGreaterThanOrEqual(1);
expect(records[0]?.ownerPid).toBe(process.pid);
expect(records[0]?.active).toBe(true);
+ expect(records.every((record) => record.pid === records[0]?.pid && record.ownerPid === process.pid)).toBe(
+ true,
+ );
+ if (records.length > 1) expect(records.at(-1)?.active).toBe(false);
});
});
@@ -287,9 +313,10 @@ function resolveReplPython(): string | null {
}
const replPython = resolveReplPython();
-const describeIf = replPython && process.platform !== "win32" ? describe : describe.skip;
+const describeIf =
+ replPython && process.platform !== "win32" && isTestTagEnabled("kernel-heavy") ? describe : describe.skip;
-describeIf("repl runtime outlives-owner watchdog (real runtime)", { tags: ["kernel-heavy"] }, () => {
+describeIf("repl runtime outlives-owner watchdog (real runtime)", () => {
it("runtime exits after its owner is SIGKILLed (stdin EOF watchdog)", async () => {
const dir = mkdtempSync(join(tmpdir(), "prime-agent-repl-watchdog-int-"));
const pidFile = join(dir, "runtime.pid");
diff --git a/packages/coding-agent/test/repl-kernel-startup.test.ts b/packages/coding-agent/test/repl-kernel-startup.test.ts
index 219453f729..e52605d8cf 100644
--- a/packages/coding-agent/test/repl-kernel-startup.test.ts
+++ b/packages/coding-agent/test/repl-kernel-startup.test.ts
@@ -78,12 +78,16 @@ describe("ReplKernelManager startup", () => {
const manager = new ReplKernelManager({ python, cwd: tempDir });
try {
- const startPromise = manager.start();
- const expectation = expect(startPromise).rejects.toThrow(/did not become ready within 30000ms/);
+ const startResult = manager
+ .start()
+ .then(() => undefined)
+ .catch((error: Error) => error);
await vi.advanceTimersByTimeAsync(30_000);
// The failure path runs a graceful shutdown bounded by its own deadline.
await vi.advanceTimersByTimeAsync(5_000);
- await expectation;
+ const startError = await startResult;
+ expect(startError).toBeInstanceOf(Error);
+ expect(startError?.message).toMatch(/did not become ready within 30000ms/);
} finally {
vi.useRealTimers();
errorSpy.mockRestore();
diff --git a/packages/coding-agent/test/repl-kernel-state-roundtrip.test.ts b/packages/coding-agent/test/repl-kernel-state-roundtrip.test.ts
index e08ece79a5..d0c0e0e3e3 100644
--- a/packages/coding-agent/test/repl-kernel-state-roundtrip.test.ts
+++ b/packages/coding-agent/test/repl-kernel-state-roundtrip.test.ts
@@ -4,6 +4,7 @@ import { homedir, tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { ReplKernelManager } from "../src/core/kernel/index.js";
+import { isTestTagEnabled } from "./test-tags.js";
function resolveReplPython(): string | null {
const candidates = [
@@ -20,9 +21,9 @@ function resolveReplPython(): string | null {
}
const python = resolveReplPython();
-const describeIfKernel = python ? describe : describe.skip;
+const describeIfKernel = python && isTestTagEnabled("kernel-heavy") ? describe : describe.skip;
-describeIfKernel("repl kernel state snapshot round-trip (real runtime)", { tags: ["kernel-heavy"] }, () => {
+describeIfKernel("repl kernel state snapshot round-trip (real runtime)", () => {
let dir = "";
let snapshotPath = "";
let manifestPath = "";
diff --git a/packages/coding-agent/test/rlm-ledger.test.ts b/packages/coding-agent/test/rlm-ledger.test.ts
index 2ebb9286dc..8bac64dc1f 100644
--- a/packages/coding-agent/test/rlm-ledger.test.ts
+++ b/packages/coding-agent/test/rlm-ledger.test.ts
@@ -11,8 +11,8 @@ import {
import { tmpdir } from "node:os";
const linkFailure = vi.hoisted(() => ({ code: undefined as string | undefined }));
-vi.mock("node:fs", async (importOriginal) => {
- const actual = await importOriginal();
+vi.mock("node:fs", () => {
+ const actual = require("node:fs");
return {
...actual,
linkSync: (
diff --git a/packages/coding-agent/test/rpc-example.ts b/packages/coding-agent/test/rpc-example.ts
index d82bd93492..be8c2df56d 100644
--- a/packages/coding-agent/test/rpc-example.ts
+++ b/packages/coding-agent/test/rpc-example.ts
@@ -7,7 +7,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* Interactive example of using coding-agent via RpcClient.
- * Usage: npx tsx test/rpc-example.ts
+ * Usage: bun test/rpc-example.ts
*/
async function main() {
diff --git a/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts b/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts
index 3c71c5f254..1bfa6328ec 100644
--- a/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts
+++ b/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env tsx
+#!/usr/bin/env bun
/**
* Manual SDK probe for OpenAI Codex prompt caching through the tool loop.
*
@@ -123,7 +123,7 @@ function parseArgs(argv: string[]): Args {
}
function printHelp(): void {
- console.log(`Usage: npx tsx test/sdk-codex-cache-probe-tool-loop.ts [options]
+ console.log(`Usage: bun test/sdk-codex-cache-probe-tool-loop.ts [options]
Options:
--turns Number of turns to run. Must be between ${MIN_TURNS} and ${MAX_TURNS}. Default: ${DEFAULT_TURNS}
diff --git a/packages/coding-agent/test/session-manager-flush.test.ts b/packages/coding-agent/test/session-manager-flush.test.ts
index 50ddafd555..4448bd04fc 100644
--- a/packages/coding-agent/test/session-manager-flush.test.ts
+++ b/packages/coding-agent/test/session-manager-flush.test.ts
@@ -30,8 +30,8 @@ const fsMocks = vi.hoisted(() => ({
renameSync: vi.fn(),
writeFileSync: vi.fn(),
}));
-vi.mock("node:fs", async (importOriginal) => {
- const actual = await importOriginal();
+vi.mock("node:fs", () => {
+ const actual = require("node:fs");
fsMocks.actualWriteFileSync = actual.writeFileSync;
fsMocks.chmodSync.mockImplementation(actual.chmodSync);
fsMocks.chownSync.mockImplementation(actual.chownSync);
diff --git a/packages/coding-agent/test/stdout-cleanliness.test.ts b/packages/coding-agent/test/stdout-cleanliness.test.ts
index 95b057b9ac..b889e50a44 100644
--- a/packages/coding-agent/test/stdout-cleanliness.test.ts
+++ b/packages/coding-agent/test/stdout-cleanliness.test.ts
@@ -6,7 +6,6 @@ import { afterEach, describe, expect, it } from "vitest";
import { ENV_AGENT_DIR } from "../src/config.js";
const cliPath = resolve(__dirname, "../src/cli.ts");
-const tsxPath = resolve(__dirname, "../../../node_modules/tsx/dist/cli.mjs");
const tempDirs: string[] = [];
@@ -55,12 +54,11 @@ async function runCli(args: string[]): Promise<{ stdout: string; stderr: string;
);
return await new Promise((resolvePromise, reject) => {
- const child = spawn(process.execPath, [tsxPath, cliPath, ...args], {
+ const child = spawn(process.execPath, [cliPath, ...args], {
cwd: projectDir,
env: {
...process.env,
[ENV_AGENT_DIR]: agentDir,
- TSX_TSCONFIG_PATH: resolve(__dirname, "../../../tsconfig.json"),
},
stdio: ["ignore", "pipe", "pipe"],
});
diff --git a/packages/coding-agent/test/streaming-render-debug.ts b/packages/coding-agent/test/streaming-render-debug.ts
index ffd4cc89b3..322c8ef59d 100644
--- a/packages/coding-agent/test/streaming-render-debug.ts
+++ b/packages/coding-agent/test/streaming-render-debug.ts
@@ -1,7 +1,7 @@
/**
* Debug script to reproduce streaming rendering issues.
* Uses real fixture data that caused the bug.
- * Run with: npx tsx test/streaming-render-debug.ts
+ * Run with: bun test/streaming-render-debug.ts
*/
import type { AssistantMessage } from "@earendil-works/pi-ai";
diff --git a/packages/coding-agent/test/suite/acp-features.test.ts b/packages/coding-agent/test/suite/acp-features.test.ts
index 5e92bc424d..33b0406f0c 100644
--- a/packages/coding-agent/test/suite/acp-features.test.ts
+++ b/packages/coding-agent/test/suite/acp-features.test.ts
@@ -765,7 +765,7 @@ describe("ACP mode preserves prime-agent features", () => {
expect(init.agentInfo).toMatchObject({ name: "prime-agent" });
expect(typeof init.agentInfo?.version).toBe("string");
// Namespaced only: unknown root keys are reserved for future ACP fields.
- expect(init._meta).toHaveProperty(PRIME_AGENT_META_NAMESPACE);
+ expect(init._meta).toHaveProperty([PRIME_AGENT_META_NAMESPACE]);
expect(Object.keys(init.agentCapabilities ?? {})).not.toContain("subagents");
// close is advertised, so a client knows it may release the session slot.
expect(init.agentCapabilities?.sessionCapabilities?.close).toBeDefined();
diff --git a/packages/coding-agent/test/suite/acp-mode.test.ts b/packages/coding-agent/test/suite/acp-mode.test.ts
index 7f386305b7..b7bc4ebb74 100644
--- a/packages/coding-agent/test/suite/acp-mode.test.ts
+++ b/packages/coding-agent/test/suite/acp-mode.test.ts
@@ -212,7 +212,7 @@ describe("ACP mode end to end", () => {
});
expect(init.protocolVersion).toBe(acp.PROTOCOL_VERSION);
expect(init.agentInfo?.name).toBe("prime-agent");
- expect(init._meta).toHaveProperty(PRIME_AGENT_META_NAMESPACE);
+ expect(init._meta).toHaveProperty([PRIME_AGENT_META_NAMESPACE]);
const session = await client.request("session/new", { cwd: harness.tempDir, mcpServers: [] });
expect(typeof session.sessionId).toBe("string");
diff --git a/packages/coding-agent/test/suite/agent-session-action-races.test.ts b/packages/coding-agent/test/suite/agent-session-action-races.test.ts
index 8239c1f2f2..1d55728e11 100644
--- a/packages/coding-agent/test/suite/agent-session-action-races.test.ts
+++ b/packages/coding-agent/test/suite/agent-session-action-races.test.ts
@@ -3,7 +3,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import type { CustomMessage } from "../../src/core/messages.js";
import type { ActionStore, SessionAction } from "../../src/core/session-action-store.js";
import { createHarness, getMessageText, getUserTexts, type Harness } from "./harness.js";
-import { createDeferred } from "./scheduling.js";
+import { createDeferred, expectPromiseRejection } from "./scheduling.js";
type ActionKind = "turn" | "command";
@@ -225,7 +225,7 @@ describe("AgentSession action commit-fence races", () => {
schedule.mockRestore();
internals._scheduleSessionInputPump();
await vi.waitFor(() => expect(internals._actionStore.unfinishedActions()[0]?.lifecycle.state).toBe("selected"));
- const rejection = expect(completion).rejects.toThrow("cleared before delivery");
+ const rejection = expectPromiseRejection(completion, "cleared before delivery");
expect(harness.session.clearQueue().followUp).toEqual([text]);
heldFence.release();
diff --git a/packages/coding-agent/test/suite/agent-session-compaction-continuation.test.ts b/packages/coding-agent/test/suite/agent-session-compaction-continuation.test.ts
index 06c9a215e4..bd57754811 100644
--- a/packages/coding-agent/test/suite/agent-session-compaction-continuation.test.ts
+++ b/packages/coding-agent/test/suite/agent-session-compaction-continuation.test.ts
@@ -16,6 +16,7 @@ import { Type } from "typebox";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { AgentSession } from "../../src/core/agent-session.js";
import { createHarness, type Harness } from "./harness.js";
+import { expectPromiseRejection } from "./scheduling.js";
type SessionInternals = {
_shouldStopAfterTurn: (context: ShouldStopAfterTurnContext) => boolean | Promise;
@@ -250,7 +251,7 @@ describe("compaction continuation", () => {
sessionInternals._schedulePostCompactionContinue();
const idle = harness.session.waitForHeadlessIdle();
- const rejectedIdle = expect(idle).rejects.toThrow("continuation failed");
+ const rejectedIdle = expectPromiseRejection(idle, "continuation failed");
await vi.advanceTimersByTimeAsync(100);
await rejectedIdle;
diff --git a/packages/coding-agent/test/suite/agent-session-compaction.test.ts b/packages/coding-agent/test/suite/agent-session-compaction.test.ts
index f19ce5b304..044f4715f5 100644
--- a/packages/coding-agent/test/suite/agent-session-compaction.test.ts
+++ b/packages/coding-agent/test/suite/agent-session-compaction.test.ts
@@ -158,10 +158,10 @@ describe("AgentSession compaction characterization", () => {
tokensBefore: result.tokensBefore,
fromHook: false,
});
- expect(harness.session.messages[0]).toMatchObject({
- role: "compactionSummary",
- summary: expect.stringContaining("model-generated summary"),
- });
+ const compactedMessage = harness.session.messages[0];
+ expect(compactedMessage?.role).toBe("compactionSummary");
+ if (compactedMessage?.role !== "compactionSummary") throw new Error("Expected compaction summary");
+ expect(compactedMessage.summary).toContain("model-generated summary");
expect(harness.eventsOfType("compaction_start")).toEqual([expect.objectContaining({ reason: "manual" })]);
expect(harness.eventsOfType("compaction_end")).toEqual([
expect.objectContaining({
@@ -1336,7 +1336,6 @@ describe("AgentSession compaction characterization", () => {
});
it("waits for threshold-compaction autonomous continuations before finishing prompt", async () => {
- vi.useFakeTimers();
const harness = await createHarness({
autonomous: {
enabled: true,
@@ -1356,7 +1355,6 @@ describe("AgentSession compaction characterization", () => {
const promptPromise = harness.session.prompt("make the change");
await vi.waitFor(() => expect(harness.session.getAutonomousStatus().continuationsUsed).toBe(1));
- await vi.advanceTimersByTimeAsync(100);
await promptPromise;
expect(harness.session.getAutonomousStatus()).toMatchObject({
@@ -1475,12 +1473,12 @@ describe("AgentSession compaction characterization", () => {
internals._persistCompactionOutcome("requested", "failed", "Requested compaction failed"),
).not.toThrow();
// The live outcome message discloses that it was not saved.
- expect(harness.session.messages.at(-1)).toMatchObject({
- role: "custom",
- customType: "compaction_outcome",
- content: expect.stringContaining("could not be saved to session history"),
- details: { reason: "requested", outcome: "failed" },
- });
+ const liveOutcome = harness.session.messages.at(-1);
+ expect(liveOutcome?.role).toBe("custom");
+ if (liveOutcome?.role !== "custom") throw new Error("Expected custom compaction outcome");
+ expect(liveOutcome.customType).toBe("compaction_outcome");
+ expect(liveOutcome.content).toContain("could not be saved to session history");
+ expect(liveOutcome.details).toEqual({ reason: "requested", outcome: "failed" });
// In-memory state is fully rolled back: no outcome entry, same leaf and entries.
expect(harness.sessionManager.getLeafId()).toBe(persistedLeafId);
expect(harness.sessionManager.getEntries()).toEqual(persistedEntries);
@@ -1497,11 +1495,11 @@ describe("AgentSession compaction characterization", () => {
);
// The unpersisted disclosure survives context rebuilds (e.g. thinking toggle).
const rebuilt = harness.session.buildSessionContext();
- expect(rebuilt.messages.at(-1)).toMatchObject({
- role: "custom",
- customType: "compaction_outcome",
- content: expect.stringContaining("could not be saved to session history"),
- });
+ const rebuiltOutcome = rebuilt.messages.at(-1);
+ expect(rebuiltOutcome?.role).toBe("custom");
+ if (rebuiltOutcome?.role !== "custom") throw new Error("Expected custom compaction outcome");
+ expect(rebuiltOutcome.customType).toBe("compaction_outcome");
+ expect(rebuiltOutcome.content).toContain("could not be saved to session history");
// Cross a millisecond boundary so the later turn's timestamp is strictly newer.
await new Promise((resolve) => setTimeout(resolve, 5));
diff --git a/packages/coding-agent/test/suite/agent-session-prompt.test.ts b/packages/coding-agent/test/suite/agent-session-prompt.test.ts
index 8ade5795d0..eaf1ba6903 100644
--- a/packages/coding-agent/test/suite/agent-session-prompt.test.ts
+++ b/packages/coding-agent/test/suite/agent-session-prompt.test.ts
@@ -10,7 +10,7 @@ import type { PromptTemplate } from "../../src/core/prompt-templates.js";
import { createSyntheticSourceInfo } from "../../src/core/source-info.js";
import { createTestResourceLoader } from "../utilities.js";
import { createHarness, getAssistantTexts, getMessageText, getUserTexts, type Harness } from "./harness.js";
-import { createDeferred, createWaitingHarness, gatedHook } from "./scheduling.js";
+import { createDeferred, createWaitingHarness, expectPromiseRejection, gatedHook } from "./scheduling.js";
function gateNextAgentStart(harness: Harness): { reached: Promise; release(): void } {
let markReached = () => {};
@@ -1166,8 +1166,8 @@ stale post-hook extension instructions`,
const delivery = harness.session.waitForAgentMessagePromptDelivery(agentMessageId);
const accepted = harness.session.acceptAgentMessagePrompt(agentPrompt, { expandPromptTemplates: false });
- const acceptedRejection = expect(accepted).rejects.toThrow("cleared before delivery");
- const deliveryRejection = expect(delivery).rejects.toThrow("cleared before delivery");
+ const acceptedRejection = expectPromiseRejection(accepted, "cleared before delivery");
+ const deliveryRejection = expectPromiseRejection(delivery, "cleared before delivery");
await admitted;
expect(harness.session.clearQueuedUserMessagesMatching((text) => text.includes(agentMessageId))).toEqual({
@@ -1232,7 +1232,7 @@ stale post-hook extension instructions`,
const prompt =
"Agent-to-agent message received.\nSource: agent_message\nTo: Target, active target, session session-target\nMessage id: agentmsg_idle_cleanup\n\ncancel me";
const accepted = harness.session.acceptAgentMessagePrompt(prompt, { expandPromptTemplates: false });
- const rejected = expect(accepted).rejects.toThrow("cleared before delivery");
+ const rejected = expectPromiseRejection(accepted, "cleared before delivery");
await Promise.all([eventQueueReached.promise, dispatchGate.reached]);
harness.session.clearQueuedUserMessagesMatching((text) => text === prompt);
@@ -2053,8 +2053,8 @@ stale post-hook extension instructions`,
followUp: [clearedAgentPrompt],
});
admission.release();
- await expect(accepted).rejects.toThrow("cleared before delivery");
- await expect(delivery).rejects.toThrow("cleared before delivery");
+ await expectPromiseRejection(accepted, "cleared before delivery");
+ await expectPromiseRejection(delivery, "cleared before delivery");
await harness.session.agent.waitForIdle();
await (harness.session as unknown as { _agentEventQueue: Promise })._agentEventQueue;
const persistedAfter = harness.sessionManager.getEntries().filter((entry) => entry.type === "message").length;
diff --git a/packages/coding-agent/test/suite/agent-session-queue.test.ts b/packages/coding-agent/test/suite/agent-session-queue.test.ts
index 14e31404a1..e800414d79 100644
--- a/packages/coding-agent/test/suite/agent-session-queue.test.ts
+++ b/packages/coding-agent/test/suite/agent-session-queue.test.ts
@@ -30,7 +30,13 @@ import {
} from "../../src/core/refinement/index.js";
import { parseSessionSlashCommand } from "../../src/core/slash-commands.js";
import { createHarness, getAssistantTexts, getMessageText, getUserTexts, type Harness } from "./harness.js";
-import { createDeferred, createWaitingHarness, gatedHook, withStreaming } from "./scheduling.js";
+import {
+ createDeferred,
+ createWaitingHarness,
+ expectPromiseRejection,
+ gatedHook,
+ withStreaming,
+} from "./scheduling.js";
type AutoRefineReason = "turn_interval" | "compact";
@@ -1554,7 +1560,7 @@ describe("AgentSession queue characterization", () => {
{ customType: "hidden-trigger", content: "hidden queued prompt", display: false },
{ triggerTurn: true },
);
- const hiddenRejection = expect(hidden).rejects.toThrow("Prompt aborted before delivery.");
+ const hiddenRejection = expectPromiseRejection(hidden, "Prompt aborted before delivery.");
await vi.waitFor(() => expect(harness.session.getSessionActionRecoverySnapshot().actions).toHaveLength(2));
expect(harness.session.getFollowUpMessages()).toEqual(["visible queued prompt"]);
@@ -2023,8 +2029,8 @@ describe("AgentSession queue characterization", () => {
streamingBehavior: "followUp",
resumeIfIdle: true,
});
- const firstCompletionRejection = expect(firstCompletion).rejects.toThrow("cleared before delivery");
- const completionRejection = expect(completion).rejects.toThrow("cleared before delivery");
+ const firstCompletionRejection = expectPromiseRejection(firstCompletion, "cleared before delivery");
+ const completionRejection = expectPromiseRejection(completion, "cleared before delivery");
withStreaming(harness, false);
await waitForPreparation;
gatePreparation = false;
@@ -2062,7 +2068,7 @@ describe("AgentSession queue characterization", () => {
streamingBehavior: "followUp",
resumeIfIdle: true,
});
- const terminalRejection = expect(terminalCompletion).rejects.toThrow("No API key");
+ const terminalRejection = expectPromiseRejection(terminalCompletion, "No API key");
await vi.waitFor(() => expect(authHarness.session.getFollowUpMessages()).toEqual(["cannot start"]));
withStreaming(authHarness, false);
await authHarness.session.waitForSessionInputIdle();
@@ -2237,10 +2243,11 @@ describe("AgentSession queue characterization", () => {
const id = "agentmsg_command_append_failed";
const delivery = harness.session.waitForAgentMessagePromptDelivery(id);
const completion = harness.session.promptAndWait("/autonomous status", { agentMessageId: id });
+ const deliveryFailure = expectPromiseRejection(delivery, "durable invocation append failed");
+ const completionFailure = expectPromiseRejection(completion, "durable invocation append failed");
pause.release();
- await expect(delivery).rejects.toThrow("durable invocation append failed");
- await expect(completion).rejects.toThrow("durable invocation append failed");
+ await Promise.all([deliveryFailure, completionFailure]);
// The failed append must roll back fully: no live-only command message and
// no unsaved leaf, so later durable entries persist cleanly.
@@ -2900,7 +2907,7 @@ describe("AgentSession queue characterization", () => {
const lateAgentMessage = harness.session.acceptAgentMessagePrompt(
agentPromptText("agentmsg_after_abort", "late child result"),
);
- const lateRejection = expect(lateAgentMessage).rejects.toThrow("queued session input is suspended");
+ const lateRejection = expectPromiseRejection(lateAgentMessage, "queued session input is suspended");
await new Promise((resolve) => setTimeout(resolve, 0));
await harness.session.prompt("explicit user resume");
@@ -2916,7 +2923,7 @@ describe("AgentSession queue characterization", () => {
const lateAgentMessage = harness.session.acceptAgentMessagePrompt(
agentPromptText("agentmsg_resume_queue", "late child result"),
);
- const lateRejection = expect(lateAgentMessage).rejects.toThrow("queued session input is suspended");
+ const lateRejection = expectPromiseRejection(lateAgentMessage, "queued session input is suspended");
await new Promise((resolve) => setTimeout(resolve, 0));
harness.session.resumeQueuedWork();
@@ -3251,7 +3258,8 @@ describe("AgentSession scheduler scenarios", () => {
await harness.session.queueAgentMessagePrompt(agentPrompt, "steer");
// Phase 3: keyed duplicates reject both agent-message outcome legs.
- const dupDelivery = expect(harness.session.waitForAgentMessagePromptDelivery("agentmsg_s2_dup")).rejects.toThrow(
+ const dupDelivery = expectPromiseRejection(
+ harness.session.waitForAgentMessagePromptDelivery("agentmsg_s2_dup"),
"equivalent follow-up is already pending",
);
await expect(
@@ -3528,9 +3536,10 @@ describe("AgentSession scheduler scenarios", () => {
const failedId = "agentmsg_failed_command";
const failedDelivery = harness.session.waitForAgentMessagePromptDelivery(failedId);
const failedCompletion = harness.session.promptAndWait("/refine --local", { agentMessageId: failedId });
+ const failedCompletionRejection = expectPromiseRejection(failedCompletion, "refine execution failed");
failedPause.release();
await expect(failedDelivery).resolves.toBeUndefined();
- await expect(failedCompletion).rejects.toThrow("refine execution failed");
+ await failedCompletionRejection;
});
it("S6: auto-refine reviews after real turns, defers while busy, and drops reviews on navigation", async () => {
diff --git a/packages/coding-agent/test/suite/daemon-serialized-refine-process.test.ts b/packages/coding-agent/test/suite/daemon-serialized-refine-process.test.ts
index 6b25ce1178..cd5fc9215a 100644
--- a/packages/coding-agent/test/suite/daemon-serialized-refine-process.test.ts
+++ b/packages/coding-agent/test/suite/daemon-serialized-refine-process.test.ts
@@ -43,8 +43,6 @@ import {
} from "../../src/modes/daemon/daemon-worker-protocol.js";
const cliPath = resolve(__dirname, "../../src/cli.ts");
-const tsxPath = resolve(__dirname, "../../../../node_modules/tsx/dist/cli.mjs");
-const repoTsconfigPath = resolve(__dirname, "../../../../tsconfig.json");
const fauxRefineExtensionPath = resolve(__dirname, "../fixtures/eng-4685-faux-refine-extension.ts");
const eventOrderExtensionPath = resolve(__dirname, "../fixtures/eng-4685-event-order-extension.ts");
const children = new Set();
@@ -83,10 +81,9 @@ async function runCli(
args: string[],
options: { agentDir: string; stdin?: string; environment?: NodeJS.ProcessEnv },
): Promise<{ code: number | null; signal: NodeJS.Signals | null; stdout: string; stderr: string }> {
- const child = spawn(process.execPath, [tsxPath, cliPath, ...args], {
+ const child = spawn(process.execPath, [cliPath, ...args], {
env: {
...process.env,
- TSX_TSCONFIG_PATH: repoTsconfigPath,
[ENV_AGENT_DIR]: options.agentDir,
PI_SKIP_VERSION_CHECK: "1",
PRIME_AGENT_INTERNAL_LEGACY_OWNED_WORKER_FRONTEND: "0",
diff --git a/packages/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts b/packages/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts
index 81a7a26823..2fea180660 100644
--- a/packages/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts
+++ b/packages/coding-agent/test/suite/regressions/2791-fswatch-error-crash.test.ts
@@ -50,18 +50,14 @@ describe("issue #2791 fs.watch error event crashes process", () => {
writeFileSync(
scriptPath,
`
-import { setTheme, stopThemeWatcher } from "${themeModulePath}";
-
process.env[${JSON.stringify(ENV_AGENT_DIR)}] = ${JSON.stringify(agentDir)};
+const { getActiveThemeWatcher, setTheme, stopThemeWatcher } = await import("${themeModulePath}");
setTheme("custom-test", true);
-
-// Find the FSWatcher among active handles
-const handles = (process as any)._getActiveHandles();
-const fsWatcher = handles.find((h: any) => h.constructor?.name === "FSWatcher");
+const fsWatcher = getActiveThemeWatcher();
if (!fsWatcher) {
- process.stderr.write("no FSWatcher found among active handles\\n");
+ process.stderr.write("theme did not create an FSWatcher\\n");
process.exit(2);
}
@@ -88,7 +84,7 @@ process.exit(0);
let stderr = "";
let exitCode: number;
try {
- _stdout = execFileSync("npx", ["tsx", scriptPath], {
+ _stdout = execFileSync(process.execPath, [scriptPath], {
timeout: 10000,
encoding: "utf-8",
env: { ...process.env, [ENV_AGENT_DIR]: agentDir },
diff --git a/packages/coding-agent/test/suite/regressions/4600-supervisor-singleton.test.ts b/packages/coding-agent/test/suite/regressions/4600-supervisor-singleton.test.ts
index c526a4c443..81613ce544 100644
--- a/packages/coding-agent/test/suite/regressions/4600-supervisor-singleton.test.ts
+++ b/packages/coding-agent/test/suite/regressions/4600-supervisor-singleton.test.ts
@@ -70,8 +70,6 @@ interface CleanupProcessIdentity {
const fixturePath = resolve(__dirname, "../../fixtures/eng-4600-supervisor-fixture.ts");
const fauxExtensionPath = resolve(__dirname, "../../fixtures/eng-4600-faux-extension.ts");
const cliPath = resolve(__dirname, "../../../src/cli.ts");
-const tsxPath = resolve(__dirname, "../../../../../node_modules/tsx/dist/cli.mjs");
-const tsconfigPath = resolve(__dirname, "../../../../../tsconfig.json");
const supervisorRegistryDirEnv = "PRIME_AGENT_INTERNAL_DAEMON_SUPERVISOR_REGISTRY_DIR";
const handles = new Set();
const harnesses: Harness[] = [];
@@ -128,7 +126,7 @@ function spawnFixture(
paths: { agentDir: string; descriptorDir: string; registryDir: string; socketPath: string },
options: { extraEnv?: NodeJS.ProcessEnv; generation?: string } = {},
): FixtureHandle {
- const child = spawn(process.execPath, [tsxPath, fixturePath], {
+ const child = spawn(process.execPath, [fixturePath], {
cwd: paths.agentDir,
env: {
...process.env,
@@ -142,7 +140,6 @@ function spawnFixture(
ENG_4600_REGISTRY_DIR: paths.registryDir,
ENG_4600_SOCKET_PATH: paths.socketPath,
PI_OFFLINE: "1",
- TSX_TSCONFIG_PATH: tsconfigPath,
},
stdio: ["ignore", "pipe", "pipe", "ipc"],
});
@@ -169,7 +166,7 @@ function spawnRealSupervisor(
): FixtureHandle {
const child = spawn(
process.execPath,
- [tsxPath, cliPath, "--mode", "daemon", "--daemon-socket", paths.socketPath, "--offline"],
+ [cliPath, "--mode", "daemon", "--daemon-socket", paths.socketPath, "--offline"],
{
cwd: paths.agentDir,
env: {
@@ -178,7 +175,6 @@ function spawnRealSupervisor(
[supervisorRegistryDirEnv]: paths.registryDir,
[ENV_AGENT_DIR]: paths.agentDir,
PI_OFFLINE: "1",
- TSX_TSCONFIG_PATH: tsconfigPath,
},
stdio: ["ignore", "pipe", "pipe"],
},
diff --git a/packages/coding-agent/test/suite/regressions/4602-snapshot-transfer-idempotency.test.ts b/packages/coding-agent/test/suite/regressions/4602-snapshot-transfer-idempotency.test.ts
index 56b99ebe0b..0b7c6e3d30 100644
--- a/packages/coding-agent/test/suite/regressions/4602-snapshot-transfer-idempotency.test.ts
+++ b/packages/coding-agent/test/suite/regressions/4602-snapshot-transfer-idempotency.test.ts
@@ -20,6 +20,8 @@ import { SnapshotTranscriptCache } from "../../../src/modes/daemon/snapshot-tran
import { type PrivateFrame, PrivateFrameDecoder } from "../../../src/modes/session-worker/private-framing.js";
import { seedSupervisorRoster } from "../../fixtures/roster-seed.js";
+import { expectPromiseRejection } from "../scheduling.js";
+
const activeSessionId = "active-4602";
const snapshotId = "snapshot-4602";
@@ -526,7 +528,7 @@ describe("ENG-4602 snapshot transfer containment", () => {
const unhandled = vi.fn();
process.on("unhandledRejection", unhandled);
try {
- const validationFailure = expect(validation).rejects.toThrow("stopped during snapshot transfer");
+ const validationFailure = expectPromiseRejection(validation, "stopped during snapshot transfer");
await internals.stopWorker(worker, false);
await validationFailure;
await catchup;
diff --git a/packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts b/packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts
index 19ab4a7f43..f362045402 100644
--- a/packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts
+++ b/packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts
@@ -8,6 +8,7 @@ import {
readFileSync,
renameSync,
rmSync,
+ symlinkSync,
writeFileSync,
} from "node:fs";
import { createConnection, type Socket } from "node:net";
@@ -93,8 +94,6 @@ interface TestPaths {
const fixturePath = resolve(__dirname, "../../fixtures/eng-4600-supervisor-fixture.ts");
const fauxExtensionPath = resolve(__dirname, "../../fixtures/eng-4600-faux-extension.ts");
const cliPath = resolve(__dirname, "../../../src/cli.ts");
-const tsxPath = resolve(__dirname, "../../../../../node_modules/tsx/dist/cli.mjs");
-const tsconfigPath = resolve(__dirname, "../../../../../tsconfig.json");
const supervisorRegistryDirEnv = "PRIME_AGENT_INTERNAL_DAEMON_SUPERVISOR_REGISTRY_DIR";
const handles = new Set();
const harnesses: Harness[] = [];
@@ -126,7 +125,14 @@ async function createPaths(): Promise {
const harness = await createHarness();
harnesses.push(harness);
const executablePath = join(harness.tempDir, APP_NAME);
- linkSync(process.execPath, executablePath);
+ try {
+ linkSync(process.execPath, executablePath);
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== "EPERM" && (error as NodeJS.ErrnoException).code !== "EXDEV") {
+ throw error;
+ }
+ symlinkSync(process.execPath, executablePath);
+ }
const socketTmpDir = `/tmp/eng-4603-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
mkdirSync(socketTmpDir, { recursive: true, mode: 0o700 });
socketTempDirs.add(socketTmpDir);
@@ -147,7 +153,7 @@ async function createPaths(): Promise {
function spawnSupervisor(paths: TestPaths): ProcessHandle {
return trackProcess(
- spawn(paths.executablePath, [tsxPath, fixturePath], {
+ spawn(paths.executablePath, [fixturePath], {
cwd: paths.agentDir,
env: {
...process.env,
@@ -160,7 +166,6 @@ function spawnSupervisor(paths: TestPaths): ProcessHandle {
ENG_4600_SOCKET_PATH: paths.socketPath,
PI_OFFLINE: "1",
TMPDIR: paths.socketTmpDir,
- TSX_TSCONFIG_PATH: tsconfigPath,
},
stdio: ["ignore", "pipe", "pipe", "ipc"],
}),
@@ -175,26 +180,21 @@ function spawnStandaloneWorker(
extraEnv: NodeJS.ProcessEnv = {},
): ProcessHandle {
return trackProcess(
- spawn(
- paths.executablePath,
- [tsxPath, cliPath, "--mode", "daemon", "--daemon-socket", workerSocketPath, "--offline"],
- {
- cwd: paths.agentDir,
- env: {
- ...process.env,
- ...extraEnv,
- [supervisorRegistryDirEnv]: paths.registryDir,
- [ENV_AGENT_DIR]: paths.agentDir,
- [DAEMON_WORKER_ROLE_ENV]: "1",
- [DAEMON_WORKER_TOKEN_ENV]: token,
- [DAEMON_WORKER_ACTIVE_SESSION_ID_ENV]: "eng-4603-worker",
- [DAEMON_WORKER_SUPERVISOR_SOCKET_ENV]: paths.socketPath,
- PI_OFFLINE: "1",
- TSX_TSCONFIG_PATH: tsconfigPath,
- },
- stdio: ["ignore", "pipe", "pipe"],
+ spawn(paths.executablePath, [cliPath, "--mode", "daemon", "--daemon-socket", workerSocketPath, "--offline"], {
+ cwd: paths.agentDir,
+ env: {
+ ...process.env,
+ ...extraEnv,
+ [supervisorRegistryDirEnv]: paths.registryDir,
+ [ENV_AGENT_DIR]: paths.agentDir,
+ [DAEMON_WORKER_ROLE_ENV]: "1",
+ [DAEMON_WORKER_TOKEN_ENV]: token,
+ [DAEMON_WORKER_ACTIVE_SESSION_ID_ENV]: "eng-4603-worker",
+ [DAEMON_WORKER_SUPERVISOR_SOCKET_ENV]: paths.socketPath,
+ PI_OFFLINE: "1",
},
- ),
+ stdio: ["ignore", "pipe", "pipe"],
+ }),
"worker",
);
}
@@ -677,7 +677,7 @@ async function runCli(
extraEnv: NodeJS.ProcessEnv = {},
): Promise<{ code: number; stdout: string; stderr: string }> {
const handle = trackProcess(
- spawn(process.execPath, [tsxPath, cliPath, ...args], {
+ spawn(process.execPath, [cliPath, ...args], {
cwd: paths.agentDir,
env: {
...process.env,
@@ -686,7 +686,6 @@ async function runCli(
[ENV_AGENT_DIR]: paths.agentDir,
PI_OFFLINE: "1",
TMPDIR: paths.socketTmpDir,
- TSX_TSCONFIG_PATH: tsconfigPath,
},
stdio: ["ignore", "pipe", "pipe"],
}),
@@ -1066,7 +1065,9 @@ describe("ENG-4603 worker recovery convergence", () => {
expect(listenersBeforeShutdown).toContain(`p${successor.child.pid}`);
const shutdown = await runCli(paths, ["shutdown", "--force", "--json"], 60_000, lsofEnvironment);
- expect(shutdown.code).toBe(0);
+ if (shutdown.code !== 0) {
+ throw new Error(`Shutdown failed with code ${shutdown.code}: ${shutdown.stderr}\n${shutdown.stdout}`);
+ }
const shutdownResult = JSON.parse(shutdown.stdout) as { stopped: unknown[]; failed: unknown[] };
const survivingIdentities = [
{ pid: predecessor.child.pid!, processStartId: predecessorStartId },
diff --git a/packages/coding-agent/test/suite/regressions/4606-update-restart-coordinator.test.ts b/packages/coding-agent/test/suite/regressions/4606-update-restart-coordinator.test.ts
index 26ce76e1b5..4546033b2e 100644
--- a/packages/coding-agent/test/suite/regressions/4606-update-restart-coordinator.test.ts
+++ b/packages/coding-agent/test/suite/regressions/4606-update-restart-coordinator.test.ts
@@ -29,8 +29,6 @@ interface SupervisorOwnerRecord {
const cliPath = resolve(__dirname, "../../../src/cli.ts");
const fauxExtensionPath = resolve(__dirname, "../../fixtures/eng-4600-faux-extension.ts");
const launcherFixturePath = resolve(__dirname, "../../fixtures/eng-4606-update-launcher.ts");
-const tsxPath = resolve(__dirname, "../../../../../node_modules/tsx/dist/cli.mjs");
-const tsconfigPath = resolve(__dirname, "../../../../../tsconfig.json");
const supervisorRegistryDirEnv = "PRIME_AGENT_INTERNAL_DAEMON_SUPERVISOR_REGISTRY_DIR";
const supervisors = new Set();
const harnesses: Harness[] = [];
@@ -49,7 +47,7 @@ function spawnSupervisor(paths: {
}): SupervisorHandle {
const child = spawn(
process.execPath,
- [tsxPath, cliPath, "--mode", "daemon", "--daemon-socket", paths.socketPath, "--offline"],
+ [cliPath, "--mode", "daemon", "--daemon-socket", paths.socketPath, "--offline"],
{
cwd: paths.agentDir,
env: {
@@ -61,9 +59,7 @@ function spawnSupervisor(paths: {
ENG_4606_COMPLETION_PATH: paths.completionPath,
ENG_4606_PID_PATH: paths.pidPath,
ENG_4606_SOCKET_PATH: paths.socketPath,
- ENG_4606_TSX_PATH: tsxPath,
PI_OFFLINE: "1",
- TSX_TSCONFIG_PATH: tsconfigPath,
},
stdio: ["ignore", "pipe", "pipe"],
},
@@ -190,21 +186,11 @@ async function withSourceCliEntrypoint(action: () => Promise): Promise
if (!previousEntrypoint) {
throw new Error("Test process has no CLI entrypoint");
}
- const previousExecArgv = [...process.execArgv];
- const previousTsconfigPath = process.env.TSX_TSCONFIG_PATH;
process.argv[1] = cliPath;
- process.execArgv.splice(0, process.execArgv.length, tsxPath);
- process.env.TSX_TSCONFIG_PATH = tsconfigPath;
try {
return await action();
} finally {
process.argv[1] = previousEntrypoint;
- process.execArgv.splice(0, process.execArgv.length, ...previousExecArgv);
- if (previousTsconfigPath === undefined) {
- delete process.env.TSX_TSCONFIG_PATH;
- } else {
- process.env.TSX_TSCONFIG_PATH = previousTsconfigPath;
- }
}
}
@@ -276,9 +262,10 @@ describe("ENG-4606 update restart coordinator", () => {
vi.useFakeTimers();
try {
- vi.setSystemTime(new Date("2026-07-14T00:00:00.000Z"));
- const writer = new DaemonUpdateRestartStatusWriter(statusPath, "test-request", "/tmp/daemon.sock");
+ let now = new Date("2026-07-14T00:00:00.000Z");
+ const writer = new DaemonUpdateRestartStatusWriter(statusPath, "test-request", "/tmp/daemon.sock", () => now);
const stopHeartbeat = writer.startHeartbeat();
+ now = new Date("2026-07-14T00:00:05.000Z");
vi.advanceTimersByTime(5000);
stopHeartbeat();
@@ -385,7 +372,7 @@ describe("ENG-4606 update restart coordinator", () => {
),
);
const originalActiveSessionId = created.activeSessionId ?? created.id;
- const updateCommand = [process.execPath, tsxPath, launcherFixturePath].map(shellQuote).join(" ");
+ const updateCommand = [process.execPath, launcherFixturePath].map(shellQuote).join(" ");
const executeResponse = await client.request(
{ type: "execute_bash", activeSessionId: originalActiveSessionId, command: updateCommand },
5000,
diff --git a/packages/coding-agent/test/suite/regressions/4685-daemon-client-modes.test.ts b/packages/coding-agent/test/suite/regressions/4685-daemon-client-modes.test.ts
index 1dee7be89c..2d05b0720b 100644
--- a/packages/coding-agent/test/suite/regressions/4685-daemon-client-modes.test.ts
+++ b/packages/coding-agent/test/suite/regressions/4685-daemon-client-modes.test.ts
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { fauxAssistantMessage } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
-import { createCliSubprocessEnv, createCliSubprocessLaunchSpec } from "../../../src/cli/subprocess-launch.js";
+import { createCliSubprocessLaunchSpec } from "../../../src/cli/subprocess-launch.js";
import { ENV_AGENT_DIR } from "../../../src/config.js";
import type { AutonomousRuntimeState } from "../../../src/core/autonomous.js";
import type { DaemonSocketClient } from "../../../src/modes/daemon/active-session-state.js";
@@ -19,8 +19,6 @@ const fixturePath = resolve(__dirname, "../../fixtures/rpc-connection-mode-fixtu
const fauxExtensionPath = resolve(__dirname, "../../fixtures/eng-4600-faux-extension.ts");
const rpcEofFauxExtensionPath = resolve(__dirname, "../../fixtures/rpc-eof-faux-extension.ts");
const cliPath = resolve(__dirname, "../../../src/cli.ts");
-const tsxPath = resolve(__dirname, "../../../../../node_modules/tsx/dist/cli.mjs");
-const repoTsconfigPath = resolve(__dirname, "../../../../../tsconfig.json");
const children = new Set();
const harnesses: Harness[] = [];
const daemonSockets = new Set();
@@ -68,10 +66,9 @@ async function runCli(
args: string[],
options: { agentDir: string; stdin?: string; environment?: NodeJS.ProcessEnv },
): Promise {
- const child = spawn(process.execPath, [tsxPath, cliPath, ...args], {
+ const child = spawn(process.execPath, [cliPath, ...args], {
env: {
...process.env,
- TSX_TSCONFIG_PATH: repoTsconfigPath,
[ENV_AGENT_DIR]: options.agentDir,
PI_SKIP_VERSION_CHECK: "1",
PRIME_AGENT_INTERNAL_LEGACY_OWNED_WORKER_FRONTEND: "0",
@@ -114,8 +111,8 @@ async function runRpc(
commands: unknown[],
options: { trailingNewline?: boolean } = {},
): Promise<{ stdout: object[]; stderr: string }> {
- const child = spawn(process.execPath, [tsxPath, fixturePath], {
- env: { ...process.env, TSX_TSCONFIG_PATH: repoTsconfigPath },
+ const child = spawn(process.execPath, [fixturePath], {
+ env: { ...process.env },
stdio: ["pipe", "pipe", "pipe"],
});
children.add(child);
@@ -233,12 +230,9 @@ describe("ENG-4685 daemon-backed client modes", () => {
});
it("resolves source subprocesses independently of a spaced runtime cwd", () => {
- const entrypoint = resolve(__dirname, "../../../src/cli.ts");
const launch = createCliSubprocessLaunchSpec([], process.execPath, [], "packages/coding-agent/src/cli.ts");
- const environment = createCliSubprocessEnv({}, entrypoint, ["--import", "tsx"]);
expect(launch.args[0]).toBe(resolve("packages/coding-agent/src/cli.ts"));
- expect(environment.TSX_TSCONFIG_PATH).toBe(repoTsconfigPath);
});
it("launches real daemon workers for every migrated client surface", async () => {
diff --git a/packages/coding-agent/test/suite/regressions/617-subagent-terminal-agent-message.test.ts b/packages/coding-agent/test/suite/regressions/617-subagent-terminal-agent-message.test.ts
index 7f04a5169f..ad7b6bec6e 100644
--- a/packages/coding-agent/test/suite/regressions/617-subagent-terminal-agent-message.test.ts
+++ b/packages/coding-agent/test/suite/regressions/617-subagent-terminal-agent-message.test.ts
@@ -50,7 +50,7 @@ describe("#617 subagent terminal agent messages", () => {
const spawned = await parent.session.runRlmChild("finish without replying", { name: childSessionName });
- await expect.poll(() => terminalNotices(parent!.session.messages)).toHaveLength(1);
+ await vi.waitFor(() => expect(terminalNotices(parent!.session.messages)).toHaveLength(1));
expect(sendAgentMessage).not.toHaveBeenCalled();
expect(terminalNotices(parent.session.messages)[0]).toMatchObject({
customType: "rlm_child_terminal_notice",
diff --git a/packages/coding-agent/test/suite/regressions/623-acp-canonical-cwd.test.ts b/packages/coding-agent/test/suite/regressions/623-acp-canonical-cwd.test.ts
index c975a5a116..ae0e0f8bc2 100644
--- a/packages/coding-agent/test/suite/regressions/623-acp-canonical-cwd.test.ts
+++ b/packages/coding-agent/test/suite/regressions/623-acp-canonical-cwd.test.ts
@@ -88,7 +88,7 @@ describe("#623 ACP canonical cwd comparison", () => {
expect(cwdMeta(created)).toBeUndefined();
}, 30_000);
- it.runIf(caseVariantCwd !== undefined)(
+ it.skipIf(caseVariantCwd === undefined)(
"uses filesystem identity when realpath preserves different component casing",
async () => {
if (!caseVariantCwd) throw new Error("Expected a case-only cwd variant");
diff --git a/packages/coding-agent/test/suite/scheduling.ts b/packages/coding-agent/test/suite/scheduling.ts
index 7d6f8cf07a..489eb64f35 100644
--- a/packages/coding-agent/test/suite/scheduling.ts
+++ b/packages/coding-agent/test/suite/scheduling.ts
@@ -20,6 +20,22 @@ export function createDeferred(): Deferred {
return { promise, resolve, reject };
}
+/** Attach an immediate rejection handler without Bun's pending `.rejects` matcher deadlock. */
+export function expectPromiseRejection(promise: Promise, expected: string | RegExp): Promise {
+ return promise.then(
+ () => {
+ throw new Error(`Expected promise to reject with ${String(expected)}`);
+ },
+ (error: unknown) => {
+ const message = error instanceof Error ? error.message : String(error);
+ const matches = typeof expected === "string" ? message.includes(expected) : expected.test(message);
+ if (!matches) {
+ throw new Error(`Expected rejection ${String(expected)}, received: ${message}`);
+ }
+ },
+ );
+}
+
/**
* A before_agent_start gate as an extension factory: `reached` resolves when the
* hook first runs (optionally filtered by prompt), `release()` lets gated runs
diff --git a/packages/coding-agent/test/suite/serialized-refine-config-integration.test.ts b/packages/coding-agent/test/suite/serialized-refine-config-integration.test.ts
index 17d0d8d781..9fb9710dda 100644
--- a/packages/coding-agent/test/suite/serialized-refine-config-integration.test.ts
+++ b/packages/coding-agent/test/suite/serialized-refine-config-integration.test.ts
@@ -162,7 +162,7 @@ describe("Serialized refine controller availability (unit)", () => {
expect(internals._rlmHeartbeatController).toBe(fakeController);
expect(internals._ipythonKernelProvisioner).not.toBe(initialProvisioner);
- expect(internals._createKernelHostHandlers()).toHaveProperty("rlm_heartbeat.create");
+ expect(internals._createKernelHostHandlers()).toHaveProperty(["rlm_heartbeat.create"]);
// Verify the controller is usable via host request.
const result = harness.session.handleRlmHeartbeatHostRequest("rlm_heartbeat.list");
diff --git a/packages/coding-agent/test/test-tags.ts b/packages/coding-agent/test/test-tags.ts
new file mode 100644
index 0000000000..6e899aa646
--- /dev/null
+++ b/packages/coding-agent/test/test-tags.ts
@@ -0,0 +1,6 @@
+export function isTestTagEnabled(tag: string): boolean {
+ return (process.env.PRIME_AGENT_TEST_TAGS ?? "")
+ .split(",")
+ .map((value) => value.trim())
+ .includes(tag);
+}
diff --git a/packages/coding-agent/test/test-theme-colors.ts b/packages/coding-agent/test/test-theme-colors.ts
index 7ee88732aa..21e8a709f5 100644
--- a/packages/coding-agent/test/test-theme-colors.ts
+++ b/packages/coding-agent/test/test-theme-colors.ts
@@ -241,7 +241,7 @@ if (cmd === "contrast") {
cmdTheme(cmd);
} else {
console.log("Usage:");
- console.log(" npx tsx test-theme-colors.ts light|dark Test built-in theme");
- console.log(" npx tsx test-theme-colors.ts contrast 4.5 Compute colors at ratio");
- console.log(" npx tsx test-theme-colors.ts test file.json Test any JSON file");
+ console.log(" bun test-theme-colors.ts light|dark Test built-in theme");
+ console.log(" bun test-theme-colors.ts contrast 4.5 Compute colors at ratio");
+ console.log(" bun test-theme-colors.ts test file.json Test any JSON file");
}
diff --git a/packages/coding-agent/vitest.config.ts b/packages/coding-agent/vitest.config.ts
deleted file mode 100644
index 320ff2a983..0000000000
--- a/packages/coding-agent/vitest.config.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { fileURLToPath } from "node:url";
-import { defineConfig } from "vitest/config";
-
-const aiSrcIndex = fileURLToPath(new URL("../ai/src/index.ts", import.meta.url));
-const aiSrcOAuth = fileURLToPath(new URL("../ai/src/oauth.ts", import.meta.url));
-const aiSrcMcp = fileURLToPath(new URL("../ai/src/mcp.ts", import.meta.url));
-const agentSrcIndex = fileURLToPath(new URL("../agent/src/index.ts", import.meta.url));
-const tuiSrcIndex = fileURLToPath(new URL("../tui/src/index.ts", import.meta.url));
-
-export default defineConfig({
- test: {
- globals: true,
- environment: "node",
- testTimeout: 30000,
- env: { DO_NOT_TRACK: "1" },
- tags: [
- {
- name: "process-stress",
- description: "Slow real-process stress and wall-clock scheduling coverage",
- },
- {
- name: "kernel-heavy",
- description: "Boots a real Python kernel and syncs skills into the shared venv",
- },
- ],
- // Kernel-heavy tests are excluded from the default sharded run: several files
- // booting real kernels in one shard starve the neighbouring kernel tests that
- // rely on the 30s default timeout. `test:kernel` runs them on their own.
- tagsFilter: ["!process-stress", "!kernel-heavy"],
- server: {
- deps: {
- external: [/@silvia-odwyer\/photon-node/],
- },
- },
- },
- resolve: {
- alias: [
- { find: /^@earendil-works\/pi-ai$/, replacement: aiSrcIndex },
- { find: /^@earendil-works\/pi-ai\/oauth$/, replacement: aiSrcOAuth },
- { find: /^@earendil-works\/pi-ai\/mcp$/, replacement: aiSrcMcp },
- { find: /^@earendil-works\/pi-agent-core$/, replacement: agentSrcIndex },
- { find: /^@earendil-works\/pi-tui$/, replacement: tuiSrcIndex },
- { find: /^@mariozechner\/pi-ai$/, replacement: aiSrcIndex },
- { find: /^@mariozechner\/pi-ai\/oauth$/, replacement: aiSrcOAuth },
- { find: /^@mariozechner\/pi-ai\/mcp$/, replacement: aiSrcMcp },
- { find: /^@mariozechner\/pi-agent-core$/, replacement: agentSrcIndex },
- { find: /^@mariozechner\/pi-tui$/, replacement: tuiSrcIndex },
- ],
- },
-});
diff --git a/packages/tui/README.md b/packages/tui/README.md
index c28647549b..120606e583 100644
--- a/packages/tui/README.md
+++ b/packages/tui/README.md
@@ -770,20 +770,20 @@ See `test/chat-simple.ts` for a complete chat interface example with:
Run it:
```bash
-npx tsx test/chat-simple.ts
+bun test/chat-simple.ts
```
## Development
```bash
# Install dependencies (from monorepo root)
-npm install
+bun install
# Run type checking
-npm run check
+bun run check
# Run the demo
-npx tsx test/chat-simple.ts
+bun test/chat-simple.ts
```
### Debug logging
@@ -791,5 +791,5 @@ npx tsx test/chat-simple.ts
Set `PI_TUI_WRITE_LOG` to capture the raw ANSI stream written to stdout.
```bash
-PI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx test/chat-simple.ts
+PI_TUI_WRITE_LOG=/tmp/tui-ansi.log bun test/chat-simple.ts
```
diff --git a/packages/tui/package.json b/packages/tui/package.json
index ee67bf2d75..7f6c2000c2 100644
--- a/packages/tui/package.json
+++ b/packages/tui/package.json
@@ -5,11 +5,11 @@
"type": "module",
"main": "dist/index.js",
"scripts": {
- "clean": "shx rm -rf dist",
- "build": "tsgo -p tsconfig.build.json",
- "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
- "test": "node --test --import tsx test/*.test.ts",
- "prepublishOnly": "npm run clean && npm run build"
+ "clean": "bun ../../scripts/remove-paths.ts dist",
+ "build": "bun --bun tsgo -p tsconfig.build.json",
+ "dev": "bun --bun tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
+ "test": "bun ../../scripts/run-with-clean-env.ts bun test --isolate --timeout 30000",
+ "prepublishOnly": "bun run clean && bun run build"
},
"files": [
"dist/**/*",
@@ -32,7 +32,8 @@
"directory": "packages/tui"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=20.0.0",
+ "bun": "1.4.0"
},
"types": "./dist/index.d.ts",
"dependencies": {
diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts
index c770eacee3..5a0f5ebf7d 100644
--- a/packages/tui/src/terminal.ts
+++ b/packages/tui/src/terminal.ts
@@ -13,6 +13,61 @@ import {
const cjsRequire = createRequire(import.meta.url);
+interface WindowsKernel32 {
+ symbols: {
+ GetStdHandle(handle: number): unknown;
+ GetConsoleMode(handle: unknown, mode: unknown): boolean;
+ SetConsoleMode(handle: unknown, mode: number): boolean;
+ };
+ close(): void;
+}
+
+interface BunFfi {
+ dlopen(library: string, symbols: Record): WindowsKernel32;
+ ptr(buffer: Uint32Array): unknown;
+}
+
+function enableWindowsVirtualTerminalInput(): void {
+ if (process.platform !== "win32") return;
+ try {
+ const STD_INPUT_HANDLE = -10;
+ const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
+
+ const bunRuntime = Reflect.get(globalThis, "Bun") as { FFI?: BunFfi } | undefined;
+ const bunFfi = bunRuntime?.FFI;
+ if (bunFfi) {
+ const kernel32 = bunFfi.dlopen("kernel32.dll", {
+ GetStdHandle: { args: ["i32"], returns: "ptr" },
+ GetConsoleMode: { args: ["ptr", "ptr"], returns: "bool" },
+ SetConsoleMode: { args: ["ptr", "u32"], returns: "bool" },
+ });
+ try {
+ const handle = kernel32.symbols.GetStdHandle(STD_INPUT_HANDLE);
+ const mode = new Uint32Array(1);
+ if (handle && kernel32.symbols.GetConsoleMode(handle, bunFfi.ptr(mode))) {
+ kernel32.symbols.SetConsoleMode(handle, mode[0]! | ENABLE_VIRTUAL_TERMINAL_INPUT);
+ }
+ } finally {
+ kernel32.close();
+ }
+ return;
+ }
+
+ const koffi = cjsRequire("koffi");
+ const kernel32 = koffi.load("kernel32.dll");
+ const GetStdHandle = kernel32.func("void* __stdcall GetStdHandle(int)");
+ const GetConsoleMode = kernel32.func("bool __stdcall GetConsoleMode(void*, _Out_ uint32_t*)");
+ const SetConsoleMode = kernel32.func("bool __stdcall SetConsoleMode(void*, uint32_t)");
+ const handle = GetStdHandle(STD_INPUT_HANDLE);
+ const mode = new Uint32Array(1);
+ if (handle && GetConsoleMode(handle, mode)) {
+ SetConsoleMode(handle, mode[0]! | ENABLE_VIRTUAL_TERMINAL_INPUT);
+ }
+ } catch {
+ // The terminal remains usable, but modified key events may be unavailable.
+ }
+}
+
const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
const TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1b]9;4;0;\x07";
@@ -356,27 +411,9 @@ export class ProcessTerminal implements Terminal {
* (e.g. \x1b[Z for Shift+Tab). Without this, libuv's ReadConsoleInputW
* discards modifier state and Shift+Tab arrives as plain \t.
*/
+
private enableWindowsVTInput(): void {
- if (process.platform !== "win32") return;
- try {
- // Dynamic require to avoid bundling koffi's 74MB of cross-platform
- // native binaries into every compiled binary. Koffi is only needed
- // on Windows for VT input support.
- const koffi = cjsRequire("koffi");
- const k32 = koffi.load("kernel32.dll");
- const GetStdHandle = k32.func("void* __stdcall GetStdHandle(int)");
- const GetConsoleMode = k32.func("bool __stdcall GetConsoleMode(void*, _Out_ uint32_t*)");
- const SetConsoleMode = k32.func("bool __stdcall SetConsoleMode(void*, uint32_t)");
-
- const STD_INPUT_HANDLE = -10;
- const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
- const handle = GetStdHandle(STD_INPUT_HANDLE);
- const mode = new Uint32Array(1);
- GetConsoleMode(handle, mode);
- SetConsoleMode(handle, mode[0]! | ENABLE_VIRTUAL_TERMINAL_INPUT);
- } catch {
- // koffi not available — Shift+Tab won't be distinguishable from Tab
- }
+ enableWindowsVirtualTerminalInput();
}
async drainInput(maxMs = 1000, idleMs = 50): Promise {
diff --git a/packages/tui/test/image-test.ts b/packages/tui/test/image-test.ts
index 6a29d16d97..e263ce71a8 100644
--- a/packages/tui/test/image-test.ts
+++ b/packages/tui/test/image-test.ts
@@ -16,7 +16,7 @@ try {
imageBuffer = readFileSync(testImagePath);
} catch (_e) {
console.error(`Failed to load image: ${testImagePath}`);
- console.error("Usage: npx tsx test/image-test.ts [path-to-image.png]");
+ console.error("Usage: bun test/image-test.ts [path-to-image.png]");
process.exit(1);
}
diff --git a/packages/tui/test/key-tester.ts b/packages/tui/test/key-tester.ts
index 5103d54418..9983a1659a 100755
--- a/packages/tui/test/key-tester.ts
+++ b/packages/tui/test/key-tester.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
import { matchesKey } from "../src/keys.js";
import { ProcessTerminal } from "../src/terminal.js";
import { type Component, TUI } from "../src/tui.js";
diff --git a/packages/tui/test/terminal.test.ts b/packages/tui/test/terminal.test.ts
index 59d1f92132..cbf2f1a8f4 100644
--- a/packages/tui/test/terminal.test.ts
+++ b/packages/tui/test/terminal.test.ts
@@ -44,6 +44,55 @@ describe("ProcessTerminal dimensions", () => {
});
});
+describe("ProcessTerminal Windows console input", () => {
+ it("enables virtual terminal input through Bun FFI", () => {
+ const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
+ const bunRuntime = Reflect.get(globalThis, "Bun") as { FFI: unknown };
+ const originalBunFfi = bunRuntime.FFI;
+ const handle = {};
+ let setMode: number | undefined;
+ let closed = false;
+
+ Object.defineProperty(process, "platform", { configurable: true, value: "win32" });
+ bunRuntime.FFI = {
+ dlopen: (library: string) => {
+ assert.equal(library, "kernel32.dll");
+ return {
+ symbols: {
+ GetStdHandle: (identifier: number) => {
+ assert.equal(identifier, -10);
+ return handle;
+ },
+ GetConsoleMode: (actualHandle: unknown, mode: Uint32Array) => {
+ assert.equal(actualHandle, handle);
+ mode[0] = 0x10;
+ return true;
+ },
+ SetConsoleMode: (actualHandle: unknown, mode: number) => {
+ assert.equal(actualHandle, handle);
+ setMode = mode;
+ return true;
+ },
+ },
+ close: () => {
+ closed = true;
+ },
+ };
+ },
+ ptr: (buffer: Uint32Array) => buffer,
+ };
+ try {
+ const terminal = new ProcessTerminal() as unknown as { enableWindowsVTInput(): void };
+ terminal.enableWindowsVTInput();
+ assert.equal(setMode, 0x210);
+ assert.equal(closed, true);
+ } finally {
+ restoreProperty(process, "platform", originalPlatform);
+ bunRuntime.FFI = originalBunFfi;
+ }
+ });
+});
+
describe("ProcessTerminal alternate screen handoff", () => {
it("keeps raw input active and discards keys until the next fullscreen TUI starts", () => {
const originalWrite = process.stdout.write;
diff --git a/packages/tui/vitest.config.ts b/packages/tui/vitest.config.ts
deleted file mode 100644
index a90c176d92..0000000000
--- a/packages/tui/vitest.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { defineConfig } from "vitest/config";
-
-export default defineConfig({
- test: {
- include: ["test/wrap-ansi.test.ts"],
- },
-});
diff --git a/prime-agent-runtime/src/rlm/repl.py b/prime-agent-runtime/src/rlm/repl.py
index ea04ca1766..1b982f762a 100644
--- a/prime-agent-runtime/src/rlm/repl.py
+++ b/prime-agent-runtime/src/rlm/repl.py
@@ -7,6 +7,7 @@
from __future__ import annotations
+import _thread
import ast
import asyncio
import codecs
@@ -366,24 +367,15 @@ def _request_interrupt(target: str | None) -> None:
else:
return
# SIGINT must land on the main thread, where cells execute. Windows has no
- # signal.pthread_kill: fall back to cancelling the active task on the loop
- # (sync-blocked cells and the finishing repr/drain cannot be broken there;
- # best-effort parity).
+ # signal.pthread_kill, but CPython's interrupt_main schedules the installed
+ # SIGINT handler on that thread. Wake the loop as well so await-suspended
+ # cells observe the interrupt without waiting for selector activity.
if hasattr(signal, "pthread_kill"):
signal.pthread_kill(threading.main_thread().ident, signal.SIGINT)
- if _loop is not None:
- # Wake the selector so a cancel scheduled by the handler runs promptly.
- _loop.call_soon_threadsafe(lambda: None)
- return
+ else:
+ _thread.interrupt_main()
if _loop is not None:
-
- def cancel_active() -> None:
- current = _active["task"]
- if current is task and current is not None and not current.done():
- _active["interrupted"] = True
- current.cancel()
-
- _loop.call_soon_threadsafe(cancel_active)
+ _loop.call_soon_threadsafe(lambda: None)
def _consume_pending_interrupt(rid: str) -> bool:
diff --git a/prime-agent-runtime/test/test_repl.py b/prime-agent-runtime/test/test_repl.py
index 26b7aca89d..14a99a1b41 100644
--- a/prime-agent-runtime/test/test_repl.py
+++ b/prime-agent-runtime/test/test_repl.py
@@ -347,8 +347,8 @@ def test_large_buffer_write_survives_short_pipe_writes(self):
self.assertIn("wrote 262144", tagged)
def test_interrupt_without_pthread_kill_cancels_awaited_cell(self):
- # Windows fallback seam: with pthread_kill absent the reader cancels
- # the active task on the loop; an await-suspended cell still interrupts.
+ # Windows fallback seam: interrupt_main runs the SIGINT handler on the
+ # main thread; an await-suspended cell still interrupts.
code = "\n".join(
[
"import signal",
diff --git a/prime-agent.sh b/prime-agent.sh
index 8baf90081f..53691f8e13 100755
--- a/prime-agent.sh
+++ b/prime-agent.sh
@@ -62,20 +62,27 @@ if [[ "$NO_ENV" == "true" ]]; then
echo "Running Prime Agent without API keys..."
fi
-# --dist runs the bundled build (what users get; ~3x faster startup than tsx).
+BUN_BIN="$(command -v bun || true)"
+if [[ -z "$BUN_BIN" ]]; then
+ echo "Bun is required. Install version $(cat "$SCRIPT_DIR/.bun-version") and run bun install --frozen-lockfile." >&2
+ exit 1
+fi
+
+EXPECTED_BUN_VERSION="$(cat "$SCRIPT_DIR/.bun-version")"
+ACTUAL_BUN_VERSION="$($BUN_BIN --version)"
+if [[ "$ACTUAL_BUN_VERSION" != "$EXPECTED_BUN_VERSION" ]]; then
+ echo "Prime Agent requires Bun $EXPECTED_BUN_VERSION; found $ACTUAL_BUN_VERSION." >&2
+ exit 1
+fi
+
+# --dist runs the bundled build. Source mode runs TypeScript directly with Bun.
if [[ "$USE_DIST" == "true" ]]; then
BUNDLE="$SCRIPT_DIR/packages/coding-agent/dist/bundle/cli.js"
if [[ ! -f "$BUNDLE" ]]; then
- echo "Bundle not found at $BUNDLE. Run npm run build first." >&2
+ echo "Bundle not found at $BUNDLE. Run bun run build first." >&2
exit 1
fi
- exec node "$BUNDLE" ${ARGS[@]+"${ARGS[@]}"}
-fi
-
-TSX_BIN="$SCRIPT_DIR/node_modules/.bin/tsx"
-if [[ ! -x "$TSX_BIN" ]]; then
- echo "tsx not found at $TSX_BIN. Run npm install from the repo root first." >&2
- exit 1
+ exec "$BUN_BIN" "$BUNDLE" ${ARGS[@]+"${ARGS[@]}"}
fi
-"$TSX_BIN" "$SCRIPT_DIR/packages/coding-agent/src/cli.ts" ${ARGS[@]+"${ARGS[@]}"}
+exec "$BUN_BIN" "$SCRIPT_DIR/packages/coding-agent/src/bun/cli.ts" ${ARGS[@]+"${ARGS[@]}"}
diff --git a/scripts/assemble-release-archives.mjs b/scripts/assemble-release-archives.mjs
new file mode 100755
index 0000000000..d8af8bfa13
--- /dev/null
+++ b/scripts/assemble-release-archives.mjs
@@ -0,0 +1,387 @@
+#!/usr/bin/env bun
+/**
+ * Assembles Bun-compiled platform release archives with metadata.
+ *
+ * Usage:
+ * bun scripts/assemble-release-archives.mjs \
+ * --base-url \
+ * --version x.y.z \
+ * --binary-dir \
+ * --sidecar-dir \
+ * [--channel stable|beta] \
+ * [--out-dir ]
+ *
+ * Produces /artifacts/:
+ * prime-agent--.tar.gz (one per platform)
+ * SHA256SUMS (aggregate checksums)
+ * (plain version pointer)
+ * latest.json|beta.json (manifest)
+ *
+ * Platforms: darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64, windows-arm64
+ *
+ * Archive layout (each archive):
+ * prime-agent[.exe] (compiled binary, executable)
+ * package.json
+ * README.md
+ * CHANGELOG.md
+ * prime-agent-runtime/ (Python runtime, recursive)
+ * skills/ (skill files)
+ * theme/ (UI theme JSON files)
+ * assets/ (UI assets)
+ * export-html/ (HTML export templates)
+ * docs/ (documentation)
+ * examples/ (example files)
+ * photon_rs_bg.wasm (image-processing WASM)
+
+ */
+
+import { createHash } from "node:crypto";
+import {
+ chmodSync,
+ cpSync,
+ existsSync,
+ lstatSync,
+ mkdirSync,
+ readFileSync,
+ readdirSync,
+ rmSync,
+ writeFileSync,
+} from "node:fs";
+import { spawnSync } from "node:child_process";
+import { isAbsolute, join, relative, resolve } from "node:path";
+
+const root = resolve(import.meta.dirname, "..");
+const defaultOutDir = join(root, "packages", "coding-agent", "release");
+const releaseChannels = new Set(["stable", "beta"]);
+const publicPackageName = "prime-agent";
+const binaryName = "prime-agent";
+
+const PLATFORMS = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "windows-x64", "windows-arm64"];
+
+const REQUIRED_SIDECARS = [
+ "package.json",
+ "README.md",
+ "CHANGELOG.md",
+ "install.sh",
+ "install.ps1",
+ "prime-agent-runtime",
+ "skills",
+ "theme",
+ "assets",
+ "export-html",
+ "docs",
+ "examples",
+ "photon_rs_bg.wasm",
+];
+
+function parseArgs(args) {
+ const parsed = {
+ baseUrl: undefined,
+ channel: "stable",
+ binaryDir: undefined,
+ sidecarDir: undefined,
+ outDir: defaultOutDir,
+ version: undefined,
+ platforms: [],
+ };
+
+ for (let i = 0; i < args.length; i += 1) {
+ const arg = args[i];
+ switch (arg) {
+ case "--channel": {
+ const value = args[++i];
+ if (!value || !releaseChannels.has(value)) throw new Error("--channel must be stable or beta");
+ parsed.channel = value;
+ break;
+ }
+ case "--base-url": {
+ parsed.baseUrl = args[++i];
+ if (!parsed.baseUrl) throw new Error("--base-url requires a value");
+ break;
+ }
+ case "--version": {
+ parsed.version = args[++i];
+ if (!parsed.version) throw new Error("--version requires a value");
+ break;
+ }
+ case "--binary-dir": {
+ parsed.binaryDir = resolve(root, args[++i]);
+ break;
+ }
+ case "--sidecar-dir": {
+ parsed.sidecarDir = resolve(root, args[++i]);
+ break;
+ }
+ case "--out-dir": {
+ parsed.outDir = resolve(root, args[++i]);
+ break;
+ }
+ case "--platform": {
+ const platform = args[++i];
+ if (!PLATFORMS.includes(platform)) throw new Error(`Unsupported platform: ${platform}`);
+ parsed.platforms.push(platform);
+ break;
+ }
+ case "--help":
+ case "-h":
+ printHelp();
+ process.exit(0);
+ default:
+ throw new Error(`Unknown argument: ${arg}`);
+ }
+ }
+
+ if (!parsed.baseUrl) throw new Error("--base-url is required");
+ if (!parsed.version) throw new Error("--version is required");
+ if (!parsed.binaryDir) throw new Error("--binary-dir is required");
+ if (!parsed.sidecarDir) throw new Error("--sidecar-dir is required");
+
+ if (!existsSync(parsed.binaryDir)) throw new Error(`Binary dir not found: ${parsed.binaryDir}`);
+ if (!existsSync(parsed.sidecarDir)) throw new Error(`Sidecar dir not found: ${parsed.sidecarDir}`);
+
+ parsed.version = normalizeVersion(parsed.version);
+ if (parsed.platforms.length === 0) parsed.platforms = [...PLATFORMS];
+ parsed.baseUrl = normalizeBaseUrl(parsed.baseUrl);
+ return parsed;
+}
+
+function printHelp() {
+ console.log(`Usage: bun scripts/assemble-release-archives.mjs --base-url --binary-dir --sidecar-dir --version x.y.z [--channel stable|beta] [--platform ] [--out-dir ]
+
+Assembles platform-specific release archives from a Bun-compiled binary and sidecar files.
+
+Output:
+ /artifacts/prime-agent--.tar.gz (macOS/Linux)
+ /artifacts/prime-agent--.zip (Windows)
+ /artifacts/SHA256SUMS
+ /artifacts/
+ /artifacts/latest.json (stable) or beta.json (beta)
+`);
+}
+
+function normalizeVersion(version) {
+ const normalized = version.startsWith("v") ? version.slice(1) : version;
+ if (!/^[0-9A-Za-z.-]+$/.test(normalized)) throw new Error(`Invalid release version: ${version}`);
+ return normalized;
+}
+
+function normalizeBaseUrl(value) {
+ value = value.trim();
+ if (/[\u0000-\u001f\u007f"'`$\\]/.test(value)) {
+ throw new Error("Release base URL contains unsafe shell characters");
+ }
+ let parsed;
+ try {
+ parsed = new URL(value);
+ } catch {
+ throw new Error(`Invalid release base URL: ${value}`);
+ }
+ if (parsed.protocol !== "https:" || !parsed.hostname || parsed.username || parsed.password) {
+ throw new Error(`Release base URL must use HTTPS without credentials: ${value}`);
+ }
+ if (parsed.search || parsed.hash || /[?#]$/.test(parsed.toString())) {
+ throw new Error("Release base URL must not contain a query or fragment");
+ }
+ return parsed.toString().replace(/\/+$/, "");
+}
+
+function assertSafeOutputDir(outDir) {
+ const base = resolve(defaultOutDir);
+ const resolvedOutDir = resolve(outDir);
+ const pathFromBase = relative(base, resolvedOutDir);
+ if (pathFromBase !== "" && (pathFromBase.startsWith("..") || isAbsolute(pathFromBase))) {
+ throw new Error(`Refusing to write output outside ${base}: ${outDir}`);
+ }
+
+ let current = base;
+ for (const part of pathFromBase.split(/[\/\\]/).filter(Boolean)) {
+ if (existsSync(current) && lstatSync(current).isSymbolicLink()) {
+ throw new Error(`Refusing to write through symlinked output path: ${current}`);
+ }
+ current = join(current, part);
+ }
+ if (existsSync(current) && lstatSync(current).isSymbolicLink()) {
+ throw new Error(`Refusing to write through symlinked output path: ${current}`);
+ }
+}
+
+function sha256File(path) {
+ const hash = createHash("sha256");
+ hash.update(readFileSync(path));
+ return hash.digest("hex");
+}
+
+function createArchive(sourceDir, archivePath) {
+ if (archivePath.endsWith(".zip")) {
+ const result = spawnSync("zip", ["-q", "-r", archivePath, "."], {
+ cwd: sourceDir,
+ stdio: "pipe",
+ encoding: "utf8",
+ });
+ if (result.status !== 0) throw new Error(`zip failed: ${result.stderr || result.stdout}`);
+ } else {
+ const result = spawnSync("tar", ["-czf", archivePath, "-C", sourceDir, "."], {
+ stdio: "pipe",
+ encoding: "utf8",
+ });
+ if (result.status !== 0) throw new Error(`tar failed: ${result.stderr || result.stdout}`);
+ }
+}
+
+function isWindowsPlatform(platform) {
+ return platform === "windows-x64" || platform === "windows-arm64";
+}
+
+function archiveExtension(platform) {
+ return isWindowsPlatform(platform) ? "zip" : "tar.gz";
+}
+
+function binaryNameForPlatform(platform) {
+ return isWindowsPlatform(platform) ? "prime-agent.exe" : "prime-agent";
+}
+
+function validateSidecars(sidecarDir) {
+ const missing = [];
+ for (const name of REQUIRED_SIDECARS) {
+ if (!existsSync(join(sidecarDir, name))) {
+ missing.push(name);
+ }
+ }
+ return missing;
+}
+
+const FORBIDDEN_RELEASE_DIRECTORIES = new Set(["node_modules", ".venv", "__pycache__", ".pytest_cache"]);
+
+function findForbiddenReleaseDirectory(root, relativePath = "") {
+ for (const entry of readdirSync(join(root, relativePath), { withFileTypes: true })) {
+ const child = join(relativePath, entry.name);
+ if (FORBIDDEN_RELEASE_DIRECTORIES.has(entry.name)) return child;
+ if (entry.isDirectory()) {
+ const found = findForbiddenReleaseDirectory(root, child);
+ if (found) return found;
+ }
+ }
+ return undefined;
+}
+
+function renderPackageManifest(path, version, platform) {
+ const manifest = JSON.parse(readFileSync(path, "utf8"));
+ manifest.name = publicPackageName;
+ manifest.version = version;
+ manifest.bin = { [binaryName]: `./${binaryNameForPlatform(platform)}` };
+ manifest.packageManager = "bun@1.4.0";
+ writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`);
+}
+
+function renderInstaller(path, baseUrl, channel) {
+ const rendered = readFileSync(path, "utf8")
+ .replaceAll("__PRIME_AGENT_DOWNLOAD_BASE_URL__", baseUrl)
+ .replaceAll("__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__", channel);
+ if (rendered.includes("__PRIME_AGENT_DOWNLOAD_BASE_URL__") || rendered.includes("__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__")) {
+ throw new Error("Release installer still contains an unresolved configuration marker");
+ }
+ writeFileSync(path, rendered);
+ chmodSync(path, 0o755);
+}
+
+function main() {
+ const args = parseArgs(process.argv.slice(2));
+ const outDir = resolve(args.outDir);
+ assertSafeOutputDir(outDir);
+
+ const missing = validateSidecars(args.sidecarDir);
+ if (missing.length > 0) {
+ throw new Error(
+ `Missing required sidecars in ${args.sidecarDir}: ${missing.join(", ")}. ` +
+ `Run "bun run copy-binary-assets" first.`,
+ );
+ }
+ const forbiddenDirectory = findForbiddenReleaseDirectory(args.sidecarDir);
+ if (forbiddenDirectory) {
+ throw new Error(`Release sidecars contain a forbidden dependency or cache directory: ${forbiddenDirectory}`);
+ }
+
+ const binarySources = new Map();
+ for (const platform of args.platforms) {
+ const binaryName = isWindowsPlatform(platform) ? "pi.exe" : "pi";
+ const binarySource = join(args.binaryDir, platform, binaryName);
+ if (!existsSync(binarySource)) {
+ throw new Error(`Binary not found for platform ${platform}: ${binarySource}`);
+ }
+ binarySources.set(platform, binarySource);
+ }
+
+ rmSync(outDir, { force: true, recursive: true });
+ const versionDir = join(outDir, "artifacts");
+ mkdirSync(versionDir, { recursive: true });
+ const archives = [];
+
+ for (const platform of args.platforms) {
+ const binarySource = binarySources.get(platform);
+ const stagingDir = join(outDir, "staging", platform);
+ mkdirSync(stagingDir, { recursive: true });
+
+ // Copy binary to archive root as platform-specific name
+ const archiveBinaryName = binaryNameForPlatform(platform);
+ const dstBinary = join(stagingDir, archiveBinaryName);
+ cpSync(binarySource, dstBinary);
+ chmodSync(dstBinary, 0o755);
+
+ // Copy all sidecars to archive root
+ for (const name of REQUIRED_SIDECARS) {
+ const src = join(args.sidecarDir, name);
+ cpSync(src, join(stagingDir, name), { recursive: true, dereference: true });
+ }
+ renderPackageManifest(join(stagingDir, "package.json"), args.version, platform);
+ renderInstaller(join(stagingDir, "install.sh"), args.baseUrl, args.channel);
+ renderInstaller(join(stagingDir, "install.ps1"), args.baseUrl, args.channel);
+
+ const archiveExt = archiveExtension(platform);
+ const archiveName = `prime-agent-${args.version}-${platform}.${archiveExt}`;
+ const archivePath = join(versionDir, archiveName);
+ createArchive(stagingDir, archivePath);
+
+ const sha256 = sha256File(archivePath);
+ archives.push({ platform, file: archiveName, sha256 });
+ console.log(`Created ${archivePath}`);
+
+ rmSync(join(outDir, "staging"), { force: true, recursive: true });
+ }
+
+ if (archives.length === 0) throw new Error("No platform archives created");
+
+ archives.sort((a, b) => a.file.localeCompare(b.file));
+ const sumsContent = archives.map((a) => `${a.sha256} ${a.file}`).join("\n") + "\n";
+ writeFileSync(join(versionDir, "SHA256SUMS"), sumsContent);
+ writeFileSync(join(versionDir, args.channel), `v${args.version}\n`);
+
+ const manifestName = args.channel === "stable" ? "latest.json" : "beta.json";
+ writeFileSync(
+ join(versionDir, manifestName),
+ JSON.stringify(
+ {
+ version: `v${args.version}`,
+ package: publicPackageName,
+ platforms: archives.map((a) => ({
+ platform: a.platform,
+ file: a.file,
+ sha256: a.sha256,
+ binaryName: binaryNameForPlatform(a.platform),
+ })),
+ baseUrl: `${args.baseUrl}/releases/v${args.version}`,
+ },
+ null,
+ 2,
+ ) + "\n",
+ );
+
+ console.log(`\nSHA256SUMS -> ${join(versionDir, "SHA256SUMS")}`);
+ console.log(`${manifestName} -> ${join(versionDir, manifestName)}`);
+}
+
+try {
+ main();
+} catch (error) {
+ console.error(error instanceof Error ? error.message : String(error));
+ process.exit(1);
+}
diff --git a/scripts/bench-attach-bytes.mjs b/scripts/bench-attach-bytes.mjs
index e64434bc51..52fd02a608 100644
--- a/scripts/bench-attach-bytes.mjs
+++ b/scripts/bench-attach-bytes.mjs
@@ -1,10 +1,10 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
/**
* Measures attach payload bytes for legacy vs slim_attach clients against a
* daemon session loaded from a given session file. Uses an isolated agent dir
* so no test sessions leak into the shared sessions directory.
*
- * node scripts/bench-attach-bytes.mjs
+ * bun scripts/bench-attach-bytes.mjs
*/
import { spawn } from "node:child_process";
import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
diff --git a/scripts/bench-daemon-startup.mjs b/scripts/bench-daemon-startup.mjs
index fb8e9e7f3b..25f9dc79c3 100644
--- a/scripts/bench-daemon-startup.mjs
+++ b/scripts/bench-daemon-startup.mjs
@@ -1,10 +1,10 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
/**
* Measures daemon cold-start latency: spawn `--mode daemon` and poll the unix
* socket until it accepts a connection. This is the readiness gate every
* interactive cold start waits on.
*
- * node scripts/bench-daemon-startup.mjs [--runs N]
+ * bun scripts/bench-daemon-startup.mjs [--runs N]
*/
import { spawn } from "node:child_process";
import { mkdtempSync, rmSync } from "node:fs";
diff --git a/scripts/build-binaries.sh b/scripts/build-binaries.sh
index cbc029f81f..ed65571c3f 100755
--- a/scripts/build-binaries.sh
+++ b/scripts/build-binaries.sh
@@ -1,176 +1,129 @@
#!/usr/bin/env bash
#
-# Build pi binaries for all platforms locally.
-# Mirrors .github/workflows/build-binaries.yml
+# Build pi binaries for supported platforms locally.
+# Mirrors the Bun-compiled release lane in .github/workflows/build-binaries.yml.
#
# Usage:
# ./scripts/build-binaries.sh [--skip-deps] [--platform ]
#
# Options:
-# --skip-deps Skip installing cross-platform dependencies
-# --platform Build only for specified platform (darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64)
+# --skip-deps Skip frozen dependency install and package build
+# --platform Build only for specified platform
+# (darwin-arm64, darwin-x64, linux-x64, linux-arm64)
#
# Output:
# packages/coding-agent/binaries/
-# pi-darwin-arm64.tar.gz
-# pi-darwin-x64.tar.gz
-# pi-linux-x64.tar.gz
-# pi-linux-arm64.tar.gz
-# pi-windows-x64.zip
+# /pi
+#
+# packages/coding-agent/release/artifacts/
+# prime-agent--.tar.gz
+# SHA256SUMS
+# stable
+# latest.json
set -euo pipefail
cd "$(dirname "$0")/.."
+# Require Bun 1.4.0
+EXPECTED_BUN_VERSION="1.4.0"
+ACTUAL_BUN_VERSION=$(bun --version 2>/dev/null || echo "not-found")
+if [ "$ACTUAL_BUN_VERSION" != "$EXPECTED_BUN_VERSION" ]; then
+ echo "ERROR: Expected Bun ${EXPECTED_BUN_VERSION}, got ${ACTUAL_BUN_VERSION}"
+ echo "Install Bun ${EXPECTED_BUN_VERSION}:"
+ echo " curl -fsSL https://bun.sh/install | bash -s -- bun-v${EXPECTED_BUN_VERSION}"
+ exit 1
+fi
+
SKIP_DEPS=false
PLATFORM=""
while [[ $# -gt 0 ]]; do
- case $1 in
- --skip-deps)
- SKIP_DEPS=true
- shift
- ;;
- --platform)
- PLATFORM="$2"
- shift 2
- ;;
- *)
- echo "Unknown option: $1"
- exit 1
- ;;
- esac
+ case $1 in
+ --skip-deps)
+ SKIP_DEPS=true
+ shift
+ ;;
+ --platform)
+ PLATFORM="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ exit 1
+ ;;
+ esac
done
# Validate platform if specified
if [[ -n "$PLATFORM" ]]; then
- case "$PLATFORM" in
- darwin-arm64|darwin-x64|linux-x64|linux-arm64|windows-x64)
- ;;
- *)
- echo "Invalid platform: $PLATFORM"
- echo "Valid platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64"
- exit 1
- ;;
- esac
+ case "$PLATFORM" in
+ darwin-arm64|darwin-x64|linux-x64|linux-arm64|windows-x64|windows-arm64) ;;
+ *)
+ echo "Invalid platform: $PLATFORM"
+ echo "Valid platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64, windows-arm64"
+ exit 1
+ ;;
+ esac
fi
-echo "==> Installing dependencies..."
-npm ci
-
if [[ "$SKIP_DEPS" == "false" ]]; then
- echo "==> Installing cross-platform native bindings..."
- # npm ci only installs optional deps for the current platform
- # We need all platform bindings for bun cross-compilation
- # Use --force to bypass platform checks (os/cpu restrictions in package.json)
- # Install all in one command to avoid npm removing packages from previous installs
- npm install --no-save --force \
- @mariozechner/clipboard-darwin-arm64@0.3.0 \
- @mariozechner/clipboard-darwin-x64@0.3.0 \
- @mariozechner/clipboard-linux-x64-gnu@0.3.0 \
- @mariozechner/clipboard-linux-arm64-gnu@0.3.0 \
- @mariozechner/clipboard-win32-x64-msvc@0.3.0 \
- @img/sharp-darwin-arm64@0.34.5 \
- @img/sharp-darwin-x64@0.34.5 \
- @img/sharp-linux-x64@0.34.5 \
- @img/sharp-linux-arm64@0.34.5 \
- @img/sharp-win32-x64@0.34.5 \
- @img/sharp-libvips-darwin-arm64@1.2.4 \
- @img/sharp-libvips-darwin-x64@1.2.4 \
- @img/sharp-libvips-linux-x64@1.2.4 \
- @img/sharp-libvips-linux-arm64@1.2.4
-else
- echo "==> Skipping cross-platform native bindings (--skip-deps)"
-fi
+ echo "==> Installing dependencies for all release platforms (frozen lockfile)..."
+ bun install --frozen-lockfile --os=* --cpu=*
-echo "==> Building all packages..."
-npm run build
+ echo "==> Building all packages..."
+ bun run build
+fi
-echo "==> Building binaries..."
+echo "==> Compiling binaries..."
cd packages/coding-agent
# Clean previous builds
rm -rf binaries
-mkdir -p binaries/{darwin-arm64,darwin-x64,linux-x64,linux-arm64,windows-x64}
+mkdir -p binaries
# Determine which platforms to build
if [[ -n "$PLATFORM" ]]; then
- PLATFORMS=("$PLATFORM")
+ PLATFORMS=("$PLATFORM")
else
- PLATFORMS=(darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64)
+ PLATFORMS=(darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64 windows-arm64)
fi
for platform in "${PLATFORMS[@]}"; do
- echo "Building for $platform..."
- # Externalize koffi to avoid embedding all 18 platform .node files (~74MB)
- # into every binary. Koffi is only used on Windows for VT input and the
- # call site has a try/catch fallback. For Windows builds, we copy the
- # appropriate .node file alongside the binary below.
- if [[ "$platform" == "windows-x64" ]]; then
- bun build --compile --external koffi --target=bun-$platform ./dist/bun/cli.js --outfile binaries/$platform/pi.exe
- else
- bun build --compile --external koffi --target=bun-$platform ./dist/bun/cli.js --outfile binaries/$platform/pi
- fi
-done
-
-echo "==> Creating release archives..."
-
-# Copy shared files to each platform directory
-for platform in "${PLATFORMS[@]}"; do
- cp package.json binaries/$platform/
- cp README.md binaries/$platform/
- cp CHANGELOG.md binaries/$platform/
- cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm binaries/$platform/
- mkdir -p binaries/$platform/theme
- cp dist/modes/interactive/theme/*.json binaries/$platform/theme/
- mkdir -p binaries/$platform/assets
- cp dist/modes/interactive/assets/* binaries/$platform/assets/
- cp -r dist/core/export-html binaries/$platform/
- cp -r docs binaries/$platform/
- cp -r examples binaries/$platform/
- cp -r skills binaries/$platform/
-
- # Copy koffi native module for Windows (needed for VT input support)
- if [[ "$platform" == "windows-x64" ]]; then
- mkdir -p binaries/$platform/node_modules/koffi/build/koffi/win32_x64
- cp ../../node_modules/koffi/index.js binaries/$platform/node_modules/koffi/
- cp ../../node_modules/koffi/package.json binaries/$platform/node_modules/koffi/
- cp ../../node_modules/koffi/build/koffi/win32_x64/koffi.node binaries/$platform/node_modules/koffi/build/koffi/win32_x64/
- fi
+ echo "Building for $platform..."
+ mkdir -p "binaries/$platform"
+ bun build --compile --minify --keep-names --bytecode --format=esm --external koffi --target="bun-$platform" ./dist/bun/cli.js --outfile "binaries/$platform/pi"
done
-# Create archives
-cd binaries
-
-for platform in "${PLATFORMS[@]}"; do
- if [[ "$platform" == "windows-x64" ]]; then
- # Windows (zip)
- echo "Creating pi-$platform.zip..."
- (cd $platform && zip -r ../pi-$platform.zip .)
- else
- # Unix platforms (tar.gz) - use wrapper directory for mise compatibility
- echo "Creating pi-$platform.tar.gz..."
- mv $platform pi && tar -czf pi-$platform.tar.gz pi && mv pi $platform
- fi
-done
+echo "==> Copying sidecar assets..."
+bun run copy-binary-assets
-# Extract archives for easy local testing
-echo "==> Extracting archives for testing..."
-for platform in "${PLATFORMS[@]}"; do
- rm -rf $platform
- if [[ "$platform" == "windows-x64" ]]; then
- mkdir -p $platform && (cd $platform && unzip -q ../pi-$platform.zip)
- else
- tar -xzf pi-$platform.tar.gz && mv pi $platform
- fi
-done
+echo "==> Assembling release archives..."
+VERSION=$(bun -e "console.log(require('./package.json').version)")
+BASE_URL="${PRIME_AGENT_DOWNLOAD_BASE_URL:-https://releases.pi.ai}"
+PACK_PLATFORM_ARGS=()
+if [[ -n "$PLATFORM" ]]; then
+ PACK_PLATFORM_ARGS=(--platform "$PLATFORM")
+fi
+bun ../../scripts/pack-prime-agent-release.mjs \
+ --channel stable \
+ --version "$VERSION" \
+ --base-url "$BASE_URL" \
+ --binary-base-dir "$(pwd)/binaries" \
+ --sidecar-dir "$(pwd)/dist" \
+ --out-dir "$(pwd)/release" \
+ "${PACK_PLATFORM_ARGS[@]}"
echo ""
echo "==> Build complete!"
-echo "Archives available in packages/coding-agent/binaries/"
-ls -lh *.tar.gz *.zip 2>/dev/null || true
+echo "Release archives:"
+shopt -s nullglob
+archives=(release/artifacts/*.tar.gz release/artifacts/*.zip)
+if (( ${#archives[@]} == 0 )); then
+ echo "No release archives were created" >&2
+ exit 1
+fi
+ls -lh "${archives[@]}"
echo ""
-echo "Extracted directories for testing:"
-for platform in "${PLATFORMS[@]}"; do
- echo " binaries/$platform/pi"
-done
+echo "Metadata:"
+ls -lh "release/artifacts/SHA256SUMS" "release/artifacts/latest.json" "release/artifacts/stable"
diff --git a/scripts/check-browser-smoke.mjs b/scripts/check-browser-smoke.mjs
index d590988d62..fe24ff3da5 100644
--- a/scripts/check-browser-smoke.mjs
+++ b/scripts/check-browser-smoke.mjs
@@ -1,36 +1,26 @@
import { writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
-import { build } from "esbuild";
-const outputPath = join(tmpdir(), "pi-browser-smoke.js");
-const errorLogPath = join(tmpdir(), "pi-browser-smoke-errors.log");
+const outputPath = join(tmpdir(), "prime-agent-browser-smoke.js");
+const errorLogPath = join(tmpdir(), "prime-agent-browser-smoke-errors.log");
try {
- await build({
- entryPoints: ["scripts/browser-smoke-entry.ts"],
- bundle: true,
- platform: "browser",
+ const result = await Bun.build({
+ entrypoints: ["scripts/browser-smoke-entry.ts"],
+ target: "browser",
format: "esm",
- logLevel: "silent",
- outfile: outputPath,
+ sourcemap: "none",
+ write: false,
+ throw: false,
});
- process.exit(0);
-} catch (error) {
- let detailedErrors = "";
- if (error && typeof error === "object" && "errors" in error && Array.isArray(error.errors)) {
- detailedErrors = error.errors
- .map((entry) => {
- const location = entry.location
- ? `${entry.location.file}:${entry.location.line}:${entry.location.column}`
- : "";
- return [location, entry.text].filter(Boolean).join(" ");
- })
- .join("\n");
+ if (!result.success || result.outputs.length !== 1) {
+ throw new Error(result.logs.map((entry) => entry.message).join("\n") || "Bun browser bundle produced no output");
}
-
- const baseError = error instanceof Error ? (error.stack ?? error.message) : String(error);
- writeFileSync(errorLogPath, [detailedErrors, baseError].filter(Boolean).join("\n\n"), "utf-8");
+ await Bun.write(outputPath, result.outputs[0]);
+} catch (error) {
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
+ writeFileSync(errorLogPath, message, "utf-8");
console.error(`Browser smoke check failed. See ${errorLogPath}`);
process.exit(1);
}
diff --git a/scripts/check-bun-version.ts b/scripts/check-bun-version.ts
new file mode 100644
index 0000000000..a4c56aa895
--- /dev/null
+++ b/scripts/check-bun-version.ts
@@ -0,0 +1,12 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+const expected = readFileSync(resolve(import.meta.dir, "../.bun-version"), "utf8").trim();
+const actual = Bun.version;
+
+if (actual !== expected) {
+ console.error(`Expected Bun ${expected}, got ${actual}.`);
+ process.exit(1);
+}
+
+console.log(`Validated Bun ${actual} (${process.execPath})`);
diff --git a/scripts/check-installer-render.mjs b/scripts/check-installer-render.mjs
index 835f306bbc..c4b5878080 100644
--- a/scripts/check-installer-render.mjs
+++ b/scripts/check-installer-render.mjs
@@ -93,9 +93,9 @@ screen_case() {
}
progress_case() {
- progress_details="Preparing global install.
-Linking command binaries.
-Finalizing npm install."
+ progress_details="Downloading verified archive.
+Extracting compiled binary.
+Validating runtime sidecars."
for progress_frame in 1 24 25 48 49 200; do
prime_agent_animation_frame="$progress_frame"
printf '__PROGRESS__ %s\t%s\t%s\\n' "$progress_frame" "$(prime_agent_animation_status "Installing Prime Agent" "$progress_details" static)" "$(prime_agent_animation_detail "$progress_details")"
@@ -241,12 +241,12 @@ function assertInstallerProgress(progress) {
if (progress.length !== 6) return;
const expectedDetails = [
- "Preparing global install.",
- "Preparing global install.",
- "Linking command binaries.",
- "Linking command binaries.",
- "Finalizing npm install.",
- "Finalizing npm install.",
+ "Downloading verified archive.",
+ "Downloading verified archive.",
+ "Extracting compiled binary.",
+ "Extracting compiled binary.",
+ "Validating runtime sidecars.",
+ "Validating runtime sidecars.",
];
for (const [index, expectedDetail] of expectedDetails.entries()) {
check(
diff --git a/scripts/check-powershell-installer.mjs b/scripts/check-powershell-installer.mjs
new file mode 100644
index 0000000000..0a9f7214e6
--- /dev/null
+++ b/scripts/check-powershell-installer.mjs
@@ -0,0 +1,39 @@
+import { spawnSync } from "node:child_process";
+import { readFileSync } from "node:fs";
+
+const source = readFileSync("install.ps1", "utf8");
+const baseMarker = "__PRIME_AGENT_DOWNLOAD_BASE_URL__";
+const channelMarker = "__PRIME_AGENT_DEFAULT_RELEASE_CHANNEL__";
+
+for (const channel of ["stable", "beta"]) {
+ const rendered = source.replaceAll(baseMarker, "https://releases.example.test").replaceAll(channelMarker, channel);
+ if (rendered.includes(baseMarker) || rendered.includes(channelMarker)) {
+ throw new Error(`PowerShell installer has unresolved markers for ${channel}`);
+ }
+ if (!rendered.includes(`$DefaultChannel = "${channel}"`)) {
+ throw new Error(`PowerShell installer did not render the ${channel} channel`);
+ }
+}
+
+const parserCommand = [
+ "$allErrors = @()",
+ "foreach ($path in @('install.ps1', 'scripts/test-windows-installer.ps1')) {",
+ "$tokens = $null",
+ "$errors = $null",
+ "[System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $path), [ref]$tokens, [ref]$errors) | Out-Null",
+ "$allErrors += $errors",
+ "}",
+ "if ($allErrors.Count -gt 0) { $allErrors | ForEach-Object { Write-Error $_.Message }; exit 1 }",
+].join("; ");
+const parser = spawnSync("pwsh", ["-NoProfile", "-NonInteractive", "-Command", parserCommand], {
+ encoding: "utf8",
+});
+if (parser.error && (parser.error).code !== "ENOENT") {
+ throw parser.error;
+}
+if (!parser.error && parser.status !== 0) {
+ throw new Error(`PowerShell installer parse failed:
+${parser.stderr}${parser.stdout}`);
+}
+
+console.log(parser.error ? "PowerShell installer markers passed; pwsh parser unavailable." : "PowerShell installer check passed.");
diff --git a/scripts/compiled-artifact-smoke.ts b/scripts/compiled-artifact-smoke.ts
new file mode 100644
index 0000000000..ff4408086f
--- /dev/null
+++ b/scripts/compiled-artifact-smoke.ts
@@ -0,0 +1,163 @@
+#!/usr/bin/env bun
+
+import { spawnSync } from "node:child_process";
+import { chmodSync, copyFileSync, cpSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { basename, dirname, join, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const defaultBinary = join(rootDir, "packages", "coding-agent", "dist", "pi");
+const defaultDist = dirname(defaultBinary);
+
+export const REQUIRED_BINARY_SIDECARS = [
+ "package.json",
+ "README.md",
+ "CHANGELOG.md",
+ "install.sh",
+ "install.ps1",
+ "prime-agent-runtime",
+ "skills",
+ "theme",
+ "assets",
+ "export-html",
+ "docs",
+ "examples",
+ "photon_rs_bg.wasm",
+] as const;
+
+type RunResult = {
+ args: string[];
+ exitCode: number;
+ stdout: string;
+ stderr: string;
+};
+
+function run(executable: string, args: string[], cwd: string, path: string): RunResult {
+ const result = spawnSync(executable, args, {
+ cwd,
+ env: { ...process.env, PATH: path },
+ encoding: "utf8",
+ });
+ return {
+ args,
+ exitCode: result.status ?? 1,
+ stdout: result.stdout ?? "",
+ stderr: result.stderr ?? result.error?.message ?? "",
+ };
+}
+
+export function inspectNativeExecutable(path: string): "elf" | "mach-o" | "pe" | "unknown" {
+ const header = readFileSync(path).subarray(0, 4).toString("hex");
+ if (header === "7f454c46") return "elf";
+ if (header.startsWith("4d5a")) return "pe";
+ if ([
+ "feedface", "cefaedfe", "feedfacf", "cffaedfe",
+ "cafebabe", "bebafeca", "cafebabf", "bfbafeca",
+ ].includes(header)) return "mach-o";
+ return "unknown";
+}
+
+export function runEmptyDiagnostic(binaryPath = defaultBinary) {
+ if (!existsSync(binaryPath)) throw new Error(`Compiled binary not found: ${binaryPath}`);
+ const tempDir = mkdtempSync(join(tmpdir(), "prime-agent-binary-empty-"));
+ try {
+ const binary = join(tempDir, basename(binaryPath));
+ copyFileSync(binaryPath, binary);
+ chmodSync(binary, 0o755);
+ const emptyPath = join(tempDir, "empty-path");
+ mkdirSync(emptyPath);
+ const runResult = run(binary, ["--version"], tempDir, emptyPath);
+ return {
+ mode: "binary-only-diagnostic" as const,
+ nativeFormat: inspectNativeExecutable(binary),
+ run: runResult,
+ detectedMissingPackageJson: runResult.exitCode !== 0 && runResult.stderr.includes("package.json"),
+ };
+ } finally {
+ rmSync(tempDir, { recursive: true, force: true });
+ }
+}
+
+export function runPackagedSmoke(binaryPath = defaultBinary, sourceDist = defaultDist) {
+ if (!existsSync(binaryPath)) throw new Error(`Compiled binary not found: ${binaryPath}`);
+ const missing = REQUIRED_BINARY_SIDECARS.filter((name) => !existsSync(join(sourceDist, name)));
+ if (missing.length > 0) throw new Error(`Missing compiled-binary sidecars: ${missing.join(", ")}`);
+
+ const tempDir = mkdtempSync(join(tmpdir(), "prime-agent-binary-package-"));
+ try {
+ const binary = join(tempDir, basename(binaryPath));
+ copyFileSync(binaryPath, binary);
+ chmodSync(binary, 0o755);
+ for (const name of REQUIRED_BINARY_SIDECARS) {
+ cpSync(join(sourceDist, name), join(tempDir, name), { recursive: true, dereference: true });
+ }
+
+ const emptyPath = join(tempDir, "empty-path");
+ mkdirSync(emptyPath);
+ let nodeAvailable = false;
+ let npmAvailable = false;
+ try { nodeAvailable = run("node", ["--version"], tempDir, emptyPath).exitCode === 0; } catch {}
+ try { npmAvailable = run("npm", ["--version"], tempDir, emptyPath).exitCode === 0; } catch {}
+ const runs = [["--version"], ["--help"]].map((args) => run(binary, args, tempDir, emptyPath));
+ const passed =
+ inspectNativeExecutable(binary) !== "unknown" &&
+ !nodeAvailable &&
+ !npmAvailable &&
+ runs.every((result) => result.exitCode === 0 && result.stdout.trim().length > 0);
+ return {
+ mode: "packaged-layout-smoke" as const,
+ nativeFormat: inspectNativeExecutable(binary),
+ binarySizeBytes: statSync(binary).size,
+ path: emptyPath,
+ nodeAvailable,
+ npmAvailable,
+ sidecars: [...REQUIRED_BINARY_SIDECARS],
+ runs,
+ passed,
+ };
+ } finally {
+ rmSync(tempDir, { recursive: true, force: true });
+ }
+}
+
+function argumentValue(name: string): string | undefined {
+ const index = process.argv.indexOf(name);
+ return index >= 0 ? process.argv[index + 1] : undefined;
+}
+
+if (import.meta.main) {
+ const binary = resolve(argumentValue("--binary") ?? defaultBinary);
+ const sourceDist = resolve(argumentValue("--dist") ?? dirname(binary));
+ try {
+ const empty = runEmptyDiagnostic(binary);
+ const packaged = runPackagedSmoke(binary, sourceDist);
+ console.log(JSON.stringify({
+ empty: {
+ mode: empty.mode,
+ nativeFormat: empty.nativeFormat,
+ exitCode: empty.run.exitCode,
+ detectedMissingPackageJson: empty.detectedMissingPackageJson,
+ },
+ packaged: {
+ mode: packaged.mode,
+ nativeFormat: packaged.nativeFormat,
+ binarySizeBytes: packaged.binarySizeBytes,
+ nodeAvailable: packaged.nodeAvailable,
+ npmAvailable: packaged.npmAvailable,
+ sidecars: packaged.sidecars,
+ runs: packaged.runs.map((result) => ({
+ args: result.args,
+ exitCode: result.exitCode,
+ stdoutLength: result.stdout.length,
+ stderrLength: result.stderr.length,
+ })),
+ passed: packaged.passed,
+ },
+ }, null, 2));
+ if (empty.nativeFormat === "unknown" || !empty.detectedMissingPackageJson || !packaged.passed) process.exit(1);
+ } catch (error) {
+ console.error(error instanceof Error ? error.message : String(error));
+ process.exit(1);
+ }
+}
diff --git a/scripts/cost.ts b/scripts/cost.ts
index 2774b8d87e..06ef039ea5 100755
--- a/scripts/cost.ts
+++ b/scripts/cost.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env npx tsx
+#!/usr/bin/env bun
import * as fs from "fs";
import * as path from "path";
diff --git a/scripts/dev.ts b/scripts/dev.ts
new file mode 100644
index 0000000000..3027221be3
--- /dev/null
+++ b/scripts/dev.ts
@@ -0,0 +1,41 @@
+import { fileURLToPath } from "node:url";
+
+const workspaceDirs = ["packages/ai", "packages/agent", "packages/coding-agent", "packages/tui"] as const;
+const rootDir = fileURLToPath(new URL("..", import.meta.url));
+const children = workspaceDirs.map((cwd) =>
+ Bun.spawn([process.execPath, "run", "--bun", "--cwd", cwd, "dev"], {
+ cwd: rootDir,
+ env: process.env,
+ stdin: "inherit",
+ stdout: "inherit",
+ stderr: "inherit",
+ }),
+);
+
+let stopping = false;
+
+async function stop(signal: NodeJS.Signals, exitCode: number): Promise {
+ if (!stopping) {
+ stopping = true;
+ for (const child of children) {
+ child.kill(signal);
+ }
+ }
+ await Promise.allSettled(children.map((child) => child.exited));
+ process.exit(exitCode);
+}
+
+process.once("SIGINT", () => {
+ void stop("SIGINT", 130);
+});
+process.once("SIGTERM", () => {
+ void stop("SIGTERM", 143);
+});
+
+const firstExit = await Promise.race(
+ children.map(async (child, index) => ({ index, exitCode: await child.exited })),
+);
+if (!stopping) {
+ console.error(`${workspaceDirs[firstExit.index]} watcher exited with code ${firstExit.exitCode}`);
+ await stop("SIGTERM", firstExit.exitCode || 1);
+}
diff --git a/scripts/edit-tool-stats.mjs b/scripts/edit-tool-stats.mjs
index 2c2c7ceabc..2e42fe9620 100644
--- a/scripts/edit-tool-stats.mjs
+++ b/scripts/edit-tool-stats.mjs
@@ -1,4 +1,4 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
import { createReadStream } from "node:fs";
import { promises as fs } from "node:fs";
@@ -62,7 +62,7 @@ function parseArgs(argv) {
}
function printHelp() {
- console.log(`Usage: node scripts/edit-tool-stats.mjs [options]
+ console.log(`Usage: bun scripts/edit-tool-stats.mjs [options]
Options:
--sessions-dir Sessions directory (default: ~/.pi/agent/sessions)
diff --git a/scripts/pack-prime-agent-release.mjs b/scripts/pack-prime-agent-release.mjs
old mode 100644
new mode 100755
index d264025e11..184ac02ab2
--- a/scripts/pack-prime-agent-release.mjs
+++ b/scripts/pack-prime-agent-release.mjs
@@ -1,348 +1,171 @@
-#!/usr/bin/env node
-
-import { spawnSync } from "node:child_process";
-import { createHash } from "node:crypto";
-import {
- cpSync,
- existsSync,
- mkdirSync,
- renameSync,
- readFileSync,
- rmSync,
- statSync,
- writeFileSync,
-} from "node:fs";
-import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
-import { fileURLToPath } from "node:url";
-
-const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+#!/usr/bin/env bun
+/**
+ * Produce Bun-compiled platform archives.
+ *
+ * This script is the CI entry point for the release artifact lane. It expects
+ * pre-built Bun-compiled binaries for 4 platforms (darwin-arm64, darwin-x64,
+ * linux-arm64, linux-x64) and sidecar files, then produces platform-specific
+ * .tar.gz archives with checksums and channel metadata.
+ *
+ * Usage:
+ * bun scripts/pack-prime-agent-release.mjs \
+ * --base-url \
+ * [--channel stable|beta] \
+ * [--version x.y.z] \
+ * [--out-dir ] \
+ * [--binary-base-dir ] \
+ * [--sidecar-dir ]
+ *
+ * Output:
+ * /artifacts/
+ * prime-agent--.tar.gz
+ * SHA256SUMS
+ *
+ * latest.json|beta.json
+ */
+
+import { existsSync, readFileSync } from "node:fs";
+import { resolve, join } from "node:path";
+
+const root = resolve(import.meta.dirname, "..");
const defaultOutputDir = join(root, "packages", "coding-agent", "release");
const defaultBaseUrl = process.env.PRIME_AGENT_DOWNLOAD_BASE_URL;
-const publicPackageName = process.env.PRIME_AGENT_PACKAGE_NAME || "prime-agent";
-const publicCommandName = process.env.PRIME_AGENT_CMD || "prime-agent";
const releaseChannels = new Set(["stable", "beta"]);
-const releasePackages = [
- { packageDir: "ai", publicName: undefined, artifactName: "prime-agent-ai" },
- { packageDir: "tui", publicName: undefined, artifactName: "prime-agent-tui" },
- { packageDir: "agent", publicName: undefined, artifactName: "prime-agent-core" },
- { packageDir: "coding-agent", publicName: publicPackageName, artifactName: publicPackageName },
-];
-
function parseArgs(args) {
const parsed = {
baseUrl: defaultBaseUrl,
channel: "stable",
+ binaryBaseDir: join(root, "packages", "coding-agent", "binaries"),
+ sidecarDir: join(root, "packages", "coding-agent", "dist"),
outDir: defaultOutputDir,
version: undefined,
+ platforms: [],
};
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
switch (arg) {
case "--channel": {
- const value = args[i + 1];
- if (!value || !releaseChannels.has(value)) {
- throw new Error("--channel must be stable or beta");
- }
+ const value = args[++i];
+ if (!value || !releaseChannels.has(value)) throw new Error("--channel must be stable or beta");
parsed.channel = value;
- i += 1;
break;
}
case "--base-url": {
- const value = args[i + 1];
+ const value = args[++i];
if (!value) throw new Error("--base-url requires a value");
parsed.baseUrl = value;
- i += 1;
+ break;
+ }
+ case "--version": {
+ const value = args[++i];
+ if (!value) throw new Error("--version requires a value");
+ parsed.version = value;
break;
}
case "--out-dir": {
- const value = args[i + 1];
+ const value = args[++i];
if (!value) throw new Error("--out-dir requires a value");
parsed.outDir = resolve(root, value);
- i += 1;
break;
}
- case "--version": {
- const value = args[i + 1];
- if (!value) throw new Error("--version requires a value");
- parsed.version = normalizeVersion(value);
- i += 1;
+ case "--binary-base-dir": {
+ const value = args[++i];
+ if (!value) throw new Error("--binary-base-dir requires a value");
+ parsed.binaryBaseDir = resolve(root, value);
+ break;
+ }
+ case "--sidecar-dir": {
+ const value = args[++i];
+ if (!value) throw new Error("--sidecar-dir requires a value");
+ parsed.sidecarDir = resolve(root, value);
+ break;
+ }
+ case "--platform": {
+ const value = args[++i];
+ if (!value) throw new Error("--platform requires a value");
+ parsed.platforms.push(value);
break;
}
case "--help":
case "-h":
printHelp();
process.exit(0);
- break;
default:
throw new Error(`Unknown argument: ${arg}`);
}
}
- if (!parsed.baseUrl) {
- throw new Error("--base-url or PRIME_AGENT_DOWNLOAD_BASE_URL is required");
+ if (!parsed.baseUrl) throw new Error("--base-url or PRIME_AGENT_DOWNLOAD_BASE_URL is required");
+ parsed.baseUrl = parsed.baseUrl.trim().replace(/\/+$/, "");
+
+ // Resolve and normalize the version once for archive names and embedded URLs.
+ if (!parsed.version) {
+ const cliPkg = JSON.parse(readFileSync(join(root, "packages", "coding-agent", "package.json"), "utf8"));
+ parsed.version = cliPkg.version;
}
+ parsed.version = normalizeVersion(parsed.version);
- parsed.baseUrl = parsed.baseUrl.replace(/\/+$/, "");
return parsed;
}
-function printHelp() {
- console.log(`Usage: node scripts/pack-prime-agent-release.mjs --base-url url [--channel stable|beta] [--version x.y.z] [--out-dir path]
-
-Creates private npm tarballs for R2 distribution:
-
- /artifacts/prime-agent-.tgz
- /artifacts/prime-agent-ai-.tgz
- /artifacts/prime-agent-core-.tgz
- /artifacts/prime-agent-tui-.tgz
- /artifacts/SHA256SUMS
- /artifacts/
- /artifacts/latest.json (stable) or beta.json (beta)
-`);
-}
-
function normalizeVersion(version) {
const normalized = version.startsWith("v") ? version.slice(1) : version;
- if (!/^[0-9A-Za-z.-]+$/.test(normalized)) {
- throw new Error(`Invalid release version: ${version}`);
- }
+ if (!/^[0-9A-Za-z.-]+$/.test(normalized)) throw new Error(`Invalid release version: ${version}`);
return normalized;
}
-function readJson(path) {
- return JSON.parse(readFileSync(path, "utf8"));
-}
-
-function writeJson(path, value) {
- writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
-}
-
-function packagePath(packageDir) {
- return join(root, "packages", packageDir);
-}
-
-function assertSafeOutputDir(outDir) {
- const relativeToReleaseRoot = relative(defaultOutputDir, outDir);
- if (relativeToReleaseRoot === "" || (!relativeToReleaseRoot.startsWith("..") && !isAbsolute(relativeToReleaseRoot))) {
- return;
- }
- throw new Error(`Refusing to remove output directory outside ${defaultOutputDir}: ${outDir}`);
-}
-
-function packageJsonPath(packageDir) {
- return join(packagePath(packageDir), "package.json");
-}
-
-function requireBuiltPackage(packageDir) {
- const dist = join(packagePath(packageDir), "dist");
- if (!existsSync(dist)) {
- throw new Error(`Missing ${dist}. Run npm run build before packing a release.`);
- }
-}
-
-function copyIfExists(source, target) {
- if (existsSync(source)) {
- cpSync(source, target, { recursive: true });
- }
-}
-
-function npmTarballName(packageName, version) {
- return `${packageName.replace(/^@/, "").replace("/", "-")}-${version}.tgz`;
-}
-
-function releaseTarballUrl(baseUrl, version, tarballFile) {
- return `${baseUrl}/releases/v${version}/${tarballFile}`;
-}
-
-function rewriteInternalDependencies(dependencies, internalPackageUrls) {
- if (!dependencies) return undefined;
- const rewritten = {};
- for (const [name, range] of Object.entries(dependencies)) {
- rewritten[name] = internalPackageUrls.get(name) || range;
- }
- return rewritten;
-}
-
-function releaseScripts(sourceScripts) {
- if (!sourceScripts?.postinstall) return undefined;
- return {
- postinstall: sourceScripts.postinstall,
- };
-}
-
-function createReleasePackageJson(sourcePackage, packageName, releaseVersion, internalPackageUrls) {
- const packageJson = {
- ...sourcePackage,
- name: packageName,
- version: releaseVersion,
- dependencies: rewriteInternalDependencies(sourcePackage.dependencies, internalPackageUrls),
- optionalDependencies: rewriteInternalDependencies(sourcePackage.optionalDependencies, internalPackageUrls),
- scripts: releaseScripts(sourcePackage.scripts),
- };
-
- delete packageJson.devDependencies;
- delete packageJson.overrides;
- delete packageJson.private;
-
- if (packageName === publicPackageName) {
- packageJson.bin = {
- [publicCommandName]: "dist/bundle/cli.js",
- };
- packageJson.piConfig = {
- ...(packageJson.piConfig || {}),
- name: publicCommandName,
- configDir: ".prime/agent",
- };
- }
-
- return packageJson;
-}
-
-function copyPackageContents(sourceDir, targetDir, packageJson) {
- mkdirSync(targetDir, { recursive: true });
- writeJson(join(targetDir, "package.json"), packageJson);
-
- for (const entry of ["dist", "docs", "examples", "skills", "postinstall.cjs", "README.md", "CHANGELOG.md"]) {
- copyIfExists(join(sourceDir, entry), join(targetDir, entry));
- }
-}
+function printHelp() {
+ console.log(`Usage: bun scripts/pack-prime-agent-release.mjs --base-url url [--channel stable|beta] [--version x.y.z] [--out-dir path] [--binary-base-dir path] [--sidecar-dir path] [--platform platform]
-function run(command, args, cwd) {
- const result = spawnSync(command, args, {
- cwd,
- stdio: "pipe",
- encoding: "utf8",
- });
+Creates platform release archives:
- if (result.status !== 0) {
- if (result.stdout) process.stdout.write(result.stdout);
- if (result.stderr) process.stderr.write(result.stderr);
- throw new Error(`${command} ${args.join(" ")} failed with exit code ${result.status}`);
- }
-
- if (result.stderr) process.stderr.write(result.stderr);
- return result.stdout.trim();
-}
+ /artifacts/prime-agent--.tar.gz
+ /artifacts/SHA256SUMS
+ /artifacts/
+ /artifacts/latest.json (stable) or beta.json (beta)
-function sha256File(path) {
- const hash = createHash("sha256");
- hash.update(readFileSync(path));
- return hash.digest("hex");
+--binary-base-dir defaults to packages/coding-agent/binaries/
+--sidecar-dir defaults to packages/coding-agent/dist/
+`);
}
-function main() {
+async function main() {
const args = parseArgs(process.argv.slice(2));
- const sourcePackages = new Map(
- releasePackages.map((releasePackage) => [
- releasePackage.packageDir,
- readJson(packageJsonPath(releasePackage.packageDir)),
- ]),
- );
- const cliPackage = sourcePackages.get("coding-agent");
- const releaseVersion = args.version || normalizeVersion(process.env.PRIME_AGENT_VERSION || cliPackage.version);
- for (const releasePackage of releasePackages) {
- requireBuiltPackage(releasePackage.packageDir);
- }
-
- // Dependency keys stay on the source package names so existing compiled imports
- // keep resolving, while release package names and artifact filenames are branded.
- const sourcePackageNames = new Map();
- const packageNames = new Map();
- const artifactFiles = new Map();
- for (const releasePackage of releasePackages) {
- const sourcePackage = sourcePackages.get(releasePackage.packageDir);
- const packageName = releasePackage.publicName || releasePackage.artifactName || sourcePackage.name;
- sourcePackageNames.set(releasePackage.packageDir, sourcePackage.name);
- packageNames.set(releasePackage.packageDir, packageName);
- artifactFiles.set(
- releasePackage.packageDir,
- npmTarballName(releasePackage.artifactName || packageName, releaseVersion),
+ if (!existsSync(args.sidecarDir)) {
+ throw new Error(
+ `Sidecar dir not found: ${args.sidecarDir}. Run "bun run build && bun run copy-binary-assets" first.`,
);
}
- const internalPackageUrls = new Map();
- for (const releasePackage of releasePackages) {
- if (releasePackage.packageDir === "coding-agent") continue;
- const sourcePackageName = sourcePackageNames.get(releasePackage.packageDir);
- const artifactFile = artifactFiles.get(releasePackage.packageDir);
- internalPackageUrls.set(sourcePackageName, releaseTarballUrl(args.baseUrl, releaseVersion, artifactFile));
+ // The assembly script is in the same scripts directory.
+ const assemblyScript = join(import.meta.dirname, "assemble-release-archives.mjs");
+ if (!existsSync(assemblyScript)) {
+ throw new Error(`Assembly script not found: ${assemblyScript}`);
}
- const stagingRoot = join(args.outDir, "packages");
- const artifactsDir = join(args.outDir, "artifacts");
- assertSafeOutputDir(args.outDir);
- rmSync(args.outDir, { force: true, recursive: true });
- mkdirSync(stagingRoot, { recursive: true });
- mkdirSync(artifactsDir, { recursive: true });
-
- const tarballs = [];
- for (const releasePackage of releasePackages) {
- const sourcePackage = sourcePackages.get(releasePackage.packageDir);
- const packageName = packageNames.get(releasePackage.packageDir);
- const stagingDir = join(stagingRoot, releasePackage.packageDir);
- const packageJson = createReleasePackageJson(
- sourcePackage,
- packageName,
- releaseVersion,
- internalPackageUrls,
- );
-
- copyPackageContents(packagePath(releasePackage.packageDir), stagingDir, packageJson);
+ const { spawnSync } = await import("node:child_process");
+ const assemblyArgs = [
+ assemblyScript,
+ "--base-url", args.baseUrl,
+ "--channel", args.channel,
+ "--version", args.version,
+ "--binary-dir", args.binaryBaseDir,
+ "--sidecar-dir", args.sidecarDir,
+ "--out-dir", args.outDir,
+ ];
+ for (const platform of args.platforms) assemblyArgs.push("--platform", platform);
+ const result = spawnSync(process.execPath, assemblyArgs, { stdio: "inherit", encoding: "utf8" });
- const tarballName = run("npm", ["pack", stagingDir, "--pack-destination", artifactsDir, "--silent"], root)
- .split("\n")
- .at(-1);
- if (!tarballName) {
- throw new Error(`npm pack did not report a tarball name for ${packageName}`);
- }
-
- const tarballPath = join(artifactsDir, basename(tarballName));
- if (!existsSync(tarballPath) || !statSync(tarballPath).isFile()) {
- throw new Error(`npm pack did not create ${tarballPath}`);
- }
-
- const artifactFile = artifactFiles.get(releasePackage.packageDir);
- const artifactPath = join(artifactsDir, artifactFile);
- if (tarballPath !== artifactPath) {
- rmSync(artifactPath, { force: true });
- renameSync(tarballPath, artifactPath);
- }
-
- tarballs.push({
- name: packageName,
- file: artifactFile,
- sha256: sha256File(artifactPath),
- });
- }
-
- tarballs.sort((left, right) => left.file.localeCompare(right.file));
- writeFileSync(
- join(artifactsDir, "SHA256SUMS"),
- tarballs.map((tarball) => `${tarball.sha256} ${tarball.file}`).join("\n") + "\n",
- );
- writeFileSync(join(artifactsDir, args.channel), `v${releaseVersion}\n`);
- const manifestName = args.channel === "stable" ? "latest.json" : "beta.json";
- writeJson(join(artifactsDir, manifestName), {
- version: `v${releaseVersion}`,
- package: publicPackageName,
- tarball: `releases/v${releaseVersion}/${artifactFiles.get("coding-agent")}`,
- tarballs: tarballs.map((tarball) => ({
- package: tarball.name,
- file: tarball.file,
- sha256: tarball.sha256,
- })),
- });
-
- for (const tarball of tarballs) {
- console.log(`Created ${join(artifactsDir, tarball.file)}`);
+ if (result.status !== 0) {
+ throw new Error(`assemble-release-archives.mjs failed with exit code ${result.status}`);
}
}
try {
- main();
+ await main();
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
diff --git a/scripts/profile-coding-agent-node.mjs b/scripts/profile-coding-agent-node.mjs
index 44ccf86b6c..c646aac9e3 100644
--- a/scripts/profile-coding-agent-node.mjs
+++ b/scripts/profile-coding-agent-node.mjs
@@ -17,13 +17,11 @@ const startupBenchmarkEnvName = "PI_STARTUP_BENCHMARK";
function printHelp() {
console.log(`Usage:
- node scripts/profile-coding-agent-node.mjs [options]
+ bun scripts/profile-coding-agent-node.mjs [options]
-Profiles coding-agent startup with the runtime selected below:
-- npm run profile:tui -> builds packages/coding-agent and profiles TUI startup with Node
-- npm run profile:rpc -> builds packages/coding-agent and profiles RPC startup with Node
-- bun run profile:tui -> profiles TUI startup from src/cli.ts directly with Bun
-- bun run profile:rpc -> profiles RPC startup from src/cli.ts directly with Bun
+Profiles coding-agent startup with the selected runtime. bun run profile:tui
+and bun run profile:rpc use Bun by default. Pass --runtime node only for
+an explicit Node compatibility comparison.
Options:
--mode tui or rpc (default: tui)
@@ -282,19 +280,8 @@ async function runBuild() {
process.stdout.write("Building packages/tui, packages/ai, packages/agent, and packages/coding-agent...\n");
const startedAt = performance.now();
const child = spawn(
- "npm",
- [
- "run",
- "build",
- "--workspace",
- "packages/tui",
- "--workspace",
- "packages/ai",
- "--workspace",
- "packages/agent",
- "--workspace",
- "packages/coding-agent",
- ],
+ process.execPath,
+ ["run", "build"],
{
cwd: repoRoot,
env: process.env,
@@ -352,7 +339,7 @@ function getRuntimeCommand(runtime, mode, profileDir, profileName, cpuProfile) {
}
args.push(distCliPath, ...benchmarkArgs);
return {
- executable: process.execPath,
+ executable: "node",
args,
};
}
diff --git a/scripts/publish-workspaces.ts b/scripts/publish-workspaces.ts
new file mode 100644
index 0000000000..ed78c44076
--- /dev/null
+++ b/scripts/publish-workspaces.ts
@@ -0,0 +1,18 @@
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const rootDir = dirname(dirname(fileURLToPath(import.meta.url)));
+const packageDirs = ["tui", "ai", "agent", "coding-agent"] as const;
+const dryRun = process.argv.includes("--dry-run");
+
+for (const packageDir of packageDirs) {
+ const command = [process.execPath, "publish", "--access", "public", "--ignore-scripts"];
+ if (dryRun) command.push("--dry-run");
+ const result = Bun.spawnSync(command, {
+ cwd: join(rootDir, "packages", packageDir),
+ stdin: "inherit",
+ stdout: "inherit",
+ stderr: "inherit",
+ });
+ if (result.exitCode !== 0) process.exit(result.exitCode);
+}
diff --git a/scripts/read-tool-stats.mjs b/scripts/read-tool-stats.mjs
index 07bedd8f90..487e11e0c9 100755
--- a/scripts/read-tool-stats.mjs
+++ b/scripts/read-tool-stats.mjs
@@ -1,4 +1,4 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
import { createReadStream } from "node:fs";
import { promises as fs } from "node:fs";
@@ -53,7 +53,7 @@ function parseArgs(argv) {
}
function printHelp() {
- console.log(`Usage: node scripts/read-tool-stats.mjs [options]
+ console.log(`Usage: bun scripts/read-tool-stats.mjs [options]
Options:
--sessions-dir Sessions directory (default: ~/.pi/agent/sessions)
diff --git a/scripts/release.mjs b/scripts/release.mjs
index 4eb4c83cba..bee63bcf43 100755
--- a/scripts/release.mjs
+++ b/scripts/release.mjs
@@ -1,19 +1,19 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
/**
- * Release script for pi-mono
+ * Release script for Prime Agent
*
* Usage:
- * node scripts/release.mjs
- * node scripts/release.mjs
- * node scripts/release.mjs --dry-run (preview changelog updates only)
+ * bun scripts/release.mjs
+ * bun scripts/release.mjs
+ * bun scripts/release.mjs --dry-run (preview changelog updates only)
*
* Steps:
* 1. Check for uncommitted changes
- * 2. Bump version via npm run version:xxx or set an explicit version
+ * 2. Bump every workspace version and refresh bun.lock
* 3. Update CHANGELOG.md files: aggregate .changes/*.md fragments into a
* [version] - date section, git rm the consumed fragments
* 4. Commit and tag
- * 5. Publish to npm
+ * 5. Publish registry packages with Bun
*/
import { execSync } from "child_process";
@@ -27,7 +27,7 @@ const BUMP_TYPES = new Set(["major", "minor", "patch"]);
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
if (!RELEASE_TARGET || (!BUMP_TYPES.has(RELEASE_TARGET) && !SEMVER_RE.test(RELEASE_TARGET))) {
- console.error("Usage: node scripts/release.mjs [--dry-run]");
+ console.error("Usage: bun scripts/release.mjs [--dry-run]");
process.exit(1);
}
@@ -82,7 +82,7 @@ function bumpOrSetVersion(target) {
if (BUMP_TYPES.has(target)) {
console.log(`Bumping version (${target})...`);
- run(`npm run version:${target}`);
+ run(`bun run version:${target}`);
return getVersion();
}
@@ -92,9 +92,7 @@ function bumpOrSetVersion(target) {
}
console.log(`Setting explicit version (${target})...`);
- run(
- `npm version ${target} -ws --no-git-tag-version && node scripts/sync-versions.js && npx shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install`,
- );
+ run(`bun scripts/set-version.ts ${target}`);
return getVersion();
}
@@ -214,8 +212,8 @@ run(`git commit -m "Release v${version}"`);
run(`git tag v${version}`);
console.log();
-console.log("Publishing to npm...");
-run("npm run publish");
+console.log("Publishing registry packages with Bun...");
+run("bun run publish");
console.log();
console.log("Pushing to remote...");
diff --git a/scripts/remove-paths.ts b/scripts/remove-paths.ts
new file mode 100644
index 0000000000..312d708fed
--- /dev/null
+++ b/scripts/remove-paths.ts
@@ -0,0 +1,9 @@
+import { rm } from "node:fs/promises";
+import { resolve } from "node:path";
+
+if (process.argv.length < 3) {
+ console.error("Usage: bun scripts/remove-paths.ts [...path]");
+ process.exit(2);
+}
+
+await Promise.all(process.argv.slice(2).map((path) => rm(resolve(path), { recursive: true, force: true })));
diff --git a/scripts/run-with-clean-env.ts b/scripts/run-with-clean-env.ts
new file mode 100644
index 0000000000..c99f4e3a6e
--- /dev/null
+++ b/scripts/run-with-clean-env.ts
@@ -0,0 +1,34 @@
+/** Run a command without inheriting live Prime Agent orchestration state. */
+
+const [requestedCommand, ...args] = process.argv.slice(2);
+if (!requestedCommand) {
+ console.error("Usage: bun scripts/run-with-clean-env.ts [args...]");
+ process.exit(2);
+}
+
+const env = { ...process.env };
+for (const name of Object.keys(env)) {
+ if (name.startsWith("PRIME_AGENT_INTERNAL_") || name.startsWith("RLM_")) {
+ delete env[name];
+ }
+}
+for (const name of ["PRIME_AGENT_CODING_AGENT_DIR", "PRIME_AGENT_KERNEL_OWNER_PID"]) {
+ delete env[name];
+}
+
+const command = requestedCommand === "bun" ? process.execPath : requestedCommand;
+const child = Bun.spawn([command, ...args], {
+ cwd: process.cwd(),
+ env,
+ stdin: "inherit",
+ stdout: "inherit",
+ stderr: "inherit",
+});
+
+for (const signal of ["SIGINT", "SIGTERM"] as const) {
+ process.on(signal, () => {
+ child.kill(signal);
+ });
+}
+
+process.exit(await child.exited);
diff --git a/scripts/session-context-stats.mjs b/scripts/session-context-stats.mjs
index e9ae425c30..6f47329c0e 100755
--- a/scripts/session-context-stats.mjs
+++ b/scripts/session-context-stats.mjs
@@ -1,4 +1,4 @@
-#!/usr/bin/env node
+#!/usr/bin/env bun
import { createReadStream } from "node:fs";
import { promises as fs } from "node:fs";
@@ -34,7 +34,7 @@ function parseArgs(argv) {
}
function printHelp() {
- console.log(`Usage: node scripts/session-context-stats.mjs [options]
+ console.log(`Usage: bun scripts/session-context-stats.mjs [options]
Options:
--sessions-dir Sessions directory (default: ~/.pi/agent/sessions)
diff --git a/scripts/session-transcripts.ts b/scripts/session-transcripts.ts
index e10dbff9f4..0b5a7410fc 100644
--- a/scripts/session-transcripts.ts
+++ b/scripts/session-transcripts.ts
@@ -1,9 +1,9 @@
-#!/usr/bin/env npx tsx
+#!/usr/bin/env bun
/**
* Extracts session transcripts for a given cwd, splits into context-sized files,
* optionally spawns subagents to analyze patterns.
*
- * Usage: npx tsx scripts/session-transcripts.ts [--analyze] [--output ] [cwd]
+ * Usage: bun scripts/session-transcripts.ts [--analyze] [--output ] [cwd]
* --analyze Spawn pi subagents to analyze each transcript file
* --output Output directory for transcript files (defaults to ./session-transcripts)
* cwd Working directory to extract sessions for (defaults to current)
diff --git a/scripts/set-version.ts b/scripts/set-version.ts
new file mode 100644
index 0000000000..d40c1b594b
--- /dev/null
+++ b/scripts/set-version.ts
@@ -0,0 +1,88 @@
+import { readFile, writeFile } from "node:fs/promises";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const rootDir = dirname(dirname(fileURLToPath(import.meta.url)));
+const manifestPaths = [
+ "package.json",
+ "packages/tui/package.json",
+ "packages/ai/package.json",
+ "packages/agent/package.json",
+ "packages/coding-agent/package.json",
+] as const;
+const target = process.argv.find((arg, index) => index >= 2 && !arg.startsWith("--"));
+const dryRun = process.argv.includes("--dry-run");
+const semverPattern = /^(\d+)\.(\d+)\.(\d+)$/;
+
+if (!target || (!semverPattern.test(target) && !["patch", "minor", "major"].includes(target))) {
+ console.error("Usage: bun scripts/set-version.ts [--dry-run]");
+ process.exit(2);
+}
+
+const manifests = await Promise.all(
+ manifestPaths.map(async (path) => {
+ const text = await readFile(join(rootDir, path), "utf8");
+ return { path, text, value: JSON.parse(text) };
+ }),
+);
+const currentVersions = new Set(manifests.map(({ value }) => value.version as string));
+if (currentVersions.size !== 1) {
+ throw new Error(`Workspace versions are not in lockstep: ${[...currentVersions].join(", ")}`);
+}
+const currentVersion = manifests[0].value.version as string;
+const currentMatch = currentVersion.match(semverPattern);
+if (!currentMatch) {
+ throw new Error(`Current version is not x.y.z: ${currentVersion}`);
+}
+
+function nextVersion(): string {
+ if (semverPattern.test(target!)) return target!;
+ let major = Number(currentMatch![1]);
+ let minor = Number(currentMatch![2]);
+ let patch = Number(currentMatch![3]);
+ if (target === "major") {
+ major += 1;
+ minor = 0;
+ patch = 0;
+ } else if (target === "minor") {
+ minor += 1;
+ patch = 0;
+ } else {
+ patch += 1;
+ }
+ return `${major}.${minor}.${patch}`;
+}
+
+const version = nextVersion();
+const workspaceVersions = new Map(manifests.slice(1).map(({ value }) => [value.name as string, version]));
+for (const manifest of manifests) {
+ manifest.value.version = version;
+ for (const section of ["dependencies", "devDependencies", "optionalDependencies"] as const) {
+ const dependencies = manifest.value[section] as Record | undefined;
+ if (!dependencies) continue;
+ for (const name of Object.keys(dependencies)) {
+ if (workspaceVersions.has(name)) dependencies[name] = `^${version}`;
+ }
+ }
+}
+
+console.log(`${currentVersion} -> ${version}`);
+if (dryRun) process.exit(0);
+
+const lockPath = join(rootDir, "bun.lock");
+const originalLock = await readFile(lockPath, "utf8");
+for (const manifest of manifests) {
+ await writeFile(join(rootDir, manifest.path), `${JSON.stringify(manifest.value, null, "\t")}\n`);
+}
+const install = Bun.spawnSync([process.execPath, "install", "--lockfile-only"], {
+ cwd: rootDir,
+ stdin: "inherit",
+ stdout: "inherit",
+ stderr: "inherit",
+});
+if (install.exitCode !== 0) {
+ await Promise.all(manifests.map((manifest) => writeFile(join(rootDir, manifest.path), manifest.text)));
+ await writeFile(lockPath, originalLock);
+ console.error("Version update failed; restored manifests and bun.lock.");
+ process.exit(install.exitCode);
+}
diff --git a/scripts/setup-kernel-venv.sh b/scripts/setup-kernel-venv.sh
index 4a2df7314a..cd02b0a859 100755
--- a/scripts/setup-kernel-venv.sh
+++ b/scripts/setup-kernel-venv.sh
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
-npx tsx packages/coding-agent/src/core/kernel/bootstrap-cli.ts
+bun packages/coding-agent/src/core/kernel/bootstrap-cli.ts
diff --git a/scripts/stats.ts b/scripts/stats.ts
index 15c353e382..72597ccfaf 100644
--- a/scripts/stats.ts
+++ b/scripts/stats.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env npx tsx
+#!/usr/bin/env bun
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
diff --git a/scripts/sync-versions.js b/scripts/sync-versions.js
deleted file mode 100644
index c850b4c75e..0000000000
--- a/scripts/sync-versions.js
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Syncs all workspace package dependency versions to match their current versions.
- * This ensures lockstep versioning across the monorepo.
- */
-
-import { readFileSync, writeFileSync, readdirSync } from 'fs';
-import { join } from 'path';
-
-const packagesDir = join(process.cwd(), 'packages');
-const packageDirs = readdirSync(packagesDir, { withFileTypes: true })
- .filter(dirent => dirent.isDirectory())
- .map(dirent => dirent.name);
-
-const packages = {};
-const versionMap = {};
-
-for (const dir of packageDirs) {
- const pkgPath = join(packagesDir, dir, 'package.json');
- try {
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
- packages[dir] = { path: pkgPath, data: pkg };
- versionMap[pkg.name] = pkg.version;
- } catch (e) {
- console.error(`Failed to read ${pkgPath}:`, e.message);
- }
-}
-
-console.log('Current versions:');
-for (const [name, version] of Object.entries(versionMap).sort()) {
- console.log(` ${name}: ${version}`);
-}
-
-const versions = new Set(Object.values(versionMap));
-if (versions.size > 1) {
- console.error('\n❌ ERROR: Not all packages have the same version!');
- console.error('Expected lockstep versioning. Run one of:');
- console.error(' npm run version:patch');
- console.error(' npm run version:minor');
- console.error(' npm run version:major');
- process.exit(1);
-}
-
-console.log('\n✅ All packages at same version (lockstep)');
-
-let totalUpdates = 0;
-for (const [dir, pkg] of Object.entries(packages)) {
- let updated = false;
-
- if (pkg.data.dependencies) {
- for (const [depName, currentVersion] of Object.entries(pkg.data.dependencies)) {
- if (versionMap[depName]) {
- const newVersion = `^${versionMap[depName]}`;
- if (currentVersion !== newVersion) {
- console.log(`\n${pkg.data.name}:`);
- console.log(` ${depName}: ${currentVersion} → ${newVersion}`);
- pkg.data.dependencies[depName] = newVersion;
- updated = true;
- totalUpdates++;
- }
- }
- }
- }
-
- if (pkg.data.devDependencies) {
- for (const [depName, currentVersion] of Object.entries(pkg.data.devDependencies)) {
- if (versionMap[depName]) {
- const newVersion = `^${versionMap[depName]}`;
- if (currentVersion !== newVersion) {
- console.log(`\n${pkg.data.name}:`);
- console.log(` ${depName}: ${currentVersion} → ${newVersion} (devDependencies)`);
- pkg.data.devDependencies[depName] = newVersion;
- updated = true;
- totalUpdates++;
- }
- }
- }
- }
-
- if (updated) {
- writeFileSync(pkg.path, JSON.stringify(pkg.data, null, '\t') + '\n');
- }
-}
-
-if (totalUpdates === 0) {
- console.log('\nAll inter-package dependencies already in sync.');
-} else {
- console.log(`\n✅ Updated ${totalUpdates} dependency version(s)`);
-}
diff --git a/scripts/test-windows-installer.ps1 b/scripts/test-windows-installer.ps1
new file mode 100644
index 0000000000..c5eb17a303
--- /dev/null
+++ b/scripts/test-windows-installer.ps1
@@ -0,0 +1,86 @@
+#!/usr/bin/env pwsh
+[CmdletBinding()]
+param()
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+$ProgressPreference = "SilentlyContinue"
+
+if (-not $IsWindows -and $PSVersionTable.PSEdition -eq "Core") {
+ throw "Windows installer smoke test must run on Windows."
+}
+
+$repoRoot = Split-Path -Parent $PSScriptRoot
+$packageDir = Join-Path $repoRoot "packages\coding-agent"
+$distDir = Join-Path $packageDir "dist"
+$version = (Get-Content (Join-Path $packageDir "package.json") -Raw | ConvertFrom-Json).version
+$testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "prime-agent-windows-installer-$([guid]::NewGuid().ToString("N"))"
+$serverRoot = Join-Path $testRoot "server"
+$releaseDir = Join-Path $serverRoot "releases\v$version"
+$stagingDir = Join-Path $testRoot "archive"
+$localAppData = Join-Path $testRoot "local-app-data"
+$artifactName = "prime-agent-$version-windows-x64.zip"
+$artifactPath = Join-Path $releaseDir $artifactName
+$originalLocalAppData = $env:LOCALAPPDATA
+$originalDownloadBaseUrl = $env:PRIME_AGENT_DOWNLOAD_BASE_URL
+$originalUserPath = [Environment]::GetEnvironmentVariable("PATH", "User")
+$server = $null
+
+try {
+ New-Item -ItemType Directory -Path $releaseDir, $stagingDir, $localAppData -Force | Out-Null
+ Copy-Item -Path (Join-Path $distDir "*") -Destination $stagingDir -Recurse -Force
+ Move-Item -LiteralPath (Join-Path $stagingDir "pi.exe") -Destination (Join-Path $stagingDir "prime-agent.exe")
+ Compress-Archive -Path (Join-Path $stagingDir "*") -DestinationPath $artifactPath -CompressionLevel Optimal
+ $hash = (Get-FileHash -LiteralPath $artifactPath -Algorithm SHA256).Hash.ToLowerInvariant()
+ Set-Content -LiteralPath (Join-Path $releaseDir "SHA256SUMS") -Value "$hash $artifactName" -Encoding Ascii
+ Set-Content -LiteralPath (Join-Path $serverRoot "stable") -Value $version -Encoding Ascii
+
+ $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
+ $listener.Start()
+ $port = ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port
+ $listener.Stop()
+
+ $server = Start-Process -FilePath "python" -ArgumentList @(
+ "-m", "http.server", "$port", "--bind", "127.0.0.1", "--directory", $serverRoot
+ ) -PassThru -WindowStyle Hidden
+ $baseUrl = "http://127.0.0.1:$port"
+ $deadline = [DateTime]::UtcNow.AddSeconds(15)
+ while ($true) {
+ try {
+ Invoke-WebRequest -Uri "$baseUrl/stable" -UseBasicParsing -TimeoutSec 1 | Out-Null
+ break
+ } catch {
+ if ([DateTime]::UtcNow -ge $deadline) { throw "Local release server did not become ready." }
+ Start-Sleep -Milliseconds 100
+ }
+ }
+
+ $env:LOCALAPPDATA = $localAppData
+ $env:PRIME_AGENT_DOWNLOAD_BASE_URL = $baseUrl
+ & (Join-Path $repoRoot "install.ps1") -Version $version
+
+ $shim = Join-Path $localAppData "PrimeAgent\bin\prime-agent.cmd"
+ $binary = Join-Path $localAppData "PrimeAgent\versions\v$version\prime-agent.exe"
+ if (-not (Test-Path -LiteralPath $shim)) { throw "Installer did not create prime-agent.cmd" }
+ if (-not (Test-Path -LiteralPath $binary)) { throw "Installer did not create the versioned executable" }
+
+ & $shim --version
+ if ($LASTEXITCODE -ne 0) { throw "Installed prime-agent --version failed with $LASTEXITCODE" }
+ & $shim --help | Out-Null
+ if ($LASTEXITCODE -ne 0) { throw "Installed prime-agent --help failed with $LASTEXITCODE" }
+
+ & (Join-Path $repoRoot "install.ps1") -Version $version -Update
+ & (Join-Path $repoRoot "install.ps1") -Uninstall
+ if (Test-Path -LiteralPath (Join-Path $localAppData "PrimeAgent")) {
+ throw "Uninstall left the PrimeAgent install directory behind"
+ }
+ Write-Host "Windows installer end-to-end smoke passed."
+} finally {
+ if ($server -and -not $server.HasExited) {
+ Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue
+ }
+ [Environment]::SetEnvironmentVariable("PATH", $originalUserPath, "User")
+ $env:LOCALAPPDATA = $originalLocalAppData
+ $env:PRIME_AGENT_DOWNLOAD_BASE_URL = $originalDownloadBaseUrl
+ Remove-Item -LiteralPath $testRoot -Recurse -Force -ErrorAction SilentlyContinue
+}
diff --git a/scripts/tool-stats.ts b/scripts/tool-stats.ts
index 8d39b8683c..97b59e1872 100755
--- a/scripts/tool-stats.ts
+++ b/scripts/tool-stats.ts
@@ -1,4 +1,4 @@
-#!/usr/bin/env npx tsx
+#!/usr/bin/env bun
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
diff --git a/test.sh b/test.sh
index 19b21dade1..081b6af55a 100755
--- a/test.sh
+++ b/test.sh
@@ -60,4 +60,4 @@ unset BEDROCK_EXTENSIVE_MODEL_TEST
unset FIREWORKS_API_KEY
echo "Running tests without API keys..."
-npm test
+bun scripts/run-with-clean-env.ts bun test --isolate --timeout 30000
diff --git a/tsconfig.json b/tsconfig.json
index 4dfbf3d254..a54d87e145 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,7 +17,9 @@
"@earendil-works/pi-tui": ["./packages/tui/src/index.ts"],
"@earendil-works/pi-tui/*": ["./packages/tui/src/*"],
"@earendil-works/pi-agent-old": ["./packages/agent-old/src/index.ts"],
- "@earendil-works/pi-agent-old/*": ["./packages/agent-old/src/*"]
+ "@earendil-works/pi-agent-old/*": ["./packages/agent-old/src/*"],
+ "bun:test": ["./node_modules/bun-types/test.d.ts"],
+ "vitest": ["./types/vitest.d.ts"]
}
},
"include": ["packages/*/src/**/*", "packages/*/test/**/*", "packages/coding-agent/examples/**/*"],
diff --git a/types/vitest.d.ts b/types/vitest.d.ts
new file mode 100644
index 0000000000..3e6c6a8be7
--- /dev/null
+++ b/types/vitest.d.ts
@@ -0,0 +1,45 @@
+export * from "@vitest/runner";
+export { assert, createExpect, expect } from "@vitest/expect";
+export type { Mock, MockedFunction, MockInstance, MockedObject } from "@vitest/spy";
+
+import type { MockInstance } from "@vitest/spy";
+
+type Awaitable = T | Promise;
+type CompatMocked = T extends (...args: any[]) => any
+ ? T & MockInstance
+ : T extends object
+ ? { [K in keyof T]: CompatMocked }
+ : T;
+export type Mocked = CompatMocked;
+
+type VitestCompat = {
+ advanceTimersByTime(milliseconds: number): VitestCompat;
+ advanceTimersByTimeAsync(milliseconds: number): Promise;
+ advanceTimersToNextTimer(): VitestCompat;
+ advanceTimersToNextTimerAsync(): Promise;
+ clearAllMocks(): VitestCompat;
+ clearAllTimers(): VitestCompat;
+ fn: typeof import("@vitest/spy").fn;
+ getTimerCount(): number;
+ hoisted(factory: () => T): T;
+ isFakeTimers(): boolean;
+ isMockFunction(fn: unknown): fn is MockInstance;
+ mock(path: string, factory?: () => unknown): void;
+ mocked(value: T): CompatMocked;
+ resetAllMocks(): VitestCompat;
+ restoreAllMocks(): VitestCompat;
+ runAllTimers(): VitestCompat;
+ runAllTimersAsync(): Promise;
+ runOnlyPendingTimers(): VitestCompat;
+ runOnlyPendingTimersAsync(): Promise;
+ setSystemTime(value: Date | number): VitestCompat;
+ spyOn: typeof import("@vitest/spy").spyOn;
+ stubEnv(name: string, value: string | undefined): VitestCompat;
+ stubGlobal(name: PropertyKey, value: unknown): VitestCompat;
+ unstubAllEnvs(): VitestCompat;
+ unstubAllGlobals(): VitestCompat;
+ useFakeTimers(options?: { now?: Date | number }): VitestCompat;
+ useRealTimers(): VitestCompat;
+ waitFor(assertion: () => Awaitable, options?: { timeout?: number; interval?: number }): Promise;
+};
+export const vi: VitestCompat;