Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4efa84bb42 | |||
| fa38e3e102 | |||
| 1d53e008d4 | |||
| 28e324d68f | |||
| 3da7312166 |
@@ -9,9 +9,13 @@ name: Publish Docker Image
|
|||||||
# Pipeline shape:
|
# Pipeline shape:
|
||||||
# 1. base-decide compute base hash from Dockerfile.base + rootfs/
|
# 1. base-decide compute base hash from Dockerfile.base + rootfs/
|
||||||
# + entrypoints; probe Docker Hub for existing tag.
|
# + entrypoints; probe Docker Hub for existing tag.
|
||||||
# 2. resolve-versions resolve pi @ npm 'latest', pi-fork/pi-obsmem refs
|
# 2. resolve-versions pi and mempalace from their pins (checked on npm/
|
||||||
# to commit SHAs (defeats registry-buildcache
|
# PyPI); pi-obsmem and pi-atelier from their pins in
|
||||||
# cache-hit footgun on byte-identical build args).
|
# Dockerfile.variant; pi-fork and the Gitea repos to
|
||||||
|
# their branch heads, pi-studio to its newest tag;
|
||||||
|
# all refs to commit SHAs (defeats
|
||||||
|
# registry-buildcache cache-hit footgun on
|
||||||
|
# byte-identical build args).
|
||||||
# 3. build-base only if probe missed; multi-arch push of base-<hash>.
|
# 3. build-base only if probe missed; multi-arch push of base-<hash>.
|
||||||
# 4. smoke amd64-only build of the variant FROMing the base
|
# 4. smoke amd64-only build of the variant FROMing the base
|
||||||
# tag; runs scripts/smoke-test.sh.
|
# tag; runs scripts/smoke-test.sh.
|
||||||
@@ -186,7 +190,7 @@ jobs:
|
|||||||
# gap -- and for it the gap is strictly worse. Shellcheck judges the TREE:
|
# gap -- and for it the gap is strictly worse. Shellcheck judges the TREE:
|
||||||
# green on main is still green at the tag, because the bytes did not move.
|
# green on main is still green at the tag, because the bytes did not move.
|
||||||
# Check 9 judges the tree against UPSTREAM NOW, and the floating refs it
|
# Check 9 judges the tree against UPSTREAM NOW, and the floating refs it
|
||||||
# watches (PI_OBSMEM_REF=master and friends, which resolve-versions below turns
|
# watches (PI_FORK_REF=master and friends, which resolve-versions below turns
|
||||||
# into SHAs) move with no commit in this repo at all -- so a green reading on
|
# into SHAs) move with no commit in this repo at all -- so a green reading on
|
||||||
# main carries no information about tag time, and that window is exactly where
|
# main carries no information about tag time, and that window is exactly where
|
||||||
# releases live. Worked example: pi-observational-memory moved cba0334 ->
|
# releases live. Worked example: pi-observational-memory moved cba0334 ->
|
||||||
@@ -386,15 +390,37 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "mempalace_version=${MEMPALACE_VERSION}" >> "$GITHUB_OUTPUT"
|
echo "mempalace_version=${MEMPALACE_VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# pi-fork / pi-observational-memory (GitHub) → commit SHAs.
|
# pi-fork (GitHub) → master's commit SHA. Floats by design.
|
||||||
FORK_REF=$(curl -sf -H "Accept: application/vnd.github.sha" \
|
FORK_REF=$(curl -sf -H "Accept: application/vnd.github.sha" \
|
||||||
"https://api.github.com/repos/elpapi42/pi-fork/commits/master" || true)
|
"https://api.github.com/repos/elpapi42/pi-fork/commits/master" || true)
|
||||||
require_sha PI_FORK_REF "$FORK_REF"
|
require_sha PI_FORK_REF "$FORK_REF"
|
||||||
OBSMEM_REF=$(curl -sf -H "Accept: application/vnd.github.sha" \
|
|
||||||
"https://api.github.com/repos/elpapi42/pi-observational-memory/commits/master" || true)
|
|
||||||
require_sha PI_OBSMEM_REF "$OBSMEM_REF"
|
|
||||||
echo "fork_ref=${FORK_REF}" >> "$GITHUB_OUTPUT"
|
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,
|
||||||
|
# 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
|
||||||
|
# and lands what pi needs on master (PR #83, pi 0.87's finishTurn,
|
||||||
|
# never reached a tag). Its peerDependencies are `*`, so a mismatch
|
||||||
|
# with pi is silent at runtime. Adopting master is therefore a
|
||||||
|
# deliberate one-line bump after audit, never a side effect of the day
|
||||||
|
# CI ran. Read like atelier's tag, and require the full 40 chars: a
|
||||||
|
# short SHA would also downgrade check-doc-drift.sh's check to a SKIP.
|
||||||
|
OBSMEM_REF=$(sed -n 's/^ARG PI_OBSMEM_REF=\([^[:space:]]*\).*/\1/p' Dockerfile.variant | head -n1)
|
||||||
|
if ! printf '%s' "${OBSMEM_REF:-}" | grep -qiE '^[0-9a-f]{40}$'; then
|
||||||
|
echo "::error::ARG PI_OBSMEM_REF in Dockerfile.variant is not a full 40-char commit SHA (got '${OBSMEM_REF:-<empty>}'). pi-observational-memory must stay pinned to an audited commit — see the note above that ARG."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "obsmem_ref=${OBSMEM_REF}" >> "$GITHUB_OUTPUT"
|
echo "obsmem_ref=${OBSMEM_REF}" >> "$GITHUB_OUTPUT"
|
||||||
|
# Informational only, like pi's npm drift warning: master moving past
|
||||||
|
# the pin is how a fix pi needs becomes visible. `|| true` so a GitHub
|
||||||
|
# hiccup cannot fail a release whose pin is already in the repo.
|
||||||
|
OBSMEM_MASTER=$(curl -sf -H "Accept: application/vnd.github.sha" \
|
||||||
|
"https://api.github.com/repos/elpapi42/pi-observational-memory/commits/master" || true)
|
||||||
|
if printf '%s' "${OBSMEM_MASTER:-}" | grep -qiE '^[0-9a-f]{40}$' && [ "${OBSMEM_MASTER}" != "${OBSMEM_REF}" ]; then
|
||||||
|
echo "::warning::pi-observational-memory master is ${OBSMEM_MASTER:0:7}; this build ships the audited pin ${OBSMEM_REF:0:7}. To adopt it: read https://github.com/elpapi42/pi-observational-memory/compare/${OBSMEM_REF}...${OBSMEM_MASTER} (the observer/reflector/dropper workers and their pi API use), re-run the turn-cap probe against the pinned pi, then bump ARG PI_OBSMEM_REF in Dockerfile.variant and name the SHA in CHANGELOG.md."
|
||||||
|
fi
|
||||||
|
|
||||||
# pi-atelier → the PINNED TAG's commit SHA. Unlike fork/obsmem
|
# pi-atelier → the PINNED TAG's commit SHA. Unlike fork/obsmem
|
||||||
# (which track a branch head) atelier wraps pi's private TUI
|
# (which track a branch head) atelier wraps pi's private TUI
|
||||||
@@ -483,7 +509,7 @@ jobs:
|
|||||||
echo "Resolved PI_VERSION=${PI_VERSION} (pinned in Dockerfile.variant; npm latest is ${PI_NPM_LATEST:-unknown})"
|
echo "Resolved PI_VERSION=${PI_VERSION} (pinned in Dockerfile.variant; npm latest is ${PI_NPM_LATEST:-unknown})"
|
||||||
echo "Resolved MEMPALACE_VERSION=${MEMPALACE_VERSION} (pinned in Dockerfile.base; PyPI latest is ${MEMPALACE_PYPI_LATEST:-unknown})"
|
echo "Resolved MEMPALACE_VERSION=${MEMPALACE_VERSION} (pinned in Dockerfile.base; PyPI latest is ${MEMPALACE_PYPI_LATEST:-unknown})"
|
||||||
echo "Resolved PI_ATELIER_REF=${ATELIER_REF} (pi-atelier ${ATELIER_TAG}, pinned)"
|
echo "Resolved PI_ATELIER_REF=${ATELIER_REF} (pi-atelier ${ATELIER_TAG}, pinned)"
|
||||||
echo "Resolved PI_FORK_REF=${FORK_REF}, PI_OBSMEM_REF=${OBSMEM_REF}"
|
echo "Resolved PI_FORK_REF=${FORK_REF}, PI_OBSMEM_REF=${OBSMEM_REF} (pinned; master is ${OBSMEM_MASTER:-unknown})"
|
||||||
echo "Resolved PI_TOOLKIT_REF=${TOOLKIT_REF}, PI_EXTENSIONS_REF=${EXTENSIONS_REF}"
|
echo "Resolved PI_TOOLKIT_REF=${TOOLKIT_REF}, PI_EXTENSIONS_REF=${EXTENSIONS_REF}"
|
||||||
echo "Resolved PI_STUDIO_REF=${STUDIO_REF} (pi-studio ${STUDIO_TAG})"
|
echo "Resolved PI_STUDIO_REF=${STUDIO_REF} (pi-studio ${STUDIO_TAG})"
|
||||||
echo "Resolved MEMPALACE_TOOLKIT_REF=${MEMPALACE_TOOLKIT_REF}"
|
echo "Resolved MEMPALACE_TOOLKIT_REF=${MEMPALACE_TOOLKIT_REF}"
|
||||||
|
|||||||
+146
@@ -11,6 +11,152 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v1.11.1 — 2026-10-14
|
||||||
|
|
||||||
|
Patch release. Headlines: **pi 1.2.0**, **`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
|
||||||
|
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
|
||||||
|
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 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **pi** | `1.1.0` | **`1.2.0`** | audited, 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 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,
|
||||||
|
so CI baked the pin only while `master` happened to equal it (v1.9.5, v1.10.2,
|
||||||
|
v1.11.0). It now reads the pin from `Dockerfile.variant`, the way pi-atelier's
|
||||||
|
tag is read. It refuses anything but a full 40-char SHA, and prints a
|
||||||
|
`::warning::` with the compare link when `master` has moved past the pin. The
|
||||||
|
pin stays an untagged `master` commit on purpose. Upstream tags rarely (the
|
||||||
|
newest tag is still 3.1.4) and lands what pi needs on `master`: #83, the
|
||||||
|
migration to pi 0.87's `finishTurn`, never reached a tag. Its
|
||||||
|
`peerDependencies` are `*`, so a mismatch with pi fails silently at runtime.
|
||||||
|
Adopting a newer `master` is now a deliberate one-line bump, after the
|
||||||
|
turn-cap probe. The new step was run on its own against six inputs: the real
|
||||||
|
pin gives `022da5e` and no warning; a short SHA, `master` or a missing ARG
|
||||||
|
fails; an older pin gives that SHA plus the warning; GitHub unreachable gives
|
||||||
|
the pin and no warning. The workflow's pipeline comment, which still said pi
|
||||||
|
resolves from npm `latest`, is corrected too.
|
||||||
|
- **`pi -p` on a devbox no longer sits idle for five minutes after
|
||||||
|
answering.** It printed its answer, then held the terminal until a timer
|
||||||
|
ran out: the feed's 300 s mine deadline (mempalace-toolkit `mempalace.ts`).
|
||||||
|
The bridge raced its transcript mine against a `setTimeout` it never cleared.
|
||||||
|
Interactive pi calls `process.exit()` and never showed it, but print mode
|
||||||
|
(`pi -p`, `--mode json`) waits for an empty event loop. Measured on tor-ms22
|
||||||
|
(pi 1.1.0): before the fix, `PONG` came at 4 s and exit at 315 s, and that
|
||||||
|
timer was the only live resource for the last 265 s. After `2f0edf5`, exit
|
||||||
|
is at 45 s, the moment the session's catch-up feed finishes (prepare 11 s,
|
||||||
|
mine 31 s). `f590a3a` then makes print and JSON mode skip that feed by
|
||||||
|
default: `pi -p` takes 2.9 s and `--mode json` 3.8 s. Nothing is lost. A
|
||||||
|
one-shot run's own transcript rarely passes the exporter's quality gate, and
|
||||||
|
the next interactive session files the device's other sessions the same
|
||||||
|
way. The mine skips an unchanged transcript and purges a grown one before
|
||||||
|
refiling, so nothing is filed twice. `MEMPALACE_FEED_PRINT=1` restores the
|
||||||
|
feed for a device that only runs `pi -p`. pi 1.0.0 behaved the same, so it
|
||||||
|
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
|
||||||
|
`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
|
||||||
|
poll (mempalace-toolkit `d5a03f4`). The injection is status, diary, and the
|
||||||
|
owed/closed mailbox lists. The `mempalace_*` tools are still registered.
|
||||||
|
Measured on tor-ms22: `pi -p` makes 3 palace requests (the MCP handshake)
|
||||||
|
and exits in 1.6 s. With the wake-up it made 15 requests, 12 of them
|
||||||
|
`tools/call`. The wake-up is for the person at an interactive session, who
|
||||||
|
sees it in the next one. In a script it is cost, and once it was harm: a
|
||||||
|
probe's flash model read the injected diary's open items as its task, ran
|
||||||
|
about 12 tool calls, and filed a wrong drawer in the shared palace.
|
||||||
|
`MEMPALACE_WAKEUP_PRINT=1` restores the wake-up for `pi -p`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **pi `1.1.0` → `1.2.0`.** Audited 2026-10-14 on tor-ms22 from an isolated npm
|
||||||
|
prefix, by the same method as v1.11.0. 1.2.0 has four breaking changes, and
|
||||||
|
none reaches this image:
|
||||||
|
- `before_agent_start` also fires for runs started by
|
||||||
|
`pi.sendMessage(…, { triggerTurn: true })`, and its event is a union on
|
||||||
|
`messageType`. `systemPrompt` is on both members. Of the three handlers in
|
||||||
|
the image, ssh-controlmaster reads only `systemPrompt`, and aws-sso and
|
||||||
|
mempalace read no event field. Nothing in the image uses `triggerTurn`.
|
||||||
|
- The session counts as busy while a run starts, so `pi.sendUserMessage()`
|
||||||
|
then needs `deliverAs`. aws-sso already passes it. mempalace's
|
||||||
|
`/mempalace-diary` is a command, so it runs while pi is idle.
|
||||||
|
- Themes are schema-validated at startup, and the schema moved to `pi.dev`.
|
||||||
|
pi-studio's themes still name the old URL, but they load and apply on 1.2.0
|
||||||
|
with no warning.
|
||||||
|
- `pi mcp list --json` reports `resources: true` instead of counts. Nothing
|
||||||
|
here parses it.
|
||||||
|
|
||||||
|
Measured on 1.2.0. pi-agent-core's dist is byte-identical to 1.1.0's. The
|
||||||
|
turn-cap probe ran 19 turns uncapped and stopped at exactly 3 with
|
||||||
|
`maxTurns=3`. atelier paints in both renderers, with controls at 0/3.
|
||||||
|
pi-observational-memory registers `/om:status` and `/om:view`. A copy of
|
||||||
|
tor-ms22's real `~/.pi/agent` starts with the same extension list as on 1.1.0
|
||||||
|
and no extension issues. `pi -p` answers in 1.0 s through ERDC and 5.2 s
|
||||||
|
through Bedrock. One change reaches scripts: `pi -p` now exits 1 when the
|
||||||
|
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.
|
||||||
|
- **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
|
||||||
|
added with `pi mcp add`, and `/mcp` manages them (tools, reconnect, OAuth
|
||||||
|
sign-in, enable/disable, exposure). Because an extension that registers
|
||||||
|
`/mcp` *replaces* the built-in, every start on v1.11.0 printed
|
||||||
|
`Extension … mcp-loader.ts registers command /mcp, so built-in extension mcp
|
||||||
|
was not loaded`, and `mcp.json` was never read. Measured on tor-ms22
|
||||||
|
(v1.11.0, pi 1.1.0) before the switch: time to ready, 7 interleaved pty runs
|
||||||
|
each, was 0.276 s with no MCP, 0.283 s with the loader and 0.272 s with the
|
||||||
|
built-in. A prompt cost the same tokens with either and with neither, and a
|
||||||
|
stdio server (`server-everything`) connected and answered a tool call
|
||||||
|
through the built-in. No device had servers in the loader's `mcp` block, so
|
||||||
|
nothing needs migrating. If one does, pi-extensions' README ("MCP servers")
|
||||||
|
shows how to move it to `mcp.json`. Tools from `mcp.json` default to
|
||||||
|
`codemode` exposure; set `"exposure": "direct"` to declare them to the model.
|
||||||
|
MemPalace is unaffected: `mempalace.ts` registers `mempalace_*` itself.
|
||||||
|
- **A preserved volume drops the stale link by itself.** `~/.pi/agent/` survives
|
||||||
|
a recreate, so the old `extensions/mcp-loader.ts ->
|
||||||
|
/opt/pi-extensions/extensions/mcp-loader.ts` link would outlive the file. The
|
||||||
|
entrypoint's pi-extensions `install.sh --yes` now removes links into its own
|
||||||
|
repo whose target is gone (`.ts` and `/ext`-disabled `.ts.off`). It warns once
|
||||||
|
if `settings.json` still has an `mcp` block.
|
||||||
|
- **`recreate-sanity-check.sh`: one new check, "no dangling extension links".**
|
||||||
|
The existing "N extensions deployed" line counts a dangling link as deployed
|
||||||
|
(`ls` lists it), so it cannot see this case. The new check WARNs instead of
|
||||||
|
FAILing, because whatever it still finds points outside pi-extensions, which
|
||||||
|
`install.sh` leaves alone. Expect one more ✓ than v1.11.0 printed.
|
||||||
|
- **Vendored `pi-extensions` skill floor refreshed** to pi-extensions `750b2a8`.
|
||||||
|
`check-skill-floor.sh` went red on the retirement commit and is green again.
|
||||||
|
The skill now says MCP servers are the built-in's, not an extension. Because
|
||||||
|
`rootfs/` is in the base hash, this release rebuilds the base.
|
||||||
|
- DOCKER_HUB.md's pi-extensions list was three releases stale (`mcp-loader`
|
||||||
|
listed; `task`, `fork-gate`, `secret-guard`, `aws-sso` missing). It now lists
|
||||||
|
the 10 that ship and has an MCP line. The vendored skills' `VENDORED.md`
|
||||||
|
pointed at "the Unreleased CHANGELOG entry", which had been stale since
|
||||||
|
v1.8.8; it now names v1.8.8.
|
||||||
|
|
||||||
## v1.11.0 — 2026-10-12
|
## v1.11.0 — 2026-10-12
|
||||||
|
|
||||||
Minor release. Headlines: **pi 1.1.0**, **secret env values are kept out of
|
Minor release. Headlines: **pi 1.1.0**, **secret env values are kept out of
|
||||||
|
|||||||
+2
-1
@@ -49,7 +49,8 @@ Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini
|
|||||||
- **pi `{{PI_VERSION}}`** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — installed at `/usr/bin/pi`, pinned to an audited version (not npm `latest`)
|
- **pi `{{PI_VERSION}}`** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — installed at `/usr/bin/pi`, pinned to an audited version (not npm `latest`)
|
||||||
- **pi-atelier** — TUI sidebar (ordered panels, split-pane, themes), vendored at `/opt/pi-atelier` and pinned to an audited tag; the exact tag is in the image labels (`se.jordbo.pi-devbox.pi-atelier-version`) and `/etc/pi-devbox/build-manifest.json`
|
- **pi-atelier** — TUI sidebar (ordered panels, split-pane, themes), vendored at `/opt/pi-atelier` and pinned to an audited tag; the exact tag is in the image labels (`se.jordbo.pi-devbox.pi-atelier-version`) and `/etc/pi-devbox/build-manifest.json`
|
||||||
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template
|
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template
|
||||||
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
|
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 10 user-facing extensions: `ext-toggle`, `todo`, `task`, `fork-gate`, `secret-guard`, `aws-sso`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
|
||||||
|
- **MCP servers** — pi's built-in MCP support: declare servers in `~/.pi/agent/mcp.json` (or `pi mcp add`), manage them with `/mcp`
|
||||||
- **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools
|
- **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools
|
||||||
- **mempalace bridge** — MCP extension auto-symlinked so pi reads/writes the host-mounted palace
|
- **mempalace bridge** — MCP extension auto-symlinked so pi reads/writes the host-mounted palace
|
||||||
- **image-baked agent skills** — skills under `/usr/local/share/pi-devbox/skills/` (e.g. `pi-devbox-environment`, which teaches agents the container's persistence/networking/DNS/tmux/REPL specifics) are symlinked into `~/.agents/skills/` on start, available with or without a mounted skillset repo
|
- **image-baked agent skills** — skills under `/usr/local/share/pi-devbox/skills/` (e.g. `pi-devbox-environment`, which teaches agents the container's persistence/networking/DNS/tmux/REPL specifics) are symlinked into `~/.agents/skills/` on start, available with or without a mounted skillset repo
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
# content-addressed over this file, so any byte change invalidates the
|
# content-addressed over this file, so any byte change invalidates the
|
||||||
# cache. Recommended cadence: once per release for security updates.
|
# cache. Recommended cadence: once per release for security updates.
|
||||||
#
|
#
|
||||||
# BASE_REBUILD_DATE: 2026-10-12 (v1.11.0 — rootfs gains link-pi-models.sh, entrypoint-user.sh links models.json; base tools re-resolve `latest` (upstream on 2026-10-12: uv 0.13.0, nvim 0.12.6, fzf 0.74.5); previous marker 2026-09-22 / v1.9.4)
|
# BASE_REBUILD_DATE: 2026-10-14 (v1.11.1 — rootfs skill floor to pi-extensions 750b2a8, mempalace-toolkit d5a03f4; apt and base tools re-resolve (upstream unchanged since 2026-10-12: uv 0.13.0, nvim 0.12.6, fzf 0.74.5); previous marker 2026-10-12 / v1.11.0)
|
||||||
#
|
#
|
||||||
# ── Lineage note ─────────────────────────────────────────────────────
|
# ── Lineage note ─────────────────────────────────────────────────────
|
||||||
# Adapted from opencode-devbox/Dockerfile.base (commit before v1.16.2).
|
# Adapted from opencode-devbox/Dockerfile.base (commit before v1.16.2).
|
||||||
|
|||||||
+44
-7
@@ -212,9 +212,11 @@ ARG USER_NAME=developer
|
|||||||
# `exposure` is `codemode`, i.e. such tools are "neither declared to the model
|
# `exposure` is `codemode`, i.e. such tools are "neither declared to the model
|
||||||
# nor listed" and must be found with `searchTools()`. That would gut the
|
# nor listed" and must be found with `searchTools()`. That would gut the
|
||||||
# MemPalace protocol if MemPalace were a builtin-MCP server. It is not: both
|
# MemPalace protocol if MemPalace were a builtin-MCP server. It is not: both
|
||||||
# mempalace.ts and mcp-loader.ts run their OWN MCP client and surface tools via
|
# mempalace.ts runs its OWN MCP client and surfaces tools via
|
||||||
# `pi.registerTool()`, which is why they are named `mempalace_search` and not
|
# `pi.registerTool()`, which is why they are named `mempalace_search` and not
|
||||||
# `mcp__mempalace__search`. Confirmed on a second route — settings.json has
|
# `mcp__mempalace__search`. (So did pi-extensions' mcp-loader.ts until it was
|
||||||
|
# retired after v1.11.0 in favour of builtin:mcp. Servers in ~/.pi/agent/mcp.json
|
||||||
|
# ARE exposure-governed: they need `"exposure": "direct"` to be declared.) Confirmed on a second route — settings.json has
|
||||||
# neither an `mcpServers` block (builtin, exposure-governed) nor an `mcp` block.
|
# neither an `mcpServers` block (builtin, exposure-governed) nor an `mcp` block.
|
||||||
# Extension-registered tools are declared like built-ins, so `exposure` cannot
|
# Extension-registered tools are declared like built-ins, so `exposure` cannot
|
||||||
# reach them.
|
# reach them.
|
||||||
@@ -258,7 +260,36 @@ ARG USER_NAME=developer
|
|||||||
# ACTIVITY/READY/TOOLS in BOTH renderers (controls 0/3, every arm render-proofed);
|
# ACTIVITY/READY/TOOLS in BOTH renderers (controls 0/3, every arm render-proofed);
|
||||||
# obsmem 022da5e registers /om:status and /om:view (control: none); and all 11
|
# 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).
|
# pi-extensions a88fa99 extensions load with no error (control: no /aws-sso).
|
||||||
ARG PI_VERSION=1.1.0
|
#
|
||||||
|
# v1.11.1: 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
|
||||||
|
# a union on `messageType`; `systemPrompt` is on the shared base. Our 3
|
||||||
|
# handlers: ssh-controlmaster reads only systemPrompt, aws-sso and
|
||||||
|
# mempalace read no event field. Nothing in the stack uses triggerTurn.
|
||||||
|
# - the session counts as busy while a run starts (isIdle() false,
|
||||||
|
# sendUserMessage needs deliverAs). aws-sso already passes deliverAs;
|
||||||
|
# mempalace's /mempalace-diary is a command and runs while idle.
|
||||||
|
# - themes are schema-validated at startup and the schema moved to
|
||||||
|
# pi.dev. pi-studio's themes still name the old pi-mono $schema URL, but
|
||||||
|
# `--use-theme pi-studio-dark` applies (palette differs from the
|
||||||
|
# default) and prints no warning.
|
||||||
|
# - `pi mcp list --json` reports `resources: true`. Nothing parses it.
|
||||||
|
# 2. pi-agent-core 1.1.0 -> 1.2.0: dist is BYTE-IDENTICAL (24 files, same
|
||||||
|
# tree hash). The turn-cap probe drove the real 1.2.0 agentLoop: uncapped
|
||||||
|
# ran its chosen 19 turns, maxTurns=3 stopped at exactly 3 + agent_end.
|
||||||
|
# 3. 1.2.0's `-p` now exits 1 on a reply cut off at the output limit, in TEXT
|
||||||
|
# mode only (print-mode.js). pi-task and pi-fork run `--mode json`.
|
||||||
|
# 4. engines.node is still `>=22.19.0`; the image ships 24.x. Peer ranges:
|
||||||
|
# atelier >=0.84.0, fork >=0.75.0, obsmem and studio `*`.
|
||||||
|
# Exercised before tagging, in a real pty with -ne controls: atelier v0.15.0
|
||||||
|
# paints ACTIVITY/READY/TOOLS in BOTH renderers (controls 0/3, every arm
|
||||||
|
# render-proofed); obsmem 022da5e registers /om:status and /om:view (control:
|
||||||
|
# none); a copy of tor-ms22's real ~/.pi/agent (all packages, pi-extensions
|
||||||
|
# 750b2a8) starts with the same [Extensions] list as on 1.1.0 and no
|
||||||
|
# [Extension issues]; `pi -p` answers PONG via ERDC in 1.0 s, Bedrock in 5.2 s.
|
||||||
|
ARG PI_VERSION=1.2.0
|
||||||
ARG PI_TOOLKIT_REF=main
|
ARG PI_TOOLKIT_REF=main
|
||||||
ARG PI_EXTENSIONS_REF=main
|
ARG PI_EXTENSIONS_REF=main
|
||||||
# Repo URLs default to the canonical gitea origin but are overridable so a
|
# Repo URLs default to the canonical gitea origin but are overridable so a
|
||||||
@@ -287,10 +318,14 @@ 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
|
# 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
|
# `npm install --omit=dev` below needs no build step. src/agents/ is
|
||||||
# UNCHANGED between the two SHAs, so the finishTurn migration is intact.
|
# UNCHANGED between the two SHAs, so the finishTurn migration is intact.
|
||||||
# KNOWN GAP, not fixed here: docker-publish.yml's resolve-versions still
|
# v1.11.1: unchanged (master is still 022da5e, 6 commits past the 3.1.4 tag).
|
||||||
# resolves obsmem from `master` and passes it as --build-arg, which overrides
|
# CI NOW BUILDS THIS PIN. Until v1.11.1 docker-publish.yml's resolve-versions
|
||||||
# this default (verified two-sided on buildx 2026-10-12). CI builds therefore
|
# resolved obsmem from `master` and passed it as --build-arg, which overrides
|
||||||
# get the pin only while master == pin, as it is at this release.
|
# 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
|
||||||
|
# but a 40-char SHA, and WARNS when master has moved past it. obsmem tags
|
||||||
|
# rarely and lands what pi needs on master (PR #83 never reached a tag), so
|
||||||
|
# adopting master is still the norm: bump this SHA, deliberately, after audit.
|
||||||
ARG PI_OBSMEM_REF=022da5e5554d04dfdc876ed7dac4a42c6b83dfd4
|
ARG PI_OBSMEM_REF=022da5e5554d04dfdc876ed7dac4a42c6b83dfd4
|
||||||
# pi-atelier (TUI sidebar: ordered panels, split-pane, themes) is PINNED TO A
|
# pi-atelier (TUI sidebar: ordered panels, split-pane, themes) is PINNED TO A
|
||||||
# TAG, which CI resolves to that tag's commit SHA — same treatment as
|
# TAG, which CI resolves to that tag's commit SHA — same treatment as
|
||||||
@@ -379,6 +414,8 @@ ARG PI_ATELIER_REPO=https://github.com/michaelmjhhhh/pi-atelier.git
|
|||||||
# panels and simplifies the Activity rows. The startup preference is still
|
# panels and simplifies the Activity rows. The startup preference is still
|
||||||
# `showSidebarOnStartup` (default true, src/config.ts:38). Painted in both
|
# `showSidebarOnStartup` (default true, src/config.ts:38). Painted in both
|
||||||
# renderers on pi 1.1.0 before tagging (see the PI_VERSION note).
|
# 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
|
||||||
|
# pi 1.2.0 before tagging.
|
||||||
ARG PI_ATELIER_REF=v0.15.0
|
ARG PI_ATELIER_REF=v0.15.0
|
||||||
# Human-readable tag PI_ATELIER_REF was resolved from; recorded as a label.
|
# Human-readable tag PI_ATELIER_REF was resolved from; recorded as a label.
|
||||||
ARG PI_ATELIER_VERSION=v0.15.0
|
ARG PI_ATELIER_VERSION=v0.15.0
|
||||||
|
|||||||
@@ -1198,7 +1198,7 @@ persisted volumes survived, and pi runtime wiring is intact:
|
|||||||
```bash
|
```bash
|
||||||
./scripts/recreate-sanity-check.sh # auto-detects variant
|
./scripts/recreate-sanity-check.sh # auto-detects variant
|
||||||
./scripts/recreate-sanity-check.sh --expected-image-version 1.8.9 # assert the pi-devbox release tag
|
./scripts/recreate-sanity-check.sh --expected-image-version 1.8.9 # assert the pi-devbox release tag
|
||||||
./scripts/recreate-sanity-check.sh --expected-version 1.1.0 # assert the pi coding agent version
|
./scripts/recreate-sanity-check.sh --expected-version 1.2.0 # assert the pi coding agent version
|
||||||
```
|
```
|
||||||
|
|
||||||
Those are **two different versions**, and the flags are not interchangeable:
|
Those are **two different versions**, and the flags are not interchangeable:
|
||||||
@@ -1237,7 +1237,7 @@ resolved to `latest` at build time:
|
|||||||
|
|
||||||
| Component | Pin | Where |
|
| Component | Pin | Where |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| pi | `1.1.0` | `ARG PI_VERSION` — `Dockerfile.variant` |
|
| pi | `1.2.0` | `ARG PI_VERSION` — `Dockerfile.variant` |
|
||||||
| pi-obsmem | `022da5e5554d04dfdc876ed7dac4a42c6b83dfd4` | `ARG PI_OBSMEM_REF` — `Dockerfile.variant` |
|
| pi-obsmem | `022da5e5554d04dfdc876ed7dac4a42c6b83dfd4` | `ARG PI_OBSMEM_REF` — `Dockerfile.variant` |
|
||||||
| pi-atelier | `v0.15.0` | `ARG PI_ATELIER_REF` — `Dockerfile.variant` |
|
| pi-atelier | `v0.15.0` | `ARG PI_ATELIER_REF` — `Dockerfile.variant` |
|
||||||
| mempalace | `3.10.0` | `ARG MEMPALACE_VERSION` — `Dockerfile.base` |
|
| mempalace | `3.10.0` | `ARG MEMPALACE_VERSION` — `Dockerfile.base` |
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ its skill file needed baking.
|
|||||||
`docker build` records the same thing CI does.
|
`docker build` records the same thing CI does.
|
||||||
|
|
||||||
Verifying "is this snapshot current?" is **not** a CI job and was deliberately
|
Verifying "is this snapshot current?" is **not** a CI job and was deliberately
|
||||||
not made one — see the Unreleased CHANGELOG entry for why (private repo;
|
not made one — see the v1.8.8 CHANGELOG entry for why (private repo;
|
||||||
another repo's branch must not be able to fail this build; and the artefact it
|
another repo's branch must not be able to fail this build; and the artefact it
|
||||||
would guard is read by no host on this fleet). The check belongs where the
|
would guard is read by no host on this fleet). The check belongs where the
|
||||||
skillset actually is: `vendor-mempalace-skill.sh --check` for a maintainer,
|
skillset actually is: `vendor-mempalace-skill.sh --check` for a maintainer,
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ Pi has **two distinct extension locations** and it's easy to look in the wrong o
|
|||||||
|
|
||||||
| Location | Mechanism | Examples |
|
| Location | Mechanism | Examples |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `~/.pi/agent/extensions/*.ts` (or `.ts.off`) | **Local extensions** — TypeScript files, usually symlinks into `/opt/pi-extensions/extensions/` or similar. Toggled via `/ext` slash command. | `ssh-controlmaster`, `git-checkpoint`, `notify`, `todo`, `mempalace`, `mcp-loader`, `ext-toggle`, `confirm-destructive` |
|
| `~/.pi/agent/extensions/*.ts` (or `.ts.off`) | **Local extensions** — TypeScript files, usually symlinks into `/opt/pi-extensions/extensions/` or similar. Toggled via `/ext` slash command. | `ssh-controlmaster`, `git-checkpoint`, `notify`, `todo`, `mempalace`, `ext-toggle`, `task`, `fork-gate`, `secret-guard`, `aws-sso`, `confirm-destructive` |
|
||||||
| `~/.pi/agent/git/<host>/<owner>/<repo>/` | **Package extensions (git-installed)** — git-cloned npm packages registered via the `packages` array in `~/.pi/agent/settings.json`. | `pi-fork` (`github.com/elpapi42/pi-fork`), `pi-observational-memory` (`github.com/elpapi42/pi-observational-memory`, **default branch `master`** — a `main` branch does not exist, so `pi install git:...` resolves against `master`) |
|
| `~/.pi/agent/git/<host>/<owner>/<repo>/` | **Package extensions (git-installed)** — git-cloned npm packages registered via the `packages` array in `~/.pi/agent/settings.json`. | `pi-fork` (`github.com/elpapi42/pi-fork`), `pi-observational-memory` (`github.com/elpapi42/pi-observational-memory`, **default branch `master`** — a `main` branch does not exist, so `pi install git:...` resolves against `master`) |
|
||||||
| `~/.pi/agent/npm/node_modules/<pkg>/` | **Package extensions (npm-installed)** — `pi install npm:<pkg>`; recorded in `packages[]` as `npm:<pkg>`. | `pi-atelier` (status rail + sidebar TUI) |
|
| `~/.pi/agent/npm/node_modules/<pkg>/` | **Package extensions (npm-installed)** — `pi install npm:<pkg>`; recorded in `packages[]` as `npm:<pkg>`. | `pi-atelier` (status rail + sidebar TUI) |
|
||||||
| `/opt/<pkg>/` — **pi-devbox containers only** | **Vendored package extensions** — cloned into an image layer at build time with `node_modules` baked, then registered at container start by `entrypoint-user.sh` via `pi install /opt/<pkg>`. Recorded in `packages[]` as a **relative** path (`../../../../opt/pi-fork`) that resolves out of `~/.pi/agent` into the image layer, so it survives volume recreate. | `/opt/pi-fork`, `/opt/pi-observational-memory`, `/opt/pi-studio` |
|
| `/opt/<pkg>/` — **pi-devbox containers only** | **Vendored package extensions** — cloned into an image layer at build time with `node_modules` baked, then registered at container start by `entrypoint-user.sh` via `pi install /opt/<pkg>`. Recorded in `packages[]` as a **relative** path (`../../../../opt/pi-fork`) that resolves out of `~/.pi/agent` into the image layer, so it survives volume recreate. | `/opt/pi-fork`, `/opt/pi-observational-memory`, `/opt/pi-studio` |
|
||||||
|
|
||||||
|
**MCP servers are not an extension here.** pi's built-in MCP support (`builtin:mcp`) reads `~/.pi/agent/mcp.json`; manage it with `/mcp` or `pi mcp add|list`. Its tools are named `mcp__<server>__<tool>` and default to `codemode` exposure (not declared to the model) unless the server sets `"exposure": "direct"`. The `mempalace_*` tools are different: `mempalace.ts` registers them itself, so exposure never applies. The old `mcp-loader.ts` (an `mcp` block in `settings.json`) was retired on 2026-10-12, because an extension that registers `/mcp` replaces the built-in.
|
||||||
|
|
||||||
When the user asks how to use "the X extension", **check all of these** — `find ~/.pi/agent -maxdepth 4 -name "*X*"` covers the first three, and `ls -d /opt/*X*` the fourth. The `/ext` slash command shows the local-extensions list with enable/disable state. There is also a distinct skill-bundled-script category (e.g. `ci-release-watcher`'s `ssh-control-master-setup.sh`) which is **not** a pi extension at all — it's a helper script inside a skill. Don't conflate the three.
|
When the user asks how to use "the X extension", **check all of these** — `find ~/.pi/agent -maxdepth 4 -name "*X*"` covers the first three, and `ls -d /opt/*X*` the fourth. The `/ext` slash command shows the local-extensions list with enable/disable state. There is also a distinct skill-bundled-script category (e.g. `ci-release-watcher`'s `ssh-control-master-setup.sh`) which is **not** a pi extension at all — it's a helper script inside a skill. Don't conflate the three.
|
||||||
|
|
||||||
**In a pi-devbox container, do not conclude "pi-fork isn't installed" because `~/.pi/agent/git/` is empty.** It is deliberately absent: `Dockerfile.variant` vendors to `/opt` and installs by local path, because a build-time `pi install git:...` would write into `~/.pi/agent`, which the named volume then shadows on first run.
|
**In a pi-devbox container, do not conclude "pi-fork isn't installed" because `~/.pi/agent/git/` is empty.** It is deliberately absent: `Dockerfile.variant` vendors to `/opt` and installs by local path, because a build-time `pi install git:...` would write into `~/.pi/agent`, which the named volume then shadows on first run.
|
||||||
|
|||||||
@@ -303,6 +303,24 @@ else
|
|||||||
fail "only $EXT_COUNT extensions deployed (expected ≥4)"
|
fail "only $EXT_COUNT extensions deployed (expected ≥4)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# no dangling extension links. The count above includes them (ls lists a link
|
||||||
|
# whether or not its target exists) but they load nothing, and pi skips them
|
||||||
|
# without a word. That is exactly how a retired extension outlives its image on
|
||||||
|
# a preserved volume: mcp-loader.ts (retired after v1.11.0) left
|
||||||
|
# ~/.pi/agent/extensions/mcp-loader.ts -> /opt/pi-extensions/... behind. The
|
||||||
|
# pi-extensions install.sh run by the entrypoint now prunes links into ITS repo;
|
||||||
|
# anything this still finds points somewhere else, hence WARN, not FAIL.
|
||||||
|
_dangling=""
|
||||||
|
for _e in "$HOME"/.pi/agent/extensions/*.ts "$HOME"/.pi/agent/extensions/*.ts.off; do
|
||||||
|
if [ -L "$_e" ] && [ ! -e "$_e" ]; then _dangling="$_dangling $(basename "$_e")"; fi
|
||||||
|
done
|
||||||
|
if [ -z "$_dangling" ]; then
|
||||||
|
pass "no dangling extension links in ~/.pi/agent/extensions"
|
||||||
|
else
|
||||||
|
warn "dangling extension links (target gone, pi ignores them):$_dangling"
|
||||||
|
fi
|
||||||
|
unset _dangling _e
|
||||||
|
|
||||||
# mempalace.ts bridge symlink
|
# mempalace.ts bridge symlink
|
||||||
if [ -L "$HOME/.pi/agent/extensions/mempalace.ts" ]; then
|
if [ -L "$HOME/.pi/agent/extensions/mempalace.ts" ]; then
|
||||||
pass "~/.pi/agent/extensions/mempalace.ts bridge symlink"
|
pass "~/.pi/agent/extensions/mempalace.ts bridge symlink"
|
||||||
|
|||||||
Reference in New Issue
Block a user