Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf5c60a342 | |||
| edd6be1737 | |||
| efd254f4e6 | |||
| 8b69b3625b | |||
| b55b44e7b6 |
@@ -114,8 +114,10 @@ jobs:
|
||||
pi_version: ${{ steps.resolve.outputs.pi_version }}
|
||||
fork_ref: ${{ steps.resolve.outputs.fork_ref }}
|
||||
obsmem_ref: ${{ steps.resolve.outputs.obsmem_ref }}
|
||||
toolkit_ref: ${{ steps.resolve.outputs.toolkit_ref }}
|
||||
extensions_ref: ${{ steps.resolve.outputs.extensions_ref }}
|
||||
steps:
|
||||
- name: Resolve pi version + fork/obsmem refs
|
||||
- name: Resolve pi version + companion refs
|
||||
id: resolve
|
||||
run: |
|
||||
set -eu
|
||||
@@ -133,8 +135,24 @@ jobs:
|
||||
[ -n "$OBSMEM_REF" ] || OBSMEM_REF=master
|
||||
echo "fork_ref=${FORK_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "obsmem_ref=${OBSMEM_REF}" >> "$GITHUB_OUTPUT"
|
||||
# Also resolve pi-toolkit / pi-extensions main HEADs to SHAs so a
|
||||
# workflow_dispatch re-run produces byte-identical images when
|
||||
# those repos haven't moved (and a clean diff in build-arg strings
|
||||
# when they have, defeating the registry buildcache footgun).
|
||||
# Gitea API requires auth even for public-repo commit listing.
|
||||
TOOLKIT_REF=$(curl -sf -H "Authorization: token ${GITEA_BUILD_TOKEN:-${GITHUB_TOKEN:-}}" \
|
||||
"https://gitea.jordbo.se/api/v1/repos/joakimp/pi-toolkit/commits?limit=1&sha=main" \
|
||||
| jq -r '.[0].sha // "main"' 2>/dev/null || echo "main")
|
||||
EXTENSIONS_REF=$(curl -sf -H "Authorization: token ${GITEA_BUILD_TOKEN:-${GITHUB_TOKEN:-}}" \
|
||||
"https://gitea.jordbo.se/api/v1/repos/joakimp/pi-extensions/commits?limit=1&sha=main" \
|
||||
| jq -r '.[0].sha // "main"' 2>/dev/null || echo "main")
|
||||
[ -n "$TOOLKIT_REF" ] || TOOLKIT_REF=main
|
||||
[ -n "$EXTENSIONS_REF" ] || EXTENSIONS_REF=main
|
||||
echo "toolkit_ref=${TOOLKIT_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "extensions_ref=${EXTENSIONS_REF}" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved PI_VERSION=${PI_VERSION}"
|
||||
echo "Resolved PI_FORK_REF=${FORK_REF}, PI_OBSMEM_REF=${OBSMEM_REF}"
|
||||
echo "Resolved PI_TOOLKIT_REF=${TOOLKIT_REF}, PI_EXTENSIONS_REF=${EXTENSIONS_REF}"
|
||||
|
||||
# ── Phase 2: build & push base (multi-arch), only when needed ──────
|
||||
build-base:
|
||||
@@ -252,6 +270,8 @@ jobs:
|
||||
PI_VERSION=${{ needs.resolve-versions.outputs.pi_version }}
|
||||
PI_FORK_REF=${{ needs.resolve-versions.outputs.fork_ref }}
|
||||
PI_OBSMEM_REF=${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||
PI_TOOLKIT_REF=${{ needs.resolve-versions.outputs.toolkit_ref }}
|
||||
PI_EXTENSIONS_REF=${{ needs.resolve-versions.outputs.extensions_ref }}
|
||||
- name: Smoke test (amd64)
|
||||
env:
|
||||
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||
@@ -301,6 +321,8 @@ jobs:
|
||||
PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||
FORK_REF: ${{ needs.resolve-versions.outputs.fork_ref }}
|
||||
OBSMEM_REF: ${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||
TOOLKIT_REF: ${{ needs.resolve-versions.outputs.toolkit_ref }}
|
||||
EXTENSIONS_REF: ${{ needs.resolve-versions.outputs.extensions_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG_FLAGS=()
|
||||
@@ -316,6 +338,8 @@ jobs:
|
||||
--build-arg "PI_VERSION=${PI_VERSION}" \
|
||||
--build-arg "PI_FORK_REF=${FORK_REF}" \
|
||||
--build-arg "PI_OBSMEM_REF=${OBSMEM_REF}" \
|
||||
--build-arg "PI_TOOLKIT_REF=${TOOLKIT_REF}" \
|
||||
--build-arg "PI_EXTENSIONS_REF=${EXTENSIONS_REF}" \
|
||||
"${TAG_FLAGS[@]}" \
|
||||
.; then
|
||||
echo "==> Attempt ${attempt} succeeded"
|
||||
@@ -374,7 +398,7 @@ jobs:
|
||||
|
||||
# ── Phase 6: update Hub description (only on real release runs) ────
|
||||
update-description:
|
||||
needs: [build-variant]
|
||||
needs: [build-variant, resolve-versions]
|
||||
if: |
|
||||
always() &&
|
||||
needs.build-variant.result == 'success' &&
|
||||
@@ -385,7 +409,27 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Update Docker Hub description
|
||||
env:
|
||||
PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||
run: |
|
||||
# Substitute {{PI_VERSION}} placeholders in DOCKER_HUB.md so the
|
||||
# Hub page always shows which pi version is in :latest. The
|
||||
# placeholder lives in DOCKER_HUB.md (committed); CI fills it
|
||||
# at publish time using the same resolved version that was
|
||||
# baked into the variant image. No drift between page and image.
|
||||
if [ -z "${PI_VERSION}" ]; then
|
||||
echo "::error::PI_VERSION env var is empty. Likely cause: the"
|
||||
echo "::error::update-description job is missing 'resolve-versions'"
|
||||
echo "::error::in its needs: list, so needs.resolve-versions.outputs.pi_version"
|
||||
echo "::error::resolves to an empty string instead of the actual version."
|
||||
exit 1
|
||||
fi
|
||||
cp DOCKER_HUB.md /tmp/hub-full.md
|
||||
sed -i "s/{{PI_VERSION}}/${PI_VERSION}/g" /tmp/hub-full.md
|
||||
if grep -q '{{PI_VERSION}}' /tmp/hub-full.md; then
|
||||
echo "::error::DOCKER_HUB.md still contains unsubstituted {{PI_VERSION}} markers"
|
||||
exit 1
|
||||
fi
|
||||
TOKEN=$(curl -s -X POST https://hub.docker.com/v2/auth/token \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"identifier":"${{ vars.DOCKERHUB_USERNAME }}","secret":"${{ secrets.DOCKERHUB_TOKEN }}"}' \
|
||||
@@ -395,8 +439,8 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
HTTP_CODE=$(jq -n \
|
||||
--rawfile full DOCKER_HUB.md \
|
||||
--arg short "Self-contained Linux container for the pi coding-agent — pi + companions + MemPalace + curated dev tooling. Decoupled from opencode-devbox at v1.0.0." \
|
||||
--rawfile full /tmp/hub-full.md \
|
||||
--arg short "Linux container with the pi coding-agent, MemPalace, and curated dev tooling." \
|
||||
'{"full_description": $full, "description": $short}' | \
|
||||
curl -s -o /tmp/hub-response.txt -w "%{http_code}" -X PATCH \
|
||||
"https://hub.docker.com/v2/repositories/${{ vars.DOCKERHUB_USERNAME }}/pi-devbox/" \
|
||||
@@ -409,4 +453,4 @@ jobs:
|
||||
echo "::error::Docker Hub description update failed with HTTP $HTTP_CODE"
|
||||
exit 1
|
||||
fi
|
||||
echo "Description updated."
|
||||
echo "Description updated (pi version: ${PI_VERSION})."
|
||||
|
||||
+32
-1
@@ -13,7 +13,38 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
||||
|
||||
## Unreleased
|
||||
|
||||
_(no changes since v1.0.0)_
|
||||
_(no changes since v1.0.1)_
|
||||
|
||||
## v1.0.1 — 2026-06-10
|
||||
|
||||
Patch release. Works around an upstream MemPalace bug that broke pi at
|
||||
first prompt against the Anthropic Claude API.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`mempalace_diary_write` schema rejected by Anthropic API.** Mempalace
|
||||
3.3.x and 3.4.0 advertise `diary_write`'s `input_schema` with a
|
||||
top-level `anyOf: [{required:[entry]}, {required:[content]}]` to
|
||||
express "either `entry` or `content` must be supplied". Anthropic's
|
||||
tools API rejects top-level `anyOf` / `oneOf` / `allOf` outright, so
|
||||
pi failed to register tools at session start with
|
||||
`tools.<n>.custom.input_schema: input_schema does not support oneOf,
|
||||
allOf, or anyOf at the top level`. `Dockerfile.base` now patches the
|
||||
installed `mcp_server.py` after `uv tool install` to drop the `anyOf`
|
||||
block and require `["agent_name", "entry"]` instead. The mempalace
|
||||
handler still accepts `content` server-side as a kwarg alias, so
|
||||
callers using either name keep working. Tracked upstream:
|
||||
[issue #1728](https://github.com/MemPalace/mempalace/issues/1728),
|
||||
[PR #1735](https://github.com/MemPalace/mempalace/pull/1735).
|
||||
The workaround is idempotent + self-deactivating and will be removed
|
||||
once a fixed mempalace release lands on PyPI.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Mempalace pinned to 3.4.0** via `MEMPALACE_VERSION` build arg.
|
||||
Future bumps must be a reviewable diff rather than an implicit pull
|
||||
of `latest` (the broken 3.3.x/3.4.0 schema slipping in unannounced
|
||||
is what caused this release).
|
||||
|
||||
## v1.0.0 — 2026-06-09
|
||||
|
||||
|
||||
+77
-34
@@ -1,15 +1,17 @@
|
||||
# pi-devbox
|
||||
|
||||
A Docker container with [pi coding-agent](https://github.com/earendil-works/pi) pre-installed, built on top of [opencode-devbox](https://hub.docker.com/r/joakimp/opencode-devbox)'s base image. Pi gets a fully-loaded development environment in one `docker run`.
|
||||
A self-contained Docker container for the [pi coding-agent](https://github.com/earendil-works/pi) — pi + companion repos + MemPalace + a curated set of dev tooling, ready to run.
|
||||
|
||||
> **Current `:latest` ships pi `{{PI_VERSION}}`** (resolved at build time; see [Versioning](#versioning)).
|
||||
|
||||
## Image variants
|
||||
|
||||
| Tag | Size (compressed) | What you get |
|
||||
|---|---|---|
|
||||
| `joakimp/pi-devbox:latest` | ~700 MB | Pi + companion repos, on top of the opencode-devbox base |
|
||||
| `joakimp/pi-devbox:vX.Y.Z` | same | Pinned pi version (tracks the [pi npm package version](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) |
|
||||
|
||||
Multi-arch: `linux/amd64`, `linux/arm64`.
|
||||
| Tag | Architectures | Size (compressed) | What you get |
|
||||
|---|---|---|---|
|
||||
| `joakimp/pi-devbox:latest` | amd64, arm64 | ~1.1 GB | Self-contained: base + pi `{{PI_VERSION}}` + companions |
|
||||
| `joakimp/pi-devbox:vX.Y.Z` | amd64, arm64 | same | Pinned semver release |
|
||||
| `joakimp/pi-devbox:base-latest` | amd64, arm64 | ~1.0 GB | Base layer alias (internal building block; pull `:latest` instead) |
|
||||
| `joakimp/pi-devbox:base-<hash>` | amd64, arm64 | ~1.0 GB | Content-addressed base; immutable. Stable parent for variant rebuilds. |
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -38,42 +40,82 @@ Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini
|
||||
|
||||
## What's inside
|
||||
|
||||
pi-devbox is a re-brand of the **pi-only build** — it builds
|
||||
`FROM joakimp/pi-devbox:base-pi-only` and adds no layers of its own. That
|
||||
building-block tag is produced by opencode-devbox's CI (from
|
||||
`Dockerfile.variant` with `INSTALL_OPENCODE=false`) but published here, in the
|
||||
pi-devbox repo, so an opencode-devbox tag never ships without opencode.
|
||||
The pi-only build is lean
|
||||
and pi-focused (no opencode — use `opencode-devbox:latest-with-pi` if you want
|
||||
both).
|
||||
Everything below is inherited from that single source of truth.
|
||||
### pi and companions
|
||||
|
||||
Base tooling:
|
||||
|
||||
- **Debian trixie** (latest stable)
|
||||
- **Node.js** (LTS), **uv** (Python tooling), **rustup** (Rust on-demand)
|
||||
- **AWS CLI v2** + AWS Bedrock-ready config
|
||||
- **MemPalace** + MCP server — persistent agent memory across sessions, queryable via `mempalace_*` tools inside pi
|
||||
- **Gitea MCP** server
|
||||
- **Dev tools**: neovim (LazyVim defaults), tmux, bat, eza, fzf, zoxide, ripgrep, git-lfs, make
|
||||
- **Shell**: bash with history tuning, prefix-search bindings, fzf/zoxide integration
|
||||
- **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the host is set up as an SSH jump to reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS`/`HOST_SSH_USER`). No-op on native Linux.
|
||||
|
||||
pi and companions:
|
||||
|
||||
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version set by the pi-only base build
|
||||
- **pi `{{PI_VERSION}}`** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — installed at `/usr/bin/pi`
|
||||
- **[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`
|
||||
- **`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 can read/write the same palace as opencode-devbox
|
||||
- **mempalace bridge** — MCP extension auto-symlinked so pi reads/writes the host-mounted palace
|
||||
|
||||
The entrypoint deploys/registers all of these on first container start. Re-running is idempotent and preserves user edits.
|
||||
|
||||
### MemPalace (persistent agent memory)
|
||||
|
||||
- **MemPalace** + MCP server — semantic search over conversation history, knowledge graph, diary; queryable via 29 `mempalace_*` tools inside pi
|
||||
- ChromaDB ONNX embedding model pre-warmed at build time (`all-MiniLM-L6-v2`)
|
||||
- Bind-mount your host's `~/.mempalace` and the host-pi and container-pi share one brain
|
||||
|
||||
### Document and image tooling
|
||||
|
||||
- **pandoc** — universal Markdown↔HTML/Org/RST/etc. conversion. Useful well beyond pi: agent-driven doc exports, format conversion, etc.
|
||||
- **graphviz** (`dot`) — diagram rendering pipelines
|
||||
- **imagemagick** (`magick`) — image conversion / resizing
|
||||
|
||||
### Modern CLI tooling
|
||||
|
||||
- **Editor**: neovim (LazyVim defaults), tmux (configured for 0-indexed sessions)
|
||||
- **Search/nav**: ripgrep, fd, fzf, zoxide
|
||||
- **Display**: bat, eza, htop, tree
|
||||
- **Data**: jq, yq
|
||||
- **Help**: tldr (tealdeer — Rust port; run `tldr --update` once to populate cache)
|
||||
- **Git**: git-lfs, git-crypt, gitleaks (for pre-commit secret scanning)
|
||||
- **Build**: gcc, g++, make, patch
|
||||
- **Misc**: gosu, age, rsync, less
|
||||
|
||||
### Language toolchains
|
||||
|
||||
- **Python**: system Python 3 + **uv** (preferred) for fast Python package management. Run any Python REPL/notebook stack on demand without bloating the image:
|
||||
```bash
|
||||
uv run --with ipython ipython
|
||||
uv run --with jupyterlab jupyter lab --no-browser --port 8888
|
||||
uv run --with marimo marimo edit
|
||||
```
|
||||
- **Node.js** v22 + npm (used by pi itself)
|
||||
- **Rust** — `rustup-init` is on PATH; install toolchains on demand
|
||||
- **Go** — opt-in via `--build-arg INSTALL_GO=true` if rebuilding from source
|
||||
|
||||
### Cloud + secrets
|
||||
|
||||
- **AWS CLI v2** — for SSO + Bedrock auth (pi's preferred LLM provider for the maintainer's setup)
|
||||
- **Gitea MCP** server — for Gitea API access from inside pi
|
||||
- **age**, **git-crypt** — encryption tooling
|
||||
|
||||
### SSH and networking
|
||||
|
||||
- OpenSSH client with **ControlMaster auto** preconfigured on a writable socket path (`/tmp/sshcm/`). Mitigates ssh banner-exchange failures behind CGNAT-restricted residential ISPs (~4-flow caps).
|
||||
- A **LAN-access helper** that auto-configures ssh jump-via-host on VM-backed hosts (OrbStack / Docker Desktop on macOS) so the container can reach the host's directly-attached LAN peers (`dssh <peer>` alias; `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER`).
|
||||
|
||||
## Versioning
|
||||
|
||||
Tags follow the pi npm version: `v0.74.0`, `v0.75.0`, etc. `latest` always points at the most recent release. The pi binary is inherited from `joakimp/pi-devbox:base-pi-only`, so each release follows an opencode-devbox release that bakes the target pi version. (`base-pi-only` is an internal building-block tag — pull `latest` or a `vX.Y.Z` tag instead.)
|
||||
From v1.0.0 onward, pi-devbox uses **semver**:
|
||||
|
||||
For container-level rebuilds on the same pi version (security updates, base bumps, fixes) the tag gets a letter suffix: `v0.74.0b`, `v0.74.0c`, …
|
||||
- **Major** — architectural changes. v1.0.0 is the first decoupled release, where pi-devbox got its own self-contained build chain (previously it was a thin re-brand of opencode-devbox's `pi-only` variant).
|
||||
- **Minor** — new image variants, significant base additions.
|
||||
- **Patch** — pi version bumps, smaller fixes.
|
||||
|
||||
The pi binary version inside any given release is shown in this description (currently **`{{PI_VERSION}}`** for `:latest`) and asserted by smoke tests to match what's documented — version drift is caught at CI time, not on user pull.
|
||||
|
||||
> **Pre-v1.0.0 history.** Tags v0.74.0…v0.79.0 followed the pi npm version directly (`v{pi_version}[letter]`). Those images remain on Hub but are deprecated in favor of `:latest` / `:v1.X.Y`. The legacy `:base-pi-only*` tags were CI artifacts of the old opencode-devbox-based build pipeline; they will be removed in a future opencode-devbox v2.0.0.
|
||||
|
||||
### Build pipeline
|
||||
|
||||
pi-devbox is built in two phases:
|
||||
|
||||
1. **Base** (`Dockerfile.base`) → `base-<hash>` tag, content-addressed over `Dockerfile.base` + `rootfs/` + `entrypoint*.sh`. Rebuilt only when those change.
|
||||
2. **Variant** (`Dockerfile.variant`) → `:latest` and `:vX.Y.Z`. FROMs the base, adds the pi install + companions.
|
||||
|
||||
`base-latest` is an alias of the most recent base.
|
||||
|
||||
## Persistent state
|
||||
|
||||
@@ -86,6 +128,7 @@ User edits and pi-installed packages survive container recreation when you mount
|
||||
| `devbox-zoxide` | `/home/developer/.local/share/zoxide` | zoxide directory jump database |
|
||||
| `devbox-nvim-data` | `/home/developer/.local/share/nvim` | neovim plugin & Mason package state |
|
||||
| `devbox-uv` | `/home/developer/.local/share/uv` | uv Python installs and tool cache |
|
||||
| `devbox-ssh-local` | `/home/developer/.ssh-local` | LAN-jump key (one-time host authorization survives recreate) |
|
||||
|
||||
Optional volumes for MemPalace (commented out by default — uncomment in `docker-compose.yml` to persist conversation memory across restarts):
|
||||
|
||||
@@ -101,10 +144,10 @@ Optional volumes for MemPalace (commented out by default — uncomment in `docke
|
||||
## Source
|
||||
|
||||
- **This image**: https://gitea.jordbo.se/joakimp/pi-devbox
|
||||
- **Base image**: https://gitea.jordbo.se/joakimp/opencode-devbox (Hub: `joakimp/opencode-devbox`)
|
||||
- **pi**: https://github.com/earendil-works/pi
|
||||
- **pi-toolkit**: https://gitea.jordbo.se/joakimp/pi-toolkit
|
||||
- **pi-extensions**: https://gitea.jordbo.se/joakimp/pi-extensions
|
||||
- **MemPalace**: https://github.com/joakimp/mempalace
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+36
-1
@@ -273,14 +273,49 @@ RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64"
|
||||
# Always installed in the base. Set INSTALL_MEMPALACE=false at base-build
|
||||
# time to shave ~300 MB.
|
||||
ARG INSTALL_MEMPALACE=true
|
||||
# Pin to a known-good version. Bump deliberately, not implicitly: an
|
||||
# unpinned install silently swept in mempalace 3.3.x/3.4.0 with a broken
|
||||
# diary_write schema (see workaround RUN below + issue #1728). Pinning
|
||||
# makes mempalace upgrades a reviewable diff rather than a surprise.
|
||||
ARG MEMPALACE_VERSION=3.4.0
|
||||
ENV UV_TOOL_DIR=/opt/uv-tools
|
||||
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
||||
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
||||
mkdir -p /opt/uv-tools && \
|
||||
uv tool install --no-cache mempalace && \
|
||||
uv tool install --no-cache "mempalace==${MEMPALACE_VERSION}" && \
|
||||
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
||||
fi
|
||||
|
||||
# ── workaround: strip top-level anyOf from mempalace_diary_write schema ──
|
||||
# Mempalace 3.3.x/3.4.0 advertise diary_write's input_schema with a
|
||||
# top-level `anyOf: [{required:[entry]}, {required:[content]}]` to express
|
||||
# "either entry or content must be supplied". Anthropic's tools API rejects
|
||||
# top-level anyOf/oneOf/allOf, so pi/Claude fail at session start with
|
||||
# `tools.<n>.custom.input_schema: input_schema does not support oneOf,
|
||||
# allOf, or anyOf at the top level`.
|
||||
#
|
||||
# Patch the advertised schema to require ["agent_name", "entry"] and remove
|
||||
# the anyOf block. The handler keeps accepting `content` server-side as a
|
||||
# kwarg alias so existing callers still work.
|
||||
#
|
||||
# Idempotent and self-deactivating: once upstream releases the fix the
|
||||
# regex no longer matches and this RUN is a silent no-op.
|
||||
# Upstream tracking:
|
||||
# https://github.com/MemPalace/mempalace/issues/1728
|
||||
# https://github.com/MemPalace/mempalace/pull/1735
|
||||
# TODO: remove this RUN once a mempalace release containing PR #1735 is on
|
||||
# PyPI and installed by the line above.
|
||||
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
||||
MP_FILE="$(find /opt/uv-tools/mempalace -path '*/mempalace/mcp_server.py' | head -n1)" && \
|
||||
if [ -z "$MP_FILE" ]; then echo "mempalace mcp_server.py not found" >&2; exit 1; fi && \
|
||||
perl -0777 -i -pe 's/(?:[ \t]*\#[^\n]*\n)*[ \t]*"required":\s*\[\s*"agent_name"\s*\]\s*,\s*\n[ \t]*"anyOf":\s*\[\s*\n[ \t]*\{\s*"required":\s*\[\s*"entry"\s*\]\s*\}\s*,\s*\n[ \t]*\{\s*"required":\s*\[\s*"content"\s*\]\s*\}\s*,?\s*\n[ \t]*\]\s*,\s*\n/ "required": ["agent_name", "entry"],\n/s' "$MP_FILE" && \
|
||||
if grep -q '"required": \["agent_name", "entry"\]' "$MP_FILE"; then \
|
||||
echo "mempalace diary_write anyOf workaround: applied (or already clean)"; \
|
||||
else \
|
||||
echo "WARN: mempalace diary_write anyOf workaround did not match expected schema — upstream may have changed shape" >&2; \
|
||||
fi ; \
|
||||
fi
|
||||
|
||||
# ── mempalace-toolkit — bash wrappers for session/docs mining ────────
|
||||
ARG INSTALL_MEMPALACE_TOOLKIT=true
|
||||
ARG MEMPALACE_TOOLKIT_REF=main
|
||||
|
||||
+12
-12
@@ -50,16 +50,16 @@ ARG PI_OBSMEM_REPO=https://github.com/elpapi42/pi-observational-memory.git
|
||||
ARG PI_OBSMEM_REF=master
|
||||
|
||||
RUN set -e && \
|
||||
git_clone_retry() { \
|
||||
url="$1"; ref="$2"; dest="$3"; \
|
||||
for i in 1 2 3 4 5; do \
|
||||
if git clone --depth 1 --branch "$ref" "$url" "$dest"; then return 0; fi; \
|
||||
rm -rf "$dest"; \
|
||||
echo "git clone $url failed (attempt $i/5), retrying in $((i*5))s..."; \
|
||||
sleep $((i*5)); \
|
||||
done; \
|
||||
return 1; \
|
||||
} && \
|
||||
# git_fetch_ref: clone-equivalent helper that accepts EITHER a branch name
|
||||
# OR a commit SHA as $ref. Uses `git fetch <ref> + checkout FETCH_HEAD`
|
||||
# which (a) works with both name and SHA forms uniformly, and (b) defeats
|
||||
# the registry-buildcache footgun when CI passes a resolved SHA. The
|
||||
# earlier helper `git_clone_retry` (using `git clone --branch`) only
|
||||
# worked with branch names — a SHA-resolved build-arg made `git clone
|
||||
# --branch <40-char-SHA>` fail with "Remote branch not found". Surfaced
|
||||
# in pi-devbox v1.0.0-rerun (run 374) 2026-06-10 and fixed by switching
|
||||
# all four clones to git_fetch_ref. Both Gitea and GitHub allow fetching
|
||||
# arbitrary commits by default (uploadpack.allowReachableSHA1InWant).
|
||||
git_fetch_ref() { \
|
||||
url="$1"; ref="$2"; dest="$3"; \
|
||||
rm -rf "$dest"; mkdir -p "$dest"; \
|
||||
@@ -77,8 +77,8 @@ RUN set -e && \
|
||||
NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent@${PI_VERSION} ; \
|
||||
fi && \
|
||||
pi --version && \
|
||||
git_clone_retry https://gitea.jordbo.se/joakimp/pi-toolkit.git "${PI_TOOLKIT_REF}" /opt/pi-toolkit && \
|
||||
git_clone_retry https://gitea.jordbo.se/joakimp/pi-extensions.git "${PI_EXTENSIONS_REF}" /opt/pi-extensions && \
|
||||
git_fetch_ref "https://gitea.jordbo.se/joakimp/pi-toolkit.git" "${PI_TOOLKIT_REF}" /opt/pi-toolkit && \
|
||||
git_fetch_ref "https://gitea.jordbo.se/joakimp/pi-extensions.git" "${PI_EXTENSIONS_REF}" /opt/pi-extensions && \
|
||||
git_fetch_ref "${PI_FORK_REPO}" "${PI_FORK_REF}" /opt/pi-fork && \
|
||||
git_fetch_ref "${PI_OBSMEM_REPO}" "${PI_OBSMEM_REF}" /opt/pi-observational-memory && \
|
||||
(cd /opt/pi-fork && npm install --omit=dev --no-audit --no-fund) && \
|
||||
|
||||
Reference in New Issue
Block a user