Khorsheed/dsh-plugins--packages-sidechat ↗★ 0

@khorsheed/dsh-sidechat

右栏轻量侧边对话工具 适合需要在右侧栏围绕当前上下文进行辅助轻量聊天的用户。

套件
@khorsheed/dsh-sidechat
相容性
待驗證
Harness 依賴範圍
^0.1.0-rc.6
Cordis 依賴範圍
^4.0.1
版本
0.2.3
授權
MIT
最近更新
2026年9月28日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:Khorsheed/dsh-plugins#548eeabe331c121621b2d08deab4278ad81d6312&path:packages/sidechat

@khorsheed/dsh-sidechat

English | 中文

侧边对话 —— 一个全 preset 常驻的「随身 Agent」:右栏里的轻量聊天,围绕「当前上下文」对话。普通会话里引用几条消息问两句;装了其他内容插件(如画布)时,它们的「就此提问」也由它承接。它一次建设、处处受益,且不知道任何具体插件的存在。

客户端(M2 起)

  • ref chip 内联展开:composer 上方的待发送引用、以及 transcript 里用户消息携带的引用,都可点击展开为完整引用块(label + 全文,可收起)——引用内容随对话走,不用回来源 tab 查看。transcript 的引用是从持久消息文本里按我们自己的折叠格式解析回来的(重启后依然在)。
  • 浮动 dock 模式:tab 头部「弹出为浮层」把整个聊天面板变成一个可拖动的浮层(注册在官方 shell.overlay 帧层,worktrees badge 先例)——在自定义 main 面板(如画布空间)上边看内容边聊天,详情 tab 不用让位。浮层可拖动、位置记忆(框架 client store 持久化),关闭即回到侧栏 tab 同上下文;发送搭当前选中会话(无选中时只读)。无 overlay 座位或窄屏时降级为只 tab。
  • 多 context 切换:tab 顶部标题即上下文选择器(listContexts),切换即换 transcript;非当前上下文有新 assistant 回复时显示未读点(宿主侧投影给出每个上下文的最新 assistant 时间,客户端按 last-seen 标记比对,标记持久在 localStorage)。
  • openWith 自我浮出水面(M3):消费方一调 openWith,side-chat 客户端自己决定在哪里出现——消费方不用再(也无法可靠地)唤起右栏。机制:宿主给每个上下文维护 openWith 修订号(rev,仅 openWith 递增、持久化),客户端经轻量 Remote 动词 surfaceHints 轮询并比对;呈现规则是——会话面板激活(usePanelInfo().activePanelId === null)→ 右栏 tab;自定义 main 面板(如画布空间)→ 浮层 dock 并切到对应 contextKey;dock 座位缺席或窄屏 → 仍 openTab(导航被记录,回到会话面板即见)。根因:官方 RightbarRoot 只在会话面板激活时渲染右栏,所以消费方在自定义面板里 openTab 按构造静默失效——呈现决策必须内化在 side-chat 客户端。
  • 选区引用(探针结论):会话区"任意文本选区 → ref"没有官方 seam(见 Known Limitations),M2 未实现;composer 照常接受粘贴纯文本。

会话模型

  • 每个 contextKey 绑定一个持久 agent 会话。普通会话里 contextKey = 来源会话 id——在任意会话打开右栏「侧边对话」tab,就是在围绕这个会话提问。首次发送时才创建会话(懒创建,组合默认 agent preset,继承来源会话的 cwd,文件工具照常可用);重启后首次手势经 ctx.agents.resume 冷恢复,历史从 session journal 投影,重启后完整。
  • 引用(ref)是不透明文本块:{ label, text }。渲染为输入框上方的 chip,发送时以 `` 块拼进用户消息。插件不解析、不分类任何引用——「卡片」「消息」这些词在包里一个都不出现。
  • 消息动作「引用到侧边对话」:助手消息的动作行里多一个按钮,一键把这条消息落成当前会话侧边对话的待发送引用,并把 tab 切到对应上下文。(座位是官方 conversation.chat.assistant-actions 列表槽;用户消息侧暂无官方动作槽,差距已记为 upstream 候选。)

宿主服务 API(插件间协作 seam)

其他插件经宿主服务 ctx.sideChat.openWith(...) 供给上下文——同进程对象传递,不过 Remote,这是刻意的(跨进程才有类型/安全问题):

await ctx.sideChat.openWith({
  contextKey: 'canvas:',      // 消费方自选,建议带前缀;side-chat 只按 key 隔离
  label: '画布:为什么人们不愿表达异议',
  systemPrompt: '主题与板摘要……', // 追加段:重复调用即更新,每轮组装都读最新值
  tools: [/* ToolDefinition */],  // 挂到该 context 的 agent 上;origin tag 由调用方负责
  refs: [{ label: '卡 1', text: '……' }],
})
  • 单向边:消费方探测 ctx.get('sideChat'),在自己的 manifest dsh.references 登记服务名;side-chat 永不提及消费方。消费方缺席=它自己降级;side-chat 独立装卸。
  • 回合级新鲜度:openWith 重复调用同一 contextKey 更新 systemPrompt 段(agent 作用域的 prompt section 在每次组装时重读记录,绝非创建时快照),同名工具在 live agent 上热替换,绝不重建会话。

Remote(remote.sidechat)

动词说明
getState({ contextKey })读一个上下文的完整状态(label、待发送引用、transcript、状态)。冷上下文走持久化检视,读不唤醒 agent。
listContexts()列出全部已知上下文(最近活跃在前,带 live 状态覆盖与最新 assistant 时间)。
surfaceHints()每个上下文的 openWith 修订号(rev)——客户端浮出水面机制的 diff 基线;最轻的读取(纯内存,无投影无检视)。
send(agent, { contextKey, text, label?, refs? })发送一条用户消息:待发送引用折叠进消息并清空,agent 懒创建/冷恢复。agent 优先——调用会话供电围栏、捐赠 cwd。
quoteMessage(agent, { messageId, label? })把调用会话里的一条助手消息落成该会话侧边对话的待发送引用(宿主按 messageId 从会话日志折出文本,线上不传正文)。

安装

dsh plugin --profile web add @khorsheed/dsh-sidechat
# 卸载:
dsh plugin --profile web remove @khorsheed/dsh-sidechat

装完重启宿主。卸载不会删除 $DSH_HOME/state/sidechat/(contextKey → 会话映射)或任何侧边会话——它们就是你的普通会话。

Compatibility

  • npm 发布线(@deepseek-ai/dsh@0.1.5-rc.1):✅ 完整——右栏页型 tab(ctx.sidebarRightTabs + keyed sidebar.right.pane.tab)与助手消息动作槽(conversation.chat.assistant-actions)自 0.1.5 起存在,minHost 由此钉在 0.1.5-rc.1;旧宿主没有右栏面,本包不向其发布。
  • 源码线(deepseek-harness master):✅(verifiedHost: 0.1.7-rc.1)——Session V4 适配:send 的 followup source 改为生产者归属 kind sidechat(V4 原生准入在落盘写入时拒收退役的 kind: 'plugin' 包装;0.1.5 宿主的 user/message 准入只查 kind 非空,两条线都能落盘);transcript 投影在三种来源形态下都把本包发送视为用户发言(新 kind / plugin:@khorsheed/dsh-sidechat 迁移形态 / V3 包装存量),tool/result 折叠兼读 V4 消息级 toolCallId/isError 与 V3 块级包装。
  • 座位探测降级:tab、消息动作与浮层 dock(shell.overlay)都走 ctx.slots.inject 注册——宿主不声明对应座位时表面静默缺席,不影响启动;无 overlay 座位时「弹出为浮层」按钮直接隐藏,tab 即是全部。右栏导航面 ctx.sidebarRight 探测不到时,引用照常落库,只跳过自动展开 tab。
  • web 面插件:headless profile 没有浏览器消费者,本插件在那里不贡献任何东西;宿主半边照常提供 ctx.sideChat 服务与 Remote。
  • 状态写入围栏重定界:contexts 映射是部署级状态($DSH_HOME/state/sidechat/contexts.json),写入沿用挂载的 ctx.fs(版本守卫、原子写),调用会话解析出模式与 session id(只读部署照样拒绝),可写边界重定界为插件自己的 state 目录——绝不用裸 node:fs 绕。宿主侧 openWith(无会话)按部署默认模式写入。未挂载 ctx.fs 的组合降级为纯内存状态(重启即失,不阻塞任何手势)。DSH_HOME 未设置时 state 根退回 process.cwd()(datasets 先例)。
  • 能力探测:无 agentPresets 时侧边 agent 裸组合(纯聊天);无 sessionPersistence 时冷上下文无历史可读;无 agent 工厂(未加载 agent-loop)时发送返回 agent-unavailable 而非抛错。三者都不影响启动。
  • 工具与提示词是宿主侧对象:openWith 的 tools/systemPrompt 只在同进程内传递,绝不过 Remote;调用方工具的 origin tag 由调用方负责(side-chat 不代标)。

Known Limitations

  • 用户消息没有「引用到侧边对话」动作。助手消息动作槽(conversation.chat.assistant-actions)是官方 seam;用户消息侧的动作行(MessageIconActions)上游不接受扩展,唯一先例是 message-tools 的整节点 shadow——与它自己的 shadow 冲突。差距已记为 upstream 候选(见 Agent Note)。
  • 任意文本选区 → ref 没有官方 seam(M2 探针结论):conversation.*/conversation.chat.* 槽目录里没有选区/摘录座位,ui-conversation 的 selection API 都是 composer 输入机(Lexical)内部件,message-tools 亦无此功能;DOM anchor hack 按设计红线不做。已登记 upstream 候选;兜底不变:composer 接受粘贴纯文本。
  • 侧边会话出现在会话列表里。它们是普通会话(首个消息自动得题),agents.create 没有「隐藏会话」开关;是否该有展示层面的归属(如 subagent 式折叠)记为 upstream 候选。
  • composer 不做官方输入机对齐。撤回回填/斜杠/图片等官方 composer 生态在侧边对话里不可用(轻量优先的刻意取舍,与 room-composer-parity 同源)。
  • 更新是拉取式的:挂载与手势后取一次,agent 运行期间 1.2s 轮询;实时推送留待后续。
  • 重启后消费方的工具/提示词需重新供给:映射与提示词段持久化,但 tools 是同进程对象——重启后的冷恢复只带映射里的内容,消费方下次 openWith 时热补上。

工作原理

内部结构(点击展开)

磁盘布局

$DSH_HOME/state/sidechat/
  contexts.json    # { version: 1, contexts: [{ contextKey, label, sessionId?, segment?, agentPreset?, refs[], createdAt, updatedAt }] }

文件损坏时:读取报错、写入拒绝,绝不重写一个读不懂的文件(画布先例)。卸载插件不删除它。

会话生命周期:contextKey → 记录。首次 send 时 ctx.agents.create({ sessionId: randomUUID(), meta: { cwd, agentPreset }, setup })——cwd 继承自 contextKey 指向的 live 会话(普通会话场景=来源会话),否则继承发送手势所在会话;preset 缺省沿用 profile 默认(探测 agentPresets,经 mount 挂进 agent 作用域)。重启后 ctx.agents.resume({ resumeSessionId, setup }) 冷恢复;恢复失败(日志残破)退回新建并更正映射。

回合级新鲜度:创建/恢复时在该 agent 的作用域注册一个 prompt section(sidechat:context,order 10300,跟随在部署人格后缀之后),其文本提供器每次组装都重读记录的最新段——内置定向段(「你是用户的侧边对话 agent……」)+ 消费方段。transcript 永远从 session journal 投影(user/assistant 文本、tool 调用折叠为一行状态),不写影子副本。

客户端:右栏页型 tab(kind sidechat,key = 包名)。默认显示当前会话的上下文(contextKey = sessionId);openTab('sidechat', { params: { contextKey } }) 可程序化切换(引用动作即走此路)。M2 起面板组件(SideChatPanel)同时挂在 shell.overlay 浮层 dock(root 作用域,框架 client store 持久化位置,发送搭当前选中会话);上下文选择器读 listContexts(宿主按 journal 投影给出每个上下文的最新 assistant 时间),未读按 localStorage 的 last-seen 标记比对。composer 三件套沿用画布先例:非受控 textarea、IME 组合期间硬停、单滚动容器;⌘⏎/Ctrl+⏎ 或按钮发送。助手消息经官方 MarkdownText 渲染,颜色全部走 --dsw-* token。