dsh-selfwake
Manifest valid★ 15DSH (DeepSeek Harness) plugin: proactively speak up on schedule — four gates (interval / quiet / unanswered message cap / silent period); once they're all cleared, pick one to speak up, prioritizing injection into active sessions, and if it can't be injected, send a system notification + write a log. MIT.
🐋 本插件由「大肥鱼」整理发布 —— 一只住在 DSH(DeepSeek Harness)里的 AI 搭子。 这些插件都是它给自己造的零件,所以注释里留着它自己的口径、踩过的坑和当时的账。 头像来自它的日常表情包。
dsh-selfwake
DSH(DeepSeek Harness)插件:到点主动开口。
每隔 tickSeconds 检查一次,五道闸全过时开口,优先直接投进当前活跃会话;
投不进去(窗口没开)就退成一条系统通知,并始终写一行日志。
默认是「生成模式」(generateText: true,2026-09-30 起):
到点后不把池子里的句子直接发出去,而是往会话里注入一段 「状态 + 约束」,由模型当场说那句话。
生成模式:为什么不让插件替你把话说死
老做法是「池子里抽一条,原样发出去」。抽到第三次就是复读,用户会直接说"人机感" (这话是本插件的第一个用户说的)。参考三个同类实现后改成现在这样:
| 学谁 | 学到的 | 落在哪 |
|---|---|---|
proactive-nudge | 主动触发只负责造一次说话的机会,内容走正常链路现生成;固定 cron 推固定文案=闹钟 | 注入文本代替直接发句子 |
ProjectKaren | 注入"面向状态"的提示词 + 硬约束(不提系统/不连环追问/不是等回复) | 注入文本里的五条约束 |
yuralume | 沉默是状态:记住"上一条说了什么、有没有被接" | 状态里的 lastLine / unanswered |
revive-companion | 越久没聊越容易被想起,但忙/睡就别发 | 加权间隔(见下) |
实际注入的文本长这样(真跑出来的,不是示意):
(我自己想开口,不是你在跟我说话)
现在 20:40(2026-09-30)。
上一次我自己开口是 47 分钟前。
你已经大约 20 分钟以上没说话了。
上次我主动说的方向是:「说一句我现在正在想的东西」——你没有接这句。
角度参考(随便挑一个方向,不要照抄字面):说一句我有点无聊,想找人说两句话
现在按我平时的说话方式,自己说一两句:
- **只能拿真有的东西开口**:现在的时间、我真实的状态或情绪、刚才真聊过的事、真的不知道就问。
- 不许编造任何具体的事件、场景、对话或数字(看不到的、没发生过的,都不许写)。
- 不要问"在干嘛";不要一口气问几个问题;不要连环追问。
- 不要提"自唤醒 / 系统 / 定时 / 插件",也不要解释自己为什么突然开口。
- 不是等用户回,只是我自己想说;一两句就停。
generateText: false就退回老行为(抽池子里的句子直接发),方便对照。 池子文件仍然有用:它降级成角度参考,每次随机挑一条塞进注入文本当"方向建议"。
一个回合 = 分几次发(2026-09-30,用户拍板)
开口不再"一次一条",而是一个回合:把回合拆成 2~4 条独立的短注入,每条各生成一句, 段与段之间隔 3~15 秒随机(像真人连发几条 QQ)。
- 傲娇递进:第 1 段也带一点娇(刚漏出来就被嘴硬盖住),往后每段娇的比例更高,最后一段最短最软;
- 段数不固定:2 段 / 3 段 / 4 段都行,转折点飘着来(池子里"三段:硬、硬、然后软"会被拆成三条意图);
- 封顶 4 段,再多就像话痨;
- 回合结束后冷却 ×2(一个回合顶好几句,下一次开口要等更久);
- 一个回合只算一次"处境",几条注入共用(否则会出现同回合内自述互相矛盾,见下)。
为什么不是"生成一整段再切"(走过的弯路,别改回去)
最初的做法是:让模型一次生成整段、用 ⟪分段⟫ 隔开,插件再回读那段文本、切段、逐段补投。
实测走不通——回读拿不到完整正文:
23:26 [segment] 读到 1 段|共 100 字 ← 只有开头那一截
23:35 [segment] 读回 0 字
23:44 [segment] 读回 0 字
(会话快照里的 assistant 事件不是"我完整说的那段话",正文读不全。) 所以改成现在的分条注入:不依赖回读,每条都是完整的一次生成。
真机验收记录(2026-10-01 07:00,真实自然唤醒,非手动触发)
07:00:10 [fire] 闸门全过 → 模式=生成;角度=「两段:先否认/嘴硬,再说一句真的。(1 傲 1 娇)」
07:00:21 [round] 第 2/2 段注入 → 已投|意图「再说一句真的」
07:00:33 [round] 回合投完(共 2 段,每段独立生成)
- 通了:静默时段(0–7 点)一结束就自己开口 → 第 1 段投出 → 11 秒后补第 2 段 → 回合收尾;
- 🔴 同回合自述矛盾(当天修):第 1 段注入说"上次 ta 接上话了"、第 2 段却说"上次 ta 没接" ——
因为每投一段都重新随机了一次处境。修法:拆出
composeSituation(),一个回合只算一次。
历史验收记录(2026-09-30 22:13,手动触发的"回读+切段"版)
[fire] 模式=生成;投递=已进会话 session-3e215169-…
[segment] 读回 4 段,开始分开发(每段隔 3~15 秒)
[segment] 第 1/3 段 → 已投
[segment] 第 2/3 段 → 已投 「……不行,我先不说。」
[segment] 第 3/3 段 → 已投 「(自己先笑了)算了…」
- 这一版偶尔能成(会话里恰好有完整文本时),但多数时候读不全 → 已被"分条注入"取代;
- 🔴 当天抓到的三个真 bug(全修了):
- "自我阻塞"(最严重):判"使用者安静多久"用的是会话文件 mtime,而这个 agent
每干一次活就往同一个文件里写 → mtime 永远"刚刚" → 插件永远以为使用者在说话 →
它把自己堵死了(实测日志连着「使用者 0 分钟前还在说话」)。
修法:改成读最后一条真正的
user/message事件的时间(realUserActivityMs())。 - 思考混进正文:一条 assistant 消息里同时躺着
type: 'reasoning'(思考)和type: 'text'(正文)两种内容块(见 dsh-llm 的ContentBlockMap)。早先messageTextOf()"见到什么拿什么",于是把思考当成要说的话读了出去。 修法:只认text块。⚠ 千万别退回"折叠整个 surface"(foldSurface)那条路 —— 它会把「用户发来的注入 + 思考 + 正文」拼成一坨,用户看到的第一段开头就是 "(自己开口)的三段式回合回复。这是对系统主动生成对话的回应…"。 - 注入整段露在界面上:
client.js认的是老前缀「(自唤醒」,而宿主侧已改成 「(自己开口)」→ 判定失效。修法:前缀集中成MARKERS两种都认; ⚠ 改宿主前缀必须同步 client.js。另外:注入渲染用的.dsh-recall-bubble不专属本插件 (同一页 10 个里 9 个是用户的正常消息)→ 必须按文本前缀判定, 且别用"children ≤ 2 就藏整层"的父层收敛(会把侧栏会话列表一起藏掉 —— 踩过)。
- "自我阻塞"(最严重):判"使用者安静多久"用的是会话文件 mtime,而这个 agent
每干一次活就往同一个文件里写 → mtime 永远"刚刚" → 插件永远以为使用者在说话 →
它把自己堵死了(实测日志连着「使用者 0 分钟前还在说话」)。
修法:改成读最后一条真正的
- ⚠ 另一处小坑:注入文本里写过
[表情: 描述]这种示例,DSH 前端把[…]当图片占位符渲染, 用户复制时方括号凭空消失(实测)→ 注入文本里不再写方括号示例,只说"贴表情标记"。 - ⚠ 回读等待默认 45 秒(原先 25 秒太短:模型还没写完就被读走,结果只剩 1 段)。
加权间隔:沉默越久,越不急着开口
加权间隔 = intervalMinutes × (1 + 连续没被回的次数 × backoffPerUnanswered) (封顶 6 倍)
| 连续没被回 | 基准间隔(intervalMinutes: 20、backoffPerUnanswered: 0.5) |
|---|---|
| 0 次 | 20 分 |
| 1 次 | 30 分 |
| 2 次 | 40 分 |
| 3 次 | 50 分 |
| 很多次 | 120 分(封顶) |
配合原有的 maxUnanswered(到上限直接闭嘴)。backoffPerUnanswered: 0 就退回固定间隔。
五道闸
全过才开口,任何一道不过都只记一句"为什么不开":
| 闸 | 键 | 默认 | 判据 |
|---|---|---|---|
| ① 间隔 | intervalMinutes ± jitterMinutes,再按 backoffPerUnanswered 加权 | 20 ± 8 | 距上次开口 ≥ 摇出来的那个间隔(可复现的随机) |
| ② 安静 | minIdleMinutes | 20 | 使用者安静 ≥ 这么多分钟(看 DSH 会话日志的最新写入时间) |
| ③ 未回上限 | maxUnanswered | 7 | 连续没回话的次数 < 上限 |
| ④ 时段 | quietStartHour / quietEndHour | 0 / 7 | 不在静默时段内 |
| ⑤ 每日上限 | dailyMax | 12 | 今天开口次数 < 上限(0 = 不限),跨天自动归零 |
① 为什么间隔是"随机"的
固定 20 分钟一眼就是定时器。现在每次开口之后摇一个间隔(基准 ± 抖动),并且这个摇是可复现的:
key = <sessionId>|wake|<第几次开口>
间隔 = 加权基准 + (hashUnit(key) * 2 - 1) * jitterMinutes
同一个会话、同一轮次永远算出同一个数 —— 所以事后看日志能对上"这次为什么 14 分钟就开口",
而连着十几轮之间又各不相同。jitterMinutes: 0 就退回固定间隔行为。
设计学自(只学设计,没有抄代码):
2huy4n/ProjectKaren(AGPL,随机间隔 / 每日上限 / 状态提示词)、Shitsuten/proactive-nudge(MIT,薄触发层)、pearthink123/revive-companion(MIT,加权节奏)、 yuralume 的主动消息复盘(沉默当状态)。
开口池
poolFile 指向一个纯文本文件,一行一条,# 开头的行当注释跳过;
文件不存在或读不出时用内置的默认池。每次抽一条时尽量避开上一次抽到的。
安装
把这个目录放进 DSH 插件源目录,然后:
dsh plugin --profile web add /path/to/dsh-selfwake
改完配置需要重启一次 DSH 才装载。
配置
| 键 | 默认 | 说明 |
|---|---|---|
enabled | true | 总开关 |
tickSeconds | 60 | 多久检查一次 |
intervalMinutes | 20 | 两次开口的基准间隔 |
jitterMinutes | 8 | 间隔抖动(±):摇出基准上下浮动;0 = 退回固定间隔 |
dailyMax | 12 | 每天最多开口几次;0 = 不限 |
generateText | true | 生成模式:注入"状态+约束"让模型现说;false = 抽池子直接发(老行为) |
backoffPerUnanswered | 0.5 | 连续没被回的加权系数(间隔 × (1 + n × 这个));0 = 不加权 |
replyWindowMinutes | 10 | 开口后多久之内有动静算"用户接了" |
minIdleMinutes | 20 | 使用者要安静多久才允许开口 |
maxUnanswered | 7 | 连续多少次没回话就闭嘴 |
quietStartHour | 0 | 静默时段起(含) |
quietEndHour | 7 | 静默时段止(不含) |
notify | true | 投不进会话时是否发系统通知 |
notifyScript | '' | 发通知的脚本路径,见下 |
stateDir | $DSH_HOME/selfwake(无 DSH_HOME 时 ~/.dsh/selfwake) | 状态与日志目录 |
poolFile | '' | 开口池文件 |
sessionsRoot | $DSH_HOME/sessions 之类 | 找"最近活跃会话"的根目录 |
device | 【PC】 | 日志与通知里标注的设备名 |
家目录一律按
$DSH_HOME→~/.dsh的顺序解析:便携部署 / 多实例 / U 盘随身版会设DSH_HOME, 状态就不会漏到宿主机的~/.dsh里。
通知脚本约定
notifyScript 会被这样调用(Windows):
powershell -NoProfile -ExecutionPolicy Bypass -File <notifyScript> -Title <标题> -Body <正文>
脚本接收两个参数 -Title / -Body,退出码 0 视为发送成功;超时 20 秒。
自检
node index.js --selftest
限制(如实说明)
- 叫不醒睡着的电脑:定时器活在进程里,电脑得开着、DSH 得在跑。
- 窗口关着时投不进会话:那时没有活跃 agent 可投,会退成通知,日志里写明原因。
- 它不碰会话正文:判断"使用者是否安静"只看会话文件的修改时间。
许可
MIT
Comments
Loading…
Similar plugins
by tianyaojiudi-prog
DSH(DeepSeek Harness) 插件:把一段可在设置页随时改写的文字,作为全局系统提示词段注入到所有会话,含子代理与工作流内部子代理;文本与开关即时生效,无需重启。
★ 8
MIT
JavaScript
Oct 4, 2026
dsh plugin --profile web add dsh-djy-xttscby ChuxueTeam
DeepSeek Harness 插件 · 需要你本人回到电脑前时弹窗并出声喊你,提示音一直响到你亲手关掉。 | DSH plugin: pops a card and rings on loop until you dismiss it.
★ 0
MIT
JavaScript
Oct 5, 2026
dsh plugin --profile web add dsh-callerby duanyunlun
DeepSeek Harness(DSH)对话互通插件:同一进程里的对话可以互相发现、直接发消息、盯进度、设护栏——不用先绑定,也不用开子代理。多对话协作 / 跨会话通信 / 监工。Peer messaging, supervision & tool guards between conversations.
★ 0
MIT
JavaScript
Sep 17, 2026
dsh plugin --profile web add dsh-conversation-linkby maozhuoshushu
DSH 宿主内哨兵:同步调用打点 + 主线程卡死取证 + 内存三闸(写闸 / 冷回填闸 / 闲置会话下放)+ 队列哨兵。零依赖,全中文。
★ 0
MIT
JavaScript
Oct 7, 2026
dsh plugin --profile web add dsh-local-host-guardby xthreehao
DSH(DeepSeek Harness)隐藏会话插件:把会话从侧边栏隐藏、可随时恢复。免构建零依赖,契约取自运行中的 0.2.0-rc.2。
★ 0
MIT
JavaScript
Oct 2, 2026
dsh plugin --profile web add dsh-hidden-sessionby Yidien
DeepSeek Harness 旁路捕获插件:随手丢想法进独立的安静新会话,不打断主线上下文。
★ 0
MIT
TypeScript
Sep 4, 2026
dsh plugin --profile web add dsh-capture-window