dsh-companion-window
Verifieddsh-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
装完需要重启 profile:dsh.bundle.patch 声明的组合层要求新起一次进程才会挂上。
为什么是 tarball,不是目录
不要用 dsh plugin add /path/to/packages/companion-window 装目录。 这不是洁癖,是 DSH 的模块解析决定的,而且失败方式很隐蔽。
DSH 的插件解析是「双锚点」的——包名从 dsh 安装目录或 profile 目录解析,但包自己内部的 import 由 Node 从它的 realpath 逐级向上找 node_modules。dsh plugin add <目录> 装成软链(link:),realpath 指向你的仓库,于是这条向上走的路走不到 $DSH_HOME/profiles/node_modules 那个安装闭包:
装目录(link:) 装 tarball
realpath = 你的仓库/… realpath = ~/.dsh/profiles/web/node_modules/dsh-companion-window
↑ 逐级向上 ↑ 逐级向上
桌面 → 用户 → 盘根 … ✗ profiles/web → profiles ✓ ← 安装闭包在这
后果分两种,第二种更坏:
- 直接
ERR_MODULE_NOT_FOUND,profile 起不来; - 有人为了让报错消失,在插件目录里跑了
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),所以这条路是通的。
自己核一遍
这个插件不要求你相信上面任何一句话。三条路,任选:
- 读源码。
lib/index.js是全部宿主逻辑,lib/client.js是全部界面逻辑,没有压缩混淆。搜sh(能看到它执行的每一条命令;搜sqSvc.能看到它读会话的每一个调用点。 - 看审计面板。 陪聊窗口的设置里有「审计」一项,它列出插件往外发过的每一次模型调用:轮次、用了多少 token、缓存命中多少。缓存命中率那一栏是给「每次说话都从零开始」这个毛病准备的证据。
- 看运行卡片。 这个插件注册了一个给 Agent 用的工具
project_state_snapshot。它只返回工作区元数据,不返回会话原文。工具卡片里写着它取哪些字段,对着源码能一条条核。
面板与设置
| 位置 | 是什么 |
|---|---|
| 右下角竖排按钮 | 手动开一次窗口。它是唯一的常驻入口。 |
| 主输入框右上角 | 它自己开口时,在这里弹一张小卡片。没事的时候这块地方是空的。 |
| 设置 → 陪聊 | 开关主动开口、开关回声、选模型、选推理档位、看审计、管理记忆 |
- 主动开口(默认开):关掉之后它就只在被叫的时候出现。
- 推理档位(默认低):调高会让它想得更久。它回答短,档位高了容易把 token 全花在思考上。
- 模型:默认跟随当前会话的默认模型。可以单独指定一个更便宜或更快的。
它明确不做的事
- 不做心理评估,不给诊断,不贴标签。
- 不做危机干预。出现自伤/伤人表述时,它没有能力处理,也不会假装能处理。
- 不建议你休息。它会给你一个身体上的小动作(喝水、站起来走两步、把窗户打开),不会说"你该歇歇了"。
- 不说"加油"「会好起来的」「我理解你」。这三句是这个插件最典型的失败方式,系统提示里被明令禁止。
DSH 升级了怎么办
先跑一次体检,再重启。
npm run doctor
它会打印本次依据的 DSH 版本,然后完整挂载一遍。版本号变了就是变了;结论不是"安装是好的",就说明契约动了。
为什么需要这一步
DSH 的模块解析升级时会自动修复——dsh-app-boot 每次启动都从当前安装重新解析依赖闭包、重建软链,这里不用管。
但内部 API 会变,而且有一半的失败是静默的:
| 变了会报错 | 变了不报错 |
|---|---|
Remote 描述符的 version: 1 |
defineTool 的 parameters 方言 |
| 服务的构造签名 | Remote 标记的格式 |
| 宿主服务的方法名 | 客户端槽位 key |
| 客户端的实参个数校验 | dsh.client.inject 里的包名 |
静默那一半的形状是:服务注册成功、界面一切正常、窗口永远不出现。所以插件启动时会自己验一遍,并把失败同时写进控制台和设置里的「审计」面板(「最近一次错误」那一行)。
界面上那句「最近一次错误」不是装饰——它就是为这类事准备的。
依赖
宿主半边需要:fs、shell、llm、agentDefaultModel、sessionQuery、workspaceRegistry、tools。
客户端半边需要:remote、slots、timer。
缺任何一个,插件会停在那等,不会半死不活地跑。
构建
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.call → rpc、
把样式挂进文档。
这样分工是因为:两个形态(开发用的动态插件、交付用的这个包)共享同一份主体,差异全是 机械的。手抄一份 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