186 lines
8.5 KiB
Markdown
186 lines
8.5 KiB
Markdown
# 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. 提问「列出工作目录里有哪些文件」→ 应看到工具卡出现、执行、结果回填、模型总结
|