Skip to content
Closed
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: 2 additions & 0 deletions backend/app/agent/factory/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ def browser_agent(options: Chat):
"browser_sheet_input",
"browser_get_page_snapshot",
"browser_open",
"browser_upload_file",
"browser_download_file",
],
)

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"test:watch": "vitest",
"test:e2e": "vitest run --config vitest.config.ts",
"test:coverage": "vitest run --coverage",
"check:i18n": "node scripts/check-i18n-locale-parity.js",
"type-check": "tsc -p tsconfig.build.json --noEmit",
"lint": "eslint . --no-warn-ignored",
"lint:fix": "eslint . --fix --no-warn-ignored",
Expand Down
5 changes: 5 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ RUN sed -i 's/\r$//' /app/start.sh && chmod +x /app/start.sh
RUN sed -i 's/\r$//' /app/celery/worker/start && chmod +x /app/celery/worker/start
RUN sed -i 's/\r$//' /app/celery/beat/start && chmod +x /app/celery/beat/start

# Bake the latest server/ commit into the image for stale-server detection.
# Uses --mount=type=bind to access .git without adding it to a layer.
RUN --mount=type=bind,source=.git,target=/tmp/.git \
echo "EIGENT_SERVER_GIT_COMMIT=$(git --git-dir=/tmp/.git log -1 --format=%H -- server/ 2>/dev/null || echo unknown)" > /app/.image_env

# Reset the entrypoint, don't invoke `uv`
ENTRYPOINT []

Expand Down
2 changes: 1 addition & 1 deletion server/README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- 配置中心 Config(保存各类工具/能力所需的密钥或参数)
- `GET /configs`、`POST /configs`、`PUT /configs/{id}`、`DELETE /configs/{id}`、`GET /config/info`
- 聊天与数据
- 历史、快照、分享等接口位于 `app/controller/chat/`,数据全部落在本地数据库
- 历史、快照、分享等接口位于 `app/domains/chat/api/`,数据全部落在本地数据库
- MCP 服务管理(导入本地/远程 MCP 服务器)
- `GET /mcps`、`POST /mcp/install`、`POST /mcp/import/{Local|Remote}` 等

Expand Down
2 changes: 1 addition & 1 deletion server/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- Config Center (store secrets/params required by tools/capabilities)
- `GET /configs`, `POST /configs`, `PUT /configs/{id}`, `DELETE /configs/{id}`, `GET /config/info`
- Chat & Data
- History, snapshots, sharing, etc. in `app/controller/chat/`, all persisted to local DB
- History, snapshots, sharing, etc. in `app/domains/chat/api/`, all persisted to local DB
- MCP Management (import local/remote MCP servers)
- `GET /mcps`, `POST /mcp/install`, `POST /mcp/import/{Local|Remote}`, etc.

Expand Down
79 changes: 79 additions & 0 deletions server/doc/server-refactor-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Server Refactor v1 - Upgrade Guide

> Applies to: v0.0.89+
> PR: #1509

## What Changed

The server codebase has been restructured from a flat layout to a **domain-driven architecture**. No API endpoints or database schemas were changed — this is a code organization refactor only.

### Directory Mapping

| Before | After | Description |
|---|---|---|
| `app/component/` | `app/core/` | Infrastructure utilities (database, encryption, celery, etc.) |
| `app/controller/` | `app/domains/*/api/` | API controllers, grouped by domain |
| `app/service/` | `app/domains/*/service/` | Business logic, grouped by domain |
| `app/exception/` | `app/shared/exception/` | Exception handling |
| `app/type/` | `app/shared/types/` | Shared type definitions |
| _(new)_ | `app/shared/auth/` | Authentication & authorization |
| _(new)_ | `app/shared/middleware/` | CORS, rate limiting, trace ID |
| _(new)_ | `app/shared/http/` | HTTP client utilities |
| _(new)_ | `app/shared/logging/` | Logging & sensitive data filtering |

### Domain Structure

Each domain (`chat`, `config`, `mcp`, `model_provider`, `oauth`, `trigger`, `user`) follows the same layout:

```
app/domains/<domain>/
api/ # Controllers (route handlers)
service/ # Business logic
schema/ # Request/response schemas
```

## Upgrade Action Required

**This is a breaking change for local deployments.** The old server code will fail to start due to changed import paths.

### Docker Users

```bash
cd server
docker-compose up --build -d
```

You **must** include `--build` to rebuild the image. Running `docker-compose up -d` without `--build` will use the stale old image and fail.

### Non-Docker Users (Local Development)

If you are running the server directly (via `start_server.sh` or `uv run uvicorn`):

1. Stop the running server process
2. Pull the latest code
3. Restart the server

```bash
# If using start_server.sh
cd server
./start_server.sh

# If running uvicorn directly
cd server
uv run uvicorn main:api --reload --port 3001 --host 0.0.0.0
```

### Electron App Users

If you are running Eigent as a desktop app, simply restart the application. The server will be restarted automatically.

## FAQ

**Q: Will I lose my data?**
A: No. Database volumes and schemas are not affected. Only the Python source code layout changed.

**Q: Do I need to re-run database migrations?**
A: No. There are no new migrations in this change.

**Q: I see import errors like `ModuleNotFoundError: No module named 'app.component'`**
A: This means you are running an old server binary/image. Follow the upgrade steps above.
79 changes: 79 additions & 0 deletions server/doc/server-refactor-v1_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Server 重构 v1 - 升级指南

> 适用版本: v0.0.89+
> PR: #1509

## 改动概述

Server 代码从扁平结构重构为**领域驱动架构 (Domain-Driven)**。API 接口和数据库结构均未变更,这是一次纯代码组织层面的重构。

### 目录变更对照

| 重构前 | 重构后 | 说明 |
|---|---|---|
| `app/component/` | `app/core/` | 基础设施(数据库、加密、celery 等) |
| `app/controller/` | `app/domains/*/api/` | 按领域分组的 API 控制器 |
| `app/service/` | `app/domains/*/service/` | 按领域分组的业务逻辑 |
| `app/exception/` | `app/shared/exception/` | 异常处理 |
| `app/type/` | `app/shared/types/` | 共享类型定义 |
| _(新增)_ | `app/shared/auth/` | 认证与授权 |
| _(新增)_ | `app/shared/middleware/` | CORS、限流、Trace ID |
| _(新增)_ | `app/shared/http/` | HTTP 客户端工具 |
| _(新增)_ | `app/shared/logging/` | 日志与敏感信息过滤 |

### 领域结构

每个领域(`chat`、`config`、`mcp`、`model_provider`、`oauth`、`trigger`、`user`)遵循统一结构:

```
app/domains/<领域>/
api/ # 控制器(路由处理)
service/ # 业务逻辑
schema/ # 请求/响应模型
```

## 升级操作(必须)

**此改动对本地部署是 breaking change。** 旧版 server 代码因 import 路径变更将无法启动。

### Docker 用户

```bash
cd server
docker-compose up --build -d
```

**必须**加 `--build` 参数重新构建镜像。直接 `docker-compose up -d` 会使用旧镜像导致启动失败。

### 非 Docker 用户(本地开发)

如果你通过 `start_server.sh` 或 `uv run uvicorn` 直接运行 server:

1. 停止正在运行的 server 进程
2. 拉取最新代码
3. 重新启动 server

```bash
# 使用 start_server.sh
cd server
./start_server.sh

# 直接运行 uvicorn
cd server
uv run uvicorn main:api --reload --port 3001 --host 0.0.0.0
```

### Electron 桌面应用用户

重启应用即可,server 会自动重启。

## 常见问题

**Q: 数据会丢失吗?**
A: 不会。数据库卷和表结构未受影响,仅 Python 源码目录结构发生了变化。

**Q: 需要重新执行数据库迁移吗?**
A: 不需要。此次改动没有新增数据库迁移。

**Q: 出现 `ModuleNotFoundError: No module named 'app.component'`**
A: 说明正在运行旧版 server。请按上述升级步骤操作。
2 changes: 1 addition & 1 deletion server/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ services:
test:
[
'CMD-SHELL',
'celery -A app.component.celery inspect ping -d celery@$$HOSTNAME',
'celery -A app.core.celery inspect ping -d celery@$$HOSTNAME',
]
interval: 30s
timeout: 10s
Expand Down
52 changes: 47 additions & 5 deletions server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
sys.path.insert(0, str(_project_root))

import logging
import sys
import subprocess
from importlib.metadata import version as pkg_version

from fastapi.staticfiles import StaticFiles
from fastapi_pagination import add_pagination
Expand Down Expand Up @@ -50,10 +51,51 @@
auto_include_routers(router, "", "app/api")
api.include_router(router, prefix=f"{prefix}/v1")

# Server version — read once at import time so it reflects the running code
try:
SERVER_VERSION = pkg_version("Eigent")
except Exception:
SERVER_VERSION = "unknown"

# Git hash of the last commit that touched server/ — used for stale-server detection.
# Captured once at startup; stays constant while the process lives.
# 1) Try git directly (works in local dev)
# 2) Fall back to .image_env baked by Dockerfile (works in Docker)
def _read_server_code_hash() -> str:
# Try git first (local dev)
try:
h = subprocess.check_output(
["git", "log", "-1", "--format=%H", "--", "server/"],
cwd=str(_project_root), text=True, stderr=subprocess.DEVNULL,
).strip()
if h:
return h
except Exception:
pass
# Fallback: read from Docker-baked .image_env
try:
env_file = pathlib.Path(__file__).parent / ".image_env"
for line in env_file.read_text().splitlines():
if line.startswith("EIGENT_SERVER_GIT_COMMIT="):
v = line.split("=", 1)[1].strip()
if v:
return v
except Exception:
pass
return "unknown"

SERVER_CODE_HASH = _read_server_code_hash()


# Health check at root level for Docker healthcheck (GET /health)
@api.get("/health", tags=["Health"])
async def health_check():
return {"status": "ok", "service": "eigent-server"}
return {
"status": "ok",
"service": "eigent-server",
"version": SERVER_VERSION,
"server_hash": SERVER_CODE_HASH,
}

# Backward-compatible webhook route (/api/webhook/...)
from app.domains.trigger.api.webhook_controller import router as webhook_router
Expand Down Expand Up @@ -99,12 +141,12 @@ def emit(self, record: logging.LogRecord) -> None:
if not os.path.isdir(public_dir):
try:
os.makedirs(public_dir, exist_ok=True)
logger.warning(f"Public directory did not exist. Created: {public_dir}")
loguru_logger.warning(f"Public directory did not exist. Created: {public_dir}")
except Exception as e:
logger.error(f"Public directory missing and could not be created: {public_dir}. Error: {e}")
loguru_logger.error(f"Public directory missing and could not be created: {public_dir}. Error: {e}")
public_dir = None

if public_dir and os.path.isdir(public_dir):
api.mount("/public", StaticFiles(directory=public_dir), name="public")
else:
logger.warning("Skipping /public mount because public directory is unavailable")
loguru_logger.warning("Skipping /public mount because public directory is unavailable")
27 changes: 25 additions & 2 deletions server/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading