Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ jobs:
files: |
gh-release-assets/*

# Extract version from tag (e.g., v0.0.85 -> 0.0.89)
# Extract version from tag (e.g., v0.0.89 -> 0.0.89)
- name: Extract version
if: startsWith(github.ref, 'refs/tags/')
id: version
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,63 @@ permissions:
contents: read

jobs:
web-local-smoke:
name: Run Web + Local Brain Smoke
runs-on: ubuntu-latest
timeout-minutes: 25

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install frontend dependencies
run: npm ci --ignore-scripts

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Set up Python
run: uv python install 3.11

- name: Install backend dependencies
run: |
cd backend
uv sync

- name: Run web + local brain smoke
run: bash scripts/smoke-web-local-brain.sh

frontend-quality:
name: Run Frontend Guardrails
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install frontend dependencies
run: npm ci --ignore-scripts

- name: Run type check
run: npm run type-check

- name: Check Electron Access Guard
run: bash scripts/check-electron-access.sh

pytest:
name: Run Python Tests
runs-on: ubuntu-latest
Expand Down
35 changes: 35 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
```bash
# Option 1: Start with uvicorn directly
uv run uvicorn main:api --port 5001

# Option 2: Standalone mode (no Electron dependency)
uv run python main.py

# Option 3: If uv run hangs, delete lock files and retry, or use venv directly:
.venv/bin/python main.py
# or
.venv/bin/uvicorn main:api --port 5001 --host 0.0.0.0

# If uv hangs, delete lock files first: rm -f uv_installing.lock uv_installed.lock
```

### Environment Variables (Standalone)

| Variable | Default | Description |
|----------|---------|-------------|
| `EIGENT_BRAIN_PORT` | 5001 | Listening port |
| `EIGENT_BRAIN_HOST` | 0.0.0.0 | Listening address |
| `EIGENT_DEBUG` | - | Set to 1/true to enable reload |
| `EIGENT_WORKSPACE` | ~/.eigent/workspace | Working directory |
| `EIGENT_DEPLOYMENT_TYPE` | (auto) | `local` / `cloud_vm` / `sandbox` / `docker`; determines Hands capabilities (see ADR-0006) |
| `EIGENT_HANDS_MODE` | - | Set to `remote` to enable `RemoteHands` (remote cluster resource mode) |
| `EIGENT_HANDS_CLUSTER_CONFIG_FILE` | - | Path to `RemoteHands` config file (TOML); **recommended** |
| `EIGENT_HANDS_TERMINAL` | - | Override terminal hand: `1`/`true`/`yes` or `0`/`false`/`no` |
| `EIGENT_HANDS_BROWSER` | - | Override browser hand |
| `EIGENT_HANDS_FILESYSTEM` | - | Override filesystem scope: `full` / `workspace_only` |
| `EIGENT_HANDS_MCP` | - | Override MCP mode: `all` / `allowlist` |

RemoteHands config file example:

```bash
cp backend/config/hands_clusters.example.toml ~/.eigent/hands_clusters.toml
export EIGENT_HANDS_MODE=remote
export EIGENT_HANDS_CLUSTER_CONFIG_FILE=~/.eigent/hands_clusters.toml
```

i18n operation process: https://github.com/Anbarryprojects/fastapi-babel
Expand Down
57 changes: 54 additions & 3 deletions backend/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,68 @@
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========

import os

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware

# Initialize FastAPI with title
api = FastAPI(title="Eigent Multi-Agent System API")

# Add CORS middleware

@api.get("/")
def root():
"""Root endpoint - confirms this is the Brain backend."""
return {"service": "eigent-brain", "docs": "/docs", "health": "/health"}


_cors_raw = os.environ.get("EIGENT_CORS_ORIGINS", "")
_allowed_origins = [o.strip() for o in _cors_raw.split(",") if o.strip()]
_default_frame_ancestors = [
"'self'",
"http://localhost:*",
"http://127.0.0.1:*",
"https://localhost:*",
"https://127.0.0.1:*",
]
_frame_ancestors = " ".join(
dict.fromkeys(
[
*_default_frame_ancestors,
*[origin for origin in _allowed_origins if origin != "*"],
]
)
)


class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
if request.url.path.startswith("/files/preview/"):
if "X-Frame-Options" in response.headers:
del response.headers["X-Frame-Options"]
response.headers["Content-Security-Policy"] = (
f"frame-ancestors {_frame_ancestors};"
)
else:
response.headers["X-Frame-Options"] = "DENY"
return response


api.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_origins=_allowed_origins or ["*"],
allow_credentials=bool(_allowed_origins),
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["X-Session-ID"],
)
api.add_middleware(SecurityHeadersMiddleware)

# Phase 2: Channel/Session header parsing (X-Channel, X-Session-ID, X-User-ID)
from app.router_layer import ChannelSessionMiddleware

api.add_middleware(ChannelSessionMiddleware)
Loading