Перейти к основному содержимому
R

dsh-session-folders

romantic123fly/dsh-session-folders

Gives every new conversation its own folder inside the workspace directory, names the folder after the conversation title, moves the agent working directory into it, and adds a session-menu delete action that asks before removing a folder that still contains files. It uses the same npm package name as DieJob/dsh-session-folders and does a similar job, so enable only one of the two; its HTTP routes have no DSH access check.

Установка

dsh plugin --profile web add github:romantic123fly/dsh-session-folders

README

会话文件夹(dsh-session-folders)

在工作区里新建会话时,自动在该工作区路径下创建一个专属文件夹,并把新会话的工作目录切换到该文件夹。 会话在侧边栏里仍然归属原来的工作区,但它读写文件、执行命令、访问相对路径时都只发生在这个文件夹里 —— 相当于每个对话一个文件夹。

行为

  1. 你在左侧新建工作区并指定路径(例如 E:\项目资料)。
  2. 在这个工作区下新建会话时,插件监听会话创建事件(session/created),确认该会话的原始目录正好等于某个已注册工作区的路径。
  3. 在 E:\项目资料 下创建文件夹,默认就用会话标题命名,例如 体检报告分析与健康趋势。
    • 创建的那一刻标题还没生成,所以先建成会话 id 的短名(例如 9f3a1c2b);标题一落库就原地改名为标题,会话的工作目录跟着改。
    • 之后你在界面上重命名会话,文件夹也会跟着改(同一次运行内)。
  4. 插件向会话写入标准事件 working-directory/change,把该会话的有效工作目录改为这个文件夹。
    • 会话头部(header)里记录的仍是工作区路径,所以分组不会被破坏,会话不会掉到"未分组"。
    • 这个变更发生在模型第一轮之前,系统提示里的 Current working directory 直接就是新文件夹。
    • 直接写事件、不经过 workingDirectory.set(),因此不会在对话里插入一条"工作目录已更改"的可见提示。
    • 另外每轮开始前(agent/pre-step)还会再校验一次:即使创建那一刻没建成(例如工作区注册表还没就绪),也会在第一轮工具执行前把文件夹补上。
  5. 该会话之后创建的每个文件、每次 cd 的起点都在这个文件夹内;换一个会话就是另一个文件夹,互不干扰。

命名

默认模板 {title}(只用会话标题),支持以下占位符:

占位符含义示例
{title}会话标题(还没生成时渲染为空)体检报告分析与健康趋势
{date}本地日期20261110
{time}本地时间(时分秒)102345
{id}会话 id 去掉 session- 后的前 8 位9f3a1c2b
{session}完整会话 idsession-9f3a1c2b-...
  • 名称里 Windows 不允许的字符会被替换为空格;重名时自动追加 -2、-3。
  • 标题最多取 60 个字符,整个文件夹名上限 100 个字符。
  • 想让文件夹名带日期,就把模板改成 {title}{date};想要唯一性可加 {time},例如 {title}-{time}。
  • 只有在本次运行内由插件创建过文件夹的会话才会跟着标题改名;重启后不会再追溯改名(避免误改你手动命名的目录)。

配置

在插件的 cordis.patch.yml 行里(安装后位于 Harness 配置的用户补丁文件中):

键默认值说明
enabledtrue关掉整个行为但保留插件。
folderName'{title}'文件夹名模板。
includeDerivedSessionsfalse是否也给分支会话(fork)和子代理会话单独建文件夹。默认关闭:它们沿用父会话的文件夹,保持文件上下文连续。
isolateSessions[]需要补充隔离的会话 id 列表。列在这里的会话即使已经有对话内容,也会在下次被打开/恢复时建文件夹并切过去(用来补建历史会话的文件夹)。补完可以从列表里删掉。
debugfalse往 ~/.dsh/session-folders.log 写诊断行(每个决定一行)。排查问题时临时打开。

判定规则(为什么有的会话不建文件夹)

session/created 不只在"新建会话"时触发,打开旧会话、DSH 启动恢复会话、在会话里点"新对话"清空重开时也会触发(清空重开的会话会保留原来的 createdAt,所以不能用"创建时间新不新"来判断)。插件按下面顺序判定,任何一条不满足就跳过:

  1. 会话必须有 header.cwd;
  2. 该 cwd 必须恰好等于某个已注册工作区的路径(忽略大小写/分隔符差异);
  3. 会话日志里还没有模型的回复(有 assistant 消息就说明这段对话已经跑过,属于旧会话,不动它);
  4. 会话还没有被隔离过(当前工作目录仍等于工作区根目录);
  5. 不是子代理/分支会话(除非 includeDerivedSessions: true);
  6. 例外:会话 id 出现在 isolateSessions 里时,跳过第 3 条,强制补建。

同一套判定还会在每轮开始前(agent/pre-step)再跑一次:如果创建那一刻因为注册表还没就绪、或工作目录指向了别处而没建成,这一步会补上,保证第一轮工具执行时文件夹已经存在。已经隔离过的会话在这一步直接跳过,不会二次搬家。

不会做的事

  • 不会改动已经有对话内容的旧会话(除非把它的 id 写进 isolateSessions)。
  • 在会话里点"新对话"清空重开时,会当成一个新对话,另建一个文件夹(旧文件夹保留旧对话的文件)。
  • 不会重命名升级前就已经建好的文件夹(例如 20261110-141707-…);那些文件夹是旧会话的工作目录,改名会让旧会话找不到自己的文件。
  • 不会修改会话头部的 cwd,也不会改动工作区注册信息;工作区路径和文件本身都不动。
  • 不会处理工作区之外的会话(例如默认目录里新建的会话),它们没有对应工作区,插件直接跳过。
  • 新建后从未发过消息的空会话会留下一个空文件夹;这个空文件夹会在你删除对话时一起清掉(见下一节)。

删除对话与清理文件夹

左侧会话列表的 ··· 菜单里多了一条 「删除对话(含工作区文件)」(原来的「删除」菜单项仍然保留)。点击后的流程:

  1. 插件先向宿主查询这个对话的文件夹:路径、是否存在、里面有几个条目;
  2. 没有文件夹,或文件夹是空的 → 直接删掉对话(空文件夹一并删掉),不弹窗;
  3. 文件夹里有内容 → 弹窗「是否删除对应工作区文件?」:
    • 是(连文件一起删) → 删除文件夹及里面所有文件,再删对话;
    • 否(只删对话) → 只删对话,文件夹和文件留在工作区里;
    • 取消 / Esc / 点背景 → 什么都不做;
  4. 查询失败时弹窗会说明原因,并提供「仅删除对话」作为保底选项。

删除对话优先走本机已安装的 @michengai/dsh-archive-manager 提供的 workspaceRegistry.deleteSession(sessionId)(它会同时清理会话日志、投影缓存和子会话);如果它不可用,插件退回去自己删会话存储目录、投影缓存并从工作区里摘除该会话。

安全限制:只有当目标文件夹严格位于某个已注册工作区之内、且不等于工作区根目录时才会被删除;任何越界路径都会被拒绝并记入日志。

删除接口是宿主上的两个本机同源 HTTP 路由(/api/dsh-session-folders/inspect、/api/dsh-session-folders/delete),要求请求头 x-session-folders: 1、回环地址与同源 Origin;浏览器半侧在 client.js 里,只负责菜单项和弹窗。

排查

插件把每个决定写进 ~/.dsh/session-folders.log(需要 debug: true):

日志行含义
module loaded (build N)模块被进程加载了(重启后应出现一行)。
apply build=N ...插件已激活且配置生效;forced=[...] 是 isolateSessions 当前列表。
created id=... seq=... cwd=... derived=... forced=...收到会话创建/打开通知;forced 表示命中了 isolateSessions。
workspaces=[...]读取到的工作区路径列表。
folder=... / committed ...建目录成功、工作目录已切换。
skip: ... / error: ...跳过原因或失败原因。
inspect id=... managed=... content=... count=...删除前查询文件夹:是否由插件管理、是否有内容、有几个条目。
delete id=... folder=... via=... deleted=...删除结果:是否连文件夹一起删、走的是 workspaceRegistry.deleteSession 还是内置存储清理、对话是否删掉。
refused folder delete not-inside-workspace ...目标文件夹不在任何已注册工作区内,拒绝删除(安全保护)。

兼容性

  • 面向 DeepSeek Harness(DSH)桌面版;Host 半侧依赖 workspaceRegistry、webServer 服务与 session/created、agent/pre-step、session/event、agent/turn-stopping 事件,以及核心的 working-directory/change 事件投影。
  • 删除对话优先复用本机可能安装的 @michengai/dsh-archive-manager(它给 workspaceRegistry 补了 deleteSession);没装也能工作,会走插件自带的清理逻辑。

安装

本插件是一个 DSH bundle(Host 半侧 index.js + Web 客户端半侧 client.js),用 Harness 的插件管理器安装:

  1. 把本仓库克隆或下载到本地,例如 D:\dsh-plugins\dsh-session-folders。
  2. 在 DSH 里让模型执行 plugin_manager action: install_bundle target: <上一步的绝对路径>。
  3. 重启 DSH,然后新建一个工作区并在其下新建会话,即可看到专属文件夹。

安装后 profile 的 package.json / pnpm-lock.yaml 会以 link: 指向这个源码目录,node_modules/dsh-session-folders 是指向它的目录联接(junction)。因此:

  • 移动或删除源码目录会让插件失效:移动后需要把 profile 里的 link: 路径和那个联接一起改到新位置,或先移除再重新安装。
  • 修改 index.js / client.js / cordis.patch.yml 后,在插件管理里把本插件关掉再打开即可热加载(debug 打开时日志里会出现 module loaded (build N));改动涉及 Host 路由时可能需要重启。
  • 本目录只是插件源码,不是"会话文件夹";插件只会给新建会话在工作区根下建一个以会话标题命名的文件夹(标题就绪前临时用会话 id 短名,如 9f3a1c2b)。

开发

npm test
# 等价于依次运行:
node tests/run3.mjs   # 创建 → 命名 → 跟随标题改名
node tests/run4.mjs   # 旧会话判定、isolateSessions 补建
node tests/run5.mjs   # 删除接口(inspect / delete 路由)
node tests/run6.mjs   # agent/pre-step 兜底

四套仿真都不需要真正启动 DSH:用假的 ctx、临时工作区和临时 HOME 跑真实文件系统,全部输出 ALL CHECKS PASSED 即通过。

许可

MIT

Похожие плагины