dsh-hermes-session
只读读取本机 Hermes Agent 的会话库,把会话导入当前会话作为有界、不可信的背景上下文:输入框按钮可搜索全部 profile 的会话,另有 hermes_sessions_list 与 hermes_session_import 两个工具。
安装
dsh plugin --profile web add dsh-hermes-session
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-hermes-session
把本机 Hermes Agent(另一个独立智能体)的会话接入 DeepSeek Harness:
列出会话、指定一个会话,就把它的完整对话作为不可信背景注入当前 DSH 会话。
三个入口
入口 · 形态 · 作用
+ Hermes · 输入框工具行按钮 · 弹窗列出全部 profile 的会话,选中后插入一个引用 chip 到草稿
hermes_sessions_list · 模型工具 · 列会话(标题 / 目录 / 消息数 / 最后活动),默认扫全部 profile 合并排序
hermes_session_import · 模型工具 · 按 sessionId 导入(支持唯一前缀),注入文本带警示头和截断事实
/hermes 弹窗和工具读的是同一份数据、走同一条注入通道,只是入口不同。
斜杠菜单里只有 /hermes 一条——插件不注册任何 Host 斜杠命令,数据通道走
HTTP 路由(见下面「数据通道为什么是 HTTP 路由」)。
架构
src/index.js 插件入口:注册工具 + HTTP 路由 + systemPrompt 路由段
src/tools.js 两个工具的 schema/execute(exec.deferContext 注入)
src/routes.js Host 端 HTTP 路由:GET /api/hermes.sessions(列表)、
GET /api/hermes.sessions(列会话)
client.js 客户端半部:注册 CommandContribution(popupSelect + icon),
options/onSelect 用 fetch 调上面两条路由
src/bridge.js 子进程调用 hermes_read.py(--db/--profile 前置,避免 argparse 报错)
src/render.js 投影 → 注入文本(警示头 + 元信息 + 预算截断)
scripts/hermes_read.py 只读访问 SQLite,Python 3.8+ 标准库,零第三方依赖
selftest/run.mjs 不经过 harness 的端到端自测(工具 + HTTP 路由)
selftest/clientid.mjs 客户端模块不变量(id === 包名、走路由、图标、无 Host 命令)
/hermes 弹窗的数据流
浏览器输入 /hermes
→ CommandContribution (client.js) 匹配
→ popupSelect.options()
└─ fetch('api/hermes.sessions')
← Host 路由返回 JSON → 解析成 SelectOption[]
→ 用户点击某条
→ 草稿里写入 @[标题](dsh-hermes:<base64url>) ← inputActions.insertText()
→ 用户写描述并发送
→ agent/pre-step 解析提及 → 读会话 → 追加 form:'recall' 上下文
→ 对话里出现一行可折叠的上下文
菜单里的命令与图标
/hermes 是客户端 contribution(CommandContribution),带 `icon:
IconArchiveOutlineRegular`(归档箱)。图标来自
@deepseek-ai/dsh-client-ui-primitives——它是 Web shell 的平台 seed
(staticModules),任何 client 模块 require 即得,无需在 dsh.client 里声明。
/compact /model /export 的图标走的是另一条路:dsh-client-ui-commands 里的
BUILTINS 表把 definitionId 映射到命令名再取 HOST_FACES 的图标——
那张表硬编码在 DSH 内部,外部插件的 Host 命令拿不到。
数据通道为什么是 HTTP 路由
试过三条路,只有第三条同时满足「有图标」和「菜单只有 /hermes 一条」:
1. 自定义 Remote 服务(TypertRemoteService + @Remote):Host 的 Gateway
能发现,但客户端不会出现 remote.hermes——客户端的 ctx.remote.<ns>
由 DSH 自带的 dsh-api-remotes 逐个手工声明(commands / account /
agentPresets / productAnalytics …),没有自动发现机制。客户端 half 永远
pending (waiting for service: remote.hermes),boot 审计判失败,harness 起不来。
2. 斜杠命令做数据通道(ctx.commands.register + ctx.remote.commands.execute):
能通,但 Host 命令无法从菜单隐藏——CommandDefinition 没有 hidden/visible
字段,客户端 candidates() 直接展开 Host catalog(无过滤钩子),每注册一条
菜单就多一项。而图标只给客户端 contribution(CommandContribution 有 icon
字段,CommandDecoration 没有),且 Host 命令与 contribution 不能同名
(同名会让 candidates() throw,整个菜单坏掉)。
3. HTTP 路由(ctx.connection.fetch.register):Host 注册 /api 下的精确路由,
客户端直接 fetch。路由不进斜杠菜单,也不需要任何 Remote 命名空间。
第 3 条有现成范例:dsh-session-log-export 的 /export 就是
ctx.connection.fetch.register({ path: '/api/session.export', ... }) + 客户端 fetch。
鉴权由物理载体处理("after the physical carrier has applied its trust and
authentication policy"),路由本身不用管。
代价:多开两个 HTTP 端点(只读列表 + 只读导入),都在 /api/hermes.* 下。
注入时机与形态:写进草稿,随消息一起发送
用户选完 Hermes 会话时不能直接注入——他还没说要做什么,直接注入会唤醒
agent 让模型自己开跑(这正是被反馈过的问题)。
对齐 DSH 原生 @ 提及会话的做法,也对齐官方「+技能」按钮的实现:**把一条
Markdown 提及写进草稿**,用户写完描述一并发送。
+ Hermes 选中会话 → 草稿里出现一个蓝色引用 chip:@[会话标题](dsh-hermes:<base64url>) → 用户在输入框写描述并发送 → agent/pre-step 钩子解析这个提及 → 读 Hermes 会话 → 渲染成 form:'recall' 的上下文消息 → 追加到该轮用户消息之后 → 模型这一轮两者一起看
chip 走原生 @ 引用同一条路径插入:在会话作用域上 bail
slash/input-insert-reference 事件,由 conversation.input 的监听器转交给
shell.insertReference,所以样式和原生 @ 会话引用完全一致——蓝色、可删除、
文本里带着提及。
附带的上下文长得也一样:source 直接复用原生形状
(kind: 'session-reference' + form: 'recall')——客户端只有看到这个 kind 才会给
会话引用图标和 "contextRecall" 标题,否则退化成通用注入图标;正文也与原生
逐字同构:同样的 ## Referenced sessions 标题、同一段警示语、同样的
<referenced-sessions> 包裹,JSON 载荷是数组,每个会话对象里的
conversation 字段是 [{ role, text }],text 是完整原文。
用自己的 kind 也能用,但会渲染成"通用注入"——同样的能力,两种长相。
因为模型侧看到的提示结构与引用 DSH 会话时一致,模型读 Hermes 快照的方式
和读 DSH 会话快照的方式也一致。
注意:公开给插件的 inputActions 只有 captureInsertion / insertText /
setDraft / addAttachments / removeAttachment / pruneAttachments / submit
——没有插 chip 的方法(insertReference 只存在于 SessionInputShell 类内部)。
拿会话作用域 ctx 的方式:sessions.using(sessionId, ..., ref => ref.binding.ctx)。
弹窗点外部或按 Esc 关闭;按钮是纯文字,与官方「+技能」按钮一致。
两个直接后果:
- 不发消息就不附加任何东西。 选会话永远不会唤醒 agent。
- 提及是普通草稿文本,可改写、可删除、可再加一个会话——模型只看到你真正发出去的。
草稿写入用的是 session slot 的标准 inputActions 面
(captureInsertion() + insertText()):span 必须在插入时刻取,从 React prop
里读的永远滞后一版,会被编辑器的 span-CAS 静默拒绝——弹窗关掉但草稿没变。
(工具 hermes_session_import 走的是另一条路——它在 turn 内执行,
exec.deferContext() 的上下文挂在本次工具结果上,loop 在 tool/result 之后
追加,天然可见,不受这里影响。)
注入在对话里长什么样
注入消息的 source 用 DSH 的 recall 形态:
source: {
kind: 'hermes-session',
form: 'recall',
version: 1,
references: [{ sessionId, label, retainedMessages, omittedMessages, truncated }],
}
form: 'recall' 让 DSH 的 ContextInjectionRow 渲染它——对话列表里是**一行可折叠的
上下文**(折叠时显示来源标签,展开后是代码块样式的正文,max-height 141px 可滚动),
而不是把全文铺开的 user 气泡。契约见 dsh-client-ui-chat 的 recalledSessions():
references[] 每项必须带 label / retainedMessages / omittedMessages / truncated。
这与 DSH 原生 @ 引用会话是同一种呈现——语义也吻合("从另一个会话恢复的上下文")。
数据来源(已实机核实)
Hermes 的会话按 profile 分库:
~/.hermes/state.db→ profiledefault(本机 270 个会话)~/.hermes/profiles/<name>/state.db→ 其它 profile(本机 geek/ngs/paper_reader/prodes/sirna 共 153 个)
主库只有 default 的会话;只读主库会漏掉其它 profile。所以默认扫全部库,
--profile / 工具的 profile 参数可限定。每条结果都标注来源 profile。
会话元信息在 sessions 表,消息在 messages 表(OpenAI 风格:user / assistant(含
tool_calls JSON) / tool(结果 JSON))。只读行过滤 active=1;默认隐藏 archived
和 hidden。
子会话默认带上(与 hermes sessions list 的默认不同):Hermes 会把"续写/子代理"
这类 parent_session_id 非空的会话藏起来,但它们往往正是用户想引用的那一段具体工作。
弹窗列表里会标 子会话。工具的 includeChildren 参数可控制。
搜索在 Host 侧做:过滤框会重新查询整库(全部 profile、全部会话),只返回匹配结果。
如果改成"先拉一页再本地筛",没被拉下来的会话就永远搜不到——曾经就因为这个
(外加写死 --limit 50),"DNA-Diffusion
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:2,最后更新 2026-10-23