19 KiB
| title | type | status | created | completed | origin | deepened |
|---|---|---|---|---|---|---|
| feat: Enterprise Client-Server Architecture Evolution | feat | completed | 2026-06-19 | 2026-06-19 | docs/plans/2026-06-19-002-enterprise-client-server-architecture-plan.md | 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 请求返回正确的
LLMResponseJSON - 流式 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_authorizedUserApiKeyModel: 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()正确发送请求并解析LLMResponsechat_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:
authstore: 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:
Permissionenum: CHAT, KB_QUERY, KB_WRITE, WORKFLOW_EXECUTE, TERMINAL_LOCAL_USE, TERMINAL_SERVER_USE, TERMINAL_WHITELIST_MANAGE, USER_MANAGE, SYSTEM_CONFIGROLE_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)必须通过以下验证才能进入下一阶段:
- 单元测试: 新增代码的单元测试全部通过
- 集成测试: 关键链路的集成测试通过
- 手动验证: 按检查点描述的手动验证步骤通过
- 回归测试:
pytest tests/unit/ -x -q不新增失败(已知test_rewoo_agent_yaml_loads除外) - 前端类型检查:
npx vue-tsc --noEmit通过 - 代码规范:
ruff check改动文件无新增错误