fischer-agentkit/docs/plans/2026-06-19-003-feat-enterpr...

449 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 sidecarLLM 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/OIDCV1 本地账号V2 预留接口)
- 代码库语义索引
- 多租户隔离(表预留 tenant_idV2 启用)
- 多设备会话同步
## 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 过期后返回 401refresh 后可用
- 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` 改动文件无新增错误