DSH Plugins Marketplace

DSH Plugins

Plugins

/

UI & Experience

/

dsh-small-talk

T

dsh-small-talk

Manifest valid★ 1

一个用于deepseek harness网页版的侧边助手:当你想知道任务进行的怎么样但是又无法暂停时,侧边助手可以读取主对话的思维链并且告诉你现在正在发生什么。 A side assistant for the DeepSeek Harness web version: when you want to know how a task is progressing but can't pause it, the side assistant can read the main conversation's chain of thought and tell you what is currently happening.

UI (client)hasBundlePatch

dsh-small-talk

English · 中文 · 仓库 · AGPL-3.0

给当前会话一键开一个常驻侧边助手:它知道这个会话此前聊了什么(以一段压缩摘要的形式,不进它的聊天记录), 可以反复追问,不会打断主任务; 面板顶部是一条实时进展条,默认只占一行——主 Agent 在跑什么、想到了什么,最新一句摘要直接看得见,展开能回看最近几步的思维链。

主 Agent 之后每跑完一步,侧助手都会收到一块进展(这一步的想法、调了什么工具、得出了什么)——所以它不再只知道创建那一刻的历史: 你问「它现在在干什么」,侧助手答的是最新一块。块走的是系统提示,不进聊天记录(见「进展条」一节)。

按钮长在会话标题栏右侧。按一下,右侧栏里就出现一个独立的对话面板: 它知道你这个会话之前聊过什么、干过什么,之后你可以一直问它,主 Agent 继续跑自己的。 已经有侧助手时,按一下会先弹出选择菜单:助手 1 / 助手 2 / … / 新建助手—— 可以换一个重开,也可以再开一个(各自独立,各自拿到同一份历史摘要)。

侧助手不是子智能体:它不出现在子智能体目录、不参与子智能体计数、不占 continuable 名额, 也从会话列表里缺席。它对「子智能体系统」唯一可见的痕迹是会话头上的一行血统信息(见「工作原理」)。


它做什么

需求落点
侧边对话面板 + 输入框 + 消息流由本插件自己的标签页承载,聊天本体直接选 DSH 的 conversation.content 工厂(embedded 变体)
看主 Agent 的实时进展(工具调用 + 思维链)面板顶部自绘进展条,session.follow 直连主会话的事件流与 assistant stream
侧助手也知道之后发生了什么主 Agent 每完成一个 step,宿主把这一步折成一块写进侧助手的系统提示片段(side-assistant:progress),不进聊天记录
知道主会话已完成的历史建会话时把主会话已完成回合压成一段摘要,作为系统提示片段 side-assistant:history(order 120)——它自己的日志从开场白开始,侧栏里看不到主对话
可反复追问(持久化侧助手)会话自带 subagent/descriptor,每次提问按需冷恢复,记录一直留着
一个都不占子智能体名额用 ctx.agents.create() 建游离会话,不追加 subagent/catalog 事实
关掉再开不丢侧栏按会话把打开的标签页存进 localStorage
一个会话多个侧助手、换一个重开按一下弹菜单:已有的按创建顺序编号,末尾另有「新建助手」
回答下面两颗快捷卡片conversation.input.dock 里挂两颗圆角矩形卡片:这个项目是在干什么? / agent 正在进行什么工作?。点一下等于自己打出来发出去(conversation.send,和输入框同一条路)

它不做什么(有意为之)

  • 不重新实现聊天界面。 消息流、Markdown、工具调用、流式输出、Stop、向上翻页都来自 @deepseek-ai/dsh-client-ui-conversation 的 conversation.content 工厂(embedded 变体); 本插件只加自己的标签页类型、资源提供方和那条进展条。自己画一套只会更差。 相位固定传 active:游离会话在客户端没有可判的元数据,交给工厂自己判会停在空壳上。
  • 不把自己登记成子智能体。 不走 ctx.subagents.startContinuable(),不往父会话追加 subagent/catalog;parentSession 只是血统字段(工作区分组 + 侧栏寻址),不代表运行时父子关系 (创建时不传 parentAgent)。子智能体目录、计数、会话树、list_agents 全部只读那份目录事实。
  • 不轮询主会话。 进展条用的是框架自己的推送:session.follow(持久事件 + assistantStream 增量), 没有自建 /status、没有定时器。
  • 不往侧助手的聊天记录里塞“进展”。 块走系统提示片段,不走 user / assistant 消息: 聊天记录里只有你和它的对话,翻历史时不会被一堆系统播报淹掉。
  • 不往会话流里塞插件私有事件。 唯二写进日志的是框架自己的两个协议事件: subagent/descriptor(这个会话是什么)与创建窗口内的沙箱/审批策略覆盖(与旧版委派路径一致)。

安装

profile 是 web 的话,直接从仓库安装:

dsh plugin --profile web add https://github.com/TPSCE/dsh-small-talk.git

不想让 pnpm 走 git clone(网络慢、或者本机没装 git)就用 tarball,一次 HTTPS 下载:

dsh plugin --profile web add https://github.com/TPSCE/dsh-small-talk/archive/refs/heads/main.tar.gz

装完刷新页面;如果按钮没出现,重启 dsh web(Host 半区的新路由需要重新加载进程)。

卸载:

dsh plugin --profile web remove dsh-side-assistant

使用

  1. 打开任意会话。
  2. 点标题栏右侧的 侧边助手。
    • 一个侧助手都还没有:直接创建(读取历史摘要、发一句开场白),随即在右侧栏打开。
    • 已经有侧助手:先弹出菜单,列出 助手 1 / 助手 2 / …(按创建顺序)和 新建助手。 选其中一个 = 把那个重开到右侧栏(各自保留自己的对话记录);选「新建助手」= 再开一个。
    • 收起菜单:菜单外点一下、按 Esc、或者再点一次按钮。收起菜单不会创建任何东西。
  3. 面板顶部是一条进展条,默认折叠成一行(一个小圆点 + 「主 Agent 进展」+ 最新一句思考): 展开后是主 Agent 的工具调用、工具结果,以及每个 step 各自的思维链——正在写的标亮,跑完的变灰留着, 所以一轮跑完再点开,最近几步想过什么还在。中途打开面板也会从当前 step 的开头补齐。 折叠只是显示——它照样把主会话的流订阅着,圆点该闪还是闪;展开/折叠的选择在本次页面里一直记着。
  4. 在下方输入框里正常提问:它起手一行高,写满后像主对话一样逐行长高。可以一直问,也可以按 Stop 打断正在生成的回答。
  5. 不要了:再点一次按钮,在菜单里那个助手右边点 删除,再点一次 确认。它从菜单里消失,标签页随即关闭 (删掉的是「显示」——会话日志留在盘上,可从归档列表恢复,见「已知边界」第 6 条)。
  6. 关闭面板不销毁会话,再点按钮就回来了。

界面:侧栏只有一个输入框

复用自带的对话视图意味着连主对话的输入区一起继承——所有插件往输入区塞的东西都会出现在侧栏里。 client.js 里的 SIDEBAR_CSS 把侧栏面板裁回一个朴素的输入框,只在这个面板内生效:

裁掉谁带来的
conversation.composer.dock(N 轮 · M 步 · tok/s · 缓存命中)dsh-client-ui-chat 的 stats 统计条、dsh-cost-meter
conversation.input.dock(未提交的更改:N 个文件、额度条)dsh-client-ui-git-graph、dsh-cost-meter、@openmaic/dsh-openmaic
conversation.input.attachments(附件栏 / 拖放区)dsh-client-ui-attachment
conversation.input.left / .right(余额、插件杂项按钮)deepseek-harness-wallet、@linxin666/dsh-web-all 等
conversation.input.activity(横跨工具行的活动指示)对话外壳
输入区左侧整行(+ 上传/命令菜单 + 计划 + 权限选择)对话外壳

留下的是:消息流、输入框、模型选择、发送/Stop,以及每条回复下的复制 / 用量 / 时间。

想调整就改 client.js 顶部的 TRIMMED_SLOTS 数组(每个槽一行,删掉哪行哪样东西回来); 只想藏 + 而保留「计划 / 权限」两个座位,就删掉 SIDEBAR_CSS 里最后那条 div:has(> input[type="file"]) 规则。

裁剪规则选的是 [data-sidebar-chat]。这个属性以前由自带的 subagent 标签页打上, 现在由本插件自己的面板根节点打上(chatTab 的根 div),所以规则依然只在侧栏面板内生效。

消息动作行

助手回复下面那排按钮不在槽里,是聊天视图自己画的固定条: 复制 → conversation.chat.assistant-actions 座位 → 分支 → 用量 + 时间。只留复制、用量、时间:

裁掉怎么认得它
点赞 / 踩(dsh-client-ui-message-feedback)、在上下文浏览器中查看(dsh-context)两者都注册进 conversation.chat.assistant-actions 座位,藏掉座位一次全消
分支按钮助手尾部这一行的最后一个直接子元素按钮

分支那条规则带了 [data-actions-reveal] 限定:这个属性只有助手轮次尾部会写, 而用户气泡那一排的最后一个按钮正是复制——不限定就会顺手把用户消息的复制也藏掉。 用量按钮在 span.endInfo 里,不是这一行的直接子元素,所以 > button:last-of-type 够不到它。

输入框:起手一行,之后照抄主对话

侧栏输入框起手只有一行高(24px,正好是自带输入区的行高),主对话那份是 36px——没写满第一行时一眼分得开。 写满之后它和主对话完全一样:每多一行长 24px,长到自带的上限(336px,约 14 行)才开始在框内滚动。

只动了两处,都是「下限」而不是「上限」:

规则作用
[data-composer-input] 的 min-height: 24px + padding-top: 0一行的行高就是 24px,所以这就是「一行」的起手高度;之后每换一行自然 +24
[data-composer-placeholder] 的 inset 上边归零提示语是绝对定位的,跟着编辑区的新内边距走,否则会飘到文字下面

没有设 max-height、没有给编辑区挂 overflow、也没有重声明 --dsh-composer-text-max-height—— 这三样任何一个都会把「第二行」变成滚动而不是长高:光标在第二行,滚动跟着光标走,第一行就被挤出那个没变高的盒子, 看起来像「第一行消失了,第二行顶到第一行的位置」。这个坑 0.9.1 踩过一次,测试里现在反过来钉死这三条不许出现。

卡片的上内边距与间距顺手收窄(8→6、12→6);下面那排按钮(模型选择、发送 / Stop)不动——它们不是本插件的东西。 想完全照主对话(36px 起手),删掉 min-height 与 inset 这两条即可。

运行中只有一个按钮

主对话在跑的时候,那颗主按钮会变成 Stop。侧栏用的是子会话,自带输入区不做这个替换 (primaryStops 要求 subagent === null),而是在 Send 旁边额外长出一颗 Stop (interruptible = running && continuable)。消息一提交草稿就清空,Send 便停在禁用态—— 于是侧栏会出现「Stop + 一颗灰色的 Send」,看着像坏了一颗。

SIDEBAR_CSS 只在这一种状态下藏掉后面那颗:行尾容器里出现两颗相邻按钮、且最后一颗是 [disabled] 时才隐藏; 一旦你开始打字、Send 重新可用,规则立刻失效,按钮自己回来。判据只用属性选择器与行结构,不依赖构建期哈希类名; 自带 DOM 若改成嵌套结构,这条只是不再匹配,不会误藏别的按钮。

输入框提示语

提示语是真实渲染出来的文本节点([data-composer-placeholder],与可编辑区同级),CSS 换不掉, 所以做法是:把原文字号压成 0 藏起来,再用 ::before 画上自己的。

ask me anything you wanna know~

只有输入框可用时才替换。不可用 / 离线 / 被拦截时,原提示语是状态而不是装饰, 那几条规则会把它放回来——判据是同一父节点里可编辑区的 aria-disabled="true"。 无障碍名称仍来自原本的 aria-label,读屏听到的还是官方那句(含 /、@ 的提示)。

为什么用 CSS 而不是接管 conversation.composer 链。 那条链是官方的「替换本会话输入区」扩展点, 但它的 select 只拿到 Session:一旦接管,子会话在主视图里被打开时输入区也会跟着变; 而且接管会把常驻输入区(它持有待处理的审批交互和 Stop 按钮)藏起来。CSS 只动观感,不动功能。

为什么不在 locale 里改提示语。 那会连主对话的输入框一起改,而主对话正需要 / 指令和 @ 文件的提示;侧栏要的是「这是聊天」的口气,不是全局换文案。

开场消息:行为不走对话框

新建侧助手时 Host 会投递一条 hello。它不是提示词,只是点火:DSH 用首条消息打开会话的第一轮。

侧助手的行为走的是会话的 agent 组合,不经过对话框:

行为载体落在哪
persona这个会话的 scoped deployment:persona-prefix 段,只覆盖它自己
denyTools这个会话的 scoped tools.restrict():工具既从提示里消失,也不可调用
reasoningEffort创建时的 agentOptions,并随 subagent/descriptor 持久化

这样面板读起来就是「一个助手,不是用户塞了一段提示词」。代价是那句 hello 仍是一条真实的 user 消息,所以它会在面板里显示成一个用户气泡——见「已知边界」第 11 条。 不想要它就配 opener: "":会话会开在一个空对话上,等你先说话。

面板不等这句开场白。 /side-assistant/ensure 在会话建出来、问候投递出去之后就应答, 问候那一轮在后台跑完才落盘、释放 agent。所以「按下按钮 → 面板出现」之间没有模型往返: 面板先开,回复随后流进来,这期间你已经可以输入(消息排在问候之后)。

点开之前就已经准备好了

侧边助手不是「点一下才开始做」——面板里那个按钮出现在会话标题栏之后,页面会先安静半秒, 然后向宿主请求把这个会话的侧助手提前建好(POST /side-assistant/preload)。 点开时只剩一次查表:会话已经在列表里,面板直接进对话。

它挑的是便宜的做法:

  • 已经有侧助手就复用,不重建——所以反复切换、反复重开都不会越建越多。
  • 只在你真的在的会话里做。 请求里只有一个 sessionId,宿主先看这个会话有没有驻留 agent; 没有就跳过(skipped: "parent-not-live")——那种会话本来就建不了侧助手,也不该建。
  • 半秒防抖。 只是路过一个会话不该留下一个助手。
  • 开场白照样在后台跑,不等它;面板开的时候你可能已经能打字了。

代价是实打实的:每个你停留过的会话,提前建好一个侧助手(一份会话日志 + 一轮问候的模型调用)。 不想要这笔开销就配 preload: false,回到「点了才建」。


进展条:主 Agent 在想什么

面板顶部那条是只读的观察窗,数据全部来自框架自己的会话流:

看到什么从哪来
工具名 + 参数摘要、工具结果(错误标红)主会话的 tool/call / tool/result 持久事件
每个 step 的思维链(流着的是 live,跑完的标灰保留)assistantStream 的 reasoning-delta,以及 assistant/message 里的 stream
本回合结束turn/start / turn/end
小圆点状态:待命 / 进行中 / 读不到流是否活着

思维链是留下来的,不是闪一下就没。 主 Agent 每个 step 的思考单独成块:正在写的那块标 live(左边框高亮), 这个 step 结束就转成 done(变灰保留),下一个 step 不会把它冲掉——所以跑完一轮再点开,最近几步想过什么还在。 两块补看是自动的:

  • 面板是中途打开的:session.follow 的快照帧带 assistantStream.activeAttempt,也就是"这个 attempt 到现在 已经流出了什么"的压缩前缀。打开时把它展开、按 nextIndex 截到已发布的部分,从 step 开头补齐,之后接增量—— 不是你打开那一刻才开始记。
  • 面板打开时某步已经结束:该步的 assistant/message 持久事件里带着整段 stream,从里面还原 reasoning。 快照里的历史事件也走这条路,所以刚打开面板就能看到之前几步的思考。

上限:思维链最多留 6 块(每块 4000 字,超了从前面截断,保留结论),工具行 30 行;块与行各有各的预算, 工具多的一轮不会把思考挤出去。展开时自动跟到底部,但你手动往上滚之后就停止自动跟随,不会跟你抢滚动条。

默认折叠。 展开态的子任务行 + 思维链有几十行高,而它们和你在主界面看到的是同一份东西: 用户在主视图里看着主 Agent 跑,侧栏再重复一遍只是把聊天的位置挤掉。所以面板打开时它只有一行—— 一个状态圆点、主 Agent 进展、最新一句思考的摘要(折叠也能看见它在想什么)、一个折叠箭头, 右端还有一个折叠箭头——需要时点开,选择在本次页面生命周期内记住(client.js 的 progressOpen:换会话、重新挂载都不丢,刷新页面回到折叠)。

折叠的是显示,不是订阅:session.follow 照样连着,圆点该闪还是闪,读不到时那一行也会显示 「进展读不到,看看控制台」,所以折叠状态下也不会漏掉异常。

实现是 ctx.remote.session.follow({ "address": { kind: "session", sessionId: 主会话 }, assistantStream: true }): 一次订阅拿到快照(已经发生过的事件 + 正在跑的 attempt 的压缩前缀)+ 之后的每条增量,面板关掉即断开。 压缩流记录的展开(reasoning-chunks → 逐条 delta)在插件里本地实现了一份,因为框架的那个 helper 不在浏览器半区可 require 的面里。

这些块也会送到侧助手那里,但走系统提示,不走聊天记录。 主 Agent 每完成一个 step,宿主就把这一步折成一块 (结论 + 工具调用 + 想法),挂进侧助手会话的系统提示片段 side-assistant:progress(order 125,紧跟委派声明)。 每块第一行是一句话结论:取这一步自己收尾文字的第一句;只动了工具、没有文字时,结论就是「Conclusion: this step only used tools (read, bash) …」 这样的动作概述。后面才是工具动作,想法原文放在最后——先给结论,再给细节,所以问「现在在干什么」时, 它答的是一句话,而不是一段思维链。手上同时有几块时,按由早到晚的顺序逐块讲(每块一句话),最后落在最新一块, 让人看到的是「它先做了什么、接着做了什么、现在到哪了」,而不是一团并行的碎块。块不进日志,面板里仍然只有你和它之间的对话, 历史摘要也仍然停在创建那一刻(见「已知边界」第 1 条)。

上限与代价:最多 4 块(每块想法 200 字、结论 120 字、工具 3 条 × 60 字,超了从最早的一块滚掉)。 一份完整注入通常只有几百字——刻意的:注入太长会挤掉你真正想聊的内容,也让「一眼看清」变成读报告。 块一变,侧助手的系统提示就变一次,提示词缓存会跟着失效——这是刻意的取舍:实时进展比缓存命中重要。 片段文本是 getter,每次拼提示词时重算,所以用户提问之后、模型开口之前刚落下的那一步也在里面。

删除助手:在菜单里,先归档再关标签页

删除按钮跟着它要删的东西走:再点一次头部按钮打开菜单,每个「助手 N」右边就是一颗 删除——两个字, 吃的是菜单行的宽度,不占进展条,也不用记「右端那颗按钮删的是谁」。文案跟随系统语言(zh 是 删除, en 是 Delete)。

两段式:按下变成 确认,再按一下才动手;四秒内不按第二下就自己放弃(一次误点不会在半小时后被「补完」)。 删除走的是框架的工作区归档(workspaces.archiveSession,带 stopActivity):

  • 从菜单里消失。 宿主列菜单时把归档集合一起减掉,所以删掉的不会被 ensure({}) 重新打开, 下次点按钮是新建一个;剩下的行重新编号(助手 1、助手 2……)。
  • 标签页关闭。 菜单只知道子会话 id,不知道它开在哪个标签页上——标签页 id 是停靠区发的。 所以面板自己在挂载时把这对关系记进 client.js 的 openTabs,卸载时忘掉;删除时查这张表, 当前打开的正是它就把标签页关掉(否则留下的只会是个空壳)。
  • 日志还在。 归档不是擦除:会话记录留在盘上,可以从工作区的归档列表恢复(恢复之后菜单里又会出现它, 因为它重新变回「游离血统 + 当前组合 + 未编目」)。
  • 失败会说话。 归档失败时那一行变成 删除失败,行和标签页都不动,再点一次即可重试(细节在控制台)。

刻意没做的:没有批量删除、没有二次弹窗(确认写在按钮自己身上,省一次点击)、也没有用它替换 sidebar.right.tab.menu.item 槽——那个槽只在标签页自己的下拉菜单里出现,而这个面板的标签页头在折叠栏里, 藏得太深;菜单里那一行是用户本来就要打开的地方。


回答下面的快捷卡片

最后一条回答下面、输入框上面,有两颗圆角矩形卡片:

  • 这个项目是在干什么?
  • agent 正在进行什么工作?

点一下就把这句话发出去——走的是 conversation.send(),和用户在输入框里按发送是同一条路径, 所以会出现一条正常的用户气泡,接着是一轮正常的回答(正在跑的时候进队列,等这一轮结束再发)。

它们挂在会话自己的 conversation.input.dock 槽位上(队列条、todo 条也在这里),所以位置天然是「回答下面、输入框上面」。 槽位本身是全会话共享的:同样的入口会出现在每一个对话视图里,所以两条 CSS 规则把默认态设成 display: none, 只有祖先带 [data-sidebar-chat](也就是本插件的面板)时才 display: flex —— 主对话那边一点痕迹都没有。

这个槽位原来整块被输入区精简规则藏掉了(它同一条里还带着 git 分支行、额度条、队列条)。0.11.2 起改成 只藏别人的子节点:[data-sidebar-chat] [data-slot="conversation.input.dock"] > *:not(:has(.dsh-sa-cards)), 卡片自己是这个锚点的直接子节点,所以照常显示,其余条目(git 行、额度、队列、todo)仍不出现在面板里。

配置

cordis.patch.yml 里的 config 会原样传给 apply(ctx, config),每个键都可省略:

键默认说明
label侧边助手菜单里的标签,同时写进会话的描述符(不再是识别键,见「工作原理」)
openerhello打开新侧助手第一轮的那条消息。只用来开场,别放指令——DSH 把它当普通用户消息投递,指令写在这里就等于「用户自己把提示词敲进了输入框」。给空字符串 = 不打招呼
persona侧助手行为规则(英文,规则里写明用用户的语言回答)侧助手的行为在这里:作为 scoped persona 前缀注入会话;不要写 {{…}},会被当模板插值
denyTools[send_message]从会话组合里删掉的工具。删掉的工具既不在它的提示里也不可调用。给空数组 = 不限制
reasoningEffortoff侧助手的思考档位。默认关掉(侧问是你在等的,不开思考模式);填 low / high / max 打开,填 inherit = 完全不传,继承主会话的路由
preloadtrue你在某个会话里停留半秒后就提前把它的侧助手建好,把「复制历史」那一步挪到点开之前(见「点开之前就已经准备好了」)。设 false 关掉

例:

- insert:
    - id: side-assistant
      name: dsh-side-assistant
      config:
        label: 我的助手
        reasoningEffort: low   # 想让它带思考,就这样打开
        denyTools: [send_message, shell]

工作原理

两半区,一个入口。

Host  index.js                             浏览器  client.js
  POST /side-assistant/list                  标题栏按钮(order -15)
    ├─ sessionQuery.listSessions()             按一下 → POST /list
    │    筛 parentSession 是本会话 + origin=subagent    ├─ 空列表 → POST /ensure {sessionId}
    └─ 减去 subagents.listChildren()(真正的委派)      └─ 非空   → 弹菜单
       ⇒ 剩下的才是游离侧助手                             助手 i → /ensure {sessionId, childId}
        按创建时间从旧到新                               新建助手 → /ensure {sessionId, create:true}
  POST /side-assistant/ensure                                    ↓
    ├─ { sessionId }  当前那个       └─ create 时:        sidebarRight.openResource(
    ├─ { childId }    指定重开          agents.create({        dsh-resource://sideassistant/chat/
    ├─ { create:true } 一定新建           没有 parentAgent,     <child>?parent=<parent>)
    └─ 其余:复用最新那个                  meta.parentSession,      ↓
       → { childId, created }         history: 历史摘要    自己的标签页 = 进展条
                                          setup: 描述符 + 组合  + 自带对话视图
                                        })
  • 创建必须走 Host。 Client 的 subagents 远程命名空间只暴露 prompt 与 interruptByParent,没有创建。
  • agents.create() 不传 parentAgent ⇒ 运行时它是个 root agent。 这不是偷懒,是需求: 子智能体的身份来自父会话日志里的 subagent/catalog 事实(listChildren / 计数 / 会话树 / list_agents 全读它)。不追加那条事实,就没有任何地方会把它算成子智能体。
  • 为什么还留着 origin: 'subagent' + parentSession。 侧栏聊天要能寻址和驱动这个会话: 地址校验要求 origin === 'subagent' 且 parentSession 与地址里的一致, 并要求这个会话自己的日志里有 subagent/descriptor;送消息时的冷恢复也只读那份描述符 (coldResume():mode 必须是 continuable,其余从描述符重建)。 也就是说:这两个字段是给侧栏用的接口,不是给子智能体系统用的登记。
  • setup 是创建窗口。 agents.create() 的 setup(childCtx, child) 在会话与 agent 被插入、发布之前跑, 框架自己也是在这里 append 描述符的。这里做四件事:写 subagent/descriptor(v3:version / mode / provider / label / agentProvider / agentModel / agentReasoningEffort / persona / toolFilter,未知键会抛)、 继承沙箱与审批策略(和旧版委派路径一样把审批钉成 never)、 agentPresets.composeFrom() 加入父会话的组合、scoped persona 段 + tools.restrict()。
  • 收尾:先落盘,再 dispose()。 AgentHandle.dispose() 只把会话从运行时注销并停掉循环, 不动它的日志;所以创建完 sessions.flush() 再 dispose,之后每次提问都走冷恢复。 (卸载插件时不能 dispose:那会连会话一起删掉。)
  • 发现靠「相减」。 sessionQuery.listSessions() 里凡是 parentSession 指向本会话且 origin === 'subagent' 的,减去 subagents.listChildren() 里有的——剩下的就是游离侧助手; 再减去工作区归档集合(workspaceRegistry.archivedSessionIds)里有的,删掉的才不会回来。 所以 label 不再是识别键(0.6 之前靠它,改名等于换一个;现在改名只改显示)。 另有一道时间闸 COMPOSITION_SINCE(index.js 里的常量),比它早建的一律不算—— 包括 0.7 之前那版真子智能体侧助手,它们大多也在目录里,两道闸都拦得住。
  • 名额。 不再占 continuable 名额(那是激活表的事,激活表只由委派路径填充)。 唯一还会碰到上限的是提问那一轮:冷恢复要临时占一个激活位(框架的 maxActiveSubagents,默认 8), 占满时报错由框架给出(subagent limit reached …),面板里会看到那条错误。
  • 地址是契约,不是字符串拼接。 本插件有自己的协议与标签页类型 (sideassistant / dsh-resource://sideassistant/chat/<child>?parent=<parent>), 与自带 subagentchat 语法互斥:两个语法解析对方地址都必须失败,谁也不会抢走谁的标签页。 test/address.contract.mjs 会把这条契约连同 glob、编码、拒收样例一起校验。
  • 进展块怎么到模型手里。 宿主订阅主会话的 session/event(tool/call / tool/result / assistant/message), 每个完成的 step 折成一块,写进侧助手会话的系统提示片段 side-assistant:progress。注册挂在 agent/created 上: 侧助手每次提问都是一次冷恢复(新的 agent 上下文),所以不能只在创建时注册一次。 块只按父会话 id 归属(parentSession 血统),别的会话的事件一律不进来。 谁会收块:新建的、菜单里点开的、以及 Host 启动时扫出来的那批(adoptExisting(),判据与菜单相同: 游离血统 + 当前组合 + 不在父会话的委派目录里)——所以重启之后直接往恢复出来的标签页里打字, 照样能拿到重启之后攒的块。
  • 历史摘要也挂在这个钩子上。 冷恢复时框架只按描述符重建 persona 与 toolFilter,创建窗口里注册的 提示配置会随 agent 上下文一起丢掉,所以摘要(side-assistant:history,order 120,在进展块之前)不是创建时注册的, 而是和块一样在 agent/created 时挂上去——否则用户问第一句话时就「失忆」了。它是静态文本(创建时的快照, 和块的动态 getter 不同),随记录存在宿主内存里的 progress 表上。
  • 思考模式默认关(reasoningEffort: "off")。 侧问是用户在那儿等着的,它也不必规划任务; 关掉能省掉整段思考的生成时间,第一句话(和每次问候)都更快。只覆盖 effort、不传 provider/model (框架的 resolveChildAgentOptions() 有一条:换了路由却没点名 effort 会丢掉继承来的 effort)。 这个值同时写进描述符(agentReasoningEffort),所以冷恢复也不会掉回去;行配置里写 low / high / max 就打开,写 inherit 则完全不动主会话的路由。

已知边界

  1. 历史是摘要,进展是流。 侧助手不再复制主会话的聊天记录:建会话时把主会话到最后一个 turn/end 为止的内容 压成一段摘要(用户问句 + 主 Agent 的回答,每条截断、整体有字数上限,最近的优先),作为系统提示片段 side-assistant:history 交给它。所以侧栏打开是一段干净的对话,而它仍知道你们聊过什么——但摘要是压缩过的, 它不该假装记得逐字原文。那之后的进展按块推进系统提示(见「进展条」)。
  2. 主会话里正在跑的那一轮不进摘要。 未闭合的回合没有结论可摘,那一段由进展块负责。
  3. 没有任何已完成回合的会话也能建侧助手,只是它从零摘要开始(不报错)。
  4. 一个主会话可以有多个侧助手,它们只靠「血统 + 未编目」识别,不靠 label。 菜单序号是创建顺序;删掉中间某个之后,后面的一起前移一位(删掉的那个不再进列表)。 真正的委派子智能体不会被误认——它们在目录里,第一道闸就筛掉了。
  5. persona / denyTools / reasoningEffort 是创建时定死的。 它们进的是会话的 agent 组合, 事后改不了(描述符虽然落在日志里、理论上可读,但插件不打算依赖它的 schema 稳定)。 改完配置要让旧侧助手失效,把 index.js 里的 COMPOSITION_SINCE 推到新的时间点。
  6. 删除是归档,不是擦除。 菜单里每行右侧的 删除 调的是工作区的 archiveSession:会话从菜单与工作区列表里消失、 标签页随即关闭,但日志留在盘上,可以从归档列表恢复。它不进会话树,所以没有「父会话里清掉子智能体」那条路—— 恢复之后它重新算作「游离血统 + 当前组合 + 未编目」,菜单里又会出现。想彻底销毁日志就得手动删 DSH 会话存储里的记录。
  7. 依赖自带的对话视图与右侧栏。 聊天本体由 dsh-client-ui-conversation 的 conversation.content 工厂提供(dsh-client-ui-chat 画消息流),标签页由 ui-sidebar-right 提供; 禁用它们之后面板要么打不开,要么只剩下那条进展条。缺失时插件不会崩: 面板服务是用 ctx.inject 单独申请的,聊天区加载失败会显示一行「侧边对话加载失败」而不是空白。
  8. 不进会话列表。 origin === 'subagent' 的会话本来就被工作区列表过滤掉,这正是我们要的零污染。
  9. 输入区精简(以及快捷卡片的可见性)靠两个 DOM 契约:面板根节点上的 data-sidebar-chat(现在是本插件自己打的) 和槽位锚点 div[data-slot="<槽名>"]。前者失效只影响裁剪规则的命中范围, 后者是别人给的外部契约(渲染器设置,display:contents,DSH 自己的样式表也按它选元素), DSH 大版本升级时值得回看一眼;失效的表现是「某些控件又出现了」,不会报错、不会坏功能。 消息动作行与提示语同理,另外还依赖 data-actions-reveal(助手轮次尾部独有)、 data-composer-placeholder 与可编辑区的 aria-disabled——这几个都是 dsh-client-ui-chat / dsh-client-ui-conversation 的渲染细节,比槽名更容易变。失效的表现分别是「按钮又回来了」和 「提示语又变回官方那句」,两者都不影响功能。
  10. 提示语只在不可用 / 离线 / 被拦截时才让位。 计划模式、引导排队这两种状态也会被换成 「ask me anything you wanna know~」——它们不是错误态,但也不是这个面板要表达的重点;想让它们也保留原文, 把 SIDEBAR_CSS 里那两条 :has(> [data-placeholder][aria-disabled="true"]) 规则的条件放宽即可。
  11. 开场那句 hello 会在面板里显示成一个用户气泡。 它是真实的 user 消息(DSH 固定这么投递, 插件改不了角色),而面板渲染的就是这个会话的记录。现在它的日志从这句开场白开始(历史是摘要、不是种子), 「第一个用户气泡」就是它,按位置藏也是安全的;插件仍然默认不藏,不想要开场白就配 opener: ""。
  12. 思考档位的取值是适配器自有的。 DeepSeek 适配器是 off | low | high | max,插件不校验: 填了该路由不认的值,表现是建会话直接失败并带 UNSUPPORTED_REASONING_EFFORT, 这时改成 inherit 即可。想中途改已建好的侧助手,就用侧栏里的模型选择器(它带 effort 档位)。
  13. 菜单是「每次点都重新问一遍」,不缓存。 所以在别的标签页里新建/删掉侧助手之后, 下一次点按钮就能看到,不用刷新页面。代价是每次点击都发一次 POST /side-assistant/list; 它读的是会话索引 + 这一层目录,不读会话日志。
  14. 菜单从「已有 1 个」就开始弹,不是「2 个以上」。 你的原话是「已存在助手有一个以上时候生效」, 但按字面实现(>1)时只剩一个助手就再也点不出「新建助手」,等于少了一个入口; 所以这里把「新建助手」放在菜单最后、并在一个都没有时走直接创建。 想改成严格的 >1,把 client.js 里 if (rows.length === 0) 改成 if (rows.length < 2) 即可, 代价是恰好剩一个时无法再新建。
  15. 菜单与进展条都是自绘的,只用主题 token。 没有引用 @deepseek-ai/dsh-client-ui-primitives 的下拉组件(普通 JS 插件不许 require Harness Client 包),所以样式跟随 --dsw-* 变量, 不跟随组件库的行为细节。
  16. 审批策略在创建窗口里被钉成 never(与旧版委派路径一致的语义):侧助手不该替你做需要审批的事。 它确实会尝试时,得到的是一条拒绝,而不是一个悬在侧栏里没人点的审批提示。 想要它更放手,得改 index.js 里 capturePolicyOverrides() / appendPolicyOverrides()。
  17. 进展条只在面板开着时订阅,主会话被关掉或归档时它显示「读不到」而不会报错; 它也不订阅侧助手自己的会话(面板下方的聊天已经能看到那些)。 折叠只改显示:订阅不断,圆点与「读不到」那一行照样更新。 留存有上限:思维链 6 块 × 4000 字、工具行 30 行,更早的会滚掉;面板重建后靠"快照里的最近几步"补, 再往前的思考本来就不在侧栏里(主界面自己的记录里还在)。
  18. 展开/折叠的偏好不落盘。 记在 client.js 的模块变量里,页面刷新就回到折叠状态; 做成 localStorage 会让一个纯 UI 细节多出一份需要清理的持久状态,暂时不值。
  19. 进展块只活在内存里,而且只有一个方向。 块表在 index.js 的 Map 里,Host 重启就清空 (侧助手还在,只是「之后的新进展」从重启那一刻重新累积);每块都有上限(想法 200 字、结论 120 字、工具 3 条 × 60 字), 最多 4 块,更早的滚掉;结论是这一步文字的第一句,不是模型再写一遍的摘要。块里没有主会话里用户说的话,也没有回合边界,只有侧助手被创建(或被菜单收养)之后主 Agent 走过的 step。 冷恢复那条路径也一样:每次提问都会重新挂上片段,所以重启之后的第一问看到的是重启之后攒的块。
  20. 输入框起手一行,长到 336px 后改为框内滚动。 起手比主对话矮一行,之后增长、上限、滚动都与主对话一致; 想完全一样就删掉 SIDEBAR_CSS 里 min-height 与 inset 那两条。

测试

没有测试框架,直接跑:

node test/host.smoke.mjs        # Host 半区:路由、幂等、并发合并、创建窗口、进展块、错误分支
node test/client.smoke.mjs      # Client 半区:工厂契约、槽/资源/标签页注册、地址、菜单、进展条
node test/address.contract.mjs  # 地址契约:自家语法、独立 URL 读法、与自带 subagentchat 互斥

三个都用假 Context、假 DOM、假 fetch,不需要启动 DSH;client.smoke.mjs 里有一个几十行的 hook 运行时(尊重依赖数组,能安装/卸载 effect),能把按钮与面板真的渲染出来、按下去、 再喂几帧 session.follow 的假数据,所以菜单与进展条的每个分支都是行为验证,而不是只断言字符串。 address.contract.mjs 会自动找到本机 DSH 安装并交叉校验两条语法的互斥性, 找不到就只跑本地语法(用 DSH_SUBAGENT_CLIENT=<…>/dsh-client-ui-subagent/lib/client.js 可强制指定)。

许可

Copyright (C) 2026 the dsh-side-assistant authors.

本程序是自由软件:你可以依照 GNU Affero 通用公共许可证第 3 版(AGPL-3.0,由自由软件基金会发布) 重新发布和/或修改它——第 3 版,或者(按你的选择)任何更新版本。

本程序是希望它有用而发布的,但不带任何担保,甚至不带适销性或特定用途适用性的默示担保。 详见 GNU Affero 通用公共许可证。许可证全文见 LICENSE,没拿到就看 https://www.gnu.org/licenses/。

因为是 AGPL-3.0:把这个插件改过之后作为网络服务提供给别人用,有义务向使用者提供对应的源码—— 它本来就是作为 DSH 网页界面的一部分运行的,所以这条义务覆盖的是你运行的那份插件代码,不只是分发出去的副本。

Comments

Loading…

From the same category

dsh-desktop

by anywhere-labs

为 DeepSeek Harness (DSH) 插件生态打造的现代化桌面端解决方案。万物皆「插件」,桌面本身也是「插件」。

UI & ExperienceManifest valid

★ 31.3k

↓ 115/wk

MIT

TypeScript

Oct 17, 2026

dsh plugin --profile web add dsh-plugin-desktop

by zhu1090093659

DeepSeek Harness (DSH) Web Plugin Aggregation Ecosystem · Everything is a plugin, distributed via the Creative Workshop

UI & ExperienceManifest valid

★ 8.9k

↓ 145/wk

Apache-2.0

TypeScript

Oct 17, 2026

dsh plugin --profile web add dsh-web

A collection of independent Web UI plugins and skins, including task boards, Git graphs, mobile access, and live token stats.

UI & ExperienceManifest valid

★ 8.4k

↓ 145/wk

Apache-2.0

TypeScript

dsh plugin --profile web add dsh-web

by omdsh-dev

开放的侧边栏底座,支持三方拓展注册新侧边栏页面。内置文件渲染编辑/终端/侧边对话/Git/子代理页面 | Open sidebar foundation, supports third-party extensions to register new sidebar pages. Built-in file rendering/editing, terminal, side chat, Git,

UI & ExperienceManifest valid

★ 4.3k

↓ 57.6k/wk

MIT

TypeScript

Oct 17, 2026

dsh plugin --profile web add dsh-better-sidebar

by bowenliang123

The best DeepSeek Harness plugin for context insight and management, with context dashboard / browser / sidebar and context command, for context statistics, composition, breakdown, evolution details,

Tools & CapabilitiesUI & ExperienceTerminal & ClientsManifest valid

★ 2.3k

↓ 52.1k/wk

Apache-2.0

TypeScript

Oct 18, 2026

dsh plugin --profile web add dsh-context

by shaobeichen

把 DeepSeek Harness 装进你的口袋:电脑上跑 dsh web,手机扫码即同步访问(局域网 + 公网,实时同屏)Put DeepSeek Harness in your pocket: run dsh web on your computer and access it synchronously by scanning a QR code on your phone (LAN +

UI & ExperienceManifest valid

★ 1.8k

↓ 3.4k/wk

GPL-2.0

JavaScript

Oct 17, 2026

dsh plugin --profile web add dsh-pocket