Skip to content

dsh-companion-window

Verified

dsh-companion-window · v0.1.0 · MIT · Web UI

陪聊窗口 —— 一个只在项目真的卡住时才出现的搭话窗口。它读工作区元数据(文件改动、深浅、节奏),不读文件内容;会话侧读你说过的话。逐条可核的清单见 README。

Install

dsh plugin add dsh-companion-window

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Creators

Readme

dsh-companion-window(陪聊窗口)

一个挂在工作区旁边的搭话窗口。它平时不出声;当它从工作区的状态看出你可能卡住了,它会自己冒出来问一句,然后回去。

它不是心理医生,不做诊断,也不给建议清单。它的定位是搭子:说两句人话,问一个具体的、你答得上的问题。


它凭什么判断你卡住了

只看工作区的形状,不看内容:

它看的 它不看的
文件改动的时间间隔与节奏 文件内容(一个字节都不读)
改动的文件数量、集中在几个文件 你写得好不好、对不对
目录结构与层级深浅 代码/论文里的任何文字
提交的时间点、提交信息、涉及的文件名 未提交的改动具体改了什么
会话间隔(你多久没说话) 你说了什么(除了下面那一节)

具体执行的命令只有这几条,都是只读的:

git rev-parse --is-inside-work-tree; git rev-parse --abbrev-ref HEAD
git log --since=120.days.ago --no-merges --pretty=format:%at%x09%s
git status --porcelain
git log --since=14.days.ago --name-only --pretty=format:%at
mkdir -p .peer

注意其中两条会读到提交信息文件名——那是你写的字,所以在这里明说。

它会读对话原文吗

会。它读的比"只读元数据"多,所以这里逐条写清楚。

工作区那侧它确实只读元数据。会话那侧不是:

  • 它读你在当前这一场会话里发过的消息。

  • 它的模型如果觉得需要,可以调 read_turns 展开某一段完整来回,看到双方的发言。

  • 它会看同一个目录下最近 6 场会话的标题,在你发过的话里搜下面这 16 个词,用来发现"你最近老在同一件事上卡住":

    卡住  崩溃  撑不住  不想干  没意义  焦虑  失眠  熬夜
    通宵  拒稿  被拒  来不及  做不完  难受  很累  坚持不下去
    

    这一项只保留命中次数和时间,不留原句

  • 不读:思考/推理块、工具调用的参数与结果、其它目录的会话、子会话。

写这条的目的不是让你放心,是让你能核:上面每一句都对应源码里一个具体的调用点,搜 sqSvc. 就能看到。

它会往你的工作区写什么

一个 .peer/ 目录,两个文件:

  • .peer/state.json —— 开关偏好,和它上次开口的时间(防止它反复烦你)。
  • .peer/notes.json —— 它的记忆。分三块,来源不同:
    • note:它自己写的,每轮重写。
    • lastNext:它上次给你的那个动作,由插件记录,模型改不了。
    • pinned只有你能写,模型永远不会覆盖它。

30 天不用自动过期。在设置面板里可以查看、编辑、清空。

删掉 .peer/ 目录不会有任何副作用——它会重新建。


安装

走官方插件渠道,和你装别的 DSH 插件完全一样:

dsh plugin --profile web add dsh-companion-window

从本地目录装(还没发布时):

npm pack --pack-destination dist
dsh plugin --profile web add dist/dsh-companion-window-0.1.0.tgz

装完需要重启 profiledsh.bundle.patch 声明的组合层要求新起一次进程才会挂上。

为什么是 tarball,不是目录

不要用 dsh plugin add /path/to/packages/companion-window 装目录。 这不是洁癖,是 DSH 的模块解析决定的,而且失败方式很隐蔽。

DSH 的插件解析是「双锚点」的——包名从 dsh 安装目录或 profile 目录解析,但包自己内部的 import 由 Node 从它的 realpath 逐级向上找 node_modulesdsh plugin add <目录> 装成软链(link:),realpath 指向你的仓库,于是这条向上走的路走不到 $DSH_HOME/profiles/node_modules 那个安装闭包:

装目录(link:)                        装 tarball
  realpath = 你的仓库/…                  realpath = ~/.dsh/profiles/web/node_modules/dsh-companion-window
    ↑ 逐级向上                             ↑ 逐级向上
  桌面 → 用户 → 盘根 …  ✗                profiles/web → profiles  ✓ ← 安装闭包在这

后果分两种,第二种更坏:

  1. 直接 ERR_MODULE_NOT_FOUND,profile 起不来;
  2. 有人为了让报错消失,在插件目录里跑了 npm install——报错是没了,但插件目录里多出一份私有的 @deepseek-ai 副本。这一份不报错,只是 TypertRemoteService 继承的 Service 基类和 profile 的 Cordis 不是同一个类,两套 Symbol 对不上,ctx.plugin(服务) 静默失效:一切看起来都正常,窗口永远不出现。

装完先体检

node build/doctor.mjs                  # 默认体检 web profile
node build/doctor.mjs --profile xxx

它加载 profile 里真实的模块实例,完整跑一遍挂载,其中一条断言就是上面说的模块同一性。装得不对的话,它在重启之前就会告诉你哪里不对、怎么修——不用靠重启试错。

# 期望看到的最后一行
结论:这个 profile 里的安装是好的,重启后窗口会出现。

卸载

dsh plugin --profile web remove dsh-companion-window

这一条会连组合层一起撤掉。即使 profile 起不来,dsh plugin 子命令本身仍然可用(它直接调 pnpm),所以这条路是通的。


自己核一遍

这个插件不要求你相信上面任何一句话。三条路,任选:

  1. 读源码。 lib/index.js 是全部宿主逻辑,lib/client.js 是全部界面逻辑,没有压缩混淆。搜 sh( 能看到它执行的每一条命令;搜 sqSvc. 能看到它读会话的每一个调用点。
  2. 看审计面板。 陪聊窗口的设置里有「审计」一项,它列出插件往外发过的每一次模型调用:轮次、用了多少 token、缓存命中多少。缓存命中率那一栏是给「每次说话都从零开始」这个毛病准备的证据。
  3. 看运行卡片。 这个插件注册了一个给 Agent 用的工具 project_state_snapshot。它只返回工作区元数据,不返回会话原文。工具卡片里写着它取哪些字段,对着源码能一条条核。

面板与设置

位置 是什么
右下角竖排按钮 手动开一次窗口。它是唯一的常驻入口。
主输入框右上角 它自己开口时,在这里弹一张小卡片。没事的时候这块地方是空的。
设置 → 陪聊 开关主动开口、开关回声、选模型、选推理档位、看审计、管理记忆
  • 主动开口(默认开):关掉之后它就只在被叫的时候出现。
  • 推理档位(默认低):调高会让它想得更久。它回答短,档位高了容易把 token 全花在思考上。
  • 模型:默认跟随当前会话的默认模型。可以单独指定一个更便宜或更快的。

它明确不做的事

  • 不做心理评估,不给诊断,不贴标签。
  • 不做危机干预。出现自伤/伤人表述时,它没有能力处理,也不会假装能处理。
  • 不建议你休息。它会给你一个身体上的小动作(喝水、站起来走两步、把窗户打开),不会说"你该歇歇了"。
  • 不说"加油"「会好起来的」「我理解你」。这三句是这个插件最典型的失败方式,系统提示里被明令禁止。

DSH 升级了怎么办

先跑一次体检,再重启。

npm run doctor

它会打印本次依据的 DSH 版本,然后完整挂载一遍。版本号变了就是变了;结论不是"安装是好的",就说明契约动了。

为什么需要这一步

DSH 的模块解析升级时会自动修复——dsh-app-boot 每次启动都从当前安装重新解析依赖闭包、重建软链,这里不用管。

内部 API 会变,而且有一半的失败是静默的:

变了会报错 变了不报错
Remote 描述符的 version: 1 defineToolparameters 方言
服务的构造签名 Remote 标记的格式
宿主服务的方法名 客户端槽位 key
客户端的实参个数校验 dsh.client.inject 里的包名

静默那一半的形状是:服务注册成功、界面一切正常、窗口永远不出现。所以插件启动时会自己验一遍,并把失败同时写进控制台和设置里的「审计」面板(「最近一次错误」那一行)。

界面上那句「最近一次错误」不是装饰——它就是为这类事准备的。


依赖

宿主半边需要:fsshellllmagentDefaultModelsessionQueryworkspaceRegistrytools。 客户端半边需要:remoteslotstimer

缺任何一个,插件会停在那等,不会半死不活地跑。

构建

lib/ 里的两个文件是生成物,不要直接改。人工编辑的是 build/ 下的规范源:

build/host.txt     宿主半边主体(15 个 Remote 处理器 + 工具 + 状态扫描)
build/client.txt   客户端半边主体(三个槽位 + 组件 + 样式)
build/build.cjs    施加"包壳"的生成器
build/doctor.mjs   启动前体检
npm run build      # 重新生成 lib/index.js 与 lib/client.js
npm run doctor     # 体检当前 profile 里的安装
npm run pack       # 打出 dist/dsh-companion-window-<version>.tgz

包壳只做这几件事:加 import、把处理器包成 TypertRemoteService 子类、手写 Remote prototype 标记、把客户端主体包进 window.__ModuleLoader__ 工厂、host.callrpc、 把样式挂进文档。

这样分工是因为:两个形态(开发用的动态插件、交付用的这个包)共享同一份主体,差异全是 机械的。手抄一份 78 KB 的源码一定会出静默错字——这个项目已经栽过三次,所以改成让脚本改。

改了 lib/ 之后必须重新打包并重装,profile 里那份是拷贝不是软链:

npm run build && npm run pack
dsh plugin --profile web remove dsh-companion-window
dsh plugin --profile web add dist/dsh-companion-window-0.1.0.tgz
npm run doctor

名字的来历

这个包本来叫 dsh-side-chat,改名是因为 npm 上已经有同名的、互不相关的插件(super-cabbage 的「并行侧边对话」)。查证之后发现撞的不止包名——他的客户端模块 id 也是 dsh-side-chat,cordis 行 id 也是 side-chat

两个插件同时装的话:行 id 相同会让组合层重复注册(他自己的 cordis.patch.yml 里就写着「否则重复注册崩溃」),客户端模块 id 相同会让模块表重复。所以三处标识符都得换,而它们都由 build/build.cjs 里的 PKG_NAME 一处派生。

线上命名空间也从泛化的 sideChat 收窄成了 companionWindow;服务类名从包名派生为 CompanionWindowGateway

发布

npm run doctor                            # 先确认当前安装是好的
npm login                                 # 一次性
npm view dsh-companion-window version     # 期望 E404,确认名字仍可用
npm publish

npm publish 之前会自动跑 prepack,重新生成 lib/ —— 发出去的产物一定是规范源生成的,不可能是过期的。

发出去的包里包含 build/(规范源 + 生成器 + 体检脚本)。这不是顺手带的:README 说「源码由规范文件机械生成」,那就得让人能自己验,而不是只能信。

许可

MIT