# YCode 落地方案 > 历史初始方案。当前运行架构优先使用 OMP RPC sidecar,自建网关保留为回退;现行目标是借助 REA 进行视觉、交互与功能 1:1 复刻。实际完成度见 `接手状态-2026-10-15.md`;本文件旧里程碑和排除项不作为缩减范围的依据。 **日期**:2026-10-14 **范围**:Electron 桌面应用 · 第一个里程碑 = 最小端到端闭环(能对话 + 能跑一个工具)· 自建 OpenAI 兼容网关 --- ## 0. 前置决策(已确认) | 项 | 决策 | 理由 | | --- | --- | --- | | 产品形态 | Electron 桌面应用 | 与对标形态一致,可复用同批开源 UI 栈 | | 首里程碑 | 最小端到端闭环 | 一次打通 UI ↔ Agent ↔ 网关 ↔ 工具,最快验证价值 | | 模型接入 | 自建网关(自有 API Key,OpenAI 兼容) | 协议可控、供应商可换、密钥不出主进程 | | 产物策略 | **clean-room** | 不复制对标产品的源码 / CSS / 资源,只用其公开架构与交互模式 | --- ## 1. 目标架构 ``` ┌──────────────────────────────────────────────────────┐ │ Renderer(React 19 + Zustand) │ │ 对话区 · 右栏上下文 · 设置弹窗 │ │ ⚠ 无 Node / fs / 网络能力 │ └───────────────┬──────────────────────────────────────┘ │ contextBridge(preload 白名单 API) │ invoke: config/session/chat │ event : chat:event(流式增量) ┌───────────────▼──────────────────────────────────────┐ │ Main(Electron 主进程 · ESM) │ │ ├ ipc.ts 唯一对外边界 │ │ ├ config.ts safeStorage 加密 Key │ │ ├ sessions.ts 会话与消息持久化 │ │ ├ agent/loop.ts Agent 循环(模型 ↔ 工具) │ │ └ gateway/ 自建网关(OpenAI 兼容 + SSE) │ └───────────────┬──────────────────────────────────────┘ │ HTTPS Bearer(Key 只在此处出现) ▼ 自有上游(OpenAI 兼容 /chat/completions) ``` ### 关键设计点 1. **单向分层**:`renderer → preload → ipc → agent → gateway → 上游`。反向只有 `chat:event` 一条流式通道。 2. **密钥不出主进程**:渲染进程只能拿到 `{ hasApiKey, keyStorage }`,永远拿不到明文。 3. **循环与工具解耦**:`loop.ts` 只管编排,工具是纯函数 `(args, ctx) => ToolResult`,新增工具只需改 `tools/index.ts` 注册表。 4. **网关只管协议**:模型路由、上下文裁剪、重试策略不放网关,后续可独立演进。 --- ## 2. 一次回合的完整链路 ``` 用户按 Enter → renderer: chat.send({sessionId, text}) → ipc: 落库用户消息 + 占位助手消息,返回 {runId, assistantMessageId} → loop: 拼 system + 历史 + 本轮 → gateway.stream() → gateway: POST /chat/completions (stream:true) → 解析 SSE ├ delta.content → emit {type:'text'} → 界面逐字追加 └ delta.tool_calls → 按 index 归并分片 → 若产生工具调用: emit tool_call → 执行工具 → emit tool_result → 把 assistant(tool_calls) + tool(result) 回填上下文 → 继续下一轮 → 无工具调用 → emit done → 更新助手消息终态 ``` 取消:`chat:cancel(runId)` → `AbortController.abort()` → 网关抛 `AbortError` → `emit cancelled`。 --- ## 3. 里程碑拆解 ### M1 · 最小端到端闭环(本次已落地) - [x] Electron 壳(`contextIsolation: true` / `sandbox: true` / 无 `nodeIntegration`) - [x] preload 白名单 API(CJS,因为沙盒要求) - [x] 自建网关:OpenAI 兼容 + SSE 流式 + 流式工具调用组装 - [x] Agent 循环:多轮工具调用、终止条件、取消 - [x] 工具:`list_dir`、`read_file`(只读 + 工作区逃逸防护) - [x] 配置持久化:`safeStorage` 加密 Key,明文降级时 UI 告警 - [x] 会话持久化 + 首条消息自动命名 - [x] 界面:会话列表 / 对话区 / 右栏上下文 / 设置弹窗 / 明暗主题 - [x] 类型检查 + 构建通过 ### M2 · 体验与可靠性 - [ ] Markdown 渲染(代码高亮、表格、KaTeX) - [ ] 消息级操作:复制、编辑重发、重新生成 - [ ] 工具卡分组折叠(连续调用合并成摘要) - [ ] 断流重试与错误分类提示 - [ ] 会话搜索与重命名 ### M3 · 能力扩展 - [ ] 工具扩展:`write_file` / `edit_file` / `run_shell`(带权限确认) - [ ] 审批流:写操作与命令执行需用户确认 - [ ] MCP 客户端接入 - [ ] Skill 加载(`SKILL.md` 约定) ### M4 · 平台化 - [ ] 迁移到 SQLite - [ ] 项目 / 工作目录多实例 - [ ] 右栏真实面板(产物预览、文件树、任务) - [ ] 打包分发(electron-builder,Windows 优先) --- ## 4. 安全基线 | 风险 | 处置 | | --- | --- | | 渲染进程被注入后提权 | `sandbox: true`、无 `nodeIntegration`、CSP 限制 `script-src 'self'` | | 密钥泄露 | 只在主进程;`safeStorage` 加密;日志不打印 | | 工具越权读写 | 路径解析后做 `relative()` 逃逸检查,越界直接拒绝 | | 外链钓鱼 | `setWindowOpenHandler` 一律 `deny` + `shell.openExternal` | | 任意命令执行 | M1 **不提供** shell 工具;M3 引入时必须走审批 | --- ## 5. 视觉对齐依据(对标实测值) 配色与排版**不再自拟**,全部取自两项实测证据: 1. 真实 WorkBuddy 运行时布局实测 —— `docs/reference/workbuddy-chat-layout.md`(CDP computed style / 元素矩形) 2. 安装包 token 表 —— `tokens-Df-i-zsO.css`、`contexts-BkjwYSZT.css` ### 调色板(浅色) | 用途 | 值 | | --- | --- | | 正文 / 主文字 | `#000` | | 次级 / 三级文字 | `rgba(0,0,0,.7)` / `rgba(0,0,0,.5)` | | 主背景 / 次背景 | `#fff` / `#f2f2f2` | | hover / active | `#ebebeb` / `#e6e6e6` | | 边框(主/次/三级) | `#e6e6e6` / `#ebebeb` / `#f2f2f2` | | 品牌 / 成功 / 警告 / 危险 / 信息 | `#00c29a` / `#0cbf5b` / `#ff7800` / `#f64041` / `#3370ff` | **注意:UI 主体是中性黑白灰,品牌绿只用于状态点缀**,不用于主按钮或大面积色块。 ### 圆角与间距 - 圆角:`sm 4px` / `md 6px` / `lg 8px` / 气泡 `16px` / 输入框 `24px` - 间距基准 4px;对话内容列左右内边距 `40px` ### 字号 | 角色 | 值 | | --- | --- | | caption / body | `12px` / `14px`(行高 18 / 21) | | 用户气泡 | `13px` / 19px | | Markdown 根 | `17.143px`,行高 `1.75`;段落 `.875em` → 15px / 26.25px | | 代码块 | `12.857px` / 22.5px,padding `12px 16px` | | 表头 | 底色 `#f7f7f7`,单元格 padding `8px 12px` | ### 结构尺寸 - 左栏 `280px`;对话内容列 `max-width 832px` 居中,左右 `padding 40px`(正文列 752px) - 助手头部:`24px` 圆形标识 + `10px` 间距,行高 `24.5px`,名称 `14px/600`,下间距 `12px` - 用户气泡:`#f2f2f2`、padding `8px 12px`、圆角 `16px 16px 0`、`min-width 40px`、`max-height 310px` - 输入容器:`1px solid #e6e6e6`、圆角 `24px`、白底 + 双层轻阴影;编辑区 `min-height 64px`、横向 `padding 16px`;工具栏 `56px`、按钮 `32px` - 滚动:消息列表 `flex:1 + min-height:0 + overflow-y:auto`,输入区 `flex-shrink:0` 固定底部 > 落地方式:CSS 使用**自有类名**(`.msg--assistant`、`.bubble` 等),仅取实测数值, > 不复制对标产品的类名与样式表结构。 --- ## 6. 与对标分析的关系 `docs/` 下三份分析报告(`WorkBuddy-AI-实现分析报告`、`WorkBuddy-AI-复刻可行性评估`、`WorkBuddy-UI复刻可行性评估`)用于**指导选型与交互模式**,不作为复制来源: - 可用:组件划分思路、交互状态机、协议约定、开源库清单 - 不可用:对方源码、CSS、图标与图片资源、文案 --- ## 7. 首次运行清单 1. `npm install && npm run dev` 2. ⚙ 设置:填 baseUrl / 模型名 / API Key;选工作目录 3. 提问「列出工作目录里有哪些文件」→ 应看到工具卡出现、执行、结果回填、模型总结