DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

kira905 /

kira905/dsh-session-title-live

Topic repository only

DSH plugin: live session titles with state prefixes (running/done), pin-safe and fully configurable | DSH 插件:会话标题实时刷新 + 状态前缀,pin 安全、可配置

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@fb60bcbd

dsh-session-title-live

一个 DeepSeek Harness (DSH) 插件:让会话标题跟着对话实时更新。

为什么需要它

DSH 自带的标题机制只在会话第一条用户消息时生成一次标题(automatic: "first-prompt")。 长会话聊到十几轮之后,侧边栏显示的还是开头那句话,跟会话实际内容完全脱节 —— 而会话列表恰恰是你找会话的唯一入口。

本插件注册一个 automatic: "all-prompts" 的标题 provider:每条人类消息之后都会用 「字节预算内最近的人类消息」重新生成一次标题。它不替换核心的 session-title 服务, 而是注册进它(和官方 provider 同一个扩展点)。

顺带解决一个相关问题:回合边界自动状态前缀。如果团队约定「干活写【运行】、收尾写【完成】」, 只靠 agent 自觉去写,实测绝大多数会话都不会有标记;本插件在 turn/start / turn/end 边界自动把前缀写上(前缀文案可配置,不想用可以关)。

  • 零第三方依赖:只用 node:fs / node:os / node:path(node:zlib 只在验证脚本里用)。
  • 零主包 import:插件随 profile 部署,运行期解析不到全局 npm 里的 @deepseek-ai/* 包, 因此事件名 / 服务名内联在 lib/host-protocol.js 一个文件里(升级主包时只核对这一处)。
  • 不读会话文件:只订阅会话事件、只 append session/title 事件(写标题是主包公开的事件契约)。

一、能力

功能 说明
实时重生成标题 每条人类消息后重新生成;选材 = 上一版标题的依据(主题锚)+ 最近消息,按字节预算收拢
不覆盖人工改名 你在 GUI 里手动改过标题之后,本插件不再动它(pin,与打标路径共用同一份判定)
回合边界状态前缀 turn/start → 【运行】,turn/end → 【完成】(文案可配、可关)
预置标题保护 会话创建阶段已写好的标题(任务卡 / 外部流程预置):只换前缀、不被摘要顶掉
自建调度 主包的自动调度在首轮之后拿不到启动时机(见 lib/index.js 文件头),所以本插件自己在 llm/stream 钩子里触发;两条通路由轮次认领去重,一轮最多一次生成
最小信息门槛 已有标题时,本轮新增消息太少(默认 < 20 字)就不重生成,避免「好」「收到」这类短噪声把标题带跑
活开关 一个 JSON 文件 / 一个环境变量即可停掉(不重启),关掉后零写入、零 token
子代理会话跳过 子代理 / 派生会话不打标、不生成

二、前置条件

  • DSH 主包 >= 0.1.1-rc.2 < 0.2.0(兼容性细节见第六节)
  • Node.js >= 18
  • 一个能改 profiles/<name>/ 的 DSH 安装(默认 profile 名为 web)

三、安装(从零开始)

0. 先备份

改 profile 之前把 cordis.patch.yml(必要)与 package.json、.package-map.json(若你会改) 各复制一份留底。

1. 把包放进去

git clone <仓库地址> dsh-session-title-live

然后装进你的 profile(二选一):

方式 A(推荐,声明式) —— 在 profiles/<name>/package.json 的 dependencies 里加一条,再安装:

"dependencies": {
  "dsh-session-title-live": "file:../../path/to/dsh-session-title-live"
}
cd <DSH_HOME>/profiles/<name>
# 用你平时给这个 profile 装插件的那条命令安装(pnpm / npm 均可)

方式 B(手动,不装依赖) —— 直接把包目录复制成实体目录:

cp -r dsh-session-title-live <DSH_HOME>/profiles/<name>/node_modules/dsh-session-title-live

两者差别只在「谁负责把文件放到 node_modules 下」,插件行为一致。

2. 注册插件 + 关掉官方那个首轮 provider(关键,别跳过)

把 cordis.patch.yml.example 那段追加到 profiles/<name>/cordis.patch.yml 末尾。它做两件事:

- disable:
    - id: session-title-llm        # 官方「只在首轮生成标题」的 provider:先关掉,避免两条抢着写

- insert:
    - id: session-title-live
      name: 'dsh-session-title-live'

⚠️ insert 的 id 不能与已有条目重复(重复会崩 duplicate loader entry id)。加之前先查:

grep -n "id:" <DSH_HOME>/profiles/<name>/cordis.patch.yml

本插件没有 client 半(package.json 里没有 dsh.client 字段),因此理论上也可以放进 dsh.profile.bundles;但推荐仍然走 patch insert —— 上面那两条要一起改才生效, 写在同一个 patch 里最好维护。若你偏好 bundles,请自行验证(见第七节「已知限制」)。

3. 重启并验证

重启 DSH(或让它的守护进程拉起新配置),然后在 GUI 里:

  1. 随便开一个会话,发一条消息 → 侧边栏标题应该在几秒内变成与新内容相关的;
  2. 再发几条不相关的消息 → 标题会跟着变(不是停在第一条);
  3. 手动把标题改一下 → 此后本插件不再覆盖它;
  4. 想确认插件真的加载了:日志里应有一行 [dsh-session-title-live] registered: all-prompts live title provider|route=… selfSchedule=true …。

四、配置

三层合并,后者覆盖前者:

  1. lib/config.js 里的默认值(即示例文件里的值);
  2. SESSION_TITLE_LIVE_CONFIG 指向的 JSON 文件(不设这个环境变量就不会读任何文件);
  3. 宿主 cordis.patch.yml 里该插件 config: 段。

对象段(marks / killSwitch)深合并(只覆盖你写的那几个键);非法值会被清洗成默认值 并打一条 warn 日志(不会让插件起不来)。

目录解析(home)走 SESSION_TITLE_LIVE_HOME > DSH_HOME > ~/.dsh,跨平台,没有机器路径硬编码。

键 默认值 说明
targetWords 5 非 CJK 语言的标题目标词数(只写进提示词)
targetCjkCharacters 10 CJK 目标字数(只写进提示词)
maxInputBytes 32768 送进模型的输入字节预算(超了从最新消息往前收拢)
maxOutputTokens 128 标题输出 token 上限
maxTitleBytes 80 标题落盘的 UTF-8 字节上限(与主包标题上限一致)
timeoutMs 30000 单次生成超时
provider / model 不设 成对配置则用独立路由生成标题;都不写 = 跟随会话主请求的路由
promptIntro 见示例 送进模型的 JSON 数组前的引导句
systemPrompt 不设 自定义系统提示词(字符串或字符串数组),省略 = 内置
selfSchedule true 自建调度总开关(false = 退回主包调度原状,多数情况下标题就不会刷新了)
selfScheduleMaxHumans 32 每会话内存里保留的人类消息条数上限
minIncrementChars 20 最小值门槛:新增字数低于它就不重生成(0 = 关)
themeAnchorMax 6 主题锚(上一版标题依据)保留条数上限(0 = 关锚)
turnMarks true 回合边界自动打前缀总开关
markBaseBudgetBytes 64 加前缀前的基名字节预算
markProvider dsh-session-title-live#turn-mark 打标写回时 source.provider 的名字(用于识别"这条是我们写的")
marks.prefix {running:'【运行】', done:'【完成】'} 状态 → 前缀文本
marks.textToState {运行:running, 完成:done, 待命:idle, 已中断:interrupted} 前缀文本 → 状态(键不要带括号)
marks.baseFallback 会话 标题只剩前缀时的兜底基名
killSwitch.file dsh-session-title-live.json 活开关文件名(相对 home)或绝对路径
killSwitch.moduleId session-title-live 开关文件里 modules.<id>.enabled 的 id
killSwitch.cacheMs 3000 读盘缓存(手改文件 ≤ 该毫秒数生效)
killSwitch.enabled true 直接停用(等价于开关文件里写 enabled:false)
killSwitch.resumeHint 见源码 关闭后打进日志 / 错误信息的恢复提示

⚠️ 改了 marks.prefix 记得让 marks.textToState 能认出你写出去的前缀形状。 本插件把两个来源合并成识别表(prefix 的字面值 + textToState 的键),所以像 [RUN] 这种自定义前缀也能被认出来、不会在下一轮叠一层;但如果你把前缀改成"无包裹"的裸词(例如 运行 ), 建议同时把它加进 textToState 以免误伤正文。

活开关(kill switch)

// <home>/dsh-session-title-live.json
{ "modules": { "session-title-live": { "enabled": false, "why": "在查别的问题,先别刷标题" } } }

也支持顶层简写 { "enabled": false }。enabled:true = 开。

  • fail-safe:文件读不到 / 坏 JSON / 没有该模块键 → 继续工作(缺文件 ≠ 要求关闭)。
  • 环境变量:SESSION_TITLE_LIVE_DISABLED=1 立即停(优先于文件;容器 / CI 用)。
  • 关闭后:不打标、不生成、零写入零 token(每次回合 / 每次生成前现读,≤3s 生效,不必重启)。

五、状态前缀(回合边界)

  • turn/start → 标题写成 【运行】<基名>;turn/end → 【完成】<基名>。
  • 只换前缀、不动基名;前缀已经是当前状态就不重复写(幂等)。
  • 绝不用 rename:rename 会把标题 pin 住(主包认定那是人工改名),自动标题从此不再调度。 本插件用 append('session/title', { source: { kind: 'provider' } }) —— 只覆盖"当前标题"的显示值。
  • 人工改名(source.kind === 'user')之后不再打标、不再重生成基名。
  • 已知来源三种(对真实实例核对过):user(人工改名 / 外部预置)、provider(自动生成,含本插件)、 fallback(主包在还没有标题时的确定性兜底)。只有 user 会被当成 pin,fallback 会被正常覆盖。

六、兼容性

主包版本 状态
0.1.1-rc.2 已在真实 DSH 上跑通(长时间使用:标题刷新、回合打标、pin、活开关都验过)
0.1.5 线(静态核对 0.1.5-rc.1 / 0.1.5-rc.2,2026-09-21) ✅ 本插件用到的 11 条宿主契约逐条取证仍在:事件 session/event、user/message、session/title、turn/start、turn/end、request/header、session/disposed,服务 llm(含 llm/stream waterfall)、sessions、sessionTitle。本插件不 import 主包任何符号(全部为字符串契约,集中在本仓 lib/host-protocol.js)
0.1.5-rc.2 端到端 ⚠️ 未实测(与 rc.1 的差异未取证)
自有诊断事件 session/title-llm-request 本插件自身写的诊断事件;官方 0.1.5 线未见同名事件(不影响功能,仅供对账)
>= 0.2.0 不支持(未评估;主包若改标题事件 / provider 契约即失效)

升级主包时请重点复核 lib/host-protocol.js(本插件内联协议常量的唯一落点): 把里面的每个名字到新主包源码里搜一遍,存在即可,不存在就要改。


七、已知限制

  1. 依赖主包的私有事件 / 服务名。本插件不是官方插件,主包改名字就会失效(现象:标题不再刷新, 日志里可能一片安静)。复核入口只有一个文件:lib/host-protocol.js。
  2. 标题生成失败时保留旧标题(不重试):LLM 超时 / 报错 / 输出为空都会走"失败"路径, 主包保留上一版标题;下一条人类消息到来时会自然再试一次。
  3. 最小信息门槛会让某些轮次不刷新:只发「好」这类短消息时,标题保持上一版(这是故意的, 为了不被噪声带跑)。想退回旧行为把 minIncrementChars 设为 0。
  4. 人工改名之后就不再自动更新了(pin 是设计,不是 bug)。想恢复自动:在 GUI 里让标题 回到"provider 来源"没有直接入口 —— 换一个会话最省事。
  5. 自建调度默认开启(selfSchedule: true)。它是为绕开主包调度的时机限制而存在的; 关掉它多数情况下就没有实时刷新了,只在排障对照时才关。
  6. bundles 加载方式未实测(本插件无 client 半,理论可行,但实测路径是 cordis.patch.yml 的 insert)。
  7. 群聊 / 多用户场景未评估:本插件按"人类消息 = source.kind==='user'"取文本, 若你的部署里有多来源消息,选材口径需自行核对。
  8. 0.1.5 线只做过静态核对、未端到端实测(见第六节)。

八、仓库结构

lib/
  index.js           插件主体:provider 注册、自建调度、回合打标、pin 账本、活开关
  config.js          配置层:默认值 ⊕ 配置文件 ⊕ 宿主 config + 校验 + 文案字典
  host-protocol.js   主包协议常量(事件名 / 服务名)—— 升级主包时唯一要核对的文件
examples/
  session-title-live.config.example.json   配置示例(逐项都是默认值)
cordis.patch.yml.example                   注册用的 patch 片段(含 disable 官方 provider)
scripts/
  verify-source.mjs  静态验证:语法 + 配置层行为 + 脱敏扫描(支持 BUILD_* 注入本机标识)
  test-e2e.mjs       端到端:干净临时路径 + 非默认配置 + 假宿主全流程
  verify-live.mjs    对运行中的真实例做**只读**核对(协议常量是否过时)
  probe.mjs          只读探针(RPC / 会话日志解帧 / 事件分析),供上面两个脚本用
_test/
  test-mark.mjs            回合边界打标的离线单测
  test-self-schedule.mjs   自建调度 + 选择器(主题锚 / 最小信息门槛)的离线单测
  test-kill-switch.mjs     活开关的离线单测(解析 / fail-safe / 缓存 / 端到端)

九、开发与测试

# 1) 静态验证(语法 + 配置层行为 + 脱敏扫描;要在本机覆盖实名规则时注入 BUILD_* 见脚本头部)
#    注:机器名 / 用户名 / 目录名 / 同步产品 / 内部专有名 / 个人称呼六类**全部**由 BUILD_* 注入——
#    扫描器源码自身会进公开仓,所以它里面一个真实标识都不写
node scripts/verify-source.mjs .

# 2) 干净路径端到端(自建临时 home + 非默认配置,跑完自动清理;--keep 保留以便排障)
node scripts/test-e2e.mjs

# 3) 三个离线单测(假宿主,秒级,零副作用)
node _test/test-mark.mjs && node _test/test-self-schedule.mjs && node _test/test-kill-switch.mjs

# 4) 对**运行中的隔离实例**做只读核对(协议常量 / 事件形状是否还成立;只读,不发消息)
node scripts/verify-live.mjs --base http://127.0.0.1:3090 --home <隔离实例 home>

发布流程(版本号策略 / 变更记录规范 / 发布前检查单)见 docs/RELEASING.md。


十、相关组件

同属 DSH 生态的伴生组件,各自独立仓、独立版本、许可各自独立;它们都回链到同一份文档仓 ops-handoff-design (Gitee 镜像 https://gitee.com/kira905/ops-handoff-design):

组件仓 做什么 与本组件的关系
dsh-session-title-live 会话标题跟着对话实时更新 本仓
dsh-butler-archive 会话归档管理(把老会话物理移出 sessions 目录,可列出 / 预览 / 恢复 / 删除) 同一个问题的两面:本仓治「标题跟会话内容对不上」,它治「老会话占着启动与加载开销」——都为了侧边栏这个唯一入口还能用
dsh-ecosystem-panel 只读生态总览面板(插件加载 / 补丁 / 技能 frontmatter / 升级风险) 它的第 ① 类「加载状态」抓的就是宿主首页的 boot 清单——本插件到底加载上了没有,在那里一眼能看到(本插件没有 client 半,加载方式只有 cordis.patch.yml 的 insert,见第七节第 6 条)
dsh-diagnostic-tools 诊断取证工具组:依赖闭包体检 + 会话图片附件对账 宿主升级前后先跑它的 closure/;本插件把宿主契约集中在一个文件(lib/host-protocol.js),那份清单就是升级时要逐条核对的输入

组件之间没有代码依赖,也不共享运行时 —— 之所以互指,是因为它们回答的是同一类人的同一批问题 (长期在自有机器上跑 agent:装得下、找得到、看得见、查得清)。谁装谁不装,互不影响。


十一、许可

当前状态:待拍板(暂按 AGPL-3.0 全文备置)。

  • 本仓 LICENSE 是 GNU AGPL-3.0 官方全文,package.json 的 license 字段同为 AGPL-3.0-only;
  • 另一个备选是 MIT(更宽松、便于他人内嵌);
  • 最终采用哪个许可由维护者拍板,拍板后统一改三处:LICENSE / 本段 / package.json。

在拍板之前,请按 AGPL-3.0 的条款使用本文档与代码。

—/ 5

No ratings yet

Manifest verification required

Commit fb60bcbd22a1

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout