5 Commits

Author SHA1 Message Date
Joakim Persson da7d70825e docs(changelog): add v1.1.4 entry (AGENTS.md autoload, settings merge, history fix)
Publish Docker Image / resolve-versions (push) Successful in 6s
Publish Docker Image / base-decide (push) Successful in 11s
Publish Docker Image / build-base (push) Successful in 42m4s
Publish Docker Image / smoke-studio (push) Successful in 3m39s
Publish Docker Image / smoke (push) Successful in 5m20s
Publish Docker Image / build-variant (push) Successful in 18m8s
Publish Docker Image / promote-base-latest (push) Successful in 9s
Publish Docker Image / update-description (push) Successful in 10s
Publish Docker Image / build-variant-studio (push) Successful in 24m41s
2026-06-17 20:52:05 +02:00
Joakim Persson 41c2c2b716 feat(entrypoint): non-destructively merge new template keys into settings.json
The settings.json bootstrap only fires when the file is ABSENT, so a
settings.json on a preserved named volume never picks up config added in a
later image (e.g. the observational-memory / pi-fork blocks, a newly-enabled
model). Users had to hand-merge after every upgrade.

On start, when settings.json already exists, deep-merge the template into it
with 'jq -s ".[0] * .[1]"' (template first, live second) so the user's values
always win and only MISSING keys are filled from the template. Arrays are
leaves (a model the user removed is not re-added). Rewrites only when the
merge changes something, backs up the original first, and skips safely (no
clobber) if either file is invalid JSON. Opt out with PI_SETTINGS_MERGE=0.

Add a recreate-sanity-check assertion that settings.json carries the
observational-memory + pi-fork blocks after recreate.
2026-06-17 20:49:41 +02:00
Joakim Persson 5c08bfc8a8 fix(shell): don't export DEVBOX_HIST_SET so nested shells flush history
The history-flush guard was exported, so it leaked into child processes.
Any nested shell -- crucially each tmux pane (which inherits the tmux
server's env) -- then saw the guard already set and skipped installing
'history -a' in PROMPT_COMMAND. Those shells only persisted history on a
clean exit, so abrupt termination (docker stop, tmux kill-server, SIGKILL)
silently lost their in-memory history. zoxide was less affected (its hook
is installed unguarded and writes immediately).

Make the guard shell-local (drop 'export') so every new interactive shell
re-installs its own per-prompt flush. Add a recreate-sanity-check assertion
that a nested login shell still wires up 'history -a'.

Storage was never the issue: ~/.cache/bash (devbox-shell-history) and
~/.local/share/zoxide (devbox-zoxide) are both persistent named volumes.
2026-06-17 17:22:30 +02:00
Joakim Persson 1371584634 sanity-check: verify global AGENTS.md symlink after recreate
pi-toolkit now symlinks pi-global-AGENTS.md -> ~/.pi/agent/AGENTS.md (pi's
global-instructions file, loaded at every start; directs the agent to read
the pi-extensions skill at session start). Add a recreate-sanity-check
assertion alongside the keybindings symlink check so a future image build
that bakes the new pi-toolkit verifies the wiring landed.
2026-06-17 16:58:18 +02:00
Joakim Persson d902b2d056 v1.1.3: add actual pi 0.79.5 release notes + document GitHub releases URL in AGENTS.md 2026-06-16 23:56:52 +02:00
5 changed files with 141 additions and 9 deletions
+2
View File
@@ -45,6 +45,8 @@ re-brand of opencode-devbox's `pi-only` variant.
1. Confirm `pi --version` resolves from npm to the expected version
(`curl -sf 'https://registry.npmjs.org/@earendil-works%2Fpi-coding-agent/latest' | jq -r .version`).
Check release notes at https://github.com/earendil-works/pi/releases for
the upstream changelog to include in `CHANGELOG.md`.
2. Update `CHANGELOG.md` Unreleased → vX.Y.Z section.
3. Verify `docker compose up` works locally with the current `latest` image
if you're upgrading users from a previous version. Then run the
+70 -3
View File
@@ -13,14 +13,81 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
## Unreleased
## v1.1.4 — 2026-06-17
Patch release: config and shell-quality fixes on a preserved volume. No pi
version bump (still `0.79.6`, latest). The `pi-toolkit` ref is auto-resolved
to `main` HEAD at build, so the AGENTS.md change below lands automatically.
### Added
- **Global `AGENTS.md` auto-loads the pi-extensions skill.** `pi-toolkit` now
ships `pi-global-AGENTS.md` and symlinks it to `~/.pi/agent/AGENTS.md` (pi's
global-instructions file, loaded at every start). It directs the agent to
read the `pi-extensions` skill at session start and carries a core
fork/recall cheat-sheet, since on-demand skill description-matching was
leaving `pi-fork` / `pi-observational-memory` under-utilised. **Heads-up:**
on a preserved volume any pre-existing real `~/.pi/agent/AGENTS.md` is backed
up to `*.bak.<timestamp>` and replaced by the symlink (same behavior as
`keybindings.json`).
- **`settings.json` merge-on-recreate.** The bootstrap only ever copied the
template when `settings.json` was *absent*, so a file on a preserved volume
never picked up config added in a later image (e.g. the
`observational-memory` / `pi-fork` blocks, a newly-enabled model). The
entrypoint now deep-merges the template into an existing `settings.json` on
start with `jq -s '.[0] * .[1]'` (template first, live second): the user's
values always win and only *missing* keys are filled in. Arrays are treated
as leaves (a model the user removed is not re-added); the file is only
rewritten when the merge changes something, the original is backed up first,
and invalid JSON on either side is skipped rather than clobbered. Opt out
with `PI_SETTINGS_MERGE=0`.
### Fixed
- **bash history loss in nested / tmux shells.** The `DEVBOX_HIST_SET` guard
that installs the per-prompt `history -a` flush was `export`ed, so it leaked
into child processes. Any nested shell — crucially each tmux pane, which
inherits the tmux server's env — saw the guard already set and skipped
installing `history -a`, persisting history only on a clean exit. Abrupt
termination (`docker stop`, `tmux kill-server`, SIGKILL) then silently lost
that shell's in-memory history. The guard is now shell-local (no `export`),
so every new interactive shell re-installs its own flush. `zoxide` was less
affected (its hook is unguarded and writes immediately). History and zoxide
storage were never the issue — `~/.cache/bash` (`devbox-shell-history`) and
`~/.local/share/zoxide` (`devbox-zoxide`) are persistent named volumes.
**Note:** existing shells/panes keep the old behavior until restarted
(`tmux kill-server` or open fresh shells).
### Maintainer
- `scripts/recreate-sanity-check.sh` gained assertions for the new wiring: the
`~/.pi/agent/AGENTS.md` symlink, a nested login shell installing
`history -a`, and `settings.json` carrying the `observational-memory` +
`pi-fork` blocks after recreate.
---
## v1.1.3 — 2026-06-16
Patch release: pi `0.79.4``0.79.5` (auto-resolved at build).
### Changed
### Bumped: pi 0.79.4 → 0.79.5
- **pi bumped to `0.79.5`** (published upstream 2026-06-16). No image-side
changes beyond the pi npm version.
Notable upstream changes (from [pi releases](https://github.com/earendil-works/pi/releases/tag/v0.79.5)):
- **Provider-scoped API key environments** — `auth.json` API key entries can
now include `env` overrides for provider-specific Cloudflare, Azure OpenAI,
Google Vertex, Amazon Bedrock, cache retention, and proxy settings without
changing the project shell.
- **Global HTTP proxy setting** — configure `httpProxy` once in global settings
to apply `HTTP_PROXY` / `HTTPS_PROXY` to Pi-managed HTTP clients.
- **Vercel AI Gateway attribution** — requests now include Pi attribution
headers by default.
- **Fixes:** inherited OpenAI Responses streaming tolerates null message content
before tool calls; DeepSeek V4 thinking no longer sends both `thinking` and
`reasoning_effort`; device-code login no longer auto-opens the browser;
various Google/Vertex Gemini model metadata corrections; session selector
empty-state fix; Cursor Up history navigation fix.
---
+29 -3
View File
@@ -86,9 +86,35 @@ if command -v pi &>/dev/null; then
# Bootstrap settings.json from template if absent (pi rewrites this
# file at runtime — lastChangelogVersion, etc — so we can't symlink it).
if [ ! -f "$HOME/.pi/agent/settings.json" ] && \
[ -f /opt/pi-toolkit/settings.example.json ]; then
cp /opt/pi-toolkit/settings.example.json "$HOME/.pi/agent/settings.json"
_pi_settings="$HOME/.pi/agent/settings.json"
_pi_template=/opt/pi-toolkit/settings.example.json
if [ ! -f "$_pi_settings" ] && [ -f "$_pi_template" ]; then
cp "$_pi_template" "$_pi_settings"
echo "pi settings.json bootstrapped from template"
elif [ -f "$_pi_settings" ] && [ -f "$_pi_template" ] && \
[ "${PI_SETTINGS_MERGE:-1}" != "0" ] && command -v jq >/dev/null 2>&1; then
# Non-destructive merge: a settings.json on a PRESERVED volume never
# otherwise sees new template keys (the bootstrap above only fires when
# the file is absent), so config added in an image upgrade — e.g. the
# observational-memory / pi-fork blocks or a newly-enabled model — never
# reaches existing users. Deep-merge with the template FIRST and the
# live file SECOND ('.[0] * .[1]') so the user's values always win and
# only keys MISSING from the live file are filled in from the template.
# Arrays are treated as leaves (the user's array is kept verbatim, so a
# model they deliberately removed is not re-added). Only rewrite when the
# merge actually changes something, and back up the original first.
# Set PI_SETTINGS_MERGE=0 to disable. Invalid JSON on either side → skip,
# never clobber.
if _pi_merged=$(jq -s '.[0] * .[1]' "$_pi_template" "$_pi_settings" 2>/dev/null); then
if [ -n "$_pi_merged" ] && \
! printf '%s' "$_pi_merged" | jq -e --slurpfile cur "$_pi_settings" '. == $cur[0]' >/dev/null 2>&1; then
cp "$_pi_settings" "${_pi_settings}.bak.$(date +%Y%m%d-%H%M%S)"
printf '%s\n' "$_pi_merged" > "$_pi_settings"
echo "pi settings.json: merged new template keys from settings.example.json (backup saved)"
fi
else
echo "WARN: pi settings.json merge skipped (jq could not parse template or live file; left untouched)"
fi
fi
# pi↔mempalace MCP bridge — single extension symlink.
+8 -1
View File
@@ -89,9 +89,16 @@ fi
# we append with a newline separator to avoid the ';;' parse error
# described at the top of this file. Guarded so repeated sourcing
# (e.g. `exec bash`) doesn't stack duplicates.
#
# The guard MUST stay shell-local (NOT exported): if it leaks into child
# processes, every nested shell -- crucially each tmux pane, which inherits
# the tmux server's env -- skips installing `history -a` and only persists
# history on a clean exit. Abrupt termination (docker stop, tmux kill-server,
# SIGKILL) then loses that shell's in-memory history. Keeping it unexported
# means each new interactive shell re-installs its own per-prompt flush.
if [ -z "${DEVBOX_HIST_SET:-}" ]; then
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a"
export DEVBOX_HIST_SET=1
DEVBOX_HIST_SET=1
fi
# ── Prompt: show [opencode-devbox] tag so it's obvious you're in the container
+32 -2
View File
@@ -6,8 +6,8 @@
# version is supplied — see the version note below)
# - Persisted named volumes survived (~/.pi config, shell history, zoxide,
# nvim data, uv cache, ssh-local)
# - pi runtime wiring is intact: keybindings symlink, ≥4 extensions, the
# mempalace.ts bridge, settings.json, and the pi-fork /
# - pi runtime wiring is intact: keybindings symlink, AGENTS.md symlink,
# ≥4 extensions, the mempalace.ts bridge, settings.json, and the pi-fork /
# pi-observational-memory / (studio variant) pi-studio package registrations
# - Shell defaults re-seeded from /etc/skel-devbox
# - /tmp/sshcm exists with mode 700 (ssh ControlMaster dir)
@@ -157,6 +157,14 @@ else
fail "~/.pi/agent/keybindings.json missing or not a symlink"
fi
# global AGENTS.md symlink (pi-toolkit) — global instructions loaded by pi at
# every start (directs the agent to read the pi-extensions skill at session start)
if [ -L "$HOME/.pi/agent/AGENTS.md" ]; then
pass "~/.pi/agent/AGENTS.md symlink (pi-toolkit)"
else
fail "~/.pi/agent/AGENTS.md missing or not a symlink"
fi
# extensions deployed (pi-extensions) — expect ≥4 *.ts
EXT_COUNT=$(ls -1 "$HOME"/.pi/agent/extensions/*.ts 2>/dev/null | wc -l | tr -d ' ')
if [ "$EXT_COUNT" -ge 4 ]; then
@@ -179,6 +187,18 @@ else
fail "~/.pi/agent/settings.json missing"
fi
# settings.json merge: the entrypoint deep-merges new template keys into a
# preserved settings.json on every start, so config added in an image upgrade
# (e.g. the observational-memory / pi-fork blocks) reaches existing volumes.
# Assert those blocks are present and that the file is still valid JSON.
if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.pi/agent/settings.json" ]; then
if jq -e 'has("observational-memory") and has("pi-fork")' "$HOME/.pi/agent/settings.json" >/dev/null 2>&1; then
pass "settings.json has observational-memory + pi-fork blocks (template merge)"
else
fail "settings.json missing observational-memory and/or pi-fork blocks (template merge did not land)"
fi
fi
# pi package registrations (pi install <local-path> → recorded in settings.json)
if [ -f "$HOME/.pi/agent/settings.json" ]; then
for pkg in pi-fork pi-observational-memory; do
@@ -214,6 +234,16 @@ else
fail "~/.bash_aliases missing"
fi
# History flush must survive shell nesting. The DEVBOX_HIST_SET guard must NOT
# be exported: if it leaks into child processes, nested shells (esp. tmux
# panes) skip installing `history -a` and lose in-memory history on abrupt
# termination. Assert a child login shell still wires up the per-prompt flush.
if bash -lic 'bash -lic "case \"\$PROMPT_COMMAND\" in *\"history -a\"*) exit 0;; *) exit 1;; esac"' </dev/null >/dev/null 2>&1; then
pass "nested shell installs 'history -a' (DEVBOX_HIST_SET not exported)"
else
fail "nested shell missing 'history -a' — DEVBOX_HIST_SET leaking to children?"
fi
if [ -f "$HOME/.inputrc" ]; then
pass "~/.inputrc exists"
else