---
title: "feat: Enterprise Client-Server Architecture Evolution"
type: feat
status: completed
created: 2026-06-19
completed: 2026-06-19
origin: docs/plans/2026-06-19-002-enterprise-client-server-architecture-plan.md
deepened: false
---

# feat: Enterprise Client-Server Architecture Evolution

## Summary

将 Fischer AgentKit 从纯本地运行架构演进为企业级客户端+服务端架构。客户端（Tauri 桌面端）作为 AI 工作台本地执行 Agent/终端/文件操作，服务端作为企业平台提供 LLM 网关（统一 Key 管理）、用户权限、审计日志、知识库共享能力。采用渐进式改造，每个阶段设立检查点验证核心假设。

## Problem Frame

当前 AgentKit 是纯本地架构：Tauri 启动本地 Python sidecar，LLM API Key 存在本地 agentkit.yaml，无用户系统、无权限管理、无服务端部署。企业场景需要：统一管控 LLM Key 和成本、团队知识共享、权限与审计、多用户支持。

## Scope Boundaries

### In Scope

- 服务端 LLM 网关（统一 Key + 用量统计 + 流式透传）
- 用户认证（JWT + API Key 双轨）
- 三级 RBAC + 权限位
- 双模式终端（本地 + 服务端）+ 三层白名单
- 终端审计日志
- 配置同步（轮询版）
- 前端连接模式切换

### Deferred to Follow-Up Work

- Web 管理台（V1 用 CLI 管理）
- 技能市场 / 工作流模板中心（YAML + Git 够用）
- 生产系统集成（暴露 Webhook 即可）
- SSO/OIDC（V1 本地账号，V2 预留接口）
- 代码库语义索引
- 多租户隔离（表预留 tenant_id，V2 启用）
- 多设备会话同步

## Key Technical Decisions

### KTD1: 终端保留在本地 Python sidecar（非 Tauri Rust）

**决策**：终端 PTY 逻辑保留在 Python sidecar 中，不迁移到 Tauri Rust 层。

**理由**：当前 `terminal.py` + `PTYSession` 已完整实现 PTY 管理、安全检测、确认流程。用 Rust 重写风险高、收益低。本地 sidecar 模式下终端逻辑不变，服务端终端新增独立端点。

### KTD2: LLM 代理模式（非客户端直连）

**决策**：客户端通过服务端 LLM 网关间接调用 LLM，不在本地存储 API Key。

**理由**：核心价值是成本管控 + 用量审计（非安全边界）。服务端必须支持内网部署以避免跨地域延迟。客户端保留直连降级能力（服务端不可用时用户自填 Key）。

### KTD3: 配置同步用轮询（非 WebSocket 推送）

**决策**：启动时全量拉取 + 每 5 分钟轮询版本号 + 手动刷新。

**理由**：配置变更频率是周/月级，WebSocket 推送是杀鸡用牛刀。轮询实现量是 WebSocket 的 1/10，覆盖 99% 场景。

### KTD4: 三级 RBAC + 权限位（非四级角色）

**决策**：member / operator / admin 三级角色 + 独立权限位。

**理由**：四级中的 viewer 是想象出来的角色。企业真实需求是权限点（能否用终端、能否管理 KB），不是角色等级。权限位比固定等级更灵活。

### KTD5: 双模式终端 + 三层白名单

**决策**：支持本地终端（客户端 sidecar PTY）和服务端终端（服务端 PTY），均配三层白名单（内置/全局/用户）+ 黑名单。

**理由**：本地终端满足开发者本地操作需求，服务端终端满足 DevOps 管理服务器需求。白名单机制平衡安全与效率。

## High-Level Technical Design

```
Phase 1: LLM Gateway + Auth          Phase 2: RBAC + Audit + Terminal
┌──────────────┐                    ┌──────────────┐
│  Client      │                    │  Client      │
│  RemoteLLM   │──JWT──►            │  Terminal    │──JWT──►
│  Provider    │                    │  (Local)     │
└──────┬───────┘                    └──────┬───────┘
       │ SSE                              │ WS
       ▼                                  ▼
┌──────────────┐                    ┌──────────────┐
│  Server      │                    │  Server      │
│  LLM Gateway │                    │  Auth+RBAC   │
│  + JWT Auth  │                    │  + Audit     │
│  + Usage     │                    │  + Whitelist │
└──────────────┘                    └──────────────┘

Phase 3: Server Terminal + KB
┌──────────────┐
│  Client      │
│  Terminal    │──JWT──►
│  (Server)    │
└──────┬───────┘
       │ WSS
       ▼
┌──────────────┐
│  Server      │
│  Server PTY  │
│  + Approval  │
│  + KB Search │
└──────────────┘
```

---

## Implementation Units

### U1. 服务端 LLM 网关 API

**Goal**: 在服务端新增 LLM 代理端点，接收客户端请求并转发到 LLM Provider，记录用量。

**Dependencies**: 无（首个单元）

**Files**:
- `src/agentkit/server/routes/llm_gateway.py`（新建）
- `src/agentkit/server/app.py`（注册路由）
- `tests/unit/test_llm_gateway.py`（新建）

**Approach**:
- 新增 `POST /api/v1/llm/chat`（非流式）和 `POST /api/v1/llm/chat/stream`（SSE 流式）
- 复用现有 `LLMGateway.chat()` 和 `chat_stream()`
- 流式端点将 `StreamChunk` 序列化为 SSE 格式（`data: {json}\n\n`）
- 记录每次调用的 user_id、model、tokens、cost 到 `llm_usage_records` 表
- JWT 认证（U2 完成前临时用 API Key）

**Test scenarios**:
- 非流式 chat 请求返回正确的 `LLMResponse` JSON
- 流式 chat 请求返回 SSE 格式，每个 chunk 是 `data: {json}\n\n`
- 无效 model 返回 404
- LLM Provider 失败时返回 502
- 用量记录正确写入数据库（model, tokens, cost）
- 无认证时返回 401

**Checkpoint CP1**: 服务端 LLM 网关可独立运行，通过 curl 验证流式和非流式调用。

---

### U2. JWT 认证模块

**Goal**: 实现用户注册/登录/JWT 签发验证，替换当前纯 API Key 认证。

**Dependencies**: U1

**Files**:
- `src/agentkit/server/auth/__init__.py`（新建）
- `src/agentkit/server/auth/models.py`（新建，UserModel + UserApiKeyModel）
- `src/agentkit/server/auth/jwt_utils.py`（新建，JWT 签发/验证）
- `src/agentkit/server/auth/password.py`（新建，bcrypt 哈希）
- `src/agentkit/server/auth/middleware.py`（新建，AuthMiddleware）
- `src/agentkit/server/auth/dependencies.py`（新建，require_permission 依赖）
- `src/agentkit/server/routes/auth.py`（新建，login/refresh/logout/me）
- `src/agentkit/server/app.py`（注册路由 + 中间件替换）
- `tests/unit/test_auth.py`（新建）

**Approach**:
- `UserModel`: id, username, email, password_hash, role, is_active, is_terminal_authorized, is_server_terminal_authorized
- `UserApiKeyModel`: user_id, key_hash(SHA256), key_prefix, name, expires_at, is_revoked
- JWT: access_token(15min) + refresh_token(7d)，HS256 签名
- AuthMiddleware: JWT 优先 → API Key → 兼容 clients.yaml → dev mode 放行
- `require_permission(*perms)`: FastAPI 依赖注入，检查 `request.state.current_user` 的角色权限
- 初始 admin 引导: 首次启动时从 `AGENTKIT_ADMIN_USERNAME/PASSWORD` 创建 admin 用户
- 数据库: V1 用 SQLite（复用 aiosqlite），后续切换 PostgreSQL

**Test scenarios**:
- 注册/登录返回 access_token + refresh_token
- refresh_token 可刷新 access_token
- 错误密码返回 401
- JWT 过期后返回 401，refresh 后可用
- API Key 认证识别用户身份
- 无认证 dev mode 放行
- 无认证生产 mode 返回 401
- `require_permission` 正确拒绝/放行
- 初始 admin 创建成功

**Checkpoint CP2**: 可通过登录 API 获取 JWT，使用 JWT 访问 LLM 网关。

---

### U3. RemoteLLMProvider（客户端 LLM 代理）

**Goal**: 在客户端新增 `RemoteLLMProvider`，通过服务端 LLM 网关调用 LLM。

**Dependencies**: U1, U2

**Files**:
- `src/agentkit/llm/remote_provider.py`（新建）
- `src/agentkit/llm/gateway.py`（增加 RemoteLLMProvider 注册支持）
- `tests/unit/test_remote_provider.py`（新建）

**Approach**:
- `RemoteLLMProvider(LLMProvider)`: 实现 `chat()` 和 `chat_stream()`
- `chat()`: POST `/api/v1/llm/chat`，带 `Authorization: Bearer {jwt}`
- `chat_stream()`: POST `/api/v1/llm/chat/stream`，解析 SSE 流为 `StreamChunk`
- 构造函数接收 `server_url` + `auth_token_provider`（callable，返回当前 JWT）
- 错误处理: 401 触发 token 刷新重试，502 抛 `LLMProviderError`
- 客户端 `LLMGateway` 配置为使用 `RemoteLLMProvider` 时，不加载本地 Provider 配置

**Test scenarios**:
- `chat()` 正确发送请求并解析 `LLMResponse`
- `chat_stream()` 正确解析 SSE 流为 `StreamChunk` 序列
- 401 响应触发 token 刷新重试
- 502 响应抛出 `LLMProviderError`
- 网络超时抛出异常
- `is_final=True` 的 chunk 正确标记

**Checkpoint CP3**: 客户端 Agent 通过 RemoteLLMProvider → 服务端网关 → LLM Provider 完成对话。

---

### U4. 前端认证 + 连接模式切换

**Goal**: 前端实现登录页、JWT 管理、API Client 附加 JWT、连接模式切换。

**Dependencies**: U2, U3

**Files**:
- `src/agentkit/server/frontend/src/stores/auth.ts`（新建）
- `src/agentkit/server/frontend/src/api/base.ts`（改造，附加 JWT）
- `src/agentkit/server/frontend/src/views/LoginView.vue`（新建）
- `src/agentkit/server/frontend/src/router/index.ts`（路由守卫）
- `src/agentkit/server/frontend/src/api/auth.ts`（新建）
- `src/agentkit/server/frontend/src/App.vue`（启动时初始化认证）

**Approach**:
- `auth` store: user, accessToken, refreshToken, permissions, hasPermission(), canAccessTerminal()
- `BaseApiClient.request()`: 自动附加 `Authorization: Bearer {token}`，401 时自动刷新
- `BaseApiClient.createWebSocket()`: 附加 `?token={jwt}` 查询参数
- 登录页: 用户名 + 密码 → 调用 `/api/v1/auth/login` → 存储 JWT → 跳转主页
- 路由守卫: 未登录跳转 `/login`，权限检查
- `initApiBaseURL()`: Tauri 模式检查 localStorage 中的远程服务器配置

**Test scenarios**:
- 登录成功后 JWT 存入 localStorage，跳转主页
- 登录失败显示错误提示
- API 请求自动附加 JWT
- 401 响应自动触发 token 刷新
- token 刷新失败跳转登录页
- WebSocket 连接附加 token 参数
- 路由守卫正确拦截未登录用户

**Checkpoint CP4**: 前端登录 → JWT 认证 → 通过服务端 LLM 网关对话，全链路打通。

---

### U5. 权限模型 + RBAC 中间件

**Goal**: 实现三级 RBAC + 权限位，集成到所有路由。

**Dependencies**: U2

**Files**:
- `src/agentkit/server/auth/permissions.py`（新建，Permission enum + ROLE_PERMISSIONS）
- `src/agentkit/server/auth/dependencies.py`（完善 require_permission）
- `src/agentkit/server/routes/portal.py`（增加权限检查）
- `src/agentkit/server/routes/terminal.py`（增加权限检查）
- `src/agentkit/server/routes/settings.py`（增加权限检查）
- `src/agentkit/server/routes/kb_management.py`（增加权限检查）
- `tests/unit/test_permissions.py`（新建）

**Approach**:
- `Permission` enum: CHAT, KB_QUERY, KB_WRITE, WORKFLOW_EXECUTE, TERMINAL_LOCAL_USE, TERMINAL_SERVER_USE, TERMINAL_WHITELIST_MANAGE, USER_MANAGE, SYSTEM_CONFIG
- `ROLE_PERMISSIONS`: member / operator / admin 权限映射
- `require_permission(*perms)`: 检查 `request.state.current_user` 的角色权限
- 现有路由增加 `Depends(require_permission(Permission.XXX))`
- dev mode（无用户）: 终端等高危功能仍需认证，其他放行

**Test scenarios**:
- member 角色可对话但不可访问终端
- operator 角色可使用本地终端但不可管理用户
- admin 角色可访问所有功能
- 未认证用户在 dev mode 可对话但不可用终端
- 权限不足返回 403

**Checkpoint CP5**: 不同角色用户访问不同功能，权限检查生效。

---

### U6. 本地终端白名单 + 审计上报

**Goal**: 本地终端增加三层白名单机制 + 危险命令审计上报。

**Dependencies**: U5

**Files**:
- `src/agentkit/server/routes/terminal.py`（改造白名单逻辑）
- `src/agentkit/server/auth/models.py`（增加白名单表模型）
- `src/agentkit/server/routes/terminal_whitelist.py`（新建，白名单管理 API）
- `src/agentkit/server/app.py`（注册白名单路由）
- `tests/unit/test_terminal_whitelist.py`（新建）

**Approach**:
- 白名单匹配优先级: 黑名单 → 内置安全白名单 → 用户白名单 → 会话白名单 → 危险检测
- 用户白名单存储在服务端 DB（`terminal_whitelist_user` 表），客户端缓存
- 黑名单存储在服务端 DB（`terminal_blocklist` 表），admin 管理
- 危险命令确认后可选加入会话白名单（不持久化到用户白名单）
- 危险命令执行后上报审计日志到服务端（`terminal_audit_logs` 表）
- 白名单管理 API: GET/POST/DELETE `/api/v1/terminal/whitelist/user`

**Test scenarios**:
- 内置安全白名单命令直接执行
- 用户白名单命令直接执行
- 会话白名单命令直接执行
- 黑名单命令被拒绝
- 危险命令弹出确认，确认后执行
- 危险命令拒绝后不执行
- 危险命令审计日志正确上报
- 白名单 CRUD API 正常工作

**Checkpoint CP6**: 本地终端白名单生效，危险命令需确认且审计上报。

---

### U7. 配置同步引擎

**Goal**: 客户端从服务端同步技能/Agent/工作流配置。

**Dependencies**: U2

**Files**:
- `src/agentkit/server/routes/config_sync.py`（新建，配置同步 API）
- `src/agentkit/server/app.py`（注册路由）
- `src/agentkit/client/sync.py`（新建，客户端同步引擎）
- `tests/unit/test_config_sync.py`（新建）

**Approach**:
- 服务端 API: `GET /api/v1/config/version`（返回版本号）+ `GET /api/v1/config/skills|agents|workflows`（返回配置列表）
- 客户端 `ConfigSync`: 启动时全量拉取 → 每 5 分钟轮询版本号 → 版本变化时全量拉取
- 配置缓存在本地 SQLite（`config_cache` 表）
- 离线时使用本地缓存

**Test scenarios**:
- 启动时全量拉取配置
- 版本号未变化时不拉取
- 版本号变化时全量拉取
- 离线时使用本地缓存
- 配置正确更新到本地

**Checkpoint CP7**: 客户端启动后自动同步服务端配置，离线时使用缓存。

---

### U8. 服务端终端 + 审批机制

**Goal**: 新增服务端终端模式，非白名单危险命令需 admin 审批。

**Dependencies**: U5, U6

**Files**:
- `src/agentkit/server/routes/terminal_server.py`（新建，服务端终端 WebSocket）
- `src/agentkit/server/auth/models.py`（增加审批表模型）
- `src/agentkit/server/routes/terminal_whitelist.py`（增加全局白名单 + 审批 API）
- `src/agentkit/server/app.py`（注册路由）
- `tests/unit/test_terminal_server.py`（新建）

**Approach**:
- `WS /api/v1/terminal/server/ws`: 服务端 PTY WebSocket
- 权限检查: `TERMINAL_SERVER_USE` + `is_server_terminal_authorized`
- 命令执行流程: 黑名单 → 内置白名单 → 全局白名单 → 会话白名单 → 非白名单需 admin 审批
- 审批机制: 创建 `terminal_approvals` 记录 → WebSocket 推送 admin → admin 批准/拒绝 → 执行/取消
- 审批超时: 5 分钟自动拒绝
- 全量审计: 所有命令（含执行/拒绝/审批）记录到 `terminal_audit_logs`
- 会话隔离: 每个用户独立 PTY 会话，工作目录隔离
- 资源限制: 最大并发会话数、会话超时

**Test scenarios**:
- 有权限用户可连接服务端终端
- 无权限用户被拒绝
- 全局白名单命令直接执行
- 非白名单危险命令创建审批请求
- admin 批准后命令执行
- admin 拒绝后命令取消
- 审批超时自动拒绝
- 所有命令审计日志记录
- 会话隔离正确

**Checkpoint CP8**: 服务端终端可用，非白名单命令需 admin 审批，全量审计。

---

### U9. 前端终端双模式 UI + 白名单管理

**Goal**: 前端终端支持本地/服务端模式切换，白名单管理页面。

**Dependencies**: U6, U8

**Files**:
- `src/agentkit/server/frontend/src/components/terminal/TerminalPanel.vue`（改造，模式切换）
- `src/agentkit/server/frontend/src/components/terminal/WhitelistManager.vue`（新建）
- `src/agentkit/server/frontend/src/api/terminal.ts`（改造，支持双模式）
- `src/agentkit/server/frontend/src/stores/auth.ts`（增加 canUseServerTerminal）

**Approach**:
- 终端面板顶部: `[本地终端] [服务端终端]` Tab 切换
- 本地终端: 连接本地 sidecar WebSocket
- 服务端终端: 连接服务端 WebSocket（带 JWT）
- 非白名单命令: 本地终端用户确认 / 服务端终端显示"等待审批"
- 白名单管理页: Tab（我的白名单 / 全局白名单 / 黑名单）

**Test scenarios**:
- 本地终端模式正常执行命令
- 服务端终端模式正常执行命令
- 模式切换正确切换 WebSocket 连接
- 无权限用户看不到服务端终端 Tab
- 白名单管理 CRUD 正常工作
- 危险命令确认对话框正确显示

**Checkpoint CP9**: 前端双模式终端可用，白名单管理页面正常。

---

## Checkpoint Summary

| 检查点 | 验证内容 | 通过标准 |
|--------|---------|---------|
| CP1 | 服务端 LLM 网关 | curl 验证流式和非流式调用 |
| CP2 | JWT 认证 | 登录获取 JWT，使用 JWT 访问 API |
| CP3 | 客户端 LLM 代理 | 客户端 Agent 通过服务端网关完成对话 |
| CP4 | 全链路打通 | 前端登录 → JWT → 服务端 LLM → 对话 |
| CP5 | 权限模型 | 不同角色访问不同功能，权限检查生效 |
| CP6 | 本地终端白名单 | 危险命令需确认且审计上报 |
| CP7 | 配置同步 | 客户端自动同步服务端配置 |
| CP8 | 服务端终端 | 非白名单命令需 admin 审批 |
| CP9 | 前端双模式终端 | 双模式切换 + 白名单管理 |

## Risks & Dependencies

| 风险 | 影响 | 缓解 |
|------|------|------|
| 流式 fallback 跨网络语义变化 | 客户端可能看到内容跳变 | 服务端首 chunk 前完成 fallback，首 chunk 后不 fallback |
| `_verify_api_key` 全局替换遗漏 | 部分路由无权限检查 | 代码审查 + 测试覆盖 |
| clients.yaml 兼容 | 两套认证并存增加复杂度 | V1 保留兼容，V2 废弃 |
| Tauri sidecar 远程地址注入 | 前端 API 调用目标变化 | base.ts 支持双目标 |
| 专家团 LLM 调用放大延迟 | 多 expert 串行 × RTT | 服务端网关连接复用 |

## Verification Strategy

每个检查点（CP）必须通过以下验证才能进入下一阶段：
1. **单元测试**: 新增代码的单元测试全部通过
2. **集成测试**: 关键链路的集成测试通过
3. **手动验证**: 按检查点描述的手动验证步骤通过
4. **回归测试**: `pytest tests/unit/ -x -q` 不新增失败（已知 `test_rewoo_agent_yaml_loads` 除外）
5. **前端类型检查**: `npx vue-tsc --noEmit` 通过
6. **代码规范**: `ruff check` 改动文件无新增错误
