中文EN

dsh-second-opinion

aginpmMIT

跨会话「第二意见」:一个会话卡住时,把问题交给另一个独立会话单独思考,把方案拿回来继续。不冒充用户发言,也不唤醒闲置会话。

下载量 / Downloads
364

安装

dsh plugin --profile web add dsh-second-opinion

沙箱测试

这个插件还没测过。提交测试 →

README

dsh-second-opinion

给 DeepSeek Harness 的「第二意见」插件——一个会话卡住时,把问题交给另一个独立会话单独思考,把方案拿回来。
求助方在自己的工具调用里同步等顾问答完,答案作为工具结果直接返回:不需要唤醒闲置会话,也不冒充用户发言。
反方向也通:用户可以跑到顾问窗里让它去看看主会话在干什么、留一张便条,全程不打断主会话。

[English](README.en.md) | 中文

dsh-proxy-on-demand — 按需代理,用完自动关

dsh-game-guard — 游戏时收起打扰面

dsh-hot-reloader — 改完插件源码不用重启

dsh-session-clock — 按会话授时与空闲间隔

dsh-second-opinion — 卡住时找另一个会话要第二意见(本仓库)

dsh-onebot-companion — QQ 通道一体化:出站沉默守卫 / 行为规则 / 对话节奏 / 人设 / 语音


一句话说明

模型陷入思维僵局时有个结构性缺陷:打转当下每一步看上去都像在推进。

站在里面的那个会话识别不出自己在原地绕圈,它只会继续试下一个变体。

打破僵局需要的是一个外部视角——不了解你的思路、不会被你的假设带着走,

从头读一遍问题然后说「你第三条假设错了」。

本插件提供这个视角,成本是一次工具调用:

A 卡住 → 调 ask_advisor → 插件为 A 建一个专属顾问会话(名为「辅助+前三字」)→ B 独立分析 → 答案回到 A

每个求助会话有自己的顾问(没求助过的会话不会被建),同一个问题想让第二个独立会话再想一遍时,传 advisor: 2 再要一个。


为什么需要它(宿主已有的两条路都不合用)

DSH 已经有跨会话通信能力,但两条现成通道都做不成这件事:

通道 · 能叫醒闲置会话 · 对方看到什么 · 为什么不用

sessionController.prompt · ✅ 自动 resume · 「用户本人发言」 · 它把消息来源硬编码成 kind: "user"(@deepseek-ai/dsh-api-session-controller 内 const source = { kind: "user", rpcId: request.requestId, ... })——冒充用户,不可接受

send_message · ✅ 起新 turn · agent-message · relay · 授权档位把它限制在父子链上(dsh-subagent 里比对 session.header.parentSession),够不着两个平级会话

缺口 = 跨任意会话 + 能唤醒 + 不冒充用户——这三项组合宿主没有提供。

本插件的办法是绕开「跨会话投递」这件事本身:求助方不把问题发给顾问、

等它回头;而是在自己的工具调用里同步等。工具结果天然属于发出调用的那个会话,

根本不需要任何投递,于是上面三条限制一条都不撞,也没有任何人被冒充。

代价是求助方在等待期间阻塞——而这正是想要的语义:卡住的那个会话本来就该

停下来等一个外部判断,而不是一边打转一边等。


工作原理

┌──────────────┐   ask_advisor(...)      ┌─────────────────┐
│  会话 A      │ ──────────────────────▶ │ dsh-second-     │
│ (卡住了)    │                         │ opinion         │
│              │ ◀────────────────────── │                 │
└──────────────┘   工具结果 = B 的答案    └────────┬────────┘
                                                  │ ① resolveAgent / create
                                                  │ ② followup(prompt)
                                                  │ ③ 等 B 的 status 变回 idle
                                                  │ ④ snapshotEvents(边界) → 取最后非空 assistant 消息
                                                  ▼
                                          ┌─────────────────┐
                                          │  顾问会话 B      │
                                          │ (自动创建,每     │
                                          │  个求助方一个)    │
                                          └─────────────────┘

四个关键实现决定:

1. 答案边界靠 session.seq 快照。 投递之前记下顾问的日志长度,

投递后只读那段新增事件。这样即使顾问会话被复用了很多次,也只会取到

本次咨询的答案,不会串到上一次。

2. 取答案用宿主自己的规则。 lib/compose.js 的 extractAnswer 与

@deepseek-ai/dsh-subagent 的 finalAssistantOutput 保持一致:取

最后一条非空 assistant 消息,空 content 的消息(记账用)不能覆盖已有正文。

这样顾问「先给正文、再补一次只有工具调用的空消息」时,正文不会被吞掉。

3. 同一个顾问槽位串行排队。 每个求助会话有自己的顾问,但同一个槽位可能被

并发咨询(同一会话的两处工具调用、或它派生的子代理),此时后者的答案边界会和

前者重叠、读到别人的回答。per-advisor 的 Promise 链把并发请求排成队列。

4. 等待以 agent.status 为准,不靠 whenIdle() 单打独斗。 宿主

wakeDriver 在 agent 已经不是 idle 时会直接 return、不新建 activityDone

(dsh-agent-loop 的 wakeDriver() 开头 if (this.phase.kind !== "idle") { ... return; })。

那种情况下 whenIdle() 解析的是上一轮的 promise,会在本轮答案产生之前

就返回,让我们读到空结果。所以这里轮询 status,whenIdle() 只当加速信号。


工具

ask_advisor

把一个卡住的问题交给独立的顾问会话。

参数 · 必需 · 说明

question · ✅ · 要顾问回答的具体问题。写成一个明确的问句,而不是「帮帮我」

situation · ⭕ · 你卡在哪:正在做什么、期望什么、实际发生什么

tried · ⭕ · 已试过哪些做法、各自为什么不行。这段最有价值——它让顾问不必重复你的死路

context · ⭕ · 相关上下文原文:文件路径、代码片段、报错信息

advisor · ⭕ · 用第几个顾问,默认 1。想让同一个问题由另一个完全独立的会话再想一遍时传 2、3……每个槽位是独立顾问会话,互不知道对方的存在

返回:

{
  "ok": true,
  "advisor": { "sessionId": "session-...", "title": "辅助正则匹", "slot": 1, "cwd": "D:\\...\\你的工作目录" },
  "elapsedMs": 48213,
  "timedOut": false,
  "answer": "……顾问的完整回答……"
}

失败或超时返回 { ok: false, ... },不抛异常——工具内部整段 try/catch,

出错只返回错误对象,绝不会阻断对话。

advisor_status

查本会话的顾问状态,或重置它。

参数 · 必需 · 说明

reset · ⭕ · 设为 true 则丢弃本会话的顾问记录,下次咨询会新建一个(不删除旧会话本身)

all · ⭕ · 设为 true 则额外列出所有求助会话各自的顾问

返回 callerSessionId / callerTitle / advisors(数组,每项含 slot /

sessionId / title / cwd / live / consults / lastConsultAt /

createdAt)/ droppedAdvisorSessionIds / legacyAdvisorSessionId /

maxAdvisors / stateFile / notes。

advisor_watch(顾问专用)

上面两个工具是「求助方 → 顾问」方向;这个和下面的 advisor_note 是反方向:

用户自己跑到顾问窗里说「去看看主会话在干什么、搭把手」。

查看你正在辅助的那个主会话现在在干什么:在不在跑、进度停在哪一步、距上次

求助过了多久、有没有换话题、最近说过什么、最后调了哪些工具。

参数 · 必需 · 说明

detail · ⭕ · summary(默认)给完整实况;tools 只给最近的工具调用清单

全程只读。 它走 sessionController.inspect——宿主的注释明确写着

without activating its Agent:会话已附加时读内存快照,冷会话读持久化,两种情况

都不会 resume、不会唤醒对方、不会往对方的日志里写任何东西。运行状态另从

sessionController.list() 取(同样不 resume)。这正是「不打断主会话思考」的技术保障。

返回 `{ ok, principal: { sessionId, slot, title, running, cwd, lastActivityAt,

idleMs, sinceLastConsultMs, topicMoved }, report, note }。report` 是给人读的

散文实况;running 拿不到时是 null,报告里如实写「运行状态未知」——**绝不默认

成在跑或空闲,那是编造**。

advisor_note(顾问专用)

给你辅助的那个主会话留一条便条。主会话下次调用 ask_advisor 或

advisor_status 时会连同工具结果一起看到它,从而知道你在旁边看着、以及你发现了什么。

参数 · 必需 · 说明

text · ✅ · 便条正文:你观察到了什么、建议它做什么、依据是什么

便条只是留言:它不弹窗、不改变主会话正在进行的思考,要等主会话自己下一次

调用才会读到。所以写的时候把结论和依据一起写清楚——主会话看到时可能已经又往前

走了一段,你得让它能自己判断这条是否还适用。

便条按求助会话归档,队列上限 maxNotes(默认 5 条,超限丢最旧——它们也最

可能已经过时),并且是消费式的:主会话读过就清空,不会每调一次工具就重新看到。

谁能调这两个工具? 只有本插件创建出来的顾问会话能——靠反

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:2,最后更新 2026-11-01

链接

GitHub · Homepage · npm

同类插件