Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .deva.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ EPHEMERAL=false
# Hybrid Agent Setup:
#
# Hybrid is the DEFAULT. deva walks every populated subdir under
# ~/.config/deva/ (claude, codex, gemini, grok, kimi) and mounts each agent's
# ~/.config/deva/ (claude, codex, gemini, grok, kimi, opencode) and mounts each agent's
# canonical entries into the container. Populated = you either
# hand-created the subdir or autolink symlinked it from legacy
# ~/.claude, ~/.codex, ~/.gemini, ~/.grok, ~/.kimi-code on first run.
# ~/.claude, ~/.codex, ~/.gemini, ~/.grok, ~/.kimi-code, or the opencode
# XDG dirs (~/.config/opencode + ~/.local/share/opencode +
# ~/.local/state/opencode) on first run.
#
# No .deva entries required for the common case. To opt OUT of
# hybrid for a single invocation, pass --config-home DIR to
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ jobs:
--build-arg GEMINI_CLI_VERSION="${{ steps.pins.outputs.gemini_cli_version }}" \
--build-arg GROK_CLI_VERSION="${{ steps.pins.outputs.grok_cli_version }}" \
--build-arg KIMI_CODE_VERSION="${{ steps.pins.outputs.kimi_code_version }}" \
--build-arg OPENCODE_VERSION="${{ steps.pins.outputs.opencode_version }}" \
--build-arg CCX_VERSION="${{ steps.pins.outputs.ccx_version }}" \
--build-arg COPILOT_API_VERSION="${{ steps.pins.outputs.copilot_api_version }}" \
.
Expand All @@ -104,6 +105,7 @@ jobs:
GEMINI_CLI_VERSION="${{ steps.pins.outputs.gemini_cli_version }}" \
GROK_CLI_VERSION="${{ steps.pins.outputs.grok_cli_version }}" \
KIMI_CODE_VERSION="${{ steps.pins.outputs.kimi_code_version }}" \
OPENCODE_VERSION="${{ steps.pins.outputs.opencode_version }}" \
CCX_VERSION="${{ steps.pins.outputs.ccx_version }}" \
COPILOT_API_VERSION="${{ steps.pins.outputs.copilot_api_version }}" \
PLAYWRIGHT_VERSION="${{ steps.pins.outputs.playwright_version }}" \
Expand All @@ -130,6 +132,7 @@ jobs:
deva.sh gemini -Q -- --version
deva.sh grok -Q -- --version
deva.sh kimi -Q -- --version
deva.sh opencode -Q -- --version

- name: Smoke Claude --chrome mount assembly
shell: bash
Expand Down Expand Up @@ -183,6 +186,14 @@ jobs:
DEVA_DOCKER_TAG=ci \
./scripts/test-kimi-auth.sh

- name: Smoke opencode auth wiring
shell: bash
run: |
set -euo pipefail
DEVA_DOCKER_IMAGE=deva-smoke \
DEVA_DOCKER_TAG=ci \
./scripts/test-opencode-auth.sh

- name: Smoke version targets
shell: bash
run: |
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/nightly-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
gemini_cli_version: ${{ steps.versions.outputs.gemini_cli_version }}
grok_cli_version: ${{ steps.versions.outputs.grok_cli_version }}
kimi_code_version: ${{ steps.versions.outputs.kimi_code_version }}
opencode_version: ${{ steps.versions.outputs.opencode_version }}
ccx_version: ${{ steps.versions.outputs.ccx_version }}
copilot_api_version: ${{ steps.versions.outputs.copilot_api_version }}
steps:
Expand Down Expand Up @@ -84,6 +85,7 @@ jobs:
- Gemini CLI: \`${{ steps.versions.outputs.gemini_cli_version }}\`
- Grok CLI: \`${{ steps.versions.outputs.grok_cli_version }}\`
- Kimi Code: \`${{ steps.versions.outputs.kimi_code_version }}\`
- opencode: \`${{ steps.versions.outputs.opencode_version }}\`
- ccx: \`${{ steps.versions.outputs.ccx_version }}\`
- Copilot API: \`${{ steps.versions.outputs.copilot_api_version }}\`
- Stamp: \`${{ steps.versions.outputs.stamp }}\`
Expand Down Expand Up @@ -146,6 +148,7 @@ jobs:
GEMINI_CLI_VERSION=${{ needs.resolve-versions.outputs.gemini_cli_version }}
GROK_CLI_VERSION=${{ needs.resolve-versions.outputs.grok_cli_version }}
KIMI_CODE_VERSION=${{ needs.resolve-versions.outputs.kimi_code_version }}
OPENCODE_VERSION=${{ needs.resolve-versions.outputs.opencode_version }}
CCX_VERSION=${{ needs.resolve-versions.outputs.ccx_version }}
COPILOT_API_VERSION=${{ needs.resolve-versions.outputs.copilot_api_version }}

Expand Down Expand Up @@ -201,6 +204,7 @@ jobs:
GEMINI_CLI_VERSION=${{ needs.resolve-versions.outputs.gemini_cli_version }}
GROK_CLI_VERSION=${{ needs.resolve-versions.outputs.grok_cli_version }}
KIMI_CODE_VERSION=${{ needs.resolve-versions.outputs.kimi_code_version }}
OPENCODE_VERSION=${{ needs.resolve-versions.outputs.opencode_version }}
CCX_VERSION=${{ needs.resolve-versions.outputs.ccx_version }}
PLAYWRIGHT_VERSION=${{ needs.load-version-pins.outputs.playwright_version }}
RUST_TOOLCHAINS=${{ needs.load-version-pins.outputs.rust_toolchains }}
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
gemini_cli_version: ${{ steps.pins.outputs.gemini_cli_version }}
grok_cli_version: ${{ steps.pins.outputs.grok_cli_version }}
kimi_code_version: ${{ steps.pins.outputs.kimi_code_version }}
opencode_version: ${{ steps.pins.outputs.opencode_version }}
ccx_version: ${{ steps.pins.outputs.ccx_version }}
copilot_api_version: ${{ steps.pins.outputs.copilot_api_version }}
playwright_version: ${{ steps.pins.outputs.playwright_version }}
Expand Down Expand Up @@ -89,6 +90,7 @@ jobs:
- Gemini CLI: \`${{ steps.pins.outputs.gemini_cli_version }}\`
- Grok CLI: \`${{ steps.pins.outputs.grok_cli_version }}\`
- Kimi Code: \`${{ steps.pins.outputs.kimi_code_version }}\`
- opencode: \`${{ steps.pins.outputs.opencode_version }}\`
- ccx: \`${{ steps.pins.outputs.ccx_version }}\`
- Copilot API: \`${{ steps.pins.outputs.copilot_api_version }}\`
- Playwright: \`${{ steps.pins.outputs.playwright_version }}\`
Expand Down Expand Up @@ -154,6 +156,7 @@ jobs:
GEMINI_CLI_VERSION=${{ needs.load-version-pins.outputs.gemini_cli_version }}
GROK_CLI_VERSION=${{ needs.load-version-pins.outputs.grok_cli_version }}
KIMI_CODE_VERSION=${{ needs.load-version-pins.outputs.kimi_code_version }}
OPENCODE_VERSION=${{ needs.load-version-pins.outputs.opencode_version }}
CCX_VERSION=${{ needs.load-version-pins.outputs.ccx_version }}
COPILOT_API_VERSION=${{ needs.load-version-pins.outputs.copilot_api_version }}

Expand Down Expand Up @@ -211,6 +214,7 @@ jobs:
GEMINI_CLI_VERSION=${{ needs.load-version-pins.outputs.gemini_cli_version }}
GROK_CLI_VERSION=${{ needs.load-version-pins.outputs.grok_cli_version }}
KIMI_CODE_VERSION=${{ needs.load-version-pins.outputs.kimi_code_version }}
OPENCODE_VERSION=${{ needs.load-version-pins.outputs.opencode_version }}
CCX_VERSION=${{ needs.load-version-pins.outputs.ccx_version }}
PLAYWRIGHT_VERSION=${{ needs.load-version-pins.outputs.playwright_version }}
RUST_TOOLCHAINS=${{ needs.load-version-pins.outputs.rust_toolchains }}
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- opencode (opencode.ai) as the 6th agent: `deva.sh opencode` (#541).
First XDG-native agent — auth/config persist across three nested
canonical entries (`.config/opencode`, `.local/share/opencode`,
`.local/state/opencode`) instead of one dot-dir; cache stays
container-local. Default `oauth` mounts the trio (auth.json carries
the subscription); `--auth-with api-key` passes `OPENCODE_API_KEY`
and mounts nothing. Container-is-the-sandbox via `OPENCODE_PERMISSION`
(opencode is already allow-by-default in-workspace); autoupdate off,
image pins `opencode-ai` (OPENCODE_VERSION). `--trace` rejected until
cctrace ships an opencode profile (thevibeworks/cctrace#89)

### Changed
- `make versions-up` is now the whole pin flow: builds core, main,
rust, and cloak at latest upstream, writes versions.env from the
exact versions built (no post-build re-fetch race), and opens the
pin PR on chore/version-pins-refresh; PR=0 keeps the bump local
(#538)
- README core-value section rewritten around operator outcomes —
full-speed YOLO, host out of vendor code's reach, explicit boundary,
identity as a launch flag, official CLIs stock, zero workflow tax
Expand Down
10 changes: 10 additions & 0 deletions DEV-LOGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
- Minimal markdown markers, no unnecessary formatting, minimal emojis.
- Reference issue numbers in the format `#<issue-number>` for easy linking.

# [2026-08-06] Dev Log: opencode as the 6th agent #541
- Why: opencode's multi-provider client + zen/go subscription is worth having in the fleet, but it's the first XDG-native agent — state spans ~/.config/opencode, ~/.local/share/opencode (auth.json, sqlite session db), ~/.local/state/opencode instead of one dot-dir, which the canonical-entry machinery assumed.
- What: agents/opencode.sh (oauth mounts the XDG trio; api-key passes OPENCODE_API_KEY, mounts nothing, blank-overlays auth.json — it outranks the env key). agent_canonical_basenames grows nested entries; mount path needed no change (docker handles nested targets), but status display, autolink, config-home scaffold/warning, and the $HOME fallback each got opencode arms. Sandbox posture: opencode is allow-by-default in-workspace, so OPENCODE_PERMISSION only unlocks external_directory/doom_loop/.env-read asks; OPENCODE_DISABLE_AUTOUPDATE=1 since the image pins opencode-ai (OPENCODE_VERSION through versions.env, the round-trip heredoc, Makefile, Dockerfiles, CI/release/nightly, version-upgrade + registry). Cache dir deliberately not mounted (models.json + self-updater bin). --trace rejected with a pointer to thevibeworks/cctrace#89 (multi-provider wire needs per-host dialects; proposal filed). Tests: scripts/test-opencode-auth.sh (21 asserts, hermetic scratch-HOME dry-run), fixtures updated in test-install-agent-tooling, test_release_utils, tests/version-upgrade.
- Result: `deva.sh opencode` with subscription or service-account key; host prep = the three XDG dirs (cache excluded). Open: cctrace profile (cctrace#89), in-container device-code login flow untested against a real build until the next image bake.

# [2026-08-05] Dev Log: versions-up pins what it built and PRs the bump #538
- Why: team flow was `versions-up && versions-pin && build-cloak` plus a hand-written PR. versions-pin re-fetches upstream AFTER the build, so pins could drift to a version never build-tested (claude-code ships several times a day). Cloak wasn't covered by versions-up at all.
- What: version-upgrade.sh now builds cloak too (cloakbrowser wrapper resolved from npm, joins the manifest and --only), then writes versions.env from the exact resolved build args — write_version_pins moved to shared version-pins.sh, round-trip guard intact — and calls new scripts/versions-pr.sh: commit in a temp worktree detached at origin/main (push HEAD:chore/version-pins-refresh, user checkout untouched), gh pr create unless one is already open. PR=0 opts out; a failed PR stage warns and exits 0 — a dead push must not sink a finished build. Tests: cloak build args, pin write, outage no-write, PR soft-fail in tests/version-upgrade.sh; hermetic real-git-fake-gh suite in tests/versions-pr.sh.
- Result: one command replaces the 3-command dance and pins are guaranteed build-tested. versions-pin stays for pin-only refresh; build-cloak stays standalone.

# [2026-07-28] Dev Log: home dir chown race bricks containers #506
- Why: intermittent `env: 'claude': Permission denied` on fresh containers. /home/deva stuck at build UID 1001 mode 750 (noble HOME_MODE) after remap to host UID — user can't traverse its own home. usermod's implicit home-tree chown walks live host mounts (~/.claude churning under concurrent sessions), aborts mid-walk with rc=12 AFTER updating passwd; shadow chowns the top dir last, so it never gets fixed. The 7511464 whitelist chowns subdirs, never $DEVA_HOME itself. Latent since 5807889 dropped the recursive home chown; only bites when the walk races live mounts, which is why sibling containers were fine.
- What: explicit non-recursive `chown "$DEVA_UID:$DEVA_GID" "$DEVA_HOME"` in setup_nonroot_user, after the usermod block, using the adapted DEVA_UID so the usermod-failed-entirely variant stays consistent. Devlog with full forensics in docs/devlog/20260728-home-dir-chown-race.org. Verified by fault injection: stub usermod (passwd updated, chown skipped, exit 12) reproduces the brick unpatched, comes out clean patched.
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,15 @@ ARG CODEX_VERSION=0.131.0
ARG GEMINI_CLI_VERSION=0.42.0
ARG GROK_CLI_VERSION=0.2.93
ARG KIMI_CODE_VERSION=0.28.0
ARG OPENCODE_VERSION=1.18.14

# Record key tool versions as labels for quick inspection
LABEL org.opencontainers.image.claude_code_version=${CLAUDE_CODE_VERSION}
LABEL org.opencontainers.image.codex_version=${CODEX_VERSION}
LABEL org.opencontainers.image.gemini_cli_version=${GEMINI_CLI_VERSION}
LABEL org.opencontainers.image.grok_cli_version=${GROK_CLI_VERSION}
LABEL org.opencontainers.image.kimi_code_version=${KIMI_CODE_VERSION}
LABEL org.opencontainers.image.opencode_version=${OPENCODE_VERSION}

ARG CCX_VERSION=v0.7.0

Expand Down
2 changes: 2 additions & 0 deletions Dockerfile.rust
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ARG CODEX_VERSION=0.131.0
ARG GEMINI_CLI_VERSION=0.42.0
ARG GROK_CLI_VERSION=0.2.93
ARG KIMI_CODE_VERSION=0.28.0
ARG OPENCODE_VERSION=1.18.14
ARG CCX_VERSION=v0.7.0
ARG PLAYWRIGHT_VERSION=1.60.0
ARG RUST_TOOLCHAINS="stable"
Expand All @@ -24,6 +25,7 @@ LABEL org.opencontainers.image.codex_version=${CODEX_VERSION}
LABEL org.opencontainers.image.gemini_cli_version=${GEMINI_CLI_VERSION}
LABEL org.opencontainers.image.grok_cli_version=${GROK_CLI_VERSION}
LABEL org.opencontainers.image.kimi_code_version=${KIMI_CODE_VERSION}
LABEL org.opencontainers.image.opencode_version=${OPENCODE_VERSION}
LABEL org.opencontainers.image.ccx_version=${CCX_VERSION}
LABEL org.opencontainers.image.playwright_version=${PLAYWRIGHT_VERSION}

Expand Down
Loading
Loading