diff --git a/.gitea/workflows/docker-publish.yml b/.gitea/workflows/docker-publish.yml index 8d3722a..ba06b0a 100644 --- a/.gitea/workflows/docker-publish.yml +++ b/.gitea/workflows/docker-publish.yml @@ -397,7 +397,7 @@ jobs: echo "fork_ref=${FORK_REF}" >> "$GITHUB_OUTPUT" # pi-observational-memory → the PINNED SHA in Dockerfile.variant. - # Until v1.11.1 this resolved `master` and passed it as --build-arg, + # Until v1.11.2 this resolved `master` and passed it as --build-arg, # which OVERRIDES the ARG default: the pin bound only local builds, # and CI baked it only while master == pin (v1.9.5, v1.10.2, v1.11.0). # The pin is a master SHA, not a tag, on purpose: obsmem tags rarely diff --git a/CHANGELOG.md b/CHANGELOG.md index b00a44b..f3757ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,15 +11,34 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`). --- -## v1.11.1 — 2026-10-14 +## v1.11.2 — 2026-10-14 -Patch release. Headlines: **pi 1.2.0**, **`pi -p` returns in seconds** on a +**v1.11.1 was tagged and never published.** Run 724 failed one smoke assertion +in each variant (`smoke` 102 passed / **1 failed**, `smoke-studio` 105 / **1**): +*esbuild works at every install site* reported "no esbuild install found at +all". The image was right and the assertion was wrong. esbuild was never a +direct pi dependency. It came through `@earendil-works/chord` (pi 1.1.0 → chord +→ esbuild 0.28.2, at the pi and pi-fork sites), and pi 1.2.0 dropped chord, so +a correct 1.2.0 image has no esbuild. pi 1.2.0's tree has no esbuild import; +1.1.0's chord bundle had one. This is the mistake the clipboard assertion made +at v1.10.0. Every publish job skipped, so no `v1.11.1`, `latest` or Hub +description was written. The one artefact pushed is the base `base-6f3b20266de1`. +It is content-addressed and this release reuses it, which is why +`Dockerfile.base`'s `BASE_REBUILD_DATE` comment still names v1.11.1: editing it +would change the hash and rebuild an identical base. **Everything below ships +here, with the assertion fixed** (*Fixed*, first entry). The tag moves rather +than being re-pointed, as at v1.10.2. One component moved between the two tags: +pi-studio published **v0.10.0 and v0.10.1** the same morning, CI builds the +newest pi-studio tag, and this release adopts 0.10.1 after testing it (*Changed* +below) rather than pinning 0.9.63. + +Patch release. Headlines: **pi 1.2.0**, **pi-studio 0.10.1** (two editors, a +Prompt and a Document, by default), **`pi -p` returns in seconds** on a devbox instead of idling for five minutes (mempalace-toolkit), **MCP servers use pi's built-in support** (pi-extensions' `mcp-loader.ts` is retired), and **CI now builds the pinned pi-observational-memory commit** instead of whatever its `master` is that day. Unchanged upstream, re-checked 2026-10-14: -pi-observational-memory `022da5e`, pi-atelier `v0.15.0`, pi-studio `v0.9.63`, -pi-fork `e69725c`, pi-toolkit `3760d90`, mempalace `3.10.0`, Node 24 (v24.21.0 +pi-observational-memory `022da5e`, pi-atelier `v0.15.0`, pi-fork `e69725c`, pi-toolkit `3760d90`, mempalace `3.10.0`, Node 24 (v24.21.0 LTS). The skillset moved (`96b840e` → `1e02b44`), but the vendored mempalace `SKILL.md` is byte-identical, so the snapshot is not refreshed. The base image is rebuilt (the rootfs skill floor and the mempalace-toolkit ref both changed), so @@ -27,14 +46,33 @@ the base tools pinned to `latest` re-resolve too. ### Components that move with this release -| Component | Baked in v1.11.0 | v1.11.1 | Why | +| Component | Baked in v1.11.0 | v1.11.2 | Why | |---|---|---|---| | **pi** | `1.1.0` | **`1.2.0`** | audited, see *Changed* below | +| **pi-studio** (studio variant) | `v0.9.63` (`fae3ad3`) | **`v0.10.1`** (`76db01f`) | tagged 2026-10-14, tested under pi 1.2.0, see *Changed* below | | pi-extensions | `a88fa99` | **`750b2a8`** (or later: CI resolves `main` at build time) | `mcp-loader.ts` retired; `install.sh` prunes stale links (below) | | mempalace-toolkit | `975ab92` | **`d5a03f4`** (or later) | `pi -p` returns in seconds: `2f0edf5` clears the feed's leftover 5-min timer, `f590a3a` skips the feed in print/JSON mode, and `d5a03f4` skips the wake-up and mailbox poll there too (below). Also `a30af61`: the bridge's `RemoteMcpClient` is now the only copy, and `scripts/check-mcp-client-sync.sh` is removed (comment/README only) | ### Fixed +- **The smoke test's esbuild assertion is now "present iff something declares + it"**, like clipboard's. Because esbuild arrives transitively, "declared" is + not read from pi's `package.json`. It is derived from every installed package + root and every `/opt` repo root, counting dependencies, optional dependencies + and peer dependencies. Anchoring on package roots means a test fixture's + `package.json` cannot count. Both directions fail: declared but missing (the + prune removed function), and installed with nothing declaring it (residue). + The new body was run before tagging. On published v1.11.0-studio it reports + "declared (2 package(s)) and compiles at 2 site(s)". With every esbuild dir + removed it fails, naming both chord `package.json` files. With + `@esbuild/` pruned it fails with "esbuild broken at …". On the new + images it reports that nothing declares esbuild. It also fails a fake esbuild + dir that nothing declares, and a package root that declares esbuild with no + install, but ignores a test fixture's `package.json`. The whole + `smoke-test.sh` then ran against arm64 candidates, built locally with run + 724's resolved build args on the same base. The old script reproduced CI's + 102/1 on core. The new one passed core 103/0 and studio 106/0, with pi-studio + 0.10.1 and with 0.9.63. - **The pi-observational-memory pin now binds CI builds** (v1.11.0's known issue). `resolve-versions` resolved the repo's `master` and passed it as `--build-arg PI_OBSMEM_REF`, which overrides the Dockerfile's pinned default, @@ -71,7 +109,7 @@ the base tools pinned to `latest` re-resolve too. predates v1.11.0. `pi-task` and `fork` children run `--no-extensions` and were never affected. `scripts/test-feed-exit.sh` in mempalace-toolkit pins both changes, and each change's pre-fix source fails it. On images before - v1.11.1, scripts that call `pi -p` should keep their `timeout` or set + v1.11.2, scripts that call `pi -p` should keep their `timeout` or set `MEMPALACE_FEED=0`. - **`pi -p` no longer gets the palace wake-up.** In print and JSON mode the mempalace bridge now skips the wake-up injection and the mid-session mailbox @@ -114,11 +152,42 @@ the base tools pinned to `latest` re-resolve too. reply was cut off at the model's output limit. That is text mode only; `pi-task` and `fork` run `--mode json`. The first start after the upgrade shows 1.2.0's What's New once. Read from the code, not reproduced: pi-studio - marks pi busy on `agent_start`, which 1.2.0 fires after the session already - counts as busy. A Studio request sent in that gap (the `before_agent_start` - handlers) gets pi's "Agent is already processing" error instead of Studio's - own "pi is busy". Before 1.2.0 the same request started a second run that - failed, so retrying is the answer either way. + (0.9.63 and 0.10.1 alike) marks pi busy on `agent_start`, which 1.2.0 fires + after the session already counts as busy. A request that reaches pi in that + gap (the `before_agent_start` handlers) gets pi's "Agent is already + processing" error instead of Studio's own "pi is busy". Before 1.2.0 the same + request started a second run that failed, so retrying is the answer either + way. From the browser it is hard to reach: three **Run Prompt** clicks 30 ms + and 150 ms apart produced exactly one run in 0.10.1. +- **pi-studio `v0.9.63` → `v0.10.1`** (studio variant). 0.10.0 is the largest + Studio change in a while. By default there are two editors: a **Prompt** for + what you send to pi and a **Document** for the file you are working on. You + can switch with `/studio --single` / `--dual` or from the footer. Unsaved text + survives reloads, and `/studio` reopens your last workspace (`--blank`, + `--last`); its state lives under `~/.pi/agent/pi-studio`, on the preserved + volume. There is a new **Files** pane, a two-row toolbar, and **Add to + Prompt**. 0.10.1 fixes the Run button flickering while a run starts. Upstream + typechecks against pi 1.2.0; its peers stay `*`. + - **What pi-devbox relies on is unchanged:** the package manifest + (`./index.ts`, `./themes`), the committed `client/studio-client.js`, the 11 + `studio_*` tools, the 6 commands, and the `--no-browser`, `--port` and + `--listen-all` flags that `studio-expose` and the README use. The set of + environment variables read is identical. + - **Tested under pi 1.2.0 before tagging**, in a copy of tor-ms22's real + `~/.pi/agent` with 0.10.1 swapped in: + - pi starts with no extension issues, and `/studio --no-browser --port + 18765` prints its token URL. + - In headless Chromium the new UI renders with no console errors. + - A prompt typed into the Prompt and sent with **Run Prompt** came back + through ERDC in both Studio and the terminal. + - The Files pane lists the workspace, and **Open in Document** loads a file. + - Quitting pi frees the port. + - **In the image:** the 0.10.1 studio candidate passes the full smoke test, + 106/0. It is 11 MB smaller than with 0.9.63, because the npm package shrank. + Inside it, pi's TUI starts with pi-studio among the extensions and no + extension issues. + - **To go back**, if 0.10 does not suit: v1.11.0-studio still has 0.9.63. + Inside a container, `/studio --single` restores the one-editor layout. - **MCP servers now use pi's built-in MCP support; pi-extensions' `mcp-loader.ts` is retired.** pi has shipped MCP as a built-in extension (`builtin:mcp`) since 0.99.0: servers go in `~/.pi/agent/mcp.json` or are diff --git a/Dockerfile.variant b/Dockerfile.variant index 379b38e..5280b5c 100644 --- a/Dockerfile.variant +++ b/Dockerfile.variant @@ -261,7 +261,7 @@ ARG USER_NAME=developer # obsmem 022da5e registers /om:status and /om:view (control: none); and all 11 # pi-extensions a88fa99 extensions load with no error (control: no /aws-sso). # -# v1.11.1: 1.1.0 -> 1.2.0 (npm latest, 2026-10-12). MEASURED 2026-10-14 on +# v1.11.2: 1.1.0 -> 1.2.0 (npm latest, 2026-10-12). MEASURED 2026-10-14 on # tor-ms22 from an isolated npm prefix (nothing in /usr or ~/.pi touched): # 1. FOUR `### Breaking Changes` in 1.2.0, none of which reaches us: # - before_agent_start also fires for sendMessage(triggerTurn) runs and is @@ -318,8 +318,8 @@ ARG PI_OBSMEM_REPO=https://github.com/elpapi42/pi-observational-memory.git # dist/index.ts instead of 32 TS sources. The bundle is committed, so # `npm install --omit=dev` below needs no build step. src/agents/ is # UNCHANGED between the two SHAs, so the finishTurn migration is intact. -# v1.11.1: unchanged (master is still 022da5e, 6 commits past the 3.1.4 tag). -# CI NOW BUILDS THIS PIN. Until v1.11.1 docker-publish.yml's resolve-versions +# v1.11.2: unchanged (master is still 022da5e, 6 commits past the 3.1.4 tag). +# CI NOW BUILDS THIS PIN. Until v1.11.2 docker-publish.yml's resolve-versions # resolved obsmem from `master` and passed it as --build-arg, which overrides # this default (verified two-sided on buildx 2026-10-12), so CI got the pin only # while master == pin. resolve-versions now reads this line, refuses anything @@ -414,7 +414,7 @@ ARG PI_ATELIER_REPO=https://github.com/michaelmjhhhh/pi-atelier.git # panels and simplifies the Activity rows. The startup preference is still # `showSidebarOnStartup` (default true, src/config.ts:38). Painted in both # renderers on pi 1.1.0 before tagging (see the PI_VERSION note). -# v1.11.1: unchanged (still the newest tag); painted in both renderers on +# v1.11.2: unchanged (still the newest tag); painted in both renderers on # pi 1.2.0 before tagging. ARG PI_ATELIER_REF=v0.15.0 # Human-readable tag PI_ATELIER_REF was resolved from; recorded as a label. diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 9aa15b4..c5641c2 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -30,7 +30,8 @@ # client bundle present + registered via `pi install` # - no foreign npm-11 platform packages (@esbuild, clipboard) beyond the host # - no build-time npm cache (/root/.npm) shipped in the image -# - esbuild compiles everywhere; clipboard present iff pi declares it +# - esbuild present iff something declares it, and compiles everywhere; +# clipboard present iff pi declares it # - image size within threshold set -euo pipefail @@ -985,8 +986,40 @@ run "no build-time npm cache shipped (/root/.npm)" \ # runbook as "if this fails, revert the release", and it duly failed for the # wrong reason on the first machine that ran it. A check must fail only for the # thing it is checking. -run "esbuild works at every install site (prune removed weight, not function)" \ - 'sites=$(find /usr/lib/node_modules /opt -type d -path "*/node_modules/esbuild" -prune 2>/dev/null); if [ -z "$sites" ]; then echo "no esbuild install found at all" >&2; exit 1; fi; for d in $sites; do node -e "require(\"$d\").transformSync(\"const x:number=1\",{loader:\"ts\"})" || { echo "esbuild broken at $d" >&2; exit 1; }; done; echo ok' +# +# PRESENT IFF DECLARED, like clipboard below. esbuild was never a direct pi +# dependency: it arrived through @earendil-works/chord (pi 1.1.0 > chord > +# esbuild 0.28.2, at both the pi and pi-fork sites). pi 1.2.0 dropped chord, so +# a correct 1.2.0 image has NO esbuild, and the old "must exist" form failed the +# v1.11.1 tag build (102/103 core, 105/106 studio, nothing published) against a +# good image — the same mistake the clipboard check made at v1.10.0. Because it +# is transitive, "declared" cannot be read from pi's package.json: it is +# derived from every INSTALLED package root (node_modules//package.json, +# node_modules/@scope//package.json) and every /opt repo root, counting +# dependencies, optionalDependencies and peerDependencies (devDependencies are +# not installed: --omit=dev). Anchoring on package roots keeps a test fixture's +# package.json from counting. Both directions fail: declared but missing (the +# prune removed function), and installed with nothing declaring it (residue). +run "esbuild: present iff something declares it, and works at every install site (prune removed weight, not function)" \ + 'declarers=$(find /usr/lib/node_modules /opt -regextype posix-extended -type f \( -regex ".*/node_modules/(@[^/]+/)?[^/]+/package\.json" -o -regex "/opt/[^/]+/package\.json" \) 2>/dev/null | node -e " + const fs = require(\"fs\"); const hit = []; + for (const f of fs.readFileSync(0, \"utf8\").split(String.fromCharCode(10)).filter(Boolean)) { + let j; try { j = JSON.parse(fs.readFileSync(f, \"utf8\")); } catch (e) { continue; } + if (!j || j.name === \"esbuild\") continue; + for (const k of [\"dependencies\", \"optionalDependencies\", \"peerDependencies\"]) { + if (j[k] && Object.prototype.hasOwnProperty.call(j[k], \"esbuild\")) { hit.push(f); break; } + } + } + console.log(hit.join(\" \"));") || { echo "declarer scan itself failed" >&2; exit 1; }; + sites=$(find /usr/lib/node_modules /opt -type d -path "*/node_modules/esbuild" -prune 2>/dev/null); + if [ -n "$declarers" ]; then + [ -n "$sites" ] || { echo "declared by $declarers but NO install found - prune removed function" >&2; exit 1; }; + for d in $sites; do node -e "require(\"$d\").transformSync(\"const x:number=1\",{loader:\"ts\"})" || { echo "esbuild broken at $d" >&2; exit 1; }; done; + echo "ok: declared ($(echo $declarers | wc -w) package(s)) and compiles at $(echo $sites | wc -w) site(s)"; + else + [ -z "$sites" ] || { echo "nothing declares esbuild yet installs remain: $sites" >&2; exit 1; }; + echo "ok: nothing declares esbuild (pi >= 1.2.0 dropped chord); no install expected"; + fi' # Clipboard is the family pruned second. Upstream pi 0.86.0 (#9163) REPLACED the # external `@mariozechner/clipboard` dependency with bundled macOS/Windows/X11