DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Kr-ATG /

Kr-ATG/dsh-seeker-bot

Verified

DeepSeek Harness 的对话增强套件:Seeker 双栏执行大盘、可交互卡片、Bot 团队/群聊/语音、记忆·档案·技能七个工作台。零源码改动,插件注入即用。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHubProject homepage
READMESource: master@77c811b0
dsh-seeker-bot

dsh-seeker-bot

DeepSeek Harness 的对话体验增强套件

双栏执行大盘 · 可交互卡片 · Bot 团队 / 群聊 / 语音 · 记忆 · 档案 · 技能 · 同步

stars forks license dsh ci discussions awesome-dsh-plugin last commit

Seeker 双栏执行大盘 + HTML 卡片

这是什么

普通 AI 对话界面只给你一个输入框和一个滚动条。dsh-seeker-bot 把「AI 到底在干什么」摊开给你看, 再把记忆、档案、技能这些散落的东西收成一排工作台:

普通 AI 对话界面只给你一个输入框和一个滚动条。dsh-seeker-bot 把「AI 到底在干什么」摊开给你看, 再把记忆、档案、技能这些散落的东西收成一排工作台:

你在意的 装上之后
想知道 AI 干了什么,但不想看一屏工具日志 右栏双栏执行大盘,工具调用翻成中文人话
想让它直接给个能点的东西 正文里出 HTML 卡片(真跑 JS)与 iu 原生交互卡(滑块 / 表格 / 清单 / 看板)
想让它画架构图 / 流程图 手写内联 SVG,直接在对话流里渲染
想要一整个 AI 团队 主 Bot + 子 Bot + 群聊(成员真能互相看见上下文)+ 日记 + 语音通话
想让 AI 记住你的偏好 工作台记忆(按 owner 隔离,Bot 之间不串味)
存账号密码 / 卡 / 证件,还要自动进上下文 工作台档案(端侧加密,明文不进模型上下文)
换电脑不想重新配一遍 同步设置:记忆 / 档案 / 技能 / MCP / 密钥端侧加密推到你的 GitHub 私库

安装

dsh plugin --profile web add github:Kr-ATG/dsh-seeker-bot

装完重启 DSH 即可。本包声明了 dsh.bundle.patch,dsh plugin add 会自动加进 profile 的 bundles 层,不用手改配置文件。

零 DSH 源码改动 —— 全部能力以 Cordis 插件注入,官方升级不动它。

安装 / 回滚请看 docs/INSTALL-TROUBLESHOOTING.md。 不要手工改 profile,也不要用定时 kill 重启当前 DSH。

适合谁

  • 已经在用 DeepSeek Harness (DSH),觉得官方界面不够用
  • 需要中文界面 + 中文人话的执行过程反馈
  • 想把 AI 当团队用(多 Bot 协作、群聊、定时干活、语音通话)
  • 关心数据在自己机器上:档案、记忆、密钥全部端侧存储与加密

目录

  • 这是什么
  • 安装
  • 适合谁
  • 你可能最想要的
  • Seeker 视图:双栏执行大盘
  • 正文里能出卡片
  • 对话读起来更顺
  • 消息撤回:对话回到那条消息之前
  • 对话完成胶囊
  • 侧边栏:工作区 ⇄ 操作间
  • Personal Agent(Bot 模式)
  • 六个工作台

你可能最想要的

想要 在哪
看 AI 到底在干什么,但不想要一屏工具日志 Seeker 视图右侧大盘;左栏只留一行「正在…」
让 AI 直接给个能点的小工具 / 可视化 / 演示页 正文里写 ```html 围栏 → HTML 卡片
让 AI 画流程图 / 架构图 / 时序图 正文里写 ```html 围栏,手写内联 SVG → HTML 卡片
快速取值换算 / 对比 / 清单,不要长篇大论 输入框发 /iu 开头 → iu 原生卡片
让 AI 知道「我现在开着什么」 输入框打 @ → 运行中的程序
给不会编程的人看「这轮做了什么」 右栏操作面板卡,中文人话时间线
跑完以后提醒我,点一下回到那条会话 对话完成胶囊(左下角小横条 / 顶部悬浮)
这轮对话不想留了,想从那条消息重来 用户消息旁 ↶ 撤回(对话回退,项目文件默认不动;附件回填输入框)
让 AI 记住我的偏好和身份 工作台记忆
存账号密码 / 卡 / 证件照片,还要能自动进上下文 工作台**档案**
存 Clash 订阅 / 配置文件 / 证书,换机也不丢 工作台**档案** → 记录详情「文件」区(随云同步上传)
换机后账号密码 / 记忆 / 技能 / MCP / 供应商与密钥 / 实时语音 Live 供应商一起跟过来 工作台**同步设置** → 勾选要漫游的资产,推自己的 GitHub 私库(端侧加密)
管 API Key / 代理 / 生图模型 官方设置弹窗 → 供应商
搜索额度打满了,自动换一家继续搜 网页搜索降级链(AnySearch → Tavily → DeepSeek)
想要一整个 Personal Agent 团队:主 Bot + 子 Bot + 群聊 + 日记 + 通话 侧栏顶部 Work / Bot 切到 Bot → Personal Agent
一键开始 / 挂断 Bot 语音通话(全局热键) 官方 设置 → 通用 → 快捷键 → 「语音通话(开始 / 挂断)」,默认 Ctrl+Alt+V;桌面壳下窗口没焦点也能按,在哪个 Bot 界面就通话哪个
Bot 到点自己干活,不用我一直开着页面 右栏例行任务 / 主动询问,执行在宿主常驻进程里 → 多消息
Bot 像同事一样分段汇报,而不是憋一大段 每条消息独立气泡,风险 / 待决策 / 待批准各是卡片 → 多消息

Seeker 视图:双栏执行大盘

会话头部标签栏多一个 「Seeker」(内部代号 KR),与官方「对话 / 轨迹」并列。点进去, 底层仍是官方 ChatView(多轮历史、虚拟滚动、Markdown、输入框全部原样),右边多一栏全高执行大盘。

┌─ 左栏(官方 ChatView)────────────┬─ 右栏 Seeker 大盘 ────────────┐
│                                   │ ┌ 子智能体 ────────────────┐ │
│  [你]  …                          │ │ 固定 5 行,整行可点跳转   │ │
│  [AI]  …                          │ └──────────────────────────┘ │
│                                   │ ┌ 操作面板 ────────────────┐ │
│  (Seeker 正在读取文件 ··· 12s)     │ │ 头像 + 当前这一件事        │ │
│  ┌ 思考过程 ─────────────────┐    │ │ 默认收起,点开看完整流水   │ │
│  │ 贴在对话流里,跑时展开     │    │ └──────────────────────────┘ │
│  │ 收口自动折叠成一行         │    │ ┌ 任务 ────────────────────┐ │
│  └───────────────────────────┘    │ │ 只列本轮清单              │ │
│                                   │ └──────────────────────────┘ │
│  ┌ 最终回答(总结卡)─────────┐    │ ┌ 产出物 ──────────────────┐ │
│                                   │ │ 本次做出的文件,可点预览  │ │
│                                   │ └──────────────────────────┘ │
│                                   │ ┌ 记忆 ────────────────────┐ │
│                                   │ │ 钉在底部,默认折叠        │ │
└───────────────────────────────────┴──────────────────────────────┘

**为什么子智能体卡固定 5 行**:它顶在滚动区最上面、卡高与内容无关,所以紧贴
其下的「操作面板」永远停在同一位置 —— 子智能体多几个少几个都不会把操作面板
推走。没有子智能体时整卡不可见但位置照留(同样是为了不推动下面的卡)。

左栏干净:只有一行状态卡(头像 + 「Seeker 正在…」+ 末尾三点 + 用时读数)。工具明细不在这边堆。

切轮次入口:右键你自己发的那条消息,右栏大盘切到对应轮次;左键保持正常文本选择 / 复制,不触发大盘。

顶栏标签行位置:Seeker 排在官方「对话 / 轨迹」左侧、紧贴右侧工具区,是插件用一组 order 规则把标签行插到标题与工具区之间实现的(零 DOM 改动,官方按 tabs.length 增删 标签行时不会崩)。DSH 0.2.1-rc.3 给标签行外面多包了一层 .tabs 容器,旧规则全部打在 内层 tablist 上导致标题被挤到最右、官方「…」跑到 Seeker 左边 —— 已按新结构补规则,两版 选择器靠 :not([role='tablist']) 区分,rc.2 / rc.3 都能正常显示。

Seeker 对话流

大盘设置:「Seeker」标签右侧有一枚齿轮按钮,点开在视口中央弹出设置卡片,里面是 「提示词通道」三条(中文优先 / 对话内交互卡片 / 团队协作)、「任务清单」 两条(任务清单纪律 / 回合结束自动收口)、「展示」两条(总结卡外框 / 工具调用卡), 以及下方通栏的「工作区记忆」。齿轮与卡片 联动(再点收起、Esc 关闭、点遮罩关闭),改完立刻生效、刷新后保留。

卡片约 1000×512,排版在 2026-10-25 按「拥挤、排版不合理」重做过一轮:

  • 上方两栏:左「提示词通道」3 行(中文优先 / 对话内交互卡片 / 团队协作), 右「展示」2 行 +「任务清单」2 行(任务清单纪律 / 回合结束自动收口),中间一道发丝线。
  • 任务清单组里的两行没有合并成一个开关:任务清单纪律往提示词里写一条行为约束(开工 先列、收尾清零),回合结束自动收口是 host 侧监听器(agent/turn-stopping 把残留的 in_progress 改写成 pending),一个字都不进提示词。两者作用机理相反,绑成一个开关 会强迫用户做无意义的交易 —— 想关掉「末项别再转圈」就得连带关掉「清单纪律」。归到同一 组只负责「看起来归一类」。
  • 下方通栏:「工作区记忆」独占一整条,带极淡底色与上方分隔线(不套卡片外框 —— 面板里再套一层圆角卡就是卡中卡,本仓库老坑)。它从右栏搬下来是因为三个性质都对不上: 它是列表(长度跟工作区数量走)、是唯一按工作区存的一组、行结构是「名称 + 路径 + 两枚迷你开关」横排 —— 挤在 392px 右栏里路径会被截成 D:\AI\E…,而路径正是「哪个工作区」 唯一无歧义的标识。
  • 行内三列 grid:标签定宽 132px / 说明弹性 / 开关定宽 40px。原来是 flex,开关跟着文字 长度浮动,左右两栏停在两条不同的竖线上,扫「开没开」时每换一栏都要重新找位置。
  • 说明常态一行:短句只讲「这一行与别行有什么不同」,共性提到组头右侧(「每会话首步 · 存于 host」「本机浏览器 · 不进提示词」)。全文挂在 title 上,鼠标悬停 / 键盘聚焦 / 触摸 点按任一方式都能展开 —— 是按需展开,不是删字。
  • 窗口窄于 820px 回退单列;窄于 560px 时行降成两列(标签+开关一行、说明独占下一行)。

「工作区记忆」列出本机全部工作区,每个工作区两枚开关:记录(要不要往这个工作区攒新记忆 —— 关掉后不再自动提取、模型也不能手动记)与不注入(要不要把已有记忆读进它的会话)。 两件事语义相反、互不影响:关掉「记录」不影响已有记忆照常注入,打开「不注入」也不影响继续攒。 当前会话所在的工作区带「当前」角标与左侧主色竖条;列表限高可滚,工作区多了不会把下面顶出卡片。

右栏五张卡:

卡 讲什么
子智能体 整场会话派出去的独立会话,一行一个、整行可点跳到那个子会话。不按轮次切分(切到哪一轮都看得全);分两段、各带段标题——上面主色的「正在执行 N」(呼吸点 + 计数,无底色),下面带对勾的「已完成 N」折叠带(灰底、整条可点、带 chevron)。行首用形状图标分状态(进行中转圈 / 已结束对勾),状态同时留在 aria-label 与悬停说明里。固定 5 行窗口:卡高与内容无关,多出来的行在窗口内滚动(这是「操作面板位置恒定」的保证);没有子智能体时整卡不可见但占位保留,不会让下面的卡上移
操作面板 把工具调用翻成中文人话(「查看文件 KrAgentPanel.tsx」而不是 read_file(path=…))。卡头是插件头像(64×64 圆角方形,就是仓库根目录那张 icon.png 的 128px 版)+ 「Seeker」+ 当前这一件事(新动作把它顶掉,最多两排,再长抹省略号),默认收起,点卡头展开完整时间线;卡头那行的耗时隐藏,明细行里的耗时保留。2026-10-30 起卡头改为紧凑横排:身份位左置、名字/状态右置,底部一条「N 个动作 · 展开/收起时间线」摘要行;2026-10-31 按用户要求把那块人物换成插件头像(原来 64×114 的任务动画在这张卡里撤掉了),卡头 160px → 约 110px;2026-11-03 再把当前动作从名字下方挪到名字右侧同一行(卡头读作一句「Seeker 正在查看文件 agent.ts」,首排与名字同高,超出部分折到第二排,两排还放不下才抹省略号);同一轮里把浏览器标志从名字右侧挪到名字下方竖排左对齐(卡头高度不变,仍 110px)。同一轮里新对话页 Hero 也一起换了、人物动画整条删除,见卡头头像。措辞不出现程序、终端、命令行这类词,也不印「在电脑上操作」这类复述图标的话 —— 终端族只留对象(模型写的 description),行首图标已经说明是哪类动作(workflow → 「执行自动流程」)。该卡不允许关闭(2026-11-03 用户要求移除那枚设置开关):它常驻右栏,没有关闭入口
任务 只列本轮 todo_write / 提交的计划,本轮没写就是空(发一条新消息不会挂着上一轮的清单)。只有回看历史轮次时才回溯到会话最近一次,并标注「会话清单」。空时整卡不显示(2026-10-28 用户要求「空的不要显示卡片」,右栏不留空壳)
产出物 本次对话做出来的文件,整行可点,按类型分流:官方右侧栏能正常展示的(图片 / 文档 / PDF / Office / 表格 / 页面 / 音视频)就地进右栏看;侧栏展示不了的(3D 模型、压缩包、安装文件、avif/tif/heic)交给系统默认应用(Blender、解压软件、看图器)。没有产出、或核对后文件全都不存在时整卡不显示;多类产出按类别分组显示(网页(HTML)→ 音频 → 文档 → 图片 → 视频 / PDF / 演示 / 表格 / 3D / 压缩包 / 安装文件),卡头标注「N 项 · M 类」;每条显示具体产出时间,右键可「引用到对话框」或「使用系统默认程序打开」;子智能体会话的产出物自动并入主会话卡(行尾标注「子代理」)
记忆 钉在底部;按需展开,只列本会话新增的条目(按写入来源判定,不按时间)

思考过程卡贴在对话流里(不在右栏):左侧一条 2px 竖线,无底色无投影,跑时展开跟随、 收口自动折叠成一行让位给正式回答——回复落定后不再占屏,点标题行随时能再展开重读。

与官方「设置 → 字号」联动:左栏思考卡 + 右栏五张卡的字号、行高、图标全部跟随官方字号设置。 字号 14(默认)时与你之前的观感逐像素一致,调大调小会一起变。

右栏被挤压时(窗口窄 / 记忆卡内容多),操作面板与记忆卡会自动缩一档。 「思考 / 产出物」等卡片各有独立开关。

卡头长这样(深色 / 浅色,截自 1440px 宽窗口的右栏):

操作面板卡头 · 深色 操作面板卡头 · 浅色

卡头 Git 大盘入口

操作面板卡头上,头像右侧那排按钮里有一枚 Git 分支图标(在设置按钮旁边), 图标右上角还会挂一枚简约小角标(淡色小圆片,Git 用主色),直接显示 Git 大盘里有多少个仓库 (工作区 + 自制插件)有未提交改动(0 时隐藏,三位数折成 99+;一个仓库改 11 个文件也只算 1, 不是文件条数),把「现在还有几个项目没提交」直接摆在眼前。点击图标打开**「工作区与自制插件 Git 大盘」** (GET /api/chat-flow/git-roster):把工作区本身和每个自制插件仓库各自的改动状况收在一张表里, 一眼看出哪个仓库有东西没提交,并可一键新建会话处理。

卡头头像

右栏操作面板卡与新对话页 Hero 卡头,身份位都是插件头像 —— 就是仓库根目录那张 icon.png(256×256,插件图标本身)压到 128×128 后内联进 client 产物的那一张 (src/client/kr-chat/brand-avatar.ts,base64 约 55 KB,不走任何路由、没有网络请求)。

尺寸由 CSS 分两档:Hero 96×96、右栏紧凑骨架 64×64,圆角都取边长的约 28% (方形圆角而不是圆形:原图是 256×256 的方形构图,裁圆会把两侧花束切掉)。

新对话页的卡头

2026-10-31 之前这里是一套 9 段透明底 WebM 人物动画(按状态切换:思考 / 操作中 / 查看照片 / 收尾 / 完成 / 吃饭 / 观月 / 小憩)。用户先要求「操作面板的这个人物删除 替换为我们的插件头像」,再要求「新对话的也换上,之前那个人物彻底删除」—— 于是 播放器、片段表、状态判定、18 个素材文件、host 的 /api/chat-flow/motion 路由、 以及 view.task-motion / host.motion 两个特性开关一并删除,两处卡头只剩这一张图。


正文里能出卡片

流程图卡片(历史兼容)

早期版本用 ```diagram 围栏画流程图,要模型手工给出每个节点的坐标与折线点—— 画歪是常态,且这个能力有独立开关、默认关,模型多半不知道它存在,常年闲置。

现在流程图 / 架构图 / 时序图 / 任何示意图,一律用 ```html 围栏**手写内联 SVG** (见下一节)。iu 曾有的 graph / arch / sequence 三个图形 kind 已整块下线: 它们只能画算法排版的拓扑图,视觉上限被组件锁死,而图形恰恰最吃设计感。 能力文档随「html 卡片」通道默认注入,直接说「画个架构图」即可。

```diagram 围栏的渲染器仍然保留:历史消息里的老围栏照常渲染成 SVG 卡片 (节点 / 连线 / 分支标注 / 三档比例),不会变成一坨 JSON。只是不再往提示词里注入 它的规范,模型也不会再主动产出——那个独立开关早已移除。

HTML 沙箱卡片(能跑真 HTML/JS)

```html
<h1>计数器</h1>
<button id="b">+1</button><p id="v">0</p>
<script>
let n = 0
document.getElementById('b').onclick = () => { document.getElementById('v').textContent = ++n }
</script>
```

对话流里出现的就是这张卡本身,没有任何常驻控件(2026-10-19 起把标题栏、源码/复制、 缩放、重载、全屏整组砍掉):卡片是正文的一部分,不是嵌进来的组件。 里面是真在跑的 HTML/JS —— 小工具、计算器、图表、可视化、单页原型都能直接交付。

内置设计系统(kit):正文里出现 k- 类名或 --k- 变量时自动注入,不写 CSS 也能 得到与宿主同一套视觉语言 —— .k-title / .k-lede / .k-h 排版,.k-num 等宽数字, .k-rule 发丝分隔线,.k-kv 键值两栏,.k-grid / .k-cols 自适应多列,.k-bar / .k-track 条状图元,.k-swatch + .k-a1~.k-a4 色标。尺寸全部跟随官方「设置 → 字号」 缩放(内部是 --k-scale),不写死 px。

内置交互(按需注入,不用自己写 JS):data-tooltip="…" 提示气泡(hover / 键盘聚焦 / 触摸点按,Escape 关闭);.k-tabs + role="tablist" 页签(点击与方向键已接管); __dshState.set({…}) / __dshState.ready(fn) 把交互状态持久化,刷新不丢; window.__dshFill(text) 把结果回填输入框,由你确认后发送。

安全:卡片跑在独立 iframe 里,只给 allow-scripts、不给 allow-same-origin —— 卡片里的脚本读不到你的会话数据与页面 DOM。高度自适应上报;点卡片里的链接会开新标签页。

流式期先出占位卡:模型逐字吐 HTML 时先显示「正在预渲染…」,围栏闭合后原地变成真卡片。 空内容、只有注释、语言标记不是精确的 html,都会回退成代码块(你能看到原文, 而不是一张空白框)。

这个能力默认开启 —— 它是交付形态本身,不注入模型就永远想不起来用。

iu 原生交互卡片

输入框以 /iu 开头,这条消息会连 /iu 一起原样发给模型,模型据此出卡。十二种:

kind 用途 样子
slider 取值换算(按人数算用量、按面积算预算…) 拖动滑块,右侧实时出各项结果
table 数据行列 可排序表格:点表头切换升 / 降序,数字列右对齐
checklist 待办清单 可勾选,带进度条
tabs 方案对比 页签切换
kanban 任务分列 拖拽看板:卡片可跨列拖动,也有点选 →「移到这里」的触摸 / 键盘通路
form 收集输入 文本 / 数字 / 下拉 / 勾选 / 多行五类字段,必填项失焦才提示,填完一键回填输入框
timeline 时间安排 竖向 / 横向时间线,节点三态(完成 / 进行中 / 待办),点击展开详情
diff 前后修改 行级对比(自算 LCS),统一 / 并排两种视图,可只看变更行
tree 层级结构 可折叠树,点箭头展开 / 收起子树
gauge 完成度 / 指标 半圆仪表或圆环进度,可带阈值变色(绿 / 黄 / 红),最多 4 个并排
quiz 考察理解 单选 / 多选自测卡,答题后揭示对错与解析,答完出得分
piano 钢琴 点键或用键盘 A W S E D F T G Y H U J K 演奏,可把弹过的音写成音名+简谱回填输入框

原生卡(不走 iframe)体积小、不白屏,还能把结果一键回填输入框由你确认后发送。 卡片状态会记住:勾过的清单、拖过的滑块、移过的看板卡片、切过的页签、填了一半的表单, 都存本地(localStorage),刷新页面不丢。

图形不再走 iu:graph / arch / sequence 三个图形 kind 已整块下线,图表 kind chart 也已下线 —— iu 只能画算法排版的拓扑图,而图形最吃设计感。现在任何示意图都写 ```html 围栏手写内联 SVG(见上一节),文档随「html 卡片」通道默认注入。 历史消息里的 ```graph / ```diagram 围栏仍照常渲染(渲染器保留,只是模型不再产出)。

每张 iu 卡右上角只有全屏(Esc 退出,全屏态不再持久化——否则刷新后整页滚不动)。 缩放档位按钮已按用户要求移除:卡片字号统一跟随官方「设置 → 字号」,不再自成一档。

细节记不住没关系 —— 直接说「用 /iu 给我算一下」「用 /iu 做个对比」即可。

@ 不会把普通中文变成文件芯片

官方 @ 文件引用的判据只要求「@ 前面是空白、后面跟一串非空白字符」,不检查那串 东西像不像路径。中文里不打空格,于是「@的时候不是实时获取的」这种写法会被认成一个 叫「的时候不是实时获取的」的文件,文件当然不存在 —— 那段文字就渲染成一枚蓝色虚线芯片。

这里用一条 order 更小的 user-text 规则抢在官方前面,只把不像路径的 @ 盖回普通文字。 判据:带引号 @"..." / 含 / 或 \ / 含扩展名 / 纯 ASCII 且至少一个字母 —— 满足任一才算 路径。所以 @的时候、@帮我看看、@3.5 都是纯文字,而 @src/main.ts、@README.md、 菜单里选的 @package.json 照旧是芯片。

可交互卡片(proto-tabs)

```tabs 围栏 → 可点的页签卡片,适合并列展示几套方案。

运行中的程序:打 @ 直接挑窗口

输入框打 @,候选里除了官方的文件 / 会话,最上面多一组 「运行中的程序」 —— 你此刻 真的开着的窗口,每条带真实进程图标 + 窗口标题,右侧一行灰字是程序名(浏览器窗口写 Chrome / Edge)。前台窗口排第一,最小化的排最后。

选中后草稿里落一枚 @ 窗口标题 的芯片,模型收到的是结构化上下文:

[运行中的程序] 程序:chrome;窗口标题:Example Domain - Google Chrome;
路径:C:\Program Files (x86)\Google\Chrome\Application\chrome.exe;状态:当前前台窗口

于是「帮我把这个页面整理成笔记」「这个报错窗口是什么问题」这类话不用再说程序名 —— 指一下就够。

怎么采集的:host 半身用一个常驻 PowerShell 子进程枚举可见顶层窗口(标题 / 进程 / 图标 / 前台 / 最小化),暴露只读路由 GET /api/chat-flow/running-apps。首次冷启动约 600700ms,之后每次往返 **24ms**(客户端还有 30s 缓存 + 后台刷新)。

过滤掉什么:托盘浮层、输入法候选窗、Progman / Shell_TrayWnd 这类桌面外壳、 Chrome 的「翻译此页?」气泡、TextInputHost 等系统提示宿主 —— 都有实测依据,见 src/running-apps/collect.ts 的注释。

失败面:采集器不可用时整组静默消失,官方的文件 / 会话候选照常工作,界面上不报错。 客户端只保留两层不算缓存的机制:并发去重(同一时刻一个请求)与失败兜底(用上一次成功的结果)。 宿主被强杀(任务管理器结束进程 / 崩溃)时,采集器由 C# 看门狗线程 TerminateProcess 自我终止,不会在机器上留下孤儿 pwsh。


对话读起来更顺

能力 说明
思考行 / 工具行 与官方同款的折叠行,实时走秒 + 分步跟随滚动
步骤卡 / 总结卡 回合中间是步骤卡,最终回复独立成总结卡。总结卡外观可在工作台记忆页里改成纯正文(去框去影)
共享活动抽屉 点思考 / 工具行打开,思考按语义分组、工具调用成树
裸路径自动变链接 正文里出现的文件路径自动变成可点链接,点了在右栏预览;整段只有一个图片路径时直接渲染成图
对话滚动守卫 你手动往上翻时不被新内容拽回底部
滚动条自动隐藏 对话流滚动条在停止滚动 3 秒后淡出,再次滚动时出现;槽位常驻,不引起内容跳动
重试行影子 模型重试时显示官方同款试次行
生图实时卡 生图时按本次图片比例与张数画出点阵动画,generate_image 一返回就在对话流里显示成图(不等总结);多图仍收成一条画廊

生图实时卡(2026-10-30):generate_image 一开跑,对话流末尾就出现点阵动画卡 —— 点阵按本次 size / aspect_ratio 的比例分格(1:1 方阵、16:9 宽阵、9:16 高阵),n 张就画 n 个格子,不是一整块铺满宽度的大方阵;中性色圆点按径向波呼吸(跟随深浅主题,不使用橙色), 不是转圈也不是一行文字。工具结果一返回,同一张卡立刻换成成图,不必等回合收口后的总结卡; 模型还在落盘 / 写最终回答时图已经在了。点阵换成图片时做 0.55s 的羽化过渡(图片从 blur(10px) 显影、下面一层点阵同步淡出),不是硬切。回合收口后由总结卡里的画廊条接管, 多图并排、点击放大、另存为全部沿用画廊条。正文自己已经贴了图时不再补画廊条(避免 同一张图出现两遍);只有正文只写文字 / 文件路径时才用画廊兜底。

动效(移植自 aa2246740/dsh-better-display,MIT):思考两行步进跟随、流式新文字淡入、 忙碌标签微光、展开收起过渡。全部只动 opacity / transform,并在系统开启 「减少动态效果」时自动关闭。

消息撤回:对话回到那条消息之前

移植自 dsh-recall-plugin v2.6.3(MIT,源码与适配点见 src/recall/UPSTREAM.md):用户消息旁点「↶ 撤回」,把对话回到那条消息发出 之前。项目文件默认一个字节都不动。

为什么文件回退默认关着:那套快照是目录级的(影子 git 仓库),分不清「哪个会话改的」。同一个工作区 开着多个会话时,回退一条聊天消息会把别的会话刚改的文件一起改回去——实测踩过(撤回一条「你是谁」, 清单里躺着另一个会话刚修的两个前端文件)。要的就是「像聊天软件那样撤回」,那就别让它动代码。 想连文件一起回退(真正的「工作区时间旅行」)在设置里打开 fileRollbackEnabled。

入口 做的事
消息气泡旁 ↶ 撤回 经官方 sessions.fork 把对话切回该消息之前的 turn 边界 → 原会话归档(可关)→ 被撤回消息的文本与附件回填输入框
最后一轮回答操作区的 ‹ X/N › 同一段对话的多个版本之间来回切换(撤回会 fork 出新版本,侧栏始终只留当前那个)

打开 fileRollbackEnabled 之后(可选,默认关闭):每次撤回前先展示将变更的文件清单(修改 / 恢复 / 删除),确认后文件回到发送时的状态,回退前自动打一份安全快照;确认面板里仍可改选「仅撤回对话」。 注意多会话同工作区时它会把别的会话的改动一起退回去——这是目录级快照的固有语义,不是 bug。

同工作区里还有别的会话 agent 在跑时(同时开多个会话、其中一个正在回复),面板自动把范围锁到 「仅撤回对话」并置灰「回退文件与对话」——文件回退会覆盖那个会话正在改的内容;等它停下后选项自动恢复。 只有这个会话自己的 agent 在跑时才整块拒绝撤回(fork 一个运行中的会话会把它留在被归档的原会话里 继续写日志)。两种「忙」分开处理,所以不会出现「同工作区有别的会话在跑 → 连仅撤回对话都点不了」。

配置与治理在「设置 → 插件 → dsh-seeker-bot」的撤回卡片里(配置表单 / 排除表 / 快照管理三张卡, 保存即热生效)。关键默认值:

配置项 默认 说明
fileRollbackEnabled 关 文件回退总开关;关着时撤回恒为「仅撤回对话」(不列清单、不动文件,旧快照也不例外)
messageOnly 开 只登记撤回记录、不写文件快照(撤回只动对话的基础)
refillDraft 开 撤回后把文本与附件回填输入框
archiveOriginal 开 撤回后归档原会话(关掉则原会话留在列表里)
maxFileBytes 8MB 打开文件回退后:超过的单文件不进快照、不被回退触碰
maxWorkspaceBytes 256MB 打开文件回退后:单工作区纳入总量上限;超限停用该工作区快照(不截断,避免回退时误删未纳入文件)
maxSnapshotsPerWorkspace 500 每工作区快照条数上限,超出删最旧
snapshotEnabled 开 快照总开关(关掉只冻结新建,已有快照仍可撤回)

撤回记录与(打开文件回退后的)快照落在 $DSH_HOME/dsh-recall-snapshots/<工作区路径哈希>/(home 不可写时才降级到项目内 .dsh-recall-snapshots/)。默认只登记消息,几乎不占空间;打开文件快照后才有 影子 git 仓库(delta 增量压缩、自动 git gc、会话删除联动清理、按条数 / 保留天数自动清理)。 想彻底删掉:卸载插件后手动删这个目录。

边界(原插件的设计边界,不是移植引入):撤回记录只在消息发送时创建,插件启用前的历史消息不 显示撤回按钮;会话第一条用户消息没有更早的对话切点,撤回无效(面板会明确说,而不是弹文件面板); 目标工作区 agent 正在运行时先停止再撤回。打开文件回退后另有这些边界:快照是尽力捕获(秒级任务可能落进 「收到消息 → git add」那个窗口)、工作区根路径超过 260 字符(Windows MAX_PATH)不支持、嵌套 git 仓库与目录重解析点(junction / 目录符号链接)不参与快照。完整清单与上游坐标见 src/recall/UPSTREAM.md。

与 Bot 模式的「撤回一条消息」不是一回事:那条作用于 Personal Agent 的会话日志与记忆 (见下文),这条作用于 Work 会话的对话(以及可选的工作区文件)。

对话完成胶囊

移植自 dsh-companion 的 done-pill 模块。任一会话(含后台会话)回合结束时提醒一次;点胶囊直接跳进那条会话,悬停滑出「进行中 + 最近完成」面板,看问题与回复全文。

能力 说明
两种形态 左下角小横条(默认,侧边栏设置行上方)/ 顶部悬浮可拖拽胶囊;设置里「胶囊形态」切换,位置与配置都记住
两种形态都有 会话标题 + 用户问题摘要 + 完成数;左下角卡在 56px 窄栏退化为带数标徽的图标钮
任务完成提醒 三条通道:原生 Notification → 壳页面桌面浮窗(经 host CDP)→ 页内动效卡兜底;系统通道成功时不再弹页内卡;提示音只响一次
录屏成 GIF 胶囊右侧录屏按钮:整屏 / 框选区域 / 单个窗口(host 用 Win32 PrintWindow 抓窗口自身,免遮挡,支持子窗口);帧率与输出宽度可调
待机机器人 完全空闲时播放 OpenBotMotion 待机动作(9 种基础动作 + 稀有 dance,减少动效时定格)
运行中笔画 有回合在跑时显示转圈笔画与实时用时;完成后转为未读点
健康提醒 「休息时间」「凌晨注意休息」两个时段开关,到点胶囊持续提示
跟随字号 尺寸跟随官方「设置 → 字号」(--dsh-content-font-size),另有「胶囊大小」「胶囊字体」两个自有设置
每日情报卡 胶囊左上角斜插的小标签(宽 1/5、-38°):每天自动联网抓 RSS + 模型整理 1–2 张日报卡(AI 前沿 / 世界国内要闻),点一下从屏幕正中划出期刊式大卡,可「继续聊这个」

设置入口:官方「设置 → 通用」里新增 9 行(胶囊形态 / 对话完成胶囊 / 任务完成提醒 / 休息时间提醒 / 凌晨注意休息 / 胶囊大小 / 胶囊字体 / 录屏帧率 / 录屏画质)。

┌ 左下角小横条(默认形态)──────────────┐
│ ● 3 个对话完成 · 「整理会议纪要」摘要… │  ← 侧边栏设置行上方
└────────────────────────────────────────┘
   悬停/点按 → 上方滑出「进行中 + 最近完成」面板

每日情报卡(胶囊上方那张小纸片)

胶囊 2/3 处斜插着最多两张半叠放的小标签——宽度 17%(约 38px)、露出约 31px,前卡 -23°、后卡 -34°,后卡往右错开 19px 沉下去一点,只从前卡边缘露出一道斜边。每张标签只写一个栏目短名(AI / 世界 / 国内)+ 一个状态点,它是一张书签,不是阅读面;点任一张才从屏幕正中划出一张期刊式大卡(点哪张就翻到哪张):

          ┌ 后卡 -34° ┐
     ┌ 前卡 -23° ┐    │   ← 叠在胶囊 2/3 处,下缘压进胶囊 9px
     │ AI •      │    │
     └───────────┘────┘
   点一下 → 遮罩淡入 + 卡片从正中划出(860px 宽 / 82vh 高 / 左右两栏)

   ┌────────────────────────────┬───────────────────────────────┐
   │ AI · 前沿       11 月 3 日 ✕ │ 01 乌镇互联网大会开幕,AI 基础 │
   │                            │    设施成主角(量子位)        │
   │ 乌镇开幕这天,AI 的         │ 02 马来西亚成全球 AI 热「黑马」│
   │ 比价对象换成了「系统」       │ 03 社区辟谣:Astra 说法有误导  │
   │                            │ 04 模型谁都买得到,差距在系统  │
   │ 导语:奖项、数据中心与…      │                               │
   │ [继续聊这个]      ‹ 1/2 ›   │                               │
   └────────────────────────────┴───────────────────────────────┘
   点遮罩 / ✕ / Escape 关闭

动效(按 HarmonyOS 的进出场规则,两条曲线不对称):

阶段 参数 依据
入场·遮罩 淡入 180ms ease 遮罩不该抢戏
入场·卡片 translateY(28px) scale(.92) → 原位,340ms cubic-bezier(0,0,.2,1) 减速曲线:起点加速最猛、末段收得慢,观感是「一张纸被抽出来摊开」;时长在官方「全屏范围 350ms」内
退场·遮罩 淡出 190ms —
退场·卡片 translateY(14px) scale(.96) + 淡出,190ms cubic-bezier(.4,0,1,1) 加速曲线:从静止启动、峰值速度结束;比入场短一截,进出不对称才像「被收回去」而不是倒放

退场不是立刻卸载:先置 data-closing 让 CSS 播完 190ms,再真正卸载(连点关闭 / 动画中重新打开都有防重入与取消)。系统开「减少动效」时全部关闭。

三版演进:① 整宽半截卡 + hover 原地撑大(盖住半个侧栏,太重)→ ② 小标签 + 贴标签弹出的小卡 → ③ 小标签 + 从正中划出的期刊式大卡。现在这版平时几乎不占地方,点开是一页能读的杂志。

每日情报卡:斜插在胶囊上方的小便签与展开态

数据怎么来(这是「每天自动」的关键,不依赖某个难找的 RSS 聚合商):

  1. RSS 主通道:宿主并行抓 BRIEF_FEEDS 里的 5 个源 —— 量子位、Hacker News、BBC 中文、纽约时报中文网、WSJ 世界;每个源解析出标题 / 链接 / 摘要 / pubDate,按 pubDate 过滤 3 天前的旧闻,URL 去重。
  2. 搜索兜底:素材不足 6 条时,再用 ctx.web.search 走一遍 briefQueries(AI / 世界 / 国内四条)。实测宿主搜索链对「今天的新闻」召回很差(返回旧闻合集),所以它只是兜底,时效性靠 RSS 的 pubDate 保证。
  3. 模型整理:素材按 lane(AI / 世界 / 国内)分组喂给当前聊天模型,要求「只依据素材、不得编造」,输出 1–2 张卡的结构化 JSON;清洗层(sanitizeBriefRecord)兜底任何字段缺失 / 超长 / 类型错误。
  4. 落盘 + 调度:写 <DSH_HOME>/storages/dsh-seeker-bot/done-pill/briefs/<YYYY-MM-DD>.json;宿主每 30 分钟检查一次,当天 06:00 后且还没有简报就自动生成(页面关着也照跑)。
  5. 降级不伪造:RSS 与搜索全失败时仍然出卡,但 origin 记为 model,卡上标「未联网」——来源不同,可信度不同,界面上要看得出来。

宿主路由:GET /api/dsh-done-pill/brief/today、POST /api/dsh-done-pill/brief/generate(手动/换一批,force: true)、GET /api/dsh-done-pill/brief/list?days=7。功能开关是「设置 → 插件 → 功能开关 → 每日情报卡」(view.daily-brief),关掉时连路由与定时器一起不挂。

从 dsh-companion 迁入:存储键(dsh.donePill.*)、样式类名与宿主路由保持原样,浏览器里的已读状态、位置与外观设置无缝延续。dsh-companion 若同时启用,两套胶囊会重复显示——只保留一个即可(本插件已内置完整胶囊,不需要再开 companion 的 pill 模块)。


侧边栏:工作区 ⇄ 操作间

侧边栏「更多」下面只有一行切换菜单,两屏互斥:

当前显示 那一行写 点它之后
工作区(会话 / 工作区列表) 操作间 工作区收起,下面列出记忆 / SKILL / 画廊 / 档案 / 系统提示词 / 同步设置
操作间(六个工作台入口) 工作区 那六个条目收起,会话列表回来

2026-11-03 起「连接器」不再列在操作间里:入口变成侧栏最下面(账号行)右侧的 「MCP服务器」胶囊(src/client/triad/connectors/sidebar-pill.tsx)。胶囊左边是 文案,右边是已装 MCP 的圆形图标(半叠放,最多 3 枚,超过用末尾的「…」表示更多); 点它直接打开连接器页,显隐跟随 work.connectors 组件开关。 Bot 模式的左侧栏底部「设置」行右侧也有同一颗胶囊(点击会先切回 Work 再打开 连接器页);Bot 侧栏折叠成 64px 图标轨道时让位。

也就是「这一行永远写着点下去会去哪」。当前停在哪一屏会记住(刷新不跳屏), 从任何一个工作台页回到会话时自动切回工作区——否则在操作间里点「新会话」 会看不到会话列表。

侧栏折叠成 56px 图标态时,这条二级导航整块让位,官方「工作台」图标行接管 (那里放不下一整屏二级导航),入口在任何形态下都不会丢。

顶部的 Work / Bot 分段控件

品牌行下面那枚「Work | Bot」分段控件来自本插件(src/client/mode-switch/)。 2026-11-01 起 Bot 模式重新开放:点 Bot 会切到全屏 Personal Agent(见下一节), 点应用里的 Work 回到官方工作台。模式记在 localStorage 的 dsh-seeker-bot.ui-mode, 刷新后保持。

这枚控件与 Bot 应用左侧栏里的 .pa-mode-seg 是同一套视觉规格(容器 4px 内边距 / 13px 圆角 / 3px 缝,按钮 34px 高 / 10px 圆角,字号统一跟随 DSH 通用字号),位置也 钉在同一处:左边距 14px、顶边 y=74(品牌行 66px 底 + 8px 间距)。两个模式来回切时, 分段不会跳尺寸、位置或字号;改一边记得同步另一边。

2026-11-04 起两枚分段都有方向滑块动效:点 Bot 时 Bot 侧栏的滑块从左端滑到右端, 点 Work 时官方侧栏的滑块从右端滑回左端(240ms、cubic-bezier(.4, 0, .2, 1) 官方标准 曲线,与静态规格同一套尺寸)。切换时两套界面在同一帧互换,位移起点和终点落在两枚 不同的 DOM 上,transition 跨不过去;mode-switch/index.ts 的 playThumbSlide 用 WAAPI 在切换后可见的那一枚上播放显式位移动画(不强制 reflow、不会被定时器半路 掐断,两枚滑块都提升为合成层),prefers-reduced-motion 下直接落位。

2026-11-03 起 Personal App 常驻 DOM,Work / Bot 的显隐由 body[data-skb-mode] CSS 属性直接控制(不再在 Work 模式卸载 React 树),切换在同一帧完成,不会出现 「官方侧栏已让位、Bot 侧栏还没挂出来」的空白闪烁;Bot 侧栏的收起图标与品牌行 也逐像素对齐 Work 模式官方实现(图标直接复刻 IconPanelLeftOutlineRegular 路径)。

左侧栏宽度(两个模式都能拖)

Work 与 Bot 的左侧栏都可拖拽调宽(264–420px):Work 用官方内置的拖拽手柄,Bot 用 .pa-sidebar 右边缘的手柄,两边共用同一个宽度并写进 localStorage (dsh-seeker-bot.sidebar-width),刷新 / 重启不会回到 280px。双击 Bot 侧栏手柄 恢复默认 280px。

2026-11-03 修复「左侧侧边栏无法拖拽」:原先宽度只写进 DOM 的 inline style,没写进 官方 layout store —— 侧栏实际 341px 时官方手柄的 left 还停在 store 的 280px, 用户按视觉上的侧栏边界根本摸不到手柄。现在恢复 / 守护都走官方 store 的 setSidebar(通过 ctx.layout.panels),手柄、grid 列与侧栏根宽度始终一致; 同时补上 Bot 模式侧栏自己的右缘手柄。

会话右键菜单:删除会话

会话行「...」菜单里多了一项删除会话,位置紧接「归档会话」之后(官方内置项的 order 是 置顶 100 / 重命名 200 / 分叉 300 / 归档 400 / 复制 Session ID 450,本项取 425)。

它补的是官方只有归档、没有删除这个空缺:归档只把会话 id 塞进 registry 的 archivedSessionIds,日志目录与工作区记账全都留着(本机实测堆了 1029 条)。删除会 真正清掉:

  • 归档集合 / 置顶集合里的条目(registry.unarchiveSession / unpinSession);
  • 工作区记账(workspace.detachSession);
  • 磁盘上的 <DSH_HOME>/sessions/<projectKey>/<uuid>/ 目录。

不直接改 workspace.json:宿主进程在内存里持有同一份 registry 状态,直接改文件 会在下一次写入时被整体覆盖,删掉的条目会复活。所以全部走官方服务层,内存与磁盘同步, 侧栏也会自己刷新。

菜单项是危险色红字(MenuItemButton 原生的 danger 变体,与官方删除类动作同款), 点它弹的是自绘确认框,不是 window.confirm——后者在 DSH 桌面壳里是系统弹窗, 标题写死成 "dsh-shell"、灰底 Windows 按钮,跟主题完全脱节。自绘框沿用记忆面板那套 弹窗规格:createPortal 到 body、z-index 1200、遮罩淡入 + 卡片自底部上滑(遵守 prefers-reduced-motion)、Esc / 点遮罩取消、确认钮 autoFocus 且是红框红字。

确认框挂在 shell.overlay 席位上常驻,而不是放在菜单项组件里:官方 Menu 是条件 渲染的,菜单一关菜单项就被卸载,挂在那里的弹窗会「刚出现就消失」。菜单项只负责投递 请求(store.ts 的模块级 pending),宿主订阅后渲染。

只有真正在跑工作的会话会被拒绝。判据是官方 registry 的活动检查,不是 「在 sessions store 里驻留」:后者太宽——DSH 会 retain 所有打开过的会话,而界面上 根本没有「关闭会话」这个操作,结果就是什么会话都删不掉(2026-11-03 用户实测: 点删除只得到「会话仍在运行」)。实现上把 archiveSession(id) 当探针:它不带 stopActivity 时会问一次 workspace/session-activity waterfall,有任何活动就抛 WorkspaceActiveSessionError 并列出活动种类(进行中的回合 / 子代理 / 后台任务 / 定时提醒),一个字节都不写。于是「打开过但空闲」的会话能正常删,真在跑回合的 仍被挡住,且报错文案会点名是哪一类活动。

注册表里有、日志早不在磁盘的僵尸记账(本机实测 18 条)走另一条分支: 探针抛 WorkspaceUnknownSessionError 时放行,让它能被清掉。

实现分两半:客户端 src/client/session-delete/(菜单项 + 确认弹窗 + 请求 store), 宿主 src/session-admin/(/api/session-admin/list|delete)。 删完 host 代码要重启 DSH 才生效(host 半身不热重载),客户端刷新页面即可。


Personal Agent(Bot 模式)

Bot 模式是一个覆盖整个 DSH Web 视口的自绘应用: 左侧 280px 的 Bot 名册、中间微信式聊天、右侧 300px 的浏览器 + 产出物 + 任务记录栏。 所有 Bot / 群聊设置都收进居中弹窗卡片(2026-11-03 从右侧滑出改成弹窗:这两个设置字段多,760px 宽的弹窗里排得开),右栏不再放设置项。

首次进入只有主 Bot:不再预置任何演示子 Bot、群聊、消息、记忆、例行任务或期刊; 旧版本留在 localStorage 里的七个演示 Bot 与示例数据会在第一次加载时自动迁移清理, 只保留主 Bot 和用户自己创建的内容。

Personal Agent 主界面

布局与交互

区域 内容
左侧栏 顶部品牌行(鲸鱼 mark + deepseek wordmark + HARNESS 徽标 + 收起按钮,与 Work 模式官方侧栏一致)→ Work/Bot 切换器 → 主 Bot(48px 头像 + 在线点 + 右下角「+」新建入口)→ 子 Bot 分组 → 群聊 → 底部「期刊日记 / 设置」。分组标题可点开收起(手风琴,箭头指示展开方向;收起状态存 pa:ui:v1 的 collapsedSections,刷新保持;群聊区块同样可收)。行悬浮「...」菜单只有置顶 / 删除(群聊行是置顶 / 解散群聊),置顶的行名字后面带一个小图钉。有新回复的行会亮起来:左侧一条强调色竖条 + 行底色极淡的橙 + 摘要升一档颜色,行尾带未读数角标(一轮回复算 1 条,按 turnId 去重);正在看的那个会话自动算已读,切进会话即清零,折叠轨道(64px)里也有小圆点。子 Bot 与群聊行的右上角带最新消息时间(刚刚 / N 分钟前 / HH:mm / 昨天 / M/D,每 30 秒走格,悬停显示完整时间;没有消息的行不显示,主 Bot 卡片不显示;鼠标悬停该行时时间淡出,把位置让给行尾「...」菜单)。侧栏可收起成 64px 图标轨道(主 Bot / 子 Bot 头像 + 群聊 + 底部图标),状态记在 pa:ui:v1 里,刷新保持
聊天区 会话头(44px 头像 / 名称 / 角色 / 电话呼叫 / 更多)、消息流、输入框(附件 / 粘贴图片 / 白色圆形语音或发送键;Bot 回复期间发送键变成红色停止键,点一下同时断开前端流并通知宿主 cancel 本轮;录音时整块切换成语音条)
右侧栏 只保留两块:产出物卡(扫描该 Bot 的产出物文件夹)、任务记录卡(每轮对话一条,点开左右分栏详情)。详情 / 资料库 / 电脑三个 tab 已删除
弹层 新建 Bot、新建群聊、Bot 设置卡(居中弹窗 820px 宽、固定高度,左分类导航 + 右面板)、群聊设置卡(居中弹窗,760px 宽、高度贴合内容并封顶 84vh)、全局设置卡(右侧滑出)、例行任务内联表单、期刊阅读器、通话弹层

字体与字号跟随通用设置:Bot 模式不另设一套字级。字体继承官方「设置 → 通用 → 界面字体」 (.pa-root 的 font-family: var(--dsw-font-family),由界面字体写入的 --dsh-font-family-text 解析而来),字号直接取同一页的「字号大小」 (.pa-root 的 font-size: var(--pa-font-size),14px 档下 Bot 正文就是 14px, 与 Work 正文同档;其余档位按标题 +1 / 摘要 −3 的相对层次用 --pa-font-delta 整体平移, 字号与行高成对,调到 20px 时整页一起变成 20/19…)。 例外只有两处,且都是功能性的:代码与终端走等宽(--ds-font-family-code), 期刊日记的标题走衬线(Georgia / 宋体,杂志排版)。

设置卡、产出物与任务记录(v3)

  • Bot 设置卡:从聊天头「更多」→「Bot 设置」打开(群聊里那一条是「群聊设置」)。 一张居中弹窗卡片(820px 宽、固定高度 84vh 且封顶 760px,点遮罩、按 Esc 或右上角 × 关闭), 左侧一列分类导航 + 右侧面板:资料 / 模型 / 语音 / 权限 / 自动化 / 团队(仅主 Bot)/ 记忆 / 产出。 一次只渲染当前那一页(不是 8 页全挂上再藏起来),换页签右侧自动滚回顶部,页签可用上下方向键切换; 记忆 / 例行任务 / 子 Bot 三页在导航上带数量角标。各页内容:资料(名称 / 角色 / 简介 / 行为约定)、 模型(先选供应商、再选模型的两级下拉,含推理强度档位、按模型公布的能力动态列出)、 语音(音色 / Live 供应商 / 自动朗读回复开关)、权限、自动化(主动询问 + 例行任务同页)、 团队(子 Bot 管理)、记忆、产出(产出物文件夹路径)。改即保存; 名称 / 角色 / 简介用「保存资料」按钮。窄屏(<720px)导航自动变成顶部 chips 并自动换行 (不做横向滚动:8 个分类在 614px 里排不下时,「产出」会被切在可视区外且看不出能滑)。

  • 群聊设置卡:同一套居中弹窗视觉(同一张卡高度随内容收放),含群名、群聊头像、模型策略 (跟随各自 Bot / 全组统一;统一时同样是先选供应商、再选模型的两级下拉,并可单独选推理强度)、 成员管理、删除群聊。

  • 头像(新建与修改都能换):7 种形状 × 8 种颜色预设 + 从本地导入图片(读文件 → 居中裁成正方形 → 压到 160×160 的 JPEG data URL,约 10–20KB,跟 Bot 数据一起存 localStorage)。新建 Bot / 新建群聊弹窗、Bot 设置卡的「基本资料」、群聊设置卡的 「群聊信息」里都能挑,预览即时。群聊不选头像就沿用成员头像叠加;选过之后侧栏、 会话头、折叠轨道都改用它,设置卡里有「恢复为成员头像叠加」一键退回。

  • 全局设置卡:左侧栏底部「设置」,主题 / 右栏开关 / 通话音色与语速 / Bot 分组 / 期刊规则全部保留;它仍走右侧滑出——入口在侧栏底部,语境是「调工作台外观」, 与「编辑某个 Bot / 群聊」不是同一类操作。

  • 产出物文件夹(每个 Bot 一个):<bot 工作区>/产出物/<Bot 名>/,下面固定 9 个分类 子目录:图片 / 视频 / 音频 / 文档 / 表格 / 演示 / 网页 / PDF / 其他。Bot 的提示词里写明 交付文件必须写进对应分类目录;收口时宿主还会把工具产物(含 _tmp 里的文件)复制归档 到对应分类目录,所以即使模型乱放,右栏也能看到。右栏产出物卡只从这些路径扫描, 按文件修改时间倒序,最多 200 条;没有产出时整卡不渲染。

  • 任务记录(不是每个对话都有):右栏「任务记录」卡按天分组列出最近 30 条(标题 + 结果 摘要 + 时间),只列有实质动作的轮次——纯寒暄、一句问答在收口时整条丢弃(不留空壳)。 点任意一条弹出任务详情卡:顶部是状态 badge + 任务标题 + 耗时 / 开始 / 关联 / 产出物, 主体是过程时间线(Work 模式同款表达):一行细线图标 + 灰色小标题(已读取文件 / 已写入 文件 / 分析 / 已执行命令…)+ 下面一段自然语言正文;工具调用的完整参数与结果默认折叠, 点击那一行才展开(等宽字体、可滚动),底部是最终回复;运行中显示「深度求索中,用时 X」。 任务记录存在 localStorage(AppState.tasks),清空对话 / 撤回消息 / 删除 Bot 时一并清理。 用户点停止的任务收口为 stopped 状态(「已停止」,与「失败」分开),执行到一半的工具 步骤标为中断。

  • 宿主接口:GET /api/personal-agent/outputs?botId=&name= 返回该 Bot 的产出物清单; bot-output-dirs.json 持久化 botId → 文件夹名 映射(改名不迁移文件夹)。

    Bot 设置卡

    任务记录与详情卡

  • 自适应主题:默认「跟随」DSH 当前主题(读 body[data-ds-dark-theme] 与 body 实际底色,系统深浅切换也会同步);设置里可强制「深色 / 浅色」。所有面板、 气泡、输入框、弹窗与右栏都有对应浅色 token,不是只换背景色。

  • 主 Bot 控制子 Bot:主 Bot 设置卡里的「子 Bot 管理」可暂停/启用、改名、移动分组、删除; 子 Bot 可自主在各自会话里主动询问、跑例行任务、维护独立记忆。

  • 主 Bot 创建子 Bot(bot_create):不用再去点「+ 新建 Bot」——直接跟主 Bot 说 「帮我建一个专门盯竞品的助手」就行。主 Bot 会先确认岗位和名字(缺哪件就用 bot_emit(kind=need-input) 问清楚,不自己编),然后调用 bot_create 真的把它建出来。

    • 权限:只有主 Bot 挂这个工具,子 Bot 连工具都看不到。子 Bot 之间互相创建会变成 一条不受控的链(A 建 B、B 建 C),而「要不要多一个分身」是全局决策,属于主 Bot 和它背后的人。 前端在 /reply 的 bot.isMain 里声明身份,宿主按它决定挂不挂——缺省按 false, 宁可少给一个工具,也不把全局权限发错人。
    • 数据流是三段,不是一条事件:宿主先落盘( ~/.dsh/storages/dsh-seeker-bot/personal-agent/created-bots.json)→ 再经 SSE 推一条 bot-created(发在创建者通道上,新 Bot 的会话此时还不存在)→ 前端合并进 pa:state:v1。宿主是权威层、前端是显示层,两边按 id 去重。
    • 页面没开也补得回来:SSE 只在页面开着时送达,而主 Bot 可能在例行任务里建 Bot。 所以前端启动时还会 GET /api/personal-agent/bots/created 拉一遍账本,把「建过但没见过」的补齐。
    • 建完立刻能派活:记录写回宿主花名册,所以同一条回复里紧接着 coordinate_bots 派给它就已经认识它了——不写回的话模型会被告知「没有这个 Bot」,而侧栏里它明明已经出现。
    • 拒绝理由是说给模型听的中文句子:重名会提示「已经有一个叫 X 的,直接派活给它」, 字段超长会说清上限。模型拿到 { ok: false, error } 后能自己换个名字再试一次, 而不是看到一段栈。落盘失败时返回 ok:false 且不广播、不写花名册, 避免「前端看到了、重启后没了」和「派活给幽灵 Bot」。
    • 回归测试:scripts/test-bot-create.mjs(参数校验 / 默认值 / id 生成 / 落盘 / 广播 / 花名册写回 / 工具注册 / 落盘失败 / 合并连接,72 项断言)。
  • 删除 Bot 删的是「整个 Bot」,不只是前端那一行:前端的删除按钮原来只把它从 pa:state:v1 里摘掉,宿主侧全都还在。现在走 DELETE /bots?botId=,一并清掉: 会话历史(所有工作区下的 pa-bot-v2-<id>/,只删当前工作区会留孤儿)、 派活花名册(不摘的话主 Bot 下一轮还会往这个「已删除」的 Bot 派活)、 工作记忆、产出物映射、Bot 页面、记忆条目(owner = bot:<id>)、 例行任务与主动询问配置、创建账本(否则下次打开页面启动补发又把它建回来)。

    • 产出物目录默认保留:那是用户让它生成的真实文件,一个按钮不该无声抹掉。 响应里带回 outputDir 绝对路径,前端补一条 toast 告诉用户东西还在哪;真要连 文件一起删,显式传 purgeFiles=1。
    • 主 Bot 删不掉(删了等于把整个入口弄没),空 id 与主 Bot 都返回 400。
    • 幂等:重复删同一个 Bot 返回 ok:true 且 removed 为空,不报错。
    • 每项独立 try/catch:删一半崩掉会让用户既删不干净、又不知道删到哪,所以失败只 跳过该项,removed 如实报告清掉了什么。
    • 回归测试:scripts/test-bot-delete.mjs(护栏 / 会话 / 花名册 / 工作记忆 / 映射 / 页面 / 账本 / 例行任务 / 产出物保留 / purgeFiles / 幂等,29 项断言)。
  • 分组:设置弹窗里可新建 / 改名 / 删除分组;子 Bot 的行菜单只留「置顶 / 删除」两个动作 (设置、移动分组、暂停启用都收进 Bot 设置卡,侧栏不再重复),移动分组在主 Bot 设置卡的 「子 Bot 管理」里改。置顶只在所属分组内排最前,取消后回到原位(稳定排序,不漂移)。

  • 群聊行菜单:同样只留「置顶 / 解散群聊」;群设置从会话头「更多」或行菜单外进入。

  • 群聊:主 Bot 卡片右下角「+」→「添加群聊」(选成员 + 起名);群内可 @成员, 成员用各自模型回复并以发送者头像/名字标记;群聊设置支持「跟随各自 Bot / 全组统一模型」。

    • 谁接话是判定出来的,不是猜的(src/client/personal/room-trigger.ts,纯函数、有回归测试): ① @名字 / @名字 → 只叫被点名的,按点名顺序;② @所有人 一类广播词 → 全员; ③ 引用了某条 Bot 消息 → 只叫被引用的那个(点「回复」是界面上最明确的一次「这条是问你」); ④ 委托意图(「让 / 请 / 叫 / 找 / 交给 …」紧邻成员名)→ 只叫被委托的那个; ⑤ 都没命中 → 群主(memberIds[0])兜底。正文里随口提到名字不会把人叫出来 (user@example.com、@2x 这类写法也不会被当成点名),成员名互为子串时最长优先。
    • 群成员现在能看见彼此:群聊回合把本群最近 20 条消息(含其他成员发言、排除本轮这条) 随请求体的 history 带上,宿主用 buildRoomContextBlock() 拼成一段 developer 消息注入。 只带本群是刻意的——别的群的消息是另一种场地的事。私聊回合维持原样,只带这个 Bot 的私聊。 上线前的实情是:群消息从来没进过模型上下文,所以「三个成员各说一版方案」一直是三个互不知情的单聊。
    • 一次最多编排 3 位成员(每位都是一次完整的模型推理)。被 @ 但超出的成员会弹提示 说清谁没接,不再静默丢弃;群里一个成员都没有时同样有提示,而不是消息发出去没反应。
    • 成员是一个说完下一个才开口:每个成员等上一位在本群的轮次收口再启动(180 秒超时兜底), 不是固定错峰。原来三个人各隔 700ms 启动,而一轮回复要几十秒——第二个开口时第一个的正文 还在流式生成,等于三个人各自回答同一段背景。
    • 「全组统一模型」现在真的生效:modelMode / unifiedModel 原来只写进 localStorage, 宿主读的仍是各自 Bot 的模型;现在回合开始时按群设置覆盖进请求体的 bot.model (在回合开始时定下,跑到一半改设置也不会让同一条回复由两个模型写)。
    • 群里的审批卡 / 问答卡会出现在群里:卡片是宿主侧异步发的,原来只带 botId,于是 落进该 Bot 私聊、群里那条回复永远等不到应答。现在回合入口登记本回合所属群 (src/host/personal/room-context.ts),发布时补进事件的 roomId;私聊回合即清除登记。
    • 清空某个 Bot 的私聊不再连带删掉它的群消息(清空只作用于没有 roomId 的消息)。
  • 主动询问:每个 Bot 设置卡里的「主动询问」可开关;点「现在问一次」立即发一条主动消息。 定时触发跑在宿主常驻进程里(活跃窗口内随机取点,种子 = botId + 日期,同一天结果稳定), 与「现在问一次」共用同一份提示词与执行路径——否则会出现「按钮点出来的和定时跑出来的语气不一样」。 新建 Bot 创建后由真实模型按岗位发第一条自我介绍与问题。

  • 例行任务:每个 Bot 都可新建(每天 / 每 2 小时 / 每周一)、启停、立即运行、删除。 执行已从「前端 setInterval」搬到宿主常驻调度器(30 秒一 tick),因此: 页面关掉照跑、多标签页不会重复执行、状态落盘到 routines.json 而不是 localStorage。 结果经 SSE 推回对话流;前端离线时落盘到 server-messages.json,下次连上补发。

  • 记忆隔离:记忆引擎自 schema v6 起有 owner 维度(work / bot:<id>)。 子 Bot 只能读自己的 + Work 的全局偏好;主 Bot 可跨全部子 Bot,并可切「Work 全局」视图。 写入永远只落到当前会话自己的归属上——模型没有任何参数能把记忆写进别人的库。

  • 审批卡:审批档位的 Bot 在敏感工具调用前会被宿主拦下,请求变成对话里一张带 「允许这次 / 拒绝」的卡片;用户点击后回传宿主,工具才继续。5 分钟不回应结算为取消 (不能把 agent 永久卡住),失效的审批回 409 而不是假装成功。

  • 期刊日记:由宿主侧真实模型基于当天对话生成,不再是前端模板拼凑的假文案。

    • 生成:POST /api/personal-agent/journal/generate 把当天素材(消息、活跃 Bot、 记忆与例行任务统计)交给当前聊天模型,要求只依据素材写一期日报,输出结构化 JSON (标题 / 导语 / 3–4 个章节 / 引文 / 本期数据 / 当日速览 / 封面提示词);宿主清洗后落盘到 ~/.dsh/storages/dsh-seeker-bot/personal-agent/journals/<id>.json,刷新、清缓存、换标签页都不丢。 模型不可用时前端回退本地版(origin: 'local'),但同样引用真实统计与真实对话摘录。
    • 触发:宿主调度器 30 秒一 tick 检查规则(第二天 0:00–8:00、连续 30 分钟无用户主动对话、 当天素材非空、昨天还没生成过),满足就自动生成并经 SSE 推给前端;页面关掉也照跑。 POST /journal/check 与调度器共用同一份判定,规则面板的「立即检查 / 模拟 07:30 触发」调它。
    • 界面:全屏「书架 + 阅读台」。左侧 260px 书架按年月分组、可折叠;右侧居中 780px 纸张, 16:9 宽幅封面 + 期号细线 + 衬线大标题 + 首字下沉导语 + 本期数据 + 编号章节 + 当日速览时间线 + 引文 + 页码页脚。亮/暗双主题各一套纸张与墨色(亮色象牙纸、暗色深墨纸), 不是只换外壳。顶部可翻期、下载封面、打开规则面板。
    • 封面:调用生图模型生成 16:9 编辑插画(失败不阻塞期刊),无图时按主题用程序化 SVG 封面。
    • 规则/日志:journal-rule.json、journal-feed.json(最近 48h 素材)、journal-logs.json 由宿主持久化;前端启动时 GET /journal/list 与 /journal/status 合并回本地。

    期刊日记 · 暗色

    期刊日记 · 亮色

    期刊触发规则

  • 语音输入(与 Work 模式同链路):输入框麦克风不是 webkitSpeechRecognition, 而是 MediaRecorder 录音 → 16 kHz 单声道 WAV → ctx.remote.speech.transcribe (本机 SenseVoice,与 Work 模式同一条 ASR)。录音时整块输入框换成一条语音条: 左「X」取消、中间一排实时音量波形(静音时是点阵基线,说话时跟着起伏)、 右「停止方块 + 发送箭头」;停止后先转写(「正在转写…」),转写完成即收起语音条 并把文字并入输入框——不自动发送,确认后用输入框的发送键发出;点 X 随时丢弃。

    语音输入条

  • 图片输入:输入框里直接 Ctrl+V 粘贴截图(或点「+」选图片)→ 缩略图预览 → 随消息发出。 图片先经 POST /api/personal-agent/upload 落盘到 bot 工作区的 uploads/,消息里只存 同源 URL(/file?path=…),刷新、重开都还在;宿主会把图片路径写进这一轮的 user 消息 提示,bot 需要看内容时自己 read_image。支持 PNG / JPEG / GIF / WebP,单张上限 12MB。

    图片输入

  • 通话是全双工实时语音(不是「录音→转写→回复→朗读」的半双工):麦克风 24 kHz 单声道 PCM16 持续上行 → 宿主代理(同源 WebSocket /api/personal-agent/realtime,API Key 只留在 宿主)→ StepFun Realtime API → 24 kHz 音频流边生成边播回。服务端 VAD 自动断句、自动 ASR、 自动话轮管理,所以能边说边听、随时打断(你一开口,播放队列立刻清空);弹窗里同时显示 双向实时转写、「在听 / 在想 / 在说」三态、麦克风电平点阵与思考过程。通话界面没有文字 输入框(底部「不方便说话时也可以打字」那一行已移除);麦克风不可用时只提示重试或挂断。 最小化收起到右栏顶部。

    • 通话音量胶囊:居中的一颗胶囊(喇叭图标 + 细轨道 + 百分比),与「通话设置」按钮同一套 底色与圆角,不再是一条横跨整宽的裸滑块。控制 Bot 声音(下行)的播放增益,拖动实时生效(改的是播放器的 master gain,正在播的这一句也会立刻变小声);拖到 0 显示「静音」。值存在本机 localStorage, 下一通电话沿用,网络重连换播放器时也不会跳回 100%。上行麦克风不在这条链路上—— 浏览器 autoGainControl 已经管了,叠增益会干扰服务端 VAD 与 ASR。
    • 转写实时进对话流,不是挂断后才生成一张卡:每一轮(你说的 + Bot 回的)定稿就作为 普通消息出现在该 Bot 的对话流里,和打字聊天同一个气泡样式;通话开始时先放一条 折叠条(「实时语音通话 · 时长 · N 条」),点它收起/展开这一整段——收起只影响这段 消息渲不渲染,展开还是普通气泡,不会换一套卡片样式。语音里让 Bot 干活产生的图片/文件 也作为普通消息落在同一段里(可点开预览)。
    • 段内消息按落库序号插位而不是追加:ASR 转写是异步的,迟到时直接追加会变成 「Bot 先答、用户后问」(与通话弹层转写排序同一套机制,见 call-turns.ts)。
    • 通话时长只有一种口径:formatDurationSeconds(ms) 向下取整,折叠条 / 弹层 / 右栏计时 / 挂断提示全部走它。挂断提示原来用四舍五入,16.5 秒的电话会一边显示 00:16、一边说「17 秒」;改了就不要再在各处就地取整(回归测试见 scripts/test-call-duration.mjs)。
    • 模型按 stepaudio-3-realtime-preview → stepaudio-2.5-realtime 顺序回退(前者限免期名字, 到期下线后自动落到后者);单次会话上限 30 分钟,到期前 1 分钟提示、前 15 秒自动收尾。
    • 内置联网搜索默认开:session.update 会带上游的 web_search 工具。语音会话里模型 没有别的联网手段(Bot 在聊天里能调的 agent 工具不在 Realtime 链路里),关掉就只剩 「我查不了」。上游万一拒绝这个字段,宿主会自动去掉 tools 降级重发——通话照常, 只是没有搜索(诊断路由的 lastError 会写明原因)。
    • 语音里能让 Bot 真正干活(不再只是聊天):模型判定用户要动手时(生成图片 / 视频、 查资料整理、读写文件、跑命令、记事情),通过 Realtime 的 function calling 调用 run_task,宿主把它交给该 Bot 的完整 agent loop 执行——权限档位、工具集、记忆与 聊天里完全一致(语音不是绕过审批的后门)。任务在后台跑,期间对话照常继续;跑完结果 以 function_call_output 回灌给模型,模型再用一两句口语讲出来。前端会显示「正在执行」、 进度与成败。没有携带 Bot 定义时,模型只能闲聊 + 联网搜索。
      • 边界:单任务 180 秒超时、结果截断 1500 字(避免语音模型念小作文)、同一 call_id 只执行 一次(防上游重发导致生图跑两遍)、挂断时中止在跑的任务。
      • 上游三种 function_call 事件形状都认:response.function_call_arguments.done、 conversation.item.created + item.type='function_call'、以及「item.created 不带参数 → ...arguments.delta 逐片生成 → ...arguments.done 的 arguments 为空」。最后一种是 真实 StepFun 的形状(官方文档的 item.created 示例里没有 arguments 字段),只认前两种 会在参数还没生成时就执行,表现为「模型说要干活,然后回你『任务描述为空』并反复重试」。 宿主会用 delta 拼出的参数兜底,response.done 时再兜一次底。
      • 任务失败时看诊断路由的 lastFunctionCall:emptyArgs: true 说明上游在参数还没生成时 就通知了工具调用;source 会写明这条是从哪种事件形状触发的。
    • Live 供应商是独立列表,全部在界面上配,不需要改代码或配置文件:
      • 入口:官方「设置 → 供应商」页顶部的模式切换(模型 / 实时语音)。切到实时语音后, 左栏换成 Live 供应商列表(沿用「列表 + 添加」那套交互),右栏是选中那条的详情 (名称 / 上游地址 / 凭据名 / 模型 / API Key / 采样率)。生图、生视频、网络代理属于 聊天模型那套,只在「模型」模式出现。
      • 可自由新增 / 编辑 / 删除:一条 Live 供应商 = 一个能连的 Realtime 端点 + 一把 key + 一个模型。新增时只填最少的(名称 / 上游地址 / API Key / 模型),音色、采样率、语速 用默认;内置的 StepFun / 千问模板可一键快填,也可以完全自己写。
      • API Key 直接填(type=password,写进 DSH 凭据库加密保存,页面永不回显; 只写不读的接口是 POST /api/personal-agent/realtime/key)。
      • 模型名可自由输入(<input list> + <datalist>:候选只是建议,能填任意 id)。
      • 每个 Bot 单独选用哪条 Live 供应商(Bot 设置卡「语音通话」、新建 Bot 弹层); 留空 = 跟随全局默认(列表里标「默认」的那条)。
      • 全局设置(默认音色 / 语速 / 内置联网搜索)也在同一页,列表下方。
      • 落盘在 ~/.dsh/storages/dsh-seeker-bot/personal-agent/realtime-config.json (vendors[] + defaultVendor + 全局音色/语速/联网;不写 DSH settings: settings.update 只认已注册的插件 entry,任意命名空间写不进去)。旧版的单份配置与 routes.<provider> 会在读取时自动迁移成一条 vendor,不用手动改文件。
      • 语速不是播放变速:Realtime API 没有独立语速参数,这一档是靠 instructions 让模型 说得慢/快一点,不保证精确。
    • 一条 Live 供应商都没配时也能跑:内置模板会先播种 StepFun 与千问两条,没填 key 的话 通话会明确报「凭据还没配」而不是静默失败。插件 config 里的 personalRealtime 是 部署级兜底,界面上改了值以界面为准。
    • 打不通时先看 GET /api/personal-agent/realtime/diagnose:它给出「有没有配 key、上游地址、 模型候选、最近一次失败原因、以及不认识的上游事件名及次数」,不含任何密钥值。 如果通话有声音但用户侧没有字幕,先看 unknownEvents 是否出现了新事件名——那说明上游 改了事件名(比如 ASR 转写事件),而不是网络问题。
  • 回复气泡可以朗读,也能自动念:每条 Bot 回复的气泡侧边、引用按钮之后有一枚喇叭按钮 (hover / 键盘聚焦浮现;播放中变成停止图标并带一圈呼吸),点一下就用通话里已经配好的那套 音色 / Live 供应商 / 语速把这条念出来——不另配一套 TTS,同一个 Bot 只有一个嗓子。 Bot 设置卡「语音通话」分区里的**「自动朗读回复」**开关打开后,新回复会自动念(只在单聊生效; 群聊与通话进行中不自动念,避免抢话,但手动点喇叭仍然可用)。

    • 链路(流式,首包即出声):POST /api/personal-agent/tts 带 stream: true → 宿主收到首片音频就回 200(application/octet-stream + x-tts-sample-rate 头), 之后 chunked 逐片写裸 PCM16;浏览器 response.body.getReader() 逐块喂 PcmPlayer 边收边播。首字延迟从「整段合成时间」降到「建连 + 首包」——不再等 response.done 才回包(旧实现要等整段念完,长回复会干等十几秒)。 兼容:旧前端不传 stream 仍拿整段 WAV(audio/wav);新前端遇到旧宿主会按 content-type 自动回落到 WAV 解码。
    • 宿主为每次朗读新开一条一次性 Realtime 会话:只下发朗读指令(「把这段文字原样念出来, 不要回答、不要总结」)+ 音色,不注册任何 tools、不带 turn_detection,念完立刻关连接, 不占通话并发。收到 session.created 就发朗读请求,不再等 session.updated—— WS 帧有序,session.update 一定先于 item.create 被上游处理,等它只是白多一次往返 (几百毫秒,正是用户抱怨的那部分延迟)。
    • 失败口径:首包之前出错回 JSON { ok:false, error }(可读中文,前端 toast); 首包之后只能断流(已经发出 200 与部分音频,再补 JSON 会被当成 PCM 播成噪声)—— 前端把「流提前结束」当半段处理。
    • 为什么复用实时语音:用户的心智是「通话里挑好的声音,聊天里也该是它」。宿主读的就是 realtime-config.json 那一份(vendors[] + 音色 + 语速),Bot 上单独选的 voice / liveVendor 优先级与通话完全一致;没配 key 时报的是同一句可读提示。
    • 文本先洗再念:代码块整段去掉、行内反引号去掉保留文字、链接只留文字、图片整段去掉、 标题 / 列表 / 表格符号去掉,压缩空白后截到 240 字(宿主再用 400 字兜底)。一条回复念两分钟 不是用户想要的。
    • 播放是全局唯一的:点另一条会打断当前这条;连点只保留最新一条的在途合成 (AbortController 取消旧的),不会让宿主同时开好几条上游会话。切会话 / 开通话 / 离开 对话流都会收声。自动朗读被浏览器策略拦住时静默回空闲(不弹错),点一下喇叭就能正常播; 手动点播失败会 toast 出原因。
    • 实现:src/client/personal/speech.ts(单例播放器 + 文本清洗)、宿主 src/host/personal/realtime/tts.ts(synthesizeSpeech / encodeWav);契约 REALTIME_TTS_PATH / RealtimeTtsRequest 在 src/shared/realtime-protocol.ts。 回归:scripts/test-realtime-tts.mjs(端到端)、scripts/test-speech-client.mjs。
  • 对话内交互 UI:选项 / 按钮 / 表单卡片可直接操作;提交后追加用户消息、Bot 继续回复, 部分表单会真实创建例行任务;卡片标记「已提交」防重复。

  • 动效:每条消息上滑入场(pa-rise 260ms)、弹窗缩放淡入、通话呼吸波纹、列表选中态 120ms。

回复形态:Bot 是聊天,不是工作台

Bot 跑的是宿主完整的 agent loop(工具、技能、MCP 全都在),但输出形态按对话设计, 和 Work 模式刻意不一样:

  • 正文精炼:1–3 句、最多两小段,先结论后补充;不用加粗伪标题、多级清单、表格、 代码块,不贴文件路径 / 命令 / 参数(用户明确说「详细说 / 报告 / 清单 / 代码」才展开)。
  • 过程不进正文:工具调用块在 Bot 消息里默认不渲染(generate_image 例外,出图卡直接 落在对话流里),正文最多一句「我看了下」——过程属于界面,不属于消息。
  • Work 专属提示段被摘掉:每个 Bot 的 agent 作用域里会过滤 team:policy(Agent Teams 协作纪律)、ui:deliverable-file-references(present / 文件路径链接规则)、 app:web-surface(Web GUI 与 HMR 说明)、harness:source(实现 checkout 位置)。 实现在 src/host/personal/agent.ts 的 trimWorkOnlyPrompt():全局段用同名空 section 覆盖,team:policy 走 system-prompt/assemble waterfall 过滤(它是 agent 作用域注册的, 同名覆盖会撞注册表,实测会把建会话流程带崩)。实测 bot 的系统提示从 ~8.7k 字符降到 ~4.3k。
  • 一份契约两个入口:契约写在 buildBotPrompt();agent 不可用时的降级路径 (/reply 的 chat 补全)用同一份措辞,避免「同一个 bot 换个入口就换了语气」。

多消息:一次 turn 可以有多条消息

DOST 式的「短消息 + 多消息」形态。host 按模型说话的次数切分:一次 turn 里模型 说完调工具、拿到结果再说一段,就是两条独立消息,而不是把两段合并成一条长回复。

事件 含义
message 新开一条消息(kind 决定外观)
message-update 原地更新(工具卡 running→ok、生图占位→真图)

kind 共五档:progress / finding / need-input / approval / final。 其中 progress 与 final 由 host 推导(最后一条标 final),另外三档由模型通过 bot_emit 工具主动声明:

bot_emit({ kind: 'need-input', text: '你要 Markdown 还是 Word?' })
  • 只在三种情况用:发现风险(finding)、缺关键信息(need-input)、 高影响动作待批(approval)。普通进度不用调——正文本身就会成为一条消息。
  • bot_emit 不产生工具卡:用户该看到「一个问题」,不是「调了一个工具」。
  • 非法 kind 退回 finding,空 text 整条丢弃(发一条空消息比不发更糟)。

工具块不跨消息是硬约束:同 callId 的工具卡始终留在发起它的那条消息里,只做 message-update。否则生图占位会「跳」到新气泡、生视频的秒数计时器会重新挂载归零。 产出物段落(parseArtifactParagraph 认的整段路径)也不参与拆分,否则 Office / PDF 卡片会丢。

外观上 finding 是左侧橙线 + 淡底,need-input / approval 是描边卡片(蓝 / 红) 带「需要你定」「待确认」标签,progress / final 是普通气泡——加框反而吵。

上下文与工作记忆(2026-11-02)

Bot 的会话是终身的(pa-bot-<botId>),所以「记住什么」比「说什么」更关键。这一轮补了三层:

层 机制 效果
工作记忆 task_state 工具写 working-memory/<botId>.json,每轮注入当前活跃任务 任务跑到一半触发上下文压缩,Bot 仍知道进度、关键数据和待办
长期记忆 记忆变更时清 stepCounters 重新注入(不再等压缩);检索层每 5 轮 / 换话题刷新;过期临时信息(ttlDays)每日软废弃 面板里新加一条记忆,Bot 下一轮就能用上;不会越记越旧
工作日志 任务 done 后自动沉淀「日期 + 标题 + 产出 + 关键数据」进记忆库 一周后问「上周做了什么」答得上来

记忆不是权威源:注入引导语里明确写了「涉及会变的事实(金额、库存、状态)先查当前源」, 避免 Bot 拿旧记忆当实时数据。

生成式 UI:Bot 也能出卡

Bot 的正文契约是「1–3 句、不用 Markdown 排版」,但结构化的东西本来就该交给卡片。 现在 Bot 回复里的 ```iu / ```html 围栏会渲染成真卡片(与 Work 模式同一套渲染器):

  • 有行列 / 要比较 / 要操作 / 多方案 → 出卡(table / tabs / checklist / kanban / timeline / form)
  • 解释 / 结论 / 寒暄 → 纯文字
  • 带卡的消息不折叠(卡片被 clamp 会切成半截)

对话流的其他改动

  • 工具卡不出现:Bot 消息里不渲染工具调用(用户明确要求);computer / browser 类仍会转成「执行态卡」, 生图 / 生视频 / 文档占位作为产物保留。
  • 轮次容器:同一轮的多条消息按 turnId 归组,轮次头显示时间与状态,轮次尾给「N 条消息 · M 个产出 · K 个待办」,可折叠;没有 turnId 的老数据照旧单条渲染。
  • 压缩标记:上下文被压缩时,对话流里插一行居中小字「上下文已压缩 · N 条消息折叠」,有摘要才可点开。
  • 引用回复:消息 hover 出「回复」,输入框上方显示引用条。发送时 replyTo 会补全成 quote (被引用消息的完整正文 + 角色,交互卡的选项按「(1)(2)」展开)随 /reply 投给宿主, 模型据此理解「1 / 这个 / 第一条」指的是哪一条——引用不是界面装饰;history 里也保留引用关系, 跨轮不会丢。
  • 待办上浮:pending 的 need-input / approval 常驻输入框上方,滚走也不会漏。
  • 执行中重定向:Bot 正在跑时发新消息,新指令优先于后台工作、可改变当前轮方向(agent.steer()); 「停止」仍然只停不撤销。
  • 执行态卡:computer / browser 类工具渲染成「正在做什么」的卡片(状态 + 中文动作 + 可展开步骤 + 截图)。
  • 审批规则:审批卡支持「以后都允许」,规则落 approval-rules.json;风险标签(不可撤销 / 改安全设置 / 外发数据);聊天内审批不过期,后台 30 分钟。
  • Pages:Bot 产出的 .html 页面出现在右栏「页面」卡;更新过的旧卡片显示「已更新到下方」。
  • 对话列宽度:对话流两侧边缘悬停出现拖拽手柄,拖动调整中间对话列宽度(左右侧栏不动), 双击恢复铺满;宽度写进 pa:ui:v1,刷新保持。
  • 工具结果摘要:单条工具结果超过 8000 字符时,进上下文的是「头 2k + 摘要 + 尾 1k」,全文落盘可展开, 按 14 天 / 512MB 自动清理。

撤回一条消息(连上下文和记忆一起)

用户消息气泡 hover / 触屏常显「↺ 撤回」,确认后同时做三件事:

  1. 删本地消息:这条用户消息 + 它之后到下一条用户消息之前的全部消息(含 pending、 群聊里同轮多个 Bot 的回复),原位置插一条「你撤回了一条消息」系统提示;
  2. 删记忆 / 工作记忆:只删这一条用户发言(provenance.sessionId + provenance.userMessageId, 旧数据没有消息 id 时退回 turn)写下的 source = extract 记忆、memory_remember 手动记忆与 task_state 工作记忆;Bot 允许执行中插话时第 2、3 句同 turn,按消息 id 才不会误删第 3 句。用户手写或面板添加、且没有本轮 provenance 的记忆不动。旧数据 才退回该轮的精确起止时间窗口(不是原来的「消息时间 -1min ~ +10min」);并为该消息 立撤回墓碑,抽取任务在落库前后看到墓碑会跳过,避免「撤回后又被写回」;
  3. 撤上下文:POST /api/personal-agent/recall → src/host/personal/recall.ts 在 agent 会话的 surface 上追加一条 replace 事件,把该轮所有节点(用户消息 + 注入上下文
    • assistant 回复 + 工具结果)整体遮蔽。日志保留,之后的模型请求重建历史时看不到它们。

实现要点(踩过的坑都写在代码注释里):

  • Session 日志 append-only,但 surface 支持 replace(compaction 同一机制); 遮蔽区间必须覆盖每一个 surface 节点(sourceEventSeqs),否则宿主校验拒绝。
  • 找目标消息只认 source.kind === 'user':宿主会往 user 角色塞注入上下文 (agent-instructions / runtime-context / system-reminder),按它们切区间会把撤回缩成一条。
  • 撤回节点是 developer/message,source.kind 必须写成 plugin:dsh-seeker-bot; v3 的 plugin 包装会被 v4 落盘校验拒绝,表现为「当场生效、重启后又想起来了」。
  • append 后必须 sessions.flush():不落盘的话,下一次 turn 会被 pre-step 判成 blocked。

语义边界:撤回只影响之后的模型上下文。模型在产生那条回复的那一步已经看过原文, 这里保证的是「后续对话不再带着它」。

数据与真实能力

  • 前端状态持久化在 localStorage 的 pa:state:v1(Bot / 群聊 / 消息 / 期刊)。 记忆、例行任务、主动询问的权威副本在宿主(见下),前端只持有展示快照。
  • Host 路由 /api/personal-agent(任一项失败都自动回退本地引擎):
    • POST /reply:走宿主真 agent loop(mode: 'agent'),返回 blocks + messages (多消息切分视图);agent 不可用时降级为一次 ctx.llm.stream 补全(mode: 'chat');
    • POST /reply/stream:同一轮的 SSE 流(message / message-update / text / tool / error / done),Bot 对话据此逐条显示消息与工具进度(生图 / 生视频占位卡靠它出现);
    • GET /events?botId=&since=:常驻事件流。宿主主动产生的消息(例行任务、主动询问、 审批卡)从这里推过来;since 用于断线重连补发(每 bot 200 条有界缓冲,25s 心跳)。 botId=* 是一条连接订阅全部 Bot(此时 since 变成逐 Bot 的 botA:5,botB:3, 因为各通道序号独立)。前端只建这一条,理由不是省事:SSE 是长连接,而浏览器对同一 origin 的 HTTP/1.1 并发上限只有 6 条——「每个 Bot 一条 SSE」在 4 个 Bot 时就吃掉 4 条,而新建 Bot 时前端 effect「关掉全部再建全部」的瞬时翻倍会直接顶破上限,之后所有 请求排队,界面看起来就是「服务断了」(实测:建到第 4 个子 Bot 必卡死,重启服务能好 一阵、再建又卡)。合并后 Bot 数量与连接数彻底解耦,且 Bot 增删不再触发任何重连;
    • GET|POST /routines:例行任务定义(前端全量同步 → 宿主执行),POST 会丢弃坏数据 并把 nextAt 补成未来时间;
    • GET|POST /proactive + POST /proactive/run:主动询问配置与「现在问一次」;
    • POST /approval/decide:审批决定回传。失效审批回 409(不假装成功);
    • GET /messages?botId=:离线期间的服务端消息补发;
    • GET /bots/created:宿主建过的子 Bot 账本(bot_create 的产物)。前端启动时 拉一遍,补上「页面关着时主 Bot 建出来的 Bot」——SSE 只在页面开着时送达, 光靠事件会漏。返回 { ok, bots: CreatedBotRecord[] },前端按 id 去重合并;
    • DELETE /bots?botId=:彻底删除一个 Bot(会话历史 / 花名册 / 工作记忆 / 产出物映射 / Bot 页面 / 记忆条目 / 例行任务 / 账本),返回 { ok, removed[], outputDir? }。 产出物目录默认保留,&purgeFiles=1 才连文件一起删。与下面那条只抹账本的 DELETE /bots/created 是两件事,别混用;
    • POST /memory/remember:面板「添加记忆」写入引擎(host 现算 projectHash + owner);
    • POST /journal/image:调用当前生图模型生成 16:9 期刊配图,落盘到 ~/.dsh/storages/dsh-seeker-bot/personal-agent/journals/;
    • WS /realtime:全双工实时语音代理(StepFun Realtime)。浏览器同源 WebSocket 升级, 宿主注入 Authorization 后转发到 wss://api.stepfun.com/v1/realtime,双向搬运 JSON 事件与 裸 PCM16 二进制;取代了原来的 POST /tts(Edge TTS)半双工链路,那个路由已删除。 实现见 src/host/personal/realtime/(帧编解码、上游客户端、事件翻译、会话编排四层分开, 不依赖 cordis,可离线端到端测试);
    • GET /realtime/diagnose:实时语音诊断(是否配了 key、上游地址、模型候选、最近失败原因, 不含密钥值);
    • POST /tts:回复气泡朗读(不是上面被取代的 Edge TTS)。请求 { text, vendor?, voice?, model?, speed? }, 复用实时语音那份配置(Live 供应商 / 音色 / 语速)。stream: true 时流式回裸 PCM16 (首片即回 200 + application/octet-stream + x-tts-sample-rate,首包前失败回 { ok:false, error });不传 stream 时回整段二进制 WAV。文本上限 400 字、请求体 32KB。 实现见 src/host/personal/realtime/tts.ts,端到端测试 scripts/test-realtime-tts.mjs;
    • POST /asr:宿主 speechController(本机 SenseVoice)转写;
    • GET /file:bot 工作区里的媒体文件(生图 / 生视频落盘产物与正文里的文件)。相对 路径以 bot 工作区为基准,图片 24MB、其它媒体 256MB;视频 / 音频支持 HTTP Range (206 + content-range,浏览器可拖动进度条),mov 按 video/mp4 声明。
    • GET /outputs?botId=&name=:Bot 产出物清单。扫描 <bot 工作区>/产出物/<bot 文件夹>/(9 个中文分类子目录 + 根/未知子目录),跳过隐藏 与 ~$ 临时文件,按文件修改时间倒序,最多 200 条;返回 path / url / name / kind / size / time / category。bot 文件夹名由 bot-output-dirs.json 持久化(改名不迁移)。
    • GET /office/info|page|pdf:Office / PDF 预览(复用 src/office 的 LibreOffice 页图与 PDF 转换),准入与 /file 同一套工作区闸门。
  • 宿主侧持久化(~/.dsh/storages/dsh-seeker-bot/personal-agent/): routines.json(例行任务定义)、proactive.json(主动询问窗口与下次时间)、 server-messages.json(每个 bot 最近 50 条服务端消息,供离线补发)、 bot-output-dirs.json(botId → 产出物文件夹名 映射)、 created-bots.json(主 Bot 通过 bot_create 建出来的子 Bot 账本,最多 500 条, 到顶丢最旧;前端合并进 pa:state:v1 后这份账本只作补发用)。
  • 记忆引擎(~/.dsh/memories/dsh-memory/):schema v6,条目带 owner (work / bot:<id>)。id 是 mem_sha1(owner|scope|projectHash|content), 所以两个 bot 记下同一句话是两条。加载时自动完成存量迁移(幂等), scripts/migrate-memory-owner.mjs 可先跑 dry-run 看归属分布。
  • 生图 / 生视频实时卡(与 Work 模式一致):bot 调 generate_image 时,对话流里先出现 按本次图片比例 / 张数呼吸的点阵占位;工具结果一回来(含被 spill 的大结果——host 会读回全文、把 base64 解码落盘到 <bot 工作区>\_tmp\generated-images\),同位置 换成真实图片,点开是大图。generate_video 同理:生成中显示「正在生成视频」点阵 + 实时秒数 + 提示词;结果里有视频 URL / b64 就落盘到 _tmp\generated-media\ 并内嵌 <video> 播放,网关返回异步任务时显示「视频任务已创建」(id + 状态)。
  • 视频与 Office 产出物:模型最终回复里写到的本地文件(_tmp/report.docx、 D:\...\x.pdf、_tmp/demo.mp4 等,markdown 链接或裸路径都认)会渲染成产出物卡: 图标 + 文件名 + 类型 + 大小 + 预览 / 下载。PDF 直接内嵌浏览器查看器,Office 先出首页 页图、展开后转 PDF 预览,视频 / 音频用原生 <video> / <audio>;工具结果里识别到的 文件(present 的 path、pwsh / run_code 落盘)也会以同样卡片出现。正文已经贴了图片 时工具卡不再重复显示同一张图,图片过期(_tmp 被清理)则显示「图片已过期」而不是裂图。
  • 独立演示页(不依赖 DSH,数据存 localStorage):
npm run build:personal-demo
# 打开 dist/personal-agent/index.html

独立演示页没有 DSH 宿主:模型回复 / ASR 会回退到本地演示引擎,界面与交互仍可完整体验; 但全双工实时通话不可用——它必须有宿主那一跳(API Key 不能出现在浏览器里)。 需要真实模型、语音识别与实时通话时,请在 DSH 应用内使用 Bot 模式(并刷新一次页面)。 参考素材里没有给出新建 Bot / 通话 / 期刊三种界面的原图,这三块按截图同一套设计语言延伸实现。

二级菜单:手风琴

记忆(工作区)、画廊(八个类别)、档案(场景)这几行展开后列出的子项,是手风琴:

  • 点开「画廊」,之前展开的「记忆」在同一拍收起,同一时刻只有一组子项展开;
  • 展开态记在 localStorage 的 dsh-sidebar-expanded-menu,刷新后仍是上次那一行;
  • 实现是「展开 = 覆盖成单值」(writeExpandedMenus),渲染层只判断自己那行, 所以互斥是数据结构自带的,不需要每次点开头再遍历兄弟行去收一遍。

档案的场景子项还多一层三级分组(账号与登录 / 支付与联系方式 / 证件与文件):分组行 点击只展开 / 收起,叶子节点才切换场景;默认展开当前选中项所在组,自定义场景平铺在分组之后。

旧版本允许多行同时展开,存量数据里可能还留着 memory,gallery 这种多值。 readExpandedMenus 只认第一个,任何一次展开/收起都会把它写回单值——升级后 不会出现「一进来所有行全撑开」。

加一个新菜单(给二次开发)

侧栏二级导航是注册表驱动的:src/client/triad/hub/nav-registry.ts 里两级数据 ——区域(section)与条目(entry),侧栏只按表渲染。加一个菜单不需要碰挂载器、 样式或显隐逻辑:

// 打开某个官方 main 面板(例:加一个「任务」菜单)
ctx.effect(() => navMenuItem({
  id: 'tasks', label: '任务', icon: MyTasksIcon, panelId: 'schedule',
}), 'dsh-seeker-bot: 任务菜单')

// 打开工作台的某个分类
ctx.effect(() => navMenuItem({
  id: 'gallery-entry', label: '画廊', icon: MyGalleryIcon, workbenchTab: 'gallery',
}), 'dsh-seeker-bot: 画廊菜单')

// 自定义动作(弹浮层、发命令……)
ctx.effect(() => navMenuItem({
  id: 'custom', label: '随便什么', icon: MyIcon, run: () => { /* ... */ },
}), 'dsh-seeker-bot: 自定义菜单')

// 再加一整屏(第三个区域,按 order 轮转)
ctx.effect(() => navSection({
  id: 'tools', label: '工具间', icon: MyIcon, order: 30,
}), 'dsh-seeker-bot: 工具间区域')

条目缺省落在操作间(section 不写就是它),order 决定行序(工作台条目 占 10–60;连接器不在工作台里,它是侧栏底部「MCP服务器」胶囊弹出的居中弹窗卡片)。 注册返回的 dispose 交给 ctx.effect 回收,热重载不残留;同 id 重复注册 = 覆盖, 不会叠出两份。


网页搜索降级链:额度没了自动换一家

宿主 ctx.web 的 seam 只会选一个搜索后端,失败就是失败——WebRuntime.search() 是 selectSearchProvider().search() 一行,web.searchProvider 的注释还写明它「不是隐藏优先级链」, registry 也是私有 Map(插件枚举不到别的后端)。所以「额度打满自动切」只能长在插件这一侧: 本包注册单入口 anysearch-auto,在自己拥有的三个实现之间按失败类别降级,宿主永远只看到一个 provider。

顺序 后端 说明
1 anysearch 复用原 AnySearch 实例,已有端点 / key / 条数配置零迁移
2 tavily 需要凭据 TAVILY_API_KEY;没有时预检直接跳过,不报错也不白打
3 deepseek-official 走 Anthropic 兼容 Messages + 原生 web_search_20250305,最贵(每次一个模型 turn),所以放最后

什么情况才切(判定在 src/provider/modules/web-search-failure.ts,实测报文形状写在文件头):

失败 归类 行为
HTTP 402 / 432 / 433、报文含 quota/额度/欠费 WEB_QUOTA_EXCEEDED 切下一家,该环冷却 30 分钟
HTTP 429 / 434 WEB_RATE_LIMITED 切,冷却听 Retry-After(封顶 15 分钟),没有就 60 秒
HTTP 401 / 403 WEB_AUTH_FAILED 切,冷却 10 分钟(等你换 key)
HTTP 5xx、连不上、响应体读不动 WEB_UNAVAILABLE 切,但不冷却——下次仍先试它(它结果最好)
没配 key WEB_PROVIDER_CREDENTIAL_MISSING 切,不冷却;链还会先用 ready() 预检,省掉一次必败往返
HTTP 400 这类查询本身有问题 WEB_PROVIDER_ERROR 不切——换一家也必败,只会白烧一次额度
用户停止生成 WEB_ABORTED 不切,原样上抛。把取消误判成故障会导致停止后继续往下几家猛打

两处反锁死兜底:全部环都在冷却时忽略冷却重走一遍;全部环凭据预检不过时照样试—— available() 是同步的、读不到异步凭据库,不能让预检变成否决权。

启用与回退(profile 的 cordis.patch.yml):

- id: web
  name: '@deepseek-ai/dsh-web'
  config:
    searchProvider: anysearch-auto   # 改回 anysearch 即回退到单体,无需改代码
    fetchProvider: http

链的顺序、额度冷却时长、两个后备后端的端点与凭据引用都在 settings 命名空间 web-search-auto (默认 order: anysearch,tavily,deepseek、quotaCooldownMinutes: 30)。注意目前没有专门的设置页 卡片:宿主「网页搜索」页只认 web-search-deepseek / web-deepseek-api / web 三个命名空间, 不改这些时用的是上面的默认值。

一个诚实的限制:WebSearchResult 只有 sources / content / truncated,没有字段携带「这次实际 用了哪家」,所以实际后端只进日志([hub/web-search-auto] 前缀),对话卡片上的来源标签不变。


六个工作台

侧边栏工作台(六个页面),每页独立:

记忆

自动沉淀的长期记忆:每轮对话后由模型提炼值得跨会话保留的事实(你的偏好、项目约定、踩过的坑), 按项目 / 全局分层。可在面板里增删改、置顶、按标签检索。在左栏选中某个项目后,列表头下方 出现一条项目上下文条,可改别名、可清空该项目的全部记忆(置顶记忆会保留)。

归属维度(2026-11,适配 Bot 模式):记忆库按归属隔离——work(Work 模式的会话)与 bot:<id>(Personal Agent 里每台 Bot 各一份)。所以侧栏「操作间 → 记忆」的二级菜单现在有两组: 工作区那几行(Work 侧的范围筛选)+ 末尾的 Bot 分组(全部 Bot + 每台 Bot 一行)。

点某台 Bot 就进它的资料库:顶部归属徽章变成主色(写着 Bot 名 + 个人资料库),列表只显示 它自己的条目,「变更」页也只显示它的变更,统计数字换成它自己的口径;点徽章一键回到 Work 全部工作区。 在 Bot 视图里添加的记忆写到那台 Bot 名下,「整理」「删除今日」也只作用于它——改造前这两条会跨归属 误伤(把 Work 与其它 Bot 的记忆一起整理/删除)。全部 Bot 是聚合视图,列表行会带来源徽章标明 每条属于哪台 Bot;聚合视图不能添加记忆(没有具体归属可写)。

改造前还有一个静默缺陷:Work 记忆页的列表请求不带 owner,宿主侧就不过滤——Bot 的记忆会混在 Work 的列表里,既没有来源标记也删得掉。现在三处都按归属过滤:列表、变更、一键操作。

两个按工作区的开关(记录要不要往这个项目里攒新记忆、不注入这个项目的记忆要不要 进上下文)不在这个面板里 —— 它们在 Seeker 的 大盘设置 卡片里,按工作区逐个列出、一次能拨完好几个。一处入口、一份真相。

记忆工作台

SKILL

管理技能(Skill):安装 / 停用 / 查看说明 / 归入技能包 / 设置分类。技能包与状态筛选在同一行, 分类在左、启用状态在右。

SKILL 工作台

连接器(弹窗卡片,不占工作台页)

入口位置与形态(2026-11-03):操作间不再有「连接器」菜单行,工作台也不再承载它。 入口是侧栏最下面账号行右侧的 「MCP服务器」胶囊(Bot 模式在左侧栏底部「设置」行右侧); 点击后弹出居中的连接器弹窗卡片(1040×860 上限,内部滚动,点遮罩 / Esc / × 关闭), 不再把整个主区切成一个工作台页。见 src/client/triad/connectors/sidebar-pill.tsx 与 src/client/triad/connectors/ConnectorModal.tsx。

MCP 连接器市场:61 个精选连接器(国内 14 / 国外 47)。目录按 18 个细分类维护, 弹窗展示时合并成 6 个大组(系统能力 / 办公协作 / 开发工具 / 内容设计 / 搜索与数据 / 生活出行),带搜索、精选 / 国内 / 国外三条快筛,点开卡片看详情、填密钥、一键安装。

「系统能力」那一组(电脑操作 / 浏览器操作)是 DSH 官方插件,不是第三方 MCP: 2026-11 起它们从 /computer-use、/browser-use 两条斜杠命令挪到了连接器面板 —— 装「电脑操作」等于把 @deepseek-ai/dsh-computer-use 的插件行写进 cordis.patch.yml 并打开工具闸门(工具下一步请求就注入),卸载则连条目带 insert 块一起摘掉、工具退出请求 (每轮省回约 3.1 万 token,也不再占宿主内存)。这样 / 菜单只留真正的指令, MCP 能力统一在连接器里管。浏览器操作那两个官方包当前没装在 profile 里, 点安装会明确提示缺哪个包(需要先在 profile 目录 dsh plugin add 装回来)。

天气用的是 open-meteo-mcp-server(v2.5.6,17 个工具):零 API Key、零注册, 实况 / 16 天预报 / 逐小时 / 空气质量 / 1940 年至今历史天气都有。选它而不是和风天气、 OpenWeatherMap 那类,是因为那两个都要注册拿 Key(和风有免费额度但要实名), 而 Open-Meteo 非商业用途完全免费;实测 api.open-meteo.com 与 geocoding 接口 国内直连可用,中文城市名能直接解析成坐标。

目录不接第三方市场——数据随包分发在 assets/mcp-catalog/catalog.json,图标自托管在 assets/mcp-catalog/icons/。这么做是照抄主流开源 agent 的成熟做法:Cline 用 cline.github.io/marketplace/catalog.json(203 条)、Hermes 用仓库里 66 个 manifest.yaml、 Cherry Studio 直接硬编码 12 条——没有一家接第三方全量市场,规模都在 3~203 条之间。

每条都验过:包在 npm / PyPI 上真实存在(npm view / pypi.org/pypi 全部 200), 图标是真品牌图(ModelScope 封面 / GitHub 组织头像 / 官网 favicon,全部 ≥64px), 条目之间按仓库地址与安装配置双重去重。

安装落地为 cordis.patch.yml 里的一条 @deepseek-ai/dsh-mcp-client 实例(与 SKILL 面板的 MCP 启停同一份配置、同一套语义)。mcp-client 条目增删不走热重载, 所以装完页面会明确提示重启 DSH 后生效——不假装已经连上。

国内那半边是这批连接器的差异点:高德地图、百度地图、飞书、钉钉、支付宝、12306、微信读书、 哔哩哔哩、微博、京东、淘宝、抖音、网易云音乐、天气。Cline 和 Hermes 一条国内都没有。

Gmail 用的是 @gongrzhe/server-gmail-autoauth-mcp(v1.1.11):首次使用要在终端跑一次 npx @gongrzhe/server-gmail-autoauth-mcp auth,浏览器登录 Google 账号后凭据存在 ~/.gmail-mcp/,之后不用再授权。Google 官方的 gmailmcp.googleapis.com 端点在国内直连不可达, 所以没有采用远程 HTTP 方案。

X(Twitter)用的是 @mobalab/x-mcp-server(v2.1.0,27 个工具):走 X 官方 API v2 的 OAuth 1.0a 四件套(API Key / API Key Secret / Access Token / Access Token Secret),合规、不会踩 X 的自动化风控。 代价是 X 官方 API 现在没有免费档、也不卖订阅,纯按量计费——发推 $0.015/次、带链接的推文 $0.200/次、 读 $0.005/资源,新账号绑卡首充送 $20,所以得先有 X 开发者账号并充值。只想先试试、不想开开发者账号的, 可以把 args 换成 @twitterapis/mcp(第三方服务商,免费送 $0.50 额度、免绑卡,公共搜索与用户读取 只填一个 key 就能用;但它发推要关联 X 会话 cookie,有账号风险,116 个工具也会明显拉长会话上下文)。

连接器弹窗卡片

画廊

所有对话生成的图片 / 网页 / 演示 / 文档 / 表格 / 音视频一页看全:跨会话增量索引、 类别筛选、搜索、跳回来源会话。

点任意一张卡片 = 用系统默认应用打开这个文件(2026-10-20)—— 图进看图器、视频进 播放器、PPT 进 PowerPoint,与你双击资源管理器里的图标完全一致。走官方 open-in-app 那条 shell-open 链路(remote.session.openWorkspacePath),不自建路由。

产出物卡的口径与画廊不同(2026-10-22):侧栏能正常展示的类型走侧栏(图片 / 文档 / PDF / Office / 表格 / 页面 / 音视频),展示不了的才交给系统默认应用。 判据逐扩展名核验过(含 ods 的接管、avif/tif/heic 这类"看着像图但官方不认"的 坑),见 src/client/sidebar-display.ts。

本机没有桌面时(手机远程访问 / 纯服务端部署)自动退回内置预览:可内联的进就地 弹窗(图片缩放、视频播放、html 沙箱 iframe、PDF / Office 页图翻页),不可内联的走 官方右栏,行为与升级前一致。那个弹窗右上角也留着一枚「用默认应用打开」,本地看到 想拿去用的那一刻就能直接送出去。

生图与生视频的结果原本只存在于 spill 的文本里,打开前会先由 host 落成真正的 png / mp4 —— 直接把 .txt 交给系统只会弹记事本。

每个类别标签上写着该类的真实条目数(图片几百张就显示几百,不做 99+ 之类的截断)。

时间轴视图(默认视图)的方格在 2026-10-24 整体放大约 21%:最小列宽 150 → 182px、 gutter 10 → 14px,缩略图与占位图标同步加法,格子仍是 1:1 正方形。四种格式 (图片 / 网页 / 视频 / 音频)共用同一套方格尺寸,换类别不会跳变。

同一次改动里去掉了方格右上角的类型徽标(分类已经在工具条上选定,每格再挂一枚纯属 噪声);2026-10-25 又把文件名与时间从常驻改成 hover 浮层:默认格子是干净的正方形 缩略图,鼠标移上来(或键盘聚焦)时以自下而上的渐变浮层淡入在缩略图内部的下缘, 压图显示。一屏因此多放一整行(实测 6 行 → 缩略图 187×187、日期分组间距压到 2px)。 网格视图的文件名仍是常驻,未受影响。

删除有两条路,互不干扰:

  • 按类别整批删:每个类别标签带一枚删除键 —— 点它,标签原地变成「删除 N 项?不可恢复 确认 / 取消」,确认后把这个类别下的条目连同磁盘文件一起删掉(不可恢复,所以有 这一步二次确认;按 Esc 或点别处即取消,几秒不操作也会自动收回)。
  • 单条删(2026-10-24):鼠标移到任意卡片上,右上角浮现一枚垃圾桶 —— 第一次点变成红底「删除?」,再点一次才真删。键盘用户聚焦卡片后按 Delete / Backspace 走同一套两拍语义。确认态没有自动回退(视线就在这张卡上,自动收回 反而像静默失败),取消出口是点别处 / Esc。

两条路都先播淡出退场再发请求;若有个别文件删不掉(被占用、已不存在),它们会留在 画廊里并给出原因。删除只作用于画廊索引里的普通文件,符号链接与目录一律拒删,也不会 连带删除目录内容。

画廊工作台

图库缩略图是真实首页位图而不是类型图标。PDF / Office 由插件的侧栏渲染器接管 (官方内嵌 pdf.js 对带 /ID 的 PDF 会崩),音视频由插件的媒体渲染器接管 —— 官方把 mp4 / mp3 这类后缀列为「没有渲染器」,不接管的话侧栏只会说一句「不支持预览」。

档案

「不管工作区关不关记忆,它都会自动记录并自动注入」——这是档案与记忆最根本的区别。 记忆库的两道闸门(按工作区「记录 / 不注入」)对档案完全不生效:档案有自己的 autoRecord / autoInject 开关,默认全开,且注入以 prepend 优先级注册, 跑在记忆库那两道闸门之前。

一页管完七类结构化信息:账号密码 / 邮箱与登录 / 支付方式 / 联系方式 / 证件 / 配置文件 / 其它,分组可改名、可加自定义分组。页面是「概览 + 场景切换 + 记录卡片网格」 的形态:顶部 Hero 给记录 / 场景 / 照片 / 占用的四格统计,下面一排场景胶囊切换视图, 每条记录是一张卡片,点开在卡片下方原地展开详情(不遮页面、不滑抽屉)。深色模式 页面底固定 #151517。每条记录是一个实体(一个站点账号、一张卡、一份证件),字段级就地 更新——「密码从 A 变成 B」是一次字段 patch 加一行历史,不是一个追加句子。证件这类 场景可以挂图片(正面 / 反面 / 补充),浏览器侧压成三档再传上来。

四条不变量(改代码时不要破坏)

# 不变量 为什么
1 敏感值落盘必加密(密文带 secret:v1: 前缀,AES-GCM,密钥是 key 文件里的 32 字节随机数、权限 0600) 任何备份 / 同步 / 误打包都等于明文泄露
2 敏感值绝不进上下文:注入里只有「密码:已加密」,连长度都不给 注入内容会发给模型厂商;长度本身能缩小暴力空间
3 列表响应里敏感值是掩码(八个星号),明文只经 POST /reveal 单字段取一次并写审计 否则「打开档案页」就等于「整库明文进浏览器内存」
4 提交回来的掩码不会被当成新值 一次保存就把真密码写成六个星号,且不可逆

模型那侧默认可见:vault_search / vault_get / vault_remember 三个查询写入工具, 外加默认开启的 vault_ssh / vault_http 两个内置凭据动作。所有工具都拿不到密码明文。

内置凭据动作:能用,但看不见

密码锁在档案里、模型拿不到,很多事就帮不上忙。档案内置了两个默认开启、零配置的动作:

  • vault_ssh:用记录里的 SSH 密码或私钥,登录记录自己的主机执行远程命令; 主机 / 用户名 / 端口只能从记录字段推导,模型不能另行指定。
  • vault_http:用记录里的 API Key / Web Key 请求记录自己的 URL; 目标域名必须落在记录 URL 字段的域名内,凭据只放进指定请求头。

不需要装 sshpass、不需要配置程序白名单;不想用就在「操作间 → 档案 → 代用」里取消 「允许内置 SSH / HTTP 动作」。密码只在本机用于 SSH 握手 / 请求头,返回值里的原文、 URL 编码、base64、hex 变体一律替换成 ********;每次代用写 audit.jsonl,可在同一 面板里看最近的记录。

⚠ 边界:vault_ssh 目前信任记录里的主机(不做 known_hosts 校验),首次连接陌生 主机前请自己确认地址;vault_http 只允许访问记录自己的 URL 域名,防止密钥被发到别处。

谁来写:主 agent 当场写,后台抽取兜底

2026-10-26 起写入以主 agent 自己调 vault_remember 为主:用户这一轮说出新的账号 / 密码 / 证件信息,模型当场写进去,当轮即时生效(用户下一句就能用)。turn/end 的 后台抽取退为兜底——模型漏调时仍能捡到,不会丢数据。两条路写同一份 store,靠 「场景 + 归一化标题」去重、patchField 的「值没变就不写」保证重复写幂等,不需要额外互斥。

这么改的原因很实际:抽取是回合结束之后跑的,写入永远滞后一轮。原来的注入规则 甚至明说「正常对话即可,回合结束会自动抽取入库」,等于劝住模型别自己写——于是用户 刚给完密码紧接着说「那你去登一下」,模型手上还是旧值。

自动抽取的结果也直接生效,没有「待确认」环节。 写进去是什么样,模型下一轮就读到 什么样:记录上没有「待确认」状态、页面没有确认 / 驳回按钮、也没有 POST /confirm 路由。

这里原本有一套「抽取结果挂 pending、等用户点确认/驳回才算数」的机制,2026-10-27 用户 明确要求档案不需要确认,整套退役(VAULT_SCHEMA_VERSION 1→2)。理由是那道闸门 同样把「当场写、当场用」拖住了:模型明明在会话里听见「密码改了」,档案里却还是旧值、 页面还堆着一栏待确认。现在护栏只剩三条——update 只改模型明确点名的字段、create 先按 「场景 + 归一化标题」查重、patchField 值没变就不写——改错了也只错在那一个字段上, 下一轮抽取还能改回来。老库里已经挂着的 pending 标记由 migrate 的 v1→v2 一级 主动删掉(不是「从此不再写」),否则会留下一条永远挂着、页面上却没有入口能处置的 假状态。

⚠ 这条路上绕不开的一件事:模型自写意味着明文要经过模型厂商一次。官方的 tool/call 事件保存的是模型产出的原始 arguments JSON 串(dsh-session 类型里原话: the raw arguments JSON string exactly as the model produced it),而 ToolCallBlock 属于 ContentBlockMap,会随 assistant message 在后续每一轮重发给 provider。所以明文 与其他对话内容同级别地走了厂商、并留在会话历史里。后台抽取同样要发给模型才能抽出 字段(只是不落在主会话历史里)。两条路都做不到「模型不看见明文」——真正的零暴露 只有用户自己在页面手录。代码里因此把「参数里传一次是必须的,但不要在回复正文里 复述」写成硬纪律,把凭空多出来的那一份副本消掉。

存储与占用

<DSH_HOME>/memories/dsh-chat-vault/
  vault.json            记录 + 分组(几 KB~几百 KB,原子写 tmp+rename)
  key                   32 字节 AES-256 密钥(0600)
  blobs/<2位>/<sha256>  图片与文件字节,内容寻址:同样的字节只存一份(照片压三档、文件原样存)
  blobs/index.json      blob 索引 + 引用计数
  revisions/<日期>.json 每日快照(「回到昨天」)
  audit.jsonl           敏感字段明文查看 / 凭据代用审计

占用的三档压缩实测(同一张 2.1MB 原图,JPEG q75):

| 档位 | 长边 | 体积 | 占比 | 用在哪 | |---|---|---|---| | thumb | 512 | 36KB | 1.7% | 列表 / 网格 | | display | 1600 | 270KB | 12% | 详情 / 灯箱 | | original | 原样 | 2100KB | 100% | 默认不存(keepOriginal) |

配额默认 512MB,超出时按 original → display 顺序淘汰,缩略图和记录文本永不删 (照片全没了,账号密码也还在——这是「有损但不会丢命」的方向)。

同步设置

把工作台资产推到自己的 GitHub 私库,换机一键拉回来。端侧加密(scrypt + AES-256-GCM), 离开本机的只有密文;同步口令与 GitHub 令牌用本机硬件密钥加密落盘,插件里不出现任何 私库名或 Token(公开仓库会被硬拦,不是提示)。

同步范围五项,各自可关:

资产 收的是什么
档案 vault.json + 主密钥 + 图片 / 文件 blob(账号密码、卡、证件、订阅配置)
长期记忆 entries.json(schema v6:每条带 owner 归属,work 与 bot:<id> 的记忆一起走)、状态元数据、各工作区 meta.json
自定义技能 ~/.dsh/skills/ 下的技能包与 SKILL.md(技能开关写在 frontmatter 里,跟着走)
MCP 配置 cordis.patch.yml 里的 mcp-client 条目(服务配置 + disabled 开关)
供应商与密钥 cordis.patch.yml 里的 llm-pi-ai / llm-deepseek / agent-default-model、~/.dsh/.credentials.yaml 里的 API 密钥 refs、model-capabilities.json(模型能力)与 network-proxy.json(网络代理),以及实时语音的 Live 供应商与通话默认(~/.dsh/storages/dsh-seeker-bot/personal-agent/realtime-config.json)

MCP 与供应商配置均为文本级增量读写:注释、其它插件条目与行尾风格原样保留。拉取时同名条目/同名 Key 覆盖、本机缺失的新增,本机独有的条目与私有 API 密钥绝不会因为云端包而丢失;写前自动备份 *.bak-before-sync。实时语音的 Live 供应商按 id 合并(同名覆盖 / 缺失新增 / 本机独有保留),通话默认(默认供应商、音色、语速、网页搜索开关)随包一并恢复。条目与凭据均在端侧强加密后离开本机。

包格式 v2 才带 Bot 归属分布与 Live 供应商这两段;读旧包(v1)照常恢复,只是少了这两块。

—/ 5

No ratings yet

Verified DSH bundle

Commit 77c811b0d28a

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout