Compare commits
6 Commits
v2.4.0
...
a31ef52b00
| Author | SHA1 | Date | |
|---|---|---|---|
| a31ef52b00 | |||
| bab78044a1 | |||
| 61ec340e40 | |||
| ee7cfae1ff | |||
| fb6588ab1f | |||
| 703edbe4a1 |
@@ -31,6 +31,19 @@ WORKSPACE_PATH=~/projects
|
|||||||
# Path to SSH keys on host
|
# Path to SSH keys on host
|
||||||
SSH_KEY_PATH=~/.ssh
|
SSH_KEY_PATH=~/.ssh
|
||||||
|
|
||||||
|
# ── MemPalace memory (local by default) ───────────────────────────
|
||||||
|
# By default each container runs its OWN MemPalace (a local stdio server;
|
||||||
|
# palace stored at ~/.mempalace). Uncomment the devbox-palace volume in
|
||||||
|
# docker-compose.yml to persist it across container recreation.
|
||||||
|
#
|
||||||
|
# To instead share ONE MemPalace across several containers / harnesses
|
||||||
|
# (pi + opencode + native), point every container at an external HTTP
|
||||||
|
# endpoint. When MEMPALACE_REMOTE_URL is set, no local mempalace-mcp is
|
||||||
|
# spawned and the devbox-palace volume is irrelevant.
|
||||||
|
# Serve it with: mempalace-mcp --transport http --host 0.0.0.0 --port 8765
|
||||||
|
# MEMPALACE_REMOTE_URL=http://mempalace.lan:8765/mcp
|
||||||
|
# MEMPALACE_REMOTE_TOKEN= # optional — sent as: Authorization: Bearer <token>
|
||||||
|
|
||||||
# ── LAN access from the container (host-OS-agnostic) ─────────────────
|
# ── LAN access from the container (host-OS-agnostic) ─────────────────
|
||||||
# On VM-backed hosts (macOS OrbStack / Docker Desktop, also Docker Desktop
|
# On VM-backed hosts (macOS OrbStack / Docker Desktop, also Docker Desktop
|
||||||
# on Windows) the container runs in a Linux VM and CANNOT reach the host's
|
# on Windows) the container runs in a Linux VM and CANNOT reach the host's
|
||||||
@@ -104,3 +117,9 @@ SSH_KEY_PATH=~/.ssh
|
|||||||
# # on image pull. Independent of ENABLE_OMOS.
|
# # on image pull. Independent of ENABLE_OMOS.
|
||||||
# # See docs/omos-skills.md
|
# # See docs/omos-skills.md
|
||||||
# OMOS_RESET=false # Force regenerate oh-my-opencode-slim config on next start (does not affect skills)
|
# OMOS_RESET=false # Force regenerate oh-my-opencode-slim config on next start (does not affect skills)
|
||||||
|
|
||||||
|
# ── Background subagents (opencode experimental flag) ────────────────
|
||||||
|
# Baked ON in the image (ENV in Dockerfile.base) because OMOS V2+ default
|
||||||
|
# orchestration depends on opencode's native background subagents. Uncomment
|
||||||
|
# to opt out (e.g. to force blocking/foreground orchestration).
|
||||||
|
# OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ GIT_USER_EMAIL=your.name@example.com
|
|||||||
# If you have per-user keys: SSH_KEY_PATH=~/<signum>/.ssh
|
# If you have per-user keys: SSH_KEY_PATH=~/<signum>/.ssh
|
||||||
# SSH_KEY_PATH=~/.ssh
|
# SSH_KEY_PATH=~/.ssh
|
||||||
|
|
||||||
|
# ── MemPalace memory ─────────────────────────────────────────────────
|
||||||
|
# Default: each container runs its own LOCAL palace. To share ONE external
|
||||||
|
# MemPalace across containers/harnesses, set the URL (no local server is
|
||||||
|
# spawned when set). MEMPALACE_REMOTE_TOKEN is an optional bearer token.
|
||||||
|
# MEMPALACE_REMOTE_URL=http://mempalace.lan:8765/mcp
|
||||||
|
# MEMPALACE_REMOTE_TOKEN=
|
||||||
|
|
||||||
# ── Locale (defaults to en_US.UTF-8) ────────────────────────────────
|
# ── Locale (defaults to en_US.UTF-8) ────────────────────────────────
|
||||||
# LANG=sv_SE.UTF-8
|
# LANG=sv_SE.UTF-8
|
||||||
# LANGUAGE=sv_SE:sv
|
# LANGUAGE=sv_SE:sv
|
||||||
|
|||||||
@@ -60,13 +60,19 @@ jobs:
|
|||||||
|
|
||||||
base-change-warning:
|
base-change-warning:
|
||||||
# Surfaces a warning when this commit changes base-image inputs
|
# Surfaces a warning when this commit changes base-image inputs
|
||||||
# (Dockerfile.base, rootfs/, entrypoint*.sh). validate.yml uses
|
# (Dockerfile.base, rootfs/, entrypoint*.sh) AND exports `base_changed` so
|
||||||
# Hub's base-latest as the parent for variant builds, so changes to
|
# validate-base/validate-omos can skip. validate.yml uses Hub's base-latest
|
||||||
# those files are NOT exercised here — only release tags rebuild the
|
# as the parent for variant builds, so a changed base is NOT exercised here
|
||||||
# base via docker-publish-split.yml.
|
# — worse, if the same commit tightens smoke-test.sh in lockstep with the
|
||||||
|
# base change (as the nano/micro commit did), those jobs HARD-FAIL against
|
||||||
|
# the stale base-latest: a false red that self-heals only once the release
|
||||||
|
# rebuilds base-latest. So we skip them here and defer full base validation
|
||||||
|
# to the release path (docker-publish-split.yml on tag push).
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
outputs:
|
||||||
|
base_changed: ${{ steps.detect.outputs.base_changed }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -74,18 +80,29 @@ jobs:
|
|||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Detect base-input changes
|
- name: Detect base-input changes
|
||||||
|
id: detect
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
if ! git diff --name-only HEAD~1 HEAD 2>/dev/null \
|
# Base inputs baked into Dockerfile.base: Dockerfile.base itself, any
|
||||||
| grep -qE '^(Dockerfile\.base|rootfs/|entrypoint.*\.sh)$'; then
|
# file under rootfs/ (COPYed into the base), and entrypoint*.sh. NB:
|
||||||
|
# rootfs/ is a PREFIX match — the earlier '^(...|rootfs/|...)$' anchored
|
||||||
|
# the whole group, so the rootfs/ branch only matched a file literally
|
||||||
|
# named 'rootfs/' and never real paths like rootfs/usr/.../x.py.
|
||||||
|
base_re='^(Dockerfile\.base$|rootfs/|entrypoint.*\.sh$)'
|
||||||
|
changed="$(git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -E "$base_re" || true)"
|
||||||
|
if [ -n "$changed" ]; then
|
||||||
|
echo "base_changed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "::warning::This commit changes base-image inputs (Dockerfile.base, rootfs/, or entrypoint*.sh). validate.yml uses Hub's base-latest as the parent for variant builds, so the new base is NOT exercised by this workflow — validate-base/validate-omos are SKIPPED to avoid a false failure against the stale base-latest. Cut a release tag, or run a workflow_dispatch of docker-publish-split.yml against a test tag (e.g. v0.0.0-base-test, promote_latest=false) for end-to-end validation of the new base."
|
||||||
|
echo "Changed base-input files:"
|
||||||
|
printf '%s\n' "$changed"
|
||||||
|
else
|
||||||
|
echo "base_changed=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "No base-image inputs changed in this commit — validate.yml fully exercises the published base-latest."
|
echo "No base-image inputs changed in this commit — validate.yml fully exercises the published base-latest."
|
||||||
exit 0
|
|
||||||
fi
|
fi
|
||||||
echo "::warning::This commit changes base-image inputs (Dockerfile.base, rootfs/, or entrypoint*.sh). validate.yml uses Hub's base-latest as the parent for variant builds, so the new base is NOT exercised by this workflow. Cut a release tag, or run a workflow_dispatch of docker-publish-split.yml against a test tag (e.g. v0.0.0-base-test, promote_latest=false) for end-to-end validation of the new base."
|
|
||||||
echo "Changed base-input files:"
|
|
||||||
git diff --name-only HEAD~1 HEAD | grep -E '^(Dockerfile\.base|rootfs/|entrypoint.*\.sh)$'
|
|
||||||
|
|
||||||
validate-base:
|
validate-base:
|
||||||
|
needs: [base-change-warning]
|
||||||
|
if: ${{ needs.base-change-warning.outputs.base_changed != 'true' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
@@ -147,6 +164,8 @@ jobs:
|
|||||||
bash scripts/smoke-test.sh opencode-devbox:ci-base --variant base
|
bash scripts/smoke-test.sh opencode-devbox:ci-base --variant base
|
||||||
|
|
||||||
validate-omos:
|
validate-omos:
|
||||||
|
needs: [base-change-warning]
|
||||||
|
if: ${{ needs.base-change-warning.outputs.base_changed != 'true' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: catthehacker/ubuntu:act-latest
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ curl -s https://api.github.com/repos/anomalyco/opencode/releases/tags/v1.15.10 |
|
|||||||
- **Registry buildkit cache-export is currently disabled** — do NOT re-add `cache-from`/`cache-to` to the `build-base` step in `.gitea/workflows/docker-publish-split.yml` without first verifying that buildkit's `mode=max` cache-export to `registry-1.docker.io` no longer returns HTTP 400 from the Hub CDN edge. The regression surfaced ~2026-05-23 and broke five consecutive opencode-devbox publish attempts (runs #332/333/334/336 + a rerun); root-caused on 2026-05-28 by a manual host-side publish that reproduced the same 400 only on `--cache-to` while image push worked fine. Failure shape is stable (`Offset:0` in the `_state` token, HTML response body = CDN-tier rejection, not registry backend), repo-specific (we're the only repo writing `:base-buildcache` mode=max), and explains why pinning `setup-buildx-action@v4.0.0` didn't help (action pin doesn't change the bundled buildkit version on the catthehacker runner image). Trade-off: dockerfile.base changes pay a full ~3 min rebuild instead of pulling cached layers; unchanged bases short-circuit at the Hub-probe step in `base-decide` and never re-build anyway. Variants don't use registry cache so they're unaffected. Re-enable condition: upstream moby/buildkit fix lands AND a low-risk test run succeeds without 400s. See CHANGELOG v1.15.12 `Unreleased` block for the full diagnostic chain. Manual escape-hatch publish procedure: `docs/manual-host-publish.md`.
|
- **Registry buildkit cache-export is currently disabled** — do NOT re-add `cache-from`/`cache-to` to the `build-base` step in `.gitea/workflows/docker-publish-split.yml` without first verifying that buildkit's `mode=max` cache-export to `registry-1.docker.io` no longer returns HTTP 400 from the Hub CDN edge. The regression surfaced ~2026-05-23 and broke five consecutive opencode-devbox publish attempts (runs #332/333/334/336 + a rerun); root-caused on 2026-05-28 by a manual host-side publish that reproduced the same 400 only on `--cache-to` while image push worked fine. Failure shape is stable (`Offset:0` in the `_state` token, HTML response body = CDN-tier rejection, not registry backend), repo-specific (we're the only repo writing `:base-buildcache` mode=max), and explains why pinning `setup-buildx-action@v4.0.0` didn't help (action pin doesn't change the bundled buildkit version on the catthehacker runner image). Trade-off: dockerfile.base changes pay a full ~3 min rebuild instead of pulling cached layers; unchanged bases short-circuit at the Hub-probe step in `base-decide` and never re-build anyway. Variants don't use registry cache so they're unaffected. Re-enable condition: upstream moby/buildkit fix lands AND a low-risk test run succeeds without 400s. See CHANGELOG v1.15.12 `Unreleased` block for the full diagnostic chain. Manual escape-hatch publish procedure: `docs/manual-host-publish.md`.
|
||||||
- **Push steps wrap `docker buildx build --push` in a 3-attempt retry loop** (15s, 30s backoff) for transient `registry-1.docker.io` blips — rate limits, brief 5xx, CDN flap. Implemented as inline `shell: bash` steps with `docker buildx build` raw rather than `docker/build-push-action@v7` so the loop is visible and tweakable. Affects the 1 base + 5 variant push steps in `.gitea/workflows/docker-publish-split.yml`; smoke-test builds (`load: true`, no push) are untouched. **This does NOT mask deterministic failures** — a true regression (like the cache-export 400 of 2026-05-23..28) fails all 3 attempts identically and the job still fails. Orthogonal to the cache-export disablement above: cache-export was about a deterministic protocol mismatch, retry is about absorbing genuine transients. Both are belt-and-braces with the `ci-release-watcher` skill's transient-rerun heuristic. If you change the matrix of push steps, keep the retry wrapper consistent across them — the pattern is duplicated rather than factored out because Gitea Actions doesn't support reusable composite shell steps cleanly.
|
- **Push steps wrap `docker buildx build --push` in a 3-attempt retry loop** (15s, 30s backoff) for transient `registry-1.docker.io` blips — rate limits, brief 5xx, CDN flap. Implemented as inline `shell: bash` steps with `docker buildx build` raw rather than `docker/build-push-action@v7` so the loop is visible and tweakable. Affects the 1 base + 5 variant push steps in `.gitea/workflows/docker-publish-split.yml`; smoke-test builds (`load: true`, no push) are untouched. **This does NOT mask deterministic failures** — a true regression (like the cache-export 400 of 2026-05-23..28) fails all 3 attempts identically and the job still fails. Orthogonal to the cache-export disablement above: cache-export was about a deterministic protocol mismatch, retry is about absorbing genuine transients. Both are belt-and-braces with the `ci-release-watcher` skill's transient-rerun heuristic. If you change the matrix of push steps, keep the retry wrapper consistent across them — the pattern is duplicated rather than factored out because Gitea Actions doesn't support reusable composite shell steps cleanly.
|
||||||
- **Shell scripts use `set -euo pipefail`** — both entrypoints are strict. Errors in volume chown or SSH permission operations are intentionally suppressed with `|| true`.
|
- **Shell scripts use `set -euo pipefail`** — both entrypoints are strict. Errors in volume chown or SSH permission operations are intentionally suppressed with `|| true`.
|
||||||
|
- **Background subagents flag baked ON — experimental, watch for promotion** — `Dockerfile.base` sets `ENV OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`. opencode gates native background subagents behind this flag (`packages/opencode/src/tool/task.ts` fails with `Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` when unset); `oh-my-opencode-slim` V2+ makes background orchestration its **default** workflow, so the omos variant is effectively degraded without it. It's a base ENV (applies to both variants; harmless on plain opencode — only *enables* a capability) and stays runtime-overridable (`-e …=false`). It's counted in the base hash, so editing that line advances `base-latest`. **REMOVAL TRIGGER:** when opencode promotes background subagents out of `EXPERIMENTAL_` (flag renamed or made default), drop the ENV. No upstream roadmap date as of opencode 1.17.15 / omos 2.1.0 (2026-07). Documented in lockstep in README env table, `.env.example`, and asserted by `scripts/smoke-test.sh` (`bg-subagents env baked`).
|
||||||
- **MemPalace `diary_write` anyOf workaround — upstream watch target** — `Dockerfile.base` carries a perl RUN block that strips a root-level `anyOf` from `mempalace_diary_write`'s advertised `inputSchema`. Mempalace 3.3.x/3.4.0 advertise `anyOf: [{required:[entry]},{required:[content]}]`, which Anthropic's tools API (and Codex) reject at session start (`input_schema does not support oneOf, allOf, or anyOf at the top level`), making the whole MCP server fail to load. The workaround is idempotent and self-deactivating: when upstream ships the real fix the regex stops matching and the build prints `WARN: ... upstream may have changed shape` — **that WARN is the signal to delete the RUN block.** Upstream status (last checked **2026-06-14**): issue **#1728 is still OPEN**; PR **#1735 is CLOSED UNMERGED (2026-06-11) — do NOT watch it, it is dead**; PR **#1717 is the current live fix candidate**; mempalace PyPI latest is **3.4.0 (== our pin)**, so **no release contains the fix yet** and the workaround must stay. **Removal trigger:** a mempalace release **> 3.4.0** that actually strips the root-level `anyOf` lands on PyPI — then bump `MEMPALACE_VERSION` (in lockstep with pi-devbox) and drop the RUN block. NOTE: `MEMPALACE_VERSION` (the pip pin) and `MEMPALACE_TOOLKIT_REF` (the git ref for the `mempalace-toolkit` clone) are unrelated despite the shared prefix; do not conflate them.
|
- **MemPalace `diary_write` anyOf workaround — upstream watch target** — `Dockerfile.base` carries a perl RUN block that strips a root-level `anyOf` from `mempalace_diary_write`'s advertised `inputSchema`. Mempalace 3.3.x/3.4.0 advertise `anyOf: [{required:[entry]},{required:[content]}]`, which Anthropic's tools API (and Codex) reject at session start (`input_schema does not support oneOf, allOf, or anyOf at the top level`), making the whole MCP server fail to load. The workaround is idempotent and self-deactivating: when upstream ships the real fix the regex stops matching and the build prints `WARN: ... upstream may have changed shape` — **that WARN is the signal to delete the RUN block.** Upstream status (last checked **2026-06-14**): issue **#1728 is still OPEN**; PR **#1735 is CLOSED UNMERGED (2026-06-11) — do NOT watch it, it is dead**; PR **#1717 is the current live fix candidate**; mempalace PyPI latest is **3.4.0 (== our pin)**, so **no release contains the fix yet** and the workaround must stay. **Removal trigger:** a mempalace release **> 3.4.0** that actually strips the root-level `anyOf` lands on PyPI — then bump `MEMPALACE_VERSION` (in lockstep with pi-devbox) and drop the RUN block. NOTE: `MEMPALACE_VERSION` (the pip pin) and `MEMPALACE_TOOLKIT_REF` (the git ref for the `mempalace-toolkit` clone) are unrelated despite the shared prefix; do not conflate them.
|
||||||
- **MemPalace install path** — installed via `uv tool install` into `/opt/uv-tools/mempalace/`. Both the `mempalace` CLI and the `mempalace-mcp` MCP server binary are shipped as entry points by the mempalace package itself and placed on PATH by uv as shims whose shebangs point at the venv's Python. No hand-rolled wrapper is needed. Do not use `pip install --break-system-packages` — that was the previous approach and has been removed. Do not use `["python3", "-m", "mempalace.mcp_server"]` in `opencode.jsonc` — system Python can't import from the uv venv.
|
- **MemPalace install path** — installed via `uv tool install` into `/opt/uv-tools/mempalace/`. Both the `mempalace` CLI and the `mempalace-mcp` MCP server binary are shipped as entry points by the mempalace package itself and placed on PATH by uv as shims whose shebangs point at the venv's Python. No hand-rolled wrapper is needed. Do not use `pip install --break-system-packages` — that was the previous approach and has been removed. Do not use `["python3", "-m", "mempalace.mcp_server"]` in `opencode.jsonc` — system Python can't import from the uv venv.
|
||||||
- **generate-config.py idempotency** — the script MUST never overwrite an existing `opencode.jsonc` or legacy `opencode.json`. Config persists in the `devbox-opencode-config` named volume; accidentally clobbering that file would destroy hand-edits. The smoke test asserts this.
|
- **generate-config.py idempotency** — the script MUST never overwrite an existing `opencode.jsonc` or legacy `opencode.json`. Config persists in the `devbox-opencode-config` named volume; accidentally clobbering that file would destroy hand-edits. The smoke test asserts this.
|
||||||
|
|||||||
@@ -6,6 +6,97 @@ Tags follow **independent semver** (since `v2.0.0`) — they version *this image
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v2.6.0 — 2026-07-08
|
||||||
|
|
||||||
|
Minor release. Headline: **CLI-toolset parity with the sibling `pi-devbox`
|
||||||
|
image** (adds `yq`, `pandoc`, `graphviz`, `tealdeer`/`tldr`, and the `dot-watch`
|
||||||
|
wrapper) plus **background subagents enabled by default**
|
||||||
|
(`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`), and an opencode bump
|
||||||
|
`1.17.13 → 1.17.15`. Touches `Dockerfile.base` (apt list, new binary, new ENV)
|
||||||
|
so the base image rebuilds (`base-<hash>` advances); the opencode bump rebuilds
|
||||||
|
the variant layers.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **CLI tooling parity with pi-devbox.** Added `yq` (YAML query/transform,
|
||||||
|
pairs with `jq`), `pandoc` (Markdown↔HTML/etc. document converter), and
|
||||||
|
`graphviz` (`dot` diagram rendering) via apt; `tealdeer` (the `tldr` command —
|
||||||
|
quick command examples) as a ~5 MB static musl binary via the floated
|
||||||
|
`TEALDEER_VERSION` arg; and the `dot-watch` wrapper (`rootfs/usr/local/bin/dot-watch`,
|
||||||
|
auto-renders a `.dot` file to PNG on save, graphviz-only). Deliberately did
|
||||||
|
**not** port `socat`, `imagemagick`, or the pi-studio-specific `studio-expose`
|
||||||
|
bridge. smoke-test asserts presence of all five.
|
||||||
|
- **Background subagents enabled by default (`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`).**
|
||||||
|
Baked as a base-level `ENV` in `Dockerfile.base`. opencode gates native
|
||||||
|
background subagents behind this experimental flag; OMOS V2+ makes background
|
||||||
|
orchestration its default workflow, so the omos variant was effectively
|
||||||
|
degraded without it. Overridable at runtime (`-e OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false`).
|
||||||
|
Documented in README env table and `.env.example`; smoke-test asserts the var
|
||||||
|
is present in the image environment. Marked experimental upstream — carries a
|
||||||
|
removal-trigger note in `Dockerfile.base` + AGENTS.md for when opencode
|
||||||
|
promotes it out of experimental (no upstream roadmap date as of opencode
|
||||||
|
1.17.15 / omos 2.1.0).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **opencode `1.17.13` → `1.17.15`.** v1.17.14 adds a code-mode MCP adapter for
|
||||||
|
confined orchestration scripts, hides the `execute` tool unless code mode is
|
||||||
|
enabled, and **fixes paginated MCP tool catalogs losing tool metadata +
|
||||||
|
output-schema validation** (relevant — this image wires the mempalace,
|
||||||
|
gitea-mcp, and context7 MCP servers). v1.17.15 is bugfixes + desktop/TUI UX
|
||||||
|
(Z.ai context-overflow error classification, macOS Sequoia titlebar, model
|
||||||
|
picker). No breaking changes. Release notes:
|
||||||
|
<https://github.com/anomalyco/opencode/releases>.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v2.5.0 — 2026-07-02
|
||||||
|
|
||||||
|
Minor release. Headline: **optional shared/external MemPalace** —
|
||||||
|
`generate-config.py` can now register mempalace as a `remote` MCP endpoint
|
||||||
|
(`MEMPALACE_REMOTE_URL`, optional `MEMPALACE_REMOTE_TOKEN` → Bearer) so one
|
||||||
|
palace can serve pi + opencode + native, instead of a per-container local
|
||||||
|
server; adds `docker-compose.mempalace.yml` for the shared server. Also fixes a
|
||||||
|
`validate.yml` false-red on base-changing commits (skips variant validation and
|
||||||
|
corrects the `rootfs/` base-input detector regex). opencode stays `1.17.13`; the
|
||||||
|
base image rebuilds (`rootfs/…/generate-config.py` changed), so the new config
|
||||||
|
generator lands in `base-<hash>`.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Optional shared/external MemPalace via `MEMPALACE_REMOTE_URL`.** MemPalace
|
||||||
|
can now be pointed at one shared HTTP endpoint instead of each container
|
||||||
|
running its own local server. Set `MEMPALACE_REMOTE_URL=http://<host>:8765/mcp`
|
||||||
|
(optionally `MEMPALACE_REMOTE_TOKEN`, sent as `Authorization: Bearer`) in
|
||||||
|
`.env`; `generate-config.py` then registers the `mempalace` MCP server as a
|
||||||
|
`remote` endpoint instead of the local `mempalace-mcp` stdio command (no local
|
||||||
|
server is spawned, and the `devbox-palace` volume becomes irrelevant). Leaving
|
||||||
|
it unset keeps the default local-per-container palace. Mirrors the same env
|
||||||
|
contract as pi-devbox's `mempalace.ts` bridge, so one palace can serve pi +
|
||||||
|
opencode + native.
|
||||||
|
- **`docker-compose.mempalace.yml`** — an optional shared MemPalace server
|
||||||
|
(`mempalace-mcp --transport http`) that several containers/harnesses can point
|
||||||
|
`MEMPALACE_REMOTE_URL` at. Binds to `127.0.0.1:8765` by default (the HTTP
|
||||||
|
transport is unauthenticated — keep it on a trusted network or behind a proxy).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`validate.yml` no longer reports a false failure on base-changing commits.**
|
||||||
|
`validate-base`/`validate-omos` build variants from the published
|
||||||
|
`base-latest`; when a commit changed base inputs *and* tightened
|
||||||
|
`smoke-test.sh` in lockstep (as v2.4.0's nano/micro did), those jobs
|
||||||
|
hard-failed against the still-stale `base-latest` until the release rebuilt it.
|
||||||
|
The `base-change-warning` job now exports a `base_changed` output and those two
|
||||||
|
jobs **skip** when base inputs changed, deferring full base validation to the
|
||||||
|
release path (`docker-publish-split.yml`). The scary red becomes a neutral skip
|
||||||
|
plus the existing warning. Also fixes the base-input detector itself: its
|
||||||
|
`^(…|rootfs/|…)$` regex anchored the whole alternation, so the `rootfs/` branch
|
||||||
|
only matched a file literally named `rootfs/` and never real paths like
|
||||||
|
`rootfs/usr/…/generate-config.py` — `rootfs/` is now a prefix match, so
|
||||||
|
rootfs-only base changes are detected too.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.4.0 — 2026-07-01
|
## v2.4.0 — 2026-07-01
|
||||||
|
|
||||||
Minor release. Adds two **non-modal editors** (`nano` + `micro`) alongside
|
Minor release. Adds two **non-modal editors** (`nano` + `micro`) alongside
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ RUN apt-get update && \
|
|||||||
openssh-client \
|
openssh-client \
|
||||||
gnupg \
|
gnupg \
|
||||||
jq \
|
jq \
|
||||||
|
yq \
|
||||||
ripgrep \
|
ripgrep \
|
||||||
fd-find \
|
fd-find \
|
||||||
tree \
|
tree \
|
||||||
@@ -72,6 +73,8 @@ RUN apt-get update && \
|
|||||||
python3-pip \
|
python3-pip \
|
||||||
python3-venv \
|
python3-venv \
|
||||||
nano \
|
nano \
|
||||||
|
pandoc \
|
||||||
|
graphviz \
|
||||||
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
|
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -295,6 +298,22 @@ RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64"
|
|||||||
rm -rf /tmp/uv-* && \
|
rm -rf /tmp/uv-* && \
|
||||||
uv --version
|
uv --version
|
||||||
|
|
||||||
|
# tealdeer — Rust port of tldr (community-maintained command examples)
|
||||||
|
# Provides the `tldr` command; ~5 MB static binary, ~135 MB smaller than
|
||||||
|
# the Node tldr global. Same UX as the Node version.
|
||||||
|
ARG TEALDEER_VERSION=latest
|
||||||
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
||||||
|
V="${TEALDEER_VERSION}" && \
|
||||||
|
if [ "$V" = "latest" ]; then \
|
||||||
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/tealdeer-rs/tealdeer/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
||||||
|
fi && \
|
||||||
|
V="${V#v}" && \
|
||||||
|
[ -n "$V" ] && \
|
||||||
|
echo "Installing tealdeer ${V}" && \
|
||||||
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/tealdeer-rs/tealdeer/releases/download/v${V}/tealdeer-linux-${ARCH}-musl" -o /usr/local/bin/tldr && \
|
||||||
|
chmod +x /usr/local/bin/tldr && \
|
||||||
|
tldr --version
|
||||||
|
|
||||||
# ── MemPalace — local-first AI memory system ─────────────────────────
|
# ── MemPalace — local-first AI memory system ─────────────────────────
|
||||||
# Provides semantic search over conversation history via 29 MCP tools.
|
# Provides semantic search over conversation history via 29 MCP tools.
|
||||||
# Always installed in the base (variant-independent). Set
|
# Always installed in the base (variant-independent). Set
|
||||||
@@ -384,6 +403,17 @@ ENV LANGUAGE=en_US:en
|
|||||||
ENV LC_ALL=en_US.UTF-8
|
ENV LC_ALL=en_US.UTF-8
|
||||||
ENV EDITOR=nvim
|
ENV EDITOR=nvim
|
||||||
ENV PATH="/home/developer/.local/bin:/home/developer/.cargo/bin:${PATH}"
|
ENV PATH="/home/developer/.local/bin:/home/developer/.cargo/bin:${PATH}"
|
||||||
|
# Enable opencode's native background subagents. opencode gates this behind an
|
||||||
|
# experimental flag (packages/opencode/src/tool/task.ts errors with
|
||||||
|
# "Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"
|
||||||
|
# when unset). oh-my-opencode-slim V2+ makes background orchestration its DEFAULT
|
||||||
|
# workflow, so the omos variant is effectively broken without this. Baked here as
|
||||||
|
# a base ENV (applies to both variants; harmless for plain opencode — it only
|
||||||
|
# *enables* a capability). Overridable at runtime: -e OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=false.
|
||||||
|
# REMOVAL TRIGGER: when opencode promotes background subagents out of experimental
|
||||||
|
# (flag becomes default / renamed), drop this ENV. No upstream roadmap date as of
|
||||||
|
# opencode 1.17.15 / omos 2.1.0 (2026-07).
|
||||||
|
ENV OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true
|
||||||
|
|
||||||
# ── Node.js (required for opencode/pi/omos at variant build + MCP servers) ──
|
# ── Node.js (required for opencode/pi/omos at variant build + MCP servers) ──
|
||||||
ARG NODE_VERSION=22
|
ARG NODE_VERSION=22
|
||||||
@@ -466,6 +496,7 @@ COPY rootfs/home/developer/.gitignore_global /etc/skel-devbox/.gitignore_global
|
|||||||
|
|
||||||
# ── Entrypoint ────────────────────────────────────────────────────────
|
# ── Entrypoint ────────────────────────────────────────────────────────
|
||||||
COPY rootfs/usr/local/lib/opencode-devbox/ /usr/local/lib/opencode-devbox/
|
COPY rootfs/usr/local/lib/opencode-devbox/ /usr/local/lib/opencode-devbox/
|
||||||
|
COPY rootfs/usr/local/bin/dot-watch /usr/local/bin/dot-watch
|
||||||
# Image-baked skills + harness instruction. Under /usr/local so a named volume
|
# Image-baked skills + harness instruction. Under /usr/local so a named volume
|
||||||
# over a home dir (e.g. devbox-opencode-config on ~/.config/opencode) can't
|
# over a home dir (e.g. devbox-opencode-config on ~/.config/opencode) can't
|
||||||
# shadow them; entrypoint-user.sh links them into ~/.agents/skills/ and
|
# shadow them; entrypoint-user.sh links them into ~/.agents/skills/ and
|
||||||
@@ -475,6 +506,7 @@ COPY rootfs/usr/local/share/opencode-devbox/ /usr/local/share/opencode-devbox/
|
|||||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh
|
COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \
|
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \
|
||||||
|
/usr/local/bin/dot-watch \
|
||||||
/usr/local/lib/opencode-devbox/*.py
|
/usr/local/lib/opencode-devbox/*.py
|
||||||
|
|
||||||
# Start as root — entrypoint adjusts UID/GID then drops to developer
|
# Start as root — entrypoint adjusts UID/GID then drops to developer
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ ARG USER_NAME=developer
|
|||||||
# edit, so the cache-hit class of bug that bit pi-devbox v0.74.0..
|
# edit, so the cache-hit class of bug that bit pi-devbox v0.74.0..
|
||||||
# v0.75.5 cannot apply here.
|
# v0.75.5 cannot apply here.
|
||||||
ARG INSTALL_OPENCODE=true
|
ARG INSTALL_OPENCODE=true
|
||||||
ARG OPENCODE_VERSION=1.17.13
|
ARG OPENCODE_VERSION=1.17.15
|
||||||
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
||||||
NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
||||||
opencode --version ; \
|
opencode --version ; \
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ docker compose exec -u developer devbox aws --version
|
|||||||
| `OMOS_TMUX` | Enable tmux pane integration for OMOS | `false` |
|
| `OMOS_TMUX` | Enable tmux pane integration for OMOS | `false` |
|
||||||
| `OMOS_SKILLS` | Symlink bundled OMOS skills from the image into `~/.agents/skills/` each start | `true` |
|
| `OMOS_SKILLS` | Symlink bundled OMOS skills from the image into `~/.agents/skills/` each start | `true` |
|
||||||
| `OMOS_RESET` | Force regenerate OMOS config on next start (does not affect skills) | `false` |
|
| `OMOS_RESET` | Force regenerate OMOS config on next start (does not affect skills) | `false` |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | Enable opencode's native background subagents. Baked on in the image because OMOS V2+ default orchestration depends on it. Set `false` to opt out. opencode marks this **experimental** — see [AGENTS.md](AGENTS.md) removal trigger | `true` |
|
||||||
| `SKILLSET_CONTAINER_PATH` | Path to skillset repo inside container (for auto-deploy when not at /workspace/skillset) | Auto-detect |
|
| `SKILLSET_CONTAINER_PATH` | Path to skillset repo inside container (for auto-deploy when not at /workspace/skillset) | Auto-detect |
|
||||||
|
|
||||||
### Reaching your LAN from the container
|
### Reaching your LAN from the container
|
||||||
@@ -594,6 +595,28 @@ Add mempalace as an MCP server in your `opencode.jsonc` (inside `~/.config/openc
|
|||||||
|
|
||||||
This gives opencode access to 29 MCP tools for searching memory, querying the knowledge graph, managing wings/rooms/drawers, and agent diaries.
|
This gives opencode access to 29 MCP tools for searching memory, querying the knowledge graph, managing wings/rooms/drawers, and agent diaries.
|
||||||
|
|
||||||
|
### Sharing one palace across containers (external server)
|
||||||
|
|
||||||
|
By default each container runs its own local `mempalace-mcp`. To share **one**
|
||||||
|
palace across several containers/harnesses (pi + opencode + native), run the
|
||||||
|
bundled server and point clients at it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.mempalace.yml up -d # serves mempalace-mcp over HTTP
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set in each client's `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MEMPALACE_REMOTE_URL=http://<reachable-host>:8765/mcp
|
||||||
|
# MEMPALACE_REMOTE_TOKEN=... # optional; sent as Authorization: Bearer
|
||||||
|
```
|
||||||
|
|
||||||
|
`generate-config.py` then registers mempalace as a `remote` MCP endpoint instead
|
||||||
|
of the local stdio command — no local `mempalace-mcp` is spawned and the
|
||||||
|
`devbox-palace` volume is irrelevant. ⚠ The HTTP transport is **unauthenticated**;
|
||||||
|
keep it on a trusted network or behind a reverse proxy that enforces the token.
|
||||||
|
|
||||||
### Basic usage
|
### Basic usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -813,7 +836,11 @@ Container (Debian trixie)
|
|||||||
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun)
|
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun)
|
||||||
├── AWS CLI v2 (SSO + Bedrock auth)
|
├── AWS CLI v2 (SSO + Bedrock auth)
|
||||||
├── neovim 0.12, nano, micro, tmux, htop, bat, eza, zoxide, uv, rustup, make, gcc, g++, rsync
|
├── neovim 0.12, nano, micro, tmux, htop, bat, eza, zoxide, uv, rustup, make, gcc, g++, rsync
|
||||||
├── git, git-crypt, age, gitleaks, ssh, ripgrep, fd, fzf, jq, curl, tree
|
├── git, git-crypt, age, gitleaks, ssh, ripgrep, fd, fzf, jq, yq, curl, tree
|
||||||
|
├── pandoc (Markdown↔HTML/PDF/etc. conversion)
|
||||||
|
├── graphviz (dot diagram rendering)
|
||||||
|
├── tldr (tealdeer — quick command examples)
|
||||||
|
├── dot-watch (auto-render .dot to PNG on save)
|
||||||
├── Node.js (for MCP servers)
|
├── Node.js (for MCP servers)
|
||||||
├── Bun (optional — included with oh-my-opencode-slim)
|
├── Bun (optional — included with oh-my-opencode-slim)
|
||||||
├── entrypoint.sh (UID adjustment, git config, provider setup)
|
├── entrypoint.sh (UID adjustment, git config, provider setup)
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Shared MemPalace server (optional) — one palace for many clients.
|
||||||
|
#
|
||||||
|
# Runs `mempalace-mcp` over HTTP so several containers/harnesses (pi +
|
||||||
|
# opencode + native) can share ONE palace instead of each keeping its own.
|
||||||
|
# Point every client at it by setting, in that client's .env:
|
||||||
|
#
|
||||||
|
# MEMPALACE_REMOTE_URL=http://<reachable-host>:8765/mcp
|
||||||
|
#
|
||||||
|
# (see .env.example). When set, the client connects over HTTP and does NOT
|
||||||
|
# spawn its own local mempalace-mcp.
|
||||||
|
#
|
||||||
|
# Start: docker compose -f docker-compose.mempalace.yml up -d
|
||||||
|
# Stop: docker compose -f docker-compose.mempalace.yml down
|
||||||
|
# Logs: docker compose -f docker-compose.mempalace.yml logs -f
|
||||||
|
#
|
||||||
|
# Why reuse the devbox image? mempalace-mcp is already installed in it, and
|
||||||
|
# reusing it GUARANTEES the server's mempalace version matches the clients'
|
||||||
|
# (both are pinned by the same image build). Override with a slimmer image via
|
||||||
|
# MEMPALACE_SERVER_IMAGE if you prefer (it must provide `mempalace-mcp`).
|
||||||
|
#
|
||||||
|
# ⚠ SECURITY: mempalace-mcp's HTTP transport has NO authentication of its own.
|
||||||
|
# Do NOT expose port 8765 to an untrusted network. The default below binds to
|
||||||
|
# 127.0.0.1 (host loopback) only. To let sibling containers reach it, either
|
||||||
|
# attach them to the shared `mempalace-net` network (container-to-container, no
|
||||||
|
# host port needed — use http://mempalace-server:8765/mcp), or front it with a
|
||||||
|
# reverse proxy that enforces MEMPALACE_REMOTE_TOKEN as `Authorization: Bearer`.
|
||||||
|
|
||||||
|
name: mempalace-server
|
||||||
|
|
||||||
|
services:
|
||||||
|
mempalace:
|
||||||
|
image: ${MEMPALACE_SERVER_IMAGE:-joakimp/opencode-devbox:latest}
|
||||||
|
container_name: mempalace-server
|
||||||
|
# Bypass the devbox entrypoint (dev-shell/LAN/config setup) and run the
|
||||||
|
# HTTP MCP server directly. HOME + explicit --palace pin the data path so
|
||||||
|
# it does not depend on the image's default user/HOME. Runs as root so it
|
||||||
|
# can initialise the fresh named volume; the volume is dedicated to this
|
||||||
|
# server (clients reach it over HTTP, never by mounting it).
|
||||||
|
entrypoint: []
|
||||||
|
user: "0:0"
|
||||||
|
environment:
|
||||||
|
- HOME=/data
|
||||||
|
command:
|
||||||
|
- mempalace-mcp
|
||||||
|
- --transport
|
||||||
|
- http
|
||||||
|
- --host
|
||||||
|
- "0.0.0.0"
|
||||||
|
- --port
|
||||||
|
- "8765"
|
||||||
|
- --palace
|
||||||
|
- /data/.mempalace
|
||||||
|
restart: unless-stopped
|
||||||
|
# Loopback-only by default (see SECURITY note). Use "8765:8765" to expose on
|
||||||
|
# all host interfaces, or drop `ports:` entirely and rely on mempalace-net.
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8765:8765"
|
||||||
|
volumes:
|
||||||
|
# The shared palace data — precious; back this up.
|
||||||
|
- mempalace-shared:/data/.mempalace
|
||||||
|
# Embedding-model cache (~79 MB, disposable) so search does not re-download.
|
||||||
|
- mempalace-shared-chroma:/data/.cache/chroma
|
||||||
|
networks:
|
||||||
|
- mempalace-net
|
||||||
|
healthcheck:
|
||||||
|
# A tools/list round-trip proves the server is answering MCP (python3 is
|
||||||
|
# always present — mempalace itself is a python tool in the image).
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- python3
|
||||||
|
- -c
|
||||||
|
- "import urllib.request,json; d=json.dumps({'jsonrpc':'2.0','id':1,'method':'tools/list','params':{}}).encode(); r=urllib.request.Request('http://127.0.0.1:8765/mcp',data=d,headers={'Content-Type':'application/json','Accept':'application/json'}); urllib.request.urlopen(r,timeout=5).read()"
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mempalace-shared:
|
||||||
|
mempalace-shared-chroma:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
mempalace-net:
|
||||||
|
name: mempalace-net
|
||||||
@@ -38,6 +38,10 @@ services:
|
|||||||
- TERM=xterm-256color
|
- TERM=xterm-256color
|
||||||
- GITEA_ACCESS_TOKEN=${GITEA_ACCESS_TOKEN:-}
|
- GITEA_ACCESS_TOKEN=${GITEA_ACCESS_TOKEN:-}
|
||||||
- GITEA_HOST=${GITEA_HOST:-}
|
- GITEA_HOST=${GITEA_HOST:-}
|
||||||
|
# MemPalace transport: empty = LOCAL per-container palace (default);
|
||||||
|
# set MEMPALACE_REMOTE_URL to share one external palace. See .env.example.
|
||||||
|
- MEMPALACE_REMOTE_URL=${MEMPALACE_REMOTE_URL:-}
|
||||||
|
- MEMPALACE_REMOTE_TOKEN=${MEMPALACE_REMOTE_TOKEN:-}
|
||||||
volumes:
|
volumes:
|
||||||
# Host workspace — user's project directory
|
# Host workspace — user's project directory
|
||||||
- ${WORKSPACE_PATH:-~/src}:/workspace
|
- ${WORKSPACE_PATH:-~/src}:/workspace
|
||||||
@@ -73,7 +77,9 @@ services:
|
|||||||
# Persist uv data (Python installs)
|
# Persist uv data (Python installs)
|
||||||
- devbox-uv:/home/developer/.local/share/uv
|
- devbox-uv:/home/developer/.local/share/uv
|
||||||
|
|
||||||
# Optional: persist MemPalace data (conversation memory, knowledge graph)
|
# Optional: persist MemPalace data (conversation memory, knowledge graph).
|
||||||
|
# LOCAL palace only (default). In EXTERNAL mode (MEMPALACE_REMOTE_URL set)
|
||||||
|
# the shared server owns the data, so this volume is irrelevant.
|
||||||
# - devbox-palace:/home/developer/.mempalace
|
# - devbox-palace:/home/developer/.mempalace
|
||||||
|
|
||||||
# Optional: persist ChromaDB embedding model cache (~79 MB)
|
# Optional: persist ChromaDB embedding model cache (~79 MB)
|
||||||
|
|||||||
+10
-1
@@ -36,6 +36,12 @@ services:
|
|||||||
- GITHUB_PERSONAL_ACCESS_TOKEN=${GITHUB_PERSONAL_ACCESS_TOKEN:-}
|
- GITHUB_PERSONAL_ACCESS_TOKEN=${GITHUB_PERSONAL_ACCESS_TOKEN:-}
|
||||||
- GITEA_ACCESS_TOKEN=${GITEA_ACCESS_TOKEN:-}
|
- GITEA_ACCESS_TOKEN=${GITEA_ACCESS_TOKEN:-}
|
||||||
- GITEA_HOST=${GITEA_HOST:-}
|
- GITEA_HOST=${GITEA_HOST:-}
|
||||||
|
# MemPalace transport: leave empty for a LOCAL per-container palace
|
||||||
|
# (default). Set MEMPALACE_REMOTE_URL to share ONE external MemPalace
|
||||||
|
# across containers/harnesses; MEMPALACE_REMOTE_TOKEN is an optional
|
||||||
|
# bearer token. See .env.example.
|
||||||
|
- MEMPALACE_REMOTE_URL=${MEMPALACE_REMOTE_URL:-}
|
||||||
|
- MEMPALACE_REMOTE_TOKEN=${MEMPALACE_REMOTE_TOKEN:-}
|
||||||
volumes:
|
volumes:
|
||||||
# Host workspace — mount your project here
|
# Host workspace — mount your project here
|
||||||
- ${WORKSPACE_PATH:-.}:/workspace
|
- ${WORKSPACE_PATH:-.}:/workspace
|
||||||
@@ -128,7 +134,10 @@ services:
|
|||||||
- devbox-nvim-data:/home/developer/.local/share/nvim
|
- devbox-nvim-data:/home/developer/.local/share/nvim
|
||||||
|
|
||||||
# Optional: persist MemPalace data (conversation memory, knowledge graph,
|
# Optional: persist MemPalace data (conversation memory, knowledge graph,
|
||||||
# embeddings). Without this, palace data is lost on container recreation.
|
# embeddings). Applies to the LOCAL palace only (the default, when
|
||||||
|
# MEMPALACE_REMOTE_URL is unset). Without this, palace data is lost on
|
||||||
|
# container recreation. In EXTERNAL mode the shared server owns the data,
|
||||||
|
# so this volume is irrelevant.
|
||||||
# - devbox-palace:/home/developer/.mempalace
|
# - devbox-palace:/home/developer/.mempalace
|
||||||
|
|
||||||
# Optional: persist ChromaDB embedding model cache (~79 MB, downloaded on
|
# Optional: persist ChromaDB embedding model cache (~79 MB, downloaded on
|
||||||
|
|||||||
Executable
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# dot-watch — auto-rerender a graphviz .dot file to PNG on every save.
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS
|
||||||
|
# pi-studio renders mermaid natively but has no graphviz/DOT renderer.
|
||||||
|
# Its markdown preview DOES render local image links (.png/.jpg/.gif/.webp),
|
||||||
|
# and the editor offers "refresh from disk". This helper closes the loop:
|
||||||
|
# edit a .dot file -> dot-watch regenerates <name>.png -> hit refresh in
|
||||||
|
# Studio to see the update. Uses mtime polling (no inotify dependency,
|
||||||
|
# which isn't in the trixie-slim base).
|
||||||
|
#
|
||||||
|
# USAGE
|
||||||
|
# dot-watch <file.dot> [layout] [dpi]
|
||||||
|
# layout: dot|neato|fdp|circo|twopi (default: dot)
|
||||||
|
# dpi: output resolution (default: 150)
|
||||||
|
# env: DOT_WATCH_INTERVAL=<seconds> poll interval (default: 1)
|
||||||
|
#
|
||||||
|
# EXAMPLES
|
||||||
|
# dot-watch /workspace/graph.dot
|
||||||
|
# dot-watch graph.dot neato 200
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SRC="${1:?usage: dot-watch <file.dot> [layout] [dpi]}"
|
||||||
|
LAYOUT="${2:-dot}"
|
||||||
|
DPI="${3:-150}"
|
||||||
|
|
||||||
|
[[ -f "$SRC" ]] || { echo "error: no such file: $SRC" >&2; exit 1; }
|
||||||
|
command -v "$LAYOUT" >/dev/null || { echo "error: layout engine '$LAYOUT' not found" >&2; exit 1; }
|
||||||
|
|
||||||
|
OUT="${SRC%.dot}.png"
|
||||||
|
INTERVAL="${DOT_WATCH_INTERVAL:-1}" # seconds between polls
|
||||||
|
ERRLOG="$(mktemp -t dot-watch.XXXXXX.err)"
|
||||||
|
trap 'rm -f "$ERRLOG"' EXIT
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if "$LAYOUT" -Tpng -Gdpi="$DPI" "$SRC" -o "$OUT" 2> "$ERRLOG"; then
|
||||||
|
printf '[%s] rendered -> %s\n' "$(date +%H:%M:%S)" "$OUT"
|
||||||
|
else
|
||||||
|
printf '[%s] DOT error:\n' "$(date +%H:%M:%S)"
|
||||||
|
sed 's/^/ /' "$ERRLOG"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# portable mtime (GNU stat, fallback to BSD stat)
|
||||||
|
mtime() { stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null; }
|
||||||
|
|
||||||
|
echo "watching $SRC ($LAYOUT, ${DPI}dpi) -> $OUT [Ctrl-C to stop]"
|
||||||
|
render
|
||||||
|
last="$(mtime "$SRC")"
|
||||||
|
while true; do
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
[[ -f "$SRC" ]] || continue
|
||||||
|
now="$(mtime "$SRC")"
|
||||||
|
if [[ "$now" != "$last" ]]; then
|
||||||
|
last="$now"
|
||||||
|
render
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -16,9 +16,13 @@ Environment variables:
|
|||||||
OPENCODE_MODEL Optional. Overrides the provider default model.
|
OPENCODE_MODEL Optional. Overrides the provider default model.
|
||||||
AWS_REGION Bedrock only. Default: us-east-1.
|
AWS_REGION Bedrock only. Default: us-east-1.
|
||||||
AWS_PROFILE Bedrock only. Default: default.
|
AWS_PROFILE Bedrock only. Default: default.
|
||||||
|
MEMPALACE_REMOTE_URL Optional. Point MemPalace at a shared HTTP endpoint
|
||||||
|
instead of spawning a local stdio server.
|
||||||
|
MEMPALACE_REMOTE_TOKEN Optional. Bearer token for MEMPALACE_REMOTE_URL.
|
||||||
|
|
||||||
MCP servers are auto-registered for tools detected on PATH:
|
MCP servers are auto-registered for tools detected on PATH:
|
||||||
- mempalace (if installed) — enabled
|
- mempalace — LOCAL stdio if `mempalace-mcp` is installed, or EXTERNAL
|
||||||
|
(remote HTTP) when MEMPALACE_REMOTE_URL is set (shared palace)
|
||||||
- gitea-mcp (if installed) — registered but disabled by default
|
- gitea-mcp (if installed) — registered but disabled by default
|
||||||
|
|
||||||
Output path: $HOME/.config/opencode/opencode.jsonc
|
Output path: $HOME/.config/opencode/opencode.jsonc
|
||||||
@@ -80,13 +84,27 @@ def register_mcp_servers(config: dict) -> list[str]:
|
|||||||
"""
|
"""
|
||||||
servers: dict[str, dict] = {}
|
servers: dict[str, dict] = {}
|
||||||
|
|
||||||
# MemPalace — local-first AI memory (if installed).
|
# MemPalace — AI memory. Two modes, same env contract as the mempalace.ts
|
||||||
# `mempalace-mcp` is the entry-point binary shipped by the mempalace
|
# pi extension (mempalace-toolkit), so one shared MemPalace can serve pi +
|
||||||
# Python package. `uv tool install mempalace` places it on PATH as a
|
# opencode + native:
|
||||||
# shim whose shebang points at the isolated venv's Python, so system
|
# LOCAL (default): stdio subprocess. `mempalace-mcp` is the entry-point
|
||||||
# `python3 -m mempalace.mcp_server` (which would fail — system
|
# binary shipped by the mempalace Python package. `uv tool install
|
||||||
# python3 can't import from the uv venv) is unnecessary here.
|
# mempalace` places it on PATH as a shim whose shebang points at the
|
||||||
if shutil.which("mempalace-mcp"):
|
# isolated venv's Python, so system `python3 -m mempalace.mcp_server`
|
||||||
|
# (which would fail — system python3 can't import from the uv venv) is
|
||||||
|
# unnecessary here.
|
||||||
|
# EXTERNAL: set MEMPALACE_REMOTE_URL to a shared MemPalace HTTP endpoint
|
||||||
|
# (e.g. http://mempalace.lan:8765/mcp). MEMPALACE_REMOTE_TOKEN, if set,
|
||||||
|
# becomes a Bearer auth header. No local mempalace-mcp is spawned, so
|
||||||
|
# the binary need not even be installed in this mode.
|
||||||
|
remote_url = os.environ.get("MEMPALACE_REMOTE_URL", "").strip()
|
||||||
|
if remote_url:
|
||||||
|
entry: dict = {"type": "remote", "url": remote_url}
|
||||||
|
token = os.environ.get("MEMPALACE_REMOTE_TOKEN", "").strip()
|
||||||
|
if token:
|
||||||
|
entry["headers"] = {"Authorization": f"Bearer {token}"}
|
||||||
|
servers["mempalace"] = entry
|
||||||
|
elif shutil.which("mempalace-mcp"):
|
||||||
servers["mempalace"] = {
|
servers["mempalace"] = {
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"command": ["mempalace-mcp"],
|
"command": ["mempalace-mcp"],
|
||||||
|
|||||||
@@ -131,12 +131,22 @@ run "fzf" "fzf --version"
|
|||||||
run "fd" "fd --version"
|
run "fd" "fd --version"
|
||||||
run "rg" "rg --version | head -1"
|
run "rg" "rg --version | head -1"
|
||||||
run "jq" "jq --version"
|
run "jq" "jq --version"
|
||||||
|
run "yq" "yq --version"
|
||||||
run "git-crypt" "git-crypt --version | head -1"
|
run "git-crypt" "git-crypt --version | head -1"
|
||||||
run "gitleaks" "gitleaks version"
|
run "gitleaks" "gitleaks version"
|
||||||
run "aws" "aws --version"
|
run "aws" "aws --version"
|
||||||
run "gitea-mcp" "gitea-mcp --version"
|
run "gitea-mcp" "gitea-mcp --version"
|
||||||
run "gosu" "gosu --version"
|
run "gosu" "gosu --version"
|
||||||
run "tmux" "tmux -V"
|
run "tmux" "tmux -V"
|
||||||
|
run "pandoc" "pandoc --version | head -1"
|
||||||
|
run "graphviz (dot)" "dot -V"
|
||||||
|
run "tldr (tealdeer)" "tldr --version"
|
||||||
|
run "dot-watch" "test -x /usr/local/bin/dot-watch && bash -n /usr/local/bin/dot-watch && echo ok"
|
||||||
|
|
||||||
|
# Background subagents: opencode gates them behind this experimental env var,
|
||||||
|
# and OMOS V2+ default orchestration depends on it. Baked ON as an ENV in
|
||||||
|
# Dockerfile.base — assert it's present in the image environment (both variants).
|
||||||
|
run_expect "bg-subagents env baked" "printenv OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS" "true"
|
||||||
|
|
||||||
# SSH ControlMaster baked defaults: the config file must exist (image-level)
|
# SSH ControlMaster baked defaults: the config file must exist (image-level)
|
||||||
# and ssh -G must report ControlPath rooted at /tmp/sshcm/ for an arbitrary
|
# and ssh -G must report ControlPath rooted at /tmp/sshcm/ for an arbitrary
|
||||||
|
|||||||
Reference in New Issue
Block a user