Skip to content

Commit fc4a95f

Browse files
authored
Merge pull request #256 from thevibeworks/feat/smux-tmux-bridge
feat(bridge): vendor smux tmux-bridge as layer-2 agent comms
2 parents 09ca3b0 + 8cce805 commit fc4a95f

40 files changed

Lines changed: 3937 additions & 433 deletions

.deva.example

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,34 @@ EPHEMERAL=false
4949
#
5050
# 6. Disable ephemeral containers (persistent):
5151
# EPHEMERAL=false
52+
#
53+
# 7. Enable Codex browser MCP for this project:
54+
# DEFAULT_AGENT=codex
55+
# CODEX_BROWSER_MCP=true
56+
#
57+
# Optional session-only Codex config overrides:
58+
# CODEX_CONFIG=features.apps=false
59+
# CODEX_CONFIG=features.plugins=false
60+
61+
# Hybrid Agent Setup:
62+
#
63+
# Hybrid is the DEFAULT. deva walks every populated subdir under
64+
# ~/.config/deva/ (claude, codex, gemini) and mounts each agent's
65+
# canonical entries into the container. Populated = you either
66+
# hand-created the subdir or autolink symlinked it from legacy
67+
# ~/.claude, ~/.codex, ~/.gemini on first run.
68+
#
69+
# No .deva entries required for the common case. To opt OUT of
70+
# hybrid for a single invocation, pass --config-home DIR to
71+
# isolate that run to a single home.
72+
#
73+
# Use VOLUME= only when you want to mount host paths OUTSIDE
74+
# ~/.config/deva/ (e.g. a shared secrets vault) at an agent target.
75+
# VOLUME= entries win over the default per-agent mount at the same
76+
# container target (first-writer-wins; CLI -v beats .deva).
77+
#
78+
# Auth credentials inside mounted agent dirs ride along — hybrid
79+
# only makes sense if you trust every agent equally.
5280

5381
# Project-Specific Config (.deva in project root):
5482
#

.github/workflows/ci.yml

Lines changed: 97 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,32 +44,40 @@ jobs:
4444
chmod +x scripts/version-check.sh
4545
./scripts/version-check.sh
4646
47+
- name: Unit tests (release-utils.sh)
48+
run: bash tests/test_release_utils.sh
49+
4750
smoke:
4851
name: Installer Smoke Test
4952
runs-on: ubuntu-latest
5053
steps:
5154
- name: Checkout
5255
uses: actions/checkout@v4
5356

54-
- name: Setup Node
55-
uses: actions/setup-node@v4
56-
with:
57-
node-version: "22"
58-
59-
- name: Resolve tool versions
60-
id: versions
61-
env:
62-
GH_TOKEN: ${{ github.token }}
63-
run: bash ./scripts/resolve-tool-versions.sh
57+
- name: Load pinned versions
58+
id: pins
59+
shell: bash
60+
run: |
61+
set -euo pipefail
62+
source ./scripts/version-pins.sh
63+
load_version_pins
64+
emit_github_outputs "$GITHUB_OUTPUT"
6465
6566
- name: Build local smoke image
6667
run: |
6768
docker build -t deva-smoke:ci \
68-
--build-arg CLAUDE_CODE_VERSION="${{ steps.versions.outputs.claude_code_version }}" \
69-
--build-arg CODEX_VERSION="${{ steps.versions.outputs.codex_version }}" \
70-
--build-arg GEMINI_CLI_VERSION="${{ steps.versions.outputs.gemini_cli_version }}" \
71-
--build-arg ATLAS_CLI_VERSION="${{ steps.versions.outputs.atlas_cli_version }}" \
72-
--build-arg COPILOT_API_VERSION="${{ steps.versions.outputs.copilot_api_version }}" \
69+
--build-arg NODE_MAJOR="${{ steps.pins.outputs.node_major }}" \
70+
--build-arg GO_VERSION="${{ steps.pins.outputs.go_version }}" \
71+
--build-arg PYTHON_VERSION="${{ steps.pins.outputs.python_version }}" \
72+
--build-arg DELTA_VERSION="${{ steps.pins.outputs.delta_version }}" \
73+
--build-arg TMUX_VERSION="${{ steps.pins.outputs.tmux_version }}" \
74+
--build-arg TMUX_SHA256="${{ steps.pins.outputs.tmux_sha256 }}" \
75+
--build-arg CLAUDE_CODE_VERSION="${{ steps.pins.outputs.claude_code_version }}" \
76+
--build-arg CLAUDE_TRACE_VERSION="${{ steps.pins.outputs.claude_trace_version }}" \
77+
--build-arg CODEX_VERSION="${{ steps.pins.outputs.codex_version }}" \
78+
--build-arg GEMINI_CLI_VERSION="${{ steps.pins.outputs.gemini_cli_version }}" \
79+
--build-arg ATLAS_CLI_VERSION="${{ steps.pins.outputs.atlas_cli_version }}" \
80+
--build-arg COPILOT_API_VERSION="${{ steps.pins.outputs.copilot_api_version }}" \
7381
.
7482
7583
- name: Build core and rust images via Makefile
@@ -79,11 +87,22 @@ jobs:
7987
TAG=ci \
8088
CORE_TAG=ci-core \
8189
RUST_TAG=ci-rust \
82-
CLAUDE_CODE_VERSION="${{ steps.versions.outputs.claude_code_version }}" \
83-
CODEX_VERSION="${{ steps.versions.outputs.codex_version }}" \
84-
GEMINI_CLI_VERSION="${{ steps.versions.outputs.gemini_cli_version }}" \
85-
ATLAS_CLI_VERSION="${{ steps.versions.outputs.atlas_cli_version }}" \
86-
COPILOT_API_VERSION="${{ steps.versions.outputs.copilot_api_version }}"
90+
NODE_MAJOR="${{ steps.pins.outputs.node_major }}" \
91+
GO_VERSION="${{ steps.pins.outputs.go_version }}" \
92+
PYTHON_VERSION="${{ steps.pins.outputs.python_version }}" \
93+
DELTA_VERSION="${{ steps.pins.outputs.delta_version }}" \
94+
TMUX_VERSION="${{ steps.pins.outputs.tmux_version }}" \
95+
TMUX_SHA256="${{ steps.pins.outputs.tmux_sha256 }}" \
96+
CLAUDE_CODE_VERSION="${{ steps.pins.outputs.claude_code_version }}" \
97+
CLAUDE_TRACE_VERSION="${{ steps.pins.outputs.claude_trace_version }}" \
98+
CODEX_VERSION="${{ steps.pins.outputs.codex_version }}" \
99+
GEMINI_CLI_VERSION="${{ steps.pins.outputs.gemini_cli_version }}" \
100+
ATLAS_CLI_VERSION="${{ steps.pins.outputs.atlas_cli_version }}" \
101+
COPILOT_API_VERSION="${{ steps.pins.outputs.copilot_api_version }}" \
102+
PLAYWRIGHT_VERSION="${{ steps.pins.outputs.playwright_version }}" \
103+
RUST_TOOLCHAINS="${{ steps.pins.outputs.rust_toolchains }}" \
104+
RUST_DEFAULT_TOOLCHAIN="${{ steps.pins.outputs.rust_default_toolchain }}" \
105+
RUST_TARGETS="${{ steps.pins.outputs.rust_targets }}"
87106
88107
- name: Install and launch each agent without a TTY
89108
shell: bash
@@ -127,6 +146,26 @@ jobs:
127146
grep -F -- "$bridge_dir:/deva-host-chrome-bridge" <<<"$dry_run"
128147
grep -F -- "$profile_dir/Extensions:/home/deva/.config/google-chrome/Default/Extensions:ro" <<<"$dry_run"
129148
149+
- name: Smoke bind mount shape guard
150+
shell: bash
151+
run: |
152+
set -euo pipefail
153+
DEVA_DOCKER_IMAGE=deva-smoke \
154+
DEVA_DOCKER_TAG=ci \
155+
./scripts/test-mount-shape.sh
156+
157+
- name: Smoke agent tooling installer
158+
shell: bash
159+
run: |
160+
set -euo pipefail
161+
./scripts/test-install-agent-tooling.sh
162+
163+
- name: Smoke version targets
164+
shell: bash
165+
run: |
166+
set -euo pipefail
167+
./scripts/test-version-targets.sh
168+
130169
- name: Smoke Chrome bridge entrypoint symlink
131170
shell: bash
132171
run: |
@@ -147,6 +186,44 @@ jobs:
147186
deva-smoke:ci \
148187
bash -lc 'link="/tmp/claude-mcp-browser-bridge-$(id -un)"; test -L "$link"; test "$(readlink "$link")" = "/deva-host-chrome-bridge"'
149188
189+
- name: Smoke tmux-bridge CLI surface
190+
shell: bash
191+
run: |
192+
set -euo pipefail
193+
# tmux-bridge is the layer-2 agent comms CLI vendored from smux.
194+
# We assert it's installed, executable, runs outside a tmux pane for
195+
# the version/help/id error paths, and that tmux itself is present
196+
# so a future operator can drive tmux-bridge against host tmux.
197+
docker run --rm deva-smoke:ci bash -lc '
198+
set -euo pipefail
199+
command -v tmux-bridge
200+
command -v tmux
201+
tmux-bridge version
202+
tmux-bridge --help | grep -q "cross-pane communication"
203+
# id requires $TMUX_PANE; outside tmux it must error cleanly.
204+
if tmux-bridge id 2>/dev/null; then
205+
echo "tmux-bridge id should fail outside a tmux pane" >&2
206+
exit 1
207+
fi
208+
# Ephemeral tmux server to prove list/name/read/type work end-to-end.
209+
sock="/tmp/tmux-smoke.sock"
210+
tmux -S "$sock" new-session -d -s smoke "sleep 30"
211+
export TMUX_BRIDGE_SOCKET="$sock"
212+
tmux-bridge list | tee /tmp/list.out
213+
grep -q "smoke:0" /tmp/list.out
214+
target="$(tmux -S "$sock" display-message -p "#{pane_id}")"
215+
tmux-bridge name "$target" smoke-worker
216+
tmux-bridge resolve smoke-worker
217+
tmux-bridge read "$target" 5 >/dev/null
218+
# read-guard: must read before type; second type without read must fail
219+
tmux-bridge type smoke-worker "echo hi"
220+
if tmux-bridge type smoke-worker "echo twice" 2>/dev/null; then
221+
echo "read-guard should have blocked second type without read" >&2
222+
exit 1
223+
fi
224+
tmux -S "$sock" kill-server
225+
'
226+
150227
docs:
151228
name: Docs Build
152229
runs-on: ubuntu-latest

.github/workflows/nightly-images.yml

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,34 @@ concurrency:
1818
cancel-in-progress: true
1919

2020
jobs:
21+
load-version-pins:
22+
name: Load Pinned Toolchain Versions
23+
runs-on: ubuntu-latest
24+
outputs:
25+
node_major: ${{ steps.pins.outputs.node_major }}
26+
go_version: ${{ steps.pins.outputs.go_version }}
27+
python_version: ${{ steps.pins.outputs.python_version }}
28+
delta_version: ${{ steps.pins.outputs.delta_version }}
29+
tmux_version: ${{ steps.pins.outputs.tmux_version }}
30+
tmux_sha256: ${{ steps.pins.outputs.tmux_sha256 }}
31+
claude_trace_version: ${{ steps.pins.outputs.claude_trace_version }}
32+
playwright_version: ${{ steps.pins.outputs.playwright_version }}
33+
rust_toolchains: ${{ steps.pins.outputs.rust_toolchains }}
34+
rust_default_toolchain: ${{ steps.pins.outputs.rust_default_toolchain }}
35+
rust_targets: ${{ steps.pins.outputs.rust_targets }}
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v4
39+
40+
- name: Load pins
41+
id: pins
42+
shell: bash
43+
run: |
44+
set -euo pipefail
45+
source ./scripts/version-pins.sh
46+
load_version_pins
47+
emit_github_outputs "$GITHUB_OUTPUT"
48+
2149
resolve-versions:
2250
name: Resolve Latest Tool Versions
2351
runs-on: ubuntu-latest
@@ -59,7 +87,7 @@ jobs:
5987
6088
build-base:
6189
name: Build Nightly Base Image
62-
needs: resolve-versions
90+
needs: [load-version-pins, resolve-versions]
6391
runs-on: ubuntu-latest
6492
steps:
6593
- name: Checkout
@@ -102,15 +130,22 @@ jobs:
102130
cache-from: type=gha,scope=nightly-base
103131
cache-to: type=gha,mode=max,scope=nightly-base
104132
build-args: |
133+
NODE_MAJOR=${{ needs.load-version-pins.outputs.node_major }}
134+
GO_VERSION=${{ needs.load-version-pins.outputs.go_version }}
135+
PYTHON_VERSION=${{ needs.load-version-pins.outputs.python_version }}
136+
DELTA_VERSION=${{ needs.load-version-pins.outputs.delta_version }}
137+
TMUX_VERSION=${{ needs.load-version-pins.outputs.tmux_version }}
138+
TMUX_SHA256=${{ needs.load-version-pins.outputs.tmux_sha256 }}
105139
CLAUDE_CODE_VERSION=${{ needs.resolve-versions.outputs.claude_code_version }}
140+
CLAUDE_TRACE_VERSION=${{ needs.load-version-pins.outputs.claude_trace_version }}
106141
CODEX_VERSION=${{ needs.resolve-versions.outputs.codex_version }}
107142
GEMINI_CLI_VERSION=${{ needs.resolve-versions.outputs.gemini_cli_version }}
108143
ATLAS_CLI_VERSION=${{ needs.resolve-versions.outputs.atlas_cli_version }}
109144
COPILOT_API_VERSION=${{ needs.resolve-versions.outputs.copilot_api_version }}
110145
111146
build-rust:
112147
name: Build Nightly Rust Image
113-
needs: [resolve-versions, build-base]
148+
needs: [load-version-pins, resolve-versions, build-base]
114149
runs-on: ubuntu-latest
115150
steps:
116151
- name: Checkout
@@ -154,6 +189,15 @@ jobs:
154189
cache-to: type=gha,mode=max,scope=nightly-rust
155190
build-args: |
156191
BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly-${{ needs.resolve-versions.outputs.stamp }}
192+
CLAUDE_CODE_VERSION=${{ needs.resolve-versions.outputs.claude_code_version }}
193+
CLAUDE_TRACE_VERSION=${{ needs.load-version-pins.outputs.claude_trace_version }}
194+
CODEX_VERSION=${{ needs.resolve-versions.outputs.codex_version }}
195+
GEMINI_CLI_VERSION=${{ needs.resolve-versions.outputs.gemini_cli_version }}
196+
ATLAS_CLI_VERSION=${{ needs.resolve-versions.outputs.atlas_cli_version }}
197+
PLAYWRIGHT_VERSION=${{ needs.load-version-pins.outputs.playwright_version }}
198+
RUST_TOOLCHAINS=${{ needs.load-version-pins.outputs.rust_toolchains }}
199+
RUST_DEFAULT_TOOLCHAIN=${{ needs.load-version-pins.outputs.rust_default_toolchain }}
200+
RUST_TARGETS=${{ needs.load-version-pins.outputs.rust_targets }}
157201
158202
summary:
159203
name: Nightly Summary

0 commit comments

Comments
 (0)