巢 · dsh-nest
在设置 → 会话从属里用下拉框安排「谁归谁管」, 然后母会话就能像访问一个 agent 那样访问子会话:原地唤醒、投一句话、等它回答。 不复制历史,不搬会话,它还是它自己。
两半
1. 面板:设置 → 会话从属
- 自动列出这台机器上所有会话(标题、创建时间、是否开着),按关系排成树,子会话缩进显示。
- 每行「上级」是一串标签:
+ 挂到…下拉里用标题挑(不用手打 id),标签上的×去掉一条。 - 一个子会话可以同时挂在多个母会话下面 —— 几个母会话都能访问它。
- 每个母会话前面
▾/▸折叠它的子会话;每行一个 👁 按钮单独隐藏/显示,顶部还能「显示已隐藏」。 折叠与隐藏记在浏览器localStorage(dsh-nest:panel),刷新还在。 - 顶部搜索框按标题/id 过滤;面板会拒绝成环、自挂。
- 关系存在
$DSH_HOME/dsh-nest/relations.json,一条一行,随时改回;它只决定谁能访问谁, 不改任何会话的日志/header,也不改 DSH 侧边栏里的会话树。
2. 工具:把子会话当 agent 用
会话 A ─┐
├──问一句──▶ 会话 B(原样,原地继续)
会话 C ─┘ │
◀──── 它的回答 ────┘
- 访问的是 B 本身:B 的 id、header、历史都不动,新的一轮写进 B 的日志。
- B 看得出是谁在问:消息带
agent-message来源(form: relay,senderSessionId= 发起方)。 - 问完放回去:B 没开着就临时
resume,答完dispose;已经开着就复用那个活 agent。 - 省略
session就是问「我的子会话」:面板里挂给当前会话的那些,只有一个时直接问,多个时会列出来让你点名。
装
已经在 D:\dsh-mod\dsh-nest 写好、并装进 desktop profile 了。换 profile 时:
New-Item -ItemType SymbolicLink -Path "$env:USERPROFILE\.dsh\profiles\<profile>\node_modules\dsh-nest" -Target "D:\dsh-mod\dsh-nest"
# package.json: dependencies 加 "dsh-nest": "link:D:/dsh-mod/dsh-nest";dsh.profile.bundles 加 "dsh-nest"
配置写在 profile 的 cordis.patch.yml(id 对上 nest):
- id: nest
name: dsh-nest
config:
enabled: true
maxListed: 25
titleScan: 60
askTimeoutSeconds: 180
panelTitleScan: 200
改这份 patch 会触发宿主热重载,不用重启;改插件代码(
lib/)要重启才吃得到。 改client.js之后要刷新页面;面板没出现时看下面「面板没出现怎么办」。
怎么用
- 打开 设置 → 会话从属,找到想当子会话的那条,在「+ 挂到…」里选母会话(可以选多个)。
- 回到母会话,说:「问一下你的子会话,它那边测试跑完没有。」
工具:
| 工具 | 作用 | 主要参数 |
|---|---|---|
nest_list_sessions |
列出会话,标出「当前会话 / 子会话 / 开着 / 归在谁下面 / 几个下级」 | query、limit |
nest_ask_session |
访问会话:投一句话,等它回答,把回答带回来 | message、session(可省略=问我的子会话)、wait_seconds |
返回 { sessionId, status, reply, waitedSeconds }:
idle = 答完;running = 没等完(它继续跑);error = 它这一轮挂了(reply 里就是原因)。
配置
| 字段 | 默认 | 含义 |
|---|---|---|
enabled |
true |
关掉就不挂工具也不挂面板接口 |
maxListed |
25 |
列会话时最多给模型看几条 |
titleScan |
60 |
按标题搜时最多读多少条标题 |
askTimeoutSeconds |
180 |
默认等多久;wait_seconds 可单次覆盖,上限 900 |
panelTitleScan |
200 |
面板一次最多读多少条会话标题 |
关系表长什么样
{
"version": 2,
"updatedAt": 1793180938448,
"parents": {
"3b14ac2d-...": ["session-f2c084b0-...", "c0f0eaf6-..."],
"session-d2900ad9-...": ["social-8b23659d-..."]
}
}
parents 是「子 → 一组上级」。v1 里一个子只有一个上级("child": "parent"),读进来会自动升级成数组,
第一次写入时落成 v2 —— 旧文件不用手动改。
改动走面板自己的接口(只收本机回环请求):
| 请求 | 作用 |
|---|---|
GET /api/dsh-nest/relations |
会话列表 + 关系表(面板数据) |
POST { child, parent } |
加一条关系(同一个子再加一个上级就是多母会话) |
POST { child, parent, action: "remove" } |
去掉一条关系 |
POST { child, parents: [...] } |
整组替换 |
POST { child, parent: null } |
清空这个子会话的上级 |
边界(重要的几条)
- 会真的让它跑一轮:被访问的会话会像收到一条消息那样开工 —— 花它的 token、用它自己的模型和工具、可能真的改文件。
- 不能访问自己,也不能访问自己的上级(DSH 血缘里的祖先):会互相等成死锁,直接拒绝。
- 目标已经开着就复用:消息会插进它当前的对话里(有意的)。
- 目标在别的进程里开着时唤不醒:
resume失败会明确报「唤不醒」。 - 超时不掐断它:返回
running后它继续跑,跑完自动放回去;这期间它被占着。 - 两个母会话同时问同一个子会话会排队:不会两个
resume撞在一起(按目标会话串行)。 - 回答 = 这一问之后最后一条带文字的助手消息;只有工具调用的那几步不算。
- 关系表是逻辑的:面板里的从属不会让它在 DSH 的会话树里变成子节点(那得重写会话日志头,风险高,没做)。
面板没出现怎么办
客户端插件是页面加载时注册的,装完要刷新一次页面(Ctrl+R,必要时 Ctrl+Shift+R)。
如果刷新后「设置 → 会话从属」还是没有,先看宿主有没有把这份客户端包识别进去:
http://127.0.0.1:65133/api/dsh-nest/client-status
返回里 present: true 说明已经识别、bundle 就是它要加载的地址;present: false 时带一次
?reset=1 再请求一次(会清掉客户端模块的识别缓存并强制重新识别):
http://127.0.0.1:65133/api/dsh-nest/client-status?reset=1
这个坑踩过一次:插件第一次被加载时 package.json 里还没有 dsh.client,宿主就把「这个包没有客户端」记进了缓存,
之后即使补上声明也不会重新识别 —— 表现就是工具全在、面板永远不出现。
实现
设置面板 (client.js)
└─ GET/POST /api/dsh-nest/relations ──▶ $DSH_HOME/dsh-nest/relations.json
nest_ask_session
├─ 省略 session → 关系表里「我的下级」(唯一才自动用;多上级时几个母会话都能省略)
├─ ctx.agents.get(id) 已经开着?直接复用
│ 否则 readSession() 取它上次的 provider/model → ctx.agents.resume({ agentOptions })
├─ agent.followup({ role:'user', source:{kind:'agent-message', form:'relay', senderSessionId } })
├─ 等它开工(seq 前进 / status=running)→ 再 whenIdle() 与超时赛跑
└─ 读它日志里「这一问之后」的最后一条助手文字;没文字就取 turn/end 的失败原因
几个踩过的坑,都写进代码注释和测试里了:
import { createUserMessage } from '@deepseek-ai/dsh-llm'会让插件在宿主里加载失败 (那个包在插件侧解析不到,整条 entry 挂掉、所有工具消失)。改成手搓 UserMessage。- 刚投完消息直接
whenIdle()会立刻返回,拿到空回答 —— 得先等它真的开工。 - 唤醒"本身是子智能体"的会话时,第一轮会报
prompt variable "{{model}}" has no value; 把会话日志里最后一次request/header的 provider/model 当agentOptions传进去就好了。 - 多上级的环检测:加一条边时看「目标上级是不是已经在子会话的下级里」,别顺着单条父链往上找。
测试
node --test test/client.test.mjs test/core.test.mjs test/plugin.test.mjs test/relations.test.mjs
46 条:关系表的增删改/坏文件/成环/多上级/v1 升级、面板接口(GET/POST 五种改法/403)、 client bundle 自注册与面板挂载、目标匹配与歧义、血缘防环、回答提取、错误原因提取、 注册/卸载、冷会话唤醒+路由+归还、已开着复用、消息来源、超时不掐断、 省略 session 走面板关系、多子会话点名、一子多母。
许可
MIT
No comments yet. Be the first to write one.