v1.7.0: bundle pi-atelier at a pinned tag; pin pi to an audited 0.84.1
Publish Docker Image / resolve-versions (push) Successful in 9s
Lint / actionlint (push) Successful in 15s
Lint / hadolint (push) Successful in 13s
Publish Docker Image / base-decide (push) Successful in 8s
Publish Docker Image / build-base (push) Successful in 41m22s
Publish Docker Image / smoke-studio (push) Successful in 5m16s
Publish Docker Image / smoke (push) Successful in 7m31s
Publish Docker Image / build-variant-studio (push) Successful in 18m31s
Publish Docker Image / build-variant (push) Successful in 27m18s
Publish Docker Image / promote-base-latest (push) Successful in 11s
Publish Docker Image / update-description (push) Successful in 12s
Publish Docker Image / resolve-versions (push) Successful in 9s
Lint / actionlint (push) Successful in 15s
Lint / hadolint (push) Successful in 13s
Publish Docker Image / base-decide (push) Successful in 8s
Publish Docker Image / build-base (push) Successful in 41m22s
Publish Docker Image / smoke-studio (push) Successful in 5m16s
Publish Docker Image / smoke (push) Successful in 7m31s
Publish Docker Image / build-variant-studio (push) Successful in 18m31s
Publish Docker Image / build-variant (push) Successful in 27m18s
Publish Docker Image / promote-base-latest (push) Successful in 11s
Publish Docker Image / update-description (push) Successful in 12s
Two changes that belong together, because the first is what makes the second dangerous to get wrong. pi-atelier (TUI sidebar + status rail) is now vendored to /opt/pi-atelier at PI_ATELIER_REF=v0.8.0 and registered by entrypoint-user.sh — the pi-fork / pi-observational-memory / pi-studio pattern, deliberately NOT `pi install npm:pi-atelier`, which writes into ~/.pi/npm-global on the config volume where it shadows the image and pins nothing. Unlike its siblings it gets no `npm install`: atelier declares zero runtime deps (peerDeps only, satisfied by the baked pi) and has no build step, so pi loads its TypeScript straight from the checkout via package.json `pi.extensions`. pi is no longer resolved to npm `latest` at build time. The pin lives in Dockerfile.variant and CI reads it from there, so a local `docker build` and a CI release ship the same versions by construction. The pin is a CHECKPOINT, NOT A FREEZE: bumping stays a one-line change; what stops is *unreviewed* adoption of whatever shipped that morning, in the same build that then gets tagged and published. CI fails when a pin is not concrete or not actually published on npm, and warns — never adopts — when npm latest moves ahead, naming what to re-check. Why this pairing needed care: pi-atelier 0.6.0/0.7.0 wrap pi's PRIVATE TUI renderer, and under pi 0.84 that wrapper recurses — pi hangs at startup burning CPU with no error. Upstream fixed the recursion in 0.7.1 and restored the non-overlapping split in 0.7.2; 0.8.0 is additive on top. atelier's own peerDependencies still say >=0.80.7, which does not express that floor, so nothing in npm metadata could have warned us. The floor is therefore encoded as an executable rule — pi >= 0.84 => pi-atelier >= 0.7.1 — asserted in both smoke-test.sh (build time) and recreate-sanity-check.sh (after a real recreate), verified against a 4x4 version matrix. Existing volumes needed migration, not just vendoring: a hand-installed `npm:pi-atelier` entry is counted as already-registered by the entrypoint guard, so every existing volume would have kept its unpinned npm copy — and a 0.6.x copy next to pi 0.84 is exactly the startup hang. The entrypoint now drops that one exact string (settings.json.bak.atelier.<ts> backup, distinct prefix so it cannot clobber the template merge's backup in the same second) and lets the pinned /opt copy register. Tested against a real settings.json: only that entry removed, other packages and all keys intact, idempotent, and unparseable JSON leaves the file untouched. DEVBOX_ATELIER=0 opts out entirely — in the entrypoint rather than via `pi uninstall`, because this component's failure mode is "pi will not start", which cannot be repaired from inside pi. 0.84.1 was audited for this release, not merely adopted: theme/TUI changes are additive, the session format is unchanged (CURRENT_SESSION_VERSION = 3 in both 0.83.0 and 0.84.1 with an identical migrateV1ToV2/migrateV2ToV3 ladder, so existing transcripts are neither migrated nor at risk and pi-session-repair stays valid), and the Node engine floor is unmoved at >=22.19.0. CI resolves the atelier tag to its PEELED commit SHA — atelier uses annotated tags, so the unpeeled ref is a tag object, not a commit; pi-studio's lightweight tags never exposed that distinction. Also: docs for overriding the read-only ~/.ssh/config from the container — container-only keys in ~/.ssh-local, hardened authorized_keys, the fact that `from=` must allow the HOST's addresses because container egress is NAT'd through it, and the macOS-only-keyword trap (`UseKeychain` is fatal to Linux OpenSSH and takes out dssh/pi --ssh while the host keeps working). Corrects two claims in "Naming LAN peers": ssh-lan.conf is not ProxyJump-only, and first-time creation does need one restart because the Include is emitted only when the file already exists at start.
This commit is contained in:
+142
-3
@@ -11,13 +11,101 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
||||
|
||||
---
|
||||
|
||||
## Unreleased
|
||||
## v1.7.0 — 2026-08-07
|
||||
|
||||
Staged, not yet tagged — more changes are queued for the next release before CI
|
||||
is kicked off.
|
||||
Minor release. Headline: **pi-atelier is now part of the image** — the TUI
|
||||
sidebar/status rail every container previously had to hand-install — and **pi is
|
||||
pinned to an audited version instead of tracking npm `latest`**.
|
||||
|
||||
*Why minor and not patch:* the policy above reserves patch for "pi version bumps,
|
||||
smaller fixes" and minor for "new variants, significant base additions". Bundling
|
||||
a new companion package into every image is the same shape as v1.1.0, which went
|
||||
minor for bundling pi-studio; v1.4.0 likewise went minor for adding typst. This
|
||||
release also adds a new build-arg pair, a new opt-out env var, and a settings
|
||||
migration, so patch would understate it.
|
||||
|
||||
### Added
|
||||
|
||||
- **pi-atelier vendored at `/opt/pi-atelier`, pinned to `v0.8.0`** — the TUI
|
||||
sidebar (ordered panels, split-pane, themes) is now part of the image instead
|
||||
of something each user hand-installs. Vendored + registered at container start
|
||||
by `entrypoint-user.sh`, the same pattern as pi-fork/pi-observational-memory/
|
||||
pi-studio, and deliberately **not** `pi install npm:pi-atelier`: an npm
|
||||
install writes into `~/.pi/npm-global` on the config volume, which shadows the
|
||||
image and pins nothing — the footgun that once hid a missing `fork` tool for
|
||||
six weeks. Unlike its siblings it gets **no `npm install`**: pi-atelier
|
||||
declares zero runtime dependencies (only peerDeps, satisfied by the baked pi)
|
||||
and has no build step, so pi loads its TypeScript straight from the checkout
|
||||
(`pi.extensions` → `extensions/index.ts`).
|
||||
- **A version FLOOR, encoded as an executable test.** pi-atelier 0.6.0/0.7.0
|
||||
wrap pi's private TUI renderer in a way that recurses under pi 0.84: pi hangs
|
||||
at startup with sustained CPU and no error message. Upstream fixed the
|
||||
recursion in 0.7.1 and restored the non-overlapping split in 0.7.2
|
||||
("avoiding the recursive render path that caused startup hangs and sustained
|
||||
CPU usage"); 0.8.0 is additive on top of that. atelier's own
|
||||
`peerDependencies` still say `>=0.80.7`, which does **not** express the floor,
|
||||
so nothing in npm metadata could have warned us. `smoke-test.sh` and
|
||||
`recreate-sanity-check.sh` now assert the pairing rule **pi ≥ 0.84 ⇒
|
||||
pi-atelier ≥ 0.7.1** — verified against a 4×4 version matrix — so a bad
|
||||
combination fails the build instead of publishing an image whose TUI never
|
||||
starts. CI resolves the pinned tag to its **peeled commit SHA**; atelier uses
|
||||
annotated tags, so the unpeeled ref is a tag object, not a commit (pi-studio's
|
||||
lightweight tags never exposed that distinction).
|
||||
- **`DEVBOX_ATELIER=0`** opts out: the entrypoint removes pi-atelier from pi's
|
||||
`packages[]` instead of registering it. The switch lives in the entrypoint
|
||||
rather than being "just run `pi uninstall`" because this component's failure
|
||||
mode is *pi will not start*, which cannot be repaired from inside pi.
|
||||
- **Migration for hand-installed copies.** A pre-existing `npm:pi-atelier` entry
|
||||
is dropped from `packages[]` (with a `settings.json.bak.atelier.<ts>` backup)
|
||||
so the pinned `/opt` copy takes over. This is not cosmetic: the registration
|
||||
guard counts `npm:<name>` as already-registered, so without this step every
|
||||
existing volume would have kept its unpinned npm copy — and a 0.6.x copy
|
||||
alongside pi 0.84 is exactly the startup hang above. Only that one exact
|
||||
string is removed; jq-parse failures or a missing file leave settings
|
||||
untouched, and the backup prefix is distinct from the template merge's so two
|
||||
rewrites in the same second cannot overwrite each other's backup.
|
||||
|
||||
### Changed
|
||||
|
||||
- **pi-toolkit's `pi-atelier.json` modernised to atelier's current schema**
|
||||
(pi-toolkit `0e1369e`, cross-repo — it reaches the image through the pinned
|
||||
`PI_TOOLKIT_REF` clone). The seeded config had been written against the pre-0.7
|
||||
vocabulary: `segments` → `segmentLayout` with explicit per-segment visibility,
|
||||
`ornament: "none"` → `{"id":"brand","visible":false}`, `showExtensionStatuses`
|
||||
→ `{"id":"statuses","visible":true}`, plus the sidebar toggles that did not
|
||||
exist when it was written (`showSidebarAgent`, `showSidebarTodos`, and
|
||||
`showSidebarOnStartup`, new in atelier 0.8.0). Upstream still reads the old
|
||||
keys, but only as non-authoritative legacy inputs, so the file worked while
|
||||
silently missing every sidebar control added since. Verified by loading the old
|
||||
and new file through pi-atelier 0.8.0's own `loadConfig()`: zero warnings from
|
||||
each and an identical *effective* config, so it is a pure schema
|
||||
modernisation — every deliberate choice (compact density, 60/85 context
|
||||
thresholds, notifications off) is preserved. `sidebarPanelLayout` is left unset
|
||||
on purpose so the panel set tracks upstream as atelier adds panels.
|
||||
- **pi is now PINNED, not `latest`: `PI_VERSION=0.84.1`** (`Dockerfile.variant`).
|
||||
CI's `resolve-versions` job used to resolve `@earendil-works/pi-coding-agent`
|
||||
to npm `latest`, which meant every release silently adopted whatever pi had
|
||||
shipped that morning — unaudited — in the same build that then got tagged and
|
||||
published. A pi minor can move the private TUI/renderer internals pi-atelier
|
||||
wraps (0.84 vs atelier 0.6.0: startup hang) or the session `.jsonl` format
|
||||
`pi-session-repair` parses. **The pin is a checkpoint, not a freeze** —
|
||||
bumping stays a routine one-line change; what stops is *unreviewed* adoption.
|
||||
0.84.1 was audited for this release: theme/TUI additions are additive, the
|
||||
session format is unchanged (`CURRENT_SESSION_VERSION = 3` in both 0.83.0 and
|
||||
0.84.1, identical `migrateV1ToV2`/`migrateV2ToV3` ladder, so existing
|
||||
transcripts are neither migrated nor at risk), and the Node engine floor is
|
||||
unmoved at `>=22.19.0`.
|
||||
- The pins live in the **Dockerfiles** and CI reads them from there (a
|
||||
`checkout` was added to `resolve-versions`), so a local `docker build` and a
|
||||
CI release ship the same versions by construction instead of by convention.
|
||||
- CI **fails** the build when the pin is not a concrete version, and when the
|
||||
pinned version is not actually published on npm — catching a typo, an
|
||||
unpublished version, or one yanked after we audited it, at resolve time with
|
||||
a clear message rather than as an `npm install` error mid-build.
|
||||
- CI **warns** (`::warning::`, never adopts) when npm `latest` is ahead of the
|
||||
pin, naming the newer version and what to re-check. That warning is the
|
||||
prompt to audit and bump — not something to silence.
|
||||
|
||||
- **mempalace pin `3.5.0` → `3.6.0`** (`Dockerfile.base` `MEMPALACE_VERSION`),
|
||||
in lockstep with opencode-devbox v2.9.0 as the pin's own comment requires.
|
||||
3.6.0 (2026-07-17) is PyPI latest and is additive/reliability only — secure
|
||||
@@ -35,6 +123,57 @@ is kicked off.
|
||||
|
||||
### Documentation
|
||||
|
||||
- **New README section: "Using pi-atelier (TUI sidebar)"** — what the status rail
|
||||
and sidebar give you, the `alt+a` / `/atelier` entry points, session-scoped
|
||||
`/atelier sidebar on|off` versus persistent Save, and `DEVBOX_ATELIER=0` to opt
|
||||
out. Plus the config story: why `~/.pi/agent/pi-atelier.json` is **copied, not
|
||||
symlinked** (atelier saves via write-temp-then-`rename(2)`, and `rename`
|
||||
replaces a symlink rather than following it, so a symlink would silently detach
|
||||
on the first save), why `install.sh` therefore only seeds it when absent, which
|
||||
keys are current versus legacy-compatibility, and the 92-column auto-hide /
|
||||
64-column main-pane floor so a narrow terminal degrades gracefully.
|
||||
- **Documents how to authenticate the container to a LAN peer with its own key**
|
||||
(README: *Giving the container its own key for a peer*) — the gap the existing
|
||||
*Naming LAN peers* section left open. That section explained `ProxyJump`
|
||||
*routing* while asserting `HostName`/`User`/`IdentityFile` are "inherited from
|
||||
the matching block in your real `~/.ssh/config`", which is precisely what fails
|
||||
in a container: host keys are normally passphrase-protected and unlocked by the
|
||||
macOS Keychain or an `ssh-agent`, neither of which exists here, so the key can
|
||||
never be decrypted — `Permission denied (publickey)` while the identical
|
||||
`ssh peer` works fine in a host terminal — and `~/.ssh` is read-only, so no
|
||||
usable key can be added there either. The new walkthrough (throwaway example
|
||||
keys) covers a passphraseless keypair in the `devbox-ssh-local` volume so it
|
||||
survives `--force-recreate`; a hardened `authorized_keys` line (`restrict`,
|
||||
`from=`, optional `permitopen`); the non-obvious detail that `from=` must allow
|
||||
the **host's** addresses, plural, because container egress is NAT'd through the
|
||||
host and a roaming laptop presents a different one per network (a `from=`
|
||||
mismatch is indistinguishable from a wrong key in the error message); the
|
||||
`IdentityFile` override in the host-owned `ssh-lan.conf`; and verification with
|
||||
`-o ControlPath=none` so a warm ControlMaster cannot fake a pass. States
|
||||
explicitly that no private key is in the published image — the volume is
|
||||
created at runtime on the operator's own machine.
|
||||
- **Corrects two claims in *Naming LAN peers***: (1) `ssh-lan.conf` is not
|
||||
`ProxyJump`-only — it is `Include`d before `~/.ssh/config`, so by
|
||||
first-value-wins *any* option set there wins, which is what makes the
|
||||
`IdentityFile` override above possible; (2) "newly added peers work
|
||||
immediately, no container or session restart needed" holds only for *edits* to
|
||||
an existing file. Creating it for the first time **does** need one restart,
|
||||
because `setup-lan-access.sh` emits the
|
||||
`Include ~/.config/devbox-shell/ssh-lan.conf` line only
|
||||
`if [ -r "$SSH_LAN_CONF" ]` at container start — until then ssh never reads it,
|
||||
which presents exactly as "my override is being ignored".
|
||||
- **Adds *macOS-only keywords in a shared `~/.ssh/config`***. The same file is
|
||||
read by macOS ssh and by the container's Linux OpenSSH, where macOS-only
|
||||
keywords are fatal rather than ignored: one `UseKeychain yes` in a `Host *`
|
||||
block yields `Bad configuration option: usekeychain` /
|
||||
`terminating, 1 bad configuration options` and takes down `dssh`/`dscp`,
|
||||
`pi --ssh`, `scp` and every helper that shells out to ssh — while the host
|
||||
keeps working, so it presents as a container regression rather than a host
|
||||
config error. Fix is `IgnoreUnknown UseKeychain` *ahead of* the keyword (macOS
|
||||
still honours it, Linux skips it), plus keeping such a `Host *` block below
|
||||
OrbStack's `Include ~/.orbstack/ssh/config`, which documents in its own comment
|
||||
that it must come first.
|
||||
|
||||
- Documents **per-variant image description labels** (committed and pushed after
|
||||
the v1.6.4 tag without a changelog entry). Both published variants used to
|
||||
inherit `Dockerfile.base`'s `description="pi-devbox — base image
|
||||
|
||||
Reference in New Issue
Block a user