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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ coverage
# Runtime data and logs
data
logs
.sandbox

# Local env files (inject at runtime via --env-file or -e)
.env
Expand Down
6 changes: 6 additions & 0 deletions .env.devin-bridge.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ENABLE_LIVE_DEVIN_TESTS=0
DEVIN_BRIDGE_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_SONNET_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_OPUS_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_HAIKU_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_SUBAGENT_MODEL=devin-cli-agentic/swe-1-7
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,18 @@ APP_LOG_TO_FILE=true
# ── Devin CLI binary path ──
# Used by: open-sse/executors/devin-cli.ts. Default: looked up via PATH.
# CLI_DEVIN_BIN=devin
# Agentic bridge-only binary override. The bridge still executes ACP stdio only.
# CLI_DEVIN_AGENTIC_BIN=devin
# Required isolated HOME for the agentic Devin child process.
# DEVIN_AGENTIC_HOME=/home/bridge
# Bounded ACP turn timeout in milliseconds. Default: 120000.
# DEVIN_AGENTIC_ACP_TIMEOUT_MS=120000
# Agentic bridge model aliases. Values must keep the devin-cli-agentic/ prefix.
# DEVIN_BRIDGE_MODEL=devin-cli-agentic/swe-1-7
# DEVIN_BRIDGE_SONNET_MODEL=devin-cli-agentic/swe-1-7
# DEVIN_BRIDGE_OPUS_MODEL=devin-cli-agentic/swe-1-7
# DEVIN_BRIDGE_HAIKU_MODEL=devin-cli-agentic/swe-1-7
# DEVIN_BRIDGE_SUBAGENT_MODEL=devin-cli-agentic/swe-1-7

# ── Command Code (custom CLI) callback ──
# Local port used for OAuth-style callbacks from the Command Code CLI helper.
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
!.env.example
!.env.devin-bridge.example
!.env.homolog.example
# Provider API keys (never commit)
*.api-key
Expand Down Expand Up @@ -207,6 +208,8 @@ scripts/i18n/_pending-keys.json
.agents/
.antigravitycli/
.claude/
!tests/fixtures/devin-bridge/e2e-workspace/.claude/
!tests/fixtures/devin-bridge/e2e-workspace/.claude/**

# PR Reviews and local feedback files
pr_reviews*.json
Expand Down Expand Up @@ -243,6 +246,8 @@ _artifacts/ # release-green artifacts

# CI/local quality artifacts (eslint-results.json, quality-ratchet.md, etc.)
.artifacts/
# Isolated Devin bridge workspaces, evidence, and test databases
.sandbox/

# Homologation E2E suite (npm run homolog) — real-environment credentials + report output
.env.homolog
Expand Down
57 changes: 57 additions & 0 deletions docker/devin-bridge/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
FROM node:26.0.0-bookworm-slim

ARG CLAUDE_CODE_VERSION=2.1.220
ARG DEVIN_CLI_VERSION=3000.2.17
ARG TARGETARCH

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl git bash python3 make g++ tini \
&& rm -rf /var/lib/apt/lists/* \
&& npm install --global "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"

RUN set -eu; \
case "${TARGETARCH}" in \
amd64) devin_arch=x86_64-unknown-linux; devin_sha=f0e1e9363afc6ee68c4ef87bab4aeb7ff5cc08a5fa838350ef3ceefdbb2a2be2 ;; \
arm64) devin_arch=aarch64-unknown-linux; devin_sha=116dc71ef085a922bc3ff0ea0377d4b26c529a431d58246e36572913e2d25624 ;; \
*) echo "Unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \
esac; \
curl -fsSL "https://static.devin.ai/cli/${DEVIN_CLI_VERSION}/devin-${DEVIN_CLI_VERSION}-${devin_arch}.tar.gz" -o /tmp/devin.tar.gz; \
echo "${devin_sha} /tmp/devin.tar.gz" | sha256sum -c -; \
tar -xzf /tmp/devin.tar.gz -C /tmp; \
install -m 0755 "$(find /tmp -type f -name devin | head -1)" /usr/local/bin/devin; \
rm -rf /tmp/devin.tar.gz /tmp/devin-*

RUN groupadd --gid 10001 bridge \
&& useradd --uid 10001 --gid bridge --create-home --home-dir /home/bridge --shell /bin/bash bridge \
&& mkdir -p /opt/omniroute /workspace \
&& chown -R bridge:bridge /opt/omniroute /workspace

WORKDIR /opt/omniroute
USER bridge
COPY --chown=bridge:bridge package.json package-lock.json .npmrc ./
RUN npm ci --ignore-scripts --no-audit --fund=false
COPY --chown=bridge:bridge . .
RUN npm rebuild better-sqlite3 || true

ENV HOME=/home/bridge \
CLAUDE_CONFIG_DIR=/home/bridge/.claude-devin-isolated \
DEVIN_AGENTIC_HOME=/home/bridge \
DATA_DIR=/home/bridge/.omniroute-isolated \
SQLITE_FILE=/home/bridge/.omniroute-isolated/storage.sqlite \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
DISABLE_TELEMETRY=1 \
DISABLE_ERROR_REPORTING=1 \
DISABLE_AUTOUPDATER=1 \
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 \
NEXT_TELEMETRY_DISABLED=1

RUN mkdir -p /home/bridge/.claude-devin-isolated /home/bridge/.local/share/devin \
/home/bridge/.omniroute-isolated

RUN DATA_DIR=/tmp/omniroute-build-data \
SQLITE_FILE=/tmp/omniroute-build-data/storage.sqlite \
npm run build \
&& rm -rf /tmp/omniroute-build-data

ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["bash"]
218 changes: 218 additions & 0 deletions docker/devin-bridge/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: omniroute-devin-bridge

x-isolated-environment: &isolated-environment
HOME: /home/bridge
CLAUDE_CONFIG_DIR: /home/bridge/.claude-devin-isolated
DEVIN_AGENTIC_HOME: /home/bridge
DATA_DIR: /home/bridge/.omniroute-isolated
SQLITE_FILE: /home/bridge/.omniroute-isolated/storage.sqlite
ANTHROPIC_BASE_URL: http://omniroute:20128
ANTHROPIC_AUTH_TOKEN: sk-local-devin-gateway
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
DISABLE_TELEMETRY: "1"
DISABLE_ERROR_REPORTING: "1"
DISABLE_AUTOUPDATER: "1"
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1"
DEVIN_BRIDGE_MODEL: ${DEVIN_BRIDGE_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_MODEL: ${DEVIN_BRIDGE_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_DEFAULT_SONNET_MODEL: ${DEVIN_BRIDGE_SONNET_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_DEFAULT_OPUS_MODEL: ${DEVIN_BRIDGE_OPUS_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_DEFAULT_HAIKU_MODEL: ${DEVIN_BRIDGE_HAIKU_MODEL:-devin-cli-agentic/swe-1-7}
CLAUDE_CODE_SUBAGENT_MODEL: ${DEVIN_BRIDGE_SUBAGENT_MODEL:-devin-cli-agentic/swe-1-7}
REQUIRE_API_KEY: "true"
OMNIROUTE_API_KEY: sk-local-devin-gateway

x-runtime: &runtime
image: omniroute-devin-bridge:local
build:
context: ../..
dockerfile: docker/devin-bridge/Dockerfile
args:
CLAUDE_CODE_VERSION: 2.1.220
DEVIN_CLI_VERSION: 3000.2.17
user: "10001:10001"
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=256m
- /opt/omniroute/.source:rw,nosuid,nodev,size=16m,uid=10001,gid=10001
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
environment: *isolated-environment
networks: [bridge-internal]

services:
omniroute:
<<: *runtime
profiles: [offline]
hostname: omniroute
environment:
<<: *isolated-environment
CLI_DEVIN_AGENTIC_BIN: /opt/omniroute/docker/devin-bridge/mock-devin.mjs
DEVIN_BRIDGE_MOCK_LOG: /evidence/mock-acp.jsonl
command: ["npm", "run", "start"]
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:20128/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 2s
timeout: 2s
retries: 60
volumes:
- omniroute-offline-data:/home/bridge/.omniroute-isolated
- ../../.sandbox/evidence:/evidence
- ./mock-devin.mjs:/opt/omniroute/docker/devin-bridge/mock-devin.mjs:ro

claude:
<<: *runtime
profiles: [offline]
depends_on:
omniroute:
condition: service_healthy
claude-egress-guard:
condition: service_healthy
working_dir: /workspace
command: ["bash", "/opt/omniroute/docker/devin-bridge/run-claude-e2e.sh"]
environment:
<<: *isolated-environment
NODE_USE_ENV_PROXY: "1"
HTTP_PROXY: http://claude-egress-guard:8080
HTTPS_PROXY: http://claude-egress-guard:8080
NO_PROXY: omniroute
volumes:
- claude-isolated-config:/home/bridge/.claude-devin-isolated
- ../../.sandbox/e2e-workspace:/workspace
- ../../.sandbox/evidence:/evidence
- ./run-claude-e2e.sh:/opt/omniroute/docker/devin-bridge/run-claude-e2e.sh:ro

contract:
<<: *runtime
profiles: [offline]
depends_on:
omniroute:
condition: service_healthy
command: ["node", "/opt/omniroute/docker/devin-bridge/run-contract.mjs"]
volumes:
- ./run-contract.mjs:/opt/omniroute/docker/devin-bridge/run-contract.mjs:ro

claude-egress-guard:
image: node:26.0.0-bookworm-slim
profiles: [offline, live-devin]
user: "10001:10001"
read_only: true
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
command: ["node", "/guard/proxy.mjs"]
environment:
GUARD_LISTEN: 0.0.0.0:8080
GUARD_POLICY: deny-all
GUARD_LOG: /guard-audit/egress.jsonl
healthcheck:
test:
[
"CMD",
"node",
"-e",
"require('net').connect(8080,'127.0.0.1').on('connect',()=>process.exit(0)).on('error',()=>process.exit(1))",
]
interval: 1s
timeout: 1s
retries: 15
volumes:
- ./network-guard:/guard:ro
- ../../.sandbox/guard-audit/claude:/guard-audit
networks: [bridge-internal]

network-guard:
image: node:26.0.0-bookworm-slim
profiles: [live-devin]
user: "10001:10001"
read_only: true
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
command: ["node", "/guard/proxy.mjs"]
environment:
GUARD_LISTEN: 0.0.0.0:8080
GUARD_POLICY: devin
GUARD_LOG: /guard-audit/egress.jsonl
healthcheck:
test:
[
"CMD",
"node",
"-e",
"require('net').connect(8080,'127.0.0.1').on('connect',()=>process.exit(0)).on('error',()=>process.exit(1))",
]
interval: 1s
timeout: 1s
retries: 15
volumes:
- ./network-guard:/guard:ro
- ../../.sandbox/guard-audit/devin:/guard-audit
networks: [devin-guard-internal, guard-egress]

omniroute-live:
<<: *runtime
profiles: [live-devin]
hostname: omniroute
depends_on:
network-guard:
condition: service_healthy
environment:
<<: *isolated-environment
CLI_DEVIN_AGENTIC_BIN: /usr/local/bin/devin
DEVIN_BRIDGE_PROXY_URL: http://network-guard:8080
networks: [bridge-internal, devin-guard-internal]
command: ["npm", "run", "start"]
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:20128/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 2s
timeout: 2s
retries: 60
volumes:
- devin-auth:/home/bridge/.local/share/devin
- omniroute-live-data:/home/bridge/.omniroute-isolated

claude-live:
<<: *runtime
profiles: [live-devin]
depends_on:
omniroute-live:
condition: service_healthy
claude-egress-guard:
condition: service_healthy
working_dir: /workspace
command: ["bash", "/opt/omniroute/docker/devin-bridge/run-claude-live-e2e.sh"]
environment:
<<: *isolated-environment
NODE_USE_ENV_PROXY: "1"
HTTP_PROXY: http://claude-egress-guard:8080
HTTPS_PROXY: http://claude-egress-guard:8080
NO_PROXY: omniroute
volumes:
- claude-isolated-config:/home/bridge/.claude-devin-isolated
- ../../.sandbox/live-workspace:/workspace
- ../../.sandbox/evidence:/evidence
- ./run-claude-live-e2e.sh:/opt/omniroute/docker/devin-bridge/run-claude-live-e2e.sh:ro

networks:
bridge-internal:
internal: true
devin-guard-internal:
internal: true
guard-egress: {}

volumes:
claude-isolated-config: {}
devin-auth: {}
omniroute-offline-data: {}
omniroute-live-data: {}
Loading