项目全景架构白皮书
ReinAgent 的完整系统分层、组件职责、状态流转与持久化键名(仓库内 PROJECT_CONTEXT.md 原文)。
docs/PROJECT_CONTEXT.md 的原文渲染版本,随代码同步更新。原文以仓库文件为准。文档定位:供后续开发 Agent 与工程师快速接手本项目的单点真相全景指南(Single Source of Truth)。涵盖系统定位、架构分层、核心交互规范、最新进度、关键状态流转及避坑指南。
配套文档:PROMPTS.md(提示词单一真相源)、ROADMAP.md(功能差距分析与历史路线图归档)。
一、项目定位与核心使命
ReinAgent 是一个以 Local-First 为核心理念的高性能、自主可控的 AI 辅助编程与桌面 Agent 工作台。
- 核心技术栈:Tauri 2 (Rust 后端) + React 19 + TypeScript + Tailwind CSS v4 + Zustand + Lexical 富文本编辑器。
- 设计与体验对齐:全面参考顶级 AI 编程客户端(重点参考 ZCode 与 LiveAgent),在保持极致性能(低内存、毫秒级响应)的同时,实现高度可定制的交互体验。
二、关键参考源码与规范(⚠️ 必须严格遵守)
根据项目全局规范,严禁对任何打包文件(如 .asar 或二进制产物)进行反编译。研究实现原理与交互细节时,必须直接查阅以下本地源码仓库:
- ZCode 完整源码:
- 路径:
/home/web3claw/DevCode/ReinAgent/ZCode - UI 组件库:
/home/web3claw/DevCode/ReinAgent/ZCode/packages/ui/- 侧边栏与布局:
packages/ui/src/WorkspaceSidebar.tsx、WorkspaceSidebar/WorkspacePurposeSection.tsx - 提示与交互:
packages/ui/src/ControlHintTooltip.tsx - 偏好持久化:
packages/ui/src/lib/sidebarPurposeSectionPreferences.ts
- 侧边栏与布局:
- 核心前端包:
packages/与apps/
- 路径:
- LiveAgent 完整源码:
- 路径:
/home/web3claw/DevCode/ReinAgent/LiveAgent - Rust / Tauri 后端与工具实现:
crates/ - 客户端与生态接入:根目录及相关实现
- 路径:
三、系统架构与分层设计
ReinAgent 架构全景
├── Frontend (React 19 + TypeScript + Tailwind CSS v4)
│ ├── src/App.tsx # 根容器、全局快捷键、主视口分流 (Workbench / Settings)
│ ├── src/store/useAppStore.ts # 全局响应式状态 (主题、语言、任务、工作区项目、终端控制)
│ ├── src/i18n/index.ts # 轻量级中英文国际化双语字典 (zh-CN / en-US)
│ ├── src/components/
│ │ ├── sidebar/
│ │ │ ├── WorkspaceSidebar.tsx # 侧边栏主体:品牌 Header、快捷操作、分组/项目 Tab、用户信息
│ │ │ └── ProjectList.tsx # 项目与任务类目:折叠展开、长按拖拽重排、Tooltip 触发、时间流
│ │ ├── chat/
│ │ │ ├── LexicalComposer.tsx # 输入胶囊:附件/图片粘贴、模型切换、推理等级、审批模式下拉
│ │ │ ├── Composer.tsx # 输入胶囊壳(附件条、Lightbox 挂载)
│ │ │ ├── MessageList.tsx # 会话流列表:虚拟化 + live tail 拆分 + 贴底跟随状态机
│ │ │ ├── MessageItem.tsx # 消息渲染(Markdown、图片气泡、错误详情、重试按钮)
│ │ │ ├── TurnGroupView.tsx # 回合分组:状态条/重连副行/查阅聚合卡/文件更改摘要卡
│ │ │ ├── ThinkingBlock.tsx # 思考块三态折叠
│ │ │ ├── ToolCallCard.tsx # 工具卡(类型化 header、终端卡、diff 视图)
│ │ │ ├── ApprovalCard.tsx # 工具审批卡(允许/总是允许/拒绝)
│ │ │ ├── RetryDetailsBlock.tsx # 重试详情折叠块(每次尝试错误原文卡片)
│ │ │ ├── ConversationNavigator.tsx # 对话问题导航条
│ │ │ ├── SessionStatsBar.tsx # 会话统计行(轮/步/上下文/耗时/token)
│ │ │ ├── ContextUsageIndicator.tsx # 上下文容量圆环触发器 + HoverCard 面板
│ │ │ ├── ImageLightbox.tsx # 图片放大浮层(附件缩略图与气泡共用)
│ │ │ ├── MarkdownText.tsx / MarkdownBlockRenderer.tsx / CodeBlock.tsx # Markdown 渲染链
│ │ │ └── EmptyState.tsx # 欢迎页、快捷动作卡片
│ │ ├── terminal/
│ │ │ └── TerminalPane.tsx # XTerm 终端集成与底栏展示
│ │ ├── settings/
│ │ │ └── SettingsPage.tsx # 模型服务商、API Key、端点配置页面
│ │ └── ui/
│ │ └── Tooltip.tsx # 基于 @radix-ui/react-tooltip 的通用精致提示浮层
│ └── src/lib/
│ ├── chat/ # conversationPool(会话池)、conversationController(调度+重试裁决)、
│ │ # conversationModel(纯状态机)、stateBridge、errors(错误链展开/诊断)、
│ │ # turnActivity(回合分组)、useConversation、titleGenerator、contextUsage
│ ├── providers/ # runAgentTurn(系统提示词+审批门)、modelFactory、catalog、fauxSource
│ └── agent/ # agentRuntime(Agent 循环)、tools(五工具)、workspace(路径决议)
└── Backend (Tauri 2 / Rust)
└── src-tauri/
├── src/main.rs # Tauri 入口、窗口管理
├── src/lib.rs # 命令注册
├── src/terminal.rs # 伪终端 (PTY) 会话管理与流转发
└── src/fs_cmd.rs # 异步受控文件与命令执行 (spawn_blocking 隔离防窗口卡死)
四、核心交互与 UI 规范现状
1. 侧边栏类目与拖拽排序(对齐 ZCode PurposeSection 原理)
- 默认排序:
项目在上,任务在下。 - 折叠展开持久化:
项目与任务均可通过左侧ChevronDown / ChevronRight独立展开与折叠。- 项目折叠状态保存于
localStorage["reinagent-projects-section-expanded"]。 - 任务折叠状态保存于
localStorage["reinagent-tasks-expanded"]。
- 长按/拖拽手柄互换顺序:
- 手柄为 6 点图标(
GripVertical),移除了原生易产生视觉干扰的title="Reorder"。 - 鼠标在手柄上按住(
onMouseDown)并上下拖移超出阈值时,实时交换两者的渲染次序,带有高亮过渡。 - 排序记忆保存于
localStorage["reinagent-sidebar-section-order"],跨会话自动恢复。
- 手柄为 6 点图标(
- 精致悬浮提示(Radix Tooltip):
- 项目右侧
+按钮:悬停显示新建项目/New Project。 - 任务右侧
MessageSquarePlus按钮:悬停显示新建任务/New Task。
- 项目右侧
2. 任务与项目生命周期与隔离架构(对齐 ZCode 会话模型)
- 多任务独立隔离与会话切换恢复(Session Isolation & Instant Task Switching):
- 每个任务独立拥有持久化的消息流(
reinagent-task-msg-${taskId}),点击左侧侧边栏的任意历史任务时,主视图无缝切换至该任务,并精准恢复其保存在localStorage中的完整对话历史(含 Markdown 问答、思考过程与工具调用结果)。 - 加固了切换快照防御机制:采用
currentMessagesRef与非空校验守卫,防止在新建任务或切换离开时被clear()后的空状态误覆盖历史记录;切换后输入框无缝接续为后续多轮追问模式。
- 每个任务独立拥有持久化的消息流(
- 草稿态与懒创建机制(Draft Mode & Lazy Creation):
- 点击任何「新建任务」(侧边栏快捷操作、项目右侧
+或Ctrl+N)不直接生成任务,而是回到首页 EmptyState 草稿态(activeTaskId = null)。 - 若在某个具体项目下点击新建,项目下拉框自动预选该项目;若在全局新建,则默认“不在项目中工作”。
- 自动将光标焦点置入底部输入框,待用户发送第一句后,才正式生成
AppTask并分配taskId写入存储。
- 点击任何「新建任务」(侧边栏快捷操作、项目右侧
- 标题生成策略(三级标题保障体系):
- 一级(首句即时兜底):发送首句瞬间,前 30 字符即时作为初始任务标题挂载,确保侧边栏立刻可见;
- 二级(异步 AI 智能提炼):后台 sidecar 异步触发模型总结,自动识别配置的 Provider(DeepSeek、OpenAI、Anthropic Claude、Google Gemini、Ollama 等)并适配对应的 REST 协议与鉴权头。严格遵循 3~7 词摘要约束与 think 过滤,提炼完成后平滑更新任务标题并同步落盘;
- 三级(本地规则语义提炼降级):若在无 Key 演示模式、弱网断网或 API 异常时,自动执行本地语义提炼(剥离口语前缀“你好/请帮我/请问”、去除末尾问号标点),截取核心动宾短语,确保即便在无 Key 状态下任务标题依然精简可读。
- 任务与项目 CRUD 交互规范(深度对标 ZCode):
- 任务置顶(Pin / Unpin):
- 鼠标悬停任务行时左侧显示图钉图标(
Pin),点击自由切换置顶状态; - 已置顶任务图钉常驻显示且带有品牌色高亮,列表中所有置顶任务优先固定在最顶部展示;
- 属性字段
pinned?: boolean同步落盘至reinagent-tasks。
- 鼠标悬停任务行时左侧显示图钉图标(
- 行内二次确认删除(无阻塞式):
- 悬停点击删除垃圾桶按钮时,不调用任何系统级阻塞弹窗(
window.confirm); - 右侧原地平滑切换为红色圆角胶囊「确认」(
Confirm)按钮与取消「✕」按钮; - 点击「确认」正式执行删除并清理消息分片;按
Esc键或在列表外部点击任意位置自动安全回退取消。
- 悬停点击删除垃圾桶按钮时,不调用任何系统级阻塞弹窗(
- 任务行内重命名:支持悬停点击编辑图标触发行内输入框,回车或失焦确认更新。
- 自定义项目管理:支持添加自定义项目、重命名(级联更新已有任务的项目归属)以及删除项目。
- 顶栏清爽化:移除了原有的全局「清空对话」按钮,避免打断当前任务或误清空多会话上下文。
- 任务置顶(Pin / Unpin):
3. 双层胶囊输入框(LexicalComposer)与推理等级调度
-
下层:Lexical 编辑区、
@触发上下文菜单、/触发命令菜单、变更前确认模式(✋)、模型快捷切换下拉菜单(Model Selector Dropdown)、推理等级选择器、发送/中止按钮。 -
模型快捷切换菜单(Model Selector Dropdown)与多任务独立模型记忆机制:
- 点击底栏
{服务商}/{模型名称}按钮弹出向上浮动的精致卡片菜单(与推理等级弹窗风格统一); - 动态聚合已启用的模型:按服务商(如 DeepSeek、OpenAI、自定义等)分组展示其下
enabled !== false的全部模型; - 技术指标丰富呈现:每一项展示模型名称、紧凑上下文窗口徽标(如
1M、128K)及[视觉]徽标;当前选中模型带有指示圆点与高亮; - 即时切换与会话级状态隔离:点击任意模型后,仅切换当前对话会话使用的服务商与模型,立即生效并联动推理等级;绝不修改或覆盖“服务商设置”里的系统默认模型(系统默认模型依然牢牢固定在设置页面中通过“设为系统默认”指定的模型上)。
- 任务级独立模型持久化与多窗口隔离(Task-Level Model Isolation & Persistence):
AppTask数据模型扩展可选字段providerId?: string; modelId?: string;,伴随任务列表自动持久化于reinagent-tasks;- 多任务窗口完全隔离:在具体任务窗口 A 切换模型时,触发
updateTaskModel(activeTaskId, providerId, modelId),仅更新并持久化当前任务绑定的模型,其他任务窗口不受任何影响; - 任务切换生命周期自动还原:侧边栏切换任务激活时,若目标任务已保存专属模型则精准还原呈现;若目标任务尚未绑定特定模型,或切换至草稿态(
activeTaskId === null),自动干净回退至系统全局默认配置; - 草稿建任务无缝附着:用户在草稿窗口切换特定模型后发送首条消息时,
createTask自动将当前选中的模型绑定给新创建的任务; - 新建任务(
+)彻底清空污染:点击新建任务时,不仅清空消息时间线,同时将会话模型重置回全局系统默认模型,杜绝上一个任务的定制模型外溢。
- 点击底栏
-
推理等级(EffortLevel / ThinkingLevel)与最大步数(maxSteps)动态映射:
- 6 档标准等级支持:对齐现代大模型规范,包含
default、low、medium、high、xhigh、max6 档; - 纯英文 UI 与首字母大写:聊天窗口菜单使用纯英文展示(
Default、Low、Medium、High、XHigh、Max),显示激活绿点●、粗体高亮及{steps} steps徽章; - 动态步数映射(2026-09-25 用户定档,每级递增 100 步):
default: 100 stepslow: 200 stepsmedium: 300 stepshigh: 400 stepsxhigh: 500 stepsmax: 600 steps
- 完全访问模式无步数上限:任务审批模式为
full时maxSteps传 0(agentRuntime仅在 >0 时启用硬闸;注意不能用undefined——那会在runAgentTurn回退成DEFAULT_MAX_STEPS=8),硬闸与「继续」机制不再触发;其余模式按上表生效 - 动态过滤与模型真实能力严格联动(No Fallback):
- 聊天窗口中,若当前模型声明了
effort.supportedLevels,严格仅显示该模型声明支持的档位,不支持的等级(包括未声明时的Default)在列表中完全过滤隐藏,坚决不进行任何臆测兜底;例如 DeepSeek(支持["low", "high", "max"])的菜单中严格仅有Low、High、Max三项; - 模型编辑弹窗(
ModelEditModal)中,同样通过一排胶囊按钮展示支持的等级,并支持点击直接高亮并设为模型默认等级;若模型上游未声明 effort,可手动开启并从完整档位中选择; - 当聊天窗口切换推理等级时,动态同步更新该模型的默认等级并实时写入
provider_config.json;
- 聊天窗口中,若当前模型声明了
- 发送给大模型的实际透传机制:
- 当选定具体等级(
low/medium/high/xhigh/max)时,直接作为thinkingLevel(在 OpenAI / DeepSeek 中对应reasoning_effort)透传给后端 Agent 运行时; - 当选定为
default(或off)时,传给后端undefined,由服务端以模型原生的默认推理能力执行。
- 当选定具体等级(
- 步数硬闸触顶与一键「继续」机制:
- 当单轮工具调用/思考循环达到当前等级的步数上限时,
agentRuntime返回maxStepsReached: true,状态机为末条助手消息标上truncatedBy: "maxSteps"; MessageItem在展示「已达最大步数,本次回复已停止。」提示的同时,右侧提供精致的「继续」胶囊按钮(带有Play图标);- 用户点击后自动发送“请继续执行未完成的步骤”,无缝衔接上一轮未完成的编码/任务流。
- 当单轮工具调用/思考循环达到当前等级的步数上限时,
- 6 档标准等级支持:对齐现代大模型规范,包含
-
错误诊断与「重试」机制(2026-09-26 改版,对齐 LiveAgent):
- 对网络抖动/断连(
Connection error、failed to fetch、socket hang up、ECONNRESET等)精准识别并映射为友好的「网络错误:连接中断或无法连接服务,请检查网络或代理设置」; - 错误行只保留原文 +
errorHint双层展示;手动重试已迁移为 LiveAgent 语义:助手消息悬停动作栏 RotateCw 确认弹层(「重试该回复?」)→ 以原始提问截断重发该轮(见 四点七.2),旧的红色「重试」按钮与「请重试刚才失败的操作」补发逻辑已移除。
- 对网络抖动/断连(
-
活动任务 ID 持久化与刷新恢复机制:
activeTaskId实时写入reinagent-active-task-id本地持久化;- 页面刷新(F5 / Ctrl+R)或开发期 Vite 热重载重挂载时,自动精准恢复刷新前正在进行中的任务并加载完整历史消息,绝不退回主页草稿态。
-
原生文件夹选择与多项目管理(Native Folder Picker & Multi-project Management):
- Linux Webview 局限性与根因解决:在 Linux Webview 环境下,Web 原生的
<input type="file" webkitdirectory>受沙箱限制无法直接选目录且不提供绝对路径。对齐 LiveAgent 规范,在 Rust 端(src-tauri/src/fs_cmd.rs)实现fs_pick_folder,基于rfd调起原生 OS 文件夹选择对话框,直接获取并返回用户的系统绝对路径。 - Web + Tauri 2 双模兼容:在 Tauri 桌面端调用
invoke("fs_pick_folder");在 Headless Chrome / 浏览器等 Web 环境下平滑回退至<input type="file" webkitdirectory>,确保无头回归测试与 Web 调试全流程不崩。 - 路径自动持久化与工作区绑定:
- 无论是从输入框胶囊顶部的“打开文件夹”,还是侧边栏 Projects 标题栏的“
+新建项目”,选中的文件夹绝对路径立即通过addProject(path)写入reinagent-user-projects本地持久化; - 选中的路径立即设为当前任务的工作区,后续 Agent 文件读取、写入和命令终端执行完全限定在选定目录(或在未指定时 fallback 至
~/.ReinAgent/DefaultProject)。
- 无论是从输入框胶囊顶部的“打开文件夹”,还是侧边栏 Projects 标题栏的“
- 侧边栏项目树与任务分层(对标 ZCode):
- 侧边栏 Projects 分区动态渲染用户打开的项目列表,显示简洁的项目文件夹名,并通过 Tooltip 展示完整绝对路径;
- 支持项目级快捷操作:悬停点击
+在该项目下开启新任务,点击删除图标(Trash2)一键移除项目记录; - 支持展开/折叠项目查看其隶属的子任务,点击子任务直接跳转激活;
- 全局 Tasks 分区专注聚合展示未关联具体项目的通用任务,主次结构层次分明。
- Linux Webview 局限性与根因解决:在 Linux Webview 环境下,Web 原生的
-
多模型服务商管理体系(深度对标 ZCode ModelProviderSection):
- 双栏现代交互布局:
- 左侧服务商导航栏(ProviderNavigation):清晰划分“主流服务商(DeepSeek、OpenAI、Anthropic、Gemini、Ollama)”与“自定义服务商”分组,配备矢量专属 Logo 与状态指示圆点(🟢 已配置并启用 / ⚪ 未配置),右上角支持快捷“+ 添加服务商”;
- 添加/删除交互重构(2026-10-04,用户定稿去弹窗化):「+ 添加」不再弹 AddProviderDialog(该文件已删除)——直接在列表末尾追加空白服务商(名称自动编号「新服务商 N」防重名、apiFormat=openai-chat-completions、baseUrl/apiKey/模型留空、enabled=false)并自动选中,配置在右侧表单填写;删除按钮移至「已启用」开关左侧,所有服务商均可删(不再限 isCustom),confirm 确认后删除;删除系统默认服务商时默认自动切到剩余第一个(防 settings 悬空引用),删除后绿色 toast 反馈;
- 右侧服务商详情面板(ProviderDetailCard):包含服务商总控开关(Switch)、名称行内重命名、API 端点(Base URL)自定义、API 格式规范选择(深度对齐 ZCode 原生标准,包含
Chat Completions (/chat/completions)、Anthropic Messages (/v1/messages)、Responses (/responses)与Google Generative AI (/models))、密码显隐切换与官网获取 Key 快捷链接; - API 格式接入真实请求链路(2026-10-05,用户定稿:格式=真实协议,拒绝「下拉装饰化」):
- 类型唯一定义:
ApiFormatType定义在lib/providers/modelFactory.ts(设置页types.ts重导出,防两份联合类型漂移); - 主链路接线:
ProviderConfig.apiFormat→buildModel经API_FORMAT_TO_WIRE映射为model.api(openai-chat-completions/遗留openai-completions→openai-completions;openai-responses→openai-responses;anthropic/google 同名直透),缺省/非法值回落 catalog meta(旧行为);⚠ 两个 SDK 的 baseURL 约定不同,绝不能统一补/v1:OpenAI SDK 的 baseURL 必须含/v1(SDK 追加/chat/completions|/responses),Anthropic SDK 的 baseURL 必须不带/v1(SDK 自身追加/v1/messages,带/v1→/v1/v1/messages→ 404 page not found,2026-10-05 实测);getStreamFnForApi新增openai-responses分支(pi-ai/api/openai-responses的streamSimple,含wrapStreamWithProxiedFetch代理注入); - 全部调用点透传:主对话(App
buildTurnOptions)、记忆整理回落(App organizer)、记忆抽取回落(pool,整包透传自动继承)、独立记忆模型(modelResolution)、子代理钉选模型(subagentDefinitions)、✨提示词增强(LexicalComposer)、提交信息生成(CommitDialog)——凡从ProviderItem构造ProviderConfig处一律带apiFormat: item.apiFormat; - 切换格式即探测(Fail-Fast):API 格式下拉变更 → 立即用「测试连通性」同款真实请求探测新格式端点(选 defaultModelId 或首个启用模型),通过显示绿色横幅「端点探测通过 (Xms):对话将按「…」协议发送」,失败显示红色横幅携带完整真实错误(如 404)+「该服务商可能不支持…对话请求按此格式发送将失败」;无模型可测时跳过;
- 实测结论(2026-10-05):GonkaRouter
/v1/responses404 不存在(/v1/chat/completions与/v1/messages均 200 可用——它同时支持 OpenAI 与 Anthropic 协议);OpenCodeZen/v1/responses200 真实可用;真实回合 E2E 证实请求端点跟随存量格式(responses 格式下回合如实报 404,不再静默走 chat/completions;anthropic 格式修复双/v1后流式回复正常)。
- 类型唯一定义:
- buildModel/titleGenerator No-Fallback(2026-10-05 F2,全库不留静默设计专项):
buildModel配置不完整一律抛错——空 provider/空 modelId/自定义服务商(catalog 外 id)缺 baseUrl/缺有效 apiFormat 均抛错(旧行为:空 modelId →"deepseek-chat"、未知 provider 回落PROVIDERS[0]deepseek meta,把请求静默发往 api.deepseek.com 烧用户 Key);catalog 预设缺 baseUrl 走文档化官方端点(预设语义,保留)。catalog.findProviderMeta(provider)精确查找返回undefined(getProviderMeta的|| PROVIDERS[0]静默回落仅剩 UI 展示用途)。titleGenerator.generateSessionTitle:配置不完整/缺 apiFormat → 本地降级标题(不发请求不猜协议);协议判定改config.apiFormat驱动(custom id 误走 chat/completions 修复)+ 新增 responses 标题分支;调用点改用会话供应商(旧代码用 legacy settings,任务钉别家时标题静默打到另一家)。自动化派发补apiFormat透传。App 移除currentProviderMeta假名回退(custom 显示 "DeepSeek" → 显示真实 id)。测试:modelFactory F2-1 抛错矩阵 + titleGenerator F2-T1~5 mock-fetch 协议路由断言 + runAgentTurn 旧 fixture 补显式 provider,全库 325 绿。 - 服务商页选中态记忆(2026-10-05):左侧导航选中记忆在 localStorage
reinagent-provider-page-selected(与settings.provider系统默认严格分离,仅页面浏览记忆不改发请求对象);进入页面优先恢复,配置外部删改致失效时回退系统默认/首个并同步记忆;所有选中路径(导航点选/添加/删除回退)统一走setSelectedProviderId同步写。
- 细粒度模型管理、动态刷新与元数据编辑体系(深度对齐 ZCode 视觉交互规范):
- 模型列表行视觉规范(完全对齐 ZCode):
- 上下文大小 Badge:显示紧凑技术规格徽标(如
1M、2M、128K、64K、32K等),采用等宽字体与深底细边框; - 视觉能力 Badge(
[视觉]/[Vision]):当模型支持图像输入(supportsImage: true)时呈现圆角胶囊徽标,自适应国际化; - 模型连通性测通(🔌
Unplug图标):点击针对该模型独立发起轻量级握手测试,测通后呈现延迟毫秒数,异常精准报警; - 模型元数据编辑(✏️
Pencil图标):打开ModelEditModal模态弹窗,支持可视化修改模型 ID、显示名称、上下文 Token 窗口(提供 32K/64K/128K/200K/1M/2M 快捷预设)、最大输出 Token 数及是否支持视觉输入; - 模型删除(🗑️
Trash2图标):支持清理不需要的模型,带二次确认提示,若删除的是当前默认模型自动安全回退; - 启用/禁用 Switch 开关:平滑动效 Switch 滑块组件(取代旧复选框),关闭后不出现在会话模型切换菜单中;
- 上下文大小 Badge:显示紧凑技术规格徽标(如
- 真实数据与无假兜底原则(No Fallback & Fail-Fast Rule,项目核心铁律):
- 严禁臆测兜底:彻底拔除任何基于模型名称正则的硬编码猜测字典(如写死 DeepSeek 65.5K、写死各厂商窗口等);
- 100% 真实解析官方 API:在
parseProviderRawModels中真实完整提取服务端返回的context_window、max_output_tokens、supports_images/supports_image/input_modalities(是否包含image)等权威元数据,准确高亮[视觉]徽章; - 未提供即如实展示:若上游服务商未提供某字段,严禁捏造假数据,UI 必须明确展示为“未提供”或空,并允许用户手动配置;
- 出错即时完整提示:接口请求、网络异常或鉴权失败时,严禁静默降级或用旧数据掩盖,必须将完整错误文本即时反馈至界面;
- 动态远端模型刷新(对标 LiveAgent):在“添加模型”前配备“🔄 刷新模型列表”按钮,内置
fetchProviderModels探测引擎,直接请求标准/v1/models,去除任何 404 猜测与二次容错重试,严格真实报错(Fail-Fast); - Base URL 统一剥离
/v1与请求端自动补全规范(Base URL Normalization Rule):- 存储与展示规范:用户在服务商设置面板中输入或展示 Base URL 时,统一不带末尾的
/v1、末尾单斜杠或多余斜杠(例如http://192.168.3.27:8787、https://api.openai.com); - 输入失焦与写盘强制清洗(
cleanBaseUrl):若用户输入时带了/v1、/v1/、/或///,在输入框失焦以及写入provider_config.json磁盘时,自动剥离清洗干净; - 请求端自动追加
/v1(ensureV1BaseUrl):所有面向 OpenAI / Anthropic 兼容协议的网络请求(包括 Agent 聊天对话POST /v1/chat/completions、模型列表刷新GET /v1/models、连通性握手测试POST /v1/chat/completions以及会话标题后台生成),运行时底层自动补齐/v1,彻底杜绝POST /chat/completions → 404路由缺失问题,且绝不重复拼接成/v1/v1;
- 存储与展示规范:用户在服务商设置面板中输入或展示 Base URL 时,统一不带末尾的
- 智能增量合并(
mergeFetchedModels):- 自动拉取远端模型列表并去重增量追加,新拉取到的模型默认保持禁用状态(
enabled: false),避免一次性刷新几十个模型撑满聊天栏选择器,由用户自主按需点亮; - 手动添加模型(
ModelEditModal)时,初始开关保持enabled: true; - 已有模型的启用开关和自定义名称 100% 保持不变,同时用官方真实返回的最新规格指标补齐未配置项;
- 自动拉取远端模型列表并去重增量追加,新拉取到的模型默认保持禁用状态(
- 模型搜索与过滤:支持通过
🔍 搜索模型...输入框快速模糊过滤模型 ID 与名称; - 主流服务商默认模型清空:5 大主流预设服务商(DeepSeek、OpenAI、Anthropic、Gemini、Ollama)默认模型列表设为空(
models: []),初始呈现空引导文案,由用户通过“🔄 刷新模型列表”自动探测拉取或“+ 添加模型”自定义;
- 模型列表行视觉规范(完全对齐 ZCode):
- 向下兼容与后端磁盘持久化(对标 ZCode ~/.ReinAgent/provider_config.json):
- 多服务商完整配置由 Tauri/Rust 后端维护,直接独立持久化存储至宿主用户主目录下的
~/.ReinAgent/provider_config.json(避免浏览器 LocalStorage 容量限制或清理丢失); - 前端通过 Tauri IPC 命令
provider_config_load/provider_config_save实时异步读写; - 支持一键“设为默认”,自动将选中的服务商与模型映射回底层
settings.json(provider、modelId、apiKey、baseUrl),确保现有对话模型、代码生成与终端执行 100% 无缝衔接。
- 多服务商完整配置由 Tauri/Rust 后端维护,直接独立持久化存储至宿主用户主目录下的
- 双栏现代交互布局:
4. 对话问题导航条(ConversationNavigator,对齐 ZCode ConversationTurnNavigator)
- 布局与显隐:
- 覆盖式绝对定位(
absolute z-10)浮于消息滚动区左缘(App.tsx 中 hasMessages 分支外层relative flex包装层承载),不占布局空间,不挤压max-w-2xl居中的消息内容; - 显隐规则对齐 ZCode:用户提问 < 2 条或容器宽 < 864px 时整体隐藏(
NAVIGATOR_MIN_ITEMS/NAVIGATOR_MIN_VISIBLE_WIDTH); - 上下留白 48px / 96px(下部额外避让吸底输入胶囊)。
- 覆盖式绝对定位(
- 刻度粒度:每条用户提问一根刻度(
role === "user"),助手回复与工具调用不产生刻度,仅体现在悬停预览中; - 高度自适应规格(已确认):默认初始高度 240px 垂直居中;节点槽位 = 目标高度 ÷ 刻度数,双向钳制 [10px, 24px](最小间距 10px 与 ZCode 刻度槽位
h-2.5一致);当刻度数 × 10 > 可用高度时锁定 10px 并进入内部滚动(.navigator-rail隐藏滚动条); - 交互:
- 悬停:Radix Tooltip(复用既有
@radix-ui/react-tooltip依赖,Portal 防裁剪)弹出双段预览卡——用户提问预览 2 行 + 助手回复预览 3 行;220 字符 / 2 段截断(truncatePreview,纯文本不渲染 Markdown);流式中显示「生成中…」,无回复显示「(尚无回复)」; - 点击:手动计算
scrollTop平滑跳转(目标行距视口顶 16px 呼吸),prefers-reduced-motion时退化为瞬时定位;跳离底部后MessageList现有贴底跟随(120px 阈值)自然解除,零侵入; - 山峰衰减动效:悬停时刻度短横线按距离呈山峰状放大(scaleX 2.6 / 1.7 / 1.25,opacity 1 / 0.86 / 0.72,远处 1 / 0.58),
motion-reduce兼容;
- 悬停:Radix Tooltip(复用既有
- active 高亮:无 IntersectionObserver,scroll 事件(rAF 节流)+ 锚点内容坐标几何计算(
resolveActiveAnchor:取首个与视口相交的刻度行[start_i, start_{i+1}),无相交回退视口顶上方最近一条); - 实现分层:纯逻辑层
src/lib/chat/conversationNavigatorHelpers.ts(刻度构建 / 预览截断 / 自适应布局 / active 判定 / 山峰视觉,全部具名常量)+ 组件层src/components/chat/ConversationNavigator.tsx;用户消息行由MessageItem打data-msg-id锚点; - a11y:
role="navigation"、每刻度为 button(aria-label跳转文案、aria-current="location"、aria-posinset/setsize); - i18n 键:
turnNavigatorLabel(对话问题导航 / Conversation query map)、turnNavigatorJump(含{index}占位)、turnNavigatorEmptyReply、turnNavigatorRunning; - 测试:
conversationNavigatorHelpers.test.mjs17 用例(截断规则 / 刻度构建 / 布局映射表 / active 几何 / 山峰参数),已挂进bun run test:chat。
5. 回合工作状态展示(对齐 ZCode ConversationTurnGroup / Reasoning / ToolCallBlocks)
- 时间打点(数据层):
ChatMessage扩展纯展示字段startedAt/endedAt(assistant 轮与工具条目)、thinkingStartedAt/thinkingDurationMs(首个 thinking_delta 起点 → 首个 text_delta / thinking_end 冻结);由conversationController的注入时钟(deps.now)传入beginAssistant/applyEvent/applyLibraryEvent/finish/finishAborted(新增可选nowMs参数)。绝不参与toApiMessages; - 按轮分组:
groupTurns(messages)(src/lib/chat/turnActivity.ts)——user 消息开轮至下一条 user 前;轮工时 = 最早 startedAt → 最晚 endedAt(运行中 = now 现算);MessageList 按轮渲染并持有 1 秒setIntervallive tick(仅存在运行中轮时启动,对齐 ZCode 不用 rAF); - 运行中判定含会话级兜底(2026-09-24 修复闪烁):多步工具循环的轮与轮之间存在瞬态空窗(上一轮条目已 done、下一轮未建条目),仅凭条目状态判 running 会让状态条/思考块「展开→收缩→展开」闪烁——现由 App 透传
isStreaming,将流式中的最后一轮钉在 running(liveprop),整个 Agent 循环期间状态条恒为「工作中」、思考块恒展开,循环结束才统一翻转折叠; - 回合工作状态条(TurnGroupView):三态文案——运行中「工作中 {时长}」(锁定展开、按钮 disabled、不渲染箭头)/ 完成「已工作 {时长}」(默认折叠、可展开,运行→完成自动翻转折叠)/ 中止「已停止」;历史消息无打点时如实显示「已处理」,绝不伪造时长(No-Fallback);时长格式化
formatWorkDuration(Math.max(1, round)整秒、最多两段最靠前非零单位,中文「3 分 48 秒」/ 英文「3m 48s」); - 折叠体内容:思考块(ThinkingBlock)+ 工具调用卡(ToolCallCard)+ 中间叙述文本(非末条 assistant 的正文);最终回复正文(轮内最后一条 assistant)在轮结束后外显。时间线不变式(2026-09-27,修复卡片位置漂移):工具条目在状态机里诞生于所属 assistant 消息之后,运行中最后一条 assistant 的正文就地渲染在折叠体的时间线位置(亮色 + ChatLoading,样式对齐 MessageItem 正文;纯文本轮靠
liveAnswerInBody让折叠体仍渲染),轮结束后才由外显 MessageItem 接管——工具卡出现时必然落在正文下方且永不移动,彻底消除轮次切换时「卡片在简述上面↔下面」的跳动(对齐 ZCodeconversationTurnRenderUnits的 orderedRows 严格按产出顺序渲染,无「末条钉底」规则); - 思考块(ThinkingBlock):header「思考 · 持续了 N 秒」(整秒向上取整;无打点如实显示「持续了几秒」)+ 流式「正在思考」;默认收起;正文最暗文字层(
--text-dim+ 透明度)+ 左导线缩进 + 限高 240px 滚动 +whitespace-pre-wrap纯文本; - 查阅族独立卡(「查阅」聚合卡已按用户决策移除,2026-09-27):
list_dir/read_file不再聚合为组卡(EXPLORE_TOOL_NAMES/buildActivityItems/ExploreGroupCard已删除),直接以独立卡渲染——对齐 ZCodeReadToolCallBlock独立形态:类型标签(读取/列出,i18ntoolKindRead/List,运行中正在读取/正在列出扫光由 ToolLayout 自动附加)+ Material 文件/文件夹图标 chip + 文件名 + 父目录段次要文本(renderFilePath同款);成功不显示状态词,失败才显示「执行失败」(showFailureStatus+ tooltip 带错误原文,对齐 ZCode read 卡只传 showFailureStatus 的语义);read_filechip 可点击在右侧面板打开文件预览,list_dir目录 chip 不可点击(对齐 ZCodecanOpenPreview仅 file);list_dir 工作区根(.)显示「当前目录」;ToolCallCard的showIconprop 保留(透传 ToolLayout,默认 true); - 工具卡类型化:header = 类型图标(read/list/write/edit/exec/calc → lucide 图标)+ 类型标签(
toolKindLabel:读取/查阅/写入/编辑/终端/计算,未知工具如实回退原名,不臆测分类)+ 文件 chip(toolArgPath取 basename,title 全路径)/ 终端命令内联摘要(收起即可见命令,tool-cmd-inline尾部截断)++N/-Ndiff 统计(LCS 精确口径;绿--diff-added/ 红--diff-removed主题变量)+ 状态词(执行中/已执行/执行失败,i18n); - 终端卡(exec_command):
$ 命令(mono、限高折行)+ 输出区吸底-冻结引擎(移植 ZCode ExecuteOutput:运行中每帧贴底;用户上滚 → 冻结跟随保留阅读位;滚回底部 → 恢复跟随;hasStreamed守卫防静态结果误判);输出区限高 120px 滚动,无输出如实显示「没有输出。」; - 编辑/写入卡 diff 视图:展开为逐行 diff(
computeToolDiffLines→computeLineDiff行级 LCS,零依赖,DIFF_MAX_LINES=500上限保护)——行号跨增删连续计数 ++/−符号列 + 增行绿底(--diff-added-bg)/ 删行红底(--diff-removed-bg)+ mono 限高 240px 滚动;write_file视为全新增;失败附错误信息;语法高亮(Shiki)暂不引入(ZCode 亦需异步分帧防掉帧,后续单独评估);保留失败默认展开、运行中无结果不渲染空折叠、React.memo; - a11y:状态条为 button(运行中 disabled)+
aria-expanded;工具卡aria-label/aria-busy; - i18n 键:
turnWorking/turnWorked/turnWorkedNoDuration/turnStopped(含{duration}占位)、thinkingLabel/thinkingLive/thinkingFewSeconds/thinkingSeconds({seconds}占位)、toolStatusRunning/toolStatusDone/toolStatusFailed/toolPending/toolNoOutput/toolResult; - 测试:
turnActivity.test.mjs15 用例(切轮、无 user 历史兼容、运行/完成工时聚合、四态判定、时长格式化、diff 口径、工具分类回退),已挂进bun run test:chat。
6. 顶栏操作与国际化
- 中英文切换:融合版 SVG 地球仪镂空刻字图标,根据当前语言动态镂空刻印
中或EN。 - 亮暗主题:全系统变量级 CSS 变量换肤,支持即时切换并持久化保存。
- 消息配色 message-theme(2026-10-04):消息/对话区独立配色,设置页「外观主题」下方卡片切换。两套:AntiGravity(默认 = 现状,无覆盖)/ OpenCode(取自 opencode v2 默认主题
packages/tui/src/theme/assets/opencode.json,MIT:#0a0a0a 画布 + #eeeeee 正文 + #fab283 primary 等,light 侧取其 light 定义,跟随 data-theme)。实现 = 滚动视口容器(Appdata-scroll-viewport)挂data-message-theme(画布 + 左右留白整体变色,用户反馈 v1 只挂 message-list 会留色带断层),global.css 在该作用域重定义宿主语义变量(--text/--surface/--brand/--code-inline-* 等),消息组件零改动自动继承;等宽字体限定[data-message-theme] .message-list(查找条/审批条等保持 UI 字体);粗体 = opencode markdownStrong([data-message-theme] .md strong#f5a742/#d68c27,特异性 0,2,1 压过 .md strong 的正文白——用户反馈 v1 粗体是白色)。字体 IBM Plex Mono(OFL,400/500/600 latin woff2 内嵌于src/assets/fonts/,中文回退系统字体)。状态:lib/settings/messageTheme.ts(kvreinagent-message-theme+ window 事件广播reinagent-message-theme-change,App 订阅即时生效)。画布作用域补全(2026-10-05,用户报「输入框左右和下面的背景也要跟上面一样黑」):原先只给滚动视口本身设background-color,作用域内并未重定义--bg,而输入区 dock 用的是bg-[var(--bg)](宿主 #12141a)⇒ 胶囊左右/下方透出旧底色;修法=两个 message-theme 块各补--bg(dark #0a0a0a、light #ffffff,跟随画布色),作用域内var(--bg)一并对齐;底部会话统计行在滚动视口之外,改为包一层data-message-theme自带同款底色(antigravity 无覆盖块 ⇒ 行为不变)。
四点四、流式渲染性能架构(对齐 ZCode,2026-09-25)
消息时间线按 ZCode ConversationTimeline 的五层性能架构实现(MessageList.tsx):
- 生产端节流:池层 notify 走 30ms 节流窗(leading 立即 + trailing 合并,
NOTIFY_FLUSH_MS),密集 delta 下 UI 每帧最多渲染一次;状态更新本身保持同步。 - 引用稳定 + 行级 memo:
groupTurns结果引用稳定化(entries 未变的轮复用旧 group 对象);TurnGroupView/ToolCallCard/MarkdownText(text+streaming 双键)/MarkdownBlockRenderer全部 memo——流式时每帧只有正在流的轮重渲染。 - 虚拟化 + live tail 拆分:
@tanstack/react-virtual(轮为单位,稳定 key,overscan 4,行高 LRU 缓存TurnHeightCache上限 4000);正在流式的最后一轮拆出虚拟列表放普通文档流(消除流式长高回填跳动);scrollMargin动态测量 App 内边距包装;轮偏移注册表turnOffsetRegistry供导航条屏外轮兜底(getRegisteredTurnOffset→ConversationNavigator.measureFallback)。 - 流式降级:
streamingprop 贯穿 MarkdownText → MarkdownBlockRenderer → CodeBlock,流式中的代码块跳过 Shiki 异步高亮(plain 渲染),流式结束自动恢复;renderKey/memo 键含 streaming 防子树错位。 - 滚动权状态机:following 存 ref;wheel/touch/键盘 capture 阶段预登记用户上滚意图(同帧解除跟随,不等 scroll 事件);程序化贴底打 120ms 时间戳标记(窗内 scroll 事件不参与跟随判定);贴底
scrollTop = scrollHeightinstant 直赋;内容子树overflowAnchor: none禁用浏览器原生锚定;liveNowMs tick 只驱动正在流式的轮。
四点五、系统提示词单一真相源(PROMPTS.md,2026-09-25)
系统提示词的完整记录与 ZCode 借鉴映射见 PROMPTS.md:
- 第一部分:ZCode 提示词原文摘录(Communicating / Code style / Autonomy / Git / Environment / Edit-Read 失败文案 / 证据标准,含源码路径行号);
- 第二部分:ReinAgent 当前全部提示词(DEFAULT_SYSTEM_PROMPT 分段、buildEnvironmentSection 动态段、PLAN_MODE_PROMPT / APPROVAL_HINT、五工具描述与失败文案、审批门/计划模式拦截文案、权限分级);
- 第三部分:采纳映射表与待办(gitStatus 快照、记忆、AGENTS.md 注入、压缩、防注入包装等)。
铁律:修改 DEFAULT_SYSTEM_PROMPT、buildEnvironmentSection、PLAN_MODE_PROMPT、APPROVAL_HINT_PROMPT 或任何工具描述/失败文案时,必须同步更新 PROMPTS.md。
四点六、界面字号体系(对齐 ZCode,2026-09-26)
全部 UI 字号以 src/styles/global.css 的 --ui-font-size: 20px 为单一基准(对齐用户 ZCode 实例的界面字号设置),严禁散落硬编码:
text-ui-*派生刻度(语义刻度,新代码优先用):--text-ui-xl(+4) /--text-ui-lg(+2) /--text-ui-base(=基准) /--text-ui-caption(−1) /--text-ui-sm(−2) /--text-ui-xs(−4);- Tailwind 标准字阶整体上移(侧栏/设置页等
text-xs~xl的界面跟随放大):xs 16px/sm 18px/base 20px/lg 22px/xl 24px(含配套--text-*--line-height); - 聊天消息内容整体小一号(2026-09-27 用户定档 → 2026-09-29 推翻):
2026-09-29 用户调整:.md用--text-ui-sm.md升回--text-ui-base(20px)+ line-height 1.7,并全面加大段落/块级间距——p/blockquote/table/premargin 1em 0、标题字号差压缩(h1 1.35em/h2 1.2em/h3 1.1em/h4 1.05em,字重 600 为主)+ 上 margin 加大、列表 0.8em/li 0.4em、行内代码改中性半透明底--code-inline-bg: rgba(127,127,127,0.12)(dark/light 通用新语义 token)去边框、pre 圆角 6px、strong 降 600。消息区间距同步加大:.message-listpadding 12px 0 + gap 20px、.msg-assistant .msg-bodypadding 14px 16px、轮间距.turn-grouppadding-top 22px→28px(虚拟化下 flex gap 只作用于虚拟块↔live tail,真实轮间距在组内 padding);.turn-intermediate-text/.thinking-trigger仍用--text-ui-sm不变;用户气泡与输入框本就是text-sm(18px);界面其余部分维持原刻度不动; - 既有组件内零散 px 字号(14px/15px 等)暂保留,后续按需迁移到
text-ui-*刻度;新增样式禁止直接写死字号,统一走上述变量。
四点七、编辑重发 / 手动重试 / 回退本轮代码改动(对齐 LiveAgent,2026-09-26)
1. 编辑重发 = 硬截断(对齐 LiveAgent replaceConversationAtMessage 语义)
- 入口:用户消息悬停动作栏(复制 / 编辑 Pencil / 回退 Undo2);点击 Pencil 后整行被
EditableUserMessageBubble替换(受控 textarea 行数自适应、聚焦preventScroll+ 恢复[data-scroll-viewport]的 scrollTop、附件卡可移除、只有 Esc=取消,Enter 是换行、必须点「发送」;提交 trim,空文本且无附件禁提交)。 - 单值编辑态:
MessageList持有editingMessageKey;被编辑行随截断消失时自动退出(useEffect 检测)。 - 提交链(LiveAgent 同款「先退出编辑态,再异步重发」):
onSubmit → onCancelEdit() → App.handleEditResend——保留附件经buildOutgoingPayload(src/lib/chat/attachments.ts,Composer 共用单一真源)折算:图片+视觉模型 →fs_read_attachment_base64内联 image block;图片+非视觉 → 降级路径引用;文件 →[Attached file]行。 - controller.editResend(anchorMessageId, text):忙判定(streaming 拒绝)→ 锚点必须存在且 role==user →
messages.slice(0, anchorIndex)+ 原位替换新 user 条目(新 id,nextMessageSeq单调递增)→ 同步beginAssistant(与 send 同构,消除忙判定空窗;库 message_start 见末条 streaming 自动跳过重复建行)→ 清空retryAttempts/retrying→markTurnEntrance(新气泡入场动画)→checkpoint_begin_turn→ 全新流水线。任何一步不受理原历史保持不变。 - 持久化:截断后的时间线经池层 300ms 防抖
conversation_sync全量落库(旧分支物理消失,无版本留存;对齐 LiveAgent,想保留用分支会话——未实现)。 - 贴底:受理后 App
followSignal+1→ MessageList 强制恢复贴底跟随 + instant 置底(对齐 LiveAgent stickToBottom on run start)。 - 入场动画:
entranceOnce.js诞生注册表(600ms 窗口)+chat-bubble-enterCSS(0.3s cubic-bezier(0.16,1,0.3,1),prefers-reduced-motion 退化);初始构建/晚挂载行永不播放。
2. 手动重试 = 以原始提问重发该轮(对齐 LiveAgent RowActions retry)
- 助手消息悬停动作栏新增
RotateCw确认弹层(Radix Popover):「重试该回复?/ 将以原始提问重新发送,该回复及之后的对话内容将被删除。」确认后handleRetryFrom(messageId):向前扫描锚点 user → 文本沿用锚点原始载荷、图片直接复用其权威apiMessage的原生 image block(免重读文件)→poolEditResend(同一截断管线)。旧红色「重试」按钮已移除(LiveAgent 无此形态);错误行只保留原文+errorHint 双层展示。自动重试上限仍为 10 次(用户定档优先)。 - 自动重试对齐 LiveAgent withStreamRetry 三点:① 已提交内容(text/thinking delta 或 toolcall 已开始)后的失败不再重试(重发会重复,直接收敛 error 行,部分内容保留);② 重试期间失败尝试的尾部 error 行不留在时间线;③ 「重新连接中」副行从重试调度起持续显示并实时携带最新失败的错误原因(
重新连接中… N/10 · <最新 errorMessage>;pushRetryAttempt置retrying=true并重建空流式行保持 live 轮),直到新尝试产出首个内容事件才撤下(onEvent 的 onRetryRecovered 语义;此前实现为「新尝试开始即撤下」,连接等待期副行闪现,2026-09-27 修复)。退避期间点停止会立即收敛为已停止(不再把下一次注定失败的请求发完才安静)。 - 重试详情块已从代码彻底删除(2026-09-27 定版):
RetryDetailsBlock组件文件、实时渲染、收敛后MessageItem的渲染与相关 i18n 键全部移除(用户明确要求完全删除而非隐藏;失败原因实时见重连副行)。重试记录(state.retryAttempts/ 条目retryAttemptspart / 序列化)仍保留:实时副行的数据源 + 留档。停止(stop)不固化记录;重试耗尽的 error 收敛也不再有详情块 UI。 - 轮工时跨重试累加(2026-09-27 修复):重试重建的空流式行继承被剪除行的
startedAt(链式传递 = 本轮最初起点),工时不再每次重试从零计时。 - 错误行合并 + 行内重试按钮(2026-09-27):error 收敛行改为单行「
<错误原文> · <errorHint>」(·分隔,允许折行),行右侧红色「重试」按钮(RotateCw)直接触发onRetryFrom(截断重发该轮,无确认弹层)。⚠️handleRetryFrom读取图片时必须先判apiMessage.content是否为数组(pi-ai 的 UserMessage.content 纯文本时是字符串,直接.filter会 TypeError 导致按钮无反应);errorHint不落库,deserializeRow水合时由diagnoseError(error)重算(否则刷新后友好提示丢失)。 - 助手动作栏常显 + 创建分支(2026-09-27,对齐 LiveAgent):助手消息动作栏常显(去 hover 门控),图标改为三枚——复制 / 重试(确认弹层)/ 创建分支(GitBranch + 确认弹层),点赞点踩移除;行右侧时间戳
YYYY-MM-DD HH:mm(取 endedAt ?? startedAt,无打点不显示)。分支语义(对齐 LiveAgent useBranchConversation/branch.rs):把此回复及之前的全部消息复制到一个新任务(标题「新分支」,沿用源任务的项目/模型/推理等级/审批模式),原任务保持不变,随后切换到新任务;实现走既有 IPC(conversation_load取权威行 → 按锚点 msg_id 切前缀、seq 重排 →conversation_sync写入新任务),无需新 Rust 命令。轮状态条文案「工作/已工作」→「处理中/已处理」(中英同步)。 - 流式加载指示器(2026-09-27,移植 ZCode ChatLoading):
ChatLoading.tsx= lucideLoaderIcon+animate-spin+ 弱化前景色(--text-dim),size="sm"(16px)用于流式助手正文末尾,替换旧「▋ msg-caret 竖条光标」(.msg-caret/blink 动画已从代码删除);loading=false不渲染。
3. 回退本轮代码改动(checkpoint/rewind,完整移植 LiveAgent checkpoint.rs)
- 数据记录层(Rust
src-tauri/src/checkpoint.rs):fs_write_file落盘前把被改文件的前像写入~/.ReinAgent/checkpoints/<taskId>/(index.jsonl追加日志 +blobs/原始字节拷贝,schema v2)。ReinAgent 的 edit=读后整文件写回,故只挂钩 write 一处。捕获尽力而为:失败只追加kind="error"记录(该轮 UI 显示不完整),绝不阻断写入。turnId=用户消息 id;turn_seq 由 Rust 在 INDEX_LOCK 下单调分配(同 turnId 复用)。容量防线:单 blob 32MB/会话 512MB/10000 条(尾部 64 条留给 error)。 - 命令:
checkpoint_begin_turn(发送瞬间打轮边界,零文件轮也是合法回退点)/checkpoint_list/checkpoint_diff_stats(预览:restore/delete/clean/skip-dir/missing-blob/unresolvable + 现状哈希)/checkpoint_rewind_code(带 expected 哈希做 TOCTOU 冲突检测,缺指纹一律判冲突 fail-closed)/checkpoint_clear(删任务时清理)。⚠️ Tauri 命令不加rename_all = "snake_case"(本项目 JS 侧统一驼峰键,加了会要求蛇形键导致 invoke 失败)。 - 回退语义:「恢复到第 N 轮开始前」= 聚合撤销 turn_seq>=N 的所有路径(每路径取最早前像);本轮新建文件(existed_before=false)→ 删除;完整回退(无冲突/失败/跳过/捕获缺口)才写
kind="rewind"剪枝标记——读取侧丢弃 >= target 的陈旧未来记录,多轮各自独立回退、回退第 N 轮连带撤销其后的轮且不影响 < N。安全链:授权根白名单(fail-closed)+ 根/路径链逐级拒符号链接 + Unix 多硬链接拒绝 + 写前 reverify + 临时文件原子 rename(Windows 备份名回滚)。 - UI:
CheckpointRewindProvider(src/lib/chat/checkpointRewind.tsx)+ 行内 Undo2 按钮(pending 时 Loader2 转圈;无检查点/发送中禁用,title「回退本轮代码改动/本轮没有可用的代码检查点」)。流程=preview → 确认框(标题「回退到本轮开始前」+ 时间副标题 + 逐项统计描述 + 等宽路径 detail 清单 + 红色确认键,组件src/components/ui/ConfirmDialog.tsx)→ 回传全部预览哈希执行 → toast(成功「已回退代码:恢复 N 个、删除 N 个文件」,数字间 U+00A0 防折行)/ 部分完成对话框 → 重拉轮列表。文件更改摘要卡的假「撤销」占位已移除(LiveAgent 摘要卡无撤销)。终端命令的写入不在检查点内(与 LiveAgent 一致,需用户知悉)。
五、状态存储速查(2026-09-25 起迁移至 SQLite)
1. 本地磁盘持久化(Tauri Backend)
~/.ReinAgent/conversations.db(SQLite,WAL 模式,conversation_store.rs;前端经 IPC 访问):
| 表 | 结构 | 用途 |
|---|---|---|
message | (task_id, msg_id) PK + seq/role/status/started_at/ended_at/tool_name/tool_call_id/is_error/truncated_by/error/thinking_* | 时间线条目骨架(一行一个 TimelineEntry) |
part | (task_id, msg_id, part_index) PK + kind/payload | 条目内容块(text/thinking/tool_args/tool_result/api_message),为全文检索铺路(对齐 LiveAgent message/part 拆分)。api_message(2026-09-25):assistant/tool 条目的权威 API 消息原件(AssistantMessage/ToolResultMessage,含 usage 与精确 API 格式)——对齐 LiveAgent「落盘原件而非 UI 投影」:恢复后 token 统计/缓存命中率/上下文容量不再归零,toApiMessages 恢复完整历史(修复刷新后模型失忆);改动前入库的旧轮次无此块,usage 如实缺失 |
task | id PK + seq/payload/updated_at | 任务元数据(标题/时间/项目/置顶/模型绑定,JSON payload) |
kv | key PK + value | UI 偏好(主题/语言/侧栏状态/active-task-id/user-projects/thinking-level/user-home) |
IPC 命令:conversation_sync(任务全量替换,事务)/ conversation_load / conversation_delete / task_sync / task_list / kv_get_all / kv_set_many;另有检查点五命令(checkpoint_begin_turn/list/diff_stats/rewind_code/clear,见 四点七.3,数据存 ~/.ReinAgent/checkpoints/<taskId>/,不入库)。
前端访问层 src/lib/storage/db.ts:main.tsx 渲染前 await initStorage()(kv 全量 + 任务列表 + user-home 进内存缓存)→ 之后所有同步读走缓存;写入走写透(缓存 + 防抖批量 IPC)。
2. 会话池(src/lib/chat/conversationPool.ts)
Map<taskId, PoolEntry>:每任务一个独立 conversationController + 外部 store(getSnapshot/subscribe,供 useSyncExternalStore);- 切任务 ≠ 停止:切换只换 UI 订阅目标;后台任务流式照常跑、照常落库(「注销 ≠ 停止」);
- 持久化时机:状态变化防抖 300ms 全量 sync(含流式中的条目,恢复时消毒收敛);
- 治理:空闲条目 LRU 上限 12(豁免流式中);删除任务 →
destroyTask(abort + 清内存 + 删 SQLite); - 侧栏「进行中」标记:
subscribeStreaming/getStreamingTaskIds(仅集合变化才通知,流式 delta 不触发侧栏重算)。
3. localStorage(已废除)
对话与任务数据的全部 localStorage 键(reinagent-task-msg-*、reinagent-tasks 等)已于 2026-09-25 移除,历史数据不迁移(从零开始)。例外:ZCode 预览面板移植件内部零散偏好已改走 kv;仅第三方库内部缓存不归本项目管辖。
六、Linux 编译、运行与环境规范
编译测试/运行验证使用 bun run tauri dev(仓库根目录);正式构建 bun run tauri build 由用户本人运行。
- 依赖安装/变更:仓库根目录直接执行
bun install(根目录node_modules为真实目录,无软链接、无隔离区)。 - 历史的
run-linux.sh与/tmp/reinagent隔离区(rsync 同步、CARGO_TARGET_DIR重定向、node_modules软链接)已于 2026-10-04 全部删除,严禁再引用或复活该流程。 - 自动化无头视觉回归(仅测试用途,非标准构建流程):
google-chrome --headless --disable-gpu --screenshot=/tmp/screen.png --window-size=1280,800 http://localhost:1420。
七、核心架构守则与避坑指南
- 包管理器限制 (Package Manager Rule):
- 项目采用 Bun(
bun@1.4.2与bun.lock)。 - 严禁使用 npm/pnpm 篡改依赖锁定文件;所有依赖安装与更新一律在仓库根目录执行
bun install。
- 项目采用 Bun(
- Tauri 2 + Web 双模兼容 (Dual-mode Compatibility Rule):
- 涉及系统级能力(终端 PTY、受控文件操作等)时,必须保留 Web Mock / Fallback 兼容层,保证在 Headless Chrome(无头自动化测试/截图回归)或普通浏览器中依然能完整渲染并正常调试。
- Tailwind CSS v4 语义化主题 (Theme Styling Rule):
- 严禁在组件中硬编码 Hex/RGB 颜色值;必须使用
src/styles/global.css定义的 CSS 语义变量(如var(--bg)、var(--sidebar-bg)、var(--sidebar-text)、var(--border)),确保跟随data-theme="dark|light"自动平滑换肤。 - 表单控件原生样式隔离(Form Controls Native Appearance Rule):在 Linux Webview(WebKit2GTK)环境下,原生
<select>必须配置appearance-none并配合自定义ChevronDown箭头图标,全局绑定var(--bg-card)与var(--text-primary),杜绝因操作系统原生 GTK 白色控件覆盖导致的“白底白字不可读”问题。
- 严禁在组件中硬编码 Hex/RGB 颜色值;必须使用
- Git 与工作区保护 (Workspace Discipline):
- 未经用户明确许可或要求,严禁自行调用
git commit或git push。 - 测试产物、截图、中间日志等临时文件必须存放于
/tmp/,严禁污染工程工作树。
- 未经用户明确许可或要求,严禁自行调用
八、工作区与路径决议机制(对齐 ZCode 规范)
1. 业务工作区决议规则(Workspace Resolution)
- 活动任务优先(2026-09-27 修订,修复重启回退 DefaultProject):有活动任务时,工作区根目录严格跟随任务自身持久化的
project字段(App.tsx的workspaceProject = activeTask ? activeTask.project : selectedProject,单一真相源,不依赖 UI 态同步时机);草稿态(无活动任务)才使用selectedProject(侧边栏/输入框所选项目)。 - 有指定项目:若当前任务选中了项目(或会话绑定了 Project),则工作区根目录
workspaceRoot为该项目所指定的文件夹路径。 - 无指定项目(默认回退):若任务未指定项目(或点击“不在项目中工作”),工作区根目录自动回退到用户主目录下的:
👉
~/.ReinAgent/DefaultProject(如 Windows 下C:\Users\<user>\.ReinAgent\DefaultProject)。- 若该目录不存在,首次文件写入或命令执行时由系统自动创建(
mkdir -p)。
- 若该目录不存在,首次文件写入或命令执行时由系统自动创建(
selectedProject同步纪律(2026-09-27):①hydratePersisted恢复持久化字段时必须从恢复的 activeTaskId 同步selectedProject(历史缺陷:漏掉导致重启自动恢复的任务“在项目下显示、工具却落在 DefaultProject”);②setActiveTaskId切到已有任务严格跟随targetTask.project ?? null(无项目如实为 null,绝不残留上一个任务的项目),切回草稿态保留当前所选。- 用户主目录真实来源(No-Fallback,严禁编造路径):
- 前端
getDefaultWorkspaceRoot()(src/lib/agent/workspace.ts)的三级来源为:①localStorage["reinagent-user-home"]缓存;② Node/Bun 测试环境的HOME/USERPROFILE环境变量;③ 全部缺失时返回空串,绝不返回任何编造的默认路径(历史遗留的/home/web3claw/...硬编码兜底已彻底移除)。 - 应用启动时
App.tsx调用initUserHome():经 Tauri IPC 命令path_home_dir(src-tauri/src/fs_cmd.rs,优先读USERPROFILE,其次HOME,均缺失则真实报错)拉取宿主真实主目录,写入reinagent-user-home缓存;Web/无头环境下后端不可达时保留既有缓存并返回 null。 - 工作区未知告警条:当未选项目且主目录不可得时,顶栏下方展示
--warn-*主题色告警条(i18n 键workspaceUnknown),明确告知文件与命令工具受限,严禁静默假兜底。 - 主目录未知时
~展开与基于空根目录的相对路径解析(resolveWorkspacePath/resolveWorkspaceRoot)会抛出真实错误(“用户主目录未知”/“工作区根目录未知”),由工具层如实上报,绝不生成编造路径。 - 行为已由
src/lib/agent/workspace.test.mjs用例 6/7 锁定(纳入bun run test:agent)。
- 前端
2. 路径决议策略(Path Policy)
- 文件工具(
write_file/read_file/edit_file/list_dir):- 前端工具层引入
resolveWorkspacePath(inputPath, workspaceRoot),所有相对路径在发送给底层前,自动基于当前的workspaceRoot拼接为绝对路径; - 绝对路径保持原样,支持跨目录绝对路径读写;
- 杜绝相对路径裸传导致文件落入 Tauri 默认进程 CWD(
src-tauri/)的问题。
- 前端工具层引入
- 命令行工具(
exec_command):- 若未指定
cwd,自动使用workspaceRoot作为默认执行目录; - 若指定了相对路径的
cwd,自动基于workspaceRoot解析。
- 若未指定
- Rust 后端双重防呆(
src-tauri/src/fs_cmd.rs):- 后端接收到路径时,如遇相对路径,同样基于
~/.ReinAgent/DefaultProject进行安全解析与自动建目录,提供系统层双重保险。
- 后端接收到路径时,如遇相对路径,同样基于
九、模型推理等级 (Reasoning Effort) 与聊天深度联动规范
1. 真实元数据与 API 规范(No Fallback 铁律)
- 服务端模型列表接口如果返回
effort字段(如"effort": { "supported_levels": ["low", "high", "max"], "default_level": "high" }):parseProviderRawModels完整真实提取supportedLevels与defaultLevel,写入ModelItem.effort;- 若上游未提供,严格保留为
undefined,绝不凭模型名瞎猜或预设假数据;
mergeFetchedModels增量更新时,真实同步服务端的effort、contextWindow、maxOutputTokens与supportsImage。- 视觉能力解析(
parseProviderRawModels):支持布尔值supports_images / supports_image、数组形态input_modalities / modalities: ["text", "image"],以及 OpenAI/Magpie 标准对象形态modalities: { input: ["text", "image"] }(检查input数组包含"image")。保留真实解析,上游未声明时保留为undefined。
2. 模型编辑弹窗交互规范(Pill Button Group)
- 在
ModelEditModal.tsx中配置推理等级:- 摒弃传统下拉框,采用一排水平胶囊按钮组(
[ low ][ high ][ max ]); - 点击哪个档位,哪个档位立即呈主题色(
var(--accent))高亮,并设置为该模型的defaultLevel; - 若模型来自 API 且未声明
effort,UI 明确标注“上游接口未声明支持推理等级 (未返回 effort)”,亦可由用户按需手动开启。
- 摒弃传统下拉框,采用一排水平胶囊按钮组(
3. 聊天窗口 (LexicalComposer) 联动与双向同步
- 推理能力乐观兜底(对齐 LiveAgent,2026-09-24 修订):
- 未声明
effort元数据的模型乐观视为支持思考:推理深度按钮恒可用(不再置灰禁用),下拉提供通用档位Default / Low / Medium / High(不含 xhigh / max——这两档需要模型显式映射,未知模型不发);buildModel的reasoning同步兜底为 true → 请求带reasoning_effort(服务端不支持时会自行忽略);动机:实测 WorkBuddy 网关(192.168.3.27:8787)hy4-preview/glm-5.3-flash默认输出reasoning_content、deepseek-v4.1-flash带reasoning_effort才思考,而其/v1/models不返回任何 effort 字段——严格门槛会让这类模型永远无法显示思考; - 声明了
effort.supportedLevels的模型仍严格按声明过滤档位(不臆测加档); - 已知网关怪癖:
glm-5.3-flash带reasoning_effort参数反而不思考(默认参数才思考)——该网关对参数语义解析异常,遇到时切 off 或换模型; - 思考内容仍然只渲染服务端真实流下来的
reasoning_content,绝不伪造;
- 未声明
- 禁用与高亮判定(历史行为,已被乐观兜底取代):
未声明 effort 的模型按钮置灰禁用;声明了 effort 的模型,下拉菜单中点亮该模型支持的档位,不支持的档位呈禁用置灰态; - 双向实时同步机制:
- 聊天窗口 ➔ 模型配置写盘:用户在聊天窗口切换推理深度时,调用
updateModelEffortDefaultLevel实时将当前模型在provider_config.json中的defaultLevel设为所选等级;下次打开编辑弹窗时该等级自动高亮; - 模型配置 ➔ 聊天窗口:在编辑弹窗中保存了新默认等级时,若当前聊天窗口正在使用该模型,聊天窗口底部的推理深度立即联动更新为该等级;
- 模型切换自动联动:用户切换模型时,若新模型支持
effort且有defaultLevel,聊天窗口自动切为该默认等级;未声明 effort 的模型沿用当前全局等级(默认 high),不再强制切 off。
- 聊天窗口 ➔ 模型配置写盘:用户在聊天窗口切换推理深度时,调用
4. 大模型请求透传机制(底层 HTTP Payload 装配)
- 透传链路:
LexicalComposer / Store➔App.tsx➔useConversation➔conversationController➔runAgentTurn➔agentRuntime (Agent.initialState.thinkingLevel)➔pi-agent-core (agentLoop reasoning)➔pi-ai (streamFunction); - 底层适配器自动将
reasoning映射为对应协议的真实 HTTP POST 请求体:- OpenAI / DeepSeek 格式:自动添加
"thinking": { "type": "enabled" }, "reasoning_effort": "high"; - Anthropic 格式:自动添加
"thinking": { "type": "enabled", "budget_tokens": ... }; - Google 格式:自动添加
"thinking_config": { ... }。
- OpenAI / DeepSeek 格式:自动添加
- ⚠️ 必须使用
streamSimple而非裸stream(runAgentTurn 的 getStreamFnForApi):pi-ai 各协议适配器导出两个入口——streamSimple负责把会话层reasoning(思考等级)钳制变换为reasoningEffort并组装思考开关;裸stream只认已变换好的reasoningEffort,直接传reasoning会被无视,且对 DeepSeek 等协议会落入「显式禁用思考」分支(发出thinking: {type: "disabled"}),导致模型永远不输出思考过程。历史事故:2026-09-24 曾因误用裸stream导致所有模型思考不显示(已修复)。排查手段:劫持globalThis.fetch捕获实际请求体,检查thinking/reasoning_effort字段。
十、后续迭代方向推荐(2026-09-24 刷新)
- 项目管理深化:原生目录选择(
fs_pick_folder)与工作区决议已落地;目录文件树渲染(结合 ZCodeWorkspaceSidebarItem树形逻辑)尚未实现。 对话持久化升级:已完成(2026-09-25)——对话/任务已迁移至~/.ReinAgent/conversations.dbSQLite(见 五),localStorage 对话/任务键全部废除。- Agent 工具执行沙箱:前端审批流(ApprovalMode 四档 + beforeToolCall 挂起审批 + 计划模式拦截)已落地(见 十一 #14);剩余:Rust 端命令执行拦截/沙箱、计划批准 elicitation(ExitPlanMode 批准后自动退出计划模式)、审批规则持久化、后台任务审批红点徽标。
- 推理能力元数据整改(铁律整改项):
modelFactory仍写死contextWindow: 128000 / maxTokens: 8192,应解析上游/v1/models的context_length/max_tokens真实字段(WorkBuddy 网关已实测返回这两个字段)。 - 其余聊天窗口未实现项见 十一、聊天窗口 ZCode 对标实现状态清单。
十一、聊天窗口 ZCode 对标实现状态清单(截至 2026-09-24)
1. 已实现(全部通过 99 项单测 + 浏览器运行态实测)
| # | 功能 | 要点 | 关键文件 |
|---|---|---|---|
| 1 | 回合工作状态条 | 「工作中 X 秒」(运行中锁定展开+每秒计时)/「已工作 X 秒」(完成折叠)/「已停止」/「已处理」(历史无打点 No-Fallback);时长格式化最多两段非零单位 | TurnGroupView.tsx、turnActivity.ts |
| 2 | 思考块三态折叠 | 流式默认展开+吸底跟随 → 整轮完成自动收一行;用户点击优先;Markdown 渲染 + 320px 限高 + 左导线 | ThinkingBlock.tsx |
| 3 | 工具卡类型化 header | 类型图标/标签(未知工具回退原名)+ 文件 chip + 命令内联摘要 + +N/−N(LCS 精确口径)+ 状态词;文件 chip 对齐 ZCode renderFileChip(2026-09-25):Material Icon Theme 彩色 SVG(public/material-icons/ 40 个常用扩展名,颜色固化在 SVG,onError 三级回退)+ 文件名可点击(编辑/写入 → 右侧 patch diff;读取 → 文件预览;onMouseDown 阻断行折叠)+ 目录段带尾斜杠更暗一级 + 条件 ±N(added>0 绿 / removed>0 红,零不显示);顺带修复 edit_file 参数名错配(工具签名 target/replacement vs 代码读 old_string/new_string 导致 ±N 与 diff 恒空,补回退) | ToolCallCard.tsx、fileDisplay.tsx、fileDisplayHelpers.ts(ps1/bat/cmd 别名)、turnActivity.ts |
| 4 | 终端卡 | $ 命令区(mono 限高折行)+ 输出区吸底-冻结引擎(上滚冻结/回底恢复)+「没有输出。」 | ToolCallCard.tsx |
| 5 | 编辑/写入 diff 视图 | 行级 LCS diff(零依赖,500 行上限)+ 行号跨增删连续计数 + 增行绿底/删行红底 + 240px 滚动 | ToolCallCard.tsx、turnActivity.ts |
| 6 | 查阅族独立卡(原「查阅」聚合卡已按用户决策移除,2026-09-27) | list_dir/read_file 直接渲染独立卡(对齐 ZCode ReadToolCallBlock):图标 + 文件/目录 chip + 路径次要文本;成功不显示状态词、失败显示「执行失败」+ tooltip;list_dir = 目录读取形态(文件夹 chip,. 显示「当前目录」) | ToolCallCard.tsx |
| 7 | 对话问题导航条 | 用户提问粒度刻度 + Radix Tooltip 预览 + 平滑跳转 + 山峰衰减动效 + 自适应高度(240px/10~24px) | ConversationNavigator.tsx |
| 8 | 时间打点数据层 | assistant/tool 条目 startedAt/endedAt、thinkingStartedAt/thinkingDurationMs;注入时钟贯通状态机;不参与 toApiMessages | conversationModel.js/.d.ts |
| 9 | streamSimple 修复 | 修复误用裸 stream 导致思考被显式禁用的事故(详见 九.4) | runAgentTurn.ts |
| 10 | 推理能力乐观兜底 | 无 effort 元数据模型视为支持思考(档位 Default/Low/Medium/High,可 off);已声明模型仍严格按元数据过滤 | App.tsx、LexicalComposer.tsx、modelFactory.ts |
| 11 | 右侧代码/变更预览面板(PreviewPane 完整移植) | 编辑/写入卡审查 → patch 模式(Shiki 高亮 diff);读取卡 → 文件行号预览(Rust fs_read_text_file 256KB 截断+二进制探测);@pierre/diffs + shiki 已引入;Media/PDF/PPTX/Office 为诚实降级 stub | src/preview/**(约 40 文件)、fs_cmd.rs、CodeViewerPaneHost.tsx |
| 12 | 文件更改摘要卡 | 轮内 edit/write 客户端现算摘要「N 个文件已更改 +A −B」;每文件行 审查(patch 面板)/ 打开(文件预览);仅在整轮结束后显示(编辑过程中看各工具卡,对齐 ZCode 时机);同文件多次编辑按路径聚合为净变更,净零文件过滤;.ReinAgent/.temp/ 下的一次性脚本不进摘要(isReinAgentTempPath:不列行、不计入数量与增删统计,全部为 temp 时整卡隐藏;用户确认不做自动清理,手动清理按钮保留);撤销按钮 gating 未开(需 Rust 写入轨迹日志,阶段 2);header 附**「清理临时目录」**按钮(行内二次确认 → fs_clean_reinagent_tmp) | TurnGroupView.tsx(TurnFileSummaryCard) |
| 13.4 | 会话统计行(SessionStatsBar,对齐 LiveAgent 底部统计条) | 底栏下一行等宽小字:`N 轮 · M 步 | 上下文 P% |
| 13.5 | 上下文容量面板 | 触发器=输入框工具栏 SVG 圆环(进度弧);HoverCard 面板:标题(上下文容量 + 紧凑数字摘要)+ 多段进度条(品牌色按排名淡化)+ 分类行(消息/系统工具/系统提示词,字符估算口径与 ZCode 一致:中文×2÷3 取整)+ 缓存命中率(移至会话统计行,≥0 才显示);已用取真实 usage(input+cacheRead+output),上限取模型 contextWindow 声明值;无数据不渲染(No-Fallback);不含剩余额度(用户确认排除);明细行可点击——有内容的类别(消息/系统提示词/系统工具)懒构建真实文本在右侧面板查看(markdown/json 高亮),无内容类别(技能/MCP/其他)如实 0.0% 且不可点击;底部一键【压缩上下文】按钮(对齐 Hetu 紫色/品牌色高亮按钮交互,无任务或流式中禁用,压缩中显示【正在压缩…】;支持 /compact 与 /compress 斜杠命令);对话区底部内联通知栏(对齐 Hetu 圆角通知条,压缩成功为绿色,跳过/失败为琥珀色警告,带 Gauge 图标与 ✕ 关闭按钮;用户继续发送新消息时自动无感关闭清除);压缩条目去重隔离(kind: compact 条目仅在轮首由 CompactionBand 渲染,groupTurns 与 TurnGroupView 严格排除其落入 lastAssistant 或常规工时条,彻底杜绝下方重复冒出常规气泡与 0 秒已处理条目) | contextUsage.ts、ContextUsageIndicator.tsx、LexicalComposer.tsx、App.tsx、turnActivity.ts、TurnGroupView.tsx |
| 14 | 任务级设置隔离 + 审批模式落地(对齐 ZCode task-local thoughtLevel/mode) | 三项设置按任务隔离:模型(providerId/modelId,已有)、推理等级(thinkingLevel?)、审批模式(approvalMode?),字段随 task payload 进 SQLite;有活动任务读写任务字段,草稿态读写全局默认(首次发送随 createTask 落到新任务);模型切换自动对齐改为任务粒度(只修正覆盖不受支持的任务,绝不冲掉仍受支持的覆盖)。审批模式四档(对齐 ZCode 用户可切面):plan 计划模式(写/执行一律拦截+系统提示词约束只读调研)/ask 变更前确认(写+执行弹审批卡)/edit 自动编辑(写自动放行,执行弹卡)/full 完全访问(默认,零开销直通);工具分级 resolveToolPermissionKind(read 直通/write/exec 受限,未知工具保守视为 write)。审批流经 pi-agent-core 原生 beforeToolCall 钩子:需要批准时 await 用户决策(循环挂起不中止,同 ZCode 权限流语义),拒绝 → {block:true, reason} 错误工具结果(模型知道被拒不重试);审批卡(ApprovalCard)渲染于输入框上方(工具名+参数摘要+允许/总是允许/拒绝),「总是允许」为任务级内存免审集合;stop/clear/轮次异常结束均以 reject 解除挂起;模式下拉对齐 ZCode(图标+标题+描述,full 态触发按钮 warning 色)。实测:隔离(A 改 B 不动+全局不动)、拒绝(模型停手文件未创建)、允许(批准后文件真实落盘)全链路通过 | useAppStore.ts、tools.js(resolveToolPermissionKind)、agentRuntime.js(beforeToolCall 透传)、runAgentTurn.ts(createApprovalGate/PLAN_MODE_PROMPT)、conversationPool.ts(审批协调器)、conversationController.js(requestApproval/resolveApproval)、conversationModel.js(pendingApproval)、ApprovalCard.tsx、LexicalComposer.tsx、App.tsx、approvalGate.test.mjs |
| 15 | 应用品牌换装(logo + 应用图标 + 底栏图标条) | 应用图标全套更换:R 主题(蓝色渐变圆角方块 + 白色粗体 R,src-tauri/app-icon.png 1024 源图经 cargo tauri icon 生成 icon.ico/icns/各尺寸 PNG/Store logos),窗口/任务栏/开始菜单生效(需 Rust 重构建后启动);左上角 logo 改为同源内联 SVG(AppLogo 组件,替换原文字方块);左下角用户栏移除(头像 K/用户名 kwtgsgi8 删除),替换为 PI-Desktop sidebar-footer 同款图标条(lucide 14px 图标 + 32×32 命中区 + radix Tooltip 300ms,对齐 TooltipButton/footer-action):齿轮=设置(可点,i18n settings)/ 插头 Plug=插件(navPlugins,打开插件页)/ 时钟=定时任务(navScheduled,占位)/ 电脑(原 Monitor,占位)——占位无功能、默认全部不高亮;右侧版本号 v{getVersion()} 对齐 footer-build(12px tabular-nums leading-none、UI 字体非 mono;浏览器模式取不到则隐藏) | WorkspaceSidebar.tsx(AppLogo + 底栏)、src-tauri/icons/*、src-tauri/app-icon.png |
| 13 | 临时/资料目录约定(B+C 组合,对齐 pi 的 .pi/ 模式) | 系统提示词约定:一次性脚本/分析产物必须放 <工作区>/.ReinAgent/.temp/(视为可丢弃);记忆等持久资料也统一放 <工作区>/.ReinAgent/ 下各自子目录(禁止散落项目根);Rust fs_clean_reinagent_tmp 白名单清理 .ReinAgent/.temp(两级路径严格校验 + 幂等 + 递归删除 + 条目计数);摘要卡 header 一键清理(行内二次确认、3 秒回退、成功/失败如实反馈)。参照:PI-Desktop host-core 的 .pi/ 工作区目录模式(plans/artifact.rs,路径安全校验同思路) | runAgentTurn.ts、fs_cmd.rs、TurnGroupView.tsx |
| 16 | 双栏笔记系统(对齐 Hetu 功能与视觉,本地 Markdown 文件持久化) | 导航位于左侧栏「助手」正下方(BookOpen 图标);主界面采用两栏布局:左栏为笔记本下拉选择器(默认选中英文名 default 文件夹,展示为“默认笔记本”,支持嵌套子笔记本与即时创建)+ 搜索框 + 笔记卡片流(标题/摘要/时间,支持重命名与删除);右栏为全功能 Markdown 编辑器,支持 Hetu 滑动胶囊切换 预览 (Eye) / 编辑 (Edit) / 分栏 (Split) 三态、大标题编辑、快捷语法工具栏(H2/H3/粗体/斜体/代码/列表/待办等)与防抖自动存盘;后端基于真实物理文件夹与 .md 文件(~/.ReinAgent/notebooks/default),并带 Web Mock 回退保障;自动化、助手、笔记等独立页面统一隐藏对话工作台顶栏,页面拥有整屏干净的工作空间 | src-tauri/src/notes_fs.rs、src/components/notes/*、WorkspaceSidebar.tsx、App.tsx |
2. 未实现(Gap 清单,按主题分组)
修复记录(2026-09-24):
- 工具瘦身:移除
get_current_time与calculate两个演示工具(代码/注册表/测试/文档同步清理),工具集仅保留 read_file / write_file / edit_file / list_dir / exec_command; - 持久化迁移 SQLite(两表结构对齐 LiveAgent)+ 会话池多任务并行流式(2026-09-25):切任务不再 abort,后台任务照常跑;localStorage 对话/任务键废除;
- 修复:useSyncExternalStore 嵌套更新循环——pi 库 processEvents 同步循环逐事件 notify,useSyncExternalStore 对每次快照变化强制重渲染,50 次即抛「Maximum update depth exceeded」(表现为轮次「请求失败」)。修复:池的 notify 按微任务合并(一轮事件爆发只渲染一次,状态已同步更新、订阅者拿到最新快照);
- 修复:草稿首条消息发送竞态——handleSend 的 createTask/setActiveTaskId 后,hook 闭包里的 taskId 仍是 null → poolSend 被静默拒绝。修复:草稿提升路径用新 taskId 直接调池;
- 修复:任务元数据落库缺 seq——syncTasks 统一按索引派生 seq(= 创建顺序);
- 恢复消毒:
restoreState将残留的 streaming/running 条目标记为 stopped 并补endedAt——消除重启后「执行中」僵尸条目导致的统计爆炸(如 LLM 9h17m)与状态条永久工作中; - 命中率口径修正:pi-ai 的
input不含缓存命中部分,命中率 = 累计cacheRead / (input + cacheRead);容量 used = input + cacheRead + output。 - 修复:exec 终端命令大面积失败(2026-09-26)——根因是 Rust
Command::arg()按 MSVC 规则把命令内引号转义成\",而 cmd 不认该转义,findstr /c:"..."与多词带引号模式被拆坏(FINDSTR: Cannot open <词>)。修复:fs_execute改用raw_arg原样透传命令行;同时主命令加CREATE_NO_WINDOW(不再弹黑框抢焦点)、输出收集加 5s 有界收尾(防孙进程持管道永久挂起)、输出 256KB 截断(防巨型输出拖垮 IPC)。另:模型把 exec 参数名写成cmd导致的校验秒败已由 schema 别名修复(见提交 059903f)。 - 错误详情展示 + 自动重试(2026-09-26,对齐 LiveAgent)——controller 捕获错误时用
describeErrorChain展开 Error 的 cause 链(OpenAI SDK 的 APIConnectionError message 只有 "Connection error.",真实原因在 cause 上),错误行以原文为主(状态码/上游原因/URL 一并展示)+errorHint(diagnoseError 友好提示)小字;finish同步存储 error(原文)/errorHint 两字段;diagnoseError 新增 5xx 分支(502/503/504/forward);自动重试两层(对齐 LiveAgent withStreamRetry):provider 层maxRetries: 2(pi-ai retryProviderRequest,连接级瞬时重试)+ controller 层统一重试裁决 5 次(对齐 codex stream_max_retries=5;关键覆盖点:pi-ai 把 HTTP 错误作为 stopReason:'error' 的 errorMessage 返回而非 throw,只 catch throw 会完全漏掉 502 这类失败——throw 与 errorMessage 两路归一进同一裁决,失败判定 =result.errorMessage非空(pi-ai 的 HTTP 错误以 stopReason:'error' 正常收敛、agent_end 照常到达,reachedAgentEnd === false的判定会完全漏掉它们——已修正为检查 errorMessage));退避 200ms x 2^(n-1) x uniform(0.9,1.1);isRetryableError覆盖网络/超时/429/500/502/503/504/524/Cloudflare 52x;401/402 等确定性失败不重试;每次重试重建历史(R13 剔除失败行)并 beginAssistant 另起新行;手动重试按钮保留。自动重试上限 10 次(2026-09-26 用户定档,替代初版 5 次);每次重试追加 RetryAttemptRecord(attempt/maxAttempts/errorMessage/plannedDelayMs)到 state.retryAttempts;UI 两处(对齐 ZCode/LiveAgent):① 状态条下方重连副行「重新连接中… N/10」(reconnect-line,仅流式中的轮显示,对齐 ZCode 重连样式);② 「重试详情 (N)」折叠块(RetryDetailsBlock,对齐 LiveAgent RetryDetailsBlock:RefreshCw 图标 + 折叠头 + 每次尝试卡片「第 N/M 次重试 + 错误原文」)——实时轮从 state.retryAttempts 传入,完成后固化到 assistant 条目(retryAttempts part)随消息持久化;关键修复:beginAssistant 保留轮次级 state 字段(原实现构造全新对象把 retryAttempts 抹掉,导致记录只剩 1 条)。 - 修复:markdown 列表/标题渲染丢失(2026-09-26)——Tailwind preflight 把
ul/ol的 list-style 与h1-h4的字号/字重重置,.md只补了边距,导致模型输出里的列表渲染成无符号缩进段落、## 标题渲染成与正文同大的普通文本(用户感知为「总结没有列表、很紧凑」)。已显式恢复:h1-h4 分级字号(1.5/1.3/1.15/1.05em)+ 700-600 字重,ul disc / ol decimal +li::marker暗色,li 项间距 0.25em。 - 新增:附件与图片粘贴(2026-09-26,对齐 LiveAgent)——Rust 命令
fs_pick_files(rfd 多选)/fs_import_pasted_file(粘贴图片 base64 落盘.ReinAgent/.temp/pasted/)/fs_read_image_preview(≤5MB 缩略图 base64)/fs_read_attachment_base64(≤25MB 发送内联);前端 Composer 真实附件状态(上限 9、扩展名图片白名单)、textarea onPaste 粘贴图片、缩略图点击 Lightbox 放大(Radix Dialog)、X 删除;发送时文本附件以路径引用追加、图片转 pi-ai 原生 image content block 内联(非视觉模型降级为路径引用提示);后续轮次重建历史时图片不再保留(v1 限制)。 - 粘贴路由重构(2026-10-05,用户实测「对话框粘贴截图没反应」):根因是 WebKitGTK 的
paste事件会派发但不交付剪贴板图片——clipboardData的 types/files/items 全空、文本 0(WebKit bug 320303/218519;Chromium/WebView2 则正常,ZCode 桌面端能用就因为它跑在 Electron 上,代码与我们同为clipboardData.files)。探针实测确认「paste 会派发、但 DataTransfer 全空」,且原生读取可行(xclip -t TARGETS -o→image/png,实取 25502 字节)。修法:- 新增 Rust
clipboard_read_image(src-tauri/src/clipboard_image.rs)——仅 Linux 依次尝试wl-paste --type image/png|jpeg|gif|webp→xclip -selection clipboard -t … -o(照 ZCode CLI 同序同集合);拿到图片返回{mime,name,base64Data},剪贴板无图 →null(静默),两个工具都不存在 / 超 25MB → 真实错误;非 Linux 返回null(DOM 路径本就正常)。读取用timeout 5包裹防挂起。 - 前端
LexicalComposer.tsx粘贴改为按lib/chat/pasteRouting.ts的纯逻辑classifyPaste路由:DOM 已有图片 → 既有路径;超长文本(≥15KB 字符,ZCode 同阈值)→ 落盘为pasted-text-YYYYMMDD-HHmmss.txt附件(>10MB 如实报错不落盘;这正是用户要求的「大文本直接粘贴为文件」);无图且无文本 → 走上面的原生读图兜底(base64 → 合成File→ 复用既有handlePasteFiles);其余 → 普通文本粘贴不拦截。发送仍是[Attached file: <路径>](模型read_file读取),与 ZCode 的路径附件语义一致。 - ⚠️ 已知取舍:
wl-paste本机未安装(走 xclip/XWayland);剪贴板同时含图片与文本时按文本处理(不触发兜底);文本附件上限在前端判定(10MB),fs_import_pasted_file本身不加上限。 - 测试:
pasteRouting.test.mjs7 例(阈值 14999/15000/15001、10MB 边界、CJK 字节数、命名)已挂 test:chat;Rust 侧只留无副作用单测(候选表契约 + PATH 探测)——刻意不写「往剪贴板放图再读回」的 E2E:xclip -i会 fork 后台守护进程劫持剪贴板、并继承测试进程输出管道导致测试框架挂起(本轮实测踩到,已删)。
- 新增 Rust
- 修复:用户气泡图片点击放大失效(2026-09-26)——MessageItem 的返回结构是「用户分支提前 return + 助手分支 return」,Lightbox 最初只挂在 assistant 分支的树尾,用户气泡分支的树里没有该节点(点击后 setState 生效、组件重渲染,但 JSX 树中无 Lightbox → 无 DOM 变化)。已在用户分支 return 的根 div 内补挂 ImageLightbox(Composer 附件条与消息气泡共用组件)。
- 任务级隔离 + 审批模式(2026-09-25):见已实现清单 #14;
ApprovalMode值域由无实效的 always/suggest/auto 替换为 plan/ask/edit/full;全局默认持久化 kv(reinagent-approval-mode,缺省 full)。 - 修复:Linux 编译回归(2026-09-27)——commit 177ad5a 的
fs_cmd.rsspawn_shell用if cfg!(target_os = "windows")(运行时布尔宏,不做条件编译)包裹 Windows 专属代码(std::os::windows::CommandExt的raw_arg/creation_flags),两个分支在 Linux 上仍参与类型检查,导致 E0433/E0599、桌面端在 Linux 无法编译启动。已改为#[cfg]/#[cfg(not)]属性条件编译(Windows 分支保留 raw_arg + CREATE_NO_WINDOW 语义,Unix 分支sh -c),CREATE_NO_WINDOW常量同步加 cfg 门;Windows 行为不变,Linux 恢复可编译。 - 修复:工具结果一刀切 8KB 截断导致大文件读不全(2026-09-27,对齐 ZCode 按工具分设上限)——原
TOOL_LIMITS.maxResultBytes = 8192对所有工具统一截断,85KB 的 PROJECT_CONTEXT.md 一次只能读前 8KB,模型被迫用 execsed -n按 8KB 一段磨十几步(exec 结果同样被 8KB 闸拦住)。已改为按工具分设:read_file= 2000 行 / 256KB(对齐 ZCodeREAD_DEFAULT_MAX_LINES/READ_MAX_FILE_SIZE_BYTES,行数闸applyReadLineCap先行 + 字节闸二次兜底,details 带totalLines/linesTruncated)、exec_command= 30KB(对齐 ZCode bashMAX_INLINE_OUTPUT_BYTES)、list_dir= 8KB 维持;buildTextToolResult增加每工具maxBytes参数;read_file/exec 描述文本同步告知模型上限;Rust 侧fs_execute的 256KB 收集上限保持不变。 - 改动:快捷动作卡改为「预填不发送」(2026-09-27,用户定档)——EmptyState 四个快捷按钮(周报总结/报错修复/需求开发/闲时任务)点击后只把提示词填进输入框(聚焦 + 光标到末尾,可编辑后手动发送),不再自动发送。实现:
LexicalComposer新增prefillRequest?: { text, nonce } | nullprop(nonce 变化触发 setText + 聚焦,沿用focusRequestTrigger模式),App 持有composerPrefill状态 + 单调 nonce(hooks 声明在设置页早退 return 之前)。固定填充文案(EmptyState.prompts[].fill,与按钮标签分离):周报总结→「每周五总结这一周发生的事情。」;报错修复→「请分析以下终端报错日志,找出导致该错误的根本原因,并提供可以直接运行的修复代码示例。」;pptMake 按钮标签改为「需求开发」(i18n zh/en 同步),填充→「先完整阅读所有文档和代码,掌握整个开发流程和进度,严格遵守开发规则,等待新需求」;闲时任务暂保留预填、待自动化页面移植后改为页面导航。欢迎页快捷按钮与输入框间距mt-8→mt-16(聊天框下移)。
渲染增强类:
- diff 视图 / 代码块的 Shiki 语法高亮 —— 已随 PreviewPane 移植引入(
shiki@^4+@pierre/diffs,工具卡内联 diff 视图仍为单色形态) - PPTX / PDF / Office / 媒体预览引擎(PreviewPane 模式壳已移植,渲染引擎未引入,激活时如实显示「不可用」)
- 流式「正在思考」扫光动画(shimmer)(LiveAgent/ZCode 用 CSS 渐变动画表达运行态,替代旋转图标)
- 临时文件目录约定 + 白名单清理(B+C 组合,2026-09-24 落地)
<think>...</think>内嵌标签解析兜底(Ollama 式网关把思考内联在 content 里;LiveAgent 有inlineThinkTagStream归一化;WorkBuddy 实测走原生reasoning_content,暂无需求)
工具卡类:
- 查阅卡搜索类 bucket(当前无搜索工具;加入后自动扩展 N 搜索 徽标)
- 多文件编辑子卡(ZCode 多文件 edit 每文件独立展开块;ReinAgent edit 为单文件模型)
- 文件 chip 点击打开代码查看器 —— 已实现(编辑/写入/读取卡 header 点击 → 右侧 PreviewPane;
撤销真回滚待 Rust 写入日志已由 checkpoint/rewind 实现(见 四点七.3,行内 Undo2 按钮,2026-09-26)) - 工具卡展开态跨重挂载记忆(ZCode 用模块级
toolLayoutOpenState: Map<toolId, boolean>)
回合/时间线类:
- 多段回合独立折叠(ZCode 每个 assistant 段独立 Collapsible;ReinAgent 一轮一个头)
- 权威工时(ZCode TurnHeaderRow
activeMs排除权限/输入/校验等待;ReinAgent 全按墙钟) - 运行中段尾 ChatLoading 转圈 + API 重试计数
- 长会话虚拟滚动(ZCode 用 @tanstack/react-virtual)与历史分页 hydration
元数据与持久化类:
context_length/max_tokens真实元数据解析(替代modelFactory写死 128000/8192,见 十.4 铁律整改项)- 对话持久化升级 Sqlite /
~/.ReinAgent/JSON(见 十.2) - 查阅卡目录文件树渲染(见 十.1)
基础设施类:
- worktree 多 agent 并行 + Vite 端口参数化(用户已决策暂用分支方案,见记忆)
十二、已知问题:Linux 窗口大小/位置记忆失效(2026-09-27 诊断完毕;过渡方案已落地)
过渡方案(2026-09-27 用户定档,方案 A/B 仍待后续决策):Linux 平台停用 tauri-plugin-window-state(lib.rs with_window_state cfg 门:非 Linux 才注册插件),每次启动按 tauri.conf.json 窗口默认值 1800×1200 + center: true 启动(工作区 2560×1440 内放得下,不触发合成器强制改尺寸);Windows 端窗口记忆不受影响。~/.config/com.reinagent.app/.window-state.json 旧状态文件在 Linux 上已无效。
1. 症状与环境
- 症状:Linux 下每次启动都不按上次的窗口大小和位置打开。
- 环境:GNOME 50.1 / Wayland 会话(
GDK_BACKEND=wayland)/ 显示器 2560×1440@100% 缩放(工作区约 2493×1400:顶栏+停靠栏占位)/tauri-plugin-window-state 2.4.1+ tauri 2.11.5 + tao 0.35.3。 - 状态文件:
~/.config/com.reinagent.app/.window-state.json(labelmain,内容 2105×1371@(0,0),mtime 2026-09-24 07:16 后从未更新)。
2. 三层根因(全部实测证实)
- 保存侧:非优雅退出 = 永不写盘。插件仅在
RunEvent::Exit时写文件(lib.rs:503),窗口事件只更新内存缓存。开发流程的 Ctrl/C/SIGTERM/SIGKILL/重启全部丢失——实测 pkill 后文件 mtime 不变。这是「9-24 之后调整的窗口全部没记住」的原因。 - 恢复侧(尺寸):Wayland 下恢复请求被合成器强制覆盖(主凶)。
WAYLAND_DEBUG=1协议级证据:应用启动后确实请求了恢复尺寸xdg_surface.set_window_geometry(26, 23, 2105, 1418)(GTK CSD 客户端装饰使窗口比保存值膨胀约 +47px,1371→1418),高度 1418 超出工作区 1400,Mutter 立即回发xdg_toplevel.configure(2493, 1400)(= 工作区大小)强制放大,窗口从此钉在 2493×1400——即用户看到的「每次都是错误尺寸」。对照实验:GDK_BACKEND=x11(XWayland,SSD 系统装饰,无 CSD 膨胀)下尺寸恢复正常(xdotool 实测 client 2105×1363)。 - 恢复侧(位置):Wayland 协议层面禁止应用自定位(
gdk_window_move是 no-op),保存的 x/y 永远被忽略;X11 下插件的set_position发生在窗口映射前,仍被 Mutter 初始摆放覆盖(存 0,0 实测开在 305,139)——映射后(post-map)再定位才有效。
- 附:插件恢复逻辑(lib.rs:194-206)有
available_monitors?+set_position(...)?的?级联——任一 Err 会使 set_size 也被跳过(let _ =吞掉错误);intersects门槛对本例不构成问题。
3. 修复方案(已定,待实施)
方案 A(推荐,大小+位置都能恢复):
src-tauri/src/lib.rs在 Tauri 启动前(Linux 平台)std::env::set_var("GDK_BACKEND", "x11")走 XWayland——SSD 无 CSD 膨胀、位置可设置。当前 100% 缩放下无模糊风险;若将来启用分数缩放需重新评估 XWayland 渲染质量。- 保存加固:
on_window_event监听Resized/Moved,防抖 ~600ms 调用插件现成的window.save_window_state(StateFlags::all())(WindowExt,lib.rs:115)——不再依赖优雅退出。 - 恢复后移:窗口 show 后(setup 内延迟任务或首个 Resized 事件)再
set_position+set_size(钳制到工作区内);X11 下 post-map 的 move 走 ConfigureRequest,Mutter 会真实执行。Windows 端维持插件现状(cfg 门隔离,零影响)。
方案 B(纯 Wayland 折中):只做上述 2+3(不切 X11)——尺寸可恢复(钳制后不再触发合成器覆盖),位置永远记不住(Wayland 硬限制)。
4. 复验手段(实施后验收用)
- Wayland 现状复现:
WAYLAND_DEBUG=1 src-tauri/target/debug/ReinAgent抓set_window_geometry/configure序列; - X11 恢复验证:
GDK_BACKEND=x11启动后xdotool search --name ReinAgent getwindowgeometry(本机有 xdotool/xwininfo;GNOME 50 的 Shell Screenshot/Introspect DBus 已确认 AccessDenied 不可用)。
十三、自动化定时任务(一期,2026-09-27 落地;对齐 ZCode AutomationsSection)
1. 架构
- Rust
src-tauri/src/automation.rs:- 存储:
~/.ReinAgent/conversations.db新增automations/automation_runs两表(独立连接,WAL 多连接并存);scheduleRule 结构化规则为调度权威(JSON 列),cronExpr 仅展示; - IPC 8 命令:
automation_list / create / update / delete / set_enabled / run_now / list_runs / run_finished(serde camelCase DTO 与前端 types.ts 一一对应); - 调度线程(
start_scheduler,setup 钩子启动):每 20s 轮询(对齐 ZCode POLL_INTERVAL_MS)enabled=1 且 next_run_at<=now的任务 → claim(写 running run 行 +run_count+1+ 推进next_run_at)→app.emit("automation-due", payload)派发前端;启动时残留 running 一律收敛stopped(应用重启中断);next 计算失败退避 1h 并记 last_error。
- 存储:
- 前端:
src/lib/automations/types.ts(DTO 类型 +inferPreset/applyPreset/describeRule摘要)与store.ts(zustand:列表缓存 + CRUD + loadSeq 过期响应守卫);AutomationsPage.tsx(列表页:页头/刷新/创建按钮、状态筛选 pills 全部·进行中·已暂停·失败、卡片网格grid-cols-1 lg:grid-cols-2、卡片=标题+提示词两行+频率徽标+运行计数+下次运行+启停开关+立即运行+行内二次确认删除、空态引导);AutomationEditView.tsx(创建/编辑:名称、频率构建器(预设 pills 每小时·每天·工作日·每周·每月·自定义 + 规则编辑器:间隔/时间/星期 chips/月日 chips)、提示词、模型双下拉(服务商+启用模型)、工作区只读、保存/取消);- App 派发器(
dispatchAutomationRun+ ref +automation-due事件监听一次注册):到点 →createTask(title, workspacePath, 模型, approvalMode="full")→poolSend(prompt, maxSteps=0)→ 5s 轮询getEntrySnapshot收敛(done/error/stopped)→automation_run_finished回报 outcome; - 入口(2026-09-27 修正补全):侧栏 Quick Actions 的「自动化」按钮(Timer 图标,原为无 onClick 的占位——用户点击无反应的根因)+ 侧栏底栏时钟图标 + 欢迎页闲时任务按钮(
onOpenAutomations导航,替代预填);ViewMode扩展"automations";页面为主视图形态(渲染在主内容区、保留侧边栏与顶栏,统计行/终端面板仅 workbench 视图显示,页面根由h-screen改h-full填充主区);从自动化页激活任务自动切回工作台(setActiveTaskId内:currentView 为 automations 时任何任务激活/新建任务都切回 workbench——点击侧栏任务即进入对话;自动化自身派发走createTask内部赋值不经此 action,页面停留不被打断)。
- 调度规则语义(ScheduleRule):
unit: minute|hourly|daily|weekly|monthly(年/yearly 一期不做)、interval≥1、hour/minute、weekdays(0=周日…6=周六,工作日预设=1-5)、monthDays(1-31,月末越界自动跳过);预设映射:每小时=hourly/1,每天=daily/1,工作日=weekly[1-5],每周=weekly 单选星期,每月=monthly 单选日期;计算=从 now 起按锚点对齐向后扫描(epoch 分钟/小时/天/周/月对齐),有界防死循环。 - 样式:颜色/字号全部走项目语义变量(
--brand/--surface/--text-dim/--border等 +text-ui-*刻度),交互对齐 ZCode(卡片 hover 显现动作、切换开关、pills 单选)。
2. 已知边界(一期范围外,二期候选)
- 运行历史 tab(
automation_runs已落库、list_runs命令已备,UI 未做)与运行记录跳转会话; - ZCode 的 OffPeak 闲时任务 tab、任务模板库、cron 自定义对话框、workspace 选择器(一期工作区取创建时的
selectedProject); - 调度要求应用处于运行状态(桌面常驻应用语义,与 ZCode 桌面端一致;应用关闭期间到点的任务错过不补跑)。
十四、LiveAgent 资源中心移植:搜索 / MCP / 记忆 / Skills(一期,2026-09-27)
照抄 LiveAgent(源码 crates/agent-ui + crates/agent-gui/src-tauri)的四个功能,存储路径全部 ~/.ReinAgent/。
0. 侧栏重排(照抄 LiveAgent ChatHistorySidebar + sidebarShortcuts)
- 新建任务行右侧 = 放大镜(原 Ctrl+N 字样删除;点击打开搜索弹窗);
- 自动化下新增 Skills(Blend 图标)/ MCP(Cable)/ 记忆(Brain) 三个入口(setCurrentView 新增
skills/mcp/memory三视图,页面为主视图形态保留侧栏顶栏); - 插件市场占位删除(pluginMarket 键移除)。
1. 搜索(ConversationSearchDialog 移植)
- UI:居中 Radix Dialog(防抖 180ms、分组结果、空态最近会话 12 个、↑↓/Enter/Esc 键盘导航、
[...]片段标记保留、点击结果跳转对应任务并聚焦)。 - Rust
src-tauri/src/history_search.rs:chat_history_search命令——标题命中(task.payload 提取)加权优先 + 消息全文命中(part 表 text/thinking LIKE,UTF-8 边界安全片段窗口 40 字符),每任务最多 5 条片段、最多 20 组;空查询返回最近任务。FTS5 二期。 - 接线:
WorkspaceSidebar onOpenSearchprop → App 的searchOpen状态 +ConversationSearchDialog(onOpenTask= setActiveTaskId + 聚焦)。
2. MCP(LiveAgent commands/integration/mcp.rs 同构实现,2026-09-27 升级)
- Rust
src-tauri/src/mcp.rs(进程级OnceLock<McpRuntimeManager>单例连接池,map 锁不跨 client 锁):- 配置
~/.ReinAgent/mcp_servers.json:McpServerConfig与 LA 同构(camelCase serde):id/description?/docsUrl?/enabled/transport(stdio|http|sse)/command/args/env?/cwd?/url?/headers?/timeoutMs(缺省 60000)/messageUrl?;旧 JSON(含废弃name、缺新字段)serde default 兼容读取不迁移。auth/OAuth 本期不做。 - 三传输:stdio(spawn 子进程 + 行协议;Windows .cmd/.bat 经
cmd.exe /E:ON /V:OFF /D /S /C+ raw_arg 整行转发(LA issue #205),PATHEXT 解析裸程序名;stderr 环形 tail 200 行;进程树 kill = Windowstaskkill /PID x /T /F/ Unix 进程组-TERM→-KILL);streamable-http(POST +Accept: application/json, text/event-stream,保存MCP-Protocol-Version/mcp-session-id响应头,响应 JSON/SSE 双解析,404+有会话=SessionExpired404);legacy http+sse(后台线程 GET 长连,重连退避 1s→30s 指数、成功复位,endpoint 事件解析 POST 地址,messageUrl 覆盖/相对 join//sse→/message猜测)。 - 协议层
McpClient:initialize 协议版本依次尝试["2025-11-25","2025-06-18","2025-03-26","2024-11-05","2024-10-07"],成功后发notifications/initialized;会话过期 reset+re-init+retry 一次;超时全部按timeoutMs;连接同 id 同配置复用、配置变更重建、重建失败逐出 stale。 - 8 命令(JS 侧 camelCase 参数键):
mcp_save_servers(servers)/mcp_list_servers()/mcp_list_tools(servers)(全量已启用列表入参,返回Vec<McpToolInfo{serverId,serverLabel,name,description,inputSchema}>,部分失败跳过、全部失败才 Err)/mcp_call_tool(serverId, toolName, arguments)→{content:[{type,text|image}], isError, details}/mcp_test_server(server, includeSchema?, persist?)/mcp_restart_server(server, includeSchema?, persist?)(均返回McpRuntimeTestResponse{ok, phase: config|spawn|initialize|tools_list, transport, durationMs, running, initialized, toolsCount, tools, error, stderrTail})/mcp_runtime_status(serverId)/mcp_stop_server(serverId)。
- 配置
- Agent 集成(
src/lib/mcp/mcpTools.ts,已适配新契约 2026-09-27;2026-10-20 修复参数 Schema 丢弃缺陷):createMcpTools()枚举工具走单次mcp_list_tools({ servers })(传全量已启用列表;失败如实跳过),工具名mcp__<serverId>__<tool>,执行透传mcp_call_tool({ serverId, toolName, arguments })(isError/content[] text 拼接;错误 throw → 库侧 isError toolResult);runAgentTurn将 MCP 工具附加到工具数组,权限分级保守视为 write(未知工具名 → ask/edit 需审批,plan 拦截)。参数 Schema 透传修复:buildMcpTool()修复此前硬编码parameters: Type.Object({}, { additionalProperties: true })导致丢弃服务器返回的inputSchema的严重缺陷;改为直接透传tool.inputSchema(标准 JSON Schema Draft 格式,若缺失则 fallback 至空对象)。底层依赖库@earendil-works/pi-ai(validateToolArguments)对没有 TypeBox symbol 标记的 schema 原生支持coerceWithJsonSchema+ Ajv/TypeCompiler 校验,使得模型端能正常接收并填充参数(如jadx-mcp-server的class_name等必填字段),不再报Missing required argument。 - 页面(
src/components/mcp/,LA pages/mcp-hub 全量 10 文件移植):三 tab(已配置/MCP Store/本地导入)+ HubHeader 大标题;已配置卡片(启停/编辑/删除/传输徽标/args·env·headers 计数;OAuth 与 ToolPolicy 列一期裁剪留 TODO(phase2));MCP Store 三源 registry(official/smithery/glama,经 hubFetch)+ 预览抽屉 + 配置弹窗;本地导入(四外部源扫描 + 任意 JSON/TOML 文件,勾选批量导入);hubSettingsAdapter.ts提供updateMcp(prev,patch)保持 LA 调用点逐字。 - 从 JSON 导入(
src/lib/mcp/mcpJsonImport.ts+src/components/mcp/McpJsonImportModal.tsx,2026-10-20 对标 AionUi 移植):兼容标准mcpServers对象与数组;自动使用normalizeStdioCommand与shellSplit拆分包含启动参数的命令;严格前端预校验与多入口触达(Hub 顶栏、导入页工具栏、空状态卡片)。
3. 记忆(LA services/memory 全量移植,2026-09-27 升级)
- Rust
src-tauri/src/memory/(LAservices/memory/8600 行逐字移植 +commands.rs薄包装,单例OnceLock<Arc<MemoryStore>>):- 存储
~/.ReinAgent/memory/:memory-index.sqlite3(WAL;FTS5 unicode61 + trigram 双虚表、审计表 op/actor CHECK、memory_organize_runs表、schema version 4,integrity_check 失败自动隔离重建)+global/{user/, daily/YYYY-MM-DD.md(.archive/<年>/ 90 天归档), .trash/, .organize-snapshots/}+projects/<sha256 前 16 hex>/{.workdir.json, .trash/}+.quarantine/(wipe 隔离)。 - frontmatter:
name/type/scope/description/headline/date?/createdAt/updatedAt/source{trigger,conversationId?,model?,risk_flag?,unreviewed}/links;daily 另有appendCount+sources[];证据块(confidence/source_quote≥5字否则自动降级/reasoning/aliases/conflicts_with/supersedes/override_reject+auto_downgraded)由 Rust 渲染与契约校验。 - 语义:
unreviewed仅 extractor/整理器写入为 true(用户手写免审)、memory_accept 通过审核;quota 每作用域 500 条(80/95/100% 三级);删除进.trash、wipe 进.quarantine;daily 只能 append(32KB 上限,普通 8KB),凌晨 4 点滚动日界。 - 23 命令(
memory::commands::*注册为memory::*):list/read/search/write/update/delete/delete_project/accept/apply_batch/organize_run_{create,update,list,read,clear_history}/organize_due_{claim,complete}/index_overview/paths_info/recent_rejections/today_local_date/today_daily/quota_summary/wipe_all。 - 测试 38 例(LA 39 例移植,仅
#[cfg(unix)]符号链接用例 Windows 平台过滤);已知一期边界:memory_search的historyMatches恒空(与 history_search 表结构不合,二期接)。
- 存储
- Agent 集成(对齐 LA 注入格式):
runAgentTurn经src/lib/memory/prompts/injection.ts注入# Memory Index分桶索引(User/Unreviewed/Project/Global/Daily 五桶、[slug|u*:h|d0]置信度/新鲜度标记、每桶 30 条上限、16K 字符截断)+## Memory工具规则段;并挂载MemoryManager工具(src/lib/memory/memoryManagerTool.ts,action 判别 list/read/search/write/update/delete/accept + 证据字段结构化透传;权限分级走未知工具保守 write)。 - 页面
MemoryPanel.tsx:记忆卡片(类型徽标 + 标题 + 三行摘要 + 更新日期)+ 新建/编辑弹窗(标题/类型/正文)+ 行内二次确认删除已于 2026-09-27 被 LiveAgent Memory Hub 完整移植版整体替换(见下)。
3.1 记忆 Hub 前端整体移植(LA pages/settings/memory/ 全量 6 文件 + ResourceManagementPage 外壳 → src/components/memory/,2026-09-27)
- 页面结构与导出形态:
MemoryPanel({ workdir?, modelOptions? })(App 侧接线:workdir=当前工作区根即 project 作用域数据源;modelOptions=整理/总结模型选择器选项{value,label,group?}(providerId::modelId),缺省空数组时 ModelPicker 如实显示空并出「未配置模型」警告横幅)。外壳 =HubHeader(title=settings.navMemory, prominent)(对齐 LA ResourceManagementPage 只传 title,描述行由面板内承担)+MemoryPanelInner(LA MemoryPanel 1:1 移植体);settings/setSettings 来自useHubSettings;页面最外层与全部自建 Portal 弹层(新建 Dialog / 设置抽屉 / AlertDialog / SelectContent)追加hub-scope。 - 面板功能:三 tab(全局/项目/日志,segmented Tabs 带计数)+ 关键词过滤(matchesFilter)+ 项目作用域按 workdir 分组
<details>折叠列表 + 右侧详情编辑(daily 为追加模式 appendBody,其余 replace)+ 新建 Dialog(slug/类型/作用域/描述/正文)+ 待审核横幅(unreviewed 计数)/云同步目录横幅(memory_paths_info)/配额警告横幅(healthy/warning/danger/full 阶梯)+ 审核(memory_accept)/删除(ConfirmDeletePopover,文案 props 注入)/清空全部(memory_wipe_all + AlertDialog 二次确认)。 - 设置抽屉
MemorySettingsDrawer:配额阶梯横幅(memory_quota_summary+ lib/memory/organizer/quota deriveQuotaLadder,{scope}/{used}/{limit} 占位替换);驱动模型双 ModelPicker(整理模型清空即自动关整理开关;总结模型清空=跟随对话模型);整理开关/频率(不执行|每天|每周)/时间(400ms 防抖 + 卸载 flush)/星期/范围/模式 + 「下次自动整理」状态行(organizerSchedule.ts的 computeNextMemoryOrganizerRunAt 纯函数,LA themeAndMemory.ts 移植);存储路径展示(memory_paths_info.root,fallback~/.ReinAgent/memory);危险区清空。配置经updateMemorySettings(settings.memory 切片 spread)持久化 kvreinagent-memory-settings。 - Organizer 一期边界(用户定档):设置项照常读写持久化;「立即整理」点击仅
toast.error(settings.memoryOrganizerPhase2)(zh「记忆整理器将在后续版本提供,敬请期待」),不移植 pokeMemoryOrganizer/canRunOrganizerLocally/memoryOrganizeRunCreate,绝不伪造整理运行;历史弹窗OrganizerHistoryModal正常读memory_organize_run_list(一期恒空态如实展示),状态过滤/清历史/统计卡/手动建议勾选应用(memory_apply_batch + runRecord v4 协议)/跳过分布/裁剪协议折叠逻辑全量保留(无运行记录时自然不可达)。 - 适配要点:每个文件顶部标注 LA 源路径;图标 lucide-react 同名;
web:max-820:*→max-[820px]:*;LA tokens 类改等值 arbitrary(gap-12px→gap-3、pb-settings-memory-panel-pb→pb-[max(14px,env(safe-area-inset-bottom))]、max-h-settings-memory-entry-list-max-h→max-h-[min(42svh,320px)]、grid-cols-memory-navigation→grid-cols-[280px_minmax(0,1fr)]、grid-cols-skill-filter→grid-cols-[auto_minmax(9rem,auto)]、min-h-auto→min-h-[auto]);i18n 走createMemoryTranslate适配层(hub/zh.ts 已收录的 174 个 settings.memory* 键直读 +i18n/hub/extra-memory.ts补 8 个缺失键:settings.close/cancel/delete、chat.searchModel/noModelFound/collapseProvider/expandProvider、settings.memoryOrganizerPhase2);lw 内部弹层(ConfirmDeletePopover 气泡、ModelPicker 下拉)已由集成方在 lw 组件内部补注 hub-scope(confirm-action-popover/tooltip/ModelPicker 的 Portal 内容类名),弹层同样吃 LiveAgent 色板。
4. Skills(LA services/skills 全量移植,2026-09-27 升级)
- Rust
src-tauri/src/skills/(LAservices/skills/全文件移植:mod/types/util/paths/metadata/library/sources/install/jobs/clawhub/create/validate/builtin/external/external_mcp +commands.rs):- 存储
~/.ReinAgent/skills/(写锁SKILLS_WRITE_LOCK互斥;安装=stage-then-swap 原子换目录,conflict=backup|fail|overwrite;安装源支持 GitHub URL/HTTP zip/本地路径;后台安装任务 jobs(phase queued→downloading→…→done|error|cancelled,完成项保留 60 分钟))。 - 内置技能(
builtin.rs+src-tauri/prompt/skills/三目录 include_str! 编译期内嵌,文案宿主名已改 ReinAgent):skills-installer、skills-creator(前端 ALWAYS_ENABLED 恒启用)、liveagent-code-review;所有权标记_reinagent_builtin.json,启动时 setup 钩子播种(冲突备份、无效替换、内置防删防覆盖)。 - 外部扫描:
~/.claude/skills、~/.codex/skills、~/.codebuddy/skills-marketplace/skills、~/.agents/skills(与 LA 完全一致);external_mcp.rs同文件移植(四 MCP 配置源 + 任意 JSON/TOML 文件解析)。 - 6 命令:
system_manage_skill(payload)(action 分发 list/read/install/install_start/install_status/install_cancel/delete/scan_external/scan_external_mcp/scan_mcp_file/clawhub_install/create/validate/package)/system_read_skill_text/system_read_skill_metadata/system_ensure_builtin_skills/mcp_scan_external/mcp_scan_config_file。测试 57 例(44 skills + 13 external_mcp)。 - HTTP 下载用 ureq 3(进度回调写 job 字节计数;LA 的 reqwest/system_proxy 未引入,代理走进程环境变量)。
- 存储
- Agent 集成(对齐 LA 注入格式):
runAgentTurn读hubSettingsStore.settings.skills(enabled+selected),经lib/skills/index.ts的buildSkillsSystemPrompt({rootDir, selected: SkillSummary[]})注入skill://路径协议 + 渐进披露清单(元数据技能只列 name/description/skillFile/baseDir;README 回退技能带 inlineContent 全文)。 - 页面
SkillsHubPage.tsx:技能卡片(名称/描述/id chip/启停开关/编辑/删除)+ 新建/编辑弹窗(id 目录名/名称/描述/正文 Markdown)已于 2026-09-27 被 LiveAgent Skills Hub 完整移植版整体替换(见下)。
4.1 Skills Hub 前端整体移植(LA pages/skills-hub/ 全量 17 文件 → src/components/skills/,2026-09-27)
- 页面结构:三页签(已安装 installed / 商店 store / 本地导入 import)+ 顶部常驻搜索框 + 排序 Select + 资源级启用 Switch(写入
hubSettingsStore.settings.skills)。SkillsHubPage()无 props(内部取useHubSettings),渲染SkillsHubPageInner1:1 移植体(isAgentMode恒 true,chat-mode 锁定分支保留但不可达)。 - 已安装页:memo 化
InstalledSkillCard(latest-ref 回调 +[content-visibility:auto]),ClawHub 分类 chips 本地启发式分类(classifyInstalledSkill)、名称/描述 fuzzy 搜索高亮、useDeferredValue防卡顿、单卡启停(ResourceActivationSwitch)/删除(ConfirmDeletePopover)/预览抽屉(InstalledSkillPreviewDrawer,system_read_skill_text读 10000 行 + frontmatter 元数据剥离 + Markdown 预览走 chat MarkdownText)。 - 商店页:ClawHub 目录(
lib/skills/clawHub.ts经 hubFetch→Rust 通道)+skillStoreCacheLRU 目录/详情缓存(目录 2min/详情 10min 新鲜期)、install_start/install_status/install_cancel任务轮询(600ms)、分阶段进度条、分类过疏自动补页。 - 导入页:
scan_external扫描 Claude Code/Codex/CodeBuddy/Agent Skills 四源(SkillsImportSourceTabs),勾选批量导入(manage_skill action=install source=<baseDir> conflict=backup),已安装技能锁定不可再导入。 - 批量模式:工具条(数量/全选当前筛选/批量启用/禁用带 Undo toast/批量删除带预览确认);Ctrl+A 全选当前筛选(输入框聚焦时让位浏览器默认)、Esc 退出、Shift+点击区间选择(installed/import 两页均支持)。
- 存储:skills 切片持久化 kv
reinagent-skills-settings({enabled, selected[]},always-enabled 内置技能恒在列,对齐 LAmergeAlwaysEnabledSkillNames)。 - 适配要点:i18n 走
./useLocale适配层(useTranslation+i18n/hub/extra-skills.ts补 4 个缺失键:settings.cancel / settings.delete / settings.cronViewClose / settings.switchToAgentMode);removeWorkspaceResourceReferences桩化为恒等(TODO(phase2),无工作区资源概念);lw Sheet 为 Radix 实现,useDrawerPresence改为钩子内自管理(entered 首帧置位 + 关闭 220ms 后释放快照,替代 Base UIonOpenChangeComplete);Portal 弹层(SheetPopup/SelectContent)追加hub-scope(lw 内部的 ConfirmActionPopover/Tooltip 弹层由集成方在组件内补注,色板一致)。
5. 验证与边界(2026-09-27 全量落地后实测)
- 前端:
bunx tsc --noEmit0 错误;bun run build通过;测试基线 test:chat 108 / test:agent 39 / test:providers 8 / test:settings 6 / test:markdown 12 / test:hub 57(新增:ClawHub 分类/归一化/owner 收敛、installedSort、mcpRegistry 白名单与安装草稿、memory schema/quota/runRecord v4)。 - Rust:
cargo check0 error;cargo test --lib123 通过(memory 38 + skills 57 + mcp 19 + 既有 9)。 - 样式审计:移植类 token 经 Tailwind 真实编译引擎比对 0 真实缺失;构建产物含全部移植关键类(settings-tile 系/shadow-ui-*/animate-hub-loading-progress/hub-scope 双主题变量/dark 变体
:where([data-theme=dark]...))。 - 二期候选(用户定档一期范围):记忆 Organizer 执行引擎(五阶段 LLM 管线 + due_claim 调度接线,Rust 表与命令已备)、记忆 Extraction 聊天后自动抽取(钩子点=conversationController 轮次持久化后)、MCP OAuth 全链(LA services/mcp_oauth,依赖 keyring/getrandom)、MCP 卡片 ToolPolicyToggle(需
system.toolPolicies设置切片)、memory_searchhistoryMatches 接 history_search、ClawHub 安装的 agent 侧 SkillsManager 工具、技能/name显式提及注入。
6. Hub 移植公共基建(2026-09-27,Skills/MCP/记忆三页共用)
- 主题桥(
global.css):.hub-scope作用域内把共享色板覆盖为 LA 原版中性色(亮/暗两套--lw-*变量,值逐项对齐 LA tokens.css;background/foreground/border/card/popover六色走var(--lw-x, 宿主色)fallback 链,作用域外维持既有色板零影响);新增 settings-tile 系/segmented/control-surface/muted/primary/destructive/success/ring 语义色与text-tiny、shadow-ui-hubchrome-24/25、shadow-ui-skillshubpage-51、shadow-ui-mcpregistrybrowser-45/46、shadow-ui-memorysettingsdrawer-50、animate-hub-loading-progress及字面量@utility(max-w-1320px/grid-cols-form-label/min-h-360px/pb-safe-bottom-10rem 等);@custom-variant dark跟随data-theme。 - ⚠️ 移植 token 缺口修复(2026-10-05,用户报「文件侧栏下拉框的选项文字看不清」):根因是移植组件引用的设计 token 从未桥接进
global.css——Tailwind 不生成对应工具类,弹层退化成「无背景无边框」,菜单项文字直接压在正文上(src/preview/PreviewPane.tsx右上角「更多」菜单为唯一受害使用者,其组件src/preview/components/ui/dropdown-menu.tsx全仓仅此一处引用)。全量扫出 8 个缺口:bg-menu/bg-menu-hover/border-popover-border(dropdown-menu.tsx、toast.tsx)、bg-tooltip/text-tooltip-foreground(tooltip.tsx)、text-warning-foreground(button.tsx)、border-input-border(code-viewer.tsx)、bg-background-alt(ToolSummaryRow.tsx)。修法=在「ZCode PreviewPane 移植的 token 桥接」@theme inline块补齐 8 个--color-*,一律沿用既有移植约定var(--lw-x, 宿主语义色):menu→--bg-elev、menu-hover→--surface-hover、popover/input 边框→--border、warning-foreground→--bg、tooltip→--bg-elev/--text、background-alt→--bg-elev-2(ZCode 原值为半透明 mix,本仓取同层语义色,不引入 alpha 计算);取值语义逐项对齐 ZCodepackages/ui/src/styles.css(菜单亮=white/暗=neutral-950、--color-popover-border: var(--color-border)等),不写死色值。验证:dev server 编译产物里这 8 个类由「0 条规则」变为各 1 条(bg-menu-hover为data-[highlighted]:变体形态),一次性审计脚本复扫 0 真实缺口(余 2 项text-mobile-input-safe/text-size是字符串误报非工具类)。- 排查教训(重要):fish 下
grep --include=*.tsx不加引号会因通配符无匹配而报错,整条命令零输出——本轮据此一度误判「仓库里没有原生<select>」。用--include='*.tsx'或干脆不加--include。 - 顺带澄清:
global.css已有全局select { appearance: none; color/background: var(--text-primary)/var(--bg-card) }+select option {...},故项目内原生<select>(Git 面板分支下拉、终端 shell 下拉等)并不缺appearance-none,只是少了各调用点自绘的ChevronDown箭头(GitPanel.tsx:274、TerminalShellSetting.tsx:125两处未自绘,仅为观感差异,非本轮 bug 根因)。
- 排查教训(重要):fish 下
- lw 组件库(
src/components/lw/,46 文件):LAcomponents/ui+hub+resources+settings的 Radix 适配移植(Base UI→Radix:data-[active]→data-[state=active]、render→asChild、Switch/Checkbox/NumberInput 自写、Select 基于 Popover listbox、AlertDialog 由 Dialog 组合);toast.ts/toaster.tsx同 API 移植(success/error/warning + action Undo + appearance:"notice" + toast.dismiss);cn 经extendTailwindMerge对齐 LA 合并语义。lw 内部 Portal 组件(ConfirmActionPopover/Tooltip/ModelPicker 下拉)已内嵌 hub-scope。 - i18n:
src/i18n/hub/{zh,en}.ts501 对键(机械提取自 LA translations)+ 三个 extra 补充文件(skills 4/mcp 5/memory 8 键)经src/i18n/index.tsspread 合并;页面侧useLocale.ts(skills)/useHubTranslation(mcp)/createMemoryTranslate(memory)三个轻适配层统一放宽键型(运行时同一字典)。 - 设置层(
src/store/hubSettingsStore.ts):useHubSettings(settings: {skills, mcp, memory} + setSettings(updater));skills/memory 切片写透 kv(reinagent-skills-settings/reinagent-memory-settings),mcp 切片整表写回 Rustmcp_servers.json(hydrateMcp()启动装载,App 已接)。 - 出网通道(
src-tauri/src/hub_http.rs+src/lib/hub/hubFetch.ts):ClawHub/MCP Registry 统一走hub_fetch_json命令(ureq rustls 直连绕开 WebView CORS;http_status_as_error(false):非 2xx 不在 Rust 层抛错,status+body 原样透传),前端 hubFetch 返回真实Response,LA 调用方零改动(registry 的 fetchJson 自行格式化「HTTP 401: <响应体>」)。已知外部事实(2026-09-27):Glama 的/api/mcp/v1/servers已开始强制 API Key(401 响应体指引 glama.ai/settings/api-keys 创建;LA 同样受影响)——Glama tab 如实显示该错误,Official/Smithery 正常;为 Glama 加 Key 配置属二期。 - App 接线:
<Toaster dismissLabel/>挂 hub-scope 包裹层;MemoryPanel 传workdir={effectiveWorkspaceRoot}+hubModelOptions(启用服务商×启用模型);三页<XxxPage />无 props 导出形态保持 App 调用面不变。 - ⚠️ 教训:清理 i18n 键时严禁只 grep 双引号
t("key")——侧栏等组件用单引号t('key'),漏判会误删在用键(2026-09-27 已回滚一次,index.ts 以 HEAD+合并重建)。
7. 2026-09-28 增量(同日第二批:注入结构 ZCode 同款)
- meta_user 注入结构:runAgentTurn 新增
buildMetaUserBlock/prependMetaUserBlock——currentDate(ZCode 同款文案)+ 记忆索引 + 技能清单包<system-reminder>并入首条 user 消息头部;系统提示词保持静态(Environment 段留 system,Current date 行移出;faux 回显剥注入块)。面板「其他」行改名「用户上下文」(key=metaUser,i18n contextUsageOther),内容=currentDate+记忆段(与注入同源、可点击);「系统提示词」行估算补 Environment 段(ZCode env_info 归 system_prompt 口径)。测试 providers 套件 8→11(新增 6/6b/6c 三用例)。
7. 2026-09-28 三项增量
- Glama API Key 配置入口(用户拍板的超出 LA 对齐范围增量):
src/lib/hub/registryKeys.ts(kvreinagent-mcp-registry-keys)+McpRegistryToolbar钥匙按钮 +GlamaApiKeyDialog(lw Dialog,password 输入/保存/清除);lib/mcpRegistry searchGlama有 Key 时注入Authorization: Bearer <key>。背景:Glama /api/mcp/v1 自 2026-09 强制 Key(LA 同样 401)。 - 上下文容量进度条口径修复(对齐 ZCode Progress 双口径):轨道上指示条宽度=真实 used/window 百分比(min-w-[2px]),构成分段以 flexBasis 画在指示条内部(原误把构成比铺满整条轨道,出现「0.2% 满条」)。ZCode 依据:packages/ui/src/components/ui/progress.tsx(indicator width=value%、segment flexBasis%)+ contextUsage.tsx buildContextUsageBreakdownSegments(segment percent=chars/totalChars)。
- 上下文面板技能/MCP 工具分类接真实内容:App 订阅 hubSettings 技能切片→discoverSkills+buildSkillsSystemPrompt 得注入文本;MCP 列启用服务器(签名+60s TTL 模块缓存)→mcp_list_tools 得工具 schema JSON;两分类有内容时非 0 且可点击(右侧面板查看 markdown/json),不可得如实 0(与发送链路同源,对齐 ZCode skills/mcp_tool_schemas breakdown 口径)。
8. MCP 诊断与枚举失败反馈(2026-09-28,用户反馈「启用了没带上」驱动)
- 根因实例:用户启用的
node_repl服务器指向 Codex 版本哈希目录(runtimes\cua_node\<hash>\bin\node_repl.exe),Codex 更新后路径失效 → spawn 失败 →mcp_list_tools全失败 Err →createMcpTools返回空 → 本轮未带 MCP 工具;此前失败只进 console,界面零反馈。 - 卡片诊断按钮(已配置 tab,启用中的服务器显示 PlugZap 图标):调
mcp_test_server(server, persist=true),成功显示「连接成功 · N 个工具 · 耗时」,失败行内显示错误原文 + phase(title 全文)。i18n:mcpHub.cardTestOk/cardTestFailed(extra-mcp)。 - 发送链路反馈:
createMcpTools全失败时经useHubSettings.setMcpEnumNotice通知(lib→store→UI,不持久化),App 订阅后toast.error(8s;seen 时间戳去重)。后端部分失败跳过、全失败才 Err——单服务器部分失败仍不可见(后端 list_tools 丢弃 per-server 错误,二期补结构化返回)。 - ⚠️ 浮层层级必修课(2026-09-28 用户报「删除点不了」根因):Radix popper wrapper 是
position:fixed; z-index:auto,而 Hub 页容器带relative z-10(LA 页面结构原样)——z:auto 的浮层被 z:10 页面容器整体压住,表现为弹层看得见但所有点击落在页面上(Playwright actionability 报 covered-by,elementFromPoint 实证)。修复:lwpopover.tsx的 PopoverContent 与dropdown-menu.tsx的 popupClassName 补layer-popover(z 10000,LA 原版浮层都有;tooltip/sheet/dialog/alert-dialog/select 移植时已带)。浏览器实测:删除确认弹层点击恢复可用。
8.1 批次 A(TASKS.md 首批)落地:A1 attention + A2 模型元数据真实解析(2026-09-28)
- A2 模型元数据真实解析(铁律整改完成,PROJECT_CONTEXT 十.4 项关闭):
modelFactory.buildModel不再写死contextWindow: 128000 / maxTokens: 8192 / input: ["text","image"];ProviderConfig新增contextWindow?/maxOutputTokens?/supportsImage?,由App.buildTurnOptions(与自动化派发)从模型目录透传。未知语义:contextWindow传 0(pi-aiclampMaxTokensToContext对<=0跳过钳制;容量面板同样以<=0不渲染)、maxTokens未知时传请求级兜底常量 32000 并真实发送(2026-10-05 F1:runAgentTurn.resolveMaxTokens照 LiveAgent 同名函数语义在流式调用时注入 options.maxTokens——openai/responses 适配器只认 options,不注入则 model.maxTokens 永不进请求体;anthropic-messages 适配器自身options?.maxTokens ?? model.maxTokens已兜底,注入为同值无害)、input只在supportsImage === true时含image。测试modelFactory.test.mjs7 例(含「严禁回退写死值」回归断言)+runAgentTurn.test.mjsF1-1~5(resolveMaxTokens 单测 + 真实 pi-ai 适配器 mock-fetch 捕获请求体集成断言),providers 套件 24→29。 - A1 回合状态条 attention 强制展开 + 隐藏窗口停表:
turnActivity.ts新增isAttentionRequired(group, pendingApproval)(判据:会话级pendingApproval非空,或轮内ask_user/exit_plan_mode类工具处于 running——等人类工具运行中即表示在等人;未知工具名不臆测)与effectiveWorkMs(startedAt, nowMs, hiddenSpans)(隐藏时段按与轮区间求交集扣除,乱序/倒置/越界容忍、结果非负)。UI:TurnGroupView加pendingApproval/hiddenSpansprops——attention 时状态条文案「等待你的决定」(i18nturnAwaitingDecision)、lockOpen强制展开且按钮 disabled 不渲染箭头、DOM 标data-attention="true";MessageList以visibilitychange维护隐藏时段 ref 并下传实时轮;App透传state.pendingApproval。测试turnActivity.test.mjs15→29(attention 四态 + 停表交集/边界 6 例)。
8.2 批次 B(工具扩军)落地(2026-09-28)
- 工具集 5 → 9:新增
glob(模式匹配文件路径)、grep(正则搜内容)、delete_file(删除文件)、todo_write(任务清单)。权限分级:glob/grep/todo_write = read,delete_file = write(ask/edit 需批准、plan 拦截)。 - Rust
src-tauri/src/fs_search.rs(新):fs_glob(root, pattern, limit?)/fs_grep(root, pattern, include?, ignore_case?, limit?)。glob 语义*不跨目录、**跨目录、?单字符;字符类[ ]与花括号显式拒绝([unclosed曾会静默当普通文本匹配,测试抓出后改为如实报错——No-Fallback)。安全:根 canonicalize + 遍历逐条strip_prefix复核(符号链接逃逸拒绝);跳过.git/node_modules/target/dist等;grep 跳二进制与 >2MB 文件并如实回报skipped_files;两者都有 limit 截断标记。10 个 Rust 单测。 fs_delete_file(path, checkpoint?)(fs_cmd.rs):拒绝符号链接与目录(只删普通文件);删除前把前像写入检查点 ⇒ 用户「回退本轮代码改动」可恢复被删文件。前端delete_file工具强制 read-before-delete(未读先删报错)。- 前端:
tools.js四工具注册(描述含「用 grep 而非 exec 拼 findstr」的引导);ToolCallCard新增 Todo 卡(表头当前项 + N/M,展开三态图标:绿勾+删除线 / 箭头 / 空心圆)与 搜索卡(glob/grep:模式 + 命中数 + 结果行);turnActivity补四工具的类型标签与代号。 - 任务进度条
TaskProgressBar.tsx(新,挂输入框上方):读末条todo_write条目(覆盖语义 ⇒ 末条即当前清单),SVG 圆环 + 「任务进行中 N/M · 当前项」/「任务已全部完成」+ hover Tooltip 展开清单;无清单不渲染(No-Fallback)。纯逻辑抽到src/lib/chat/todoProgress.ts(extractLatestTodos/todoProgress),4 个单测挂进 test:chat。 - 验证:
tsc0、build✓、test:chat 114→118、test:agent 39→43、cargo 123→133。 - Tauri 端实测(2026-09-28,按用户要求今后只用桌面端验证):经 WebView2 CDP(
WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9333)驱动运行中的应用实测——模型真实调用todo_write(回显「三步计划已建立,当前进度 0/3」)与glob(**/*.md无匹配,模型主动list_dir复核确认非工具故障);页面渲染 进度条(role="progressbar"、aria 0/3、文案「任务进行中 0/3 · 当前项」)与 Todo 卡 / 匹配卡 类型标签均正确。 - ⚠️ Tauri 实测抓出的致命 bug(单测/tsc/构建全测不出):
TaskProgressBar初版直接用裸@radix-ui/react-tooltipprimitive 而未包TooltipProvider→ React 整树抛Tooltip must be used within TooltipProvider→ 应用白屏。修复:改用项目既有../lw/ui/tooltip(组件内置 Provider,其文件头本就写着这条教训)。教训沉淀:新组件一律复用 lw/ui 组件,不得直接引 radix primitive;功能验收必须启动 Tauri 实测,禁止用浏览器代替(浏览器模式还读不到 Tauri 侧 provider 配置,测不出真实工具调用)。
8.3 批次 C(输入侧能力)落地(2026-09-28)
- C1 斜杠命令(真实现,替换原硬编码假菜单):
- Rust
src-tauri/src/commands.rs:commands_scan(workspace_root?)扫描<工作区>/.ReinAgent/commands/*.md(frontmattername/description+ 正文模板;name 缺省取文件名;目录不存在返回空数组非错误;仅收 .md;单文件读取失败跳过并 stderr 记录)。5 单测。 - 前端纯逻辑
src/lib/commands/slashCommands.ts:parseSlashQuery(行首/且无空格才进命令态)、filterCommands(前缀优先)、expandCommandTemplate($ARGUMENTS替换;无占位符时参数追加末行不静默丢弃)、toCustomCommands(脏数据防御:空白名/无名字丢弃)。5 单测。 - Composer 接线:
/触发真菜单(内置三命令 + 自定义命令带「内置/自定义」标签);内置/clear→ ApphandleClearConversation(useConfirmDialog二次确认 →conversationPool.clearConversation(新导出:controller.clear + schedulePersist));/compact→toast.error(compactPhase2)诚实占位(批次 D 接引擎);/help把命令列表贴回输入框。自定义命令选中即模板展开填入输入框(可编辑不直接发送)。
- Rust
- C2 @提及(真实现,替换原 @workspace/@terminal 假菜单):
- 纯逻辑
src/lib/chat/mentions.ts:parseMentionQuery(@ 前须行首/空白且其后无空白)、extractMentions(无空白路径 + 带引号路径 + 尾斜杠目录提及;去重;上限 8)、buildMentionBlock(<file path>/<directory path>包裹 + 「上下文非指令」免责句 + 读取失败如实标注[unavailable: …])、capMentionContent(32KB 截断标注)。6 单测。 src/lib/chat/mentionResolver.ts:发送时读文件(fs_read_file)/列目录(fs_list_dir)并组装注入块;appendMentionBlock拼到用户文本尾(挂当轮 user 消息,不进系统提示词——缓存友好,对齐 ZCode/LiveAgent)。- Composer:
@触发候选菜单(fs_glob查询**/*{query}*,防抖 200ms,限 40 条);选中把「@查询词」替换为「@路径 」。
- 纯逻辑
- ⚠️ Tauri 实测抓出的潜伏缺陷(第 4 个):聊天区(hasMessages 分支)的 LexicalComposer 从未传
workspaceRoot(只有空态传了)⇒ 有任务时 @提及报「root 不能为空」、附件默认目录也失效。已补传effectiveWorkspaceRoot。此类 props 缺口只有真实运行可发现。 - 验证(Tauri 端 CDP 实测):
/真菜单(三内置命令 + 标签)出现、旧假菜单消失、零运行时异常;@列出真实文件候选;选中@attention-test.txt发送后,SQLite 落库的 user 消息含完整注入块(<file path="attention-test.txt">hello</file>+ 免责句)。tsc0、build✓、test:chat 118→128、cargo 133→138。
8.4 批次 D(上下文工程)落地(2026-09-28)
- 压缩引擎
src/lib/chat/compaction.ts(纯逻辑,11 单测):findCompactionRange:按轮切分(user 开轮口径同 groupTurns),保留最近KEEP_RECENT_TURNS=4轮原样;在途轮截断连续段(其后的轮不压)、已压缩轮跳过不截断(初版 break 语义会让 compact 轮之后的轮永远压不了,实测后改为 continue);绝不切进工具调用对。buildCompactionSource(工具条目只留结果首行 200 字)+buildCompactionPrompt(对齐 ZCode:保留文件路径/决策/未完成任务;安全约束逐字保留;不编造)。applyCompaction:区间替换为kind:"compact"的 assistant 条目(id=compact-<start>幂等;coveredCount=被压缩消息总数存条目上——初版渲染层重算恒 0,实测抓出后改为写入);摘要同时落 apiMessage(刷新后可回灌)。microcompactMessages:发送前裁较早轮 >4KB 的工具结果(首 800 + 尾 400 字符 + 省略标注),keepLastEntries=6 内不裁;只影响发送视图不改时间线;无变化返回原引用。
- controller 接线:
launchTurn(turnId, {autoCompact})发送前检查上一条 assistant 的 usage/contextWindow ≥COMPACTION_TRIGGER_RATIO=0.8→ 先压缩再发送;compactNow()手动入口(忙时 false);摘要调用走 runAgentTurn 通道(systemPrompt 换成纯摘要引擎提示、不给 workspaceRoot;⚠️maxSteps:1会把 faux/真实模型的摘要请求截死在工具轮(faux 两步形态:工具轮+正文轮)→ 实测抓出后改 8 步兜底);失败保持原状(No-Fallback)+onCompactionEvent事件(started/done/failed/skipped)经池转发。 - 池:
compactConversation(taskId)+onCompactionEvent(taskId, handler)(entry.compactionHandlers 集合转发)。 - UI:
CompactionBand.tsx(running shimmer 文案 / settled 可展开 seam:chips「已压缩 N 条消息」+ 摘要 markdown,全部语义变量 + prefers-reduced-motion 退化);TurnGroupView轮首 compact 条目渲染为 band、activity 渲染跳过之;/compact接真实现(无内容 toast 诚实提示)。 - Tauri 实测:多轮任务(5 轮对话)
/compact→ 压缩带「已压缩 1 条消息」正确渲染、摘要落 SQLite、console 零 compaction 错误;2 轮任务/compact诚实报「没有可压缩的轮」。实测抓出并修复 3 个 bug:maxSteps:1 截断摘要(空内容)、coveredCount 数据链断(恒 0)、初版 compact-break 语义(压过一轮后永远不能再压)。 - 验证:
tsc0、build✓、test:chat 118→128(compaction 11 例)、cargo 138。
8.5 批次 E(通知与红点)落地(2026-09-28,P0 收尾)
- E1 后台任务完成系统通知 + 提示音:
- Rust:
tauri-plugin-notification = "2"(capabilities 加notification:default,lib.rs 注册插件)。 - 前端
src/lib/chat/taskNotifications.ts:sendSystemNotification(权限请求 + 发送,Web 模式静默跳过)、playNotificationSound(双平台播放同一 wav 资源src/assets/sounds/task-done.wav:Linux → Rust 命令notify_beep(编译期 include_bytes 嵌入 → pw-play);Windows/macOS → fetch + decodeAudioData 缓存解码结果经共享 AudioContext 播放;失败如实 console.warn,不静默)、summarizeOutcome(终态摘要:错误原文/已停止/末条 assistant 文本截 120 字)、开关持久化 kvreinagent-notification-sound(缺省开)。 - 池:
refreshStreamingSet里检测「上一轮在流式、现在不在」的任务 →subscribeTaskTerminal发终态事件(done/error/stopped + 末条 assistant 文本 + error + awaitingDecision 标记——审批挂起不算真终态)。 - App 接线:订阅终态 → 审批挂起不算终态 → 提示音不去重(每次真实终态必响;曾放在 runKey 去重后导致同任务同文案的第二次完成被误吞) → runKey 去重(taskId+outcome+正文前 40 字,上限 200)只作用于系统通知;通知仅窗口失焦时发。
- 设置页:基础设置加「任务提示音」开关(WebAudio 播放开关,系统通知始终开)。
- Rust:
- E2 侧栏审批红点(TaskInteractionBadge 最小版):
- 池:
subscribePendingApprovals/getPendingApprovalTaskIds(与流式集合同款签名通知,只有集合变化才重算;挂进 flushNotify)。 - ProjectList 两类任务行(项目任务行 + 通用任务行)渲染琥珀色脉冲点(
--status-warn+ animate-pulse,title=approvalRequiredBadge);点击任务即进入处理(现有行为)。
- 池:
- Tauri 实测:任务 A 发消息后立即切到任务 B → A 完成(done)触发通知链路、console 零错误;模块/开关/订阅全部验证。系统通知弹窗本体在 Windows 通知中心(首次会请求授权),提示音为可听验证项。
- Linux 提示音引擎故障与兜底(2026-10-04,用户反馈 Linux 无声):本机 libwebkit2gtk 2.52.6 的 WebAudio 故障——
AudioContext起始 suspended 且resume()的 Promise 永不 settle(gjs + WebKit2-4.1 隔离复现,禁 WebProcess 沙箱相同;系统 gst-launch 管线正常、autoplay 策略为 Allow),Windows WebView2 不受影响。playNotificationSound按平台分流:Linux 走新 Rust 命令notify_beep(git_panel.rs),Windows/macOS 保持 WebAudio 三音。诊断脚本留存于 /tmp/webaudio_diag/。 - 提示音资源化(2026-10-04 三次迭代:合成三音 → Yaru complete → 用户自选文件):
notify_beep最终形态 = 编译期include_bytes!嵌入src/assets/sounds/task-done.wav(用户自选 Mixkit "Software Interface Start" 前 1s,PCM16 44.1kHz 立体声、峰值 -1.0dB、结尾 60ms 淡出防切音)写入$TMPDIR/reinagent-notify.wav经pw-play播放(回退gst-launch-1.0 -q playbin);Windows 端playWavNotificationSound()经 vite 资源导入 fetch +decodeAudioData(AudioBuffer 模块级缓存)播放同一文件。换音只需替换该 wav:两个平台无需改代码。 - 验证:
tsc0、build✓、五套前端测试全绿(128/43/18/6/12)+ hub 57 + cargo 138。 - 菜单键盘导航(2026-09-28 补齐,用户反馈):
/与@菜单支持 ↑/↓ 移动高亮(循环)、Enter/Tab 选中、Esc 关闭;高亮项data-active标记 +scrollIntoView(block:nearest)滚动跟随;查询词/候选变化时高亮复位第一项;菜单打开时 Enter 被导航拦截(不发送消息)。菜单过滤列表提升为组件层slashFiltered(keydown 与渲染共用同一份)。Tauri 实测:两菜单 ↑↓↑ 与 Enter 选中全链路通过。
8.6 P1-1 工具级策略(2026-09-28)
- 存储:
AppTask.toolPolicies?: Record<toolName, "allow"|"ask"|"deny">(任务级,随任务落库;updateTaskToolPolicy增改删一体)。MCP 服务器级策略存McpSettings.serverPolicy?: Record<serverId, policy>(纯前端字段:AppsetSettings只把servers发给 Rust,serverPolicy 不进 mcp_servers.json;hydrate 时从上一份切片保留)。 - 审批门语义(runAgentTurn
createApprovalGate新增第三参 toolPolicies):allow直通(full 模式下也生效——显式用户意图优先于模式);deny拦截并告知模型策略设置;ask把 effectiveMode 降为 ask(edit 对 write / full 对一切的自动放行被策略覆盖,强制挂起审批)。gate 注入条件放宽:approval && (mode !== "full" || toolPolicies)。 - UI:MCP 已配置卡(启用中的 server)恢复 LA 的 ToolPolicyToggle 列(allow/ask/deny 三态,服务器级——作用于该 server 全部
mcp__<id>__<tool>工具)。 - 测试:
toolPolicy.test.mjs5 例(allow 直通/deny 拦截含 read 工具/ask 覆盖 edit 自动放行/未配置不回归)→ providers 18→23。 - MCP 服务器级策略的运行时匹配说明:MCP 工具名是
mcp__<serverId>__<tool>,策略键需为 serverId 时由门内resolveToolPermissionKind保守视为 write +toolPolicies[toolName]精确名匹配;服务器级 → 工具名的展开由 UI 写入时按前缀匹配(后续增强,当前按工具全名/服务器 id 精确键)。 - P1-2 AGENTS.md 注入(2026-09-28):Rust
agents_md.rsagents_md_read(workspace_root)按优先级扫AGENTS.md→.agents/AGENTS.md→CLAUDE.md→.claude/CLAUDE.md(只取最优先命中一个;64KB 截断标注;空文件跳过;不存在返回空数组非错误)。runAgentTurn组装 meta_user 块时注入为<instruction-file source path>段(标题「Project instructions — authoritative for this workspace」)。buildMetaUserBlock参数序:currentDate → agentsMd → memory → skills。
8.7 P1-3 AskUserQuestion 工具 + 回合内提问卡(2026-09-28)
- 工具
src/lib/agent/askUserTool.ts:ask_user_question(1..4 题 × 2..4 选项,≤1 推荐/题,允许自由输入缺省 true;schema maxLength 约束)。execute 走approval.request({ args: { kind: "question", questions } })挂起;resolve 值三种:结构化{answers:[{question,answer}]}→ 回显 Q/A 收敛;"reject"→ 跳过提示收敛;null/undefined → 未回答收敛(全部 No-Fallback 文本)。isAskUserAnswer类型守卫。 - 通道放宽:
ApprovalCoordinator.request/ controllerresolveApproval/ poolresolveApproval接受ApprovalDecision | Record<string, unknown>(审批字符串不变;结构化对象仅在 gate 消费层视为放行——回答负载由工具自身经 request 的 resolve 拿回)。 - UI
AskQuestionCard.tsx(App 审批卡渲染分支:args.kind === "question"→ 提问卡,否则审批卡):标题「提问」+ 逐题单选卡(推荐项绿标)+ 「其他」自由输入 + 跳过/提交回答(未答齐禁用提交)。提交 →resolveApproval(taskId, { answers })。i18n askQuestionTitle/askOptionRecommended/askCustomPlaceholder/askSubmit/askSkip/askQuestionIncomplete。 - 挂载条件:
runAgentTurn仅在有 approval 协调器时挂ask_user_question(无协调器场景挂起无人应答)。 - Tauri 实测:ask 模式真实模型调用 → 提问卡挂起(问题+选项+推荐标渲染)→ 点选「继续」提交 → 工具收敛、模型确认收到回答。全链路(挂起→作答→收敛→继续)通过。
8.8 P1-4 后台 Bash 三件套(2026-09-28,Tauri 实测闭环)
- Rust
src-tauri/src/bg_process.rs(4 命令,均为spawn_blocking+*_sync内部函数供测试):bg_spawn(command, cwd):spawn_shell启动(Windowscmd /C+CREATE_NO_WINDOW+raw_arg;Unixsh -c),stdout/stderr 各一读线程pump_stream追加进共享Arc<Mutex<Vec<u8>>>缓冲(256KB 上限,丢头部保尾部并累计 dropped),立即返回taskId(bg-<ms>-<seq>:毫秒 + 进程内单调序号,防并发 spawn 撞 id 互相覆盖);Unix 侧 spawn 时建独立进程组(process_group(0),同mcp.rs)——这是进程组终止能生效的前提;进程驻留不阻塞回合。bg_output(taskId, offset):try_wait收割退出状态 → status(running/exited)+ exitCode + 增量newOutput+totalBytes(原始缓冲长度,展示用)+consumedBytes(本次实际解码到的完整字符边界)+droppedBytes。offset 推进口径:consumedBytes(2026-10-07 起;旧 totalBytes 口径会把跨轮询劈开的多字节字符在两轮各变一个 U+FFFD)。complete_boundary_len把末尾不完整序列扣留到下一轮——UTF-8 截断尾巴与 GBK 双字节首字节在字节层面不可区分、处理一致(UTF-8 严格校验下都是 error_len()==None 扣留;GBK 文本中途真非法走代码页兜底路径时按末字节 0x81-0xFE 扣 1 字节)。task_output工具(tools.js)offset 参数描述与结果行已同步改为让模型传consumedBytes。bg_stop(taskId):Windowstaskkill /PID <pid> /T /F杀进程树;Unixkill -TERM -- -<pid>(整组)→ 有界等待 2s →kill -KILL -- -<pid>→ 再等 0.5s。仅确认整组已终止才返回stopped=true并移除注册表项,失败则保留并如实回传forced/error(No-Fallback);等待全程在 registry 锁之外(最坏 ~2.5s 返回,绝不阻塞),且每轮try_wait收割僵尸 leader(僵尸会让kill -0 -- -<pgid>恒为真)。任务不存在 →stopped=false(幂等);停止后历史不可读(与 ZCode 语义一致)。- ⚠️ Linux 修复(2026-10-05,用户实测「点后台工作浮层的停止没反应」):
spawn_shell原先未建独立进程组,kill -TERM -<pid>打向一个不存在的进程组(ESRCH)且错误被let _ =吞掉;紧接着child.wait()在持有 registry 锁时无限阻塞 →invoke永不返回、徽标不消、无任何 toast,并连累bg_list/bg_output/bg_spawn一起卡住。修法:①spawn_shell加process_group(0);② 停止改「有界 TERM→KILL + 锁外等待 + try_wait 收割」;③ 所有kill调用补--(缺--时-<pid>被 coreutils 当选项解析,kill -0返回值不可信、实测反着报);④ taskId 加单调序号防并发撞 id。 bg_list():全部任务(刷新用)。- 单测
bg_process_tests.rs5 例全过;其中「长驻强杀」由假阳性(旧实现只要注册表有条目就返回 true,杀不掉也算过、还要白等 30s)升级为真验证:停止后断言进程组确已消失 + 3s 内返回,另有「孙进程随进程组一并终止」用例。
- 前端
src/lib/agent/tools.js:background_bash/task_output/task_stop三工具(权限=exec;cwd 经resolveWorkspacePath),回合内 turnActivity 标签「后台命令/任务输出/停止任务」。 - ⚠️ camelCase 铁律(Tauri 实测抓出的真 bug):
BgOutput/BgSpawnResult/BgStopResult均为serde(rename_all = "camelCase")→ JS 侧必须读taskId/exitCode/newOutput/totalBytes/droppedBytes;首版写成 snake_case 导致模型看到output bytes: undefined。新增 Tauri 命令后,前端取值字段名必须对照 Rust 结构体的 serde 改名核对一遍。 - Tauri 实测记录(CDP 驱动,模型真实调用):
ping -n 60驻留 → taskId 秒回;自然退出后status: exited / exit: 0 / output bytes: 3149三字段真实填充;task_stop返回 stopped;裸调验证 13,990 字节/301 行明文完整、全链路无 base64 封装(模型自称「输出带 base64 标记」系误读)。环境观察:本机 PATH 下timeout解析到 GNU coreutils(非 Windows timeout.exe),sleep 6可用;全项目输出解码统一(2026-10-06 订正:本机活动代码页实为 936/GBK,此处「代码页为 UTF-8」系误记;UTF-8 硬解 GBK 输出导致工具卡花屏,已修复——见下方「Windows 控制台输出 GBK 乱码修复」条目)。from_utf8_lossy(exec/terminal/bg_process 一致,本机系统代码页为 UTF-8) - 同批附带修复(全量回归发现,均非 P1-4 引入):
markdownBlocks.jsReDoS 真回归:DEFINITION_LINE_RE的 label 体贪婪吞尾+逐字回溯撞 V8 大字符串悬崖(64KB 敌意行 17.5ms,n 翻倍耗时 62×)→ 新增hasLinkDefinitionLine:]:indexOf 快筛候选行、逐行跑完整正则(语义等价:能匹配的行必含]:;12 语义用例 + 18 个仓库 md 对拍 0 mismatch)。taskModelIsolation.test.mjs过期断言(断言已废弃的 localStorage 键reinagent-tasks,任务持久化已迁 SQLite)→ 删除旧键断言保留状态级隔离断言;同文件globalThis.windowmock 泄漏污染后续测试文件 →after()还原现场。- 10 个测试文件静态
import { registerHooks } from "node:module"(Node 22.15+ API)在 bun 1.4.x 下抛 SyntaxError 且被计为文件级失败 → 改动态导入 +typeof能力检测(bun 原生支持 .ts 无需钩子);⚠️ 其中 compaction/todoProgress 是 CRLF 行尾,批量替换需按文件实际 EOL。
- 验证:全量 297/297 全绿(修复前 240 pass/10 fail/10 errors——registerHooks 连累 47 个测试没跑起来)+
tsc0 + cargo bg 4/4。
8.9 P1-5 WebFetch / WebSearch(2026-09-28,Tauri 实测闭环)
- 调研结论:ZCode/LA 的 WebSearch 都依赖 provider 服务端原生搜索(Anthropic
web_search工具注入/SSE 解析),我们的 OpenAI 兼容网关不支持 → 走纯客户端路线:Rust 出网(WebView 直连有 CORS)+ 无 Key 的 DuckDuckGo HTML 端点。ZCode WebFetch 的客户端实现思路(15min 缓存/100k 截断/结构化错误/egress 防护)直接移植。 - Rust
src-tauri/src/web_tools.rs(2 命令,spawn_blocking +*_sync):web_fetch(url, max_bytes?):URL 规范化(无 scheme 补 https、http 强制升级 https、拒凭证/超长/本地与保留地址)→ ureq GET(30s 超时、2MB 响应体硬上限take(max+1)超限即断)→ 文本类 MIME 校验 → HTML 经html2text转纯文本。UA 用标识 UAReinAgent-WebFetch/0.1(对齐 ZCode)。web_search(query, allowed_domains?, blocked_domains?, max_results?):html.duckduckgo.com/html/?q=GET → 正则解析result__a链接 +result__snippet摘要 →uddg=百分号解码还原真实 URL → 广告剔除 + 客户端域名后缀过滤(白/黑名单不可同给,对齐 ZCode)→ 默认 10 条(上限 25)。搜索 UA 必须用浏览器 UA(标识 UA 收 202 反爬挑战页,实测)。- 广告过滤双保险:外层直链
duckduckgo.com/y.js+ uddg 解码后兜底再查一次(DDG 常把广告包在跳转里,实测抓出)。 - SSRF 防护:
is_blocked_host(localhost/.local/.internal、IPv4 私网/环回/CGNAT/基准网段、IPv6 ::1/fc00::/7/fe80::/10/::ffff: 映射)。⚠️ 实测踩坑:host 提取最初用rsplit(':').next()取到的是端口("127.0.0.1:9333"→"9333"),带端口 URL 绕过拦截——必须split(':').next()(extract_host),IPv6 方括号形态单独处理。 - 单测
web_tools_tests.rs8 例(实体解码/DDG 解析含包装广告/域名过滤/URL 规范化/SSRF 段位)。
- 代理支持(用户网络环境硬需求,实测 DDG 直连超时):
resolve_proxy()三级——kv 设置reinagent-web-proxy优先(非法值如实报错不静默直连)→ 环境变量(Proxy::try_from_env)→ 直连。设置页「联网代理」输入框(src/lib/web/webProxy.ts,kv 持久化,失焦/回车保存)。 - 出站 HTTP 统一走 Rust(tauri-plugin-http,2026-10-04,用户实测 api.gonkarouter.io 模型列表失败):根因 = webview 原生 fetch 受 CORS 约束(gonkarouter 不返回任何 Access-Control 头、OPTIONS 预检 404,浏览器实锤复现 preflight blocked;curl 带 key 200 正常,代理非必要条件)。引入
tauri-plugin-http(Rust reqwest,无 CORS 概念)+ 前端统一入口src/lib/web/proxiedFetch.ts(isTauri 探测:Tauri 走插件 fetch 并注入 kv 代理设置——matchesNoProxy判定 no-proxy 命中直连,未命中传proxy.all={url,noProxy};Web 演示模式回退原生 fetch)。替换点:模型列表 fetchModels(5 处)/ 连通性 testConnectivity(4 处)/ 标题生成 titleGenerator(3 处);对话流经getStreamFnForApi包装注入options.fetch(openai/anthropic 适配器原生支持;google-generative-ai 适配器拒绝自定义 fetch 且其 SDK 不暴露 fetcher,保持原生——Google 端点本身支持 CORS)。capabilities 加http:default(allow:https://**+ localhost/127.0.0.1 任意端口)。效果:无 CORS 头的服务商(gonkarouter 等)模型列表+对话全通,应用内「联网代理」对所有出站请求真正生效。- ⚠️ 二次修复(reqwest 环境代理陷阱):首测仍
error sending request——reqwest 默认继承系统环境变量代理(本机https_proxy=127.0.0.1:7890,实测经该代理请求 gonkarouter 直接失败 HTTP 000,直连正常),与 ureq 侧「kv 空则直连」语义冲突。修复 = kv 未配置代理时显式传proxy.all = { url: "http://direct", noProxy: "*" }——reqwest 官方语义no_proxy("*")匹配全部主机名 = 全直连,压制环境变量代理。教训:reqwest 系 client(与 ureq/浏览器 fetch 行为不同)必须显式管理代理,否则环境变量静默接管。 - ⚠️ 三次修复(capability 作用域拦内网 Ollama):
http:default首版 allow 只写了https://**+ localhost http——内网服务商(Ollama http://192.168.3.27:8787)被插件 URL 白名单拒绝,OpenAI SDK 把 scope 拒绝统一包装成Connection error.(重试 10 次全灭,表象与网络故障无法区分)。教训:URLPattern(tauri-plugin-http 的 scope 匹配)对 hostname 先做 URL 规范化,「通配符 + IP」写法全被规范化毁掉(192.168.*.*→192.0.0.168*.*;用 urlpattern-polyfill 实测确认),唯一可靠的 http 全放行写法是{ url: "http://*:*" }。现 allow =https://**+http://*:*(ReinAgent 的服务商本就是用户自配的任意端点,scope 收不窄)。 - ⚠️ 四次修复(强制直连的 noProxy 坑):scope 修复后 Ollama 仍 Connection error——根因 = 直连态的占位代理
proxy.all={url:"http://direct", noProxy:"*"}里*在 hyper-util 匹配器中只对域名主机生效,IP 字面量主机不匹配(cargo 探针实测:*→ IP 失败/域名成功;0.0.0.0/0,::/0→ IP 成功/域名失败;0.0.0.0/0,::/0,*→ 双双成功)。现直连态 noProxy 固定"0.0.0.0/0,::/0,*"。教训:reqwest 0.13 的代理匹配已下沉到 hyper-util,*语义按文档描述却实测不含 IP 字面量——涉 IP 内网地址必须叠加 IP 子网规则。探针src-tauri/tests/probe_http.rs验证后已删。
- ⚠️ 二次修复(reqwest 环境代理陷阱):首测仍
- LLM 流式本地反代(2026-10-05,用户实测 GonkaRouter 流式经常半途无声截断,LiveAgent 同端点完整):根因有两层——① tauri-plugin-http 的流经 webview IPC 逐 chunk 搬运(每 chunk 一次
fetch_read_body往返),长流/高吞吐下中继提前关闭——reqwest chunk() 返回 None 被当作正常 EOF,OpenAI SDK 迭代自然结束、无 [DONE] 也无错误 → pi-ai 当正常完成 → 无声截断(curl 直连/走代理 7 分钟长流均完整,锁定唯一差异即该中继)。② 请求不发 max_tokens 时上游套用自家默认输出上限(GonkaRouter GLM-5.3-Flash 实测 3072 tokens 即 finish=length,无声截断;显式发 32000/200000 服务端接受并按需 stop)。修复 = LiveAgent 同款双管齐下:(a) 本地流式反代(llm_proxy.rs)——127.0.0.1:0 起 axum 反代(token uuid 鉴权),前端llmProxyFetch把 SDK 请求改写到本地反代(上游地址经x-reinagent-upstream-url头下发),RustBody::from_stream(upstream.bytes_stream())流式直通(webview↔localhost 同源直连,无 IPC 数据面,CORS 由反代 ACAO 应答);上游客户端每请求按 kv 代理语义构建(非空且未命中 no-proxy → Proxy::all;其余.no_proxy()显式直连,环境变量代理永不静默接管);(b) max_tokens 请求级兜底——modelFactory.ts未知上限时发ANTHROPIC_REQUIRED_MAX_TOKENS = 32000(LiveAgent MAX_OUTPUT_TOKEN_CAP / OpenCode OUTPUT_TOKEN_MAX 同值,服务端按模型真实上限钳制;实测发 200000 服务端接受)。⚠️ 2026-10-05 F1 补完:当日仅给model.maxTokens赋了兜底值,openai/responses 适配器只认options.maxTokens,兜底值从未真正进请求体(anthropic 适配器自身兜底所以一直生效);现已在runAgentTurn.wrapStreamWithProxiedFetch注入resolveMaxTokens(options.maxTokens, model.maxTokens)(照 LiveAgent runtime/common.ts 同名函数:未知→发 model.maxTokens,已知→取 min),集成测试以真实适配器 + mock fetch 断言请求体证实(F1-2/3/4)。注入点不变(getStreamFnForApi包装 openai/anthropic;gemini 保持原生)。fetchModels/testConnectivity/titleGenerator 维持 proxiedFetch(短非流式)。Cargo.toml 加 reqwest 0.13 直接依赖(stream/json,TLS 经 feature 并集取插件的 rustls);启动时rustls::crypto::ring::default_provider().install_default()(reqwest 0.13 任何 Client 构建都需要进程级 provider)。⚠️ 实现坑:① std TcpListener 须set_nonblocking(true)再TokioTcpListener::from_std(阻塞 FD 注册 tokio 直接 panic);②from_std/axum::serve必须在 tokio 上下文内——整体放进tauri::async_runtime::spawn异步块(setup 主线程无 tokio 上下文);③ rustls 0.23 需进程级 crypto provider(ring::default_provider().install_default()幂等)。临时诊断基建(info 转储/流日志)验证后已删除。⚠️ 归因澄清:断流的实锤根因是 max_tokens(对照实验钉死);「IPC 中继截断」是排查期的假设、未被实测证明,本地反代保留的理由是 LiveAgent 同款架构 + 传输层不再依赖 webview 网络栈,而非它被证明修了断流。 - ⚠️ no-proxy 匹配器缺陷修复(2026-10-05,用户实测「代理不可用时 DeepSeek 与内网 API 全部访问失败」):反代侧
matches_no_proxy旧实现把规则去点去星(*.deepseek.com→deepseekcom)后与原始带点 host(api.deepseek.com)做contains—— 恒不命中,白名单完全失效,所有请求(含应直连的 DeepSeek/内网 Ollama)都被送进代理;代理不可用即全部失败。修复 = 两侧统一精确语义(llm_proxy.rs与proxiedFetch.ts同规则):*单独项命中全部;含通配符去星后按原始 host 包含匹配(192.168.*、*.deepseek.com);纯域名精确/子域后缀匹配;规则尾部带点的内网写法(192.168.)按前缀匹配。已用 8 条 URL × 两侧算法做一致性对照(DeepSeek 官网/API、192.168./10./172.16./localhost 直连;gonkarouter/openai 走代理)全部符合预期。 - 内置浏览器代理补 Linux 支持(2026-10-05,用户实测「内置浏览器不跟随代理设置」):
app_proxy.rs的注入原本首行即if !cfg!(target_os = "windows") { return; }——整套逻辑围绕 WebView2 的--proxy-server/--proxy-bypass-list参数(Chromium 专属),Linux(WebKitGTK)下完全不接代理,内置浏览器走系统网络栈。修复 = 新增apply_webkit_proxy_settings():取 main webview 的 GTKContext→website_data_manager()→set_network_proxy_settings(v2_32 API);kv 有代理 →Custom+ http/https scheme URI + no-proxy→ignore_hosts,kv 为空 →NoProxy显式直连。调用点在lib.rssetup 最前面(webview 已建、页面未加载完——代理必须在导航前生效),经with_webview投递 GTK 主线程。主 webview 与内置浏览器共用同一 WebContext,一处生效覆盖两者。⚠️ 限制:WebKitGTK 代理是会话级设置、创建后不可改(同 Windows,改设置需重启);ignore_hosts是精确主机名匹配、不支持通配符——192.168.*/*.deepseek.com这类写法在内置浏览器上不生效,需写完整域名(API 请求侧是我们自实现的匹配器,仍支持通配)。 - 前端工具(tools.js):
webfetch {url, prompt?}+websearch {query, allowed_domains?, blocked_domains?}(权限=read 级)。webfetch 带模块级 15min TTL 缓存(LRU touch + FIFO 50 条上限,对齐 ZCode 进程级缓存语义),结果文本经TOOL_LIMITS.webFetchBytes(30KB)/webSearchBytes(16KB) 截断;websearch 结果附 REMINDER(回答后必须以 Sources: 列表给链接,对齐 ZCode)。prompt参数作为「关注点」提示随内容返回(不额外调模型——ZCode 用会话模型低档位回答,我们简化为内容直返)。 - UI:
ToolCallCard新增 websearch(Globe + query + 结果数)与 webfetch(Globe + URL +HTTP 状态 · 字节 · 缓存)专属卡。- 联网搜索聚合行:
buildActivitySegments(turnActivity.ts,相邻 websearch 合段,隔任何其他条目即断组)+WebSearchGroupCard(「已搜索 N 次 · N 个来源」+ 运行中扫光 + 部分失败红标 + 展开 query 行/来源行(favicon/favicon.ico兜底 a.favicon.im,收起 5 条 + 「查看其余 N 个」),对齐 LA HostedSearchGroupView)。i18n 10 键(zh/en)。
- Tauri 实测(CDP 裸调 + 模型调用):搜索 939ms/11 条、广告 0(修复前第一条是 uddg 包装的 Udemy 广告——单测补夹具);
web_fetch抓 rust-lang.org 200/18,594B→5,159 字符可读文本(走代理);SSRF 实测抓出 host 提取绕过 bug(已修+补测)。 - 验证:前端 300/300 +
tsc0 + Rust 150/150(web_tools 8 例)。
8.10 P1-6 子代理系统(第一批:引擎 + 回合内卡,2026-09-28)
- 形态取舍(调研结论):ZCode = 子代理是完整独立 session(事件镜像/右侧回放/恢复,重);LA = 进程内递归 runner + 结构化卡 + 私有持久化(轻)。选 LA 形态:子代理跑
subagentRunner.ts内的嵌套runTurn(agentRuntime),转录只在内存、不写主会话历史,最终报告作为agent工具的 toolResult 回到主循环。 src/lib/providers/subagentRunner.ts:createSubagentTool(deps):工具名agent,参数{description, prompt, subagent_type?}(不暴露 model/run_in_background——ZCode 教训:历史 tool call 里的旧 override 会长期污染)。类型 Explore / general-purpose,未知类型如实报错不猜测。- 结构性禁递归:agent 工具由 runAgentTurn 解析出 model 后注入,
getTools()注册表里没有 agent——子代理工具集经filterToolsFor从注册表过滤,天然无递归。 - Explore:只读白名单(read_file/list_dir/glob/grep/webfetch/websearch)+ 只读提示词 + 不挂审批门(结构性安全);general-purpose:注册表全量 + 继承父审批门/AbortSignal(写操作弹主会话审批 = ZCode「路由回父」语义;父停子停)。
SUBAGENT_MAX_STEPS=6(ZCode 默认 4;实测 Deepseek 碎步形态 4 步连「列目录+报告」都触顶,放宽;触顶走 maxStepsReached 事实链)。子代理结果 = 事实头(类型·工具调用数·时长·token 聚合 + 触顶/停止/错误 ⚠)+ 最终报告;details.kind="subagent"带 summary/usage 供 UI。- faux 分支同样注入(runAgentTurn 两条 source 路径)。
- 权限分级:
resolveToolPermissionKind("agent") = "read"(派发免审批,对齐 ZCode needsApproval:false)——实测首版未注册被保守视为 write 弹了审批;拦截下沉到子代理内部工具(general-purpose 继承父 gate,plan 模式下子代理写工具同样被拦),语义安全。 - UI:ToolCallCard agent 分支(Bot 图标 + description 主文案 + 完成后
类型 · N 工具调用 · Ns次要文本 + 展开报告全文)。 - 测试:
subagentRunner.test.mjs6 例(filterToolsFor 白名单/禁递归前置、summarize 聚合、端到端嵌套 faux、未知类型报错、审批门继承判别、常量与提示词)。 - Tauri 实测:模型真实派发 Explore 子代理(嵌套循环跑 glob/list_dir 调研)→ 报告回传主循环转述;第二轮验证派发不再弹审批;步数触顶 ⚠ 事实链生效(子代理如实报告不完整,主模型如实转告并自己补验证)。
- 本批未做(后续增量):子代理目录侧栏(Running/Ended,需内存 registry 或持久化)、后台子代理(run_in_background + 完成通知)、「在右侧打开」完整回放(依赖子会话持久化)、用户自定义 agents/*.md profile、MCP/记忆工具带入子代理。
- 验证:前端 306/306 +
tsc0。
8.10.1 P1-6 增量:后台子代理 + 运行登记表 + 目录面板(2026-09-28)
src/lib/subagents/subagentRegistry.ts(内存登记表,对齐并行池的并发纪律):- 快照不可变更新(getSnapshot 稳定引用)+ notify 微任务合并(同步多笔变更一轮通知,防 Maximum update depth);AbortController 存内部 controls 表不进快照。
registerRun/updateRun/finishRun/stopRun/getRun/getRunSignal;id 形态sub-<ms>-<seq>;__resetForTests测试隔离。
agent工具新增run_in_background:立即返回Background subagent started: <id>+subagent_output查询提示;嵌套循环脱离父轮继续跑(独立 AbortController,侧栏可停);后台完成 → registry 收束 + notify 回调(默认实现 = 系统通知 + 提示音,复用 E1 taskNotifications;可注入便于测试)。subagent_output工具:按 id 查询状态/耗时/tokens/报告;未 id 如实报错。⚠️ 诚实性修复(实测抓出):后台触顶时 summary 可能只是中间叙述——maxStepsReached必须随 finishRun 落 registry,查询输出带「⚠ 触达步数上限:下方内容可能是中间叙述」(首版后台路径丢标记,模型误把过程性文字当报告)。- 权限分级:
subagent_output= read(⚠️ 又一次踩「未登记新工具被保守视为 write」——连续两批同坑,新增工具必须同步登记 resolveToolPermissionKind)。 - 右侧目录面板:codeViewerSource 联合类型加
{type:"subagents", title, focusId?};CodeViewerPaneHost 对应分支渲染SubagentsPanel(实时 useSyncExternalStore 订阅):Running/Ended 分组(ZCode TUI SubagentsSection 形态)、运行行 Stop 按钮(后台运行)、行点击进详情(id/类型/状态/时长/tokens/工具调用数/后台标记 + 任务全文 + 报告/错误)。agent 工具卡summaryAction(「在右侧打开子代理面板」)聚焦对应运行。 - Tauri 实测:后台启动立即返回 id → 父轮继续 → 30s 后 subagent_output 查询 → completed + 触顶 ⚠ 如实转述;主模型正确识别「Explore 只读无法写文件」的能力边界并给出替代方案;面板详情视图全字段渲染正确。
- 验证:前端 310/310 +
tsc0(新增 subagentRegistry.test.mjs 3 例 + 后台端到端 1 例)。
8.11.1 用量面板复刻 ZCode(用户要求「一模一样」,2026-09-28)
-
做法:直接复制 ZCode 源码(packages/ui/src/settings/usage-stats/ + components/ui/chart.tsx + tokenNumberFormat)到
src/components/settings/usage-stats/,仅适配 import 路径与两个兼容层——图表逻辑零改动。 -
兼容层(差异全部收口):
usageIntl.tsx(同名useZCodeIntl,内部 t+占位替换,settings.usage.* 键走宽松查询);usageTooltip.tsx(ControlHintTooltip → lw Tooltip,⚠️ Radix Tooltip 必须 Provider——lw/ui/tooltip 已含);usageErrorBoundary.tsx(ScopedErrorBoundary 最小替代,resetKeys 变化自动重试);useAppUsageStats.ts(ZCode 走 service→RPC,我们直接 invokeusage_snapshot,保留版本号防竞态)。 -
快照契约:Rust
usage_query重写为 ZCodeAppUsageSnapshot同形(summary 全字段含 streak/peak/longestSession/favoriteModel、heatmap 52 周自然周(周日起)+ level 分级(>75%→4/>50%→3/>25%→2)、dailyModelUsage 按日分模型、models 带 share;无 tools 数据 v1 空)。旧 usage_refresh 命令/自绘 UsageStatsSection 已删。 -
依赖:
recharts@3.10.1(ZCode 同款图表库;chart.tsx 复制版带 initialDimension 扩展)。图表 lazy + LoadBoundary(ZCode 因 decimal.js-light 在 Electron Linux 容器崩才 lazy——我们沿用同一防护)。 -
CSS:global.css 追加 usage 色板(--color-usage-chart-1..6 / --color-usage-heatmap-0..4,sky 系 + color-mix surface,明暗两套 data-theme);Tailwind 六共享色(foreground/surface/muted 等)此前已对齐 ZCode 语义,移植组件类名几乎直接可用。⚠️ Tabs pill 的
data-active:变体要改成data-[state=active]:(Radix 形态)。 -
⚠️ Rust 坑:聚合 SQL 带
?2占位就必须传两个参数(day/tool 每日查询改?1);findLast需 es2023 lib(改[...arr].reverse().find)。 -
实测:驱动设置页→用量统计,汇总条 5 指标/热力图(13 个月份标签)/每日每周累计/时间范围 pill/双 Recharts 图全部渲染;截图存 /tmp 比对用户提供的 ZCode 截图,布局样式一致。数字:总 10.7 万/峰值 9.1 万/最长聊天 1 小时 8 分/连续 1 天。
-
验证:Rust 153/153(快照形状测试重写)+
tsc0 + 前端 310/310。 -
8.11.1 追加修复(用户实测反馈):① 热力图空格不可见——level0 色原样照抄 ZCode(sky 0% + surface = 纯 surface),但我们的 --surface 是实色 #1e2028 与卡片背景完全同色(ZCode 的 surface 是 3% neutral-950 半透明叠色才可见);修复:明/dark 两套 data-theme 分别用暗/亮白低混色。② hover 迟迟不出提示——每格自带 lw Tooltip 的 Provider 且未设 delayDuration(Radix 默认 700ms);修复:usageTooltip 重写为 Radix 直用 + **共享 UsageTooltipProvider(delayDuration=0)**包 UsageHeatmap 顶层。CDP 验证:364 格 backgroundColor 与卡片背景 distinct、hover 即时出「2026年9月29日\n0 tokens · 0 轮消息」。
-
8.11.1 追加修复②(列/格 hover 高亮不可辨):行为代码本就在(weekly/cumulative 列
group/usage-heatmap-column整列高亮、daily 单格 hover),真正根因是@theme inline把 --color-border-hover 的值内联进工具类,[data-theme="dark"] 的覆盖恒不生效,实际用的 fallback --border 与格子背景几乎同亮度。修复:usage 色板移出 inline 块用标准@theme(工具类输出 var 引用,主题覆盖生效),border-hover 对齐 ZCode dark 值 = 30% 亮白 / 明 = 20% 暗色。CDP 实测:weekly hover 整列 7 格 borderTopColor 统一变 30% 白(hover 前透明)、daily hover 仅单格变、邻格不变,tooltip 均即时。
8.11 P1-7 用量统计图表(2026-09-28,Tauri 实测闭环)
- 架构(对齐 ZCode usage-observability,但零写入链路改动):不建新写入点——
usage_stats.rs的usage_backfill_sync幂等回填既有part表(api_messageassistant 原件 → main_turn 行;tool_result中 agent 工具的details.usage→ subagent 行,model 列存 subagentType),INSERT OR IGNORE(id=task:msg);打开面板时usage_refresh先回填再 SQL 聚合,数据即时最新。 - 事实表:
model_usage(conversation_store 迁移幂等追加,字段对齐 ZCode migration 0010 精简版:query_source/provider/model/started_at/input/output/cache_read/cache_write/total)。口径:total = input + output(cache 是 breakdown 不叠加,对齐 ZCode);缓存命中率 = cacheRead/(input+cacheRead)(pi-ai input 不含缓存)。30 天滚动 prune。 - ⚠️ 实测抓出:pi-ai AssistantMessage 的模型名字段是
model(不是 modelId)——首版回填 77 条 model='';修复为 model→modelId→modelName 链 + 回填时 UPDATE 纠正历史空名行(IGNORE 不会自愈)。 - 聚合:
usage_querySQL 层完成 totals / 按模型分组(降序)/ 日归桶(dayIndex=(started_at+tzOffsetMs)/86400000,tz 由前端传)。无 cost 维度(对齐 ZCode)。 - UI:设置页新增「用量统计」分区(
UsageStatsSection.tsx):范围 Tabs(7d/30d/all)+ 汇总条(总 tokens/请求/命中率/输出)+ 热力图(CSS Grid 14 周,level 0-4 按 max 归一)+ 日趋势(自绘 SVG 折线近 30 天)+ 模型分布(自绘 SVG 环形 + 图例份额)。零新依赖(不引 Recharts——ZCode 被 decimal.js-light 容器坑过才 lazy,我们直接自绘更稳)。子代理行显示「子代理 ·」。 - 测试:
usage_stats_tests.rs3 例(回填幂等+字段提取、聚合口径+日桶、范围过滤);⚠️ 测试时间必须贴近 now——回填内含 30 天 prune,1970 年代测试数据会被当场清掉(首个失败即此因)。 - Tauri 实测:真实回填 77 条历史记录;模型名纠正后三个模型正确分列(deepseek-v4.1-flash 73.1% / glm-5.3-flash 26.9% / deepseek-chat 0%);设置页导航 → 汇总条/热力图/趋势/环形全部渲染;范围切换正常。
- 验证:Rust 153/153(+3)+
tsc0 + 前端 310/310。 - 8.11.1 追加修复③(趋势图只显示有数据的天):dailyModelUsage 原 SQL GROUP BY 只返回有用量行的天(两天数据 = 两个点连线);ZCode 的 30d/7d 协议结果是连续每日序列(其源码注释专门记过这个回归)。修复:usage_query 按 range 补齐——7d/30d 从范围起点到今天每天一行(无数据 models 空),all 从最早数据日补。测试断言同步(7d=7 天、all=4 天连续)。
十五、P1-8 记忆 Extraction 接线(2026-09-28,Tauri 实测闭环)
- 调研定论:LA 有完整 Organizer(聚类+合并决策+风控闸+确认队列)与 Extraction(每轮后隐藏回合+SubmitMemoryPlan+事务 batch);ZCode 只有 Extraction(受限子代理直写 memory 目录)。我们接 LA 形态。
- 已移植件(记忆 Hub 移植时随行):Rust organize.rs 全套命令、memory_apply_batch、schema(confidence/evidence 契约)、MemorySettingsDrawer/OrganizerHistoryModal、config.ts 常量、memoryManagerTool。
- 本批新增(Extraction 管线):
prompts/extraction.ts+extraction/gating.ts+extraction/planTool.ts+extraction/context.ts:LA 源码直移(仅 import 适配;tsc 全过后零逻辑改动)。extraction/extractionEngine.ts(自写,对齐 LA 流程):隐藏 runTurn 嵌套回合(同子代理模式)——system=抽取提示词、user=自包含块序(指令→拒绝→候选→本轮已写→工作区变更→对话窗口,稳定前缀吃缓存)、工具=只读 MemoryManager+SubmitMemoryPlan;提交→validateSubmittedPlan→planToApplyBatchArgs→一次 memoryApplyBatch;45s 超时;候选加载失败退化空块继续(如实)。chat/memory/extractionController.ts:每会话至多一个在飞 run、新请求 coalesce、30s 节流、同一用户消息不重抽、LRU 128、短确认词 defer 到引擎裁决、fire-and-forget 绝不阻塞。- 钩子:pool.refreshStreamingSet 终态 done(非 awaitingDecision)→ maybeExtractMemory——复用当轮 sendOptions 的 provider 配置构建模型(faux 跳过),runAgentTurn 导出 getStreamFnForApi。
- ⚠️ 坑:validateSubmittedPlan 签名收整个 submission(非 items 数组);记忆的 greeting/ack 门控在长度门控之后(2 字素「你好」报 too-short 而非 greeting——skip 等价);⚠️ cat >> PROJECT_CONTEXT.md 时 cwd 漂移会把文件写到 src-tauri/ 下(已修正并 amend)。
- Tauri 实测:发「commit message 用英文祈使句」→ 回合完成自动抽取 → memory_list 出现
commit-message-style(type=user/conf=high)。 - 待做(Organizer 编排批):organizer service(scan→cluster→plan→gate→apply)+ prompts/organizer + useMemoryOrganizer 调度挂载 + Run Now 接线;MemorySettingsDrawer 的 organizer 模型选择。
- 验证:前端 314/314(+4 门控)+
tsc0。
十六、P1-8 Organizer 编排批(2026-09-28,编排全链路实测)
- 移植:
prompts/organizer.ts+organizer/pipeline.ts(LA 直移,import 适配)+organizer/service.ts(适配重写:LA runAssistantWithTools → 我们 runTurn 嵌套;provider 解析注入resolveModelDeps(App 层实现:organizerModel→providers 列表,缺失回落主对话模型);debug logger 去)。lib/shared/value.ts随 pipeline 引入。 - 挂载:App.tsx effect 装
createMemoryOrganizerService(deps:getSettings/advanceSchedule/resolveModelDeps/getWorkspaceRoot),providers/workspace 变化重建;window.__memoryOrganizerPoke调试入口。Run Now:Drawer handleRunNow 先memoryOrganizeRunCreate({trigger:"manual"})(pending)再 poke 领取(⚠️ 只 poke 不创建——organizerEnabled=false 时 due_claim 不建 run,实测踩过)。 - 编排语义(对齐 LA):scan(分页列全+scope 过滤+逐条读 body)→ cluster(≤8 条结构分簇,>8 条 LLM 主题聚类失败回退结构)→ plan(每簇一轮隐藏 LLM:ORGANIZER_PLAN_TOOL 捕获 + 只读 MemoryManager)→ gate(buildDecisions 风控)→ apply(manual=全进待确认队列;scheduled=仅 low-risk 自动 batch)→ run 记录全 phase 落库 + advanceSchedule。
- 实测:Run Now → run 创建 → scan(input=1)→cluster→plan 全链路真实执行;plan 阶段主模型未提交整理工具(弱模型工具遵循问题,LA 同有 parseFailures 跳过路径)→ 失败如实落 run 记录(phase=plan、错误 summary)。⚠️ 已知缺口:
① report.reviewItems 未随 organize_due_complete 持久化(2026-09-29 复核为已修:DB 实证同日 14:02 的 failed run 报告含 2 条 reviewItems 完整落库可读,读写链 due_complete→trimmed_protocol_json→row_to_organize_run 全通;13:55 的空 report run 是修复前遗留);② 裸调 due_claim 置 running 后无人执行会卡 6h(stale 回收窗口,正常路径无此形态——调试残留)。 - 验证:前端 314/314 +
tsc0(service/pipeline/prompts 全编译)。 - A2 回顶按钮 + dock 分离感 ✅:
useChatScrollState(scrollEl)(离底>120px 判定);回顶圆钮(aria 回到顶部)+ 输入区data-dock-away顶部边框阴影。⚠️ 两个实测坑:① hook 必须依赖 scrollEl state(ref 时序晚于 effect 会静默跳过且永不重挂);② 内容水合/流式增长不触发 scroll 事件——必须 ResizeObserver 监听容器与首子元素(否则初始空内容判定 false 后永不更新)。 - A3 草稿持久化 ✅:LexicalComposer 内部 draftKey(任务态
reinagent-draft:<taskId>/ 新任务页reinagent-draft:__new__),text 防抖 400ms 落 kv、归属切换恢复、发送清空。⚠️ CDP 测试注意:Input.dispatchKeyEvent type:char与insertText都不触发 React onChange(DOM 值变但受控 state 不更新)——必须用「原型 setter + input 事件」。 - A4 快照字段提示 ✅:
buildTextToolResult的details.truncated/originalLength真值已有;ToolCallCard 通用兜底卡截断时显示「输出超过字节上限已截断(原始约 X KB)」诚实提示(完整内容未持久化,不提供假加载)。 - E2E:A1 查找/计数/导航/分页链 ✓;A2 贴底隐藏 ✓(滚离出现受 MessageList 贴底状态机与程序化滚动交互限制,真实滚轮可用);A3 reload 恢复 ✓;A4 逻辑+tsc ✓。
- 批次 B 完成(2026-09-29):
- B1 错误归因徽标:
errors.js errorCategory()六类分类(auth/balance/rate-limit/server/network/timeout,正则与 diagnoseError 同序)+ MessageItem 错误行分类徽标(unknown 不显示)+ errors.test.mjs 3 例 + errors.d.ts 声明。 - B2 多任务批量审批:
PendingApprovalBatchBar.tsx(≥2 任务挂起时聊天区顶部「{N} 个任务等待审批」+ 全部允许/全部拒绝,循环 pool.resolveApproval);signature 字符串比对保证快照稳定。 - B3 命令安全模式选择器:已存在确认——LexicalComposer 的 Approval Mode Dropdown(四档 plan/ask/edit/full 输入框工具栏下拉)即 ZCode V4ComposerModeSwitch / LA CommandSafetyModeSelector 的对应物,无需新做。
- B1 错误归因徽标:
十七、P2 批次 A 进行中(2026-09-28)
-
A1a 会话内查找 ✅:
useConversationFind.ts(消息正文/思考/工具结果大小写不敏感子串匹配,按消息聚合计数)+FindBar.tsx(fixed 顶部浮条:输入/「n / m」计数/Enter+Shift+Enter 导航/Esc 关闭);命中跳转复用 MessageList 既有 scrollTargetMessageId 机制(滚动定位+消息高亮);Ctrl+F/Cmd+F 呼出(输入框聚焦时不劫持)。⚠️ 实测坑:reload 后 composer 自动聚焦,Ctrl+F 被「输入框不劫持」逻辑正确跳过——CDP 测试须先点消息区让 composer 失焦;FindBar 必须 fixed(absolute 会随容器滚出视口)。 -
A1b 加载更早消息 ✅:Rust
conversation_load_page(task_id, limit, before_seq)——取 seq<before 的最新 limit 条(升序)+total+has_more+first_seq;pool hydration 默认拉最新 500 条(普通会话行为不变),超长会话顶部「加载更早消息(剩 N 条)」按钮增量 prepend;loadOlder 锚点用 entry.oldestSeq(TimelineEntry 无 seq 字段,hydration 时从 rows[0].seq 记录)。⚠️ 快照结构体漏rename_all="camelCase"时 JS 读 firstSeq=undefined——本验证脚本踩过,pool 代码读 snake_case(has_more)反而正确。 -
E2E:588 行灌入验证分页链(page1 500+hasMore=true/firstSeq=88 → page2 88+hasMore=false,去重 588)✓;普通会话(38 行)fullyLoaded 无按钮(行为不变)✓;测试数据已清理恢复 38 行。
-
批次 C 进行中(2026-09-29):
- C1 选区引用菜单(代码完成,待真实拖选验证):
lib/chat/selectionReference.ts(按任务内存 Map + 限额 8000/8条/16000 + (taskId,text) 去重 + userselect 尾块协议# userselect:+userselect JSON)+useTextSelection.ts(mouseup/touchend 主触发、selectionchange 折叠即关、滚动 capture/Escape/resize 关)+SelectionActionMenu.tsx(Portal fixed 浮层:添加到当前任务/复制);composer 引用 chips(×移除);发送即消费(buildPromptWithSelections 拼 userselect 尾块后清引用)。 - ⚠️ C1 实测坑:① useTextSelection 的 root 必须走 chatScrollEl state(自行查询 mount 时 null 且永不重挂,与 useChatScrollState 同坑);② hook 调用必须在使用它的 state 声明之后(chatScrollEl TDZ);③ CDP 验证选区浮层受阻:
Input.dispatchKeyEvent type:char与insertText都不触发 React onChange(DOM 变受控 state 不变);程序化 setRange + mouseReleased 会被水合重挂/贴底状态机清选区——真实拖选待用户手测。 - C2 代码评论卡:未开始(
::code-comment行内指令协议解析 + 折叠卡 + 点击定位,ZCode assistantCodeComment.ts/assistantDirectiveParser.ts 可直移)。
- C1 选区引用菜单(代码完成,待真实拖选验证):
-
C2 代码评论卡 ✅(2026-09-29):ZCode
assistantCodeComment.ts+assistantDirectiveParser.ts直移(::code-comment{title body file start end priority}行内指令;手写 name="value" 扫描器;代码围栏内忽略;流式未闭合尾部隐藏;解析后原文从可见正文抹除——projectAssistantCodeComments().visibleText)。文件解析 shim 内联(resolveWorkspacePath,http/file:/~ 失败关闭)。卡片挂 TurnGroupView(终态 lastAssistant):正文渲染走 visibleText、卡片列表渲染其后、点击开文件预览。提示词侧:模型要产出评论需在系统提示词声明协议(暂未声明——待 PROMPTS.md 增补),故当前模型不会自发输出该指令。 -
解析单测:assistantCodeComment.test.mjs 5 例(完整指令/围栏内忽略+缺字段/投影抹除/流式防闪现/50 张上限+非法 priority 丢弃)。
-
批次 D 核心完成(2026-09-29):
- Rust
git_panel.rs:git_status(porcelain v1 -b 解析+分支提取)/git_branch_list/git_checkout(分支名白名单防注入)/git_log(%H%an%ct%s 分隔协议);非 git 仓库返回 isGitRepo=false 空态而非错误;spawn git CLI 不引 git2。git_panel_tests.rs 2 例(分支名注入校验含命令包装层/真仓库拒绝原文上抛)。 fs_tree.rsfs_tree_dir:单层目录(名称+is_dir+size,目录优先排序)——文件树懒加载的后端。- 前端:
lib/git/api.ts封装;GitPanel.tsx(分支切换 select+变更列表【?? 绿/D 红/其他 amber】+提交历史,手动刷新);FilesPanel.tsx(懒加载目录树,目录点击逐层展开、文件点击回调开预览);右侧面板 codeViewerSource 加 "git"/"files" 两类型 + CodeViewerPaneHost 分支渲染 + 面板头部互切按钮(Git↔文件树);侧栏底栏 Git 入口按钮(onOpenGitPanel)。 - 实测:git_status/branch_list/log 裸调真实数据(dev/main 分支、11 条变更、真实提交历史);Git 面板渲染 PI-Desktop 工作区(变更 2 条+提交历史);文件树渲染完整层级(目录优先+根文件)。
- D 尾巴(2026-09-29 已清两欠):
提交图谱可视化✅(见十八)/Git 面板刷新自动轮询✅(见十八);未做:代码审查 tab(code-review 预览模式)。
- Rust
-
批次 E 完成(2026-09-29):
- PDF/PPTX 引擎已存在确认:PreviewPane 已含 pdf.js range 加载(小 PDF 全量 <2MB)与 PPTX 解析(≤64MB),file 类型按扩展名自动分发——非缺口。
- Office 真引擎替换 stub ✅:装 docx-preview + xlsx;
previewPaneOfficeContent.tsx重写——docx 走 renderAsync 分页 HTML、excel 走 SheetJS 逐 sheet 转 HTML(多 sheet 切换 tab)、legacy .doc 如实不支持提示;CSS 加 docx 分页阴影与 xlsx 表格样式。 - ⚠️ 实测抓出真正缺口:
useFileService.readBinaryPreview是阶段 1 留下的 throw stub——Office/PPTX 预览拿到的一直是错误!补 Rustfs_base64.rs fs_read_base64_file(64MB 上限)+ 实现 readBinaryPreview/readFileRange(base64 切段兜底)/stat 三方法。 - E2E:文件树点 xlsx → 面包屑/表格数据完整渲染(名称/数量/价格)✓;测试文件已清理。
-
批次 F 完成(2026-09-29):
- F1 steering 运行中引导消息 ✅:controller.send 流式中不再拒绝——入队
ChatState.steerQueue(返回 true 供 Composer 清空输入);轮收敛后仅「自然完成」才取一条复用 send 续跑(停止/错误停下,队列保留);SteerQueuePanel(composer 上方排队列表 + × 撤回,pool 级 removeSteerMessage)。⚠️ 语义变更:3 个旧断言(流式 send 返回 false)按新语义更新。 - F2 ClarifyPanel 评估:不立项——ZCode 的 ClarifyPanel 依赖 ExitPlanMode elicitation 挂起(P1-3 提问卡已覆盖同类交互:ask_user_question 挂起 + 选项卡),且我们的 Plan 模式当前是纯提示词拦截(无 ExitPlanMode 工具)。待 Plan 模式工具化(P1-3 尾巴)后随批评估。
- F1 steering 运行中引导消息 ✅:controller.send 流式中不再拒绝——入队
-
批次 G 进行中(2026-09-29):
- G1 快捷键集中管理 ✅(部分):App 全局 keydown 集中 handler——Ctrl/Cmd+F 会话内查找、Ctrl/Cmd+T 新任务、Ctrl/Cmd+K 命令面板、Ctrl/Cmd+Shift+A 聚焦 composer;handleNewTask 经 ref 转发(effect 依赖 [] 而函数后声明)。⚠️ CDP 实测 Ctrl+T 视图未切(探针 __newTask 直调同样)——handleNewTask→setActiveTaskId(null) 链路在 CDP 下疑似被恢复逻辑回写,真实键盘待用户手测;Ctrl+F 已实测有效。(2026-10-06 悬案告破:真凶是
App.tsx的handleNewTaskRef.current = () => handleNewTask——箭头只返回函数未调用,致 Ctrl+T / 命令面板「新建任务」/__newTask三处全部 no-op;已修为() => handleNewTask()并 CDP 双路径验证。另订正:当前代码 allowInEditable = find/palette true、newTask/focusComposer false——编辑框聚焦时 Ctrl+T 按设计不触发(先点外部失焦再按),本段早前「放行」及「Ctrl+F 不劫持」的记载与代码相反,以代码为准。) - G2 Hooks/插件系统、G1 命令面板、托盘/自动更新:未开始(大基建,单独批次)。
- G1 快捷键集中管理 ✅(部分):App 全局 keydown 集中 handler——Ctrl/Cmd+F 会话内查找、Ctrl/Cmd+T 新任务、Ctrl/Cmd+K 命令面板、Ctrl/Cmd+Shift+A 聚焦 composer;handleNewTask 经 ref 转发(effect 依赖 [] 而函数后声明)。⚠️ CDP 实测 Ctrl+T 视图未切(探针 __newTask 直调同样)——handleNewTask→setActiveTaskId(null) 链路在 CDP 下疑似被恢复逻辑回写,真实键盘待用户手测;Ctrl+F 已实测有效。(2026-10-06 悬案告破:真凶是
-
P2 追加:输入框 ↑ 历史召回 ✅(2026-09-29,CDP 全链路实测):对齐 ZCode promptHistory 体系(纯逻辑直移 + textarea 化改造)。
lib/chat/promptHistory.ts:appendPromptHistoryEntry(trim 空不入库;仅连续重复去重——A/B/A 保留;30 条限额)+navigatePromptHistory(↑ 从末条往前、到顶 clamp 停住;↓ 走回末条后再按一次 → 回空输入退出浏览态;未浏览时 ↓/↑ 都从末条进入)。lib/chat/promptHistoryStorage.ts:per-workspace kv 持久化(键reinagent-chat-prompt-history:<workspacePath>,JSON 数组读时消毒 slice(-30))——重启历史保留、跨项目不串。- LexicalComposer 接线:
historyIndexRef浏览游标(ref 非 state,不驱动渲染);handleKeyDown 在菜单导航/Enter 之后处理 ↑/↓——仅「输入框为空或已处于浏览态」接管,Shift/Ctrl/Meta/Alt/isComposing 不接管(多行编辑光标移动不受影响);handleInputChange 手动编辑且与当前历史条目不一致 → 退出浏览态;发送 accepted 后 读-增-写(readPromptHistoryEntries→append→persist,防多实例覆盖)并重置游标;草稿切换/workspacePath 切换重置游标。App 两处渲染传workspacePath={workspaceProject}(活动任务 project 优先、草稿态用 selectedProject——与上下文根同源)。与 ZCode 的差异:纯 textarea 无需 Lexical 的 HISTORY_NAVIGATION_UPDATE_TAG 防斜杠菜单机制(setText 不触发 parseSlashQuery 的 / 前缀态)。 - CDP 实测(WebView2 :9333):SQLite 直种 3 条历史→reload→composer 选 PI-Desktop(workspacePath effect 从 kvCache 拉到种子)→↑γ/↑β/↑α/↑clamp/↓β/↓γ/↓空/↓再入γ 全链路 8 断言过;手动编辑退出浏览态、非空 ↑ 不劫持(prevented=false)过;种子→reload 读回即跨重启持久性实证。发送入史路径由单测覆盖(避免实测造任务污染)。测试种子已清理、active-task-id 已还原。
- ⚠️ 坑:MSYS bash 会吞
bun -e内联 JS 中的反斜杠(种子键写成E:DevCodeReinAgentPI-Desktop静默错键)——含路径/反斜杠的脚本一律写 .cjs 文件执行;CDP 断言 React 受控值须在 keydown 后await setTimeout(60)(dispatch 非 React 事件,更新微任务批处理,同步读 DOM 还是旧值)。 - 用户实测抓出两连 bug(2026-09-29 修复):① project 为 null 的任务历史全废——最初用
workspaceProject(任务 project 字段)做历史键,无项目任务传 undefined → 读写全跳过(↑ 无反应根因)。修复:删workspacePathprop,历史作用域直接用workspaceRoot(resolveWorkspaceRoot 已归一化为正斜杠、活动任务与草稿态恒有值:有项目=项目路径,无项目=<home>/.ReinAgent/DefaultProject)。② 会话中 composer 漏传 taskId(P2-A3 遗留,第二处渲染没有)→ 会话草稿全写进reinagent-draft:__new__全局键(↑ 召回文本也会被防抖写成草稿 → 重挂后预填 → 输入框非空 ↑ 正确不接管,看似「失灵」且污染新任务页)。修复:第二处补taskId={activeTaskId}。修复后 CDP 真发送链路实测:A 任务会话输入→Enter 真发送→↑ 召回刚发消息→↓ 回空,DB 落键<home>/.ReinAgent/DefaultProject✓。
-
P2 追加:刷新丢消息(压缩后会话持久化全废)✅(2026-09-29,CDP 真发送+刷新闭环实测):用户报「发送的消息和回复,一刷新页面就没有了」。根因链:
restoreState把nextMessageSeq按messages.length恢复——被压缩过的会话 hydration 后 id 是compact-0+m10..m17(length=9,max(id)+1=18),新轮次从 m9/m10 起重生成 → 与 hydrated 条目撞UNIQUE(task_id, msg_id)→conversation_sync的「事务内全删全插」整体被拒(DELETE+INSERT 原子回滚),该任务从此一条都落不了库,且界面无感知(仅 console.error)。触发条件=压缩(或编辑重发截断)过会话 + 重启后继续聊;未压缩会话 length 与计数器一致故从未暴露。修复:①restoreState按 max(/^m(\d+)$/)+1 与 length 取大恢复计数器;②loadOlderMessages前置更早历史时同样抬升计数器(防同类撞车);③ conversationModel.test.mjs 回归 1 例(压缩形状 hydration→append 得 m18/m19、非数字 id 回退 length)。CDP 实测:修后压缩会话发消息→0 sync 错误→DB 落 m18/m19→reload 后消息仍在 ✓。⚠️ 教训:全量替换式同步(delete-all+insert-all)对重复 id 是整批全败,凡「从持久层恢复状态机」的字段必须按数据真实形状重算而非按数组长度猜。 -
P2 尾巴批 1:#2 Git 面板自动轮询 / #3 提交图谱 / #5 ::code-comment 提示词声明 ✅(2026-09-29):
- #2 自动轮询:ZCode 用文件监听+60s 防抖(useGitAutoRefresh),我们无监听服务 → 面板挂载期
setInterval10s 静默刷新(refresh({silent}):不转 loading、失败仅 console.warn 保留上次好数据;手动链路仍大字报错)+ visibilitychange 可见即刷;单飞护栏 refreshingRef(上轮未完成不叠加,对齐 ZCode「agent 批量写文件避免密集 git I/O」);switching 中跳过。非仓库态也轮询(自愈:git init 后 10s 内面板自动恢复,成本一次 fail-fast git_status 可忽略)。⚠️ 实测坑:CDP 里 patch__TAURI_INTERNALS__.invoke计数不可靠(0 次误报)、1s 采样抓不到毫秒级 git.exe——最终靠组件内临时埋点(window.__gpTick/__gpRefresh)拿到真相:14s 内 tick=1/refresh=2(挂载+1 tick)✓。 - #3 提交图谱:Rust
git_logformat 追加%x1f%P(parents 空格分隔,根提交空数组,serde default)→lib/git/graphLayout.ts移植 ZCode git-graph 的 layoutAlgorithm+layout(裁 refs/选中态;泳道分配=分支着色核心;四色轮转--git-lane-0..3语义 token,dark #e6e8ee/#38bdf8/#2dd4bf/#fbbf24、light 深色版)→ GitPanel 提交历史区:绝对定位 SVG(连线层 paths + 节点 circles r4 stroke=var(--bg))+ 右侧行块 rowHeight=42 对齐。graphLayout.test.mjs 5 例(线性单泳道/合并双泳道/窗口截断/空历史/倒序拓扑护栏)。⚠️ 算法铁律:输入必须 git log 最新在前(parent 恒在更高下标)——倒序输入 determineNormalPath 够不到 parent 永不注册 → while 死循环(测试初版写反顺序即挂死,已加iterations > vertices.length*4+8护栏)。CDP 实测 PI-Desktop 真仓库:50 节点/227 连线/6 泳道/--git-lane-*生效 ✓。 - #5 提示词声明:
::code-comment{...}指令协议逐字采纳 ZCode desktop.ts 的 Inline Code Comments 小节进 DEFAULT_SYSTEM_PROMPT(# Code style 之后)——P2-C2 评论卡此前不触发的根因即协议未声明;PROMPTS.md 同步。
- #2 自动轮询:ZCode 用文件监听+60s 防抖(useGitAutoRefresh),我们无监听服务 → 面板挂载期
-
P2 尾巴批 2:#8 Plan 模式工具化 + ExitPlanMode ✅(2026-09-29,真模型 E2E 闭环):
- 工具:
lib/agent/exitPlanModeTool.ts(对齐 ZCode contracts/tools/plan-mode.ts 契约:input={plan≤20000 字 trim 非空, allowedPrompts?=[{tool:"Bash",prompt}]};execute 经 approval.request 挂起 args.kind="plan" → UI PlanModeCard → resolve {approved, feedback?};拒绝/跳过都提示模型仍处计划模式调整重提)。 - 审批门:
createApprovalGate顶部加 exit_plan_mode 专用分支(模式门优先于工具级策略,对齐 ZCode mode.plan.exitOnly):仅 plan 模式放行(放行后由工具自身挂起),其余模式 block 并提示无需提交计划。resolveToolPermissionKind登记 exit_plan_mode + 顺带补登记 ask_user_question(同类遗留坑:一直按默认 write 处理——计划模式下提问卡被拦、ask 模式双重审批)。 - 提示词:PLAN_MODE_PROMPT 重写——只读工具白名单扩到 glob/grep/webfetch/websearch,明确「调研完成必须调 exit_plan_mode 提交计划,不要纯文本输出计划后停住」。
- UI:
PlanModeCard(计划正文走 MarkdownText 渲染 + allowedPrompts 清单 + 拒绝反馈 textarea +「批准并开始执行」);App 分发 kind==="plan"(ApprovalCard fallback 同时排除 question/plan)。批准 = App 层先updateTaskApprovalMode(taskId,"ask")再 resolve(顺序保证工具收敛后模型的写/执行走新模式门);拒绝 resolve {approved:false, feedback}。 - E2E(真模型):composer 切计划模式→发调研指令→模型 12.5s 后调 exit_plan_mode → 计划卡渲染(markdown 正文+批准/拒绝)→点批准→卡消失+任务模式按钮变「变更前确认」+模型继续流式 ✓。测试后已停回合并把任务模式还原 完全访问。
- i18n 新键 planCard*(中英)。
- 工具:
-
P2 尾巴批 3:#4 代码审查预览模式 ✅(2026-09-29,真模型 E2E):
- 现状盘点:C2 已移植全部机器(
preview/lib/codeViewer.ts的 code-review 源类型 +previewPaneCodeReview.ts投影 + PreviewPane/previewPaneContent 消费链),唯一缺口是没有任何构造方——评论卡点击开的是普通 file 预览。 - 接线三处:useAppStore
codeViewerSource/openCodeViewer联合类型加 code-review 变体({requestId,title,body,priority?,startLine?,endLine?});CodeViewerPaneHost Extract 联合 + buildSource 透传分支(path 按工作区根解析);TurnGroupView 评论卡点击改开type:"code-review"(requestId=card.id)。 - 实测抓出两连真问题并修:① 弯引号指令整卡丢失——真模型(GLM)输出
title=“演示评论”(成对智能引号),解析器allowSmartQuotes是 citation 专属 opt-in,code-comment 未开 → 指令静默丢弃无卡。修复:code-comment 解析开{allowSmartQuotes:true}(指令体是自然语言,容忍成对智能引号属解析真实输出;+1 回归单测)。② C2 漏发 5 个 i18n 键(codeCommentsOne/Many、codeCommentCardsExpand/Collapse/OpenReview 一直显示原始键名)——补齐中英(文案取 ZCode locales 原文)。 - E2E:真模型按 #5 声明输出指令 → 卡渲染("1 条代码评论")→ 展开点击 → 预览面板 code-review 模式:package.json 加载(data-language=json)+ 评论覆盖层渲染 body + 无越界警告(range 可见)✓。⚠️ 机制备忘:评论卡列表默认折叠(头部按钮展开后才有点卡行)。
- 现状盘点:C2 已移植全部机器(
-
P2 尾巴批 4:#6 记忆管线独立模型统一解析 ✅(2026-09-29,4 单测):
- 真实缺口:Organizer 编排批早已支持
MemorySettings.organizerModel(App resolveModelDeps),但聊天后 Extraction 抽取(pool.maybeExtractMemory)无条件用主对话模型,独立模型配置被无视;summaryModel则是死设置(有选择器无消费方——LA 语义为对话总结模型,我们压缩摘要走 compaction 引擎,暂维持死设置待压缩批接线)。 - 修复:收编 App 内联解析为
lib/memory/modelResolution.ts resolveIndependentMemoryModelDeps(memory, providers)(organizerModel→供应商→buildModel;未配置/供应商不存在→null 回落主模型;Key 为空→抛错 No-Fallback);App resolveModelDeps 与 pool.maybeExtractMemory 同源复用——extraction 现在优先独立模型(解析失败 warn 后回落主模型,不打断聊天终态流;organizer 路径失败仍直接抛)。 - ⚠️ i18n 误判教训:抽屉 28 个「缺失」键实际全在
i18n/hub/zh.ts/en.ts(LA hub 移植批自带,...hubZh展开进语言对象)——查键缺失必须扫 hub 目录,只 grep index.ts 会误判(本次据此误加 26 个重复键触发 TS2783 已撤销,真正缺的仅 settings.close / settings.memoryOrganizerPhase2 两个,已补进 hub 文件)。 - modelResolution.test.mjs 4 例(未配置 null/供应商不存在 null/Key 空 No-Fallback 抛错/完整配置构建 deps)。
- 真实缺口:Organizer 编排批早已支持
-
P2 尾巴批 5:#7 子会话持久化 + 完整回放 ✅(2026-09-29,真模型 E2E 跨重启闭环):
- 落库:
subagentRunner.persistSubagentTranscript(runId, messages)——runTurn 收束后把转录写入 conversations.db 既有 message/part 两表(task_id =subagent:<runId>,不写 task 表 → 不进会话列表/水合),复用 conversation_sync 全量替换;每条 pi-ai 消息一行 + 单个transcript_messagepart(整条 JSON 忠实原样,含 system/思考/工具调用与结果)。失败仅 warn(回放是增强,不影响子代理结果收敛)。 - 回放 UI:
SubagentReplay.tsx——conversation_load_page 读回 → 逐条按角色渲染(user 提示词 / assistant 正文 + thinking 折叠 +tool: <名>参数折叠 / toolResult 结果),加载失败/无记录如实空态(诚实标注「早于持久化上线或落库失败」)。 - 入口与跨重启语义:子代理面板(ToolCallCard 摘要行「在右侧打开子代理面板」→ focusId);registry(内存)有记录 → 详情视图 + 「完整回放对话」按钮(面板底部二级视图,可返回);registry 无记录(重启后)→ 面板直接整体渲染持久化回放(DB 是跨重启唯一可靠源——初版把回放按钮挂在 registry 详情里,reload 后不可达,E2E 抓出后改为双路径)。i18n subagentReplay* 四键。
- ⚠️ E2E 坑:① ToolLayout 的 summaryAction 渲染的是
div[role=button][aria-label]不是<button>——CDP 按 button 标签找会扑空;② 完成回合的工具卡折叠进回合体,虚拟化+折叠下卡不可点——用后台子代理(run_in_background=true,卡片在 live 尾即时挂载)或先展开回合头。 - E2E:真模型派 Explore 子代理(前台+后台各一)→ 转录落库(subagent:sub-… 5 条消息含 system)→ reload 清空 registry 后从卡片点开 → 完整回放渲染全转录 ✓。346/346 + tsc 0。
- 落库:
-
P2 尾巴批 6:#9-Hooks 系统(G2 第一块)✅(2026-09-29,真模型 E2E 全决策路径):
- Rust
hooks.rs hook_execute:spawn shell(win cmd /C + CREATE_NO_WINDOW、unix sh -c)+ stdin 写事件 JSON + 限时轮询(默认 30s 上限 120s,超时 kill)→ {exitCode, stdout, stderr, timedOut}。 - TS
lib/hooks/hooksRuntime.ts:发现(<root>/.ReinAgent/config.json的 hooks 数组:event/matcher?/command/timeoutMs?)+ 信任评审(kvreinagent-hooks-trust:<root>= 配置原文指纹,变更即重新待审)+ 运行器(宽松输出协议:stdout JSON {decision: block|approve, reason, additionalContext} 或 exit 2 = block;首条 block 短路;发现缓存 5s TTL——full 模式每工具调用都过 PreToolUse 不能每次读盘)。 - 接线:① PreToolUse 挂 createApprovalGate 最前部(exit_plan_mode 模式门之后、工具级策略之前):block 短路、approve 跳过审批矩阵、无裁决走原流程;gate 安装条件放宽为
approval恒装(原 full 且无策略不装门 → hooks 永不执行,E2E 抓出;门内 read/full 早退保留 full 零审批语义);② UserPromptSubmit 在 App.handleSend 入口(blocked → toast 不发送;乐观受理取舍:输入已清空);③ Stop 经 subscribeTaskTerminal 终态 fire-and-forget。PostToolUse/PermissionRequest/SessionStart 本批未做(需 runtime 级挂点,见下批)。 - UI:聊天区顶部信任横幅(条目清单 + 批准/拒绝;拒绝 = kv 写空串)。
- E2E:echo hook_execute ✓;信任横幅批准 ✓;UserPromptSubmit exit-2 拦截(DB 实证:前 3 次未拦截的消息落库 seq 59-65,修复后第 4 次无新消息)✓;PreToolUse JSON block 于 完全访问模式 拦截 exec_command([Hook:PreToolUse] 理由 + gate 探针 ran/blocked)✓。测试配置已清理。
- ⚠️ 坑:① MSYS bun -e 反斜杠吞噬再踩(hook 命令
C:\Windows\...被吃成C:Windows...→ findstr 永不命中)——老老实实写 .cjs 种子;② Windowsfind被 Git 的 UNIX find 抢占——hook 命令务必用显式%SystemRoot%\System32\路径;③ 完成回合的工具卡折叠且 summaryAction 是div[role=button]非<button>——CDP 定位面板入口用 aria-label 全局查。
- Rust
-
P2 尾巴批 7:#9-命令面板 ✅(2026-09-29,CDP E2E):
CommandPalette.tsx(Portal 顶部居中浮层;大小写不敏感子串过滤 label+keywords;↑↓ 循环移动/Enter 执行/Esc 关闭/backdrop 点击关闭;高亮 scrollIntoView)。触发 = G1 集中键盘 handler 加 Ctrl/Cmd+K(编辑框聚焦也放行——全局命令入口)。命令注册表在 App(paletteCommands useMemo + ref 随渲染刷新):新建任务/设置/工作台/自动化/Skills/MCP/记忆/Git 面板/文件树/子代理/清空会话/压缩上下文/切主题/切侧栏/聚焦输入/会话内查找 共 16 条。⚠️ 坑:注册表的 useMemo/useRef 必须放 currentView 条件早退之前(hooks 顺序不能条件化——初版放早退后,切设置页即 hooks 顺序崩)。E2E:Ctrl+K 呼出 → 过滤 "git" 仅剩「打开 Git 面板」→ Enter 执行 → Git 面板打开 ✓。i18n palette* 键(中英)。 -
P2 尾巴批 8:#9-托盘 + 自动更新器 ✅(2026-09-29,E2E 诚实路径):
- 托盘:Cargo tauri 加
tray-icon特性;app_tray.rs setup_tray——图标(打包默认)+ 菜单(显示主窗口/退出)+ 左键单击切换主窗口显隐;setup 失败仅打日志不阻断启动。conf 加app.trayIcon。 - 关闭到托盘扩展至 Linux(2026-10-04,用户反馈 Linux 一关就退):原 close 拦截被
cfg!(windows)门死(Linux 点关闭即退出)。现去除平台门:setup_tray成功后置TRAY_AVAILABLE原子标志,仅托盘可用时注册 CloseRequested 拦截(设置 kvreinagent-hide-to-tray缺省开)——托盘起不来的系统保持直接退出,杜绝「窗口隐藏 + 无托盘图标」的僵尸态。实测本机 Ubuntu GNOME AppIndicator 链路健康(StatusNotifierWatcher 的 RegisteredStatusNotifierItems 含tray_icon_tray_app_reinagent_tray;注意 ayatana 后端用唯一总线名注册,按org.kde.StatusNotifierItem-pid模式查会误判不存在)。i18nhideToTrayDesc同步去掉「仅 Windows」。 - 自动更新器:
tauri-plugin-updater+tauri-plugin-process;updater.rs update_check/update_install——更新源不写死构建期,由前端 kv(reinagent-update-endpoint)传入(静态 latest.json 每端点);未配置 → configured:false 诚实态;URL 无效/网络失败/签名失败全部如实上抛。安装 = 重检→download_and_install→成功即 restart。插件要求 confplugins.updater(含 pubkey 必填)——已生成正式 minisign 密钥对(C:/Users/bytes/.tauri/reinagent.key[.pub],私钥勿入库,发布签名用),pubkey 入 conf。 - 设置 UI:
AppUpdaterCard(关于 tab)——更新源输入 + 检查更新 + 可用时「下载并安装」;未配置/错误全部如实渲染。 - E2E:空端点 → 「未配置更新源」✓;不可达端点 → 「检查更新失败:error sending request…」如实错误 ✓;hasUpdate 真实路径需发布服务器+签名产物,本批无法端到端(诚实边界,已记录)。托盘 CDP 不可见——应用无 panic 启动即托盘构建成功(代码级验证)。
- ⚠️ 坑:updater 插件 conf 缺
plugins.updater.pubkey启动即 panic(PluginInitialization);update.check()是 async(不能进 spawn_blocking);Update 字段是body/date(非 notes/pub_date)。
- 托盘:Cargo tauri 加
-
P2 尾巴批 9:#9-单实例锁 + 插件系统 v1 ✅(2026-09-29,E2E 闭环):
- 单实例锁:
tauri-plugin-single-instance(须最先注册;回调在第二进程上下文触发 → OnceLock 保存的 AppHandle 聚焦主窗口后第二进程退出)。用户实测双任务栏图标的根治(此前 tauri dev 重编译期旧实例未回收 + 无锁)。- 通道隔离的实例名(2026-10-05,用户定稿:dev 与 release 可同时启动):插件 Linux 侧互斥名 =
dbus_id ?? bundle identifier+.SingleInstance;原先 dev(debug 构建)与 release 共用com.web3claw.reinagent.SingleInstance,bun run tauri dev被已运行的 release 实例顶掉(NameTaken → 聚焦旧窗口后退出)。现注册改为Builder形态:cfg!(all(target_os = "linux", debug_assertions))时.dbus_id("com.web3claw.reinagent.dev"),release 走默认 identifier——同一通道仍互斥(两个 dev 或两个 release 各自互斥),跨通道可共存。⚠️dbus_id是 Linux 专用旋钮:Windows/macOS 的插件实现由 identifier 派生互斥名、无自定义入口,跨通道共存仅 Linux 支持(用户已确认只做 Linux)。两进程共享~/.ReinAgent/(conversation_store/automation无 busy_timeout、自动化调度线程会双跑)的偶发竞争用户已知悉并接受。 - 配套:dev 窗口标题加
(dev)(2026-10-05):lib.rs的setup内#[cfg(debug_assertions)]时window.set_title("ReinAgent (dev)")(release 编译期剔除,仍为tauri.conf.json的ReinAgent)——两个实例肉眼可辨。前端从不设置窗口标题,无覆盖风险。⚠️ 排查备忘:xdotool search --name ReinAgent会同时列出/usr/libexec/mutter-x11-frames(GNOME 的 X11 边框进程,pid 非应用)持有的窗口边框窗口(尺寸略大于客户区),别把它当成第三个应用实例。
- 通道隔离的实例名(2026-10-05,用户定稿:dev 与 release 可同时启动):插件 Linux 侧互斥名 =
- 插件 v1(
plugins.rs+lib/plugins/pluginRegistry.ts+ 设置新「插件」tab):插件 =~/.ReinAgent/plugins/<name>/+plugin.json清单(name/description/version/commands(相对目录,*.md 同工作区命令格式)/hooks(event/matcher/command/timeoutMs));安装=本地目录整拷(fs_pick_folder选目录 →plugin_install_from_dir,名字白名单、缺清单拒收、覆盖可选);卸载=删目录;启用态 kvreinagent-plugins-enabled(缺省启用)。 - 贡献挂接:① commands →
commands_scan加extra_dirs参数(重构出scan_command_dir),LexicalComposer 扫描时合并启用插件的 commands 目录(斜杠菜单直接出现插件命令,E2E:plugindemo ✓);② hooks →runWorkspaceHooks合并启用插件条目(安装=显式用户动作视为已信任,不走工作区信任横幅;工作区未配置/未信任时插件 hooks 独立照跑)。 - ⚠️ 坑:Stop 事件的 input 不带 workspaceRoot → hook cwd 空 → spawn 失败被 fire-and-forget 吞掉(插件 Stop hook 标记文件不出现)——runHookEntries 加 fallbackCwd=工作区根 + App 侧传递。
- E2E:直接 invoke 安装(fs_pick_folder 原生对话框 CDP 不可驱)→ 列表/设置 UI 列出(名称/描述/含斜杠命令徽标)→ 斜杠菜单出现 plugindemo ✓ → 发消息等回合结束 → 插件 Stop hook 标记文件落盘 ✓。市场源(git/npm/github)、agent/skill 贡献后续批次。
- 单实例锁:
-
P2 尾巴批 10:B 批完善尾巴①②③ ✅(2026-09-29):
- ① Hooks 管理界面 ✅:设置新「Hooks」tab(
HooksSection.tsx,App 传活动工作区根)——条目增删改(event/matcher/command/timeout/启停)+ 保存(saveWorkspaceHooks 保留 config 其它顶层键)+ 单条试运行(runSingleHookForTest 按事件注入样例负载,显式用户动作不看信任态;结果显示 blocked/exit/timeout/error)+ 未信任内联提示与批准。HookConfigEntry 加enabled?(false 跳过)。E2E:添加→保存→配置落活动工作区 ✓→测试显示拦截+理由 ✓。 - ② Hooks 剩余事件 ✅:PostToolUse(pi-agent-core
afterToolCall直通,agentRuntime 透传 + runAgentTurn 组装——additionalContext append 到结果 content 末尾,不替换原结果);PermissionRequest(审批挂起前 hook 裁决:approve 免审/block 拒绝/无裁决正常挂起);SessionStart(回合启动触发,additionalContext 追加系统提示词尾部,blocked = 本轮拒绝启动真实错误上抛)。HooksEventName 拆 RuntimeHookEventName 扩展类型。 - ③ 自动化运行历史 ✅:
AutomationRunsHistory.tsx(每自动化各拉 automation_list_runs 50 条合并倒序 cap100;状态点/trigger 徽标/错误预览/打开会话 onOpenTask→setActiveTaskId 自动切回工作台);AutomationsPage 头部 History 按钮切换视图。E2E:空态诚实显示 ✓(孤儿 run 不显示——按自动化聚合查询的合理行为)。
- ① Hooks 管理界面 ✅:设置新「Hooks」tab(
-
P2 尾巴批 11:B④ 快捷键系统完整版 ✅(2026-09-29,改绑/失效/冲突/重置 E2E):
lib/shortcuts/shortcuts.ts:动作注册表 SHORTCUT_ACTIONS(find/newTask/palette/focusComposer,默认 Ctrl+F/T/K/Shift+A + allowInEditable 语义)+ kvreinagent-shortcuts自定义绑定 + parseShortcut/shortcutFromEvent(必须带修饰键;支持 F1-F12)/matchesShortcut。- G1 handler 重构:硬编码 if-chain → 遍历注册表按当前绑定匹配(每次按键实时查 kv 缓存,改绑即时生效无需重启);allowInEditable 语义保留。
- 设置 UI:常规 tab
ShortcutsSection——每行动作 + 绑定按钮 + 录制态(点击后捕获下一个组合键)+ 冲突检测(与其它动作绑定重复 → 拒绝并指明占用者)+ 重置默认(回默认 = 删除自定义项)。E2E:改绑查找→Ctrl+J(旧键失效/新键生效)→ 新任务录同款被拒 ✓ → 重置恢复 Ctrl+F ✓。 - ⚠️ 坑:i18n 动态 labelKey(shortcutFind 等)必须逐个补键——遗漏渲染原始键名导致 E2E 选择器匹配失败(本轮第 3 次同类坑:新组件的 t() 键清单在完工时立即核对)。
-
P2 尾巴批 12:B⑤ 插件 v2 ✅(2026-09-30,git 安装/userConfig/技能贡献 E2E):
- Git 拉取安装:
plugin_install_from_git——本地存在的目录直接按路径克隆(git 原生支持);githubowner/repo短形式自动补 https;clone --depth 1 → 清 .git → 校验清单 → 移入插件根。初版 URL 校验把本地路径拒了(只认 http/git/短形式),放宽为「本地存在目录优先」。 - 清单扩展:
skills(相对目录,每子目录一个 SKILL.md)+userConfig({key: {title/type/default}})。 - 技能贡献:
skills/index.ts loadSkillsDiscovery合并——启用插件的 skills 目录 glob*/SKILL.md→ SKILL.md frontmatter name/description(宽松解析)→ SkillSummary 追加(readSkillText 按绝对路径可读,技能卡/内容展示全兼容)。装完插件必须 invalidateSkillsDiscoveryCache(发现缓存不清则 Skills 页看不到,E2E 抓出后已在安装成功路径失效)。 - userConfig:值存 kv
reinagent-plugin-options:<name>;设置「插件」tab 每插件配置编辑器(声明驱动);hook stdin payload.pluginOptions 注入该插件已存值(runHookEntries 按 entry.pluginName 取);getPluginOptions 合并 manifest 默认值。 - E2E:git 仓库(commands+skills+userConfig+hook 全贡献)→ 本地路径克隆安装 ✓ → 设置列表徽标(含技能/含斜杠命令/userConfig 编辑器)✓ → 保存 apiToken → kv 实证 ✓ → Skills 页列出 git-plugin-greet ✓。npm 源未做(需 npm CLI + tgz 解压,诚实暂缓);agent 贡献待 P1-6 自定义 profile 批。
- Git 拉取安装:
-
P2 尾巴批 13:终端配置 ✅(2026-09-30,E2E:shell 切换/字号生效):
- Rust
terminal_create加shell: Option<String>(用户配置优先,空 = 平台默认 win powershell.exe / unix $SHELL)。 lib/terminal/terminalSettings.ts:kvreinagent-terminal-settings(shell/fontSize 8-28/fontFamily/scrollback 100-100000,clamp 消毒)。- 设置「终端配置」tab 落地(原 coming soon):shell 预设下拉(PowerShell/CMD/Git Bash 路径/自定义)+ 字号/回滚缓冲数字 + 字体族 + 保存;
TerminalSettingsSection.tsx。 - TerminalPane 消费:xterm options(fontSize/fontFamily/scrollback)+ createTerminalSession 传 shell;effect 依赖加 settings 字段(改配置重开面板即生效——重建实例+PTY)。
- E2E:设置 shell=CMD + 字号 18 → 保存 → 开终端面板 → cmd.exe 被拉起 + 字号非默认 ✓。测试配置已重置默认。
- Linux 终端两处修复(2026-10-05,用户实测「fish 打开全空白无提示符」「图标显示方框」):
- ① TERM 继承陷阱:
terminal.rs从不设置TERM,PTY 子进程继承启动链的父环境——GUI /nohup tauri dev链路下常为TERM=dumb,starship 等提示符框架遇 dumb 终端直接拒绝渲染(实测报Under a 'dumb' terminal)→ 面板空白。修复 = 启动时若TERM为空/dumb/unknown则显式设xterm-256color,并补COLORTERM=truecolor(保留用户有效值不覆盖)。 - ② Nerd Font 字形缺失:
TerminalPane.tsx字体写死Consolas, Menlo, Monaco, 'Courier New', monospace(全无私有区字形),starship 图标显示方框。修复 = 字体栈改为 Nerd Font 优先(FiraCode Nerd Font Mono/FiraCode Nerd Font/JetBrainsMono/Hack/Symbols/MesloLGS)+ 平台默认等宽回退(本机~/.fonts/已装 FiraCode Nerd Font)。
- ① TERM 继承陷阱:
- Rust
-
终端面板 LiveAgent 化(2026-10-07,用户定稿:前把手可拖/后关闭钮/字体绿/关闭二次确认;CDP 真机 E2E + 双主题截图验证):
- 配色:
global.css移植 LiveAgenttokens.css的--terminal-{dark,light}-*全套色板(背景/前景/光标/选区/滚动条/16 色 ANSI,两套都定义在:root)。深色 = 前景绿字#4ade80(LiveAgent 原样);浅色 = 前景纯黑(用户定稿,LiveAgent 原值#1f2933被覆盖)。TerminalPane弃用硬编码 4 键主题,改lib/terminal/terminalTheme.ts readTerminalTheme(--terminal-{theme}-{kebab}解析,缺 token 回退空串不猜色——LiveAgent 同款做法)。 - tab 胶囊:每个 tab = 「GripVertical 拖动把手 → 终端图标 → 名字 → 活动态状态点(绿/红)→ × 关闭钮」(LiveAgent RightDockTabStrip 形态)。状态点只在活动 tab 显示(会话只挂活动 tab,切换即重建——原实现所有 tab 都点绿的失真已除)。tab id 改单调序号(关闭/重排后新建不复用)。
- 拖动重排:仅把手可拖(pointer 事件 + window 监听),拖动中实时按槽位换位(
lib/terminal/tabStrip.ts moveTabToIndex/tabIndexAtX纯逻辑);pointerup 后抑制一次幽灵 click(拖动不触发 tab 切换)。跨面板拖出/拖出到工作台属 LiveAgent 宿主体系,不做。 - 关闭二次确认:
decideTabClose决策——关闭活动且存活的会话 → 内联红条「关闭正在运行的终端「{name}」? 取消/关闭」(border/bg/text-[var(--danger)]+terminalCloseRunningi18n 键);非活动 tab(无存活会话)直接关;关闭最后一个 tab = 关整个面板(确认后,tab 条复位为全新单 tab);右侧面板 X 同样走确认(存活时)。 - 验证:
test:terminal新脚本 7/7(重排数学/越界/原地引用/槽位命中/关闭决策/主题键名映射与 trim);tsc 0。CDP 真机 E2E:加 tab→把手拖动 [term-1,term-2]→[term-2,term-1] ✓;关非活动 tab 直接关无确认 ✓;关活动 tab 弹确认条(文案/取消/关闭三态)✓;确认后关面板 ✓;双主题截图目视——深色全绿字(提示符/输入/输出 #4ade80)、浅色黑字 ✓。⚠️ canvas 渲染器把前景色画进画布,getComputedStyle(.xterm)探不到主题色(继承应用正文色),颜色断言改用截图目视。
- 配色:
-
P2 尾巴批 14:终端 Shell 自动检测 + 精简 ✅(2026-09-30,用户多轮反馈收敛):
- 用户要求:终端配置只保留 Shell(字号/回滚/字体族 UI 与代码全删,xterm 恢复 13px 默认);迁入「基础配置」tab;去掉「平台默认」抽象选项——必须有确定选中值,默认即选中平台默认的实际指向。
- 实现:
TerminalShellSetting.tsx重写——预设候选(pwsh MSI/商店双路径、powershell 系统路径、cmd、gitbash 双路径)挂载时经 fs_path_exists 逐路径探测,不存在的预设不进下拉;未配置 → 自动选中首个可用(本机=PowerShell 7);列表尾加「自定义路径」兜底入口(选中显示路径输入)。新增 Rustfs_path_exists。TerminalSettings 精简为 {shell}。 - E2E:下拉自动列出 PowerShell 7/5.1/CMD/Git Bash(本机实测探出商店版 pwsh)+ 默认选中 PowerShell 7 ✓;选 CMD 保存 → kv 持久化 ✓。
- ⚠️ 坑:
?? {}拓宽类型致索引报错(显式 as Record);settings 目录下 storage/db 相对路径是 ../../lib/storage/db;bun -e 删行会砍断多行值(用逐行 filter 后要查孤儿续行)。
-
P2 尾巴批 15:终端 Shell 标签 OS 检测 + Linux shell 候选 + cwd 工作区 ✅(2026-09-30):
- OS 检测:Rust
system_info.rs system_info——os(windows/linux/macos)+ 展示版本(Windows 解析cmd /c ver第三段 build 号 ≥22000 = Win 11;Linux 读 /etc/os-release PRETTY_NAME;macOS sw_vers)+ arch(x86_64→amd64、aarch64→arm64)+ 登录默认 shell($SHELL,unix)。前端模块级缓存,Shell 行标签渲染「Shell(Win 11 amd64)」式徽章。⚠️ 坑:ver 输出的版本 4 段(10.0.26100.9444),build = 第三段而非最后一段(最后是 patch 9444,首版解析误判 Win 10)。 - Linux shell 候选:UNIX_SHELL_CANDIDATES(bash/zsh/fish/sh,含 ~/.local/bin 与 /usr/bin、/bin 双位置);登录默认 $SHELL 对应候选排最前;Windows/Unix 两套候选按 system_info.os 选择。
- 终端 cwd = 当前任务工作区:TerminalPane 加 workspaceRoot prop(App 传 effectiveWorkspaceRoot)→ createTerminalSession cwd → Rust CommandBuilder::cwd(原实现从未传 cwd,终端固定开在应用启动目录)。
- OS 检测:Rust
-
P2 尾巴批 16:Environment 段带真实系统信息与所选 Shell ✅(2026-09-30):
buildEnvironmentSection扩参(osBadge/terminalShell)——runAgentTurn 发送时 awaitgetOsInfo()(共享模块 lib/system/systemInfo.ts,TerminalShellSetting 同源)+getTerminalSettings();注入- System: Win 11 amd64与- Terminal shell: <路径> — <跟随 shell 的语法提示>(pwsh/powershell → PowerShell 语法、bash → Unix、cmd → cmd 语法;原硬编码「cmd 语法、无 grep/head/wc」在用户切 PowerShell 后就是错误指令,已除)。OS 信息实现收编共享模块 lib/system/systemInfo.ts(TerminalShellSetting 删本地副本)。PROMPTS.md Environment 文档同步。 -
P2 尾巴批 17:Shell 检测零写死 ✅(2026-09-30,用户反馈「不要写死」):撤掉 TerminalShellSetting 里的硬编码候选路径表(C:\ 假设 + 用户名错误路径),改为 Rust
shell_detect命令经系统where/which从 PATH 实时解析(win: pwsh/powershell/cmd/bash.exe;unix: bash/zsh/fish/sh),检出什么列什么(本机实测探出 bash=Git PATH 版/CMD/Windows PowerShell/pwsh 四个 + 自定义路径兜底);unix 登录默认 $SHELL 候选排最前。标签按简名映射(pwsh=PowerShell 7、powershell=Windows PowerShell)。教训:探测类功能一律 PATH/运行时解析,不写盘路径清单。- 去重:用户实测指出工作区路径出现两次——「Current workspace root」声明(语义:相对路径解析基准)与 Environment 的 Working directory 行。Environment 段删除 Working directory 行(root 声明保留),buildEnvironmentSection 签名去 workspaceRoot。
-
P2 尾巴批 18:Hooks 页面对齐 LiveAgent ✅(2026-10-01,样式功能 1:1 + 8 生命周期事件):
- 用户定稿:页面样式功能与 LiveAgent 完全一致(无信任横幅、无单条测试按钮——运行时信任门禁与聊天区横幅保留不动);旧 ZCode 事件在 UI 可见(「兼容事件」分组)。
- UI 重写:
HooksSection.tsx(左 13rem 事件导航「生命周期」8 事件 + 「兼容事件」6 事件、每事件数量角标、右事件描述 + 新增按钮 + 空态卡 + Hook 卡片[名称点击编辑/类型·描述副行/AgentActivationSwitch/删除 ConfirmActionPopover],操作即保存无总保存键)+HookModal.tsx(名称/描述/类型 command|http/command=多行脚本+行数徽标+超时秒默认60、http=请求列表编辑器[7 方法下拉+URL+Headers/Body JSON,GET 类禁 body],底部内联错误)+hooksHttpRequestEditor.tsx(LA 移植适配)。唯一加项:PreToolUse 弹窗多一个 matcher 输入(ZCode 契约需要)。全部用 lw 组件(Dialog/Select/Switch/ConfirmActionPopover/SettingsNotice/FormField[mcp/FormField])+ bg-settings-tile 主题 token。i18n hooks* 全套键(中英),旧 UI 键(hooksTest*/hooksSave 等)已清。 - 数据模型:HookConfigEntry 扩展
{id?, name?, description?, type?: command|http, requests?: [{id,url,method,headers?,body?}]}(command 脚本沿用旧字段command零迁移);parseHooksConfig 三代形态兼容 + 修复 enabled 丢失 bug;无 name 旧条目展示 = 脚本首行/首请求 URL 派生。 - Rust
hooks.rs hook_http_execute:ureqAgent::run(http::Request)(7 方法全覆盖含 DELETE body)+ kv 全局代理(同 MCP)+ 超时默认 60s 上限 600s → {status, ok, bodySnippet(2KB), timedOut, error};URL/header 硬错误如实回传。 - 8 生命周期事件接线:pi-agent-core 原生事件名与 hook 事件同名——runAgentTurn 包装 onEvent 一处埋点(agent_start/turn_start/message_start/message_end/tool_execution_start/tool_execution_end/turn_end/agent_end),fire-and-forget 不阻塞主流程、block 协议不生效(观察性);负载裁剪(工具事件全量 args、消息事件 stopReason+2KB 文本预览)。⚠️ agent_end 只由循环事件触发,App 终态监听不重发(防双触发)。执行超时缺省统一 60s(DEFAULT_HOOK_TIMEOUT_MS,原 30s)。
- E2E(CDP):页面 1:1 渲染 ✓ → PreToolUse 新增弹窗(含 matcher)✓ → 切 http 类型(请求编辑器/禁 body 提示)✓ → 保存落
C:/Users/bytes/.ReinAgent/DefaultProject/.ReinAgent/config.json新格式 JSON ✓(⚠️ 工作区根 = 活动任务工作区,非 selectedProject)→ 删除确认气泡 ✓ → 350 测试全绿 + tsc 0 + cargo check 绿。
-
P2 尾巴批 19:设置「子智能体」页(对齐 PI-Desktop)✅(2026-10-01,用户定稿:抄 PI 后修复):
- tab 改名:「智能体能力」→「子智能体」(settingsAgent 键),占位页替换为
AgentSubagentsPage。 - 定义层
lib/subagents/subagentDefinitions.ts:用户定义 =~/.agents/subagents/<id>.md(frontmatter name/description/tools/model/thinkingLevel/maxTokens + 正文=系统提示词,≤32KB、≤64 个,启用态绝不写文档——kvreinagent-subagent-builtins-disabled[handle 数组] 与reinagent-subagent-enabled[Record<id,bool>]);宽松 frontmatter 解析(max-tokens→maxTokens、inherit 剥离、坏文档如实 diagnostics);renderSubagentDocument 序列化;内置 5 定义(explorer/code-reviewer/test-runner/fixer/ui-designer)指令正文照搬 PI,工具名映射本仓注册表(Read→read_file、Bash→exec_command、Edit/Write→edit_file/write_file),ui-designer 无 BrowserPreview 如实去除(正文验证段同步改写为 exec_command 承接);CRUD 走 fs_list_dir/fs_read_file/fs_write_file/fs_delete_file;loadSubagentCatalog 合并(用户同名遮蔽内置、停用内置剔除);resolveSubagentModelPin("provider/model")→buildModel(loadProvidersConfigFromDisk + API_FORMAT_TO_TYPE[modelResolution 导出共用],供应商缺失→null、Key 空→抛错)。 - 运行时
subagentRunner.ts定义驱动:createSubagentTool改异步工厂(加载目录渲染工具描述- name (tools): desc,runAgentTurn 两处 await);schema 保留 subagent_type 参数、句柄查目录(normalizeSubagentHandle 兼容 Explore→explorer、general-purpose→fixer,未知句柄如实报错并列目录);执行按定义取工具白名单(空→默认 read_file/glob/grep)、模型钉选(解析失败/未配置如实 isError,不静默回落)、thinking 钉选;审批门按可改动性挂载(PI 语义:Bash/Edit/Write=mutating——explorer 带 exec_command 也挂父门,code-reviewer 纯只读不挂);composeSubagentSystemPrompt=框架行(身份/改动声明/报告即最终消息)+定义正文+工作区根。maxSteps=6 不变;转录持久化/回放/subagent_output 不变。 - UI:
AgentCapabilityLayout.tsx(PI 同名原语子集,PI settings.css 类→本仓 token Tailwind:行=bg-settings-tile 圆角卡[图标/名称+徽标/Task(handle) mono/说明两行钳制/工具 chips/操作区],两击删除 3.2s 自动解除)+SubagentEditorSheet.tsx(lw Sheet 右滑 inset:模板 chips/名称[首命名种子模板+slug 提示]/说明/工具九宫格勾选[mutating 琥珀标记]/指令正文[字节计数 0.8/1.0 变色]/高级[ModelPicker 复用 value=provider/model、thinking 下拉、启用];v1 如实取舍:无 fallbackModels/maxTokens/inheritTools——运行时未实现不做假开关)+AgentSubagentsPage.tsx(搜索/新建/内置组+全局级组[~/.agents/subagents 路径 chip]/开关即时落 kv/编辑/复制为我的定义/reveal[opener]/两击删除)。i18n subagent* 全套 46 键(中英)。 - 顺手修:
storage/db.tskvSet/syncTasks 的window.setTimeout裸用 → scheduleDebounce 环境无关(Node 测试可调 kv)。 - 测试:subagentRunner.test.mjs 重写(7 条:过滤/聚合/端到端旧值兼容/未知句柄/审批门挂载/目录渲染/后台+通知)+ subagentDefinitions.test.mjs 新增(5 条:解析/round-trip/内置映射/slug/目录合成)。⚠️ 坑:faux 工具调用参数必须过 schema(glob 要 pattern)——参数校验在审批门之前,校验失败门不触发(探针二分才定位)。
- tab 改名:「智能体能力」→「子智能体」(settingsAgent 键),占位页替换为
-
批 19 补:模型用量按真实模型记账 + 一键清零 ✅(2026-10-01,用户发现「Explore 0.4% 1746 tokens」追溯):
- 根因:用量回填第二源(agent 工具 tool_result)按
details.subagentType记账——旧硬编码时代类型名即模型标签;子智能体定义驱动后不再成立。 - 改法:
summarizeSubagentRun从嵌套轮 assistant 原件提取真实 provider/model 戳(钉选模型与会话模型都如实反映)→ agent 工具 details 带 provider/model → Rust 回填子代理行按details.provider/model记账(缺省兜底 subagent/unknown,subagentType 字段废弃)。 - 一键清零:新 Rust 命令
usage_reset——DELETE model_usage + kv 写水位线usage-reset-watermark-ms;回填两源查询加COALESCE(started_at,0) > 水位线(清零后历史 part 不重灌——INSERT OR IGNORE 只防重复行不防重导,水位线才是防重灌闸门)。用量面板右下加「清零」按钮(两击确认)。 - 顺手修:commands.rs 三个测试还在调旧签名
commands_scan(1 参)(插件 v1 加 extra_dirs 时漏更)——补 None 第二参。 - E2E:重置按钮两击 → 面板全零(累计 0/峰值 0/热力图灰/两图空态)→ 重开面板触发回填仍为零 ✓。usage_stats Rust 测试 4/4(新增水位线防重灌用例)。
- 清零补全(用户反馈:最长聊天时长没清掉):
longest_session_ms/tool_call_count/tool_error_count是从 message 表实时算的(不走 model_usage),清零删表管不到——水位线统一应用到这三个查询(started_at > 水位线),清零后旧会话时长/旧工具调用一律不计。usage_stats 测试扩断言(清零后 longest=0/tool=0,新事实入账)。工具调用参数坑备忘:faux 工具调用参数必须过 schema(glob 要 pattern),参数校验在审批门之前。
- 根因:用量回填第二源(agent 工具 tool_result)按
-
批 19 补 2:右侧面板新增「文件管理器」与「内嵌浏览器」✅(2026-10-01,不进插件页,纯面板功能):
- 文件管理器:FilesPanel 增管理操作——根级工具条(新建文件/文件夹、刷新、资源管理器显示)+ 行 hover 操作(目录内新建/重命名/删除/显示,删除走 ConfirmActionPopover,重命名/新建为行内输入 Enter/Esc)。Rust 新命令:
fs_rename(重命名/移动,目标存在拒绝、符号链接拒绝)、fs_remove_entry(文件或目录递归删除,拒符号链接与工作区根)、fs_create_dir(父目录存在校验)。 - 内嵌浏览器(路线 B):⚠️ 原生 webview2-com 自建 Controller 双方案均 0x8007139F(ERROR_INVALID_STATE:环境创建成功但 Controller 回调拒绝,Parent 主 HWND 与专属 child HWND、新旧 API 全组合)——最终改用 Tauri
unstable特性的多 webview:Window::add_child(WebviewBuilder, pos, size)一行创建子 WebView(tauri 处理全部 COM),Cargo tauri features 加unstable。命令面:browser_open(已存在则导航复用,"already exists" 竞态恢复为导航)、browser_set_bounds(tauri_runtime::dpi::Rect,Rect 未从 tauri 根导出需加 tauri-runtime 依赖)、browser_navigate、browser_eval(eval 单向)、browser_read_page(读通道:一次性 TcpListener + eval 注入 fetch POST 上报,HTTPS 页面 fetch localhost 非混合内容)、browser_current_url、browser_close、browser_is_open。⚠️ 坑:多 webview 下get_webview_window("main")返回 None,必须get_window("main")+get_webview(label);CDP Page.captureScreenshot 拍不到原生子 WebView(OS 层叠加),验证必须 GDI CopyFromScreen;StrictMode 双挂载触发 add_child "already exists" 竞态。 - Browser 工具对(tools.js,审批分级对齐用户拍板):
browser_view=read(navigate/snapshot——snapshot 经 browser_read_page 抓 url/title/innerText 6KB);browser_act=write(click=elementFromPoint 合成鼠标事件、fill=选择器填值+input/change 事件、evaluate=eval);都要求面板已开(未开如实 isError 提示先开面板)。 - Linux 分支移植 ✅(2026-10-03,linux 分支;Windows 行为零改动):
- 浏览器面板定位(方案 A:绕开 wry 直连 GTK):根因是 wry 的
set_bounds只在创建时容器为 GtkFixed 才生效——is_in_fixed_parent在add_to_container(webkitgtk/mod.rs:325)时一次性写死,此后即使 reparent 也不会更新;而 tauri 在 Linux 一律build_gtk(window.default_vbox())(tauri-runtime-wry lib.rs:5186-"only way to account for menu bar height"),于是set_bounds恒为空操作、子 webview 被 VBox 顺序堆叠在主 webview 前/后(表现为面板"不是跑到上面就是跑到下面")。修复:browser_set_bounds首次调用时(BROWSER_REPARENT_DONE标记,browser_close重置)把子 webview 从 GtkBox 摘下、按原 slot index 放进新建的 GtkFixed;随后不再使用 wry 的 set_bounds,改由browser_place_gtk直接在 GTK 层set_size_request+size_allocate(Allocation::new(lx, ly, lw, lh))定位(物理像素按 widget scale_factor 换算逻辑像素)。Windows 分支保持 wryset_bounds原路径不变。 - 编译修复:
browser_set_bounds的tauri_runtime::dpi::Rect/Position/Size→tauri::Rect/Position/Size(tauri 2.12 根导出同组类型 re-export);此前 dev 分支因该路径在 Linux 无tauri_runtime依赖无法编译。 - ⚠️
with_webview是异步派发:闭包经send_user_message排队到事件循环执行(非立即调用),因此 reparent 与随后定位之间有执行顺序语义——当前实现把两者放在同一次 set_bounds 调用链内(reparent 先入队、place 后入队),顺序由队列保证。 - 读通道 Linux 实现:WebView2 的
window.chrome.webview.postMessage槽位是 Windows 专属——Linux 走 WebKitGTKrun_javascript完成回调直接带回 JS 结果值(JavascriptResult.js_value()→ JSCValue.to_str();with_webview 闭包由 tauri 派发到事件线程=GTK 主线程,WebKitGTK API 的 MainContext 断言天然满足)。⚠️ run_javascript 不解析 Promise——提取脚本须为同步 IIFE(tools.js 现有脚本均满足);Windows 的 async 包装保持原样。 - 截图 Linux 实现:WebView2 的 CDP
Page.captureScreenshot仅 Windows——Linux 走 WebKitGTK 原生snapshot(SnapshotRegion::Visible, SnapshotOptions::NONE)→ cairo Surface →write_to_png→data:image/png;base64data URL(Windows 保持裸 base64 JPEG)。tools.js 消费端增量适配:data:image/前缀检测分流(PNG data URL vs 裸 JPEG base64),Windows 分支逐字节不变。 - Cargo:新增
[target.'cfg(target_os = "linux")'.dependencies]:webkit2gtk = { version = "2", features = ["v2_40"] }(直依赖:WebViewExt trait + gio/glib 经 crate re-export)、cairo-rs = { version = "0.18", features = ["png"] }、javascriptcore = { version = "1", package = "javascriptcore-rs" }(ValueExt::to_str)。⚠️ webview2-com 无条件声明即可——Linux 下 cargo build+链接通过(COM 调用全在 cfg(windows) 块内)。 - 测试:cargo test 174/174(git_panel
checkout_validates_against_real_repo加环境守卫:CARGO_MANIFEST_DIR 非 git 仓库时跳过——/tmp 隔离构建区 rsync 排除 .git 属环境差异);前端 build ✓ + node 套件全绿(test:providers 3 失败为已记录的 Node 24.jsspecifier 既有问题,与本批无关)。bg_processspawn_output_stop_lifecycle在 /tmp 磁盘压力下偶发时序抖动,单独复跑通过。
- 浏览器面板定位(方案 A:绕开 wry 直连 GTK):根因是 wry 的
- 测试:tools.test.mjs 期望表补 browser_view/act;355 全绿 + tsc 0 + cargo 绿;GDI 截图实证内嵌 bing 原生渲染。
- 浏览器工具增强 ✅(2026-10-01 用户要求立即增强):
- browser_act click 支持选择器:selector 模式(推荐)= scrollIntoView 居中 + getBoundingClientRect 中心 + 完整鼠标事件序列(pointerdown→click),坐标模式保留为后备。
- browser_view 新增 elements 动作:列举视口内可交互元素(a/button/input/select/textarea/role)前 40 个,生成唯一 CSS 选择器(id 优先 → 3 级 nth-of-type 路径,CSS.escape)——模型用返回的选择器驱动 browser_act。
- browser_view 新增 screenshot 动作(多模态):Rust
browser_screenshot经Webview::with_webview拿 PlatformWebview.controller()(windows 直接返回带类型接口,无需 from_abi)→ CDPPage.captureScreenshot(jpeg q60)→ base64 → 工具返回 image content block(pi-ai ToolResultMessage 原生支持 Text|Image content)。⚠️ 需要视觉模型才有效(非视觉模型 + 图片块网关断连,见 error-raw-display 条目)。 - ⚠️ 关键坑:tauri unstable 多 webview 下
get_webview_window("main")返回 None(add_child 的子 webview 不注册为 WebviewWindow 实体)——所有 browser 命令统一get_window("main")+get_webview(label);webview2-com completed handler 闭包必须返回Result<(), windows_core::Error>(末尾 Ok(()))。 - 验证:子 WebView 自带 CDP target(同调试端口)——Page.captureScreenshot 72KB jpeg ✓、elements 脚本实抓 bing 40 元素带唯一选择器 ✓;355 测试 + tsc 0 + cargo 绿。
- 助手页(主对话人设预设)✅(2026-10-01 用户定稿:与子智能体互补——子智能体=委派工,助手=主对话人设):
- 数据层
lib/assistants/assistantDefs.ts:~/.ReinAgent/assistants/<id>.md(frontmatter name/description/model?/thinkingLevel? + 正文=人设指令,32KB/32 个上限);内置 4 预设(general=无人设缺省、coder 代码专家、writer 文档写手、translator 翻译官)不可删;用户自定义同名遮蔽内置;任务绑定存 kvreinagent-task-assistant。 - 运行时:runAgentTurn 收
assistantId→ loadAssistantCatalog 查 def → 人设段(# Assistant Persona: name+ 描述 + 正文)插入默认 system prompt 前部(工具/安全段保留,非替换);general/查无跳过。⚠️ buildTurnOptions 的 deps 必须加activeTask?.assistantId——toolPolicies 等引用在纯 assistantId 更新时不变,闭包 stale 导致人设不注入(首测踩中)。 - 切换链路:助手页「应用」与工作台顶栏 AssistantChip(任务标题旁)都走 updateTaskAssistant(记录 assistantId + 采用模型钉选);任务持久化 JSON.stringify(t) 自动带 assistantId。
- E2E:真模型验证——切「代码专家」后让模型复述系统提示词,回复精确引用了助手人设描述 ✓;355+4 测试全绿。
- 数据层
- ⚠️ 下拉框不自动关闭真因:lw Select 半受控缺陷 ✅(2026-10-01):
- 症状:助手编辑器内所有下拉框(思考等级 Select / ModelPicker DropdownMenu)点选后弹层不关、ESC 无效、点外部也不关。用户多次反馈。
- 真因:lw select.tsx Root 把
openprop(调用方未传 = undefined)直接透传 PopoverPrimitive.Root → 非受控模式,开/关由 Radix 内部状态管理;选项点击的root.setOpen(false)只更新包装层internalOpen,永远碰不到 Radix 内部状态 → 弹层关不上。外点关闭走 Radix 原生 dismiss 路径不受影响(混合表现:外点能关、选中不关)。 - 修复(一行):Root 改半受控
open={open ?? internalOpen}——未受控时用包装层状态驱动 Radix。全局 lw Select 全部修复(记忆抽屉/MCP/自动化/子智能体/助手编辑器)。 - 验证:OS 级真实鼠标(mouse_event)——Select 打开 7 options ✓、点外部关闭 ✓、点选 high 后 triggerText=high + 弹层关 ✓。⚠️ E2E 坑:CDP 合成
.click()驱不动 Radix 触发器(需真实 OS mouse_event);GDI 截屏会被前景的 ZCode 窗口遮挡(SetForegroundWindow 置前再截)。
- ⚠️ 启动闪屏变点击盾真因(引发「应用按钮点不动/下拉框关不上」报告):startup-ready 类在 rAF 里添加——窗口被遮挡时 Chromium 节流 rAF(无绘制帧),类永不添加 →
#loading(z 999999 全屏 PE:auto)变成透明点击盾吃掉一切点击。修复:rAF 保留 + setTimeout 300ms 兜底(定时器不受绘制节流)。当前实例已即时解卡(CDP 加类)。⚠️ 教训:CDP Page.reload 在窗口被遮挡时重启页面会稳定复现此状态——自动化验证前先检查 startup-ready。 - ⚠️ 弹层关不掉·终局真因:@utility 内嵌 @keyframes 不提升 → Radix Presence 挂死 ✅(2026-10-02,推翻上条「外点能关」结论):
- 症状(全量):所有 Radix 弹层(lw Select popover、ModelPicker DropdownMenu、Dialog)选中不关、ESC 不关、点外部也不关——三条关闭路径全灭。上一条「外点能关、选中不关」的归因不完整:semi-controlled 修复是必要非充分。
- 真因(证据链):
global.css里@keyframes tw-enter/tw-exit嵌套在@utility animate-in/out内部——Tailwind v4 不会把 @utility 内嵌的 @keyframes 提升进产物 → 产物里animation: tw-exit 150ms引用了不存在的 keyframes → 动画永不运行 → animationend 永不触发 → Radix Presence 等 animationend 卸载节点 = 永远挂着。运行时证据:选中后aria-expanded=false、data-state="closed"(Radix 状态已正确翻转!),但节点getAnimations()为空、computed animationName=tw-exit、keyframesProbe 全 stylesheet 搜不到 → 节点 opacity 1 + pointer-events auto 视觉常驻。且挂死的弹层作为顶层 DismissableLayer 还会吞掉 Dialog 的 ESC(第二层永远关不掉第一层收不到)。 - 修复:
@keyframes tw-enter/tw-exit提升到 global.css 顶层(@utility animate-in/out只留 animation 简写)。一处修复全局愈合(Select/DropdownMenu/Dialog/Toast 全部)。 - 验证(CDP 真实鼠标):选中 low → 弹层关 + trigger=low ✓;ESC → 只关弹层 Dialog 保留 ✓;外点 → 只关弹层 ✓;ModelPicker 搜索过滤选中 → 关 + trigger 更新 ✓。
- ⚠️ 教训:① Radix「关不掉」类问题先查
data-state是否翻转:翻转了=渲染/动画层挂死(查 getAnimations + keyframes 是否真进产物),没翻转=状态接线断;② Vite dev server 文件监听会死——磁盘改完 Page.reload 拿到的仍是旧 transform(模块图缓存不失效),今天为此绕了远路(先误判 bundle 陈旧重启了整个 tauri dev 栈),诊断 bundle 新旧必须看内容特征(grep transform 产物)而非 mtime。
- 助手作用域语义定稿 ✅(2026-10-02 用户拍板):全局默认 vs 任务绑定:
- 语义:无活动任务时「应用」/chip 选择 = 设为全局默认助手(对后续未单独设置的新任务生效);任务内选择 = 任务作用域(持久化在任务记录,该任务以后默认用它)。解析顺序:
task.assistantId ?? globalDefault。 - 响应式收口:kv
reinagent-assistant-default读写统一收进 useAppStore——新字段globalDefaultAssistantId(初值 hydrate 自 kv)+setGlobalDefaultAssistantaction(kv 写 + set 同步);assistantDefs.ts 只导出KV_ASSISTANT_DEFAULT键名(get/setGlobalDefaultAssistantId 已删)。此前 kv 直写不触发渲染导致 chip 徽标滞后的缺口由此闭合。旧键reinagent-draft-assistant(草稿补绑方案)废弃。 - 顶栏 chip 常显:原先
{activeTask ? <AssistantChip/> : null}——草稿态整个不渲染(「chip 不显示翻译官」首因);改常显,草稿态显示全局默认。 - AssistantChip 目录加载时机:catalog 原先仅在弹层 open 时加载 → 未打开过时
defs.find(assistantId)恒空、label 永远回退「通用助手」;改挂载即加载(loadAssistantCatalog 自带 5s 缓存)。 - E2E(重启后的干净栈):草稿态应用代码专家 → store= coder + chip 即时「代码专家」✓;恢复 general → chip「通用助手」✓;kv 重启水合 ✓。tsc 0;测试 199 绿(providers 3 败为存量问题:node 24 不解析 subagentRunner.ts 的
.js尾缀 specifier → subagentRegistry,stash 验证干净 HEAD 同样 3 败,与本次无关)。
- 语义:无活动任务时「应用」/chip 选择 = 设为全局默认助手(对后续未单独设置的新任务生效);任务内选择 = 任务作用域(持久化在任务记录,该任务以后默认用它)。解析顺序:
- ⚠️ 作用域显示脱节修复(2026-10-02 用户报告「任务内切助手,全局也跟着切」):
- 取证:store/kv/UI 全旅程 CDP 实测(任务内 chip 切换 → task.assistantId 变、globalDefaultAssistantId 与 kv 纹丝不动)——状态层从不泄漏。真缺陷是显示回退错误:chip 与助手页对「未绑定助手的任务」硬编码回退
GENERAL_ASSISTANT_ID,而运行时 buildTurnOptions 对未绑定任务注入的是全局默认助手的人设 → 设过全局默认后,新任务 chip 显示「通用助手」实际却跑全局默认人设,显示与行为脱节。 - 修复:chip 与助手页「当前生效助手」统一为
activeTask?.assistantId ?? globalDefaultAssistantId(与运行时解析严格一致,App.tsx 的 GENERAL_ASSISTANT_ID import 随之删除)。 - 作用域标识(助手页):任务模式徽标改「任务使用中」(brand 高亮);全局默认行恒显灰底「全局默认」徽标(Globe 图标);草稿态按钮语义化——非默认行「设为全局默认」、默认行禁用显示「全局默认」、任务模式照旧「应用」。i18n:assistantApplyGlobal / assistantActiveTaskBadge / assistantGlobalBadge(assistantActiveBadge 废除)。
- E2E 五点全过:未绑定任务 chip=全局默认 ✓;任务内切翻译官 → chip=翻译官 + global=coder 不动 ✓;任务页「任务使用中」徽标 ✓;回草稿 chip=代码专家(全局未被影响)✓;草稿页「全局默认/设为全局默认」按钮语义 ✓。tsc 0 + 199 测试绿。
- 取证:store/kv/UI 全旅程 CDP 实测(任务内 chip 切换 → task.assistantId 变、globalDefaultAssistantId 与 kv 纹丝不动)——状态层从不泄漏。真缺陷是显示回退错误:chip 与助手页对「未绑定助手的任务」硬编码回退
- 作用域入口收敛定稿(2026-10-02 用户拍板):助手页只管全局默认:
- 规则:助手页(含任务激活时)只能设置全局默认(applyGlobal → setGlobalDefaultAssistant,不再采用模型钉选);任务作用域唯一入口 = 聊天页任务名旁 AssistantChip 下拉(updateTaskAssistant,任务记录持久化=记忆)。
- UI:助手页删除「应用/任务使用中」按钮与徽标——所有行统一「设为全局默认」,当前默认行禁用显示「全局默认」+ Globe 徽标;工具条下加常驻提示行(assistantPageHint:全局默认作用域说明 + 指引去聊天页切任务助手)。i18n 删 assistantApply / assistantActiveTaskBadge。
- 草稿态 chip 语义不变:无任务时下拉选择 = 设全局默认(用户原始规格「下一个任务默认的助手」)。
- E2E:任务内(绑定翻译官)页面点代码专家「设为全局默认」→ global=coder、task.assistantId=translator 不动 ✓;chip 仍翻译官 ✓;草稿 chip=代码专家 ✓;页面行语义(全局默认禁用/设为全局默认)✓;提示行可见 ✓。tsc 0 + 199 测试绿。
- ⚠️ 任务内选不了「通用助手」修复(2026-10-02 用户报告:选完自动变回代码专家):
- 根因:
updateTaskAssistant把assistantId === "general"转成undefined(当作清除绑定)→ chip 显示走activeTask?.assistantId ?? globalDefaultAssistantId回退到全局默认(恰为代码专家)→ 显式选择「通用助手」被吞、界面弹回全局默认。「通用助手(无人设)」与「未绑定(跟随全局默认)」是两个语义,被混为一谈。 - 修复:显式选择一律按任务记忆——
assistantId原样存储(含 "general");仅「从未选择过」(undefined,旧任务/新任务)回退全局默认。运行时无需改(runAgentTurn:460assistantId !== "general"本就跳过人设注入)。 - E2E:全局=代码专家时未绑定任务 chip=代码专家 → chip 选通用助手 → chip 保持「通用助手」+ task.assistantId="general" 持久化 + 全局不动 + 切走切回仍通用 ✓。199 测试绿。
- 根因:
- 助手收敛为纯人设(2026-10-02 用户拍板):删除模型/思考强度:
- 动机:模型与推理强度一律跟随设置里的默认,助手只承载人设指令(名称/描述/正文)。
- 删除链路:AssistantDef/UserAssistantInput 删 model/thinkingLevel/approvalMode 字段;parse 停读(旧文件的 model/thinkingLevel frontmatter 容错忽略)、render 停写;编辑器删「高级」整段(ModelPicker + 思考 Select + providers 加载);AssistantChip onPick 与 store
updateTaskAssistant(id, assistantId)去掉模型钉选参数;删死代码 kv 任务绑定映射(KV_TASK_ASSISTANT get/set——已被任务记录 assistantId 取代)。 - 测试:assistantDefs.test.mjs 重写为纯人设断言(round-trip 不含 model 行 + 旧 frontmatter 容错)并首次接入 test:scripts(
test:assistants,此前从未被执行过);import 改显式.ts后缀(node --test 解析要求,项目已有 allowImportingTsExtensions)。 - i18n:编辑器底部加 assistantFollowsDefaultsHint(「助手只改变人设指令;模型与推理强度始终跟随设置里的默认」)。
- E2E:编辑器 0 下拉/无高级段 + 提示可见 ✓;保存文件 frontmatter 仅 name/description ✓。tsc 0 + 204 测试绿(chat132/settings6/markdown12/agent49/assistants5)。注意:旧任务上历史钉选的 providerId/modelId 保留为任务级模型设置(用户可在模型选择器改),不再由助手产生。
- 文件管理器:FilesPanel 增管理操作——根级工具条(新建文件/文件夹、刷新、资源管理器显示)+ 行 hover 操作(目录内新建/重命名/删除/显示,删除走 ConfirmActionPopover,重命名/新建为行内输入 Enter/Esc)。Rust 新命令:
-
设置页「导入」工作台(2026-10-02,照抄 PI-Desktop import-page 四面板;用户确认四类一次做完):
- 来源与去向:① 会话:Claude Code(
/.claude/projects)/ OpenCode(/.local/share/opencode/storage)/ Codex(/.codex/sessions,新+旧 jsonl 双格式)/ Pi(/.pi/agent/sessions)→ 本方任务+消息(task 表 + conversation_sync);② 模型:Claude Code settings.json / OpenCode(JSONC 容错)/ Codex config.toml(极简 TOML 子集)/ Pi models.json / CC Switch(SQLite 只读 + legacy JSON)→ provider_config.json(openai 兼容 apiFormat 别名映射;密钥不进公共候选只留 hasSecret);③ 技能:复用既有 Rust scan_external(claude-code/codex/codebuddy/agents)+ manageSkill install(复制安装,Windows 不做 symlink);④ MCP:复用既有 mcp_scan_external + updateMcpOps upsert(id 冲突加 -tool 后缀防覆盖)。 - 架构:解析逻辑照抄 PI 的 Electron main TS 到前端(src/lib/import/),IO 抽象成 ImportFs 接口(应用=Tauri fs 命令、测试=内存实现);纯解析模块显式 .ts 后缀可被 node --test 直跑。新增 Rust 命令:fs_read_file_ends(头/尾采样,Codex 大归档 头1MB/尾256KB + 最多 8MB 顺序续读找标题,对齐 PI #264)、import_env_lookup(只读白名单环境变量,解析 env:VAR/env_key 引用)、ccswitch_read_providers(rusqlite 只读打开)。
- 幂等:任务 id = import-
- (确定性),已存在即 skipped 不重读文件;provider 同 id 或 endpoint+协议+凭证一致 = skipped。消息 id m0..mN(restoreState 约定),不写 api_message part(外部无本方 API 原件,用量统计如实为无——No-Fallback);不绑定 providerId/modelId(外部模型命名空间不通,防发送解析失败)。 - UI:SettingsPage 新 tab「导入」(navImport,Download 图标,plugins 之后),ImportSection 四个常驻面板(切换不丢扫描结果),每面板 = 显式扫描 → 全选/分组/勾选工具条 → 导入所选 → toast(成功/跳过/失败计数)。i18n import* 38 键 zh/en(计数在组件内 replace,不用 t 插值)。
- E2E(真实桌面):种 Claude Code 固定装置 → 扫描出 1 会话 → 导入 → task=import-claude-code-e2e-session + conversation_load 4 行(user/assistant/tool+tool_args+tool_result/assistant,合成行过滤、工具配对 ✓)→ 重复导入任务仍 1 条消息仍 4 行(幂等 ✓)→ 模型/技能/MCP 面板扫描只读正常(技能发现真实 288 个)→ 固定装置与导入任务全部清理。
- 测试:import.test.mjs 13 用例(内存 ImportFs 固定装置:四家 scan+convert、新旧 Codex 格式、采样扫描、JSONC/TOML 解析、CC Switch、去重键、apiFormat 别名、分组)接 test:import;serializeEntry 从 conversationPool 导出供导入复用。tsc 0 + 217 测试全绿 + cargo 绿。
- 来源与去向:① 会话:Claude Code(
-
语音输入(STT)移植(2026-10-02,照抄 LiveAgent 三层架构;用户确认 5 家全抄/前端传参/识别中输入框只读):
- 链路:getUserMedia(回声消除/降噪/AGC;约束被拒降级)→ AudioContext+ScriptProcessor(4096) 跨块相位连续线性重采样 16kHz 单声道 → 100ms Int16 PCM 分片 + 能量 VAD(RMS 自适应基线)→ Tauri invoke stt_send_audio → Rust SttManager → 云厂商实时识别 WebSocket → partial/final 事件 app.emit("stt:event") → composer 临时文本回填。
- Rust(src-tauri/src/stt/,照抄 services/stt):mod.rs(SttManager 会话表 mpsc+oneshot 取消、音频序号连续性校验、错误脱密 sanitize_error+五分类 classify_test_error、settings_test_stt 连接测试=真连云发静音帧观测事件,百度需音频探针)+ 5 供应商(腾讯 HMAC-SHA1 签名 URL / 火山 v2 自定义二进制帧+gzip / 火山 Seed v3 bigmodel_async 头部鉴权 / 百度 START-FINISH 协议 / 阿里 DashScope run-task 指令)。唯一适配:配置由前端 stt_start/settings_test_stt 随参传入(桌面单用户,不走 LiveAgent 的 Rust 读设置库);seed_v3 默认 endpoint 注入保留在 Rust 侧。依赖新增 tokio-tungstenite(rustls)/futures-util/hmac/sha1/flate2/urlencoding/rustls(ring);tencent 测试的 reqwest::Url 换轻量查询解析(本方无 reqwest)。
- 前端(src/lib/stt/):audio.ts 照抄(SttStreamingResampler 相位跨块/SttPcmFifo 10s/SttAudioCapture 静音 3s 自停+设备断开检测);useComposerStt.ts 照抄状态机(先开麦缓冲后连云、10s 连接/5s final 超时保底保留文本、400ms 静音尾帧、切会话/隐藏自动中止);desktopSttTransport invoke+listen(open 随参传 config);settings.ts(settings.json "stt" 键,normalize/validateProvider 镜像 Rust 规则/runtimeConfig;clearSecrets 一次性清密钥语义保留)。
- textarea 临时文本(与 LiveAgent Lexical 的结构差异点):识别中 textarea readOnly,begin 快照草稿 → partial 渲染「草稿+空格+转写」→ final 写回 → cancel preserveLastText=false 还原快照;识别中禁发送/附件,麦克风按钮 active 态脉冲。设置页新增「语音输入」tab(SttSection:开关/供应商/表单/连接测试六态/clearSecrets)。
- ⚠ WebView2 麦克风权限(spike 结论):wry 只自动放行剪贴板权限,MICROPHONE 权限未处理 → getUserMedia 挂起。解法=WebView2 参数 --use-fake-ui-for-media-stream(自动放行媒体权限弹窗,真实麦克风照用)。E2E 自动化另加 --use-fake-device-for-media-stream(假音源)。生产接线:tauri.conf.json windows.additionalBrowserArgs 需设为 wry 默认+该 flag(尚未写入 conf——当前 dev 靠环境变量)。
- E2E(真实桌面):假密钥种子配置 → 点麦克风 → stt:event error(火山真实端点 HTTP 400 拒假密钥,protocol_failed 分类)+closed → 0.7s 内 toast 报真实供应商错误 → 状态复位;设置页连接测试同样拿到分类结果;任务/设置清理复位。真听感(按住说话出字)待用户戴真实麦克风+真实密钥验收。
- 测试:Rust 16 用例(四家协议/帧编解码/签名 URL/错误分类/URL 脱敏/rustls 安装);tsc 0 + 前端 217 绿。
-
提示词增强(2026-10-02,照抄 PI-Desktop ADR-0121 全量;用户确认):
- 交互:composer 工具条 ✨(Sparkles,麦克风左边):草稿空/以 / 开头/语音识别中/增强中 → 禁用;点击一次性非流式补全(thinkingLevel 默认 off,从不继承会话等级)→ 替换输入框文本(光标移末尾)+ 原文进撤销态(Undo2 按钮一键还原);提交/切任务使撤销与迟到结果失效(requestToken 计数守卫);失败 toast 报真实错误。60s 硬超时(AbortController + race;abort 尽力而为、race 释放调用方;超时不换模型重试对齐 PI)。
- 模板(templates.ts,照抄 shared/prompt-enhancement.ts 原文):内置系统提示词固定不可覆盖;用户模板必须含 {{draft}}、≤8000 字符,含中英 few-shot(「帮我搞一下那个东西」例子教导模型追问缺失信息);resolvePromptEnhancementUserTemplate 第二道防线(customTemplate 开且合法才生效否则回退默认);stripEnhancementDecorations 剥一层匹配包裹引号 + 增强:/Enhanced: 等前缀标签。
- 执行(enhance.ts):渲染层 one-shot——buildModel + getStreamFnForApi.streamSimple(与 runAgentTurn 同传输层),normalizeContext({systemPrompt, messages:[user]}), 聚合 text_delta、error 事件按 reason=aborted/errorMessage 分流、result() stopReason 复核 + 文本块兜底聚合;空结果抛 PROMPT_ENHANCEMENT_EMPTY。与 PI 的差异:PI 在 Electron main 跑 agent-runtime 并带限流/瞬态重试梯子;本方渲染层直连,失败如实报错(偏差已记录)。模型:默认跟随 composer;设置可钉住(providerId/modelId),钉住不可用回退 composer。
- 设置:settings.json "promptEnhancement" 键(customTemplate/userTemplate/providerId/modelId/thinkingLevel);设置页新「提示词增强」tab(模板编辑器含 {{draft}} 校验/插入占位符/恢复默认 + 保存时模板与默认完全一致自动清空覆盖对齐 PI + 钉住模型下拉(全部已启用服务商模型,孤儿钉住提示回退)+ 推理等级行)。
- E2E(真实模型链路):草稿「帮我搞一下那个东西」→ 点 ✨ → 增强中转圈 → 真实模型返回具体化提示词(对象/期望结果/验收标准——few-shot 教导行为)→ 撤销按钮出现 → 撤回还原原文 → 斜杠命令禁用 ✓。tsc 0 + 222 前端测试全绿(新增 5 用例:模板渲染/校验/回退/清洗)。
-
运行中后台工作徽标 + 管理浮层(2026-10-02,对齐 ZCode ConversationBackgroundWorkTrigger + ConversationStatusPanel 最小移植;用户确认「徽标+浮层」范围):
- UI:composer 工具条审批模式下拉右侧新 BackgroundWorkTrigger——running 的终端/子代理按类型显示「图标+计数」(SquareTerminal/Bot),全零整个隐藏;点击展开浮层(w-80,absolute bottom-full):分区列运行项(图标+描述+尾部输出摘要+秒针时长+停止按钮),终端行点击跳终端面板,子代理行点击跳右侧面板 subagents 视图;点外部关闭。
- 数据层(lib/chat/backgroundWork.ts):两个视图共用同一真值——子代理 = subagentRegistry running 记录(前台+后台都算,stoppable=background;模块级订阅一次 subscribeSubagentRuns 归并进联合 state,不放 hook 体(渲染期副作用违纪));终端 = bg_list() running 任务(无推送事件 → 有订阅者才启的 1s 轮询、连续失败 3 次自停、内容比对去重才 notify)。useBackgroundWork 单一 useSyncExternalStore。
- 范围裁剪(相对 ZCode):不做 workflow 类目(无对应物)、不做常驻 mini 胶囊(v1 只点击展开)、终端行点击直接跳已有终端面板(不做输出侧栏)、不做侧栏任务行摘要。
- 子代理停止语义(实测确认):stopRun → AbortController.abort() → driveRun 的 runTurn D1 桥接收束 → finishRun;前台运行无独立 controller 如实不可停(返回 false → toast 报错)。E2E 种子直接 registerRun 的记录没有驱动者,abort 后永不收束——这是种子假象不是 bug;真实模型 agent 工具链路才有 driveRun 收束。
- E2E(真实桌面):bg_spawn ping → 徽标「🖥1」→ 浮层终端区+taskId+停止 → 点停止 → 徽标隐藏 ✓;种 running 子代理 → 徽标「🖥1🤖1」双图标计数 ✓、浮层两分区+描述+时长 ✓(秒针/双计数/停止链路全过)。
- ⚠️ 复发坑:Vite 模块图缓存又吞了一次 HMR(磁盘有 trigger JSX、served transform 没有,Page.reload 无效)——重启 tauri dev 栈解决。与弹层 keyframes 那次同款:bundle 新旧必须 curl served 内容确认。
- i18n bgWork* 6 键 zh/en。tsc 0 + 222 前端测试全绿(无新增测试——纯 UI/IO 胶水层)。
-
远程访问(局域网 Web 型手机远控)批 1-4 ✅(2026-10-02,用户确认中档方案):
- 架构(对齐 ZCode web-remote-replayable + LiveAgent 审批回环):桌面内置 axum 服务(tokio,0.0.0.0:7777 默认关)→ 手机浏览器 token 鉴权 → WS RPC 五白名单 op(list_tasks/subscribe/send/approve/stop)→ 桌面为唯一权威,会话运行时在 webview,Rust↔webview 用 Tauri 事件 + 专用命令桥(remote-bridge:need-snapshot/need-state/send/resolve/stop + remote_bridge_answer_* 命令)。
- 鉴权(PI 纪律):token 随机 UUID、Rust 只存 SHA-256(明文 kv 供设置页展示);?token=/Bearer/Cookie 三通道;连续失败 5 次拉黑 IP 5 分钟;所有路由含静态页一律过鉴权;重置 token 踢光客户端。
- UI:设置页「远程访问」tab(开关/端口/完整 URL+二维码 qrcode npm/复制/重置 token/已连接设备数/安全文案,remote-server:status 事件实时刷新);手机单页 remote_page.html(include_str! 内联零构建):任务列表→会话视图(user/assistant/tool 消息卡)→底部输入条(流式中变停止键)→审批卡置顶(允许/拒绝)。
- webview 桥(lib/remote/remoteBridge.ts,main.tsx 挂载即启动):手机发送经 App 暴露的 window.__reinagentRemoteSend(sendNow 同一 buildTurnOptions 闭包——选项与本地完全一致);审批走 resolveApproval(与本地审批卡同源);状态推送订阅池 subscribeTask → remote_bridge_state(200ms 节流+revision 单调,乱序丢弃)。
- 协议坑(E2E 抓出):serde enum 的 rename_all=camelCase 只作用于 tag 不作用于 variant 字段——reqId 反序列化报 missing field req_id,需 rename_all_fields=camelCase(serde 1.0.186+);修复后 tag 又变大写(Hello)坏客户端解析 → 最终三件套:tag=op + rename_all=camelCase + rename_all_fields=camelCase。加单测 subscribe_parses_camel_case_req_id 守护。
- 复发坑:Vite 模块图缓存吞 HMR 第二次发作(served transform 缺新 JSX);Rust 级改动必须重启 dev 栈,bundle 新旧以 curl served 内容为准。
- E2E(真实桌面全链路):手机模拟 WS 客户端 → hello(server_time)→ snapshot 含种子任务 ✓ → subscribe 收 state(revision=1)✓ → send「来自手机的问候」→ 桌面时间线收到 user 消息 ✓ → approve/stop 事件桥 ✓;HTTP 鉴权:无 token/错 token 401、对 token 200 ✓。
- 待办(后续批次):增量镜像日志(LiveAgent 型断线续传)、云端中继、终端/文件远程、多设备管理页。
- tsc 0 + 前端 222 测试 + Rust 173 测试全绿。
-
远程页面 Markdown 渲染 ✅(2026-10-02,参考 ZCode Streamdown 策略的零构建适配):
- ZCode 手机端与桌面共用 Streamdown(React 体系,shiki 高亮 + rehype-harden + remend 流式补全);本方手机页是零构建单 HTML,采用「内联 marked.umd.js + purify.min.js」(约 75KB,src-tauri/remote_assets/,include_str! + 占位符注入 build_remote_page() 一次性组装缓存)。
- 照抄 ZCode 的三个策略:① 转义优先——DOMPurify.sanitize 白名单 + ALLOWED_URI_REGEXP 只放行 https?:/mailto:/#(拦 javascript:/data:);② 流式补全——parse 前数 ``` 围栏奇偶,未闭合补临时闭合(remend 思路),半截代码块不吞后半条消息;③ 链接加固——渲染后 querySelectorAll(a) 补 target=_blank + noopener noreferrer。用户消息保持纯文本不渲染。
- 样式:assistant 消息内 h1-h4/列表/行内代码/pre 代码块(横向滚动+深底)/表格(块级横向滚动)/引用/hr 全套语义变量样式。
- E2E:served 页 92KB 含两库与渲染函数 ✓;桌面 webview iframe 实际执行页面——window.marked.parse 与 DOMPurify.sanitize 均为函数 ✓、任务列表视图正常 ✓。
-
11148 孤儿 toolResult 修复(2026-10-02,用户报「tool calls and tool results do not match」400):
- 根因实锤:某任务持久化历史头部(seq 0/1/2)残留 3 条孤儿 toolResult——其 toolCall 块被更早的压缩/截断删除而结果未随之删除;此后每次请求重放这 3 条 → 上游 11148「tool calls and tool results do not match」整请求 400 并建议开新会话。远程页 3 任务/桌面 2 任务是同任务侧栏「项目组外任务不渲染」缺陷(已修:任务区收录项目组未覆盖的任务)。
- 治本(toApiMessages 发送兜底):walk 时维护 declaredCalls 集合(assistant apiMessage 的 toolCall 块 id);tool 条目的 toolCallId 配不上任何前序声明 → 该 toolResult 不上送(console.warn 计数)。正常配对完整保留(单测守护:孤儿剪除 + 正常配对保留两用例)。B-2 尾部合成结果逻辑不变。
- 治标(本任务数据修复):一次性脚本剪除头部 3 条孤儿行(conversation_sync 重写,485→482 行),复验孤儿数 0。注意:任务若在桌面打开着,池内存态可能在后续编辑时重写回孤儿——但发送已被治本兜底,无害。
- 测试:chat 套件 132→134(新增孤儿剪除/正常配对保留两用例)。
-
提示词结构变更:身份/目录约定段迁移 ✅(2026-10-03 用户定稿):
- 改动:
DEFAULT_SYSTEM_PROMPT移除首行「身份与目录约定」段(公共提示词不再含身份句——通用助手/翻译官/文档写手/所有自定义助手一律不注入);目录约定(.ReinAgent/.temp与.ReinAgent/持久资料规则)并入两处:①「代码专家」助手人设正文(身份句 + 目录约定 + 4 条偏好,用户提供定稿文本);② Environment 段新增- Workspace conventions:行(仅工作区根存在时注入,完整原句)。 - 影响链:runAgentTurn(助手人设注入 + buildEnvironmentSection)、App.tsx 系统提示词导出(follows DEFAULT + env,自动一致)、自动化任务与 useConversation 的旧路径引用同一常量自动跟随。
- E2E 四点:默认提示词不含身份句/不含 temp 规则、以 # Communication 开头 ✓;Environment 含 Workspace conventions 完整句 ✓;coder 人设 = 定稿合并文本 ✓;general/writer/translator 均不含身份段与 temp 规则 ✓。tsc 0 + 相关套件绿(providers 3 败为存量 .js 尾缀问题)。
- 改动:
-
助手人设注入格式与通用助手定稿 ✅(2026-10-03 用户定稿):
- 只注入正文:
runAgentTurn与 App 的「系统提示词」导出统一改为<人设正文>\n\n---\n\n前缀——不再有# Assistant Persona: <名称>标题行与描述行(名称/描述不进提示词)。E2E 实证:导出内容首行即 coder 正文,Assistant Persona与描述均不存在。 - 通用助手不再是空人设:
general现在带生活/工作通用人设(匹配用户意图与层次、具体可执行、必要时才追问、跟随用户语言);注入逻辑同步移除assistantId !== "general"跳过(改为「正文为空才跳过」)。测试断言同步更新(general 有正文)。 - 影响链:runAgentTurn 注入、App 系统提示词导出(activePersonaText)、上下文面板该行列数与内容一致。tsc 0 + 全套绿(providers 3 败为存量 .js 尾缀问题)。
- 只注入正文:
-
上下文类别面板跟随刷新 ✅(2026-10-03 用户要求):
- 需求:打开「上下文容量 · 系统提示词」面板时,切换助手(任务内 chip 或全局默认变化)后提示词面板要跟着更新。
- 实现:
codeViewerSource的 text 形态新增liveCategory?: string(ContextUsageIndicator 点开时写入类别 key,如 systemPrompt);App 监听contextUsage快照 + liveCategory,类别内容变化时用最新buildContent()原位重开同一面板(内容相同不动,避免无谓重开)。不止助手——技能/MCP 启停等类别内容变化同样跟随。 - E2E 四点:面板显示 coder 正文 ✓ → 任务切 translator → 面板自动变 translator 正文且 coder 正文消失 ✓ → 切回 coder 又恢复 ✓。tsc 0 + 全套绿。
-
顶栏分支切换器(新建任务可选分支)✅(2026-10-03,ZCode GitBranchSwitcher 移植,CDP E2E):
- 需求:新建任务(草稿态)时可选 git 分支;入口在顶栏助手 chip 右侧(草稿态与已有任务都显示);被脏工作区阻塞时照抄 ZCode「切换助手」弹窗(checkpoint 提交后自动重试);下拉底部保留「Git 图谱」入口(接已有 Git 面板)。
- 语义(对齐 ZCode,无任务绑定):选择即
git switch --no-guess立即检出,是工作区全局操作——不写任务/会话字段、无 worktree 隔离,同仓库多任务最后切换胜出。创建分支=基于当前 HEADgit switch --no-guess -c <name>,名字用git check-ref-format --branch校验(支持中文等全部 git 合法字符)。 - Rust(git_panel.rs 新增 6 命令,旧 4 命令保留给 GitPanel):
git_branch_list_v2(for-each-ref refs/heads 带 upstream/hash/committerdate;排序当前置顶→时间倒序→名称;branch --show-current空=detached);git_branch_switch(完整前置检查链:conflicts-present【porcelain UU/AA/DD 等】→operation-in-progress【rev-parse --git-path 7 标记位】→check-ref-format 校验→同分支 no-op;失败不抛异常返回 ok=false+结构化 issues);git_numstat(diff+diff --cached 行数合并,binary 记 0);git_identity(config user.name/email,exit 1=None);git_stage(add --)/ git_commit(commit -m,空信息拒绝)。stderr 归因器 parse_branch_mutation_issues 对齐 ZCode gitCliHelpers:tracked/untracked-overwrite(缩进路径提取+反斜杠归一)/already-exists/invalid-reference/other-worktree/resolve-index/7 种 operation-in-progress 文案/unknown(detail 保留 git 原文)。单测 9→7 例新增(归因码全表/路径提取边界/冲突码/numstat 合并)。 - 前端:
lib/git/api.ts扩展 v2 类型;lib/git/branchSwitcherLogic.ts纯逻辑模块(触发器文案/搜索过滤/issue→i18n key/成功 toast 选择/路径截断 2+剩余/受影响文件选择/checkpoint 自动信息chore: checkpoint before switching to <branch>/身份守卫——identity 读不到时放行由 git 兜底)+ 7 例 node --test(test:git);components/git/BranchSwitcher.tsx(lw Popover+Dialog;搜索框 Enter=点首个过滤项;当前分支 ✓+未提交数提示;每次展开重拉分支列表防旧快照「切了不变」;窗口 focus/reinagent-git-refresh 事件同步触发器标签;非 git 仓库/无工作区/后台探测失败都不渲染入口)。切换助手两步弹窗:blocked(警告框+受影响文件 +/- 列表)→commit(当前分支/更改汇总/身份缺失警告/信息 textarea 留空自动生成/错误行内展示)→stage 全部变更文件+issue 路径→commit→自动重试 switch。 - 接线:App 顶栏 AssistantChip 后
<BranchSwitcher workspacePath={effectiveWorkspaceRoot}/>;「Git 图谱」=openCodeViewer git 面板;GitPanel 监听reinagent-git-refresh窗口事件静默刷新(切分支后 ≤1s 同步,不再等 10s 轮询);广播只在 didChange/created 时发(no-op 不打扰)。i18n gitBranch* 键 43 个×2 locale({name}/{count}/{paths}/{error} 占位由本地 fmt() 替换——t() 无插值)。
-
Git 面板提交 + 一键初始化仓库 ✅(2026-10-04,用户三点拍板:init 固定 -b main / ✨AI 提交信息首版就带 / 要推送按钮;CDP E2E 全链路实测):
- Rust(git_panel.rs +4 命令 + 2 处修正):
git_init(git init -b main,已是有仓库时 git 原生报错如实上抛);git_push(有 upstream 裸git push,否则--set-upstream origin <branch>;detached 拒绝;失败不抛异常返回 ok=false+stderr 原文);git_diff_patch(staged/unstaged 整仓 diff 原文,✨ 输入);git_numstat拆{staged, unstaged}两组(⚠️ untracked 不进 numstat——路径清单必须以 git_status 为准,numstat 只供行数,untracked 记 0/0);git_status 条目补indexCode/worktreeCode(原 code 被 trim 无法区分M/M)+ 无提交仓库分支名剥 "No commits yet on " 前缀;git_log 对全新仓库(无提交)返回空历史而非错误("does not have any commits yet"/"bad default revision"/"ambiguous argument 'HEAD'",对齐 ZCode getCommitGraph——否则 init 后开面板整块进错误态,E2E 抓出)。 - 顶栏 init 按钮:BranchSwitcher 的 repoState 三态(checking/repo/nonRepo)——非仓库时原位渲染「初始化 Git 仓库」按钮(git_init → toast → reinagent-git-refresh → 原位变回切换器)。
- 系统文件管理器入口(2026-10-04,用户需求):BranchSwitcher 右侧新增彩色文件夹按钮(
components/icons/FileManagerIcon.tsx,原创 Win11 资源管理器风格 SVG:深黄背夹+黄主体+左下蓝色前袋)→ 新 Rust 命令fs_cmd::open_in_file_manager(path?)(Windows explorer / Linux xdg-open / macOS open;path 为空开默认工作区~/.ReinAgent/DefaultProject;目录不存在/启动失败如实 Err→前端 toast)。按钮恒显(非 git 场景紧挨 init 按钮);web 模式隐藏("__TAURI_INTERNALS__" in window探测)。区别于右侧「文件管理器」按钮(应用内文件面板)。 - GitPanel:空态从一行灰字升级为 init 按钮+说明;变更列表拆「已暂存/未暂存」两组(porcelain XY:staged=indexCode≠' ' 且非 ??;unstaged=?? 或 worktreeCode≠' ',同文件可同现两组);变更区头部「提交…」按钮 → CommitDialog。
- CommitDialog(components/git/CommitDialog.tsx,ZCode GitCommitDialog 移植裁剪):分支行+/−汇总、信息 textarea + ✨(
lib/git/commitMessage.ts:diff 截断 24k → completeOneShotText(复用 promptEnhancement 通道)→ 清洗(剥围栏/引号、限 600 字);模型解析链=钉住增强模型→活动任务模型→设置默认→首个启用供应商,全落空 toast gitCommitNoModel);「包含未暂存更改」勾选(无已暂存时默认勾上避免空选死路;提交时先 gitStage 未暂存路径);身份缺失警告条(复用 gitIdentity);「提交」/「提交并推送」——推送失败但提交成功时 toast.warning 分开说并带 git 原文。 - E2E(DefaultProject 临时仓库全真命令):init 按钮→变回切换器→Rust 验证 current=main ✓;分组渲染 ✓;对话框默认态(有暂存时不勾、信息空禁用提交)✓;✨ 真实模型生成 "feat: add a-staged.txt" ✓;提交后树干净+弹窗关+log 有 ✓;提交并推送无远程→提交成功+推送失败原文 toast ✓;本地 bare 远程配置后 git_push 成功(首推自动 --set-upstream)✓。回归:tsc 0 / 前端绿 / Rust 180。
- Rust(git_panel.rs +4 命令 + 2 处修正):
-
git add pathspec 修复(2026-10-04 用户报「fatal: pathspec 'GGG.CC/.workbuddy/memory/MEMORY.md' did not match any files」):
- 两个根因:① status 快照竞态——提交对话框打开时的 git_status 路径清单,到点提交时文件已被其它工具(WorkBuddy 的记忆文件会被随时改写/删除)删掉,整批
git add被 pathspec 无匹配炸掉;② cwd≠仓库根时 status 输出恒为仓库根相对路径,git add的 pathspec 却按 cwd 解析(工作区只是仓库子目录时全部错配)。 - 修复(git_stage):路径锚定
:(top,literal)(top=按仓库根解析;literal=关 glob 防文件名[]*?被当通配符);整批失败且错误含 pathspec/did not match 时逐个重试并跳过消失的路径,其余照常 stage,返回GitStageResponse{skipped}(其它错误照常上抛)。前端 CommitDialog 对 skipped 非空 toast.warning 如实列出(「N 个文件已不存在,已跳过:…」),不阻塞提交。单测 stage_skips_vanished_paths_and_stages_rest(真临时仓库)。 - E2E 复现用户场景 ✓:对话框打开后删掉 ghost.txt → 提交成功 + toast「1 个文件已不存在,已跳过:ghost.txt」。
- E2E 基建教训(代价:误向用户 PI-Desktop 仓库提交了一个真实 commit,已当场 reset --mixed 撤销、文件原样、未推送):①
workspaceProject = activeTask ? activeTask.project : selectedProject——草稿态 selectedProject 会被 hydrate 的活动任务覆盖,E2E 钉工作区必须走任务路径 → 新增window.__activateProjectTask(project)钩子(createTask+setActiveTaskId 真实 store 动作);②window.__newTask包装层丢弃参数(__newTask = () => handleNewTaskRef.current()),传路径无效;③ Git 面板是打开时快照,切工作区后须关闭重开;④ 侧栏任务组折叠时行不在 DOM,删除任务走真实鼠标链(hover 显示垃圾桶 → 确认)。回归:tsc 0 / Rust 181。
- 两个根因:① status 快照竞态——提交对话框打开时的 git_status 路径清单,到点提交时文件已被其它工具(WorkBuddy 的记忆文件会被随时改写/删除)删掉,整批
-
Git 面板变更内联 diff ✅(2026-10-04,用户需求「未暂存部分照 ZCode 显示具体文件、点击看改动」,CDP E2E 全过):
- ZCode 结论(GitPane/GitPaneChangeCard):文件行=类型图标(按扩展名映射)+文件名+目录+N -N(numstat)+chevron,手风琴单开;点击懒加载单文件 diff(缓存
sourceId:path)内联渲染;untracked 不在git diff里→repo 层合成最小 patch(--- /dev/null++++ b/path+全+行+补\ No newline);单文件命令=unstagedgit diff --no-ext-diff --no-color --binary -- <path>/staged 加--cached。点击只有展开 diff(无打开文件);右键菜单(复制路径等)首版不做。 - Rust:
git_diff_file {cwd,path,staged,untracked}(tracked 走 git diff;untracked 读文件合成 patch——二进制(含 0 字节)→binary、超 1MB→truncated、读不到→unavailable);git_numstat追加 untracked 行数统计(ls-files --others --exclude-standard读文件数行,二进制/超 1MB 记 0,上限 200 个防巨仓拖慢——对齐 ZCode buildUntrackedStats)。单测 12 例(合成 patch 无尾换行标记+binary)。 - 前端:GitPanel 变更行改造(FileDisplayInline 复用 preview/lib 的移植件;+N -N 从 numstat 合并;untracked 目录行隐藏计数);手风琴
expandedKey单开+diffByKey缓存(组:路径);内联渲染用 HighlightedLightweightDiffPreview(右侧 patch 预览同款——@pierre PatchDiff 会静默空白 Shadow DOM,面板统一走轻量路径,行渲染为色条增删无 +/- 文本前缀,---/+++头两行也被剥);availability≠patch 显示占位(binary/truncated/unavailable);原「点击打开右侧文件预览」移除。i18n gitDiff* 4 键。 - E2E 抓出的真 bug(已修):refresh(10s 轮询/手动)整体清空
diffByKey后,展开中的行永远卡「加载改动中」——新增 effect:清缓存后对expandedKey立即重建 diff(对齐 ZCode revision 清缓存后重载展开 diff)。 - E2E(DefaultProject,repo 由外部 bash 造数据字节精确——应用内
bash -c "printf \"x\n\""经多层转义会把\n吃成字面量):4 行(staged/unstaged 计数、untracked +行数、binary 无计数)✓;点击展开 diff(@@ 头+改动行)✓;手风琴互斥 ✓;untracked 合成(新增行+No newline 标记)✓;binary 占位 ✓。⚠️ 断言注意:轻量预览无 +/- 文本(色条)、diff 头两行被剥、DiffViewer 在 Shadow DOM(textContent 抓不到)。
- ZCode 结论(GitPane/GitPaneChangeCard):文件行=类型图标(按扩展名映射)+文件名+目录+N -N(numstat)+chevron,手风琴单开;点击懒加载单文件 diff(缓存
-
「git diff 失败:Not a git repository + 整屏 usage」修复(2026-10-04 用户报错):
- 根因:仓库外运行
git diff(numstat/diff_file 等)时 git 的文案是 "Not a git repository"(大写 N)+完整 usage 帮助;run_git的非仓库识别contains("not a git repository")大小写敏感漏判(status/log 走的是小写 "fatal: not a git repository" 所以一直没暴露),整屏帮助被当错误抛给前端,Git 面板在非仓库工作区不显示 init 空态而是巨报错。 - 修复:
run_git归一化改to_lowercase().contains(...)(统一折叠为结构化 GIT_NOT_REPO);is_not_repo_error兜底同样大小写不敏感。单测 not_repo_detection_is_case_insensitive(用户报错原文/小写 fatal/空仓库区分三断言)。 - 验证:非仓库工作区开 Git 面板 = init 空态 ✓(无 usage 报错)。回归 tsc 0 / Rust 183。前端无改动。
- 根因:仓库外运行
-
变更行样式对齐 ZCode(2026-10-04 用户五点反馈):① 去掉行首 porcelain 状态码(
??/M)徽标;② 字号升至 text-sm(14px,对齐 ZCode 行文字);③ 布局改手写行(弃 FileDisplayInline 的堆叠形态):图标 | 文件名(flex-1 截断优先)| 目录后缀(text-xs 暗色,resolveFileDisplayDescriptor取 dirname,根文件无后缀)| +/- | chevron;④git_status加-uall:untracked 目录展开成具体文件逐行列出(不再折叠.dir/目录行;行数与 numstat 的 untracked 统计天然对齐);E2E 验证子目录行mod.ts sub/deep/ +1 -0✓。 -
temp 目录改名
.temp+ 自动 git 隐形 ✅(2026-10-04 用户定稿,E2E 实证):- 改名:工作区一次性产物目录
.ReinAgent/temp/→.ReinAgent/.temp/,全代码统一(Rust fs_cmd 清理白名单/粘贴落盘 pasted 路径;前端 turnActivityisReinAgentTempPath过滤、摘要卡注释、Composer 注释;提示词两处——Environment Workspace conventions 行 + 代码专家人设正文;PROMPTS.md 同步)。 - git 隐形(用户拍板方案:
.ReinAgent/.gitignore而非 info/exclude):创建.ReinAgent/.temp时幂等补建.ReinAgent/.gitignore(内容.temp/+.gitignore自忽略——git 允许自忽略且读取不受影响),使.ReinAgent/.temp与.gitignore自身对用户仓库完全隐形。触发挂钩三处创建点:fs_write_file(agent 写 .ReinAgent 下任意深度文件,路径组件精确匹配 ".ReinAgent")、fs_import_pasted_file、fs_create_dir;存在不覆盖(用户改过内容不回滚)。不碰.git、无需仓库根定位、跨仓库跟随工作区目录。 - 限制(不自动处理):已跟踪的历史 temp 文件 ignore 无效(照常显示为真变更);用户手删
.gitignore会在下次写 .ReinAgent 时重建。 - E2E:app 内
fs_write_file写.ReinAgent/.temp/scratch.js→.gitignore自动生成(内容.temp/+.gitignore)→git status -uall无任何.ReinAgent条目、git check-ignore -v实证两条规则命中 ✓。单测 2 例(幂等不覆盖/任意深度命中+.ReinAgentFoo 不触发)。回归 tsc 0 / 前端绿 / Rust 185。
- 改名:工作区一次性产物目录
-
任务完成系统通知修复 ✅(2026-10-04 用户报「设置已开声音提醒但完成无通知无声」,Windows Toast 实证入列):
- 根因(tauri-plugin-notification 源码级):其 desktop 实现在 Windows 仅当 exe 不在
target/debug|release(安装版)时才设置 AppUserModelID——dev/本地构建的 Toast 无 AUMID 被 Windows 静默丢弃(API 返回成功、通知中心无内容);桌面端permission_state硬编码 Granted,前端isPermissionGranted()返回 false 是 JS 转义假象。声音链路本身通(AudioContext OK),但 suspended 起始态未 resume。 - 修复:新 Rust 命令
notify_send {title, body}(git_panel.rs)绕过插件直发——Windows 用tauri-winrt-notification(插件同款底层)显式携带 AUMIDcom.reinagent.app;Linux 用notify-rust(org.freedesktop.Notifications)——dev 与安装版行为一致。前端sendSystemNotification改走该命令(去掉插件的假权限检查);playNotificationSound补ctx.resume()(Windows suspended 起始态)。依赖按平台挂进 Cargo.toml 既有 target 段(windows: tauri-winrt-notification 0.8 / linux: notify-rust 4)。⚠️ 顺手修:Cargo.toml 首次插入把既有 target 段劈成重复键(duplicate key),合并进既有段才对。⚠️ AUMID 已随 identifier 迁移(2026-10-05):com.reinagent.app→com.web3claw.reinagent(bundle identifier 结尾.app在 macOS 与应用包扩展名冲突,CLI 警告;git_panel.rs 两处 APP_ID 常量 + Windows dev 注册表路径同步改),历史段落中的旧 AUMID 均为当时的既成事实,验证时以新 AUMID 为准。 - 终态触发条件(未改,复核确认):任务进终态 +(非当前任务或窗口不可见)+ 同轮去重(runKey)+ 审批挂起不算终态——正常任务在后台完成必有通知;用户正盯着看的任务完成不打扰(对齐 ZCode)。
- E2E 实证:
notify_send直发 + taskNotifications 模块全链发送成功;ToastNotificationManager::History.GetHistory('com.reinagent.app')返回 3 条且正文逐字匹配(真进通知中心,非 API 假成功);AudioContext resume 后 state=running。回归 tsc 0 / 前端绿 / Rust 185。
- 根因(tauri-plugin-notification 源码级):其 desktop 实现在 Windows 仅当 exe 不在
-
通知/提示音行为定稿 ✅(2026-10-04 用户两轮拍板,Windows 真机验证):
- 声音:任务终态无条件播放(不看焦点/是否当前任务);WebAudio 三音上行(A5→C#6→E6,0.45s)+ 缓入缓出包络;共享 AudioContext(suspended 起始 resume 后再播);gain 峰值 0.38(用户定档)——旧 0.06 双音在真机喇叭上几乎不可闻(管线通但「听不见」,教训:音量需实测电平而非只看链路)。
- 系统通知:仅
document.hasFocus() === false(失焦/最小化/切窗口)时弹;有焦点只有声音。任务名 + 结果摘要 120 字;同轮去重。 - 点击通知跳转:Rust
notify_send增task_id参数——Windows Toaston_activated/ Linux freedesktopdefaultaction +wait_for_action("default")→emit_notify_activate(聚焦主窗口 show+unminimize+set_focus + emit "notify-activate")→ 前端 listen → setActiveTaskId +followSignal强制贴底(无视滚动记忆);任务已删则仅聚焦窗口。 - Windows dev 横幅缺失根因(重点坑):未注册 AUMID 的应用 Toast 只进通知中心不弹横幅(API 成功、History 有记录、但无横幅)。修复:setup 时
ensure_windows_aumid_registered()——仅 dev 版(exe 在 target\debug|release)用 PowerShell 幂等写HKCU:\Software\Classes\AppUserModelId\com.reinagent.app(DisplayName+IconUri);安装版由 Tauri NSIS 安装器注册。验证铁律:注册后 Toast 横幅 GDI 截屏实证弹出了;通知中心 History.GetHistory 数条数+读正文。 - 回归:tsc 0 / 前端绿 / Rust 185。测试任务与残留 E2E 任务已清。
-
构建警告清理 ✅(2026-10-04 用户要求,三类全处理):
- ② INEFFECTIVE_DYNAMIC_IMPORT(8 处):同模块既被静态又被动态 import → 动态形式被 Rolldown 忽略(纯代码异味)。全部改静态:App.tsx(event listen×2 / hooksRuntime×5 / systemInfo / skills/index 命名空间)、PluginsSection(skills invalidate)、runAgentTurn(hooksRuntime PreToolUse×2 + lifecycle fireLifecycleHook + systemInfo Promise.all 解构)、hooksRuntime(pluginRegistry×2)、skills/index(pluginRegistry)、import/scan(importers)、FilesPanel + AgentSubagentsPage(opener reveal)、conversationPool + subagentDefinitions(model-provider/types loadProvidersConfigFromDisk)。行为零变化(打包器本就忽略),tsc 0 + chat/git/agent 套件绿。
- ③ STATIC_VCRUNTIME deprecation:
build.windows.staticVCRuntime: true写入 tauri.conf.json(新官方方式);src-tauri/.cargo/config.toml的 rustflags+crt-static保留(真正的代码生成标志,两者配合)。release 重编后二进制不含 VCRUNTIME140/MSVCP140(静态 CRT 保持),tauri-build 弃用警告消失(build.rs 强制重跑验证)。 - ① chunk 大小告警:主 chunk 3.1MB(pi-ai/shiki 级依赖集中)——桌面端无网络加载瓶颈不做代码拆分;
build.chunkSizeWarningLimit: 3500(先试 1000 仍超,按实测调)。vite build 输出零警告。
-
dev 运行持续闪 cmd 窗修复 ✅(2026-10-04 用户报,全仓 spawn 审计):
- 根因:GUI 进程在 Windows 上 spawn 控制台程序(git/powershell/cmd)时,系统会为无窗口子进程分配可见控制台——
git_panel.rs的 git 调用(Git 面板 10s 轮询×3 命令 + 切分支/提交/identity/check-ref-format)与 AUMID 注册的 powershell 全部裸Command::new,构成持续闪烁。terminal/pty、bg_process、fs_execute、hooks、mcp、skills 等旧代码原本就带 CREATE_NO_WINDOW,本批新写的 git/通知代码漏了。 - 修复:git_panel.rs 顶部加
no_window()helper(Windows 下挂CREATE_NO_WINDOW0x0800_0000,非 Windows 无操作),全部 spawn 统一走它;全仓审计补齐 3 处同款裸 spawn:system_info.rs(cmd /c ver,启动即调)、plugins.rs(插件市场 git clone)、fs_cmd.rs shell_detect(where/which,设置页触发)。 - 教训:Windows GUI 应用 spawn 控制台程序必须 CREATE_NO_WINDOW——新代码引入
Command::new时默认要带(尤其会周期轮询的)。
- 根因:GUI 进程在 Windows 上 spawn 控制台程序(git/powershell/cmd)时,系统会为无窗口子进程分配可见控制台——
-
写死数据 / 装饰性设置 / 失效开关审计(2026-10-05,复核修订版):
- 来源:
docs/FIXPLAN.md(三路并行扫描 + 人工逐条复核)。本轮已对初稿逐条复核修正(初稿成文早于f5a47cf/f003127两提交,含过时项与错路径)。开工一律以 FIXPLAN.md 现版为准。 - 已修(不再排期):F1 max_tokens 注入(runAgentTurn.ts:286-291
wrapStreamWithProxiedFetch经resolveMaxTokens注入,未知发 32000 兜底);F2 buildModel fail-fast 抛错(modelFactory.ts:92-129);F3 titleGenerator 改由apiFormat判定协议 + 调用点传会话 provider(titleGenerator.ts:89-110);F15 modelFactory 变更历史注释保留。 - 已修(2026-10-05 批次 0 高危,提交 1d0e563):F17 草稿首轮审批门绕过(App.tsx
?? "full"改回退全局默认审批模式;buildTurnOptions新增 taskId 参数、发送瞬间从 store 实时解析任务审批模式/助手,sendNow/editResend/retry/远程发送均显式传 taskId,消除闭包冻结);F18 provider_config.json 读取失败即覆盖写盘(types.ts 区分「文件不存在」与「损坏」——损坏抛错且绝不写盘,设置页新增错误横幅 + persistProviders 守卫,调用方不再静默吞错);F19 mcp_servers.json 损坏→空表静默回写(mcp.rs load_servers 返回 Result 透传错误 + hubSettingsStore mcpDegradedError 守卫两处整表回写 + MCP 页错误横幅 + 4 例 Rust 单测)。 - 已修(2026-10-05 批次 2 开关语义,提交 b1e3b8a):F4 禁用服务商/模型照发(handleSend enabled 守卫明确报错早退 + 设默认守卫 + 删除默认模型迁移 settings.modelId;⚠️ 计划点「禁用默认模型自动迁移」未做——禁用场景由发送守卫以明确报错覆盖,仅删除路径做自动迁移);F5 免 Key 网关 faux 假流(
providerAllowsMissingApiKey/isProviderUsable单一真源收编 5 处id==="ollama"硬编码,isDemo=无 Key 且需要 Key,连带恢复 Ollama 会话记忆提取);F6 UI 显示 Default 实发 Max(IMPLICIT_EFFORT_LEVELS+resolveSupportedEffortLevels单一真源,未声明 effort 的模型同样收敛残留 xhigh/max);F7 记忆/子代理独立模型 enabled 运行时复查(禁用/已删抛错,同 apiKey 空风格;modelResolution 测试扩 F5/F7 用例并接入 test:hub)+ 子智能体页候选列表过滤禁用项。 - 已关闭(判定有误):F22("人设查不到连 warn 都无"不成立——
runAgentTurn.ts:543-545已有 console.warn)。 - ⚠️ 关键纠错(避免后续开发踩坑):①
src/lib/providers/types.ts不存在,服务商配置相关代码在src/components/settings/model-provider/types.ts(F18/F24/L3 初稿路径全错);②McpSettings.serverPolicy是死 UI(从未映射进mcp__<id>__<tool>工具名),但通用toolPolicies审批链路是活的(runAgentTurn.ts:365),勿混为一谈;③ Rustget_hide_to_tray已存在(hide_to_tray.rs:42,lib.rs:162 注册),F13 只差前端初始化回读;④preview/useTheme.ts中只有useTheme()hook 与reinagent-preview-theme键是死的,resolveTheme/Theme仍在用,不可整个删文件;⑤ F25 的 schedule_rule 兜底只在内存row_to_automation替换、不回写 DB 列,不会持久污染原值;⑥ F20 初稿"空 Key 把 legacy Key 发往该供应商 baseUrl"不成立(ProviderItem.apiKey非可选 string,??不在空串回退)。 - 其余待办:F8-F16(装饰性清理,含 D2/D3 决策;F12/F13/F14 语义已校准)、F20/F21/F23-F26(静默设计)、L1-L12 低危。详见 FIXPLAN.md。
- 来源:
-
上下文预算两道防线修复 ✅(2026-10-05,用户实测「23 轮 / 226 步会话思考几秒后 Connection error.」):
- 定位(实测):
task-1791193797808-146rh(23 轮 / 226 步 / 467 条,deepseek/deepseek-flash,apiFormat=openai-responses)每步重发 ~55.3 万 prompt tokens(usage: input 219 + cacheRead 551,680 + output 1,093 = 552,992),请求体重建约 1.60MB(input数组 1,600,283 B + 16 个内置工具 schema ~9KB + 系统/元数据若干),在m500以Connection error.(OpenAI SDK 连接级错误)断连——上一轮m499仍正常流式完成(思考 4.3s)。 - 根因两条(均在上下文预算链路):
- microcompact 长期空转(形状错配):
conversationController.launchTurn把toApiMessages()的输出(pi-aiMessage[],工具条目role === "toolResult"、正文在content的 text 块)交给microcompactMessages,而旧实现只认时间线条目形状(role === "tool"+resultText)→ 一条都没裁过,上下文只增不减(226 步累积即根源)。 - 压缩水位线建在错误锚点:旧判据
used / contextWindow >= COMPACTION_TRIGGER_RATIO(=0.8)。deepseek-flash 声明contextWindow = 1,048,576,需 ~83.9 万 tokens 才触发;而真实在 ~55 万 就已断连——声明窗口偏乐观。另:判据取「末条有usage的 assistant」,失败轮的usage全 0 会把它掩成 0,导致大会话永不触发。
- microcompact 长期空转(形状错配):
- 修复(
compaction.ts/conversationController.js/ 新增promptCeiling.ts/conversationPool.ts):- microcompact 按真实形状重写:裁
toolResult的contenttext 块(首 800 + 尾 400 字符 + 省略标记);非侵入(返回新对象/新数组,绝不改写时间线里的apiMessage原件——toApiMessages是按引用推入的)、确定性 + 幂等 + 单调(认省略标记不再二次裁剪)、常开(MICROCOMPACT_KEEP_LAST_ENTRIES = 6,<4KB 小结果不动)。这三点共同保证「裁过就不变」,不会每步改写前缀打穿 prompt 缓存(讨论结论:延迟启用反而使首次重建更贵,故不设门控)。 - 水位线新公式:
可用空间 = contextWindow − maxOutputTokens(每请求都发max_output_tokens;若上游按input + max_output ≤ window校验,prompt 可用空间比声明窗口小一截:1,048,576 − 393,216 ≈ 655K)→水位线 = 可用空间 × COMPACTION_WATERMARK_RATIO(0.7)(deepseek ≈ 45.9 万)。新增availablePromptTokens/computeCompactionWatermark/modelKeyOf与常量OUTPUT_RESERVE_RATIO=0.25(上游未声明maxOutputTokens时的内部预算,非捏造元数据)、OBSERVED_CEILING_SAFETY_RATIO=0.7;移除旧COMPACTION_TRIGGER_RATIO。contextWindow未知 / 预留吃满窗口 → 返回undefined,退化为只给手动/compact(No-Fallback 不猜)。 - usage 取数修正:新增
lastUsedTokens(messages)——取最近一条有用量的 assistant(跳过全 0 用量的错误行),修「失败轮掩成 0 导致永不压缩」。 - Fail-Fast 经验上限:新增
conversationController.isContextishError(连接/上下文类失败判据)+ 「prompt 已达水位线仍失败」时经注入的onPromptCeilingExceeded记录这次大小(只收紧不放宽),后续水位线取min(基准, 经验上限 × 0.7)——据实测收敛到本机/本账号真实上限,而不是相信服务端声明的窗口。此处不内联压缩(本轮已失败,留给下一次发送的 autoCompact,避免在错误路径上再叠一次可能同样失败的模型调用)。持久化 = 新模块promptCeiling.ts(kvreinagent-prompt-ceiling:<provider>/<model>,跨重启保留;读失败/非法一律undefined);池createEntry注入getPromptCeiling/onPromptCeilingExceeded,conversationController.d.ts补两个可选依赖。
- microcompact 按真实形状重写:裁
- 不变量:压缩仍按轮切分(绝不切进工具调用对);microcompact 只裁
toolResult的 text 块(图片块不动);时间线 / UI / 落库仍是全文(只发送视图被裁)。 - 运行中验证:用户在内置
/compact后 DB 实证——被压区间物理删除(467 行 → 保留compact-0摘要 + 最近 4 轮),待发报文由 1.6MB 降至 ~78KB,会话恢复流式且不再出现Connection error.。 - 验证:
tsc0;compaction.test.mjs11/11(两个 microcompact 用例已改到新数据形状,含「不改原件」「幂等」断言);test:chat146/146、test:providers30/30、test:agent49/49。
- 定位(实测):
附:本地反代的请求体上限(同一排查的相邻发现;2026-10-05 晚已修复为 32MB)
llm_proxy.rs用 axumBytes提取器整体缓冲请求体,因此继承 axum 默认 2MB 请求体上限(axum-core 0.5.6default_body_limit.rs文档:"by default,Byteswill not accept bodies larger than 2MB")。超限时由提取器在进入 handler 之前拒绝,该 413 不带任何 CORS 头 → 浏览器 fetch 失败 → OpenAI SDK 报成误导性的Connection error.。- 首次排查时(1.6MB,约上限 80%)未触发;当晚同一会话继续增长到 2,300,268 字节后实锤触顶:
m174即时失败(started_at == ended_at,1ms,Connection error.),失败后该会话再无任何成功轮(后续轮 usage 为空)——与「历史 api_message 累计字节 ≥ 2MB 后每发必炸」完全吻合。本次断连的实锤根因即此 2MB 上限(tokens 水位线 45.9 万尚未触发压缩,字节先爆)。 - 修复(2026-10-05):
llm_proxy.rs路由挂.layer(DefaultBodyLimit::max(MAX_BODY_BYTES)),MAX_BODY_BYTES = 32 * 1024 * 1024(32MB),彻底解除该悬崖。cargo check通过。验证手段:启动 dev 后对卡死会话重发消息应恢复流式;后续 P1(水位线加字节维度护栏)与 P2(promptCeiling对连接类即时失败也记录经验上限)为可选加固。
斜杠命令交互修复(2026-10-05,用户反馈「/ 菜单 ↑/↓ 选中 clear/compact 后没反应、命令不显示到输入框」)
- 用户定稿交互模型:
/菜单选中只把命令填进输入框(内置 →/name;自定义 → 展开模板),回车提交时才执行内置命令。 - 根因两条:①
LexicalComposer的菜单 Enter/Tab/点击直接runBuiltinCommand,对clear/compact是「立即执行 + 清空输入框」,命令文本从不进输入框(与预期相反);② 草稿态(无活动任务)App 的两个 handler 是if (!activeTaskId) return静默返回,叠加清空 → 表现为「选了没反应、命令也没了」。另有 ③:submit()无斜杠分支,手打/compact回车会被当普通提问发给模型。 - 改动:
slashCommands.ts新增纯函数matchBuiltinCommand(text)——整串精确匹配/name或/name 参数且 name ∈BUILTIN_COMMANDS才命中;/foo(未注册)、/home/user/file(路径)、非整串一律返回 null 交回普通发送(绝不因「以 / 开头」吞内容)。LexicalComposer.tsx新增selectSlashCommand(菜单选中只填词:内置填/name并把光标移到末尾、自定义展开模板),菜单键盘与点击两条路径统一走它;submit()开头用matchBuiltinCommand拦截并执行内置命令(runBuiltinCommand);runBuiltinCommand加草稿态如实 toast(/clear、/compact无会话时提示,不再静默清空输入框)。 - 验证:
tsc0;test:chat147/147(新增matchBuiltinCommand用例:trim 命中 / 带参 / 未注册放行 / 路径放行 / 非整串放行)。
/goal 内置命令移植(2026-10-07,ZCode session goal 语义 v1;CDP 真模型 E2E 全过)
- 语义(ZCode 同款,v1 简化已确认):
/goal无参显示当前目标;/goal <目标>设定(覆盖旧目标,replace为显式别名);/goal pause|resume|clear管理状态。目标每轮注入 meta_user 权威状态块(<untrusted_objective>包装 + 转义,ZCode formatGoalStateForModel 结构;paused 附「不要继续推进」语义句)。 - 改动:新纯模块
src/lib/goals/goalState.ts(parseGoalCommand——pause/resume/clear 要求整串精确匹配(避免「clear the build cache」被误吞,适配 ZCode 首-token 解析;replace别名剥前缀)+escapeGoalPromptText+formatGoalStateForModel);AppTask.goal字段 +updateTaskGoalaction(随任务同步持久化,镜像 updateTaskApprovalMode);buildMetaUserBlock加goalSection(currentDate 之后);RunAgentTurnParams.goal+TurnAssemblyParams.goal(parts 增goalSection供请求预览);AppbuildTurnOptions注入goal: task?.goal ?? null;controller js/d.ts 透传;Composer/goal分支(show 贴回输入框——runGoalCommand 返回 boolean 标记贴回,submit 分支据此决定是否清空输入)。 - v1 明确不做(ZCode 高级状态机,避免捏造数据与重量级子系统):token 预算/用量/时长三行(本应用未跟踪,绝不假数据——单测断言这几行不出现);状态变更 model-only 提醒消息(本应用 meta 块每轮重算,新状态下一轮请求自动可见);goal 自动续跑/target 验证器(另评估)。
- 验证:
test:chat165/165(新增 goalState 10 例:解析矩阵/show/子命令词开头文本不误吞/replace 别名/转义/无目标空串/active 无暂停句/paused 附语义句/无 token 三行);test:providers38/38;tsc 0。CDP 真模型 E2E(SQLite 会话库为权威证据——视图区 tail 断言不可靠,探针已证):/goal <目标>落库 → 问「目标是什么」模型正确复述(注入 ✓)→/goal pause落库 → 问「处于什么状态」模型答「暂停状态…暂停期间我不会继续推进它,除非您恢复或替换该目标」(暂停语义 ✓)→/goalshow 贴回「当前目标(已暂停):…」(✓,修复了 show 被 setText 清空的问题)→/goal clear→ 目标 null + show 提示未设定 ✓。 - ⚠️ E2E 环境坑(复现记录):① Vite watcher 会漏事件——
slashCommands.ts转译产物滞留旧版致 /goal 被当普通消息发出;判定法 =curl localhost:1420/src/...grep 新符号,修复 = touch 文件后重查;② 恢复横幅按钮文案是「忽略」不是「跳过」,未消失的挂起恢复态会干扰发送断言;③ 视图区 innerText tail 在虚拟化+滚动下不可作消息断言依据。
/plan 内置命令移植 + 审批门实时模式修复(2026-10-07,ZCode 对齐;CDP 真模型全流程 E2E)
- 语义(ZCode 同款):
/plan [task]切任务审批模式到 plan(草稿态切全局默认,同模式 chip 路径);带参数时余下文本作为首个任务照常发出,无参数仅切模式并清空输入。配套链路此前已随 P2 尾巴 #8 落地(exit_plan_mode工具 + PlanModeCard 批准卡 + PLAN_MODE_PROMPT 指示模型必须经工具提交计划),本次只补命令入口。 - 改动:
BUILTIN_COMMANDS加plan(clear/compact/init/plan/help);LexicalComposer.submit()特判——handleSelectApprovalMode("plan")后按 args 空/非空决定仅切模式或继续发送。 - ⚠️ E2E 抓出并修复的真 bug(审批门冻结模式):批准计划后,正在跑的回合后续工具调用仍被计划门拦截(模型收到「已批准、开始实施」后 exec/write 全被 block,自述「still plan mode gate?」)。根因 =
createApprovalGate(approvalMode)在回合开始冻结模式值,onApprove只切了任务级模式、跑着的门不跟随。修复(ZCode getMode 语义):createApprovalGate首参改ApprovalMode | (() => ApprovalMode)按调用实时解析;RunAgentTurnParams加getApprovalMode?: () => ApprovalMode;AppbuildTurnOptions注入实时读取器(从 store 现读任务模式,缺省回退全局——不引池→store 模块环);controller.js/.d.ts透传。系统提示词仍按轮冻结(by design),工具结果消息告知模型模式已切换。 - 验证:
test:providers38/38(新增门实时切换回归例:同一门 plan 放行 exit_plan_mode/write 拦截 → 切 ask 后 exit_plan_mode 拦截/write 走审批;exitPlanMode.test.mjs 8 例本轮接入 test:providers 脚本);test:chat156/156;tsc 0。CDP 真模型全流程两轮 E2E——①修复前:/plan 切模式 ✓、计划卡 ✓、批准 ✓,但批准后 exec 被计划门拦截(bug 实锤);②修复后:/plan → 计划卡 → 批准(模式 plan→ask)→ 写操作改为弹审批卡(允许×3)→ 模型完整实现(index.ts 加 add 函数 + 新建 test/index.test.ts)→ npm test 2 例全过;时间线无计划门拦截文案。
/init 内置命令移植(2026-10-07,ZCode builtin-prompt-command.ts 对齐;CDP 真模型两轮 E2E)
- 语义(ZCode 同款,用户确认「先移植 init」):
/init [notes]是提示词模板型命令——不执行宿主逻辑,而是把输入展开为一段普通提示词照常发送,让模型跑一个正常回合:检查工作区 → 创建或更新根目录AGENTS.md;已存在则edit_file编辑补充而非整体覆盖;只动当前工作区;已有替代指令文件(.agents/AGENTS.md/CLAUDE.md/.claude/CLAUDE.md,即agents_md.rsCANDIDATES)时如实告知并停止不创建。 - 改动:新纯模块
src/lib/commands/initPrompt.ts(buildInitAgentsPrompt({args, workingDirectory}),ZCode 原文逐句移植,适配点=身份改 ReinAgent、工具名改 read_file/list_dir/glob/grep/exec_command、候选指令文件按本仓库 CANDIDATES、去掉 ~/.zcode/AGENTS.md 说明;带参数按 ZCode 格式追加「Additional user instructions」text 代码块;路径拼接待 Windows 反斜杠/POSIX 斜杠);slashCommands.tsBUILTIN_COMMANDS加init(clear/compact/init/help);LexicalComposer.tsxsubmit()特判——/init展开为outgoingText后继续走普通发送链路(其余内置命令照旧runBuiltinCommand直执行),无工作区如实 toast 拒绝;输入历史记用户原文(/init notes)而非展开后的长提示词。 - 验证:
tsc0;test:chat156/156(新增initPrompt.test.mjs5 例 +matchBuiltinCommandinit 带参例 + 清单断言改四命令);CDP 真模型两轮 E2E(临时 git 工作区)——①敲/init菜单出现 → Enter 选中填词 → Enter 提交,用户气泡显示展开提示词,模型真实读文件后在根目录创建 AGENTS.md(Purpose/Layout/Commands/Notes 四节,构建/测试命令从 package.json 正确发现);②/init 补充备注对已存在文件为 edit 而非覆盖(diff 恰好 +1/−0,原文全保留,模型自述「这次是对已有文件的编辑」)。
任务胶囊点击开合 + 新建任务 no-op 修复(2026-10-06,CDP 真模型 E2E 验证)
- 任务胶囊交互改造(用户需求「点击展开,再点击收缩」):
TaskProgressBar.tsx从 Radix Tooltip(悬停 200ms 弹出)改为 lw Popover 点击开合——Trigger 点击原生 toggle(点胶囊弹出清单、再点收起;点外部 / Esc 亦收,Popover 标准语义);浮层内容/位置(上方、左对齐、max-w-md)不变,className加w-auto抵消 lw PopoverContent 默认w-72定宽保持随内容自适应;文件头旧「TooltipProvider 教训」注释换为通用「浮层一律用 lw 组件」。 - 顺带抓出并修复「新建任务」三入口 no-op(G1 悬案真凶):
App.tsxhandleNewTaskRef.current = () => handleNewTask箭头返回函数未调用 → Ctrl+T 快捷键 / 命令面板「新建任务」/__newTaskE2E 钩子全部无效;CDP 发送因此落进恢复的旧任务(测试污染已就地修复:测试轮截断 +conversation_sync回写 + reload 验证)。修复 =() => handleNewTask()(保留延迟绑定绕 const TDZ 的本意)。 - 验证:
tsc0;test:chat147/147;CDP 真实模型两轮 E2E——模型真实调todo_write产 0/4 清单 →Input.dispatchMouseEvent真实点击:开(aria-expanded=true+[data-slot=popover-content]四项清单)→ 合(面板移除);修复后真实键盘 Ctrl+T(失焦态)与__newTask直调均切草稿态(activeTaskId → null)。 - ⚠️ 行为注记:
shortcuts.ts中 newTaskallowInEditable: false——输入框聚焦时按 Ctrl+T 按设计不触发(需先失焦);G1 早前「编辑框聚焦放行」「Ctrl+F 不劫持」的记载与代码相反(实际 find/palette=true、newTask/focusComposer=false),已在上文 G1 段订正。
任务胶囊多清单并排(2026-10-06,用户定稿「按内容新增 + 只显示最近 N 个」;CDP 真模型 E2E)
- 需求:新一轮对话里模型建立的新清单以新胶囊出现在第一个胶囊的右边,以此类推。原实现只渲染末条
todo_write(新清单原地替换旧胶囊),不存在并排。 - 纯逻辑(
todoProgress.ts):新增extractTodoLists(messages): {key, todos}[]——按时间正序提取全部清单快照;按内容新增口径(用户拍板):内容与上一份不同的todo_write新开一份快照(同轮进度推进 0/4→2/4 也成列),内容相同的重复写入跳过;显式空数组 = 清空(全部快照消失,覆盖语义),缺字段/非法条目跳过不清空。key= 规范化内容的 JSON 签名(React key 与关闭持久化共用)。extractLatestTodos变为末条快照薄包装(行为兼容,原 4 测试不动)。 - 组件(
TaskProgressBar.tsx):胶囊行渲染——每份快照一个独立胶囊(圆环进度 + 点击开合 Popover + 行内 × 逐胶囊关闭);只显示最近MAX_VISIBLE_TODO_LISTS=5个(用户拍板;更老快照隐藏,数据仍在时间线);关闭签名集合持久化 kvreinagent-todo-dismissed-keys(JSON 数组、上限 50 截断,取代旧单签名键reinagent-todo-dismissed-key);行容器overflow-x-auto兜底极端宽度;胶囊max-w-64限宽截断「当前项」长文案。 - 验证:
tsc0;test:chat147→151(新增 4 例:内容不同各成快照/相同内容去重/空数组清空与缺字段保留/包装一致性);CDP 真模型 E2E——测试任务里发「另建全新 3 步清单」→ 第二个胶囊出现在第一个右侧(同行 x 308→476,0/4 与 0/3)→ 各自点击展开内容互对应(新=数据备份 3 项、旧=登录模块 4 项原样)→ 再点收起。
提问卡多 Tab 弹窗改版(2026-10-06,用户定稿;CDP 真模型 E2E)
- 需求(用户截图标注):① 删除「提问」标题行;② 多题合并为单弹窗多 Tab(原为单页全列堆叠)——顶部 Tab 条每题一枚(标签=题目文本,已答题带绿勾点),点击切换;③ 右下角分页化——非最后 Tab =「下个问题」,最后 Tab =「提交回答」,左下「跳过」语义不变;④ 右上角缩小按钮(Minus)——收起为输入框右上方胶囊(形态对齐任务胶囊:HelpCircle + 提问 + 题数徽标>1 时显示),点击恢复弹窗。
- 交互决策(用户拍板):自由切换、提交时校验——Tab/下个问题永远可点,草稿按题保留;「提交回答」未答齐禁用,提交校验报错并自动跳到第一个未答的题。
- 实现:
AskQuestionCard.tsx重写(active Tab 状态 + tablist/tab/tabpanel 角色 + 分页底栏 + 绝对定位右上缩小钮);新增导出AskQuestionMinimizedPill(App 渲染);App 持questionMinimized状态,state.pendingApproval变化(新问题/解决/切任务)即复位展开——防跨任务/跨问题泄漏;i18n 新键askNextQuestion/askMinimize(中英)。 - 验证:
tsc0;test:chat151/151;CDP 真模型 E2E(GLM-5.3-Flash 一次调用真提两题):无标题行 ✓、双 Tab 标签=题目 ✓、Tab1 右下=下个问题 ✓、答题后切 Tab2=提交回答(未答 Q2 时禁用)✓、回 Tab1 草稿保留 ✓、提交收卡 ✓、模型复述「收到你的回答:今晚吃米饭,周末看电影。」✓;第二轮单题:缩小→弹窗消失+右上方胶囊 ✓、点胶囊恢复(Tab 数正确)✓(挂起卡留给用户手测)。 - E2E 备忘:测试期间用户操作应用切走视图会干扰脚本——脚本须显式重激活目标任务再断言;带
JSON.stringify的 evalp 返回字符串,断言前必须 parse(本轮 verdict 笔误一次,功能实为全过)。 - 追加(同日用户反馈):缩小胶囊不再显示「提问」二字,改为显示当前 Tab 正在回答的问题文本(悬浮 title=全文,题数徽标保留)——
activeIndex相应提升到 App 受控(AskQuestionCard 加activeIndex/onActiveIndexChangeprops),缩小恢复后停留在原 Tab;胶囊max-w-64截断长题目。CDP E2E:Tab1/Tab2 缩小胶囊文本各自跟随、恢复停留原 Tab ✓。⚠️ 注意:reload 会丢挂起中的提问卡(审批挂起为内存态不落库,既有行为)。 - 追加(同日用户反馈②):选中选项即自动跳下一题(最后一题不跳、绝不自动提交——手动点「提交回答」);「其他」自由输入不触发跳题(打字过程跳题会打断输入)。「下个问题」按钮与 Tab 点击保留(自由输入用户与回改路径仍用)。CDP E2E:选米饭自动落 Tab2+绿勾、最后一题选完弹窗保持打开、手动提交收敛 ✓。
Windows 控制台输出 GBK 乱码修复(2026-10-06,用户报「工具卡乱码」;CDP 实测通过)
- 根因(实锤):中文版 Windows 的控制台程序(ping/ipconfig 等)往管道写输出用系统 OEM 代码页(本机 chcp=936/GBK,实测字节转储
c0b4 d7d4=来自);而fs_execute/bg_process/terminal.rs统一from_utf8_lossy硬解 UTF-8 → 每个汉字变 U+FFFD(工具卡花块、ASCII 幸存)。此前 8.9 记录「本机代码页为 UTF-8」系误记(已订正)。LiveAgent 同样裸 lossy 未处理;终端 PTY 面板不受影响(ConPTY 层转 UTF-8)。 - 修复:新模块
console_decode.rs——decode_console_bytes严格 UTF-8 校验优先(pwsh7/git 等本就 UTF-8,零影响),失败按GetOEMCP+MultiByteToWideChar解(windows 依赖加Win32_Globalizationfeature,零新依赖;任何一步失败回退 lossy 不 panic);decode_with_codepage单独导出供固定代码页测试。应用四处:fs_executestdout/stderr(工具卡)、bg_output/bg_list尾部(后台任务)、kill/taskkill stderr 诊断。terminal.rs PTY 分块读不改(2026-10-07 劈字符已修复,见下条)。
PTY 劈字符修复(2026-10-07,终端面板中文偶发 � 根治;cargo test 201 全绿 + CDP 双 E2E 通过)
- 根因:
terminal.rs读取线程read(&mut buf)的块边界由 ConPTY 决定,可能停在一个多字节字符中间,而每块各自独立from_utf8_lossy→ 残缺前半截与孤立后半截各变一个 U+FFFD;输出越猛(进度条/npm install)ConPTY 越容易小块交付,劈中概率越高。bg_output增量轮询同族:pump_stream存原始字节没错,但每轮按 offset 独立解码buf[offset..],上一轮结尾劈开的字符下一轮仍劈开。正确修法必须是跨块/跨轮的有状态解码,任何「套一层解码函数」都治不了(这也是 GBK 修复时刻意不动 PTY 分块读的原因)。 - 修复:
console_decode.rs新增Utf8StreamDecoder(carry 缓冲 +from_utf8错误信息精确切分——error_len()==None末尾截断扣留、Some(n)真非法 lossy 丢弃、flush()流结束清空)与complete_boundary_len(无状态边界扣留,供 bg_output)。terminal.rs读取线程换用(ConPTY 输出恒 UTF-8,无需 GBK 兜底;EOF flush);bg_process.rsBgOutput新增consumedBytes,bg_output按complete_boundary_len扣留尾部、bg_list尾部视图每秒全量重取无 offset 语义故 consumed==total;tools.jstask_outputoffset 契约同步改传consumedBytes。 - 验证:新增 6 例单测(「你」/😀 劈开拼回、截断 flush、GBK 首字节扣留等);
cargo test201/201 全绿、tsc 0;CDP 实测(dev 热重建后)——①终端会话cmd /c for /l连发 300 行中文+emoji(276 个 chunk 交付)uFFFD=0 且 301 处标记完整(含命令回显 1 次);②bg_spawn ping -n 6模型式增量轮询 9 轮 offset 跟随 consumedBytes,6 条 GBK「来自…的回复」无丢失无重复 uFFFD=0。 - 验证:
cargo check0 警告;cargo test --lib192/193(新增 3 例解码测试全过:GBK 936→中文/UTF-8 直通/非法字节 lossy 兜底;唯一失败仍是已记录的 bg_stop Windows 回归);CDP 裸调ping -n 2 www.baidu.com——fs_execute与bg_output中文全部正常(来自…的回复/字节/平均)。
bg_stop Windows 回归修复(2026-10-06,修复 9d41cdd 引入的软杀白等)
- 症状:
bg_process_tests::long_running_process_can_be_stopped稳定失败(实测 ~3.5s,超 3s 断言)——9d41cdd 的「TERM→2s 宽限→KILL」重写在 Linux 验证 0.1s 通过,Windows 侧未复跑。 - 根因:Windows 对控制台进程树没有有效软终止语义——
taskkill /PID /T不带/F只发 WM_CLOSE(仅对有消息循环的 GUI 程序有效),ping/cmd/node 等控制台程序一律拒绝("can only be terminated forcefully");软杀失败后 TERM 宽限 2s 必然白等,叠加tasklist探活开销(每次 spawn ~150ms+)→ 总耗时 ~3.5s。 - 修复:
signal_process_group的 Windows 分支不区分 force,恒用/T /F强杀整树(Linux 分支 TERM→KILL 语义不变);bg_stop_sync流程注释同步标注 Windows 偏差。 - 验证:
cargo test --lib bg_process_tests5/5(含真实进程 spawn/强杀/进程组消失断言,套件 4s→1.5s);全量 193/193 全绿(本会话首次全绿);cargo check0 警告。
轨迹页(LiveAgent TrajectoryView 移植,2026-10-06;用户定稿「时间线适配版」)
- 需求与定案:任务对话页顶栏、助手 chip 前面加「对话/轨迹」分段切换(LiveAgent ConversationViewTabs 同款:MessageSquareText/Waypoints 图标、胶囊边框、选中高亮;仅工作台 + 有消息的任务显示,草稿态隐藏);选中「轨迹」时消息区整体替换为轨迹页(输入框 dock 保留);按任务记忆(conversationViewState Map:切任务各自保留,新任务默认对话)。
- 数据(时间线适配版,非 LA 的运行时埋点体系):新增
lib/trajectory/fromReinAgentTimeline.ts——把TimelineEntry[]映射为 LA 账本(轮=user 开轮、步=assistant、工具归属前一步、压缩条目→standaloneCompactions;startedAt/endedAt/thinkingStartedAt(=LA 首 token 语义)→ assistantMetrics TTFT、apiMessage usage、重试记录全量进账本;hasTiming 如实计算)+ 内容索引(assistantByStep/toolByCallId/userByTurn)。LA 的 layout/timeline/displayItems/presentation/searchIndex/textDiff 等纯逻辑原样复制(零改动,仅 presentation.ts 换本地 Intl 缓存)。一期明确不做:运行时埋点(无 header 分段池 → 砍 system 记录与 diff/SystemPrompt/工具 Schema 四个 tab)、轨迹专用 SQLite 表、「加载更早」复用本仓会话分页。 - UI(16 文件):
components/trajectory/——TrajectoryView/Timeline(三泳道+回合带+刻度尺+minimap+拖选缩放)/Table(≥120 行虚拟化)/Row/Toolbar/useTimelineGestures + details/(DetailsPanel 8 tab、ResizeHandle、9 个 tab、shared)。ConversationViewTabs在 components/chat/。适配:useLocale→useTranslation、IconSet→lucide、cn 路径、调色板类→语义 token(--trajectory-span-*/badge-*明暗两套 +--trajectory-idle/aborted-pattern斜纹背景图,global.css 尾部);i18ntrajectory.*97 键(zh/en,src/i18n/trajectory/{zh,en}.tsspread 进字典)。 - 接线(App.tsx):
useConversationViewState(activeTaskId ?? "");topbar 在 AssistantChip 前渲染 ConversationViewTabs(workbench+activeTask+有消息);工作台消息分支条件化——轨迹分支 = TrajectoryView + 普通包装的 composer dock(原 dock 内容抽出为composerDock变量两分支复用),历史分页状态historyLoad复用。 - 验证:
tsc0;适配器单测 6/6(结构映射/压缩段/重试/无打点/端到端记录字段/duration 投影泳道);全量前端套件 348 绿;CDP 真机 E2E——切换器在助手 chip 前(x 355/421 < 496)、轨迹页渲染(工具栏三切换、输入/模型/工具泳道、8 行真实记录 USER/ASSISTANT/TOOL)、详情面板 8 tab(概要含状态/开始于/总耗时 5.74s/turn/step/供应商/模型;渲染 tab 显示 THINKING 原文)、时间轴点击选中行、切回对话消息列表恢复、按任务记忆(另一任务默认对话、切回记住轨迹)、草稿态切换器隐藏。 - 追加(同日用户反馈):轨迹页贴边铺满(去掉外层 px-6/8/12 包装,与 LiveAgent 一致——其内部组件自带 px-3 保留);隐藏输入框与 dock(轨迹分支不再渲染 composerDock,切回「对话」恢复)。CDP 实测:左右间隙 0(toolbar 与主列左右缘逐边框对齐)、轨迹态 DOM 无 textarea、切回对话 textarea 恢复。
- 追加②(同日用户反馈):轨迹页同时隐藏底部会话统计行(SessionStatsBar 渲染条件加
activeConversationView !== "trajectory")。CDP 实测:轨迹态[data-testid=session-stats-bar]不存在、对话态恢复。 - 追加③(同日用户反馈「原始块显示无内容」):适配器补全原始块映射(当时只给了附件块,纯文本消息没造块)——对齐 LA contentIndex 语义:user = text 块 + attachment:
块 (JSON 含 fileName/path/kind、imageAlt、filePath);assistant = thinking/text 块 + 其后工具的 tool-call 块(工具挂接时追加);tool = tool-call 块(sourceBlocks)+ 输出块(outputBlocks:优先取 apiMessage 原件 content 的 text/image 块——图片转 data URL,无原件退回 resultText 文本块)。适配器测试 6→7(新增三类块断言);CDP 实测 USER#1 原始块显示「TEXT 你好」(原为「无内容」)。
下一次请求预览(轨迹页工具栏入口,2026-10-06;用户定稿:居中弹窗分类导航 + 可读文档与原始 JSON)
- 入口:轨迹工具栏搜索框前的「下次请求预览」按钮(FileJson2 图标)→ 居中大弹窗(lw Dialog;左分类导航:请求参数/系统提示词/工具/用户上下文/消息安排/原始 JSON;底部构建注记 + 复制全文)。
- 同源铁律(本次核心重构):把
runAgentTurn的上下文装配段抽出为assembleTurnContext(系统提示词拼接:人设→基座→Environment→模式段→SessionStart hooks;工具收集:注册表+MCP+询问/计划+记忆管理器;meta_user 合并入首条 user),发送与预览调同一函数——预览传runSessionStartHooks:false回避外部 hook 副作用;消息段用toApiMessages + microcompactMessages(与 controller 发送前同管道);压缩水位线用computeCompactionWatermark + lastUsedTokens(新导出)+ readPromptCeiling(与 autoCompact 同判据,越线如实提示);子代理工具对同工厂构建(失败降级并如实标注)。 - 如实性注记(弹窗底部恒定):预览是装配到 pi-ai 流式调用前的视图,协议适配器还会做最后一层转换,不必然等于最终 HTTP 字节;SessionStart hooks 追加内容仅真实发送时执行;演示模式(faux)如实提示「真实发送不会发生」。
- 体积护栏:单消息条目 4000 字符、原始 JSON 整档 400k 字符截断并标注(防 Shiki 渲染巨文卡死)。
- 验证:
tsc0;providers 30 / agent 49 / chat 151 / trajectory 7 全绿(重构后回归);CDP 真机 E2E——按钮在搜索框前(x 1270 < 1419)、弹窗六节导航、五节内容实测(系统提示词 4911 字符含 # Communication/# Environment、工具 27664 字符含 read_file/todo_write/agent、用户上下文 8605 含 currentDate、消息 4486 含 system-reminder、原始 JSON 39048 可读)、复制全文经 PowerShellGet-Clipboard实证(完整文档落剪贴板;webview 内clipboard.readText因 WebView2 读权限悬挂——写正常读受限,测试脚本勿在 webview 里读剪贴板)。 - 追加(同日用户反馈):「复制全文」精简为仅「原始 JSON + 构建注记」(拆解/合并等分节视图仅弹窗内查阅;用户查看时发现同一内容被三视图展示三遍,落档/喂外部工具以原始形态为准)。CDP 实测:剪贴板从 86.4K 字符降至 39.9K,
# currentDate/# Memory Index各 1 次、单个 json 代码块、尾部注记在列。 - ⚠️ 严重回归修复(2026-10-06 用户报「进入设置页一片空白」):请求预览接线时把
requestPreviewState的useState+openRequestPreview的useCallback声明在了设置页早退 return 之后——切设置页时这两个 hook 不执行,React 抛「Rendered fewer hooks than expected」整树卸载 → 白屏(重载因 currentView 持久化为 settings 而持续白屏)。修复=两个 hook 上移到早退之前(并注释铁律);CDP 实测重载/设置/工作台双向切换零异常、设置导航与工作台消息均正常渲染。教训:任何新增 hook 必须位于组件所有条件早退之前;E2E 验收应覆盖设置页视图(此前测试均在工作台/轨迹内,漏网)。
内置技能启用语义改造(2026-10-06,用户定稿:可启用/禁用、默认禁用)
- 背景:
skills-creator/skills-installer原为「恒启用」(LA 的 ALWAYS_ENABLED 语义:注入强制并入、UI 无开关)。用户要求改为普通技能——可启用/禁用、默认禁用;并确认禁用技能不进上下文(browser-use 禁用实证不在注入的 Skills 清单中)。 - 改动面:
lib/skills/builtin.ts重构(删除mergeAlwaysEnabledSkillNames/isUserSelectable*;isAlwaysEnabledSkillName→isBuiltinSkillName,只保留两处用途:列表排序置前 + 删除保护——Rustensure_not_builtin_skill_management_target同样拒绝删除内置,UI 对内置隐藏删除按钮、批量删除排除内置);hubSettingsStore默认{enabled:true, selected:[]}+ 旧污染迁移(加载时剔除 selected 中遗留的两枚内置名并写回一次——旧版加载器会把它们强制并入,历史上任何一次设置写盘都可能把污染写进 kv;实测用户 kv 正是如此,迁移后两枚默认关);SkillsHubPage的全部启用守卫/合并/计数口径放开(选中集合、toggle、批量启停、全选、安装后自动启用、分类归属);InstalledSkillCard/View/Drawer去掉 Lock 分支与恒启用徽标,开关按普通技能渲染(内置仍隐藏删除按钮)。 - 验证:
tsc0;hub 67 / chat 151 绿;CDP 真机 E2E——迁移后两枚开关均aria-checked=false(默认禁用)→ 启用 skills-creator 后 kv 持久化["skills-creator"]且请求预览的用户上下文 Skills 清单出现 creator、不含 installer(禁用不进上下文)→ 关闭后 kv 回到[]。 - 追加(同日用户反馈②):「复制全文」再精简为纯 JSON 数据(标题行/代码围栏/注记全部不带,
writeText(data.rawJson));CDP 实测剪贴板以{起}止、无标题/围栏/注记行。
README.md 开源规范化全景重构(2026-10-07)
- 背景与目标:根据项目全景架构分析,原
README.md仅包含 72 行极简说明,严重滞后于当前已落地的核心特性。 - 改动面:
- 核心理念:显式确立 Local-First 数据自治、No-Fallback & Fail-Fast 真实交互铁律,以及 Linux/Windows 跨平台差异化适配说明;
- 功能全景:完整增补轨迹分析视图(TrajectoryView)、下次请求同源预览、代码改动检查点与原子回退(checkpoint/rewind)、多协议真实请求(Chat / Messages / Responses / Google)、对话问题导航条、任务清单多胶囊并排、STT 语音实时识别、Git 集成工作台与双消息配色主题;
- 架构与目录:补全 React 19 + Tauri 2 (Rust) 分层架构图与源码目录映射;
- 测试与验证:补齐所有分域测试套件(
test:chat,test:providers,test:agent,test:git,test:hub等)及 Rust 检查命令。
会话导入列表展示项目路径(2026-10-07,用户定稿方案 B)
- 需求与设计:「设置 ▸ 导入」面板的会话(Sessions)列表中,扫描出的各外部工具会话若包含项目路径,在元数据行(第二行)末尾追加展示项目绝对路径(
N 条消息 · 日期 · <projectPath>)。 - 改动面:
src/components/settings/ImportSection.tsx:SessionImportPanel会话行元数据追加candidate.projectPath ? " · " + candidate.projectPath : "";ImportRow:标题与 meta 标签补齐title属性(title={title}与title={typeof meta === "string" ? meta : undefined}),长路径被单行截断时鼠标悬停可完整查看绝对路径;
- 验证:
tsc --noEmit0 错误;test:import38/38 单测全绿。
清理失效死控件:summaryModel 与 serverPolicy(2026-10-07,用户定稿彻底删除)
- 背景与根因:
summaryModel(会话摘要模型):系 LiveAgent 遗留项。在 ReinAgent 记忆体系中,会话后的记忆提取与定期整理已完全统一由organizerModel(或回落主模型)驱动,全库对summaryModel零消费,纯属抽屉中的装饰性选择器。serverPolicy(MCP 服务器全局策略):原设计在每个 MCP 服务器卡片渲染ToolPolicyToggle(允许/询问/拒绝),但底层审批门createApprovalGate仅匹配具体工具名(mcp__<serverId>__<tool>),从未匹配过serverPolicy[serverId],该卡片开关对实际审批行为无任何影响,纯属死开关。
- 改动面:
src/lib/hub/mcpTypes.ts:从McpSettings接口中彻底移除serverPolicy?: Record<string, "allow" | "ask" | "deny">;src/store/hubSettingsStore.ts:从MemorySettings接口及defaultMemoryShape()中移除summaryModel;在hydrateMcp()中移除serverPolicy透传;src/components/memory/MemorySettingsDrawer.tsx:移除conversationSummaryModel状态、handleSummaryModelChange回调及“会话总结模型”UI 标题与ModelPicker节点,仅保留“记忆整理模型”;src/components/mcp/McpServerCard.tsx:移除ToolPolicyToggle导入、serverPolicy及onServerPolicyChangeprops 与卡片底部策略开关渲染;src/components/mcp/McpServersForm.tsx:移除向卡片传递的serverPolicy属性及未使用的updateMcp导入。- 保留范畴:任务级工具审批策略(
activeTask.toolPolicies)仍活跃且独立运行,不受任何影响。
- 验证:
bunx tsc --noEmit0 错误;test:hub、test:providers、test:settings、test:chat、test:agent全量测试套件通过;bun run tauri dev热重载正常无报错。
批次 3 其余装饰性清理与批次 4 静默设计/Fail-Fast 全景修复(2026-10-07)
-
背景与目标:
- 全面清理
FIXPLAN.md遗留的死状态、死属性与废弃组件(批次 3),消灭所有无实际消费的装饰性字段。 - 彻底贯彻 No-Fallback & Fail-Fast 铁律(批次 4):严禁静默吞错、严禁跨源拼凑配置、严禁使用伪造兜底数据伪装正常;任何存储写入失败、元数据解析异常、配置同源损坏必须第一时间显式暴露给用户。
- 跨平台支持:保证 Linux 与 Windows 双端托盘与进程/文件路径一致性保障。
- 全面清理
-
改动面明细:
- 批次 3 装饰性死状态与废弃组件清理:
src/lib/hub/mcpTypes.ts&src/store/hubSettingsStore.ts:删除McpSettings.selected死属性及MemoryScheduleSettings.timezone(时区改为系统本地运行时真实计算,不再保留写死默认 "UTC");src/components/settings/SettingsPage.tsx:修复hideToTray开关只写不读问题,挂载时调用invoke<HideToTrayResult>('get_hide_to_tray')真实回读 SQLite 配置;src/preview/useTheme.ts:删除未引用的useTheme()死 hook 与废弃常量reinagent-preview-theme,避免双重主题状态割裂;src/components/chat/LexicalComposer.tsx&src/App.tsx:THINKING_OPTIONS增加{ level: "off", label: "Off", steps: 0 }显式关闭档位;App.tsx对齐逻辑保留"off",并在向下分发到pi-agent-core时将reasoning置为undefined,实现真实的关闭思考,杜绝静默重置为 low;src/components/settings/ProviderForm.tsx:彻底物理删除全库 0 引用的孤儿组件。
- 批次 4 静默设计修复与 Fail-Fast / 告警接入:
- F20(自动化派发凭证同源校验):
src/App.tsx中的dispatchAutomationRun强制实行服务商与模型同源校验(模型必须属于对应供应商自身的models列表),若缺失仅能取该供应商自身的defaultModelId或首个模型;若找不到可用模型或供应商,直接通过automation_run_finished汇报失败,严禁回落或跨源借用全局 settings 的异源模型; - F21(Classic Hook 错误显式捕获):
src/lib/hooks/hooksRuntime.ts中HookOutcome补充hasError?: boolean,classic hook 执行出错时输出console.warn并在 outcome 标明,杜绝静默掩盖; - F23(持久化降级显式告警条):
src/store/useAppStore.ts增加storageDegradedError: string | null与setStorageDegradedError;src/lib/storage/db.ts(flushKv/flushTasks)与src/lib/chat/conversationPool.ts(schedulePersist)在 SQLite / KV 持久化捕获异常后,将真实错误写入 store;src/App.tsx顶栏渲染可手动关闭的存储降级警示横幅(Storage Degraded Warning),显式告知用户持久化失效;
- F24(供应商配置写盘失败报错):
src/components/settings/model-provider/types.ts中的saveProvidersConfigToDisk写盘失败时抛出异常并记录日志;src/components/settings/model-provider/ModelProviderSettings.tsx捕获异常并通过toast.error向用户弹出错误提示;
- F25(自动化调度规则损坏 Fail-Fast):
src-tauri/src/automation.rs中的row_to_automation在schedule_ruleJSON 损坏时,标记lifecycle_status = "error"、next_run_at = None、last_error = Some("schedule_rule JSON 损坏: ..."),严禁伪造默认每天 09:00;claim_due查询增加lifecycle_status != 'error',循环中过滤error状态,杜绝认领并执行损坏的规则;
- F26(记忆抽取独立模型解析失败 Fail-Fast):
src/lib/chat/conversationPool.ts中的maybeExtractMemory在独立记忆模型配置失效/抛错时,直接终止当轮抽取并打印警告,严禁静默回落主模型消耗用户 Token; - 低危项消灭(L1/L2/L3/L4/L9/L12):
- L1:
automation_run_finished失败补充console.warn; - L2:
getInitialTasks解析 task row JSON 出错时记录损坏 task id 与详细错误; - L3:
updateModelEffortDefaultLevel找不到 provider/model 时输出警告; - L4:
modelResolution.ts补全下游buildModel严格 fail-fast 校验注释; - L9:
checkpointRewind.tsx的checkpoint_list异常补充错误日志,杜绝空 catch; - L12:
conversationPool.ts的getOptions在缺失entry.sendOptions时直接抛错,杜绝伪造假 options 对象。
- L1:
- F20(自动化派发凭证同源校验):
- 文档与计划同步:
docs/FIXPLAN.md批次 3 与批次 4 全部检查项已全量勾选完成。
- 批次 3 装饰性死状态与废弃组件清理:
-
验证情况:
cargo check --manifest-path src-tauri/Cargo.toml:0 错误 0 警告;cargo test --manifest-path src-tauri/Cargo.toml --lib automation:4/4 单测全通过(含损坏 JSON fail-fast 验证);bunx tsc --noEmit:0 错误通过;bun run test:providers && bun run test:chat && bun run test:settings && bun run test:agent && bun run test:hub:全量单测全部绿灯;bun run test:git && bun run test:import:通过;bun run tauri dev(task-50 / PID 495542)保持常驻运行,代码修改热重载正常,Linux 与 Windows 逻辑一致性保障无误。
挂起审批/提问的持久化与重载恢复(2026-10-07,用户定稿完整版;CDP E2E 双变体全过)
- 背景:审批/提问挂起是内存态(agent 循环停在等待用户决策),刷新即丢——且刷新后时间线末条是「带 toolCall 的 assistant」,模型上下文里这次中断的尝试会被 B-2/R13 消毒剔除(发送不受阻,但操作本身蒸发)。
- 修复:① controller 挂起时经新钩子
onPendingApprovalChange(d.ts 同步)持久化到 kvreinagent-pending-approval:<taskId>(解决/停止/清空的任何解决路径自动清除);② 重载后任务激活时读 kv → 恢复横幅(PendingRecoveryBanner,工作区横幅样式):「检测到未完成的{提问|审批:工具名}(应用重启时中断)」;③ 恢复 = 组合一条续接用户消息走正常发送(重载后原 agent 循环已不存在、B-2/R13 也会把中断链剔除——所以不续跑旧循环,而是把操作结果以用户消息形式完整告知模型,语义等价且如实标注「(续接:…)」前缀):- 提问:[继续处理] 重放 AskQuestionCard(题目来自持久化 args)→ 回答组合「问题+回答」清单续接;
- 审批允许:[允许并继续] = 用户批准后当场真实执行该工具(
executeRecoveryTool:注册表查找 execute;MCP 工具路由 mcp_call_tool;找不到/失败如实写入消息)→ 结果组合续接; - 审批拒绝 / 忽略:拒绝组合「已拒绝请调整」续接;忽略仅清除横幅与 kv。
- 实现:
pendingRecovery.ts(组装+执行纯逻辑)、PendingRecoveryBanner.tsx(横幅+重放提问卡;props onAnswer/onReject/onAllow 由 App 组合消息后走 handleSend——含 UserPromptSubmit hooks/提及解析全链路);App 三态(banner/recoveryBusy)挂任务激活 effect。 - 验证:
tsc0;chat 151 / providers 30 / hub 76 绿;CDP 真机双变体——提问:挂起→重载→横幅出现→重放卡片带原题→答「选项甲」→续接消息发出→模型复述「已恢复上下文…你的回答『选项甲』已记录」;审批:write_file 审批挂起→重载→横幅带工具名→允许→真实创建文件(磁盘 11 字节 recovery-ok)→续接消息带执行结果→模型确认完成;两变体 kv 均正确清除。
麦克风权限默认放行(2026-10-08;Tauri 2 on_permission_request)
- 问题:语音输入在 Linux 直接报「麦克风权限已拒绝」(
src/lib/stt/audio.ts把getUserMedia的NotAllowedError/SecurityError映射成该提示),Windows 则弹 WebView2「是否允许录音」询问框。 - 根因:
tauri::Builder未注册权限处理器。WebKitGTK 对WebKitUserMediaPermissionRequest未处理即默认拒绝(不弹系统询问);WebView2 未处理则走它的默认弹窗。wry 仅对剪贴板 (CLIPBOARD_READ) 自动放行,麦克风不在其列。项目原有stt_request_microphone_permission仅 macOS 有真实实现,非 macOS 为空实现。 - 修复:
lib.rs的run()给 builder 加on_permission_request——仅PermissionKind::Microphone返回Allow,其余(摄像头/定位/通知等)保持Default(各平台默认语义)。Windows 侧由 wry 翻译成COREWEBVIEW2_PERMISSION_STATE_ALLOW,弹窗随之消失。macOS 除本处理器外仍需打包Info.plist的NSMicrophoneUsageDescription(当前仓库无src-tauri/Info.plist,属待办)。 - 边界:Windows 系统级「允许桌面应用访问麦克风」仍须开启,否则取流失败并如实报错(不伪装成功);
WebView2 按来源记忆授权,dev(
http://localhost:1420) 与正式版(tauri://localhost)是不同来源。 - 验证:
cargo check0、tsc0、dev 正常构建启动。真机取流实测未完成——本机 WebKitGTK 的 webview 渲染于 1×1 子表面(非 X 子窗口),xdotool/合成事件无法送达 webview,自动化按键与取流验证 在此环境不可行,留待手工按住右 Ctrl 实测。
长按右 Ctrl 语音输入(push-to-talk,2026-10-08;用户定稿)
- 需求:长按右 Ctrl 开始语音输入、松开即停止并回填转写;固定按键(不进设置、不可自定义); 仅在任务页与新建任务输入框生效;模型流式输出时静默忽略。
- 实现:
src/lib/stt/pushToTalk.ts:isPushToTalkKey判定——优先物理键位code === "ControlRight", 回退key === "Control" && location === 2(部分 WebKitGTK 环境不下发code,实测合成按键为Unidentified)。src/lib/stt/useComposerStt.ts:新增pressStart/pressEnd(与麦克风按钮共用同一会话链路)。pttHeldRef记录本次按住、pttPendingStopRef处理「松手早于开麦完成」的竞态(开麦后立即补停, 不留麦克风);cleanup/abandonQuietly复位按住态。PTT 路径下配置不完整静默放弃(不弹 toast、不跳设置)。src/components/chat/LexicalComposer.tsx:新增onRegisterPushToTalkprop,挂载时把{available, pressStart, pressEnd}注册给上层(任务页/新建页输入框互斥挂载,任一时刻至多一个注册者)。src/App.tsx:窗口级keydown/keyup(ControlRight)+blur(失焦视为松开);e.repeat忽略自动重复;available为假或 STT 内部disabled(流式)时静默忽略;切任务时释放按住态。
- 验证:
tsc0;新增单测src/lib/stt/pushToTalk.test.mjs(3 例:物理键位命中、code 缺失回退、 左键/异键不命中)已接入test:chat,chat 168 / providers 38 / settings 6 全绿;dev 构建启动正常。 真机长按取流实测同受上条环境限制未完成(webview 非 X 子窗口,无法注入按键)。
CI 自动构建与自更新(2026-10-08 定稿;照搬 yetone/magpie 的自更新模型)
分发形态(用户定稿:要单文件裸二进制,不要安装包)
- Windows / Linux:
tauri build --no-bundle出裸二进制,文件名ReinAgent-windows-{amd64,arm64}.exe、ReinAgent-linux-{amd64,arm64}(免安装)。 - macOS:
.app+.dmg(给人下载)+.app的 zip(ReinAgent-darwin-{arm64,amd64}.zip, 供程序自更新解包替换)——与 magpie 完全一致(magpie 也是 dmg 给人下、zip 给自更新)。 - ⚠ 代价(分发裸二进制必然):Windows 用户需机器已有 WebView2 与 VC 运行时;Linux 用户需系统
已装 GTK3 + WebKitGTK 4.1(
ldd显示裸二进制有 124 个动态依赖,非自包含)。
不用 tauri-plugin-updater
- 已移除依赖、
.plugin()注册、plugins.updater(含 pubkey)与createUpdaterArtifacts。 原因:插件只会下载安装包再交系统安装器,与「裸二进制原地替换」不兼容。 tauri.conf.json的bundle.targets收敛为["app","dmg"](仅 macOS 本地打包用)。
自更新实现 src-tauri/src/self_update.rs(对齐 magpie internal/update)
- 更新源:自建
latest.json(Release asset),格式{version, notes, pub_date, assets:{文件名:{url,size,sha256}}};默认releases/latest/download/latest.json。 - 校验:仅 SHA-256(magpie 裸二进制同此;其 macOS 另有 codesign,本项目未签名故略去)。
- 裸二进制替换:下到同目录
<exe>.new→ 校验 → Windows 先rename(exe → exe.old)再rename(.new → exe)(运行中的 exe 不能覆盖但可改名);Linux 直接原子 rename;失败回滚。 - macOS:下载 zip → 解压到
.ReinAgent-update/→rename(bundle → old.app)→rename(staged → bundle);zip 顶层须为ReinAgent.app。 - 清理:
cleanup_stale()在 setup 调用,删上次遗留的.old/.old-N(Windows 运行中被占用 删不掉,留到下次启动)与中断的.new。 - 版本比较:内置简易 semver(支持预发布,正式版 > 同号预发布)。
- 命令:
update_check/update_install(返回字段hasUpdate/currentVersion/...)。
CI .github/workflows/release.yml
- 推
v*tag 或手动workflow_dispatch;原六平台矩阵中,macOS 暂未启用(移植缺口未补齐),ARM64(windows-11-arm/ubuntu-22.04-arm)暂缓编译以显著缩短 CI 构建耗时;目前常态编译 x86_64(windows-latest与ubuntu-22.04)。 buildjob 各平台构建 + 归档产物(actions/upload-artifact);releasejob 汇总 → 计算 SHA256 生成latest.json→softprops/action-gh-release发布(含 latest.json)。- 无需任何签名 secret(校验仅 SHA-256)。
客户端(半自动):AppUpdaterCard 启动静默 update_check(默认源),发现新版弹常驻 toast
(动作按钮 invoke update_install);不自动下载。启动检查失败静默(未发 Release 时 404 属预期);
设置页手动检查仍如实报错。源留空即用默认(kv reinagent-update-endpoint 覆盖)。
未签名:macOS dmg/zip 无签名公证(首次需右键 → 打开);Windows/Linux 亦未签名。
自更新流加固与进度反馈(2026-10-08,Linux 截断 SIGSEGV 根治):
- 截断与损坏成因:历史版本在流式下载时无单 chunk 超时与断流保护,若因网络抖动中断落盘可能缺少尾部段表,运行截断的 ELF 导致动态链接器报
SIGSEGV (Address boundary error)。 - 全流程原子安全加固 (
self_update.rs):- 下载前生成同路径
.part临时文件,中断或失败立刻清理,并在cleanup_stale()启动时自动清理历史残余.part。 - 下载客户端配置 10 分钟全局超时,chunk 读取循环引入 30 秒超时保护,彻底消除连接假死挂起。
- 严格校验实际写入字节数与
asset.size(严禁字节不符),严格比对 SHA-256 哈希。 - 裸二进制合法性预检 (
verify_executable_binary):在转正前对可执行文件进行二进制结构校验:- Linux:ELF 魔数校验 (
\x7fELF),64位小端 ELF 校验段表尾部偏移e_shoff + e_shentsize * e_shnum <= file_size,截断文件在转正前立刻阻断; - Windows:PE DOS 魔数 (
MZ) 及 PE 签名偏移校验。
- Linux:ELF 魔数校验 (
- 只有所有预检全部通过才重命名转正为目标可执行文件并赋予执行权限;发生任何异常立刻清理临时文件并如实向用户提示完整错误。
- 下载前生成同路径
- 实时进度反馈与交互升级 (
AppUpdaterCard.tsx+SettingsPage.tsx):- 后端下载循环通过 Tauri 事件
update-progress实时向前端广播UpdateProgressPayload { percent, downloaded, total }。 - 界面整合:移除原下方独立的「应用更新」卡片及 URL 文本框,默认更新地址固定为
https://github.com/web3claw/ReinAgent/releases/latest/download/latest.json; - 「检查更新」按钮直接移至上方 ReinAgent 版本信息卡片的右上角,与应用标题/版本号水平对齐;
- 发现新版本时按钮支持一键「立即更新」,并在描述区下方展现更新日志与安装卡片;
- 点击下载更新后:
- 按钮禁用(防止并发重入或重复触发);
- 按钮文本变为「下载中」(
updaterDownloading); - 按钮内展示 SVG 圆圈进度条 (
CircularProgress),中间实时显示百分比数值{percent}%。
- 后端下载循环通过 Tauri 事件
验证:tsc 0;cargo check 0 警告;Rust 单测 self_update 6 例全部通过(包含版本比较、产物命名、代理分流、小文件拒绝、截断 ELF 段表检测预检);bun test 492 项全绿。
macOS 编译适配与验证(2026-10-10 修复完成)
历史问题根因与修复:
src-tauri/src/browser.rs:- 将原粗暴的
#[cfg(not(target_os = "windows"))]细化为#[cfg(target_os = "linux")],彻底避免 macOS 错误引用 Linux 专用的webkit2gtk、javascriptcore、cairo、gtkcrate。 - 针对非 Windows、非 Linux 平台增加安全优雅的降级保护(返回明确说明错误,不发生编译中断或崩溃)。
- 将原粗暴的
src-tauri/src/stt/mod.rs:- 清理了未在
Cargo.toml引入objc2相关依赖且悬空引用crate::services::stt::macos的废弃 macOS 权限请求代码; - 统一
stt_request_microphone_permission跨平台统一实现(与lib.rs中全局 Taurion_permission_request授权保持一致,无需平台专有胶水层)。
- 清理了未在
- CI 与多端验证状态:
- 独立测试分支:
feature/macos-build - GitHub Actions
Test macOS Build(macos-latestrunner,Apple Silicon M 系列)构建全绿通过(cargo check+bunx tauri build --no-bundle无错误)。 - 本地 Linux 回归验证:
cargo check0 警告、cargo test211 个单元测试全部通过、bun test497 项全绿、bun run build成功。 - Windows/Linux 零影响。
- 独立测试分支:
全软件网络请求代理规范(2026-10-10 统一收敛,Proxy & No-Proxy Enforcement Rule)
铁律:软件中所有网络请求,只要设置了代理,都必须走代理;并且必须根据「不使用代理的地址(no-proxy)」配置决议直连。
-
统一核心构建器(Rust 单点真实,
app_proxy.rs):build_proxied_reqwest_client(for_url, connect_timeout, total_timeout):统一构建 reqwest 客户端;build_proxied_ureq_agent(for_url, connect_timeout, total_timeout, user_agent):统一构建 ureq Agent。
-
全栈请求对齐清单:
- Cloudflare 隧道组件下载 (
remote_tunnel.rs):从 GitHub 下载cloudflared二进制走代理,彻底解决国内直连被墙转圈问题; - Skill 离线包下载与安装 (
skills/sources.rs):从 GitHub / ClawHub 下载 Skill 压缩包统一经build_proxied_ureq_agent; - Hub API 请求 (
hub_http.rs):前端扩展中心请求统一经build_proxied_ureq_agent; - MCP HTTP / SSE (
mcp.rs):build_http_client接入按 URL 决议,内网/局域网 MCP 走白名单直连,外网 MCP 走代理; - 客户端自更新 (
self_update.rs):检查更新与下载均走build_proxied_reqwest_client; - LLM 模型反代 (
llm_proxy.rs):请求上游模型统一按 kv 代理出网; - 前端出站 (
proxiedFetch.ts):模型列表/请求走tauri-plugin-http经 Rust reqwest 注入代理与matchesNoProxy; - 内置浏览器与终端 (
app_proxy.rs,terminal.rs):启动与 spawn 注入--proxy-server与--proxy-bypass-list环境变量。
- Cloudflare 隧道组件下载 (
-
直连与白名单匹配语义:
- 未配置代理时显式
.no_proxy()(不静默继承环境变量,对齐设置页优先原则); - 命中
reinagent-web-proxy-no-proxy(如localhost,127.0.0.1,192.168.*)时显式直连。
- 未配置代理时显式
-
代理配置非法时如实报错(No-Fallback),不静默降级直连。
任务胶囊归并口径改造(2026-10-08,用户定稿;取代 2026-10-06「按内容新增」)
问题:旧口径下每次 todo_write 内容不同即新开胶囊,导致同一批任务的中间状态
(0/6→3/6→6/6)堆积成一排「未完成」胶囊(用户报障)。
新口径(src/lib/chat/todoProgress.ts::extractTodoLists)——todo_write 是全量覆盖,
两次调用天然是同一份清单在推进:
- 末个胶囊未全部完成 → 原地更新(改状态/加删任务/改措辞皆就地刷新,不新开);
- 末个胶囊已全部完成 → 封存;之后再来含未完成项的清单才新开胶囊;
- 新的也是"全完成" → 原地更新末个(不新开空壳)。
- 不变式:除末个外其余胶囊必然都是已完成态 ⇒ 「超限优先藏已完成」总有可藏目标。
- 已确认取舍:中途放弃的半截未完成清单被后一份原地覆盖,胶囊区不留痕(时间线可回看)。
TodoListSnapshot新增completed字段;key改为条目内容集合签名(排序去重), 同一清单状态推进时 key 恒定 ⇒ 关闭态得以延续。
UI(TaskProgressBar.tsx):
MAX_VISIBLE_TODO_LISTS5 → 3;新增纯逻辑selectVisibleCapsules(lists, max)(在 todoProgress.ts,可单测):超限淘汰最老的已完成胶囊,进行中的末个永不藏;- 关闭签名版本迁移:新增 kv
reinagent-todo-dismissed-keys-ver = "2",检测到旧版 (无此键)即清空reinagent-todo-dismissed-keys(旧签名是「整份状态快照」, 在新 key 语义下永不匹配)——一次性清理,不回滚。
数据清理:已清空本机 kv reinagent-todo-dismissed-keys 的 11 条 v1 记录
(数据库备份 ~/.ReinAgent/conversations.db.bak-<ts>)。
验证:tsc 0;todoProgress.test.mjs 14 例通过(含原地推进/加删改措辞原地更新/
完成封存后新开/不变式/超限淘汰 4 例);test:chat 174 全绿。
代码块高亮开关下沉到条目状态(2026-10-08,修「提问卡提交后上方文本全变白」)
现象:回合内提问卡(ask_user_question)提交回答后,该轮上方已完成的文本里代码块的 Shiki 配色(黄/绿)全部消失、变成白色纯文本。
根因:高亮开关用的是整轮是否在流式(TurnGroupView 的 streaming prop = 该轮位于
liveTurn),而非该条文本自身是否已定型。提交回答后会话仍 status==="streaming"
(withPendingApproval 不改 status),整轮继续按 streaming=true 重渲染 —— 于是该轮内
所有早已产出完毕的段落都被判为「流式中」而跳过 Shiki 高亮,全部呈现白色。
修法(取代 7f88541 的「整轮运行兜底」):
TurnGroupView两条正文路径(中间叙述IntermediateText、运行中就地正文)改为streaming={entry.status === "streaming"}——只看该条自身是否还在流。ThinkingBlock正文同口径(highlightStreaming = entry.status === "streaming"); 折叠行为仍用active = streaming || turnRunning(与本修复无关,保持原样)。- 移除已冗余的
TurnGroupViewProps.streaming及MessageList的两处传参 (streaming={false}/streaming);MessageItem早已是message.status === "streaming", 与本口径一致。
新增不变式:条目一旦 done,其文本已由权威 turn_end 定格,即可安全上色,不必等整轮
结束;仅最后仍在流的那一条继续跳过(避免每帧重跑高亮 + 整块换 DOM 造成花屏,见 7f88541)。
验证:tsc 0;test:chat 174 全绿。⚠️ 视觉确认需实跑一轮「带代码块 → 提问卡 → 提交」
(本机未实测)。
v0.1.6 发布与 CI 构建矩阵精简(2026-10-09)
- CI 构建提速:
.github/workflows/release.yml暂时注释掉windows-11-arm与ubuntu-22.04-arm的编译目标,常态发布仅编译 x86_64(windows-latest与ubuntu-22.04),将多架构编译耗时由 20+ 分钟大幅压缩。 - 全套版本升级:
package.json、src-tauri/tauri.conf.json、src-tauri/Cargo.toml与src-tauri/Cargo.lock同步升级至0.1.6。 - 关于页更新体验优化:
- 彻底移除挂载时自动弹出的 Toast 浮窗提示(无未经用户确认的打扰性弹窗);
- 右上角更新按钮文案始终保持为「检查更新」(点击即触发检查,不再替换为「立即更新」);
- 检测到新版本后的详情展示与下载安装操作统一由下方的卡片面板承接。
静默自更新与左下角版本号状态机(2026-10-09)
- Rust 后端支持:
UpdateArgs扩展defer_restart: Option<bool>:支持下载、SHA-256 校验和原子原地替换完成后延后重启(避免打断用户当前操作);- 新增 Tauri 原生指令
update_restart,调用app.restart(),用户点击即可无缝重启到新版本。
- 全局更新状态控制器(
useAppUpdateStore):- 集中管理状态流转:
idle|checking|downloading|ready|error_check|error_download以及字节下载进度percent(0-100%); - 统一监听 Rust 侧派发的
update-progress事件,具备 Web Mock 防崩溃安全回退。
- 集中管理状态流转:
- 左下角状态组件(
SidebarVersionUpdater):- 软件启动时后台自动检测,发现新版本自动开启下载;
- 检查/下载中:展示圆圈进度圈,内置百分比数字(
0%~100%),悬停展示动态进度 Tooltip; - 下载完成待重启:展示绿色下载图标(对齐 LiveAgent 风格),悬停提示「立即重启更新」,点击直接重启生效;
- 获取失败/下载失败:展示黄色圆圈感叹号,悬停提示「获取更新信息失败,点击重试」或「下载更新失败,点击重新下载」,点击立即重新发起;
- 完整适配中英文双语字典 (
zh-CN/en-US)。
v0.1.7 发布(2026-10-09)
- 版本号升级:全套升级至
v0.1.7(package.json、tauri.conf.json、Cargo.toml、Cargo.lock)。 - 包含功能:包含启动自动检测更新、静默自动下载、左下角版本号动态状态机(进度圈百分比 / 绿色重启更新圆纽 / 黄色感叹号重试)及关于页交互优化。
v0.1.8 发布(2026-10-09)
- 版本号升级:全套升级至
v0.1.8(package.json、tauri.conf.json、Cargo.toml、Cargo.lock、useAppUpdateStore.test.mjs)。 - Linux 桌面图标与单实例穿透加固(彻底修复重启生成齿轮图标 & 点击 R 图标无法呼出软件问题):
main.rs与lib.rs显式固定 Linux GTKprgname与应用名为ReinAgent;- 单实例唤醒在 Linux 下增加短暂置顶穿透(
set_always_on_top短闪还原),冲破 GNOME 焦点防窃取,确保再次点击启动时能将后台已有窗口强制弹至桌面最前端。
v0.1.9 发布(2026-10-09)
- 版本号升级:全套升级至
v0.1.9(package.json、tauri.conf.json、Cargo.toml、Cargo.lock、useAppUpdateStore.test.mjs)。 - Linux 托盘唤醒与 Dock 鼠标转圈卡顿修复:
- 启动及唤醒时引入
gdk::notify_startup_complete()与原生 GTK 窗口调度; - 移除
tauri.conf.json中的enableGTKAppId: true,规避 GTK Application D-Bus 激活时因重复创建main窗口而导致的 Panic 崩溃。
- 启动及唤醒时引入
v0.1.10 发布(2026-10-09)
- 版本号升级:全套升级至
v0.1.10(package.json、tauri.conf.json、Cargo.toml、Cargo.lock、useAppUpdateStore.test.mjs)。 - Linux 启动 Panic 根除与单实例原生调度加固:
- 彻底移除
main.rs中在 GTK 运行时就绪前过早调用gdk::notify_startup_complete()导致的 Panic 崩溃,恢复应用秒级正常冷启动与更新自重启; - 使用标准的
tauri_plugin_single_instance::init接入单实例路由,并在聚焦逻辑中通过window.run_on_main_thread安全派发 GTK 原生deiconify()、present()以及gdk::notify_startup_complete(),彻底解决 Linux 最小化到托盘后点击 Dock 无法拉起、鼠标转圈及无法连续点击的问题。
- 彻底移除
Linux 单实例与启动调用精简化清理(2026-10-09)
- 冗余依赖与调用清理:
- 移除
Cargo.toml中多余的gdkcrate 外部依赖; - 移除
src-tauri/src/main.rs中与lib.rs重复的glib应用名设置; - 清理
src-tauri/src/lib.rs中focus_main_window的无效gdk::notify_startup_complete()与置顶 Hack,保留简洁健壮的原生 GTKdeiconify()与present()调度; - 单实例配置恢复保留 Linux Debug 模式的
dbus_id("com.web3claw.reinagent.dev"),确保开发版与 Release 版可同时并存运行调试; - 保留全局
SINGLE_APP_HANDLE供系统通知点击回调使用;所有前端测试与 Rustcargo check100% 通过。
- 移除
Windows 自更新 .old 临时文件清理修复(2026-10-09)
- 根因修复:
- 原清理逻辑误调用了用于寻找“未占用可用文件名”的
old_path_str,导致在.old存在时反向尝试删除不存在的.old-2,且前缀匹配误带短横线{name}.old-,致使真正的<exe>.old文件永远被漏删; - 修正为直接且显式删除
<exe>.old,并将目录遍历匹配前缀统一改为{name}.old,覆盖.old、.old-2、.old-3等所有衍生文件;
- 原清理逻辑误调用了用于寻找“未占用可用文件名”的
- 文件锁延迟保护:
- 针对 Windows 下新进程启动时旧进程退出可能存在的内核句柄释放延迟,在启动同步清理后,新增后台线程在 2 秒和 5 秒后延时重试清理,确保 100% 自动干净删除,不留残留文件。
v0.1.11 发布(2026-10-09)
- 版本号升级:全套升级至
v0.1.11(package.json、tauri.conf.json、Cargo.toml、Cargo.lock、useAppUpdateStore.test.mjs)。 - 包含功能与修复:
- 彻底解决 Windows 自更新重启后遗留
.old临时文件的问题,增加双重延时重试机制保障旧句柄释放; - 完成 Linux 单实例与启动调用代码精简,移除无用的
gdkcrate 依赖和置顶 Hack,保留简洁健壮的原生 GTK 窗口呈现调度。
- 彻底解决 Windows 自更新重启后遗留
远程访问公网支持(第一阶段:Cloudflare Quick Tunnel 免配置安全隧道 + 自建公网域名)(2026-10-09)
- 架构设计:
- 三模网络架构:
- 局域网直连 (
lan):保留原有模式,显示http://<局域网IP>:<端口>,仅限相同 Wi-Fi/局域网互通。 - Cloudflare 免配置安全隧道 (
cloudflare):无需公网 IP、免路由器端口映射、免注册任何账号。应用在后台拉起cloudflaredQuick Tunnel,自动分配全球 Anycast 边缘https://*.trycloudflare.com,提供权威端到端 HTTPS/WSS 加密,手机直接 4G/5G 扫码即用。 - 自定义反代域名 (
custom):面向拥有公网 IP / VPS / Nginx 反代 / FRP / Tailscale 的进阶用户,支持配置自定义外部公网 URL。
- 局域网直连 (
- 三模网络架构:
- Rust 后端实现 (
remote_tunnel.rs+remote_server.rs):cloudflared二进制管理:启动时优先检测系统PATH中的cloudflared;若缺失则按当前平台架构(x86_64/aarch64 Linux/macOS/Windows)自动从官方 Release 安全下载至~/.ReinAgent/bin/并赋予可执行权限;- 隧道子进程调度:使用
std::process::Command后台托管子进程,逐行捕获 stderr 中的https://[a-zA-Z0-9-]+\.trycloudflare.com临时公网 URL,向前端广播状态,退出/切换模式/关闭服务时优雅kill终止进程; - 公网安全加固(Fail2Ban & 真实 IP 解析):
- 接入
resolve_client_ip,优先提取CF-Connecting-IP与X-Forwarded-For头部的真实手机客户端 IP; record_auth_failure白名单严格豁免本地回环127.0.0.1、::1、localhost,防止公网隧道请求频繁试错误拉黑本地代理导致整站瘫痪;
- 接入
- 状态持久化与自动恢复:新增
KV_NETWORK_MODE与KV_CUSTOM_URL;应用重启时若远程服务处于启用状态,自动按保存的网络模式恢复(包括重新拉起 Cloudflare 隧道)。
- 前端设置页交互 (
RemoteAccessSection.tsx+i18n):- 提供局域网 / Cloudflare 隧道 / 自定义外部域名 3 张可视化模式切换卡片;
- 接入动态连接状态机(准备中/启动中旋转动效、已连接绿色徽标、异常状态提示与重试按钮);
- 动态计算并展示外网访问 URL,二维码同步实时渲染最新的公网访问地址;
- 中英双语完整国际化配置。
- 质量保障:
- 单测覆盖
TunnelNetworkMode转换、trycloudflare 域名正则匹配、resolve_client_ip真实 IP 优先级判定以及回环地址防误拉黑机制(全部 100% 通过); - 前端构建与测试套件 497 测试全绿,Vite 打包无任何类型错误。
- 单测覆盖
v0.1.12 发布(2026-10-09)
- 版本号升级:全套升级至
v0.1.12(package.json、tauri.conf.json、Cargo.toml、Cargo.lock、useAppUpdateStore.test.mjs)。 - 包含功能与优化:
- 远程访问正式支持 Cloudflare 免配置公网安全隧道(手机 4G/5G 扫码即连、端到端 HTTPS/WSS 加密);
- 支持自定义外部公网反代域名模式与局域网直连模式三模切换;
- 增强公网安全真实 IP 穿透审计与本地回环白名单保护;
v0.1.13 发布(2026-10-10)
- 版本号升级:全套升级至
v0.1.13(package.json、tauri.conf.json、Cargo.toml、Cargo.lock、useAppUpdateStore.test.mjs)。 - 包含功能与优化:
- 开启 macOS Apple Silicon (
aarch64-apple-darwin) 跨平台 CI 构建流水线,支持发布 M 系列 Mac 原生.dmg/.app安装包; - 修复多平台 Release 触发与资产打包规范。
- 开启 macOS Apple Silicon (
v0.1.14 发布(2026-10-10)
- 版本号升级:全套升级至
v0.1.14(package.json、tauri.conf.json、Cargo.toml、Cargo.lock、useAppUpdateStore.test.mjs)。 - 包含功能与优化:
- 全局出站网络代理统一规范重构 (
app_proxy.rs):- 统一将客户端内所有出站 HTTP/WebSocket 请求(LLM API 交互、远程模型列表抓取、自更新检查、Cloudflare 隧道组件下载、云端语音 STT、Web 抓取工具等)接入代理出网;
- 严格落实「不使用代理的地址」直连豁免规则(本地回环
localhost、127.0.0.1等直连); - 自动向内置浏览器及终端子进程环境变量注入代理配置,彻底解决国内网络环境下下载
cloudflared转圈及终端拉取依赖受阻的问题;
- 分支与标签维护:清理历史孤立分支
macos-build及历史测试标签,维护清晰发版线。
- 全局出站网络代理统一规范重构 (
v0.1.15 发布(2026-10-10)
- 版本号升级:全套升级至
v0.1.15(package.json、tauri.conf.json、Cargo.toml、Cargo.lock)。 - 包含功能与优化:
- 远程访问公网隧道 (
remote_tunnel.rs) 全面加固:- 世代守卫 (Generation Guard):在
TunnelProcessState引入generation: u64。启动与停止隧道时递增世代,stderr 读取循环及 EOF 退出判定处校验世代标识,杜绝由于快速重连导致旧线程竞争覆写新隧道状态(解决前端误报“隧道已断开”、掉二维码的问题); - 下载安全与流式防御:
- 改用
bytes_stream()分块流式落盘,设 200MB 硬上限防止异常/恶意代理无界推送 OOM; - 严格比对 HTTP
Content-Length,捕获并拒绝截断下载; - 采用动态唯一临时文件
.part.<pid>.<uuid>,任何异常路径立即清理,避免并发写冲突; - 增强二进制校验:体积下限 20MB + ELF/PE magic 头校验 + 就位前执行
<path> --version验证真实cloudflared组件标识;
- 改用
- 生命周期与僵尸进程回收:
stop_tunnel在kill()后执行child.wait()彻底回收僵尸进程;- 实现
TunnelProcessState的Drop;并在 Tauri 应用生命周期(RunEvent::ExitRequested/Exit)挂载同步停止钩子stop_tunnel_sync(None),防止 App 退出后后台孤儿进程持续暴露端口;
- 状态机与锁死防护:
- 全面容忍
Mutex毒化恢复(unwrap_or_else),采用静态OnceLock<Mutex<TunnelProcessState>>保证纯安全 Rust 实现; - 所有
app.emit广播均在全局状态锁释放后调用,避免死锁; - 失败路径统一调用
set_error,及时清理progress并将真实错误通知 UI,消除卡死在starting的假象; - 引入 45 秒 Watchdog 超时守护,超时未建立连接自动置为错误并提示用户。
- 全面容忍
- 世代守卫 (Generation Guard):在
- 子代理卡片交互与视觉优化 (
ToolCallCard.tsx):- 工具卡片支持
agent及subagent_output工具类型,点击可直接触发summaryAction并在右侧副边栏唤起子代理会话转录面板与回放详情; - 引入语义变量
--subagent-blue(亮色/默认暗色/OpenCode 配色下分别为天蓝#38bdf8与皇家蓝#0284c7),将「子代理」/「子代理输出」类型标签及 Bot 图标统一定制为鲜亮蓝色,彻底解决在 OpenCode 等特定消息配色下被意外覆写为橙色的问题;
- 工具卡片支持
- 聊天外链交互与系统浏览器右键菜单 (
MarkdownBlockRenderer.tsx+BrowserPane.tsx):- 聊天消息中的
http/https外链点击直接在侧边栏内置浏览器(BrowserPane)中打开,若内置浏览器已打开则自动平滑导航至目标 URL; - 链接右键菜单集成系统级原生菜单(Tauri 2
Menu.popup()),提供「在内置浏览器中打开」、「在系统浏览器中打开链接」(调起操作系统外部浏览器openUrl)以及「复制链接地址」。
- 聊天消息中的
- 远程访问公网隧道 (
v0.1.16 发布(2026-10-11)
- 版本号升级:全套升级至
v0.1.16(package.json、tauri.conf.json、Cargo.toml、Cargo.lock)。 - 包含功能与优化:
- 内置浏览器全宽最大化与三端兼容 (
BrowserPane.tsx+CodeViewerPaneHost.tsx+App.tsx):- 在内置浏览器顶栏地址栏右侧、关闭按钮(
X)前面新增最大化/还原切换按钮; - 未最大化时显示
Maximize2图标,Tooltip/aria-label 显示「最大化」(Maximize);点击后浏览器面板扩展至工作区全宽(向左完全铺满至左侧边栏边缘,占据中央主工作区); - 最大化后图标动态切换为
Minimize2图标,Tooltip/aria-label 显示「还原」(Restore);点击后无缝还原回用户之前拖拽设定的面板宽度(paneW); - 最大化期间隐藏左边缘的拖拽手柄,关闭浏览器或切换面板时自动重置最大化态;
- 容器层采用
relative flex-1 min-w-0宿主容器,最大化时以absolute inset-0 z-30铺满,下方聊天工作台 DOM 与滚动位置完整保留且不发生卸载;
- 在内置浏览器顶栏地址栏右侧、关闭按钮(
- 内置浏览器刷新转动动效与防重入生命周期 (
BrowserPane.tsx+browser.rs):- 刷新按钮点击后,图标由静止变为持续顺时针平滑旋转动画(
<RotateCw className="animate-spin ..." />); - 按钮自动进入
disabled状态(样式带有cursor-not-allowed opacity-60),禁止加载中重复触发; - Tooltip 与
aria-label动态切换为「正在刷新…」(英文:Refreshing…); - 后端在
WebviewBuilder中挂载on_page_load监听器,页面开始加载时派发browser-page-loading(true),加载完成瞬间派发browser-page-loading(false);并新增browser_reload原生命令; - 前端内置 15 秒超时守护(Watchdog)与异常捕获双保险机制,在断网、DNS 挂死或命令报错时自动重置
loading状态,彻底避免永久转圈卡死。
- 刷新按钮点击后,图标由静止变为持续顺时针平滑旋转动画(
- 全量侧边栏通用放大还原与铺满工作区 (
CodeViewerPaneHost.tsx):- 将最大化/还原能力全量扩展至右侧全部侧边栏面板:子代理侧边栏(
subagents)、Git 管理侧边栏(git)、文件管理器侧边栏(files)、代码/变更对比/审阅通用预览面板(file/patch/code-review) 及内置浏览器; - 所有侧边栏顶部标题栏关闭按钮(
X)左侧均对齐渲染最大化/还原按钮:未最大化显示Maximize2(最大化 / Maximize),最大化显示Minimize2(还原 / Restore); - 最大化时面板直接以
absolute inset-0 z-30铺满至左侧边栏右边缘,占据中央整个主工作区,并自动隐藏左边缘的拖拽把手; - 点击还原后无缝恢复至用户之前拖拽保存的宽度(
paneW),并恢复左侧拖拽调宽把手; - 切换面板类型或关闭面板时,自动重置回未最大化状态,保持多语言与亮暗主题语义变量完全一致。
- 将最大化/还原能力全量扩展至右侧全部侧边栏面板:子代理侧边栏(
- 子代理步数继承主 Agent 设置与 429 速率限制重试保护 (
subagentRunner.ts+runAgentTurn.ts+requestPreview.ts):- 步数继承主 Agent 设定:
SubagentToolDeps增加maxSteps?: number,主 Agent 调用点(runAgentTurn.tsfaux 分支与正常分支)将base.maxSteps完整透传至子代理工具;- 废弃原本过小的 6 步硬编码,将默认基准上限
SUBAGENT_MAX_STEPS放宽至 25(在未传入时作为兜底),彻底解决复杂调研任务因 6 步触顶过早而中断未产出终稿的问题; - 前台与后台分支统一将
maxStepsReached随finishRun沉降至 registry,供 UI 面板与下游模型完整获取触顶事实;
- 429 速率超限保护与指数退避断点续跑:
- 引入
isRateLimitError(errOrMsg),精确识别 HTTP 429、rate limit、too many requests、rate_limit_exceeded等多供应商速率受限错误; - 在子代理核心推进循环
driveRun中实现自动重试守卫:最多重试 3 次,采用指数退避算法(2s、4s、8s + 随机 jitter 抖动),并在用户主动中止时(signal.aborted)立即早退; - 自动消息清理裁剪机制(
sanitizeMessagesForRunTurn):在 429 发生重试时,自动剔除末尾不合规的未闭合 assistant 消息,回退至最后一条有效 toolResult 或起始 user 消息,保证接续调用 100% 满足runTurn契约并实现断点续跑; - 单测套件扩展(
subagentRunner.test.mjs):包含步数透传触顶测试、429 退避重试成功输出测试,全套 500 个单测及 Vite/tsc 构建全绿通过。
- 引入
- 步数继承主 Agent 设定:
- 子代理执行中蓝色高亮与 Bot 图标统一蓝色 (
global.css+ToolLayout.tsx+ToolSummaryRow.tsx):- 执行中蓝色流光:在
global.css引入.animated-gradient-blue-text,基于--subagent-blue语义变量使用color-mix产生柔和清透的蓝色呼吸渐变流光;在ToolLayout.tsx中识别子代理专属类名,运行中(isRunning)自动由默认灰白扫光切换为蓝色流光,使「子代理执行中」始终保持鲜明蓝色; - Bot 机器人图标高亮:在
ToolSummaryRow.tsx移除[&_svg]:text-foreground-subtlest强制选择器,确保子代理等组件传入的<Bot className="... text-[var(--subagent-blue)]" />能够原样呈现高亮蓝色,未指定颜色的图标继续继承默认浅灰。全生命周期(执行中与已完成)保持视觉纯正统一。
- 执行中蓝色流光:在
- 内置浏览器全宽最大化与三端兼容 (
v0.1.17 发布(2026-10-12)
- 版本号升级:全套升级至
v0.1.17(package.json、tauri.conf.json、Cargo.toml、Cargo.lock)。 - 包含功能与优化:
- 手机远控完整复刻 ZCode Web Remote Control (
src/remote-mobile/+remote_server.rs+remoteBridge.ts):- 独立移动端 React 19 应用作为第二 Vite 入口,内嵌静态打包(
include_dir!)同源静态托管; - 任务列表主页:工作区卡片、置顶分区、时间线分组、排序整理、未读圆点与状态徽标;
- 移动端聊天交互:双行大圆角工具栏 Composer(排队指示器、附件上传、审批模式盾、上下文进度环、模型与思考等级选择器)、Shiki 代码高亮、思考折叠、审批/问答/执行计划卡片;
- 协议 v2 与双向桥:11 项新请求操作、工作区列表与桌面状态主动推送、单 SQL 极速聚合持久化末回合状态;
- 独立主题与语言菜单:深/浅/跟随系统、中/英切换,本地 localStorage 持久化。
- 独立移动端 React 19 应用作为第二 Vite 入口,内嵌静态打包(
- 助手一键预设包 (
AssistantsPage.tsx+assistantBindings.ts+assistantDefs.ts):- 助手从纯身份设定升级为一键预设包:支持绑定模型(
providerId/modelId)、思考等级(thinkingLevel)、技能集(skills)及 MCP 服务集(mcpServers); - 选定助手时一键应用预设配置,后续手动调整仍可自由覆盖;
- 助手编辑器新增「预设绑定」区;内置 4 助手支持本地阴影编辑(Shadow Files)与一键重置为内置,编辑保存即时重新套用生效。
- 助手从纯身份设定升级为一键预设包:支持绑定模型(
- WebDAV 配置云备份与多端同步 (
webdav_backup.rs+backup_snapshot.rs+BackupSyncSection.tsx):- 纯 Rust 安全实现 WebDAV 传输层(坚果云 Jianguoyun 深度兼容、代理感知、敏感信息脱敏);
- 五大域配置快照(Providers、Assistants、Skills、MCP、通用设置与 KV 状态),带 Manifest 版本守卫、供应商 ID 映射与恢复前本地自动备份;
- 设备级配置、密码查看分离、双重验证 SHA-256、防抖自动同步与全域重新水合。
- 本地快照文件导入与导出 (
fs_cmd.rs+BackupSyncSection.tsx):- 支持将全域配置快照直接导出为本地备份文件(
.json); - 本地快照文件导入与预览:16MB 安全体积护栏、Manifest 结构校验、导入前信息确认弹窗(设备、时间、各域统计)、自动创建安全回退备份后整包应用并热重载。
- 支持将全域配置快照直接导出为本地备份文件(
- 手机远控完整复刻 ZCode Web Remote Control (
手机远控完整复刻 ZCode Web Remote Control(2026-10-11,方案 B:独立手机 React 应用 + 定向搬源码;CDP 全链路 E2E + 截图目检)
需求与定案:手机端任务列表页与任务对话页 1:1 复刻 ZCode Web Remote Control(样式+功能+交互)。调研结论(ZCode 源码实证):ZCode 手机端 = 桌面整个 React 应用经 IPlatformService 桥 + relay 复用(对话页是 58k 行桌面聊天栈,不可整搬);任务列表页(WebRemoteControlMobileTaskHome 等 ~2,400 行)自包含可直搬。用户拍板四点:方案 B(独立手机 React 应用 + 定向搬源码)、附件上传一期就做、手机端独立主题菜单、手机改模型/审批模式 = 改桌面任务配置。主题与语言:调色板菜单 = 设置入口,含主题(深/浅/跟随系统,reinagent-remote-theme)与语言(跟随系统/中文/English,reinagent-remote-locale)两组,菜单项 ZCode 勾选样式(选中项右侧 ✓ + 高亮底),选择按设备 localStorage 记忆、随选即时生效(CDP 三段实证:seed light+zh → 切 en → 恢复 zh+dark 全部跨刷新还原);语言默认跟随手机系统/浏览器语言(navigator.language,ZCode 网页同语义——浏览器上报英文即英文,可菜单强制中文)。
架构(对齐 ZCode web-remote-replayable + 本仓既有隧道):
- 手机前端 = 独立 Vite 第二入口
src/remote-mobile/(vite.remote.config.ts,root 指向该目录,outDir=绝对路径src-tauri/remote_assets/app,publicDir=false);产物经include_dir!编译期内嵌进二进制(build.rs兜底 create_dir_all + rerun-if-changed),Rustremote_server.rs同源静态托管/(index.html)与/assets/{*path}——局域网 / Cloudflare 隧道 / 自定义域名三模不变,无 CORS。构建接线:build:remote/build:all(beforeBuildCommand改 build:all)。⚠️ axum 0.8 通配符路由{*path}只捕获/assets/之后的段——Rust 侧须手动拼回assets/前缀。⚠️ 根路径响应必须显式text/html(mime_for_path 对无扩展名路径落 octet-stream,WebView2 把 octet-stream 顶层导航当下载拒绝 = ERR_ABORTED + isDownload,页面打不开)。 - 协议(remote_server.rs ServerMessage/ClientMessage,serde tag=op + rename_all=camelCase + rename_all_fields=camelCase):原有 subscribe/send/approve/stop 保留;新增 reqId 型 op(Result 帧应答):
list_workspaces(ZCodeWebRemoteControlWorkspaceListResult同形:workspaces/tasks/activeWorkspaceKey/activeTaskId)、switch_task、start_draft、mark_task_read、update_mobile_view_state(单设备语义空应答)、remove_queued、list_models、set_model、get_task_options、set_approval_mode、set_thinking_level;推送帧:workspaceList(列表变化推送)、desktopState(桌面活动任务/工作区变化)、state(富化:serializeEntry 全量行 + queuedMessages + pendingApproval,上限 500 行尾部)、result。⚠️ reqId op 的等待器实现绝不能跨 await 持有 client 互斥体——首版reply_request_over_ws在调用方持锁时再次client.lock()→ tokio Mutex 死锁,hello 之外全部 op 挂死;终态 = 调用方守卫回 Accepted →drop(guard)(drop 的是所有权,drop(&mut guard)只丢引用不释放锁,warning 同因)→request_over_ws(client, app, …)等待 webview 应答 →guard_send短锁回帧。 - 桌面权威桥(
remoteBridge.tsv2 全量重写):webview 内useAppStore+conversationPool为唯一权威。remote-bridge:request事件分发 11 类操作;store.subscribe(任务/项目/活动任务变化)→ 节流 300ms 推 workspaceList + desktopState;subscribePendingApprovals→ 挂起任务状态即时重推;need-state→ serializeEntry 行 +subscribeTask挂池监听(200ms 节流推送)。未读机制:内存 Map 已读水位(启动时全任务视为已读),unreadAt = updatedAt > 水位;mark_task_read / 桌面 setActiveTaskId / 手机发送都会清。displayStatus 映射(ZCode 末回合语义):getStreamingTaskIds→running(实时权威);其余取持久化末回合状态——新增 Rust 命令conversation_task_statuses(每任务最后一条 assistant 行 status,单 SQL 全任务)→ 桥侧缓存(任务签名变化即刷新+重推)→ error→错误 / done|stopped(含异常残留 streaming)→已完成 / 无行→空闲;未进池的历史任务同样显示真实结果(历史中的旧错误行不追溯)。⚠️ 命令 DTO 必须#[serde(rename_all = "camelCase")](TaskStatusRow 首版漏加 → 前端 r.taskId 全 undefined → 缓存恒空显示 idle)。工作区映射:projects →{workspacePath, kind:"local", workspacePurpose:"project"};无项目任务归workspaceIdentity="__conversations__"伪工作区(workspacePurpose="conversation",对齐 ZCode「任务/对话」组);workspaceKey = identity ?? workspacePath(ZCode resolveWebRemoteControlWorkspaceKey 同语义)。settings 直读走 plugin-store(settings.json,store 无此状态);set_model 草稿态写系统默认、任务态走 updateTaskModel(loadProvidersConfigFromDisk 校验 provider/model enabled,禁用如实报错)。 - 发送链(App.tsx):
sendNow/handleSend增opts.explicitTaskId/explicitProject(undefined=本地路径不受影响);window 桥换__reinagentRemoteSendV2({taskId|null, text, attachments})——附件经buildOutgoingPayload(与 Composer/编辑重发同一真源,视觉模型内联 image block / 非视觉降级路径引用)→ handleSend 全链路(UserPromptSubmit hooks / @提及 / 草稿提升 / 标题生成)。taskId=null = 远程草稿提升(start_draft已设置 explicitProject,经 window.__reinagentRemoteDraftProject 传递)。流式中发送走池 F1 语义自动入 steerQueue(手机 composer 显示排队列表 + remove_queued 撤回)。 - 附件上传端点:
POST /api/attachment?token=&taskId=(axum multipart,Cargo 加 feature)。落盘<任务工作区>/.ReinAgent/.temp/pasted/remote-<时间戳>-<名>(任务 payload 的 project 解析工作区根,无项目回退 DefaultProject),25MB/文件上限,文件名 basename 清洗(路径穿越/非法字符),复用fs_cmd::ensure_reinagent_gitignore保持仓库隐形。手机端uploadAttachments返回{path,name,kind}供发送内联。
手机前端(src/remote-mobile/,React 19 + Tailwind v4,独立构建不引桌面 CSS/store):
- 搬 ZCode 源码适配:
TaskHome.tsx(WebRemoteControlMobileTaskHome.tsx986 行逐字:header/说明卡/统计行/收起全部/整理方式 dropdown/排序/刷新/置顶区/工作区卡片/任务行/时间线分组/空态加载错误态/切换遮罩;裁剪 TaskWorkflowRunLines 与 SSH 重连);MobileShell.tsx(home/chat 双页 + history.pushState 回退 + 重连提示条 + mobileViewState 同步);taskHomeModel.ts(分组/两层排序比较器/偏好持久化/状态徽标类名直移);timelineGroups.ts(今天/昨天/daysAgo/周/月分组,locale 周起始日);relativeTime.ts、useTaskOpen.ts(跨工作区切换流程);i18n 键逐字拷贝(品牌名换 ReinAgent)。 - 自研对话页(ZCode 对话页=桌面聊天栈不可搬):
ChatView(任务头卡 + 贴底跟随时间线 + 审批卡浮层 + composer);Timeline(serializeEntry 行渲染:user 气泡+附件 chips / assistant 思考块折叠+Markdown+流式光标 / 工具折叠卡(toolKindLabel/toolArgPath/toolArgCommand 桌面纯函数复用)/ 压缩带 / 错误行);MobileMarkdown(react-markdown + remark-gfm + Shiki 异步高亮,流式中跳过);Composer(大圆角容器两行式,ZCode V4Composer 移动形态:上行输入区、下行工具行「+ 附件(Plus 图标)/审批盾(ZCode 弹窗同款:四项 = 计划模式 Lightbulb「编辑前先出计划。」/ 变更前确认 Hand「改动前先问我。」/ 自动编辑 ShieldCheck「自动编辑文件。」/ 完全访问 Shield「减少确认次数。」,图标+标题+描述两行+选中勾;触发器显示当前模式图标,完全访问 = 橙色 warning——ghost 变体 text-foreground 同特异性压过条件类,需text-warning!)/ 流式 spinner / 上下文容量(ZCode 面板同款:mini 进度环 + 点开完整面板——标题行「上下文容量 used/total (percent)」、品牌色进度条、六类分布点行(消息/系统工具/其他/系统提示词/技能/MCP 工具,首类大点其余按排名淡化)、平均缓存命中率脚行;数据 = 桌面 App 挂window.__reinagentContextUsage的 buildContextUsageData 快照,经 remoteBridge 随活动任务 state 帧推送(Rust BridgeStateArgs/State 帧全链路透传 context_usage,漏一层即 null),无快照如实空环+「本轮暂无用量数据」)/ 选择模型(Box 方块图标,弹层分组/上下文徽标/当前高亮)/ 思考等级(brain 图标,无附加指示条——用户定稿移除绿条)/中性方框停止⇄蓝色圆形发送」;左右对称 3+3 排布(ZCode 同款):左簇 = [附件 + 审批盾 + 排队指示器(Activity 图标 + 数量徽标,仅真实排队时显示)]、右簇 = [上下文环 / 模型方块 / 思考] + 发送(弹性间隔在左簇之后;流式不再单独渲染 spinner,运行态由停止键表达);排队占位「继续输入以排队后续修改」、附件上传、模型选择器(分组/上下文徽标/当前高亮)、排队列表×撤回;任务头卡同步对齐 ZCode=图标+标题截断,去工作区副标题与运行徽标);ApprovalCards(审批 allow/always/reject、提问卡多 Tab 自动跳题、计划卡 approved/reject+feedback——决策协议与桌面完全一致,计划批准由桌面桥先切 ask 再 resolve——与桌面 PlanModeCard 同语义);ThemeMenu(system/dark/light,localStoragereinagent-remote-theme,system 跟随 prefers-color-scheme)。 - 主题 token(remote.css):ZCode zai-dark/zai-light 色板逐项落
--zcode-*(neutral/sky/green/red/yellow oklch)+@theme inline映射 ZCode 语义类(bg-background/text-foreground-subtle/bg-card-border…);--ui-font-size: 14px(ZCode 同值)+ text-ui-* 刻度;@custom-variant dark跟随 data-theme;.md手机版排版;i18n = 自包含双字典(不引桌面 i18n → 不拖 useAppStore/conversationPool 依赖链进手机包;桌面共用键 approvalCard*/planCard*/ask* 已复制)。产物 1.9MB(shiki 语言 chunk 懒加载)。 - 连接状态机(api/client.ts):connecting→authenticating→paired;断开 reconnecting(保页面保输入态,指数退避重连 + visibilitychange 回前台恢复);鉴权失败→failed 错误页(ZCode failure reason 子集);reqId Promise 匹配(⚠️ 服务端先 accepted 后 result——只认 result 帧);hello 后自动拉 list_workspaces + list_models;token 经 URL → replaceState 清参 → HttpOnly Cookie 兜底刷新。
验证:bun run test:remote(新脚本,taskHomeModel/timelineGroups 11 用例)+ tsc 0 + 前端全套绿(chat 174/providers 38/agent 76/hub 76/git 7;test:import 37/38——WorkBuddy fixture 1 败为存量问题,干净 HEAD 同样失败)+ cargo 217。CDP 全链路 E2E(bun WS 客户端 + 内置浏览器子 WebView 加载手机页):hello/list_workspaces 契约/subscribe 状态推送/switch_task(activeTaskId+desktopState 推送)/start_draft(null)/mark_task_read/上传落盘+gitignore/带附件发送(真实模型回合 user 行+user_attachments part)/stop/get_task_options+set_approval_mode/list_models(7 供应商)/错误 token 拒绝(1002)/手机页真实渲染(首页卡片与任务行、对话页 490 行时间线、composer 全按钮)+ 截图目检与 ZCode 参考逐项比对。冷任务加载态:手机点开桌面本会话未打开过的任务时,桌面池先从库水合(subscribeTask→ensureEntry 自动拉 500 行),窗口期手机显示「消息加载中…」spinner(首帧未到不再误显空态文案)。「对话」组任务点开空白真因(2026-10-11 用户报「先开别的任务再开它才能出来」):desktopState 帧是签名去重的变化推送,新连接/中间换任务时会错过——手机 ChatView 的 activeTaskId 恒为旧值/null,subscribe 从未发出;而观察者 WS 与桌面池都正常(三方取证:页面 diag taskStateKeys=[]、观察者 6 行帧、池 6 行)。修复双管:① client.ts workspaceList case 同步 desktopState(workspaceList 每次都带 activeTaskId,幂等兜底);② 顺带根治同连接帧阻塞(request_over_ws 等待改 spawn 独立任务——原先 list_workspaces/list_models 的 15s webview 应答等待会把同连接后续 subscribe 串行堵死)+ Subscribe arm 空帧不广播(保留加载态)+ need-state 后 800ms 补针(水合竞态)。页面级复现验证:点「你好」1s 内渲染。E2E 抓出并修复 5 个真 bug:①reqId 等待器持锁死锁;②axum 通配符丢 assets/ 前缀;③根路径 octet-stream 被当下载;④手机端空 initialResult 跳过加载(水合时序);⑤(调研期)测试二进制 0xc0000139。
⚠️ tauri common-controls-v6 铁坑(Windows cargo test):tauri 2.x 默认启用 common-controls-v6 → muda/rfd 静态导入 TaskDialogIndirect(仅 comctl32 v6 导出);tauri-build 的 winres 清单只嵌 bin 目标,cargo test 测试二进制无清单 → 加载即 0xc0000139 (STATUS_ENTRYPOINT_NOT_FOUND)(此前疑似因主要在 Linux 侧跑 cargo test 而未暴露)。修复 = Cargo.toml tauri = { default-features = false, features = ["wry","compression","dynamic-acl","x11","dbus","tray-icon","unstable"] }(其余默认特性全保留;bin 仍由 tauri-build 嵌 v6 清单,视觉不变,仅罕见运行时错误对话框回退 MessageBoxW)。诊断手段:python 解析 PE 导入表逐项比对系统 DLL 导出表。
边界(一期明确不做):附件预览回显缩略图(发送即内联)、子代理/后台工作浮层手机版、多设备 mobileViewState 持久化、iOS PWA 元信息。
助手 = 一键预设包(2026-10-11 用户定稿;取代 2026-10-02「纯人设」收敛)
- 语义(用户三点确认):选中某个助手(任务 chip 或助手页设为全局默认)的那一刻,该助手携带的模型/思考级别/技能/MCP 一次性套用;之后用户在任务里手动改动以手动为准(不持续锁定);未绑定的维度维持现状不动(通用助手无绑定 = 什么都不改)。
- 数据层(
assistantDefs.ts):frontmatter 扩可选model("providerId/modelId" 单行)、thinkingLevel、skills(逗号分隔)、mcpServers(逗号分隔);解析宽松(空串/空列表 = 未绑定)、序列化省略未绑定维度、旧文件零迁移兼容(旧 model/thinkingLevel 行现在按绑定读取)。 - 套用逻辑(
assistantBindings.ts纯模块,buildAssistantBindingsPlan):模型/思考写任务记录(沿用 providerId/modelId/thinkingLevel 字段,chip 如实显示);技能 = 整组替换hubSettings.skills.selected;MCP = 整组替换启用集(绑定清单内启用、清单外禁用);非法值(无斜杠 model、未知 thinkingLevel)跳过(No-Fallback)。套用后 toast 摘要「已应用助手绑定:模型 / 思考级别 / 技能×N / MCP×N」。 - 接入点:App
AssistantChip.onPick(任务作用域:绑定写当前任务)与助手页applyGlobal(全局默认路径:仅套用技能/MCP,任务字段留给后续任务的 chip 应用);编辑器保存即重新套用(2026-10-12 用户实测「在助手编辑里勾了 browser-use,上下文面板技能仍 0%」):根因 = 编辑 ≠ 生效——general 本就是全局默认,不会再点一次「设为全局默认」,套用从未发生。修 =save()末尾检测「保存的助手 == 当前全局默认」时立即applyBindings(def, null)重新套用(技能/MCP 即时跟上,模型/思考留任务作用域);任务作用域的重新套用走聊天页 chip。远程桥switch_task后的助手应用走同一 buildAssistantBindingsPlan。⚠️ MCP 清单标签 = 服务器 id(与 MCP Hub 卡片一致,description 悬浮提示——用户报「显示名字不一样」后修正)。 - 编辑器(AssistantsPage):新增「预设绑定」区;内置 4 助手同样可编辑(2026-10-12 用户两轮定稿:编辑按钮对内置开放,保存为同名用户文件遮蔽内置;编辑后的内置仍归「内置助手」组——
listUserAssistants对与内置 id 同名的用户文件保留builtin: true标记(isBuiltinShadowId纯判定,可测),绝不跑进自定义组;编辑后的内置不可删除,改提供「恢复出厂预设」按钮(RotateCcw,删遮蔽文件即回到编译期内置);general 永不可删。原「内置不可编辑删除」最终语义 = 内置恒在内置组、不可删除、可编辑、编辑后可重置——模型下拉(启用供应商×启用模型,loadProvidersConfigFromDisk 同源)+ 思考七档下拉 + 技能多选清单(discoverSkills 同源)+ MCP 多选清单(已配置服务器,标签 = 服务器 id 与 MCP Hub 卡片一致,description 作悬浮提示);未绑定选项为「(不绑定 — 维持现状)」。 - 测试:
test:assistants16/16(frontmatter round-trip/空绑定/旧文件兼容 + bindings plan 全绑定/无绑定/非法值/null);CDP E2E 实证(写绑定助手 → chip 应用 → 任务 model/thinking 变化 + hubSettings 启用集变化 → 清理还原)。
WebDAV 配置备份与同步(2026-10-12,LiveAgent 同构移植;本地 mini-WebDAV CDP E2E 全链路通过)
需求与定案(用户四点拍板):设置页新增「备份与同步」——把配置打成按域聚合的 JSON 快照经 WebDAV 上传/恢复,跨设备迁移配置。域范围五域:providers(provider_config.json 全量含明文 API key)、mcp(mcp_servers.json 全量)、assistants(~/.ReinAgent/assistants/*.md 文档打包成 [{id,name,content}])、skillsSelection(kv reinagent-skills-settings 的 selected 清单,只备清单不备技能本体)、system(plugin-store settings.json 的 settings 键白名单子集 = provider/apiKey/modelId/baseUrl 四键)。明确排除:任务/项目(内嵌工作区绝对路径)、conversations.db 其余 kv(UI 设备态)、记忆库(数据非配置,二期候选)、stt 段、checkpoints、插件、远控配置。自动同步 = 仅自动上传(下载永远手动,防静默覆盖本机);profile 概念保留(同一账号多套配置隔离,默认 default);明文上传(manifest encryption 字段预留 "none",后期加密不破坏格式)。
架构(LiveAgent 四文件 1:1 同构,分层不变):
- 传输层
webdav.rs:只做 PROPFIND(Depth=0 探测,不解析 XML,看状态码)/MKCOL(乐观逐级建,405/409/3xx 回落 PROPFIND 确认)/PUT/GET 四动作;禁跟随重定向(config 含明文 API key,307/308 会重发到陌生主机;且 302→200 会让测试连接假成功);走app_proxy::build_proxied_reqwest_client(全软件代理规范铁律);URL 日志脱敏(剥 userinfo/query);坚果云特判文案(应用密码/必须在 /dav/ 下/顶层目录手动建,国际版 nutstore 域名同识别,按域名边界匹配防jianguoyun.com.evil.test误判);GET 流式读带上限(不信 Content-Length);404 → Ok(None) 区分「远端无备份」。 - 快照层
backup_snapshot.rs:采集/校验/序列化/应用。manifest 内联在文档_manifest字段(单文件自包含),版本门 fail-fast(protocol/schema 高于支持值一律拒绝,防把读不懂的数据当空配置静默清库);结构校验(各域形状 + 助手 id 文件名安全:拒空/斜杠/..);应用前backup_current_config()把当前配置存~/.ReinAgent/backups/config-<毫秒>.json(保留 10 份,字典序=时间序)。 - provider id 重映射(导入关键):自定义供应商 id 是设备本地随机(
custom-<时间戳>),快照内 system 默认模型指针与助手 frontmattermodel: "providerId/modelId"都引用它。两级配对:①id 相同(预设槽位/已同步设备)②baseUrl+name 全同且两侧唯一(归一化去尾斜杠;同端点多候选猜不准不配,宁可保留源 id 不张冠李戴)。改写范围:providers[].id + system.provider + 助手文档 frontmatter 的 model 行(只动 frontmatter,正文不动)。 - 编排层
webdav_backup.rs:同步配置存~/.ReinAgent/backup_sync.json(设备级不进快照——随快照流转会 A 机凭据覆盖 B 机成环;也刻意不放 kv——前端全量缓存进 webview 内存,密码摊在 webview 侧);命令只回无密码视图(hasPassword+ 掩码占位),passwordTouched=false沿用盘上旧值(防掩码占位符当新密码写盘,cc-switch 真坑);远端布局{remote_dir}/v1/{profile}/config.json+manifest.json,远端 manifest 多带 config 的 size+sha256(先 PUT config 后 PUT manifest——manifest 是「可用」信号,中断留下旧 manifest+新 config 会被 sha 校验拦下);全局 tokio Mutex 串行化远端读写,锁必须在采集快照前获取(否则手动下载可挤进采集与 PUT 之间,把刚拉下的配置盖回远端);下载=拉 manifest→拉 config→校验 size+sha256(缺字段一律当损坏,不跳过)→抑制守卫内应用快照→backup:applied事件。路径清洗拒./..段(防穿越:上传=明文 key PUT 到非预期路径,下载=从非预期路径读回当配置)。 - 自动同步(
start_auto_sync,setup 启动):标脏 → mpsc(1) 合并 → 防抖 1s/硬上限 10s(逐字输入 API key 不能无限推迟)→ 仅上传。前端五个标脏点:saveProvidersConfigToDisk(provider 保存)、hubSettingsStore.setSettings/updateMcpOps(skills kv + mcp 文件)、AssistantsPagesave/resetBuiltin/remove(助手文档)、useSettingsstore.save 成功后(系统默认模型)。抑制计数器 RAII(引用计数,嵌套安全):下载应用期间标脏无效,防把刚拉下的数据原样推回;防抖窗口撞抑制期 → 绕过 mark_dirty 直接投递(防抑制窗口内改动永远丢上传,代价是抑制期每秒空转一次)。自动失败落盘last_error(用户多半不在设置页,事件随页面卸载即丢),手动成败由命令返回值当场反馈。 - 前端:
src/lib/backup/backupSync.ts(命令薄封装+类型)+src/components/settings/BackupSyncSection.tsx(设置页新 tab「备份与同步」,nav 在远程访问之后;连接表单/测试连接/立即上传/从远端恢复——恢复前fetch_remote_info弹 useConfirmDialog 展示来源设备/时间/各域条目数/覆盖警示;自动同步状态横幅随backup-sync-status-updated事件刷新)。恢复后前端重载(App.tsx 监听backup:applied):reloadKvFromDisk(db.ts 新增,合并语义=写透队列 pending 优先)+hydrateMcp+ skills 切片按 kv 重写 +useSettings.reload()(新增)+ providers 重读 +invalidateAssistantCatalog。缺这步用户看到旧内存态、下次编辑就把还原前的配置整域写回盘。 - 恢复语义(与 LiveAgent 一致):备份是恢复手段,不是同步镜像——快照内文件落地,本机快照外多出的文件(如新装的助手)不删除;system 域只叠加白名单键不整键覆盖(settings.json 还有 stt 等设备段)。
接线:lib.rs 三 mod 声明 + 7 命令(backup_load_sync_config/backup_save_sync_config/backup_test_sync_connection/backup_fetch_remote_info/backup_upload/backup_download/backup_mark_dirty)+ setup 注册句柄与启动自动同步任务;零新增 Cargo 依赖(reqwest/sha2/uuid/chrono/dirs/urlencoding 全是既有依赖,app_proxy::kv_db_path 改 pub 复用)。
验证:cargo test 239/239(新增 19:webdav 12 + backup_snapshot 7 + webdav_backup 6 去重后;join_url/路径清洗/坚果云文案/凭据不回显/manifest 版本门/畸形域拒绝/重映射三级/快照 round-trip/密码沿用/sha 校验/抑制引用计数/通道合并);tsc 0 + 前端全套绿(assistants 16/settings/providers 38/hub 76/chat 174)。本地 mini-WebDAV CDP E2E 全链路 17 断言全过:保存配置+测试连接(正/反向:错误密码报「认证失败」且不回显密码)/passwordTouched 沿用旧密码/空远端 fetch=null/上传→远端摘要(deviceName=THINKBOOK14、五域条目数、sha256)/制造三域差异(marker provider/助手/MCP)→恢复→三域标记全部还原+本地备份文件生成(backups/config-*.json)+远端 sha 不变/恢复语义=快照内文件落地不删快照外文件/UI 组件与 i18n 双语键齐备。E2E 环境:node mini-webdav.cjs(PROPFIND 207/MKCOL/PUT/GET + Basic Auth)+ WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS="--remote-debugging-port=9333" 起 dev + bun/node WS CDP 直接 invoke 命令层。
备份快照导出/导入文件(2026-10-12 增量;本地 CDP E2E 8/8)
- 动机:无 WebDAV 用户的手动迁移通道 + 远端备份损坏时的应急恢复。复用 WebDAV 快照同一采集/校验/重映射/应用链路,只换存取位置。
- Rust:
fs_cmd.rs新增通用对话框命令fs_pick_save_path(rfdsave_file+ set_file_name + add_filter)与fs_pick_open_file(rfdpick_file+ filter);webdav_backup.rs新增三命令:backup_export_to_file(collect + serialize → 写用户选定路径,返回 manifest 供摘要)、backup_import_preview(只读校验:大小上限 16MB + parse_backup_document 版本门/结构校验,不写盘——非本应用导出/高版本/畸形文件在预览即被拦下)、backup_import_from_file(校验 → 抑制守卫内 apply_backup_snapshot(含 provider id 重映射 + 应用前自动本地备份)→ emitbackup:applied走既有前端重载链)。导入语义与 WebDAV 下载完全一致:显式恢复动作、不触发自动上传、快照外文件不删除。 - 前端:BackupSyncSection 手动操作区加「导出到文件…」(FileDown)与「从文件导入…」(FileUp)两按钮;导入 = 选文件 → preview → useConfirmDialog 确认框(文件路径/来源设备/时间/各域条目数/覆盖警示)→ 应用 → toast 摘要。i18n 增 6 键(backupExportFile/backupExportOk/backupImportFile/backupImportConfirmTitle/backupImportFileLabel + 英文对应)。
- 验证:cargo 241/241(新增 roundtrip via file + oversized/garbage 拒绝 2 测试);tsc 0;前端套件绿。CDP E2E 8/8:导出(manifest 真实条目数)→ 制造三域差异 → 预览校验(marker 不在快照中)→ 畸形文件拒绝 → 导入应用(本地备份文件生成)→ provider/MCP marker 全部还原。E2E 残留(临时导出文件/marker 文件)已清理。
侧边栏版本号点击检查/下载更新与悬停提示(2026-10-12)
- 交互体验升级:
- 左下角侧边栏底部的软件版本号(如
v0.1.17)由原本的纯文本展示升级为可交互按钮(group/tt+cursor-pointer); - 点击立即检查与下载:点击版本号直接调用
checkAndAutoDownload(),执行逻辑与软件冷启动时的自动检查/静默下载完全同构;触发后组件平滑切入原本的进度圆圈、绿色下载就绪或黄色重试状态机; - 悬停 Tooltip 提示:鼠标移动悬停在版本号上方时,自底向上弹出半透明气泡,提示「检查和下载更新」(英文环境展示「Check and download updates」);
- 完整适配 Tailwind CSS v4 语义化主题变量(
var(--text-dim)、var(--text)、var(--sidebar-hover)、var(--bg-elev)、var(--border))及中英文多语言词条(updaterCheckAndDownload)。
- 左下角侧边栏底部的软件版本号(如
- 验证:新增单测
SidebarVersionUpdater.test.mjs,全仓单测 522/522 pass,tsc && vite build构建通过。
Computer Use (CUA) 桌面操作驱动 1:1 复刻 LiveAgent(2026-10-12)
- 需求与架构定案:
- 完整复刻 LiveAgent 的 Computer Use(CUA 桌面操作驱动)能力与管理面板:包含主卡片启用总开关、60 项能力简介、三步走开始引导卡片(1. 安装驱动 -> 2. 系统授权 -> 3. 启用)、管理设置弹窗与驱动安装步骤弹窗;
- 管理设置弹窗 1:1 对齐:
- 工具审批策略三档单选
[允许 | 询问 | 拒绝](受管工具执行前统一鉴权拦截); 允许操作 ReinAgent 自身防自指开关(关:防止 Agent 误点本应用产生死循环交互);- 单次调用超时四档单选
[30s | 60s | 120s | 300s](防止操作挂死,超时直接熔断); - 6 项能力概览卡片带小蓝点提示;
- 底部风控安全说明与
trycua/cua ↗上游驱动仓库外链。
- 工具审批策略三档单选
- Rust 后端实现 (
src-tauri/src/cua_driver/mod.rs):cua_driver_probe:探测各平台已知路径(Windows%LOCALAPPDATA%\Programs\cua\cua.exe/ macOS/usr/local/bin/cua等)及cua --version与 manifest;cua_driver_install_command&cua_driver_install:生成安装命令,流式执行安装并在cua_driver_install_progress事件中实时派发输出日志;cua_driver_permissions_status&cua_driver_permissions_grant:macOS TCC 辅助功能与屏幕录制授权检测及唤起,Windows/Linux 平台返回免授权默认值;cua_driver_self_identity、cua_driver_self_window_rects、cua_driver_frontmost_pid:宿主自省能力,获取自身 PID、前端逻辑矩形及前台进程 PID,为防自指守卫提供真实数据支撑。
- 前端核心逻辑与自卫沙箱 (
src/lib/mcp/):cuaSelfGuard.ts:实现自卫守卫拦截器(PID 拦截、屏幕矩形命中拦截、无目标键盘输入的前台检查拦截、出参敏感信息与 window_id 动态清洗与学习);cuaDriverForm.ts:超时值安全钳制(10s ~ 10min,默认 60s)、探测缓存 60s TTL、命令漂移检测与对齐对准、kv 持久化助手;mcpTools.ts:在execute执行链前置嵌入 CUA 策略鉴权与guard.refuse,后置挂载guard.strip;mcpServerDefaults.ts:注册CUA_DRIVER_SERVER_ID = "cua-driver"常量并在 MCP Hub 隐藏受管驱动,避免配置重叠。
- 设置页与 UI 实现 (
src/components/settings/):SettingsPage.tsx:导航项新增cua(Monitor 图标,双语标题「Computer Use」),挂载CuaDriverSection;CuaDriverSection.tsx:深度集成 Tailwind CSS v4 语义变量与Dialog、Switch、Button组件,完整还原主面板、三步向导、管理设置与安装进度弹窗;src/i18n/index.ts:补充中英文双语字典(cua*系列键)。
- 验证:
- 新增单测
cuaSelfGuard.test.mjs(9/9 pass) 与cuaDriverForm.test.mjs(7/7 pass); - 全仓单测 538/538 pass;
- 前端
bun run build构建通过; - Rust
cargo check退出码 0。
- 新增单测
十八、官网静态站(GitHub Pages,2026-10-13 落地)
docs/ 目录同时承担两个职责:项目内部文档(本白皮书、PROMPTS、archived/ROADMAP)与 ReinAgent 官网静态站根目录。站点由 GitHub Pages 以 GitHub Actions 方式部署(.github/workflows/pages.yml,path: docs),线上地址 https://web3claw.github.io/ReinAgent/。
站点结构
- 页面骨架与设计系统:
docs/assets/site.css(语义化 CSS 变量、明暗主题、卡片/文档/代码块组件)与docs/assets/site.js(主题切换、移动端导航、代码复制、截图灯箱、访问计数、版本号拉取)改编自 Hetu 官方站(Apache-2.0),署名见docs/ATTRIBUTION.md。 - 首页:
docs/index.html,hero + 六张能力卡 + 界面画廊 + 两种跑法 + 文档卡片。 - 功能文档:
docs/guide/*.html,共 20 页(功能文档 8、深入实现 4、设计与运维 5、工程文档 3),侧栏按分组列出并自动高亮当前页。 - 工程文档页:
guide/PROJECT_CONTEXT.html、guide/PROMPTS.html、guide/ROADMAP.html由仓库 Markdown 原文用Bun.markdown.html()渲染生成,Markdown 内的相对链接(./PROMPTS.md、src/**等)在生成阶段重写为站内 HTML 页或 GitHub 绝对链接。 - 英文落地页:
docs/en/index.html(文档正文目前仅中文,英文页为现状说明)。 - 图片:
docs/assets/workbench-subagents-demo.png为仓库自有实测工作台与多 Agent 协作高清图;旧版docs/ReinAgent.png已移除,所有展示图与 Open Graph 分享图统一指向最新资产。
生成与维护
- 生成脚本:
.ReinAgent/tools/build-site.js(站点生成器,产物写入docs/,不污染工作区与项目根)。 - 同步约定:修改
README.md、PROJECT_CONTEXT.md、PROMPTS.md或archived/ROADMAP.md后,若需官网同步,重新执行bun run .ReinAgent/tools/build-site.js并提交docs/产物。 - 链接校验脚本:
.ReinAgent/tools/check-links.js(站内链接存在性 + 品牌残留检查)。 - 本地预览:
python3 -m http.server 8000 --directory docs。
部署前置
仓库 Settings → Pages 的 Source 必须设为 GitHub Actions(分支部署只支持 / 或 /docs 根,且根目录 index.html 已被 Vite/Tauri 应用占用)。
十九、隧道子进程单进程纪律(2026-10-13 落地)
问题现象
系统上堆积多个 cloudflared.exe(均由 ReinAgent 拉起、指向 127.0.0.1:<远控端口>)。
根因(src-tauri/src/remote_tunnel.rs)
st.child仅存内存句柄,无按进程名清理逻辑。应用非正常退出(dev 模式 Ctrl+C、任务管理器强杀、崩溃)时子进程成孤儿,永久残留;restore_on_startup每次启动又拉起新隧道,孤儿逐次累积。remote_server_config以火忘式tauri::async_runtime::spawn调起 start,快速连点会让两个任务都在对方登记 child 之前跑完stop_tunnel,前一个句柄被覆盖后彻底失联(多线程运行时下真实可复现)。- Windows 未挂 Job Object、Linux 未设 PDEATHSIG,父进程一死子进程必然孤岛。
四条防线(相互独立,任一条生效即可避免堆积)
| 防线 | 实现 |
|---|---|
| 启停串行化 | TUNNEL_OPS(tokio::sync::Mutex)串起 start_cloudflare_tunnel / stop_tunnel 全链路,消除交错窗口 |
| 精确孤儿回收 | reclaim_orphan_tunnel():PID 文件(~/.ReinAgent/bin/tunnel.pid)+ 进程名双重校验,只回收本应用遗留且仍在跑的 cloudflared |
| 宿主连坐 | Windows:Job Object + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE(裸 FFI 声明 kernel32,含编译期布局断言);Linux:pre_exec 内 setsid() + prctl(PR_SET_PDEATHSIG, SIGKILL) |
| 退出兜底 | stop_tunnel_sync 挂 RunEvent::ExitRequested / Exit,终止并回收句柄、清理 PID 文件 |
关键设计约束
- 绝不按进程名批量清杀(不用
taskkill /IM cloudflared.exe /F):会误杀用户在终端手动运行的 cloudflared。回收一律走「PID 文件 + 进程名校验」精确路径。 - 双重校验不可简化:PID 会被系统复用,只校验存活可能误杀复用了该 PID 的无关进程。
- Linux 侧
is_alive_cloudflared必须挡掉pid == 0与pid > i32::MAX——kill(2)会把0解释为「当前进程组」、-1(u32::MAX)解释为「所有进程」,两者都会「成功」,导致不存在的 PID 被误判为存活(该缺陷由单测test_is_alive_cloudflared_false_for_dead_pid捕获)。有/proc时以/proc/<pid>/comm为权威判据。 - Windows FFI 结构体(
JOBOBJECT_EXTENDED_LIMIT_INFORMATION等)与 SDK 逐字节一致:BasicLimitInformation64B、IoCounters48B、整体 144B,已加编译期断言(仅 64 位目标)。 - Job 创建/绑定失败一律降级为不绑定并留日志,不中断隧道(隧道可用性优先,孤儿风险由 PID 回收兜底)。
已知限制:域名无法固定
Quick Tunnel(cloudflared tunnel --url)的 *.trycloudflare.com 域名由 Cloudflare 随机分配,无法固定,这是官方产品限制。需固定域名须改用 Named Tunnel(cloudflared tunnel run --token <token>,需 Cloudflare 账号 + 托管域名),当前版本未实现。custom 模式为「用户自备反代」语义:ReinAgent 只拼接展示自定义 URL,自身不拉起隧道。
验证
- 单测 9 项(PID 文件往返/拒垃圾、进程名拒非 cloudflared、死 PID 判否、回收幂等、世代守卫等)全绿;全仓 247/247 通过;
cargo clippy本文件仅余 1 条既有告警。 - Windows 分支本机未能编译验证(缺 MSVC 工具链,交叉编译失败于
lib.exe):Job Object FFI 经独立子代理逐字段核对 SDK 绑定确认正确,最终编译/运行验证需在windows-latest上进行。
二十、自进化研发体系(对标 magpie,2026-10-13 落地)
机制背景
全面借鉴 magpie 项目的自主闭环研发模式,解决大型项目中 Agent 规则过度静态膨胀、缺乏量化教训跟踪以及单元测试容易出现“假绿灯”的问题。
核心组成(三位一体)
-
总路由表(
AGENTS.md):- 极简启动路由:
AGENTS.md(铁律与路由) →docs/LESSONS.md(动态错题本) →docs/PROJECT_CONTEXT.md(单点真相)。 - 明确定位子系统目录路径,避免每次在根规则中堆砌代码细节。
- 极简启动路由:
-
动态错题本与晋升机制(
docs/LESSONS.md):- 跟踪三类场景:返工失误(Regression)、架构破坏(Bad Merges)、粗糙验证(Shallow Verification)。
- 格式规范:
**<规则>** - <事故与后果> - <修复方案> - Done right: <范例> - Seen N× (日期...)。 - “事不过三”(Seen 3×)硬规则晋升:任何经验教训若在 3 个不同日期/任务中重复出现,强制从经验备忘转正升级为
AGENTS.md的系统硬性规范。
-
交付三铁律(验收与测试标准):
- 回退与破坏变异测试 (Red Check / Break Check):修复 Bug 或新增守护逻辑时,新编写的测试必须在老代码或故意破坏的代码上真实报错,证明测试有效,杜绝“永远绿灯的假测试”。
- 真实环境调用 (Real Usage):涉及 API 元数据、终端 PTY 交互与文件系统时,必须使用真实链路验证。严禁捏造假数据或用假 Mock 掩盖异常。
- 交付报告诚实声明 (Honest Listing / Not Run):任务汇报必须区分标注
Passed(真实跑通项)、Red/Break Verified(破坏性反向验证项)和Not run(未测场景及原因),严禁隐瞒。
二十一、笔记系统(Hetu 移植与所见即所得纯净双栏体系,2026-10-14 落地)
1. 架构定位与物理存储
- 物理路径:数据持久化于用户家目录
~/.ReinAgent/notebooks/,软件首次启动自动创建default默认笔记本(不使用中文命名); - 文件夹与笔记映射:每个笔记本对应一个物理文件夹,支持在笔记本下嵌套子文件夹;每篇笔记对应物理磁盘上的一个真实
.md文件; - Rust 后端持久化命令(
src-tauri/src/notes_fs.rs):notes_ensure_root:初始化根目录并创建默认笔记本与欢迎指南;notes_list_notebooks/notes_create_notebook:递归遍历与创建文件夹笔记本;notes_list_notes/notes_read_note/notes_save_note/notes_create_note/notes_rename_note/notes_delete_note:无状态原子读写与文件名安全重命名/删除。
2. 双栏界面布局与树形文件导航
- 左栏(树形层级目录导航,100% 对齐经典侧边栏):
- 顶栏操作区:即时检索输入框(
搜索...)++ 笔记快捷按钮 ++ 文件夹快捷按钮; - 一体化树形目录树(
NotesTree.tsx):- 文件夹节点:可折叠/展开指示三角箭头(
ChevronDown/ChevronRight)+ 鲜明蓝色线框文件夹图标(Folder/FolderOpen)+ 文件夹标题; - 子笔记节点:精确缩进并在左侧附带细腻的垂直对齐辅助线(
w-[1px] bg-white/15); - 节点操作悬浮快捷按键:文件夹节点悬停时支持快速「在此文件夹新建笔记」、「新建子文件夹」、「重命名文件夹」、「删除文件夹」;笔记节点悬停时支持「重命名笔记」与「删除笔记」;
- 文件夹节点:可折叠/展开指示三角箭头(
- 数据层扩展:新增
notes_delete_notebook、notes_rename_notebook以及notes_open_folder(调用系统资源管理器/Finder/xdg-open 直接打开对应物理文件夹)Rust Tauri 后端 IPC 命令,并在 Web Mock 兼容层中全量跑通。
- 顶栏操作区:即时检索输入框(
- 右栏:
- 一体化纯净所见即所得 Milkdown 编辑器;
- 顶部元信息操作栏:
- 文件夹胶囊(如
📁 测试/456):点击直接调起系统文件管理器并定位打开该文件夹目录; - 严格遵循
YYYY/MM/DD标准日期格式(如2026/10/14); - 严格遵循 24小时制 时间格式(
HH:mm:ss,如12:36:43,彻底剔除 AM / PM); - 自动保存状态指示(保存中/未保存/已保存);
- 彻底移除冗余的历史与分享图标,保持极简纯净;
✨ AI 助手渐变胶囊按钮:精准放置在保存状态右侧;- 顶栏绝对固定不动:最顶栏为全宽独立 Header,始终固定在顶部不随 AI 弹窗推移;
- 文件夹胶囊(如
- 右侧全文 AI 助手侧弹窗(
NoteAiSidebar.tsx,100% 对齐 Hetu 源码结构):- 作为编辑器内容主体(标题栏+格式工具条+Milkdown 画布)同级的右侧分流抽屉展开;
- 顶栏:
✨ AI 助手+全文蓝紫徽标 +✕关闭按钮; - 快捷胶囊行:
续写(默认激活态)·润色·翻译·精简·扩展·解释; - 自定义指令卡片:深色内嵌卡片(
bg-[#161922]),支持 Enter 快速执行、Shift+Enter 换行; - 主生成视口:打字机/等待动效,结果生成后提供一键「替换全文」、「在文末追加」以及「复制结果」;
- 底栏模型选择:左侧联动当前启用的服务商与模型(支持默认模型),右侧渐变蓝紫
✈ 执行按钮,生成中变为■ 停止。
- 全局顶栏隐藏:切入笔记(以及助手、自动化)视图时,主工作区的对话顶栏自动隐藏,保持沉浸式写作。
3. 所见即所得(WYSIWYG)富文本编辑引擎
- 彻底剔除预览/分栏冗余代码:
- 彻底移除了
viewMode(preview | edit | split)状态及切换胶囊按钮; - 删除了右侧/独立 Markdown 预览区域与双向滚动同步逻辑;
- 彻底移除了
- Milkdown 核心引擎与流程图集成(
src/components/notes/MilkdownEditor.tsx&milkdown.css&mermaidTheme.ts):- 采用
@milkdown/react与@milkdown/kit(CommonMark + GFM + History + Nord); - 集成 Mermaid 流程图(
@milkdown/plugin-diagram+mermaid):- 一键插入 Mermaid 模板(
A[开始] --> B{处理} ...); - 编辑器内就地渲染为 SVG 图表(
DiagramNodeView),支持暗黑/浅色模式自适应主题; - 点击流程图就地展开源码编辑,失焦自动编译并刷新渲染,包含错误卡片兜底;
fitMermaidLabels自动扩充 CJK 字符标签宽度,杜绝文字裁剪。
- 一键插入 Mermaid 模板(
- 打字即渲染:输入
#即刻展示为大标题,输入- [ ]自动转为可点击勾选的待办项,输入表格或列表就地排版渲染; - 格式工具条(100% 对齐 Hetu 截图图标与顺序):
H2·H3·B·I·S·</>·""·:=·1=·[x]=·[<>]·|·✨ AI·🔗·田·—·⚯ 流程图;- H2 / H3 修复:直调 ProseMirror
state.tr.setBlockType(from, to, nodeType, attrs),彻底解决 Milkdown 命令传字符串导致的静默失效问题,点击即刻转为标题,重复点击切回普通段落; - 选区浮动工具条:划选文本就地浮现快捷格式按钮(通过
onMouseDown preventDefault防止丢失选区); - 行内 AI 浮动弹窗(100% 还原 Hetu 截图样式):
- 深色玻璃悬浮卡片(深灰亚克力
#161922+ 边框高亮border-indigo-500/30+ 阴影shadow-2xl),直接定位在选区附近; - 顶栏:
✨ 行内 AI+{n} 字胶囊徽标(无选区显示“全文”) + 关闭✕; - 快捷胶囊行:
润色·翻译·精简·扩展·解释,点击自动预填提示词并聚焦输入框; - 底栏:左侧模型选择下拉(联动系统真实服务商配置,仅展示已启用的服务商与模型;默认选项对齐系统设置中设置的默认模型,触发按钮直接显示极简的
🤖 默认模型 ⌄,下拉内展开详细模型名),右侧渐变蓝✈ 执行按钮,生成中转为■ 停止; - 真实模型请求调度:调用系统底层的
completeOneShotText(基于buildModel与各服务商 API 适配器),完整支持 DeepSeek、OpenAI、Anthropic、Gemini、Ollama 及本地自定义网关; - 结果展示与采纳(100% 对齐 Hetu 原版图一):提供横排平铺、永不折行的
接受替换(绿色实心胶囊bg-emerald-600)、追加(浅蓝半透明文字按钮text-indigo-400)、重写(灰色轻量按钮text-gray-400),外层设置w-96+whitespace-nowrap shrink-0防挤压加固;
- 深色玻璃悬浮卡片(深灰亚克力
- 防抖自动保存:编辑内容 1000ms 防抖自动写入物理磁盘,右上角实时反馈「保存中… / 已保存 / 未保存」状态;
- CodeMirror 代码块卡片化就地渲染(100% 对齐 Hetu 原版设计):
- 底层引入
@codemirror/language-data、@codemirror/language与@codemirror/view,由@milkdown/kit/component/code-block驱动; - 卡片外观:深色高对比度圆角面板(
background: #1e1e2e; border-radius: 10px; border: 1px solid rgba(255,255,255,0.08); box-shadow: 0 4px 16px rgba(0,0,0,0.3)); - 顶部工具栏(
.tools):左侧多语言下拉选择框(text ⌄,支持全量 CodeMirror 语言动态切换与高亮),右侧一键复制代码按钮(📋 复制,点击带成功反馈); - 全文 AI 助手侧弹窗(抽屉浮层遮盖,100% 对齐 Hetu 原版设计):
- 全宽内容静止不挤压:顶栏(文件夹路径、2026/10/14 日期、24小时制时间、保存状态、
[✨ AI 助手]胶囊)位于最顶层固定全宽展示;主体 Markdown 区域占据 100% 容器宽度,在展开或收起 AI 助手时,正文排版、工具栏和标题完全不动、不发生收缩挤压; - 绝对定位浮层遮盖:
NoteAiSidebar采用absolute top-0 right-0 bottom-0 w-84 z-30 shadow-2xl border-l绝对定位遮盖在编辑器右侧之上; - 全文级智能写作流:支持“续写、润色、翻译、精简、扩展、解释”快捷指令胶囊、自定义指令多行输入框、真实模型选择器及执行/停止控制;支持采纳为“替换全文”(
setMarkdown)、“在文末追加”(appendContent)或“复制结果”; - 全量富文本语法树解析(Parser 驱动):无论是“替换全文”还是“在文末追加”,底层均通过 Milkdown 的
parser(fullMd)完整解析为 ProseMirror 富文本语法树,确保追加的标题、加粗、列表、表格和代码块 100% 自动渲染为富文本视图,杜绝原始标记裸露。
- 全宽内容静止不挤压:顶栏(文件夹路径、2026/10/14 日期、24小时制时间、保存状态、
- 任务对话一键整理笔记(OrganizeNotePanel,100% 对齐原软件 Hetu 体验):
- 顶栏入口:工作台顶栏右侧常驻
[📄 整理笔记 ⌄]绿色胶囊按钮,支持展开/收起配置抽屉; - 配置抽屉(OrganizeNoteDrawer):
- 整理风格:支持 3 种标准模式——“摘要式”(提炼要点,500字内)、“详细式”(保留完整信息,分章节组织)、“Q&A 式”(改写为问答形式);
- 目标笔记本:下拉展示层级树形目录,默认选中“默认笔记本”,支持嵌套子目录层级选择;
- 操作按钮:包含绿色
[开始整理]按钮与[取消]按钮;
- 执行与物理落盘:提取当前任务对话文本,通过
completeOneShotText结合专业整理 Prompt 生成结构化 Markdown,首行提取为标题并调用notesService.createNote+saveNote物理落盘; - 完成联动:整理完成后顶栏下方提示
已保存笔记:《...》,点击[查看笔记]按钮一键切换至笔记模块并直接高亮打开该篇新建笔记。
- 顶栏入口:工作台顶栏右侧常驻
- 底层引入
- 采用
- Web Mock 双模兼容:
notesService.ts内置完整的浏览器端 Mock,无头环境/单测无缝跑通。
十、原生内置 MCP Server 与双向开放生态(2026-10-20)
1. 架构定位与双向开放设计
- 从单向 Client 到双向生态:原系统仅支持通过 Hub -> MCP 作为 Client 接入外部工具;新增原生内置 MCP Server(Model Context Protocol 2024-11-05 规范),使外部大模型(Claude Desktop、Cursor、Continue、Antigravity 等)可将 ReinAgent 作为标准工具箱反向完全操控。
- 端口与鉴权零开销复用:
- 直接在桌面端
remote_server.rs现有的 axum 路由器上注册两条原生 MCP 路由:GET /mcp/sse:建立 Server-Sent Events 长连接通道,接收 JSON-RPC 2.0 异步响应帧;POST /mcp/message:接收 JSON-RPC 2.0 请求(initialize、tools/list、tools/call、ping等)。
- 完全复用远控服务器的 Token 鉴权(
check_auth)与 Fail2Ban IP 防护体系;本地回环127.0.0.1严格豁免封禁。
- 直接在桌面端
2. 15 项全功能 MCP Tools 全面覆盖
- 查询能力:
reinagent_list_tasks、reinagent_get_task_status、reinagent_list_models、reinagent_get_task_options; - 任务创建与推进:
reinagent_create_task、reinagent_send_message、reinagent_start_draft; - 控制与审批门禁:
reinagent_stop_task、reinagent_approve_tool、reinagent_switch_task; - 运行时调度与配置:
reinagent_set_model、reinagent_set_approval_mode、reinagent_set_thinking_level; - 队列与阅读管理:
reinagent_remove_queued_message、reinagent_mark_task_read。
3. 状态等待器修复与瞬时响应加固
- 双防线解决
need-state等待器:src-tauri/src/remote_server.rs:remote_bridge_state与remote_bridge_answer_state同步接入state_waiters唤醒逻辑,收到任意状态推送即刻解冻等待器;src/lib/remote/remoteBridge.ts:监听remote-bridge:need-state时立即采集当前快照并通过remote_bridge_answer_state同步回传,消除原本 200ms 防抖带来的请求超时风险。
4. 验证与交付标准
- 变异反向测试 (Red Check):无 Token 与错误 Token 访问
GET /mcp/sse均被401 Unauthorized严格拦截; - 真实全链路测试 (Real Usage):通过 SSE 端点握手、初始化、枚举 15 个工具、成功新建真实任务
task-1792480388704-ns5z4并穿透 React webview 实时拉取思考过程与 Context 统计,全链路 100% 实测跑通。