DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Side Assistant — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
S

dsh-side-assistant

Side Assistant

Open a side assistant for the current session: compress the completed history of the main session into a summary, then push each subsequent step (thoughts, tools, conclusions) as system prompt snippets. A real-time progress bar, collapsed by default, also appears at the top of the panel. Two quick-a

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:TPSCE/dsh-side-assistant#c592d768be2f512db63c0f0ab3ebc2c73a99f848
READMECompatibilityVersions

Description

Open a side assistant for the current session: compress the completed history of the main session into a summary, then push each subsequent step (thoughts, tools, conclusions) as system prompt snippets. A real-time progress bar, collapsed by default, also appears at the top of the panel. Two quick-action cards appear below responses. The assistant can be deleted (archived) from the menu, and chat history in the panel is no longer duplicated from the main conversation.

Compatibility and provenance

Side Assistant is published as dsh-side-assistant and currently resolves to version 0.15.2. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/18/2026

Versions

0.15.2stable
10/18/2026

Related plugins

Loading related plugins…

Latest
0.15.2
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
AGPL-3.0-or-later
Source
github
GitHub
★ 1
Weekly downloads
0
Last push
10/18/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in memory-context.

Memory Plugin@openviking/dsh-memory-pluginOpenViking memory and context bundle for DeepSeek HarnessContextdsh-contextA DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.Ab Memorydsh-ab-memory--- description: "The dsh-ab-memory package: a cross-session, file-backed agent memory with five tools (remember / recall / forget / load_skill / distill) and an always-on light injection." kind: "package-reference" ---Weknora@wxg-prc-cpg/dsh-weknoraWeKnora knowledge retrieval tools for DeepSeek Harness (dsh): semantic search, document reading and RAG/agent answers over your own knowledge bases.

README

dsh-side-assistant

English · 中文 · 仓库 · 更新日志 · AGPL-3.0

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

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

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

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


它做什么

需求落点
侧边对话面板 + 输入框 + 消息流由本插件自己的标签页承载,聊天本体直接选 DSH 的 conversation.content 工厂(embedded 变体)
看主 Agent 的实时进展(工具调用 + 思维链)面板顶部自绘进展条,session.follow 直连主会话的事件流与 assistant stream
几十上百步也能一眼概览、随时下钻提示词里是每步一行的索引(工具 + 目标 + 成败 + 结论),最新的几步保留原文,更早的用侧助手专属工具 side_assistant_read_steps 现取
侧助手也知道之后发生了什么主 Agent 每完成一个 step,宿主把这一步折成一块写进侧助手的系统提示片段(side-assistant:progress),不进聊天记录
知道主会话已完成的历史建会话时把主会话已完成回合压成一段摘要,作为系统提示片段 side-assistant:history(order 120)——它自己的日志从开场白开始,侧栏里看不到主对话
可反复追问(持久化侧助手)会话自带 subagent/descriptor,每次提问按需冷恢复,记录一直留着
看得见主会话的后台任务job 注册表按会话围栏,侧助手自己的 job_list 只看得见自己的任务,所以宿主把主会话的 job 搬进提示片段 side-assistant:jobs(order 122),再配一个 side_assistant_read_job 取输出尾部
一个都不占子智能体名额用 ctx.agents.create() 建游离会话,不追加 subagent/catalog 事实
关掉再开不丢侧栏按会话把打开的标签页存进 localStorage
一个会话多个侧助手、换一个重开按一下弹菜单:已有的按创建顺序编号,末尾另有「新建助手」
回答下面两颗快捷卡片conversation.input.dock 里挂两颗圆角矩形卡片:这个项目是在干什么? / agent 正在进行什么工作?。点一下等于自己打出来发出去(conversation.send,和输入框同一条路)
模型、上下文预算、回答风格可自定义设置 → 侧边助手(和「通用」「模型」「插件市场」并列的独立一栏):模型三种模式(跟随主会话 / 指定宿主模型 / 自定义 OpenAI 兼容端点)、API Key 走 credentials、真的发一次请求的「测试连接」、完整 step 条数、token 预算、简约/详细(见「设置:模型、上下文、回答风格」)

它不做什么(有意为之)

  • 不重新实现聊天界面。 消息流、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-side-assistant.git

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

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

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

改本地代码时可以改装这个目录(装的是同一份代码,只是绕过网络)。路径含空格,务必加引号:

dsh plugin --profile web add "~/code/dsh-side-assistant"

也可以从 Web 界面的插件页安装:粘贴仓库地址,或选这个本地目录。

卸载:

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 anything~

只有输入框可用时才替换。不可用 / 离线 / 被拦截时,原提示语是状态而不是装饰, 那几条规则会把它放回来——判据是同一父节点里可编辑区的 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 条)。

上限与代价:索引行 + 下钻工具。 每步的「一行」不再是一句散文,而是一条索引:

[step 12] bash:node test/host.smoke.mjs✗, edit:client.js — 加了删除按钮

跑了什么工具、动了哪个目标、成功还是失败、结论是什么,大约 60–90 字符。于是三层各管一段:

层覆盖上限
原文块最新的几步,想法 + 工具 + 结论5 步(设置可改 1..50)
索引行之前每一步200 行,字数额度跟随「上下文预算上限」(默认 6000 字 ≈ 七十步)
下钻宿主还留着的最近 400 步一次最多 40 步 / 8000 字

下钻是侧助手自己的工具 side_assistant_read_steps({from, to, query}):问「第 12 步改了什么文件」、 「哪一步碰过 client.js」时它自己去取原文,而不是让宿主猜该塞多少进提示词。它注册在侧助手的工具作用域里 (agent.ctx 的 tools.register),主 Agent 与真正的子智能体都看不见;冷恢复时由 agent/created 重新挂上, 每次最多返回 40 步并说明自己实际读到第几到第几步,越界时也说清楚「只留到第 N 步」。

一份完整注入通常一两千字——刻意的:注入太长会挤掉你真正想聊的内容。块一变,提示词缓存就失效一次—— 实时进展比缓存命中重要。片段文本是 getter,每次拼提示词时重算,所以用户提问之后、模型开口之前刚落下的那一步也在里面。

后台任务:job 与 shell 进程

主 Agent 的每一步里,后台命令只留下一句「启动了 bash-3」;它跑得怎么样,在 job 注册表里。侧助手拿不到那份数据——job_list 是按会话围栏的,它自己的调用只看得见自己的任务。所以宿主把主会话的任务搬过来,挂在系统提示片段 side-assistant:jobs(order 122,在历史摘要之后、进展块之前):

bash-3  running  npm test  2/5  2m ago
bash-2  completed  pnpm build  exit code: 0  took 34s
  • 列的是正在跑的和最近十分钟内结束的,最多 8 条,一行一条:id、状态、命令(截到 70 字)、进度行、用时或结局。
  • 要看输出就调第二个工具 side_assistant_read_job({job}),返回尾部最多 4000 字符(被裁会说清「这是尾部」)。它读的是保留输出窗口的绝对偏移(readAt),不动主 Agent 自己的读取游标——否则就把它该看的那段吃掉了。
  • 两个工具都注册在侧助手自己的工具层(agent.ctx),主 Agent 和真正的子智能体都看不见;job 注册表不存在时这段提示与这个工具都不出现,面板照常工作。

边界:侧栏里的交互式终端不属于 job 注册表,宿主也没有列举它们的服务,所以这里看不到; 子智能体 job 会以 subagent-N 出现,但它们的产出去子会话日志,不在输出环里,尾部读起来是空的。

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

删除按钮跟着它要删的东西走:再点一次头部按钮打开菜单,每个「助手 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)仍不出现在面板里。

设置:模型、上下文、回答风格

设置 → 侧边助手(0.15.2 起;0.12.0–0.15.1 它藏在「设置 → 插件」的标签页里):设置对话框左侧导航里独立的一栏,和「通用」「模型」「插件市场」并列,不点进「插件」也能找到。保存后不用重启,新开的助手按新设置工作。

设置项默认说明
模型跟随主会话跟随主会话=和主 Agent 同一个 provider/model;指定宿主模型=填 provider + model(先在 设置 → 模型 里配好);自定义端点=填 Base URL + 模型名 + API Key,宿主把它写成 llm-pi-ai 的 providers.side-assistant-custom,于是自定义端点和内置 provider 共用同一套重试、凭据解析和模型列表
API Key—存进 credentials 服务(引用名默认 SIDE_ASSISTANT_API_KEY),不进设置文件、不回显;页面只显示「已配置」
测试连接—真的发一次极小请求(maxTokens ≤ 16),回报耗时和实际用的 provider/model。配置能解析不等于能用,只有打到网关上才算通过
进度保留的完整 step 条数51..50;比这更旧的 step 折成一行索引(工具 + 目标 + 成败 + 结论)
上下文预算上限(token)2000500..200000;1 token ≈ 索引 3 个字符 / 历史摘要 2 个字符
回答风格简约简约=一到两句;详细=讲清每一步做了什么、为什么、动了哪些文件
助手名 / 开场白侧边助手 / hello只影响新开的助手

存在 ~/.dsh/storages/side-assistant/settings.json(根目录可用 DSH_HOME 改):一个小 JSON 文件, 读坏了就逐字段回落默认值,不会让面板起不来。

几个刻意的边界:

  • 没有 Config schema:设置是插件自己管的文件,不走 DSH 的设置命名空间。好处是保存即生效、不用重启、不受 schema 校验束缚; 代价是这些键不出现在 dsh config 之类的命令行视图里。
  • 行配置仍是基线:cordis.patch.yml 里的 label/opener 是默认值,页面上保存的值优先。
  • 风格是活的,其他是创建时定的:回答风格挂在会话的 prompt context 上(side-assistant:style,order 118), 改完下一个 agent/created 就生效;助手名和开场白写进描述符,只影响新建的助手;step 条数只影响之后的折叠 (已经折叠成索引的历史不会因为调大上限而重新展开)。
  • 自定义端点的 key 走 credentials:页面永远读不回来,留空保存=不动已有的 key。

配置

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: dsh-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 语法互斥:两个语法解析对方地址都必须失败,谁也不会抢走谁的标签页。 会把这条契约连同 glob、编码、拒收样例一起校验。

已知边界

  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 anything~」——它们不是错误态,但也不是这个面板要表达的重点;想让它们也保留原文, 把 SIDEBAR_CSS 里那两条 :has(> [data-placeholder][aria-disabled="true"]) 规则的条件放宽即可。
  11. 开场那句 hello 会在面板里显示成一个用户气泡。 它是真实的 user 消息(DSH 固定这么投递, 插件改不了角色),而面板渲染的就是这个会话的记录。现在它的日志从这句开场白开始(历史是摘要、不是种子), 「第一个用户气泡」就是它,按位置藏也是安全的;插件仍然默认不藏,不想要开场白就配 opener: ""。
  12. 思考档位的取值是适配器自有的。 DeepSeek 适配器是 off | low | high | max,插件不校验: 填了该路由不认的值,表现是建会话直接失败并带 , 这时改成 即可。想中途改已建好的侧助手,就用侧栏里的模型选择器(它带 effort 档位)。

测试

没有测试框架,直接跑:

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 网页界面的一部分运行的,所以这条义务覆盖的是你运行的那份插件代码,不只是分发出去的副本。

test/address.contract.mjs
  • 进展块怎么到模型手里。 宿主订阅主会话的 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 则完全不动主会话的路由。
  • UNSUPPORTED_REASONING_EFFORT
    inherit
  • 菜单是「每次点都重新问一遍」,不缓存。 所以在别的标签页里新建/删掉侧助手之后, 下一次点按钮就能看到,不用刷新页面。代价是每次点击都发一次 POST /side-assistant/list; 它读的是会话索引 + 这一层目录,不读会话日志。
  • 菜单从「已有 1 个」就开始弹,不是「2 个以上」。 你的原话是「已存在助手有一个以上时候生效」, 但按字面实现(>1)时只剩一个助手就再也点不出「新建助手」,等于少了一个入口; 所以这里把「新建助手」放在菜单最后、并在一个都没有时走直接创建。 想改成严格的 >1,把 client.js 里 if (rows.length === 0) 改成 if (rows.length < 2) 即可, 代价是恰好剩一个时无法再新建。
  • 菜单与进展条都是自绘的,只用主题 token。 没有引用 @deepseek-ai/dsh-client-ui-primitives 的下拉组件(普通 JS 插件不许 require Harness Client 包),所以样式跟随 --dsw-* 变量, 不跟随组件库的行为细节。
  • 审批策略在创建窗口里被钉成 never(与旧版委派路径一致的语义):侧助手不该替你做需要审批的事。 它确实会尝试时,得到的是一条拒绝,而不是一个悬在侧栏里没人点的审批提示。 想要它更放手,得改 index.js 里 capturePolicyOverrides() / appendPolicyOverrides()。
  • 进展条只在面板开着时订阅,主会话被关掉或归档时它显示「读不到」而不会报错; 它也不订阅侧助手自己的会话(面板下方的聊天已经能看到那些)。 折叠只改显示:订阅不断,圆点与「读不到」那一行照样更新。 留存有上限:思维链 6 块 × 4000 字、工具行 30 行,更早的会滚掉;面板重建后靠"快照里的最近几步"补, 再往前的思考本来就不在侧栏里(主界面自己的记录里还在)。
  • 展开/折叠的偏好不落盘。 记在 client.js 的模块变量里,页面刷新就回到折叠状态; 做成 localStorage 会让一个纯 UI 细节多出一份需要清理的持久状态,暂时不值。
  • 进展块只活在内存里,而且只有一个方向。 块表在 index.js 的 Map 里,Host 重启就清空 (侧助手还在,只是「之后的新进展」从重启那一刻重新累积);每块都有上限(想法 200 字、结论 120 字、工具 3 条 × 60 字), 默认最多 5 块(设置里可改 1..50),更早的折成一行索引,索引也有条数与字数上限;结论是这一步文字的第一句,不是模型再写一遍的摘要。宿主另外还留着最近 400 步的原文,只通过侧助手自己的 side_assistant_read_steps 工具给出去(一个进程内的 Map,重启即空)。块里没有主会话里用户说的话,也没有回合边界,只有侧助手被创建(或被菜单收养)之后主 Agent 走过的 step——建会话时如果主 Agent 正好在一个回合中间,那个回合已经完成的步骤会被补折进索引,所以中途新开一个助手不会从空白开始;它补的是已完成步,正在写的那一步仍然要等它落盘。 冷恢复那条路径也一样:每次提问都会重新挂上片段,所以重启之后的第一问看到的是重启之后攒的块。
  • 输入框起手一行,长到 336px 后改为框内滚动。 起手比主对话矮一行,之后增长、上限、滚动都与主对话一致; 想完全一样就删掉 SIDEBAR_CSS 里 min-height 与 inset 那两条。
  • 设置走插件自己的文件,不是 DSH 的设置命名空间。 ~/.dsh/storages/side-assistant/settings.json 由插件自己读写(GET/POST /side-assistant/settings),所以保存即生效、不需要重启,也不进 dsh config 的视图。 三个后果值得知道:助手名/开场白写进新建会话的描述符,改完只影响之后新建的助手;完整 step 条数只影响之后的 折叠(已折成一行的不会重新展开);自定义端点会被写成 llm-pi-ai 的 provider side-assistant-custom, 因此在「设置 → 模型」里也能看到它——它和手工加的 provider 是同一种东西,删掉时也从那里删。
  • 「测试连接」真的发一次请求(maxTokens ≤ 16),所以它证明的是「这条路能用」,不是「配置语法没错」; 跟随主会话模式下没有确定的 provider/model,这个按钮会直接告诉你先选一种模式。
  • 回答风格是唯一「活」的设置。 它挂在会话的 side-assistant:style(order 118)上,改完下一个 agent/created 就生效,连已有的侧助手也立刻按新风格回答;其余设置要么写进描述符,要么只影响之后的折叠。