--- 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` 改动文件无新增错误