RWork 功能说明
本文是功能清单 + 界面示意图,回答「这个应用有什么、长什么样」。
动机与取舍写在 README.md 的「已知取舍」一节;后续要做什么写在 REQUIREMENTS.md。
图是 ASCII 示意,反映真实的控件与排布,但不逐像素精确(终端等宽字体下的近似)。 文案与实际界面一致,取自 i18n 表。
0. 全景
┌────────────────────────────────────────────────────────────────────────────────┐
│ ┌──────────────┐ │
│ │ RWork 工作台 │ ← 侧栏:折叠按钮 + 工作空间(图标 + 提示) │
│ │ [📁] [«] │ │
│ ├──────────────┤ ┌────────────────────────────────────────────────────────┐ │
│ │ 💬 对话 │ │ │ │
│ │ 📖 核心行为 │ │ 主内容区 │ │
│ │ 📊 用量审计 │ │ (随左栏导航切换) │ │
│ │ ⚙ 设置 │ │ │ │
│ ├──────────────┤ │ │ │
│ │ 搜索对话… │ │ │ │
│ │ ──────────── │ │ │ │
│ │ 介绍一下自己吧│ │ │ │
│ │ 关于 MCP │ │ │ │
│ │ … │ │ │ │
│ ├──────────────┤ │ │ │
│ │ + 新建对话 │ └────────────────────────────────────────────────────────┘ │
│ └──────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
↑ 右边缘 6px 可拖动改宽(200–520px),宽度记进设置
四个页面:对话 / 核心行为 / 用量审计 / 设置。last_page 记在设置里,重启回到上次那页。
1. 对话
1.1 主界面
┌────────────────────────────────────────────────────────────────────────────────┐
│ RWork 工作台 介绍一下自己吧 deepseek-flash · 1.8K tokens │
├────────────────────────────────────────────────────────────────────────────────┤
│ 我 2026-09-14 09:12 [⧉] │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 解释一下这个项目的架构 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ▸ 思考过程 ◐ 2026-09-14 09:12 [⧉] │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 先看 crate 划分,再找入口函数,最后对着依赖关系说清楚。 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ 助手 deepseek-flash Token 1,821 耗时 3.2s 2026-09-14 09:12 [⧉][📄] │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 这个项目分成 5 个 crate: │ │
│ │ - **rwork-core**:领域模型,不做 IO │ │
│ │ … │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ⚙ bash ◐ 2026-09-14 09:12 [⧉] │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ command=find crates -name '*.rs' | wc -l timeout_seconds=120 │
│ │ 41 │
│ │ 比上一版多了 6 个文件 │
│ │ [📄 findings.md] ← 小卡片,点一下用系统默认程序打开 │
│ └────────────────────────────────────────────────────────┘ │
│ │
├────────────────────────────────────────────────────────────────────────────────┤
│ 流水线 顺序即执行顺序,每一位都能看到前面的产出 │
│ [代码探查] [方案规划] [代码实现] [故障定位] [代码评审] │
├────────────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 输入消息,Enter 发送,Shift+Enter 换行 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ 正在运行 bash… [📎] [ 发送 ] │
└────────────────────────────────────────────────────────────────────────────────┘
要点
对话记录是一串块,不是一串气泡:一次助手回合会依次长出思考块、正文块、 工具块与产出物卡片。三种块宽度不同,因为它们的角色不同——正文是主角, 思考与工具是过程;三者一样宽时,一屏里最先被读到的会是工具输出那堆字。
| 块 | 宽度 | 默认形态 |
|---|---|---|
| 正文 | 最宽(占满正文列) | 完整展开,可复制(markdown 原文 + 富文本) |
| 思考 | 正文列的 82% | 收起,只显示最新的一小段(滚动输出),点开看全文;推理中带转圈 |
| 工具 | 正文列的 82% | 外面一个框、里面两个小框:「输入」(调用参数,一行一个参数)与「输出」(结果)。默认折叠成一行(工具名 + 参数摘要,超出用省略号,不折行);执行中自动展开并转圈,输出逐行刷出来;跑完自己收回去,想细看就点标题行 |
| 产出物 | 只包住文字的小卡片 | 文件图标 + 文件名,点一下用系统默认程序打开(挂在写出它的那个工具块下面) |
这些宽度都不是写死的像素(见 crates/rwork-app/src/layout.rs):
| 尺寸 | 由什么决定 |
|---|---|
| 正文列 | 字号 × 61 字。决定读起来累不累的是一行的字数,不是像素数:同一个 920px,15px 字号下是一行 61 字,18px 下就成了 51 字。设置里改字号,列跟着走(实测 13/15/18 字号 → 793/915/1098 px) |
| 过程块(思考 / 工具) | 正文列的 0.82:过程是被读的「过程」,不是主角 |
| 页内对话框 | 窗口宽度的 42%,两端夹在 420–720px:窗口窄时它得让路,窗口宽时也没必要长成一个页面 |
| 首次打开的窗口 | 显示器可用区域的 78%,上限 1600×1000。写死 1440×920 在 4K 屏上只占中间一小块,在 1366×768 的笔记本上又比屏幕还高 |
| 侧栏 | 用户拖过的那个值(收窄档是固定值) |
窗口比正文列窄时不需要任何特判:max_w 只封顶、不撑开,内容由父容器自然压窄。
布局值只在一屏里算一次,且变化时会在日志里留一行(布局宽度按字号与窗口重算)——
调布局时最想知道的就是「这一屏到底算成了多宽」。
| 能力 | 说明 |
|---|---|
| 流式输出 | 逐字上屏;随时「停止」,已生成内容保留并入库 |
| 思考通道 | 带推理的模型(DeepSeek Reasoner / Claude 思考模式)单独折叠展示,默认收起、只显示最新的那一段,点开看全文 |
| 实时命令输出 | 命令与脚本每打出一行就当场进工具块(stdout 与 stderr 都收,stderr 标出来),不必等它跑完。装包过程同理:pip 的 Collecting… / Downloading… 会一路刷出来,而不是让人对着空白等十几分钟 |
| 复制按钮 | 正文(markdown 原文 + 富文本)、思考(收起时也能复制全文)、工具(输入 + 完整输出)。工具块复制的是完整那一份,不是屏幕上被裁剪、被折叠的显示 |
| 选中的文字一定能复制 | 鼠标选中任意文字后按 Ctrl+C 都有效。复制是绑定在焦点上的动作,而选区是窗口级的事实:焦点停在按钮或别处时,「选中了却复制不出来」是必然的,因此外壳在最外层兜了一次底 |
| 运行记录(队列与链路) | 「核心行为」页里列出最近 30 次运行(回合 / 派活;定时任务的执行在它自己的「执行历史」里,不重复列在这里):时间、状态、标题、轮数与调用数、产出文件。点开看它跑了哪些步——每一步是工具调用(工具名 + 结果第一行)或上下文(压了几条、丢了几条、省了多少 token、存了几个文件)。压在请求发出之前发生,因此链路上的先后顺序就是真实顺序。进程被杀之后这一页才真正有用——停在「进行中」的记录会在下次启动被标成「被中断」,于是「上次跑到哪」有据可查 |
| 被打断会说出来 | 如果上一轮没跑完(进程被关掉/杀掉),打开会话时输入框上方会有一条提示:停在哪一步、多半是被关掉或杀掉,并给一个「继续」按一下接着做。历史里的缺口此前就已经会自动补好 |
| 附件与正文一起 | 附件在正文里是一个整体(① ② ③…,插在光标处,退格一次就删掉整块,像表情);输入框上方是文件清单(编号 + 文件名 + 大小,点一下移除,同时删掉正文里对应的占位符)。发送时以正文为准:删掉占位符的附件就不发 |
| 附件就在输入行里 | 已选附件显示成「图标 + 文件名」的小卡片,与输入框同处一行、排在文字左边(不是框外或上方另起一行),点一下移除。状态提示(例如「模型不支持图片」)留在框外 |
| 从剪贴板添加 | 输入区的「从剪贴板添加」按钮:复制来的图片落成附件文件(按它自带的格式),复制来的文件直接用路径;纯文本仍由输入框自己粘贴 |
| 附件组装说明 | 带附件的消息会先给模型一份清单:本次包含几段文字、几个文本附件、几张图片,逐条写明名称与大小,并标出「这份只有摘要」和「文档里还有 N 张内嵌图没发」。图片按图发送、正文里没有它的文字,因此点名尤其重要——不点名模型不知道这是哪来的图 |
| 会话压缩 | 内容多到接近上下文窗口时,先把过程压掉:用户说过的话一个字不动,助手的长回答截断并标明,推理整段丢掉,工具输出换成一行摘要(首行 + 末尾的结论/路径)。超过 1200 字的工具输出还会把全文存成文件(.rwork/<会话>/context/tool-<第几条>-<内容哈希>.md,身份是哈希、序号只是让人好认),摘要里留的是开头加一个引用——引用用 read 读得回来,所以「后面还需要那份原始输出」不必重跑一次工具;内容相同的输出每轮都会落到同一个文件上(历史窗口滑动会让序号变,所以不能拿序号当身份),因此重压同一段历史不会多出文件、也不会换引用。存不下时(目录不可写、磁盘满)退回只压不存,绝不给一个读不到的引用;智能体没勾 read 时也不落盘——引用是给它读的,给一个读不了它的模型留下一句「全文在这里」只会让它白试一次。压缩发生后会在请求里说明「哪些被压过」,模型不会把摘要当原始细节。实测 30 轮会话省下 3.4 万 token |
| 上下文预算 | 压不动了才按条裁剪:丢掉最早的整条消息,并在请求里说明省略了多少条,不静默丢弃。压缩与裁剪都会记进这一轮的统计(压了几条、丢了几条、省了多少 token、存了几个文件),因此「它怎么忘了刚才说的」在会话旁边就能看到,不必去翻日志 |
| 历史会话 | 可搜索,标题取首条消息首行;侧栏可置顶 |
| 轮次上限 | 一条消息最多 12 轮工具调用(可配),到顶会明说原因 |
| 工具报错由模型自己收拾 | 一次调用失败不中止回合:失败结果(退出码、stderr、错误原因)回到模型手里,由它分析、改、重跑。工具报错最常见的原因是环境细节——python3 不在 PATH 里、包还没装、路径写法不对——这些模型拿到错误后通常自己就改对了 |
| 工具结果入库 | 调用请求与工具结果一起落库。少存结果的话,下一次提问会把一个「有调用、没人应答」的历史发给服务端,而那是会被整条拒绝的 |
| 轮次用尽也要有交代 | 一次回合的工具轮次是有预算的(默认 12 轮)。用到最后,不会静默结束:工具会被摘掉,模型必须给出一份收尾汇报——做完了什么(带文件路径)、交付物能不能直接用、还差什么、继续下去的第一步。同时每轮都会把「已用几轮、还剩几轮」告诉模型,让它把交付物排在打磨细节前面 |
| 收尾必须说清完成度 | 每轮结束,助手要明确回答「任务完成了没有」:完成了就给产出文件的相对路径加一句内容说明,没完成就说清还差什么、卡在哪;不许用「已完成」三个字代替清单,也不许把带占位符 / TODO / 示例数据的文件当成品交付(提示词里单列了一条收尾要求,并且会把本会话已经写出的文件清单一起下发,好让它接着改而不是重造) |
| 派活给哪个角色 | delegate 可以指定智能体(预设的名字或 id 都认):评审的活派给评审位、实现的活派给实现位,各自带自己的系统提示词与工具范围。不指定就用当前这位;指定了但找不到会报错并列出可选项 |
| 长文本自动存成文件 | 粘贴超过 2000 字的内容,正文里只留一句说明、一个本地文件引用和开头预览,模型需要时自己读文件。对话记录里仍然完整显示你的原文 |
| 设置页分区标签 | 设置分成五个区(外观 / 智能体 / 模型服务 / 工具与扩展 / 数据),标签常驻在页头下面,一次只看一区,不用一直往下滚 |
| 保存回执 | 页头下面常驻保存状态:成功显示绿色「已保存 · 时刻」,失败显示红色并写明原因 |
| 派活的过程收在一个块里 | 子代理做的事以「交给子代理」的方框呈现:任务说明、编号步骤、结论。它与普通对话分开显示——子代理的消息在库里也是普通消息,混着渲染会让人以为「用户说过这句话」 |
| 派活的时机 | 主循环剩不到 2 轮时不再派活(派出去收不回来),会让模型自己收尾 |
| 派活(delegate) | 模型遇到「要反复试错」的活(调画图脚本、试环境依赖、修格式)可以派给子代理:它有自己的轮次预算和自己的历史,做完把结论与写出的文件带回来。那些试错因此不占主循环的轮次——主循环的轮次留给「把交付物做出来」 |
| 执行计划 | 输入像多件事时,先拆成带完成条件的步骤(依赖构成有向无环图),再按图执行:每做完一步按条件核对(文件存在/非空/含某段文字/JSON 里有某字段),不满足就把差什么告诉模型让它接着补;上游产出失效就退回最早那一步重做,受影响的步骤一起重做。重试与回滚都有上限,到顶如实报告卡在哪;有副作用的步骤(推送、发布、覆盖文件)要重跑时先问用户。步骤清单与当前进度既进系统提示(哪一步、要满足什么、不要跳步),也在对话右侧留白里的「执行计划」面板上画出来:每步的状态、完成条件、每次尝试与退回原因、回滚次数,读的是库里那份,因此重开应用还看得到上一轮停在哪 |
| 我的输入(概览面板) | 右侧留白那排图标里的第一个入口,点开是一张浮层面板:每一条都是我发过的话的第一行概要加时间,点一条就跳到那一条去。长对话里「找某次输入」不再靠滚 |
| 交付物 | 每个回合末尾列出这一回合真正落到盘上的文件:工具直接写出的,以及模型在回答里点名、磁盘上确实存在的(脚本用 python-docx / matplotlib 产出的也算)。每张卡片点一下用系统默认程序打开 |
| 回到最新 | 长对话往回翻之后,右下角出现一个圆形向下箭头,点一下回到最新;贴着底部时它不出现。与右侧留白里的「回到我发的话」(跳到本轮用户消息)是两个不同的动作 |
| 右侧留白三个入口 | 正文列有阅读上限,窗口再宽右边也空着一条。那里竖排三个按钮:我的输入(跳到某一条我发的话)、这一轮的统计(助手回复数、工具调用数、输入/输出 token、用时、模型用时、输出速度、产出文件)、执行计划(步骤、状态、完成条件、每次尝试与退回原因)。三者共用同一个右侧栈、同一层外壳(标题 + 关闭按钮 + 弹出层底色与阴影),点图标开合两可;「用时」是整轮墙钟、「模型用时」只算模型在生成的那段时间,输出速度按后者算 |
| 等模型/下载的时候窗口还能用 | 装本机 OCR(最多 5 分钟)、手动整合长期记忆、环境探测与装运行时这几处的等待,都不会再让界面「睡住」:等待期间照常点侧栏切页、滚对话。等不到结果时会如实收场(把「正在下载」那句话收掉并给出提示),不会留一句永远停在那里的状态。第 44 批修的(此前这几处是界面线程上的阻塞轮询,实测点一下「整合长期记忆」会把窗口按住 4 秒) |
| 长对话不再越滚越重 | 对话正文只把看得见的那一段画成真正的元素,视口外的块用等高的占位撑着,因此每次重绘的开销与「这段会话有多长」脱钩(实测 380 条消息的会话:每次重绘 97.7 ms → 18.6 ms)。滚动位置、贴底跟随、跳到某条我发的话三个行为都不变——占位与真块一样高 |
| 状态说的是正在发生的事 | 有工具在跑时,输入区下方与工具块标题显示的是「正在运行 <工具>…」与转圈,而不是「正在生成回复…」。那一刻模型那边什么都没在生成,说成「生成中」会让人一直干等——这正是「用了一次工具之后程序像卡住了」的由来 |
1.2 智能体流水线(新建对话时)
┌────────────────────────────────────────────────────────────────────────────────┐
│ 流水线 不选则用设置里当前的那一位 │
│ [代码探查] [方案规划] [代码实现] [故障定位] [代码评审] [文件盘点] … │
└────────────────────────────────────────────────────────────────────────────────┘
点了几下之后(顺序即执行顺序):
┌────────────────────────────────────────────────────────────────────────────────┐
│ 流水线 [1 代码探查 ×] [2 代码实现 ×] [3 代码评审 评审 ×] [故障定位] │
└────────────────────────────────────────────────────────────────────────────────┘
多选 = 一条流水线,按点击顺序接力。 后一位能看到前一位的全部产出(每一步都已落库, 因此不需要额外的交接结构)。
- 每位保留自己的模型、系统提示词、工具白名单与审批模式——不合并。 这正是「便宜模型做规划、强模型做执行」成立的前提,也让「第 1 位只读、第 2 位可写、 第 3 位只读复审」天然可行。
- 顺序即执行顺序,最多 5 位;至多一位评审,且不能排第一(前面没有可评审的产出)。
- 交接说明:从第二位起自动插一条说明(前一位是谁、你是第几位、这条线几位)。
- 轮次预算每位各一份——共用一份的话第一位跑满会把后面掐死。
- 失败不连锁:某一位一次都没做成(工具全部失败、或请求本身就失败)就停在那里, 已完成的产出如实保留。注意这是整位跑完后的判断:中途某次命令报错不会中止这一位。
- 附件只在第一位那轮带出去(之后已在历史里)。
- 对话一旦开始,选择锁定:中途换人会让前半段与后半段的约定不一致,事后无从判断谁做了什么。
执行计划会跨棒接力(第 26 批)。整条流水线共用一份计划,每个节点记着 上一次是哪一棒做的;退回到别人的产出上时,本棒立刻停手,把序号拨回那一棒:
第 1 棒「代码实现」 写草稿 ✓ ──► 进入第 2 步「生成图表」
第 2 棒「报告成文」 作图 ✓,但顺手把草稿清空了
└─► 审计发现上游失效:退回给第 1 棒「代码实现」重做
(本棒的回合就此结束——不让它替别人写一半)
第 1 棒 重写草稿 ✓ ──► 第 2 步上次是第 2 棒做的 → 又交回给它
第 2 棒 重做图表 ✓ ──► 全部步骤通过条件核对
对话右侧的「执行计划」面板里,每一步下面会多一行「第 N 棒:谁」, 于是界面上说的和日志里那句「退回给第 2 棒」对得上。来回交棒不会无限—— 每次交棒都由一次回滚触发,而回滚总数受设置里「总回滚上限」约束。
计划停在「等你确认」上时(退回要重跑有副作用的步骤),用户回一句「继续」, 流水线会直接从当初做那一步的那一棒接着做,而不是从第 1 位重跑一遍。
1.3 内置的 10 个子智能体
| 角色 | 职责 | 工具白名单 | 审批 |
|---|---|---|---|
| 编程 | 单人对话的编程档位:先看懂现状,再改,改完自验证 | 全部(含 write/edit/write_version/run_script/bash) | 自动写入 |
| 代码探查 | 只读摸清代码库,给「位置 + 结论」清单 | read, glob, grep, refs | 每次都问 |
| 方案规划 | 把现状变成可执行、可验收的计划 | + skill | 每次都问 |
| 代码实现 | 按计划改代码并自验证 | 全部(含 write/edit/write_version/run_script/bash) | 自动写入 |
| 故障定位 | 复现 → 缩小范围 → 给出根因 | read, glob, grep, refs, bash, run_script | 每次都问 |
| 代码评审 | 独立检查产出,通过 / 退回 + 逐条问题 | 同上(无写入工具) | 每次都问 |
| 文件盘点 | 只读地列出一批文件各是什么、有什么 | read, glob, grep, refs | 每次都问 |
| 数据分析 | 用脚本把表格/文档算成带数字的结论 | read, glob, grep, refs, run_script, skill | 每次都问 |
| 报告成文 | 整理成文档,写新版本文件 | 含 write_version,刻意没有 write/edit | 自动写入 |
| 数据核对 | 独立重算关键数字,只报对不上的 | read, glob, grep, refs, run_script | 每次都问 |
每个角色都写明「不该做什么」,这是防越界的关键:探查不提出修复方案、排错只回答 「为什么坏」、评审不改自己发现的问题、成文不引入新结论、分析不估算数字。
白名单是硬约束:specs_for 按名字过滤发给模型的工具表,所以「只读角色改不了文件」
是机制保证,不靠提示词自觉。两条最有价值的边界:
报告成文没有write/edit→ 只能写新版本,一次「整理一下」不可能覆盖你的原始文件。代码评审也没有写入工具 → 评审自己动手就失去独立性了。
没有一个角色用「全部放行」——内置角色是开箱默认值,而放宽权限是危险方向。
用户能改、升级能拿到新的、不覆盖用户改过的:
内置角色被用户改动 → 设置里存一份同 id 的「影子」,用户那份胜出
内置角色被用户删除 → 记进 disabled_agents,升级带来的新角色仍默认可用
升级新增的角色 → 直接出现
内置角色不占用户自建预设的额度(20 个)。
1.4 评审位会真的驱动退回
结论:退回
问题(按严重度):
1. [严重] crates/x.rs:42 — 越界未检查 — 输入为空时 panic — 加边界判断
未验证项:无
评审角色的产出被解析首行结论。退回时把意见交给上一位重做一次,最多一轮—— 不加上限的话两位互相不满意会变成不停烧额度的循环,而「什么时候算够好」没有客观判据。
解析只认开头几行,因为按关键词搜全文会有这几种误判(都会让评审变成自动放行):
- 「不通过」里含有「通过」
- 正文里写「测试通过」「这段可以通过」
- 读不出结论时不当作通过(如实提示,不静默放行)
2. 核心行为
一个页面,因为它俩共享同一套作用域概念(项目 / 全局),而作用域切换器只该有一个。
┌────────────────────────────────────────────────────────────────────────────────┐
│ 作用范围 [ 项目:RWork ●] [ 全局 ] [ ↻ 整合长期记忆 ] │
├────────────────────────────────────────────────────────────────────────────────┤
│ 长期记忆 │
│ 这些会随每次对话一起发给模型。项目范围的记忆只在当前工作空间里生效。 │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 写一条要长期记住的要求或事实,回车添加 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ 回答一律用中文,不要用 emoji │ │
│ │ 全局 手动添加 还从未被取用 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ 这个项目用 pnpm │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ 用方案 B(而不是队列) │ │
│ │ 决定 项目 (…\RWork) 整合生成 从第 12–18 条消息总结 被取用 3 次 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ 补上边界测试 │ │
│ │ 待办 全局 整合生成 从第 31 条消息总结 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ 向量库里有 1 条记录找不到对应的记忆(记忆被删之后留下的)。 [ 🗑 清理孤儿向量 ]│
├────────────────────────────────────────────────────────────────────────────────┤
│ 任务与定时任务 [ + 新建任务 ] │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ 生成每日简报 │ │
│ │ 普通 可开始 全局 计划: 0 9 * * * 下次 2026-09-15 09:00 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ 检查依赖有没有新版本 │ │
│ │ 普通 可开始 全局 智能体:代码评审 计划: 30 8 * * 1 下次 2026-09-15 08:30 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ 每半小时看一次测试 │ │
│ │ 普通 可开始 全局 每 1800 秒 下次 2026-09-14 23:35 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 填间隔秒数即为定时任务;留空则是一次性任务,先记下来稍后手动做。 │
└────────────────────────────────────────────────────────────────────────────────┘
列表在前、新建折叠在按钮后面。表单常驻会让这一页看起来像「正在编辑一条」, 而它首先是一份列表。
记忆分三类,类别与出处都显示在条目上。整合时模型认出的「决定 / 待办 / 事实」 连同这次提炼实际读过的那段消息序号一起入库。列表里带类别的条目会显示类别 标签与「从第 12–18 条消息总结」——出处是当时真正读过的区间,不是让模型逐条 标注的(它会写错、会漏写,而写错的那种看起来像有了出处,比没有出处更坏)。
决定与待办不看相关度,每轮都进提示词。召回是按相似度算的,而「三个月前 定下的方案」跟今天的问题可能一个词都不重合——它一旦丢出提示词,模型就会把 已经定下来的方案当成还没定的选项,重新问一遍「你想用 A 还是 B」。所以这一节 按类别 + 当前意图组装,列成「已经定下的 / 接下来要做 / 与当前这件事明显 相关的事实」:决定与待办全收(新的在前、最多 12 条),事实只收最像的那几条 (最多 5 条),被截掉的部分写明还有几条没展开;进过这一节的记忆不再在下面 那一节里重复(同一句话在提示词里说两遍,模型更容易把它当成两件事)。
这一节按会话缓存:换话题、或者记忆集合变了才重建,其余时候逐字不变—— 每轮按当轮召回重排会让同一件事在提示词里进进出出,而这一节说的是「这一段里 已经定下的东西」。
「这条到底有没有被用上」也显示出来了。每条记忆后面跟一句「被取用 N 次」,
一次都没进过提示词的写「还从未被取用」——这两种情形必须分得开:前者可能该删,
后者说明它总是和其他几条一起被命中。取用是发生过的事,一行一次记进
memory_use(只追加);「这次用完有没有起上作用」只在一种情况下说得清——
那一轮只命中了一条。命中多条时谁也不记功,只标「同时命中」并照实显示:
一次成功给五条各记一次「赢」等于把噪声当信号,而权重会直接改召回排序。
上一次没跑完留下的记录,会在你下一次提问时被标成「没有结论」——否则它会把这
一轮的成败记到一个中途死掉的回合头上。
哪些回合不写结论:派活与流水线那几位跑完只说明「这一棒跑完了」,因此只有
你自己那一轮会留下成败;团队跑一轮时记忆只有取用次数、没有胜败。这是有意的:
谁起的作用本来就得靠对照实验才说得清(见 docs/09 的 I1 / I2)。
一句话里几件事,每件事各自去查记忆。你写「1. 改掉那个变量名 2. 部署走内网」 或「先把日志清一下;部署走内网」时,程序会按你写的分项把这句话切开, 每段各自去查一遍记忆,再把结果合并(同一条被两段命中时按更相关的那段算)。 不这么做的后果不明显但很实在:整句算一个向量时它是几件事的平均,跟哪一件 都不够像,于是「已经定下的 / 明显相关的事实」那一节会空掉——而关键词那条路 更直白:整句是当短语查的,长消息几乎必然查不到任何记忆。
切分的边界只认你手打的分项:编号(1. 1、 (1) 一、)、带圈数字
(①)、行首的项目符号(- *)、行首的「另外 / 然后 / 最后 / 其次」这类词、
以及分号。普通的换行不算边界——贴一段日志、一份配置时,几十行里没有一行是
「一件新的事」,按行切只会白花几次嵌入。最多切 5 段,超出的并进最后一段(不会丢)
;切不出第二段就完全按从前那样处理(普通消息的行为一个字没变)。
「换了话题」也按段判:一句话里两处都明显偏离当前话题,就直接算换话题 (以前要等下一轮);只偏一处仍然先观察一轮——一句话说偏可能是换个说法、也可能 是贴了一段报错,不该立刻把上下文切掉。你要是写了「另外问一下……」这类说法, 那就立刻算换,而且新话题是那一段,不是整句的平均。
人机交互的过程也留档(核心行为页的运行链路里看得到)。审批问过的那一步
后面接「问过:总是允许」,没等到回答(进程被杀、框被关)接「问过:问了,
没等到回答」——「没答」和「拒绝」是两句话,前者什么都没发生。记忆与定时任务
的候选也一样:不记 / 不建 这两条否定回答以前什么都不留,现在落一行,
候选卡上因此能写「这类提议你已经跳过 N 次」(按这段对话算)。三类交互、运行
链路那一步、账本那一行共用同一个 id,所以「第 3 轮为什么问我」查得到。
2.0 自动整合长期记忆(默认关闭)
「整合长期记忆」按钮是手动的。打开设置页里的开关后,换话题时会自动跑一次, 但只有这三条同时成立才真的跑(缺一条都不跑):
① 开关打开 默认关闭:不在你没要求的时候花钱
② 这一轮换了话题 显式说法(「换个问题」)立刻算,向量漂移要连续两轮
③ 这一段里出现过「该记住」的信号 「以后……」「记住……」「别再……」(只在你说的话里找)
跑之前会明说一句「换了话题,正在整理长期记忆(这一次会花一次模型调用)」—— 什么时候在花钱必须是你看得见的事实。
只发上次之后的消息:会话里记着一个水印(消息序号),整合成功才往前推。 所以第二次换话题不会把上次已经总结过的四十条重发一遍。模型答了但没按格式给出 条目时不推水印——那批内容还没被真正总结过,下次该再试一次。
2.1 错过执行的补跑
┌────────────────────────────────────────────────────────────────────────────────┐
│ ┌──────────────────────────────────────────────────────────── ⚠ 边框 ────────┐ │
│ │ 有 3 次该执行但没执行 │ │
│ │ 应用没运行的那些时刻不会被自动补做。要补哪一次由你决定: │ │
│ │ [ ▷ 补做 2026-09-14 09:00 ] [ 不补 ] │ │
│ │ [ ▷ 补做 2026-09-13 09:00 ] [ 不补 ] │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
- 总数照实说(3 次),但只列最近两次:用户记得自己最近在做什么,能判断这两次该不该补; 错过 47 次时列出全部他也想不起来。总数让他判断严重性。
- 不自动补做:「每天 9 点备份」在你开机时自动补做会产生一次你没预期的写入,时机也不对。
- 间隔型任务不报错过:它的语义是「距上次多久」,开机晚一点本就该顺延。
- 补做时会把「你补的是哪一次」告诉模型——「生成昨天的日报」这类带时点的内容, 不说清的话模型会按现在写。
- 失败会再试一次(只一次,约 60 秒后):网络抖一下、模型 429、机器刚启动时文件 还锁着——这类失败不值得等一整天(cron 型任务的下一个周期可能是一天以后)。 重试走的是同一条调度通道,所以「迟到多久算错过」那套判断没变;再失败就照旧 记成失败、等下一个计划点。执行历史里那一行会补一句「将重试 <时刻>」—— 只显示「失败」会让人以为那件事到此为止了。
- 同一件事不会被并行做两遍:到点触发、你点「补做」、重启后接着做,三条路共用 一道门;上一个还在跑时,后一个会如实报「上一次还在跑」,而不是重复启动 (两边会同时改同一份文件)。
2.2 新建定时任务(手动)
┌────────────────────────────────────────────────────────────────────────────────┐
│ + 新建任务 [收起 ▲] │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 要做什么,回车添加 [间隔秒数] │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ [每隔几分钟] [每天 ●] [每周] [每月] [自定义表达式] │
│ 时刻 ▼ 09:00 ▲ │
│ 每天 09:00 · 0 9 * * * │
│ 下次执行:2026-09-15 09:00 │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 发给模型的提示词(留空则用上面的标题) │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ 用哪个智能体 [不指定 ●] [代码审查] │
│ 能力范围与对话窗口一致:不指定智能体时跟随全局设置,指定了就用它的模型、工具与审批模式。│
└────────────────────────────────────────────────────────────────────────────────┘
任务行(右侧三个按钮:编辑 / 执行历史 / 删除):
┌───────────────────────────────────────────────────────────────────────┐
│ 生成每日简报 [✎ 编辑] [⌄ 历史] [🗑] │
│ 普通 可开始 全局 计划: 0 9 * * * 下次 2026-09-15 09:00 │
│ ┌ 执行历史 ─────────────────────────────────────────────────────────┐ │
│ │ 2026-09-14 09:00 按计划 · 成功 · 4.2s · deepseek-flash │ │
│ │ 今天没有值得报告的改动。 │ │
│ │ 2026-09-13 09:00 按计划 · 失败 · 连接超时 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
2.3 cron 编辑器:不用手写
频率 [每隔几分钟] [每天] [每周] [每月] [自定义表达式]
按频率显示 每周 → 时刻 ▼ 08:30 ▲ 星期 ‹ 周一 ›
中文说明 每周一 08:30
表达式 · 30 8 * * 1
预览 下次执行:2026-09-15 08:30
- 接受 5 段标准写法(
分 时 日 月 周)与 6 段(含秒)。保存时归一化成 6 段,回显保留你的原文。 - 星期用中文按钮,按周一到周日排(不是按编号——编号是 1=周日)。
- 「自定义」一档保留给图形化表达不完的形态(「每月最后一天」);你写的表达式原样保留, 认不出就放进自定义档,不静默重置。
- 时区按本地解释:你写
0 9 * * *,就是你这边的 9 点。
2.4 对话式创建定时任务
你:帮我每天 9 点检查一下依赖有没有新版本
助手:(正常回答你的问题)
─────────────────────────────────────────────────────────────────────────────
┌─────────────────────────────────────────────────────────────── 边框 ──────┐
│ 要不要建成定时任务? │
│ 检查依赖更新 · 0 9 * * * · 下次 2026-09-15 09:00 │
│ 对比 package.json 与上周的提交,列出新增的依赖 │
│ 可用工具:read、grep │
│ [ 建任务 ] [ 不用 ] │
└──────────────────────────────────────────────────────────────────────────┘
只提议、不自动建——定时任务会在未来反复执行,时间理解错了它会安静地一直跑下去。 与记忆候选同一个模式。
2.5 定时任务的能力范围
与对话窗口完全一致,不是另一套配置:
选了智能体 → 继承它的模型、系统提示词、工具白名单、审批模式
没选智能体 → 跟随全局设置
工具、审批、提示词一个都不另配——同一条规则配两处,两处必然分叉, 而分叉的表现是「在对话里能干的事,定时任务干不了」这种要查很久的问题。
与 MCP 是间接关系:MCP 工具由连接层注册进注册表,而上面那份白名单 决定它能不能用——与对话里同一条路径。
上下文也是同一套:两条路都走 turn::build_request,因此基座系统提示词、
记忆、skill 索引、结构化压缩与按条裁剪、超长工具输出落盘成可读回的引用,
定时任务一个不缺。这一点此前是真的分叉过:定时任务自己拼请求,只发预设
提示词(基座一条都没有)、没有任何上下文预算,跑久一点就把窗口撑爆,而界面上
一点异常都看不到。账本同样是同一本:每次模型调用(用量、费用、耗时、
首字延迟、成败)与每次工具调用(工具名、目标、裁决、耗时)都进 audit_record,
与对话那条路同一张表——此前这条路一次都没写,于是「这个月花了多少」在不跑
对话的时候是假的。仍然有意不同的只有三处,都写在 scheduler.rs 的注释里:
「需要确认」在无人值守时等于不执行、轮次用尽没有收尾轮、不接执行计划与附件。
代价要说清:一个配置成「全部放行」的定时任务在半夜会真的写文件。 这是你配置的结果,不是我们替你做的决定;执行历史会如实记下它做了什么。
执行历史逐次记录:
┌ 执行历史 ──────────────────────────────────────────────────────────────────┐
│ 2026-09-14 09:00 按计划 成功 耗时 4.2s deepseek-flash │
│ 工具:read ×3、grep ×1 │
│ 2026-09-13 09:00 按计划 失败 超时 │
│ 2026-09-12 09:00 补做 成功 耗时 5.1s │
└────────────────────────────────────────────────────────────────────────────┘
记的是计划触发的时刻而不是点按钮的时刻——否则补做的记录看起来像一次正常触发。 应用被强杀留下的「进行中」记录会在下次启动时标成失败,否则它会让那一次看起来 「做过了」,于是永远被跳过。
2.6 执行计划:两个上限与历次拆解(第 26 批)
上限可以在设置里改。 「模型服务」分区里,紧随「会话交互轮次」多了一张 「执行计划」卡片:
┌ 执行计划 ──────────────────────────────────────────────────────────────────┐
│ 拆出来的每一步最多重来几次、一次计划最多退回几次。两个上限缺一个都停不下来。 │
│ 单步重试上限(1–10) [ 3 ] [ 保存 ] │
│ 同一步骤反复不满足它的完成条件时最多再试几次。到顶就如实报告卡在哪一步。 │
│ 总回滚上限(0–20) [ 4 ] [ 保存 ] │
│ 一次计划里最多退回重做几次;0 表示不允许回滚,发现问题直接停下来报告。 │
└────────────────────────────────────────────────────────────────────────────┘
两个数各有分工:单步上限挡「这一步就是做不成」,总回滚上限挡
「两个步骤互相把对方的产出改坏」那种震荡——后者用单步上限是挡不住的
(每一步都在自己的上限之内)。输入框与 AppSettings::sanitize 用同一区间,
不会出现「填进去的数被悄悄改掉」。
历次拆解在核心行为页(任务与运行记录之间):
┌ 执行计划 ──────────────────────────────────────────────────────────────────┐
│ 历次拆解都在这里(当前这次在对话右侧的「执行计划」面板里)。 │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ 先写一份调研笔记,然后按主题整理成表格并导出 CSV │ │
│ │ 已完成 进度 2/2 步 全局 2026-09-16 13:20 │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ 把这个模块拆成三个文件,顺便补上单元测试 │ │
│ │ 等你确认 进度 1/3 步 回滚 2 次 项目(…\RWork) 2026-09-16 13:20 │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
与对话右侧那个面板分工明确:那个显示当前这次的每一步与每次尝试(含退回原因), 这个按作用域回答「上次让它拆的那件事做到哪了」。回滚次数为 0 时不显示—— 它是这一页里最能解释「为什么卡住」的那个数,而不是每行都该有的装饰。
切进这一页会重读:这一页的内容几乎全是你在别处改掉的(对话里拆出的计划、 那一轮才建出来的记忆向量表),不重读就会显示打开窗口那一刻的快照。
3. 附件
┌────────────────────────────────────────────────────────────────────────────────┐
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 输入消息,Enter 发送,Shift+Enter 换行 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ [📄 报表.xlsx ×] [🖼 截图.png ×] [📄 规范.pdf ×] │
│ 将使用 deepseek-flash [📎] [ 发送 ] │
└────────────────────────────────────────────────────────────────────────────────┘
| 类别 | 扩展名 | 处理方式 |
|---|---|---|
| 图片 | png, jpg, jpeg, gif, webp, bmp | 多模态模型 → base64 直接发;看不了图时走本机 OCR(见下) |
| 表格 | csv, tsv, xlsx, xls, xlsm, ods | 不内置解析器,把路径交给模型,由它自己写脚本读 |
| 文档 | pdf, docx, doc, odt, rtf | 同上 |
| 文本 | txt/md/json/yaml/toml/ini/log/xml + 40 余种源码扩展名 | ≤ 24000 字节直接内联 |
- 上限:单个 32 MB、一次 8 个。
- 不认识的扩展名一律拒绝,不当成文本硬读。
- 任何一条转换失败都中止发送:静默丢掉一个文件,你会以为模型看过了而据此判断答案。
- 附件落库存的是当初真正发出去的内容,重开会话时原样回放。
图片走本机 OCR(当前模型看不了图时)
用户:①这张发票的金额是多少 [🖼 发票.png ×]
当前模型不支持图片,这些图片会用本机 OCR 识别成文字发过去:发票.png
助手:▸ 识别到「价税合计 1,234.56 元」,金额是 1234.56 元。
- 用百度 PP-OCRv6(检测 + 识别两个模型,共 6.3MB)+ onnxruntime,纯本机推理, 一张 900×320 的截图约 33ms。
- 模型与运行时放在程序目录旁的
.models/ocr/下——和嵌入模型同一处 (第 42 批统一了此前分居两处的模型目录),整个程序目录能拷给别人用。 用到时自动下载(点发送发现没装就先下,下完提示再发一次),设置页的 「本地 OCR」卡片里也能手动下,两处都有进度与结果;装完逐个文件校验哈希。 - 发出去的正文里带一行说明(「图片:…,用本机 OCR 识别成文字」)与识别出的 文字,模型不会以为它看到了原图——画面里的颜色、版式、位置它并不知道。
为什么要模型自己写脚本,而不是内置解析器
办公格式的多样性没有尽头(xlsx 的共享字符串表、Excel 1904 时制、PDF 扫描件), 每支持一种就欠一笔债。而模型本来就会写代码。应用只保证环境可用并给路径:
用户:这个表格里第三列的总和是多少
助手:▸ run_script { language: "python", packages: ["openpyxl"],
code: "…load_workbook('…/book.xlsx')…" }
结果:1450
4. 脚本运行环境
┌────────────────────────────────────────────────────────────────────────────────┐
│ 脚本运行环境 │
│ ✓ node v22.23.1 C:\Program Files\nodejs\node.exe │
│ ⚠ python 没有找到 [ ⬇ 下载并安装 ] │
│ 没有可用的解释器时,模型无法处理 Office / PDF / 表格这类文件。 │
│ 将下载约 30 MB 并安装到 %APPDATA%\RWork\runtimes(不改系统、不需要管理员) │
│ 镜像源:pip 阿里云 · npm npmmirror [ 重新检测 ] │
└────────────────────────────────────────────────────────────────────────────────┘
- 检测:跑
--version取版本。存在但跑不起来(如 Windows 商店的占位别名) 不算可用——那种情况下报「不可用」而不是报个版本号。 - 一键安装:从国内镜像下载(node 走 npmmirror、python 走华为云),解压到用户目录。 不装到系统、不要提权——否则一次「点一下」会变成一串 UAC 弹窗。
- 镜像源:pip 与 npm 各自的候选列表,以环境变量传给子进程,不写
pip.conf/.npmrc(那是全局配置,改了会影响你的其它项目)。 - 默认 pip 源用阿里云而非清华:实测清华对所有包返回 403。
run_script 工具
模型要跑代码时用它,它替模型做掉那些与任务无关的机械工作:
run_script { language: "python" | "node" | "shell",
code: "…", // 多行脚本,落到 .rwork/scripts/ 再跑
packages: ["openpyxl"], // 缺包自动装,走镜像源
timeout_seconds: 300 }
- 工具块的文字一律按默认字号渲染:输入输出是要读的内容,不是脚注。 把它们缩成小字是这类界面里最常见的毛病——为了「不抢正文的戏」牺牲可读性, 而它恰恰是最需要看清的那部分。
- 脚本写成文件再跑,而不是
-c "...":绕开命令行转义。 - 装包走镜像源;
PYTHONIOENCODING=utf-8;python 加-u关缓冲。 - 缺解释器时在写脚本之前就拒绝,不留垃圾文件。
- 配额:超时 300 秒、输出 24 KB、脚本 64 KB。
- 装包也有上限(180 秒):
pip install是这条路上唯一会长时间不返回的动作, 镜像不响应时它会一直等下去。超时就掐掉,并把「依赖可能没装上」如实告诉模型—— 否则它接下来看到的ModuleNotFoundError会被当成自己代码写错了。 - 每一步都进日志(见
README的「日志」一节):起进程、装包、运行、结束或超时。 一次「卡住了」的排查全靠这几行。 - 审批档位是执行——脚本能做的任何事都等同于一条命令。
5. Skill(可复用的做法说明)
┌────────────────────────────────────────────────────────────────────────────────┐
│ Skill(可复用的做法说明,按需注入给模型) [导入 skill] [添加目录]│
│ ┌─────────────────────────────────────────────────────────────────── [☑] ┐ │
│ │ 生成周报 把本周改动整理成固定格式的周报 │ │
│ │ 来源:D:\skills\weekly-report\SKILL.md │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
- 形式:YAML frontmatter(
name+description)+ markdown 正文。 接受<名>/SKILL.md,也接受平铺的.md。 - 导入:
.zip/.skill文件,或选定一个目录(存目录、每次启动重扫, 于是你在应用之外改了 skill 文件,重启即可生效)。 - 注入策略:系统提示里只放索引(名字 + 一句话),正文由
skill工具按需读取。 skill 正文动辄几千字,无条件注入会让每一轮对话都为用不到的内容付费。
6. MCP 服务器
┌────────────────────────────────────────────────────────────────────────────────┐
│ MCP 服务器 [+ 添加服务器] │
│ ┌─────────────────────────────────────────────────────────────────── [☑] ┐ │
│ │ filesystem │ │
│ │ npx -y @modelcontextprotocol/server-filesystem D:\work │ │
│ │ 启动应用时连接并列出它的工具;工具会以「filesystem__工具名」出现,调用前需确认。 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
- 配置形状照抄生态(
{ "mcpServers": { <别名>: {...} } }), 于是你从别处复制的配置能直接粘进来。 - 支持 JSON 导入 / 导出(导出时以名字为键,不重复写
name)。 - 一轮连接一次、用完即关:不常驻。定时任务可能在半夜跑,常驻子进程会积累 脏状态;而它们是外部可执行文件,持有会阻止它们退出(你会在任务管理器里 看到一堆不认识的进程)。代价是每次多一次握手,相对一次模型调用可以忽略。
- 每台服务器给一个工具声明(远端工具的 schema 是运行时才知道的),
参数里用
action指定调哪个远端工具;可用工具名注册成<服务器>__<工具>。 - 与对话/定时任务的关系是间接的:连接层把工具注册进注册表, 而智能体(或全局)的白名单决定它能不能用——两条路径同一条规则。
- 审批档位是执行:MCP 服务器的实现由外部提供,无法预知它做什么,因此一律先确认。
- 连接失败如实报错(列出是哪几台、为什么),否则你会对着一个静默少了的 工具表猜为什么模型不用它。
- 远程传输(HTTP/SSE)如实说不支持,而不是假装连上。
- 只提供 prompts 的服务器对
tools/list回-32601,识别为「连接成功,只是没有工具」。
7. 权限
┌────────────────────────────────────────────────────────────────────────────────┐
│ 工具与权限 │
│ 审批模式 [每次都问 ●] [自动写入] [全部放行] │
│ 已记住的授权 │
│ ┌─────────────────────────────────────────────────────────────────── [×] ┐ │
│ │ bash npx 手动设置 用了 3 次 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────── [×] ┐ │
│ │ write D:\work\notes 多次批准后记住 用了 7 次 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
三层级的工具声明,可按参数变化:
| 档位 | 含义 | 「每次都问」 | 「自动写入」 | 「全部放行」 |
|---|---|---|---|---|
read |
只读取 | 自动放行 | 自动放行 | 自动放行 |
write |
工作区内写入 | 询问 | 自动放行 | 自动放行 |
exec |
执行命令/脚本 | 询问 | 询问 | 自动放行 |
destructive |
删除 | 询问 | 询问 | 仍然询问 |
bash 遇到 rm -rf 会自己升级成强制确认,遇到 rm -rf / 直接拒绝。
审批对话框画在整页最上层,并且是页内浮层而不是窗口级对话框:循环停驻在一个 channel 上 等回答的期间,输入区与发送键都被禁用,因此无法在第一个回合被答案阻塞时启动第二个回合。 输入区下方那行提示此时显示的是「有一步操作在等你确认」,而不是「正在生成回复…」—— 审批期间模型那边什么都没发生,说成「正在生成」会让人一直等下去。
内容研判在决定前跑(即使模式是「全部放行」):
- office 文档、PDF、压缩包、图片是容器格式,按文本改写会破坏它们 → 拒绝。
- 版本库内部目录(
.git/等)→ 拒绝。 - 密钥文件(
.env、id_rsa…)、工作区外路径、符号链接、只读文件、大文件、整文件覆盖 → 升级为需要确认。
授权作用域分层,更具体优先,且拒绝优先于允许:
本次会话 < 工具 < 工具组(整个 MCP server)< 目录 < 工具+目录 < 单个文件
所以针对 .env 的拒绝不会被目录级允许覆盖。
用户意图:只有你的话能预授权。
- 「把这几个 import 改一下」→ 预授权它点名的文件。
- 「只看不改」→ 整轮拒绝所有写入与执行。
- 绝不从模型输出推断权限——能自己放宽权限的模型会让其它保证失效。
在线学习(从对话过程里学)
奖励信号来自对话本身,没有环境奖励、不做梯度更新:
| 信号 | 来源 | 效果 |
|---|---|---|
| 正 | 你在弹窗上点「同意」 | 计数 +1 |
| 负 | 你在弹窗上点「拒绝」 | 计数 -1(降低通过率) |
| 强负 | 你在对话里明确说不要(「别动那个文件」) | 一票否决 |
| 强负 | 你强制终止(停止生成、取消这一轮) | 一票否决 |
相似度超过 60% 就按同一类处理,因此 src/a.rs 与 src/b.rs 是同一类,
src/ 与 docs/ 不是。权重:
同工具 + 同目录 1.0
同工具 + 父子目录 0.9
同工具 + 无关目录 0.6 ← 刚好门槛
不同工具 + 同目录 0.7
两者都不同 0.0
「不同工具但同目录」(0.7) 高于「同工具但目录无关」(0.6) 是有意的: 你在同目录下反复批准过写文件,说明你对「有人动这个目录」放心; 而「同一个工具在完全无关的目录被批准过」不说明新目录安全。
一票否决不给计数:给计数的话「拒 1 次但批了 9 次」会算出 0.9 而被放行,
而你那次拒绝是明确针对这个情况的。否决的作用范围与学到的作用域是同一套相似度
——拒绝 src/ 不会让 docs/ 也不可用。
学到的规则区分来源:你手写的显示「手动设置」,引擎推断的显示「多次批准后记住」, 两者都能单独撤销。学习只从你真正回答过的问题取数(「没问就执行」不算批准)。
8. 用量审计
┌────────────────────────────────────────────────────────────────────────────────┐
│ 用量审计 [近 7 天 ●] [30天] [90天] [全部]│
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 调用次数 │ │ 总 Token │ │ 预估费用 │ │ 成功率 │ │ 平均首字 │ │
│ │ 1,284 │ │ 3.2M │ │ ¥ 12.84 │ │ 98.2% │ │ 0.82s │ │
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 日历热点图(26 周,周一起始) 口径:[费用 ▾]│
│ ░░▒▒▓▓░░▒▒▓▓██▓▓▒▒░░ ░▒▓█▓▒░ … │
│ 悬停显示当天明细 │
│ │
│ 按天趋势:折线=调用次数 柱形=Token │
│ ╭──────────────────────────────────────────────╮ │
│ │ ╭─╮ ╭─╮ │ │
│ │ ╭───╯ ╰──╮ ╭───╯ ╰──╮ │ │
│ ╰──╯ ╰─╯ ╰──────────────────────╯ │
│ │
│ 按模型汇总 │
│ 模型 调用 输入 输出 缓存 费用 占比 平均耗时 │
│ deepseek-flash 890 1.2M 0.4M 0.9M ¥ 6.20 48% 3.1s │
│ claude-sonnet 394 0.8M 0.3M 0.6M ¥ 6.64 52% 5.2s │
│ │
│ 调用明细(滚动到底自动加载下一页) │
└────────────────────────────────────────────────────────────────────────────────┘
- 每次调用(成功、失败、取消)都写一条:模型、服务、五个 token 计数、费用、耗时、首字延迟、结果。
- 窗口可切近 7/30/90 天、近一年、全部。
- 热点图 26 周、周一起始,可按调用次数 / Token / 费用三种口径着色。
- 工具调用与模型调用分开统计,所以「求助了多少次 API」和「它实际动了什么」都能查。
价格不在代码里硬编码,从 models.dev 导入(213 个服务、7700+ 模型, gzip 约 457 KB),缓存进数据库后离线可用。
匹配要求命中接口地址或服务名,没有「谁有这个模型 id 就用谁」的兜底:中转站会用同样的 id 发布加价后的价格,挂到直连端点上会污染审计数字。匹配不到的模型如实显示「参数未知」。
9. 设置
┌────────────────────────────────────────────────────────────────────────────────┐
│ 设置 外观、模型服务与数据留存 │
├────────────────────────────────────────────────────────────────────────────────┤
│ 1. 智能体 名字 + 系统提示词 + 模型 + 工具白名单 + 审批模式 │
│ 2. 脚本运行环境 node / python 检测与一键安装(见 4) │
│ 3. 扩展 Skill(见 5)+ MCP 服务器(见 6) │
│ 4. 外观 主题、界面字号、等宽字号 │
│ 5. 工具与权限 审批模式 + 已记住的授权(见 7) │
│ 6. 模型服务 服务名、类型、Base URL、密钥、模型列表(含价格) │
│ 7. 本地数据 数据库位置与大小、保留策略、清理入口 │
└────────────────────────────────────────────────────────────────────────────────┘
模型服务
┌────────────────────────────────────────────────────────────────────────────────┐
│ 模型服务 [同步模型目录] │
│ ┌─────────────────────────────────────────────────────────────────── [🗑] ┐ │
│ │ DeepSeek │ │
│ │ 类型 [Anthropic ▾] │ │
│ │ 接口地址 https://api.deepseek.com/anthropic │ │
│ │ API 密钥 •••••••• 或 环境变量 DEEPSEEK_API_KEY │ │
│ │ 默认模型 [deepseek-flash ▾] [↻ 刷新模型列表] │ │
│ │ 模型 │ │
│ │ deepseek-flash 上下文 1,000,000 输出 384,000 ¥2/¥8 [✨] [×] │ │
│ │ deepseek-v4-pro … │ │
│ │ 定价区(可滚动,用常驻滚动条) │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
- 三种接口方言:OpenAI 兼容 / Anthropic / Ollama。
- 密钥可从环境变量读:变量名填了却取不到值会明确报错,不静默发空密钥。
- 「同步模型目录」按缺口填充:只写空的字段,手填的价格不会被覆盖。
- 每个模型行有「✨」按钮做同样的事。
同一个分区里还有两张跟「花一次调用」有关的卡片:
┌ 会话交互轮次 ──────────────────────────────────────────────────────────────┐
│ 每回合工具轮次上限(1–50) [ 12 ] [ 保存 ] │
│ 自动整合长期记忆 [ ●] │
└────────────────────────────────────────────────────────────────────────────┘
┌ 执行计划 ──────────────────────────────────────────────────────────────────┐
│ 单步重试上限(1–10) [ 3 ] [ 保存 ] ← 见 2.6 │
│ 总回滚上限(0–20) [ 4 ] [ 保存 ] │
└────────────────────────────────────────────────────────────────────────────┘
放一起是因为它们回答的是同一类问题:什么时候会为这一轮花掉一次模型调用 (轮次与自动整合),以及一次回合允许重来几次(执行计划的两个上限)。
本地模型与本地 OCR
┌────────────────────────────────────────────────────────────────────────────────┐
│ 本地模型 │
│ bge-small-zh-v1.5(92 MB) 已就绪 │
│ 下载后放在程序目录旁的 .models/ 下… [已下载] │
├────────────────────────────────────────────────────────────────────────────────┤
│ 本地 OCR │
│ PP-OCRv6 tiny(19.9 MB) 还没有下载 OCR… │
│ 模型来自 PaddleOCR(百度,Apache-2.0) [⤓ 下载] │
│ 放在 .models\ocr 下:D:\…\.models\ocr │
└────────────────────────────────────────────────────────────────────────────────┘
- 嵌入模型与 OCR 的模型、运行时都放程序目录旁的
.models/(OCR 在它下面的ocr/)——整个.models能拷给别人用。第 42 批把此前分居两处的模型目录统一了: 位置只由RWORK_MODELS一个变量决定。 - 两处的状态都分三种:没文件 → 去下载;文件不对(下了一半、拷错了)→ 删掉重下;就绪 → 没事做。
- 下载都带进度(按「正在下哪个文件 / 包」显示),完成后立刻校验哈希。
本地数据
┌────────────────────────────────────────────────────────────────────────────────┐
│ 本地数据 │
│ 数据库 C:\Users\ligen\AppData\Roaming\RWork\rwork.db 12.4 MB │
│ 保留策略 [✓] 启用 保留 [90 ▾] 天 [✓] 置顶对话豁免 │
│ [✓] 启动时清理 [ ] 清理后压缩(VACUUM) │
│ 上次清理 2026-09-14 09:12 清理 3 段会话、412 条审计记录 [ 立即清理 ] │
└────────────────────────────────────────────────────────────────────────────────┘
清理前会显示「将清理多少条」,不会先斩后奏。
后台与启动
┌────────────────────────────────────────────────────────────────────────────────┐
│ 后台与启动 │
│ 关掉窗口之后程序还在不在、开机之后它自己会不会回来——定时任务能不能无人值守, │
│ 就看这两条。 │
│ │
│ 关窗口后继续在后台运行 [ ●——] 开 │
│ 关窗口只是把界面收进托盘,定时任务照常执行;要真正退出,用托盘图标右键菜单里的 │
│ 「退出」。关掉这一条时,关窗口就是退出。 │
│ │
│ 开机自动启动 [ ○——] 关 │
│ 开机时静默启动(只驻留托盘,不弹窗口)。写的是 HKCU\Software\Microsoft\… │
│ \Run 里的一项,关掉它就会删除那一项;目前只支持 Windows。 │
└────────────────────────────────────────────────────────────────────────────────┘
第一条默认开:定时任务是无人值守的,「关窗口 = 退出」会让它在用户以为它还在跑的 时候悄悄停下。第二条默认关:它是这台机器上的一次持久改动,得由用户点头。
2.7 后台常驻与「被杀之后接着做」
窗口右上角 ✕ ──▶ 窗口消失、进程还在(托盘图标留着)
托盘图标 右键 ──▶ ┌ 打开主界面 ┐
└ 退出 ┘
再双击一次 exe ──▶ 已经开着的那一个把窗口叫回来,新的那个自己退出(单实例)
- 关窗口 ≠ 退出:关掉窗口只是把界面收进托盘,定时任务照常执行。真要退出走托盘菜单 那一项(或者把设置里那条开关关掉,那时关窗口就是退出)。第一次收进托盘时会冒一个气泡 告诉你这件事——否则「我明明关了它」是最容易产生误解的地方。
- 托盘图标:右键有「打开主界面 / 退出」,双击也是打开。用的是系统默认图标(仓库里没有
.ico)。 - 开机自启:设置页「后台与启动」里打开它,写
HKCU\...\Run的一条值 ("<程序路径>" --silent);--silent表示开机时不弹窗口,只驻留托盘、直接开始跑任务。 - 单实例:第二个实例不会重复跑任务——它把已经开着的那个窗口叫到前台,然后自己退出。
这一条对无人值守是硬要求:
due_tasks是「查一次、改一次」两步,两个进程同时跑会各跑一遍。 - 被杀之后接着做:进程被杀(或断电)时,那一轮任务在库里留下「跑到哪一步」;下次启动会
接着做一次——用的是原来那段会话,提示词里写清「上一次执行在第 N 步被打断,请接着那次
继续,已经做完的不要重做」。收件箱式的交代就在那段会话里,翻得到。
没有跨任务的通用队列:只有定时任务这一轮会被接着做(
docs/10设想的job表仍不存在)。
10. 工作空间
┌────────────────────────────────────────────────────────────────────────────────┐
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ RWork 工作台 [📁] [«] ← 折叠按钮方向性:收起 › 展开 ‹ │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ 点 [📁] 展开: │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 工作空间 │ │
│ │ 当前 RWork │ │
│ │ D:\Develop\workspace\gitonline\RWork │ │
│ │ 最近 ▸ 另一个项目 │ │
│ │ [ 📁 选择文件夹 ] [ + 新建项目 ] │ │
│ │ 新建项目:名字 [________] [✓] 同时 git init │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
- 全部用系统文件夹选择器,没有手输绝对路径的输入框。
- 主工作空间 + 最多 10 个额外根;最近使用最多记 8 个。
- 侧栏右边缘 6px 可拖动改宽(200–520px)。
- 工作空间决定:相对路径的基准、写入是否算「工作区内」、项目范围记忆与任务的可见性。
- 工作空间跟着会话走:每一段对话都记住自己创建时(或被切换时)的那个目录, 之后在别处切换工作空间不会把这段对话挪走。侧栏顶部显示的、以及工具实际使用的 都是这一段的那个目录——显示全局而实际按会话跑,等于界面在说谎。
- 切换器同时做两件事:改全局默认(新建会话用哪个目录),并把当前这段会话 钉到新目录。只做前者时,同一个动作对「还没聊过的会话」和「已经聊过的会话」 效果不同,没人能预测。
- 空字符串表示「跟随全局」——升级前就存在的会话迁移过来就是这一种, 因此它们的行为与升级前完全一致(我们并不知道那段对话当初是在哪个目录里进行的, 回填只会猜错)。
11. 外部智能体(omp)
┌────────────────────────────────────────────────────────────────────────────────┐
│ 设置 → 工具与扩展 → 外部智能体 │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 外部智能体接管回合 [ ●——] │ │
│ │ 打开后,这一轮由外部程序(默认 omp --mode rpc)执行…… 关 │ │
│ │ omp --mode rpc │ │
│ │ 注意:当前配置下,它对工作区的写入不经过 RWork——只读工具清单为空(等于把它 │ │
│ │ 的内置工具全部打开),或你在参数里自己写了 --tools 并且里面有 write / edit / │ │
│ │ bash。那些改动没有权限判定、没有内容研判,也不会登记文件出处,记录里只剩转述。│ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────────┘
打开开关后,对话里的每一轮:RWork ──prompt──> omp ──事件流──> 逐字上屏
│
├─ text_delta → 正文
├─ thinking_delta → 思考通道
├─ tool_execution_* → 工具块(它执行的)
└─ turn_end/agent_end → 用量、费用、收尾
↓
镜像进库(助手消息 + 工具消息 + 一条账本)
- 它执行,我们记账:这一轮的工具由外部程序决定并运行,RWork 不做二次执行
(工具调用在这里是已经发生的事实,不是「请你去跑」)——因此镜像时
工具结果走一条单独的路,与
StreamEvent::ToolCall分开。 - 但写入类工具交回 RWork:
启动:omp --mode rpc --tools read,grep,glob ← 它的内置工具收成只读
set_host_tools: rwork_write / rwork_edit / rwork_bash ← 我们提供
它请求:host_tool_call { toolName: "rwork_write", arguments: {...} }
我们: prepare → decide(纯函数引擎 / 内容研判 / 历史授权)→ 需要时弹同一个审批框
→ invoke(在工作空间里真的写)→ 审计 + 文件出处登记
回它: host_tool_result
于是「它每一次改动工作区或执行命令」都要过 RWork 的权限判定——这是
「谁执行谁负责」的落点,也是审计页能回答「它到底动了什么、谁批准的」的原因。
名字必须加 rwork_ 前缀:实测宿主工具不能与它的内置工具重名(即使那一个已经
被 --tools 关掉),否则 set_host_tools 直接回 conflicts with an existing tool。
- 库里三种结果分得开:
host(我们执行的)、external-write(它自己执行的写)、external(它自己执行的读与搜索)。重放时不会把别人的工具输出当成我们能重跑的东西; 而「这一次改动没经过我们」在对话里也直接写在工具块的名字旁边(未经 RWork 的写入), 不必去翻库。它自己跑的那一次,我们只看得见结果——所以宁可少标,也不乱标: 名字认不出来时按external记。 - 「这一段是转述」也写在名字旁边:它自己执行的读与搜索标成「由它自己执行(转述)」, 与我们执行的那些区分开;写法是陈述语气,与写入那条警告色互斥。这两句合起来回答同一个 问题——这一段对话里哪几次调用真的经过 RWork。
- 用量与费用取它报的那一份(
turn_end的usage.cost),记在omp名下。 审计页因此能把「它花的」与「RWork 自己花的」分开;价格表是它的, 按我们的表重算只会得到两个都不对的数。 - 按对话复用一个进程:对方的会话状态(它自己的上下文、它压缩过的历史)
接着上一次才算同一段对话。切走对话或退出应用时收掉(
kill之前先关 stdin, 让它把待发输出发完)。 - 默认关闭,而设置页里写清了边界:只有写入类工具过 RWork 的权限链; 它的读与搜索、以及它的上下文压缩都是它自己的,那部分在这份记录里是转述。 读为什么不设边界(我们自己的读同样自动放行、同样不做工作区包含检查)、 兜底与残余风险是什么,写在架构文档 §8 第 3 条(威胁模型)。
- 没装 omp 也不影响任何事:开关默认关着,而开着但起不来时给的是 「启动外部智能体「omp」失败:……」,不是一句「出错了」。宿主工具注册失败时 也会说出来(那一轮它就只能读、改不了工作区),而不是让用户以为写入受管辖。
12. 本地数据与渐进刷新
- 单文件 SQLite(WAL、
synchronous=NORMAL、外键开启),默认%APPDATA%\RWork\rwork.db,可用RWORK_DB覆盖。 - 审计记录不挂在对话下:删掉对话不会抹掉「曾经花过这些 token」这一事实。
- 模型运行时与下载的安装包放在和数据库同一个目录下。
首屏渐进刷新:窗口先出现,内容分层补齐,任何一侧慢都不拖住其它部分。
1. 运行时与数据库就绪,内置字体在首帧之前注册(避免中文落到替代字体)
2. 打开窗口,渲染侧栏、导航与启动清单 —— 此时已可交互
3. 启动管线依次报告「打开数据库 → 读取设置 → 载入模型服务 → 汇总审计」
4. 每个卡片各自持有 加载中 | 就绪 | 失败,各自发查询、各自重绘
5. 慢查询(调用明细)滚动到才加载
13. 数据流
GPUI 界面线程 ──渲染──> 页面实体 ──spawn──> tokio 运行时
│ │
│ ├─ spawn_blocking: SQLite 查询
│ └─ reqwest: 流式 SSE
│
逐帧 delta 重绘 ── 完成后写入消息 + 审计记录
界面线程只负责渲染,不做 IO。一个要点:GPUI 的异步执行器不是 tokio,
而 cx.spawn 的 future 在界面线程上被 poll,因此主线程必须始终处在 tokio 上下文里
(main.rs 的 runtime.enter())——缺了它,任何 reqwest/tokio::time 调用都会 panic
而不是返回错误。
定时任务的调度循环不同:它在普通线程上自建 tokio 运行时,与界面完全解耦。
14. 命令行与配置
cargo run -p rwork-app # 依赖 Rust 1.90+(edition 2024)与 MSVC 工具链
rwork-app.exe --silent # 静默启动:窗口建出来但不显示,只驻留托盘(开机自启用它)
--silent 是开机自启写进注册表的那一份命令行。只有一个实例:第二个进程会把已经开着的
那个窗口叫到前台,然后自己退出。
环境变量:RWORK_DB(数据库位置)、RUST_LOG(日志级别,默认 rwork=info,warn)、
以及各 API 密钥的环境变量名(在服务配置里填变量名)。
设置落在数据库 meta 表(键 settings 的 JSON),也可手改后重启。
主要项:locale / theme / font_size / retention / audit_window_days /
heatmap_metric / providers[] / approval_mode / workspace / sidebar_width /
enabled_tools / tool_policies / learn_from_history / max_tool_rounds /
max_context_tokens / agents[] / disabled_agents / skill_dirs / disabled_skills /
last_page / run_in_background。
(开机自启不在这个 JSON 里:它的唯一事实是注册表 HKCU\...\Run 里那一项。)
密钥按明文存在本机数据库文件里,不会外传。
15. 快捷键
| 键 | 作用 |
|---|---|
Enter |
发送消息(对话页)/添加(记忆、任务输入框) |
Shift+Enter |
换行 |
Esc |
清空搜索框 |
附:四个页面的导航
对话 ────────── 与模型交流;工具调用、附件、流水线、权限确认都在这里
│
核心行为 ────── 长期记忆(项目/全局)+ 任务与定时任务(含补跑、执行历史)
│
用量审计 ────── token 账本:窗口统计、热点图、按天趋势、按模型汇总、调用明细
│
设置 ────────── 智能体 / 脚本环境 / 扩展(Skill+MCP) / 外观 / 权限 / 模型服务 / 本地数据