AGENTS.md@docker · diff
git:20260811.1bbcad3 to git:20260819.cccbfbd
2 added, 2 removed. Audit A to A.
# DOX — docker
Files in this directory. One row per file. Non-source area. Subdir files owned by their own `AGENTS.md` (`fixtures/`, `scripts/`). See change: migrate-file-index-to-agents-tree. See change: fold-oversized-agents-directories.
| File | Purpose |
|------|---------|
| `.env.example` | Container env knobs, each commented. API keys, `DASHBOARD_PORT`, `PI_GATEWAY_PORT`, `PI_GATEWAY_BIND`, `TUNNEL_ENABLED`, `ZROK_TOKEN`, `PI_SPAWN_STRATEGY`, `MEM_LIMIT`, `PI_WORKSPACES`, `PI_DASHBOARD_PIN_DIRS`. See change: docker-packaging. |
| `.gitignore` | Excludes `.env` + `compose.override.yml` (user-specific). See change: docker-packaging. |
| `compose.dev.yml` | Dev overlay. Bind-mounts source, anonymous node_modules volume keeps Linux node-pty, exposes 5173, `NODE_ENV=development`, command `start --dev`. See change: docker-packaging. |
| `compose.override.yml.example` | Power-user template. Path-identical workspace binds, `:ro` example, `PI_DASHBOARD_PIN_DIRS`. Copy to `compose.override.yml`. See change: docker-packaging. |
| `compose.test.cap.yml` | Overlay-mode capability layer. Grants `cap_add: [SYS_ADMIN]` for `mount -t overlay` in test-entrypoint.sh. test-up.sh layers it only in overlay mode; omits when `TEST_COPY_MODE=1` (copy mode runs no added capability). See change: docker-test-harness. |
- | `compose.test.yml` | Test overlay on `compose.yml`. Sets `PI_DASHBOARD_NO_MDNS=1`, `DASHBOARD_PORT="${DASHBOARD_PORT:-18000}"`, `PI_GATEWAY_PORT="${PI_GATEWAY_PORT:-18999}"` (de-hardcoded, env-driven), `PI_GATEWAY_BIND=127.0.0.1`, `TUNNEL_ENABLED=false`. Per-worktree image tag `pi-dashboard:${TEST_IMAGE_TAG:-local}`, `restart: "unless-stopped"` (matches base compose.yml; was "no": dashboard daemon is container PID 1, so `POST /api/restart` exited the container and nothing revived the harness. unless-stopped still honours explicit `docker stop`; `test-down.sh` runs `compose down` regardless. Trade-off: crash-looping container respawns instead of staying dead; `test-up.sh` health gate still fails the run when dashboard never answers). No `cap_add` here — CAP_SYS_ADMIN layered by `compose.test.cap.yml` in overlay mode only. Overrides `pi-state` volume to tmpfs (ephemeral). Mounts `HOST_CWD` ro lower at `/mnt/test-lower`, tmpfs upper/work, fixtures source at `/fixtures-src`, `test-entrypoint.sh` bind. entrypoint `test-entrypoint.sh`. Runs `user: root` + `HOME=/home/pi` so entrypoint can `mkdir`/`mount` overlay at `/` while state lands in pi-state tmpfs. Passes `PI_E2E_SEED` env to container (`PI_E2E_SEED: "${PI_E2E_SEED:-}"`); default empty keeps harness UI-only. Bind-mounts `../qa/fixtures:/app/qa/fixtures:ro` so test-entrypoint stages faux fixture against warm image without rebuild. Passes `PI_E2E_TRUSTED_NETWORKS: "${PI_E2E_TRUSTED_NETWORKS:-}"` (optional comma-separated override for the PI_E2E_SEED trustedNetworks seed; empty => trust any peer). Passes `PI_TEST_PEERS: "${PI_TEST_PEERS:-}"` (flow-plugin e2e peer selector: both\|no-am\|legacy\|bad-registration; empty => no flow-peer wiring). See change: docker-test-harness. See change: parallelize-test-harness. See change: add-e2e-spawn-scenarios. See change: add-e2e-faux-model-roundtrip. See change: add-flow-plugin-e2e-tests. Passes `PI_E2E_OAUTH` + `PI_E2E_OAUTH_BASE` env through to container (PI_E2E_OAUTH=1 seeds resolvable github provider + bypassUrls before boot so /auth/* routes exist for oauth-redirect-base.spec.ts; requires PI_E2E_SEED=1; unset = harness unchanged). See change: config-override-oauth-redirect-base. |
+ | `compose.test.yml` | Test overlay on `compose.yml`. Sets `PI_DASHBOARD_NO_MDNS=1`, `DASHBOARD_PORT="${DASHBOARD_PORT:-18000}"`, `PI_GATEWAY_PORT="${PI_GATEWAY_PORT:-18999}"` (de-hardcoded, env-driven), `PI_GATEWAY_BIND=127.0.0.1`, `TUNNEL_ENABLED=false`. Per-worktree image tag `pi-dashboard:${TEST_IMAGE_TAG:-local}`, `restart: "unless-stopped"` (matches base compose.yml; was "no": dashboard daemon is container PID 1, so `POST /api/restart` exited the container and nothing revived the harness. unless-stopped still honours explicit `docker stop`; `test-down.sh` runs `compose down` regardless. Trade-off: crash-looping container respawns instead of staying dead; `test-up.sh` health gate still fails the run when dashboard never answers). No `cap_add` here — CAP_SYS_ADMIN layered by `compose.test.cap.yml` in overlay mode only. Overrides `pi-state` volume to tmpfs (ephemeral). Mounts `HOST_CWD` ro lower at `/mnt/test-lower`, tmpfs upper/work, fixtures source at `/fixtures-src`, `test-entrypoint.sh` bind. entrypoint `test-entrypoint.sh`. Runs `user: root` + `HOME=/home/pi` so entrypoint can `mkdir`/`mount` overlay at `/` while state lands in pi-state tmpfs. Passes `PI_E2E_SEED` env to container (`PI_E2E_SEED: "${PI_E2E_SEED:-}"`); default empty keeps harness UI-only. Bind-mounts `../qa/fixtures:/app/qa/fixtures:ro` so test-entrypoint stages faux fixture against warm image without rebuild. Passes `PI_E2E_TRUSTED_NETWORKS: "${PI_E2E_TRUSTED_NETWORKS:-}"` (optional comma-separated override for the PI_E2E_SEED trustedNetworks seed; empty => trust any peer). Passes `PI_TEST_PEERS: "${PI_TEST_PEERS:-}"` (flow-plugin e2e peer selector: both\|no-am\|legacy\|bad-registration; empty => no flow-peer wiring). See change: docker-test-harness. See change: parallelize-test-harness. See change: add-e2e-spawn-scenarios. See change: add-e2e-faux-model-roundtrip. See change: add-flow-plugin-e2e-tests. Passes `PI_E2E_OAUTH` + `PI_E2E_OAUTH_BASE` env through to container (PI_E2E_OAUTH=1 seeds resolvable github provider + bypassUrls before boot so /auth/* routes exist for oauth-redirect-base.spec.ts; requires PI_E2E_SEED=1; unset = harness unchanged). See change: config-override-oauth-redirect-base. Passes `PI_SYNTH_AGENT_TICKS: "${PI_SYNTH_AGENT_TICKS:-}"` (throttle-cadence arm: 1 => test-entrypoint stages the synthetic Agent-tick producer and SKIPS the subagents producer; requires PI_E2E_SEED=1; unset = real subagents). See change: reduce-bridge-tick-bandwidth. |
| `compose.yml` | Base compose. Service `pi-dashboard`, `init:true`, env-driven ports, named volumes `pi-state`→`/home/pi/.pi` + `zrok-state`→`/home/pi/.zrok2`, tmpfs `/tmp`, healthcheck `/api/health`, mem limit. Three commented volume perf profiles (default/performance/ephemeral). See change: docker-packaging. |
| `Dockerfile` | Multi-stage. base: `node:24-bookworm-slim` + tmux/jq/git/curl/ripgrep/fd-find/poppler-utils/build-essential/python3 + zrok (ARG `ZROK_VERSION`). app: non-root pi UID 1000, global `@earendil-works/pi-coding-agent@0.84.1` (pinned; old `@mariozechner` scope frozen). See change: update-pi-core-0-84-adopt-apis, pnpm install+build, npm link, purge build-essential/python3. EXPOSE 8000 9999. VOLUME `/home/pi/.pi` `/home/pi/.zrok2`. ENTRYPOINT `entrypoint.sh`. base also installs `gh` (GitHub CLI, ARG `GH_VERSION`) + `jj` (jujutsu, ARG `JJ_VERSION`, musl static binary, no checksum manifest); app installs `openspec` global alongside pi. base also `COPY tsconfig.base.json tsconfig.json scripts patches` before `pnpm install` (packages extend `../../tsconfig.base.json`; postinstall runs `scripts/maybe-patch-package.cjs` + `scripts/fix-pty-permissions.cjs`; patch-package replays `patches/`). zrok checksum grep uses `grep -E "[ /]${asset}$"` (checksum lines prefix asset with `./`). zrok v2 (`ZROK_VERSION=2.0.4`): tarball ships `zrok2`; extract `zrok2` + `ln -s zrok2 zrok`; smoke `zrok2 version` && `zrok version`. See change: support-zrok-v2. `COPY qa/fixtures ./qa/fixtures` so test-entrypoint stages faux extension. app also installs the flow-plugin e2e peers globally (ARG `PI_FLOWS_VERSION` `@blackbelt-technology/pi-flows`, ARG `PI_ANTHROPIC_MESSAGES_VERSION` `@blackbelt-technology/pi-anthropic-messages`); test-entrypoint.sh selectively wires them per PI_TEST_PEERS. See change: add-flow-plugin-e2e-tests. `corepack enable` pins `pnpm@11.15.1` (root `packageManager`); build `COPY package.json pnpm-lock.yaml pnpm-workspace.yaml` then `pnpm install --frozen-lockfile` + `pnpm run build` (pnpm re-resolves the platform optional-dep tree from the lockfile, so the old npm #4828 `rm -f package-lock.json` lockfile-nuke hack is gone); global tool installs (`npm install -g`) stay npm. See change: docker-packaging. See change: adopt-pnpm-for-dev-ci. See change: docker-test-harness. See change: add-e2e-faux-model-roundtrip. See change: selectable-tool-output-links. See change: default-node-24-keep-22-floor. See change: align-pi-080.6-docker. code-server install layer removed (ARG `CODE_SERVER_VERSION` gone). See change: remove-external-editor-integration. app also installs ARG `PI_DASHBOARD_SUBAGENTS_VERSION` `@blackbelt-technology/pi-dashboard-subagents` globally — the ONLY producer of `tool_execution_update` (its `extensions/agent.ts` drives `createProgressEmitter` → `progress.schedule(details)` into the tool `onProgress`). Absent it, the collapse scenarios P2/F4 have no workload and pass VACUOUSLY. See change: collapse-superseded-tool-execution-updates. |
| `entrypoint.sh` | Runs `seed-auth.js`, seeds spawnStrategy into config.json first-run-only, starts tmux server, enrolls zrok v2 headless when `ZROK_TOKEN` set + not already enrolled (`zrok2 enable "$ZROK_TOKEN" --headless`, idempotent via `~/.zrok2/environment.json`), execs pi-dashboard with env→flag mapping (`DASHBOARD_PORT`→`--port`, `PI_GATEWAY_PORT`→`--pi-port`, `TUNNEL_ENABLED=0`→`--no-tunnel`). See change: docker-packaging, support-zrok-v2. |
| `lib-ports.sh` | Sourced pure-helper lib. Exports `derive_hash(cwd)` (cksum), `derive_project(cwd)` -> `pi-dash-test-<hash>`, `is_free(port)` (bash /dev/tcp check), `find_free_in_window(start,lo,hi)` (wrap scan, cap=window size). Windows: dashboard 18000-18999, gateway 19000-19999. See change: parallelize-test-harness. |
| `README.md` | User guide. Quick-start, config table, path-identical workspace mounts, volume perf profiles, gateway access, dev mode, Electron remote mode. See change: docker-packaging. |
| `test-down.sh` | Teardown. Re-derives `COMPOSE_PROJECT_NAME` from `$PWD` via lib-ports.sh cksum. `docker compose -p <project> -f compose.yml -f compose.test.yml down -v`. Warns on malformed `.pi-test-harness.json` but continues. Removes `.pi-test-harness.json` after down. Drops tmpfs `pi-state` + overlay upper. Host pristine. See change: docker-test-harness. See change: parallelize-test-harness. |
- | `test-entrypoint.sh` | Test entrypoint wrapper. `HOST_CWD` set → mounts overlayfs (lower `/mnt/test-lower` ro, upper/work tmpfs) at identical `${HOST_CWD}`; `TEST_COPY_MODE=1` falls back to `cp -a` onto tmpfs (no CAP_SYS_ADMIN). Copies `/fixtures-src` to `/fixtures` tmpfs, git init + commit each VCS fixture via a loop (`sample-git sample-hook-ok sample-hook-fail` — the hook fixtures added for change friendlier-worktree-init). Launches base `entrypoint.sh` in background. Fail-fast smoke: curl `/api/health` 200 + one `/ws` WebSocket connect via Node 24 global WebSocket; exits non-zero on failure. Runs base `entrypoint.sh` (launches DETACHED `pi-dashboard start` daemon), then smoke check, then keeps PID 1 alive by polling `~/.pi/dashboard/server.pid` until daemon exits; SIGTERM/INT forwarded to daemon. Tolerates base launcher readiness-timeout non-zero exit (detached daemon survives); polls `/api/health` up to ~90s itself. curl probes bounded `--connect-timeout 1 --max-time 2`. Copy-mode workspace `cp` fail-fast (no error swallow). `PI_E2E_SEED=1` → before base entrypoint, seeds fake never-valid anthropic oauth credential to `~/.pi/agent/auth.json` (flips providersReady, clears LandingPage onboarding gate), seeds trustedNetworks to `~/.pi/dashboard/config.json` so the browser's non-loopback source IP clears createNetworkGuard for `/api/browse` directory listing + `/api/providers`. NOW DEFAULTS TO `0.0.0.0/0`, narrowable via `PI_E2E_TRUSTED_NETWORKS` (comma-separated): the old RFC1918-only list (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) assumed published-port traffic is always SNAT'd through a private bridge gateway, which is FALSE on a VPN'd host — behind Cloudflare WARP the observed peer was the PUBLIC 172.67.221.13, outside 172.16.0.0/12 (spans only 172.16-172.31), so every browser request 403'd (“Network not allowed” + “Server offline”) and every scenario spec died in `pinDirectory`. Trust boundary is the disposable, localhost-published, RAM-backed container, not an IP range. See change: compact-warm-replay-stream (harness fix); both no-op if files exist so base seed-auth.js + config seed skip. `PI_E2E_SEED=1` also stages faux fixture as global extension `~/.pi/agent/extensions/faux-provider/index.ts` + sibling `faux-scenarios.ts`; seeds `defaultModel=faux/faux-1` into `~/.pi/dashboard/config.json` + `~/.pi/agent/settings.json` (merge, no clobber); symlinks `/app/node_modules` into staged extension dir so fixture resolves `@earendil-works/pi-ai`; no-op when present; default OFF. See change: docker-test-harness. See change: add-e2e-spawn-scenarios. See change: add-e2e-faux-model-roundtrip. PI_E2E_SEED=1 also seeds config.json#modelProxy (enabled + one apiKey, hash=sha256 of fixed pi-proxy-e2e-oauth-filter-* key, scopes ["all"]) so /v1/* reachable by tests/e2e/model-proxy-oauth-filter.spec.ts. See change: filter-oauth-incompatible-models. PI_E2E_SEED=1 also seeds the faux role-preset (`/app/qa/fixtures/faux-roles.json`, `_comment` stripped) to `~/.pi/agent/providers.json` (all roles -> faux/faux-1). When `PI_TEST_PEERS` set (both\|no-am\|legacy\|bad-registration): registers the baked pi-flows engine into settings.json packages[] (+ bare `pi-flows` node_modules symlink under /fixtures/sample-git for the bridge tier-1 probe) so flows discover/run, and symlinks the baked anthropic-messages peer into /fixtures/sample-git/node_modules under the scoped name (both/bad-registration), the legacy `@pi/anthropic-messages` (legacy), or not at all (no-am); bad-registration also exports PI_DASHBOARD_DISABLE_PLUGIN_BRIDGE_PACKAGES_WRITE=1 so the bridge stays out of packages[]. When PI_TEST_PEERS set it ALSO: pre-trusts SESSION_CWD in ~/.pi/agent/trust.json (the .pi/flows project resources make /fixtures/sample-git trust-requiring; a headless RPC session would block on pi's "Trust project folder?" prompt -> REGISTER_TIMEOUT); bumps config.json spawnRegisterTimeoutMs=90000 (pi-flows engine jiti-compile on a cold spawn can exceed the 30s default); and warms the pi-flows jiti compile cache in the BACKGROUND (`pi --list-models &`, never delays health). See change: add-flow-plugin-e2e-tests. `PI_E2E_SEED=1` also seeds a decoration-mismatched local install at `/fixtures/local-pkg/image-fit-extension` (`package.json#name` = `@blackbelt-technology/pi-image-fit-extension`, dir basename decorated differently so the pure-string basename rule cannot match) and registers that PATH in settings.json `packages[]` → lands in `activeSources`, exercising the fs-aware name fallback that drives the card's Active/Remove button. Consumed by tests/e2e/recommended-local-name-match.spec.ts. See change: match-local-installs-by-package-name. Fixture MUST be a LOADABLE extension: `type:module` + `pi.extensions:["index.js"]` (not `main`) + an inert `index.js` default export — pi loads every `packages[]` entry at session start and an entry-less manifest is fatal. package.json + index.js written only when absent; settings registration always ensured. See change: restore-ask-user-tool-state-on-reconnect. `PI_E2E_SEED=1` also seeds two plugin-page fixture states into `~/.pi/dashboard/plugins/`: `e2e-broken` (`server` entry throws → `PluginStatus.error`) and `e2e-needs-req` (`requires.piExtensions: ["pi-e2e-absent-extension"]` → `missingRequirements`); and `e2e-dependent` (`dependsOn: ["e2e-needs-req"]` → disabling the dependency raises the cascade-confirm dialog; no monorepo plugin declares `dependsOn`, so the cascade path is otherwise unreachable at L3). All three claim `settings-section` so they earn a plugin page + nav child. Each FILE is guarded by its own `-f` check so a partial fixture heals on the next run. Idempotent (each skips when its package.json is present). Consumed by tests/e2e/plugin-settings-pages.spec.ts. See change: plugin-settings-pages. PI_E2E_SEED also stages `qa/fixtures/e2e-notify.ext.ts` → `~/.pi/agent/extensions/e2e-notify/` (the `ctx.ui.notify` L3 driver). `register_flows` now pins `/app/packages/extension` FIRST in `settings.json#packages` — tool-name precedence is FIRST-registration-wins, and pi-flows' own `ask_user` ({question,type}) otherwise shadows the dashboard's ({method,title}), failing every faux ask scenario. See change: split-notify-from-prompt-request. `PI_E2E_OAUTH=1` (opt-in; unset = harness unchanged): before base entrypoint + daemon launch, seeds `auth.providers.github` (`e2e-client-id`/`e2e-client-secret`) + `auth.secret` + `bypassUrls:["/"]` + `redirectBaseUrl` (`PI_E2E_OAUTH_BASE`, default `https://pi-e2e-a.example.com`) into config.json. MUST be before launch: `registerAuthPlugin` returns early on empty boot provider registry → no /auth/* route, no `_reloadAuth` (design D6). Spec cannot seed itself: pi-state is RAM-backed tmpfs, every container start hands server fresh empty ~/.pi and discards `PUT /api/config` writes. github = only built-in provider resolving with NO network I/O (static endpoints, no OIDC discovery) → works offline. `bypassUrls:["/"]` mandatory: Playwright-host requests arrive NON-loopback; prefix matches every URL so armed gate denies nothing on SHARED harness. Consumed by tests/e2e/oauth-redirect-base.spec.ts; set by tests/e2e/global-setup.ts (`PI_E2E_OAUTH: process.env.PI_E2E_OAUTH ?? "1"`). See change: config-override-oauth-redirect-base. `register_subagents` registers the baked `pi-dashboard-subagents` into `settings.json#packages` + a `node_modules` symlink under SESSION_CWD. Runs UNCONDITIONALLY inside the PI_TEST_PEERS block (not per-arm): collapse scenarios need the tick producer in every peer shape, and it registers no `ask_user`, so it cannot disturb the first-registration-wins precedence `register_flows` guards (bridge stays pinned FIRST). Adds one more TS extension to the per-session jiti compile set — on a COLD cache (e.g. after `docker volume prune`) the first turn can exceed a spec's 30 s budget; the existing background `pi --list-models` warm covers it, so let the warm finish before driving the UI. See change: collapse-superseded-tool-execution-updates. |
+ | `test-entrypoint.sh` | Test entrypoint wrapper. `HOST_CWD` set → mounts overlayfs (lower `/mnt/test-lower` ro, upper/work tmpfs) at identical `${HOST_CWD}`; `TEST_COPY_MODE=1` falls back to `cp -a` onto tmpfs (no CAP_SYS_ADMIN). Copies `/fixtures-src` to `/fixtures` tmpfs, git init + commit each VCS fixture via a loop (`sample-git sample-hook-ok sample-hook-fail` — the hook fixtures added for change friendlier-worktree-init). Launches base `entrypoint.sh` in background. Fail-fast smoke: curl `/api/health` 200 + one `/ws` WebSocket connect via Node 24 global WebSocket; exits non-zero on failure. Runs base `entrypoint.sh` (launches DETACHED `pi-dashboard start` daemon), then smoke check, then keeps PID 1 alive by polling `~/.pi/dashboard/server.pid` until daemon exits; SIGTERM/INT forwarded to daemon. Tolerates base launcher readiness-timeout non-zero exit (detached daemon survives); polls `/api/health` up to ~90s itself. curl probes bounded `--connect-timeout 1 --max-time 2`. Copy-mode workspace `cp` fail-fast (no error swallow). `PI_E2E_SEED=1` → before base entrypoint, seeds fake never-valid anthropic oauth credential to `~/.pi/agent/auth.json` (flips providersReady, clears LandingPage onboarding gate), seeds trustedNetworks to `~/.pi/dashboard/config.json` so the browser's non-loopback source IP clears createNetworkGuard for `/api/browse` directory listing + `/api/providers`. NOW DEFAULTS TO `0.0.0.0/0`, narrowable via `PI_E2E_TRUSTED_NETWORKS` (comma-separated): the old RFC1918-only list (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) assumed published-port traffic is always SNAT'd through a private bridge gateway, which is FALSE on a VPN'd host — behind Cloudflare WARP the observed peer was the PUBLIC 172.67.221.13, outside 172.16.0.0/12 (spans only 172.16-172.31), so every browser request 403'd (“Network not allowed” + “Server offline”) and every scenario spec died in `pinDirectory`. Trust boundary is the disposable, localhost-published, RAM-backed container, not an IP range. See change: compact-warm-replay-stream (harness fix); both no-op if files exist so base seed-auth.js + config seed skip. `PI_E2E_SEED=1` also stages faux fixture as global extension `~/.pi/agent/extensions/faux-provider/index.ts` + sibling `faux-scenarios.ts`; seeds `defaultModel=faux/faux-1` into `~/.pi/dashboard/config.json` + `~/.pi/agent/settings.json` (merge, no clobber); symlinks `/app/node_modules` into staged extension dir so fixture resolves `@earendil-works/pi-ai`; no-op when present; default OFF. See change: docker-test-harness. See change: add-e2e-spawn-scenarios. See change: add-e2e-faux-model-roundtrip. PI_E2E_SEED=1 also seeds config.json#modelProxy (enabled + one apiKey, hash=sha256 of fixed pi-proxy-e2e-oauth-filter-* key, scopes ["all"]) so /v1/* reachable by tests/e2e/model-proxy-oauth-filter.spec.ts. See change: filter-oauth-incompatible-models. PI_E2E_SEED=1 also seeds the faux role-preset (`/app/qa/fixtures/faux-roles.json`, `_comment` stripped) to `~/.pi/agent/providers.json` (all roles -> faux/faux-1). When `PI_TEST_PEERS` set (both\|no-am\|legacy\|bad-registration): registers the baked pi-flows engine into settings.json packages[] (+ bare `pi-flows` node_modules symlink under /fixtures/sample-git for the bridge tier-1 probe) so flows discover/run, and symlinks the baked anthropic-messages peer into /fixtures/sample-git/node_modules under the scoped name (both/bad-registration), the legacy `@pi/anthropic-messages` (legacy), or not at all (no-am); bad-registration also exports PI_DASHBOARD_DISABLE_PLUGIN_BRIDGE_PACKAGES_WRITE=1 so the bridge stays out of packages[]. When PI_TEST_PEERS set it ALSO: pre-trusts SESSION_CWD in ~/.pi/agent/trust.json (the .pi/flows project resources make /fixtures/sample-git trust-requiring; a headless RPC session would block on pi's "Trust project folder?" prompt -> REGISTER_TIMEOUT); bumps config.json spawnRegisterTimeoutMs=90000 (pi-flows engine jiti-compile on a cold spawn can exceed the 30s default); and warms the pi-flows jiti compile cache in the BACKGROUND (`pi --list-models &`, never delays health). See change: add-flow-plugin-e2e-tests. `PI_E2E_SEED=1` also seeds a decoration-mismatched local install at `/fixtures/local-pkg/image-fit-extension` (`package.json#name` = `@blackbelt-technology/pi-image-fit-extension`, dir basename decorated differently so the pure-string basename rule cannot match) and registers that PATH in settings.json `packages[]` → lands in `activeSources`, exercising the fs-aware name fallback that drives the card's Active/Remove button. Consumed by tests/e2e/recommended-local-name-match.spec.ts. See change: match-local-installs-by-package-name. Fixture MUST be a LOADABLE extension: `type:module` + `pi.extensions:["index.js"]` (not `main`) + an inert `index.js` default export — pi loads every `packages[]` entry at session start and an entry-less manifest is fatal. package.json + index.js written only when absent; settings registration always ensured. See change: restore-ask-user-tool-state-on-reconnect. `PI_E2E_SEED=1` also seeds two plugin-page fixture states into `~/.pi/dashboard/plugins/`: `e2e-broken` (`server` entry throws → `PluginStatus.error`) and `e2e-needs-req` (`requires.piExtensions: ["pi-e2e-absent-extension"]` → `missingRequirements`); and `e2e-dependent` (`dependsOn: ["e2e-needs-req"]` → disabling the dependency raises the cascade-confirm dialog; no monorepo plugin declares `dependsOn`, so the cascade path is otherwise unreachable at L3). All three claim `settings-section` so they earn a plugin page + nav child. Each FILE is guarded by its own `-f` check so a partial fixture heals on the next run. Idempotent (each skips when its package.json is present). Consumed by tests/e2e/plugin-settings-pages.spec.ts. See change: plugin-settings-pages. PI_E2E_SEED also stages `qa/fixtures/e2e-notify.ext.ts` → `~/.pi/agent/extensions/e2e-notify/` (the `ctx.ui.notify` L3 driver). `register_flows` now pins `/app/packages/extension` FIRST in `settings.json#packages` — tool-name precedence is FIRST-registration-wins, and pi-flows' own `ask_user` ({question,type}) otherwise shadows the dashboard's ({method,title}), failing every faux ask scenario. See change: split-notify-from-prompt-request. `PI_E2E_OAUTH=1` (opt-in; unset = harness unchanged): before base entrypoint + daemon launch, seeds `auth.providers.github` (`e2e-client-id`/`e2e-client-secret`) + `auth.secret` + `bypassUrls:["/"]` + `redirectBaseUrl` (`PI_E2E_OAUTH_BASE`, default `https://pi-e2e-a.example.com`) into config.json. MUST be before launch: `registerAuthPlugin` returns early on empty boot provider registry → no /auth/* route, no `_reloadAuth` (design D6). Spec cannot seed itself: pi-state is RAM-backed tmpfs, every container start hands server fresh empty ~/.pi and discards `PUT /api/config` writes. github = only built-in provider resolving with NO network I/O (static endpoints, no OIDC discovery) → works offline. `bypassUrls:["/"]` mandatory: Playwright-host requests arrive NON-loopback; prefix matches every URL so armed gate denies nothing on SHARED harness. Consumed by tests/e2e/oauth-redirect-base.spec.ts; set by tests/e2e/global-setup.ts (`PI_E2E_OAUTH: process.env.PI_E2E_OAUTH ?? "1"`). See change: config-override-oauth-redirect-base. `register_subagents` registers the baked `pi-dashboard-subagents` into `settings.json#packages` + a `node_modules` symlink under SESSION_CWD. Runs UNCONDITIONALLY inside the PI_TEST_PEERS block (not per-arm): collapse scenarios need the tick producer in every peer shape, and it registers no `ask_user`, so it cannot disturb the first-registration-wins precedence `register_flows` guards (bridge stays pinned FIRST). Adds one more TS extension to the per-session jiti compile set — on a COLD cache (e.g. after `docker volume prune`) the first turn can exceed a spec's 30 s budget; the existing background `pi --list-models` warm covers it, so let the warm finish before driving the UI. See change: collapse-superseded-tool-execution-updates. `PI_SYNTH_AGENT_TICKS=1` stages `qa/fixtures/faux-agent-ticks.ext.ts` → `~/.pi/agent/extensions/faux-agent-ticks/` (synthetic `Agent`-tick producer for the throttle cadence L3 rows) and SKIPS `register_subagents` — the synthetic `Agent` tool owns the tool name (first-registration-wins) and the two never coexist. See change: reduce-bridge-tick-bandwidth. |
| `test-up.sh` | Spin-up. Sources lib-ports.sh. Exports `HOST_CWD=$PWD`. Derives stable port pair + `COMPOSE_PROJECT_NAME` from HOST_CWD hash. Honours `DASHBOARD_PORT`+`PI_GATEWAY_PORT` override as pair (exactly one set = exit 1). Idempotent re-up reuses ports when project containers run. Exports `TEST_IMAGE_TAG=$COMPOSE_PROJECT_NAME` (per-worktree baked image tag; falls back `:local`). Writes `${HOST_CWD}/.pi-test-harness.json`. Sets `PI_GATEWAY_BIND=127.0.0.1`, `TUNNEL_ENABLED=false`. Runs `docker compose -p "$COMPOSE_PROJECT_NAME" -f compose.yml -f compose.test.yml up`, wrapped in a bounded bind-collision retry (MAX_BIND_RETRIES=5, re-derives port pair on `port is already allocated`; skipped when ports pinned verbatim). Layers `compose.test.cap.yml` only in overlay mode; omits when `TEST_COPY_MODE=1`. Banner shows chosen URL + project. Exports `PI_TEST_PEERS="${PI_TEST_PEERS:-}"` (flow-plugin e2e peer selector) passed through to the container. See change: docker-test-harness. See change: parallelize-test-harness. See change: add-flow-plugin-e2e-tests. |
| `TESTING.md` | Runbook for disposable isolated test harness. Quick start. Isolation-guarantee table: home-lock, mDNS, ports, `~/.pi` state. Path-parity explanation. Overlay-vs-copy-mode tradeoff. Fixtures-vs-mount usage. UI-only-vs-e2e key seeding. agent-browser manual-QA entry. See change: docker-test-harness. |
| `up.sh` | Workspace launcher. Parses `PI_WORKSPACES` path-separator list, one path-identical `-v dir:dir` RW bind per entry, exports `PI_DASHBOARD_PIN_DIRS`=same list, `docker compose run --service-ports`. See change: docker-packaging. |