DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Session Manager — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins

@he0119/dsh-session-manager

Session Manager

DSH session management plugin: a settings page for exporting/importing .dshsess session packages and migrating workspaces and sessions to a new directory—rewriting session log cwd paths, migrating log directories, reassigning workspace ownership, and optionally moving files created by sessions.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add @he0119/dsh-session-manager@0.6.0
READMECompatibilityVersions

Compatibility and provenance

Session Manager is published as @he0119/dsh-session-manager and currently resolves to version 0.6.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
npm
Registry updated
10/16/2026

Versions

0.6.0stable
10/16/2026
0.5.0stable
10/14/2026

Related plugins

Loading related plugins…

Latest
0.6.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
1.7 MB
Files
43
Surface
web
License
MIT
Source
npm
GitHub
★ 0
Weekly downloads
0
Last push
10/16/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in productivity-workflow.

Acp App@deepseek-ai/dsh-acp-appThe dsh ACP profile bundle: automation-only JSON-RPC stdio and process lifecycle over dsh-baseExperimental Schedule Bundle@deepseek-ai/dsh-experimental-schedule-bundleOptional bundle that adds Schedule, its task page, and time context to the Web compositionAb Ocrdsh-ab-ocr--- description: "The out-of-tree ocr tool: recognizes a scanned PDF or image page by page, merges the pages in order only after the last one, and saves one Markdown document with the heading levels the model decided." kind: "package-reference" ---Client Ui Task Board@linxin666/dsh-client-ui-task-boardHost-authoritative task board for the DSH Web GUI with real session execution, Host cron scheduling, and optional cross-platform idle-sleep protection; mounted without DSH source changes.

README

View image

dsh-session-manager

DSH 的会话管理插件:把工作区与会话搬到新目录,把会话导出 / 导入成 .dshsess 包,以及用 WebDAV 在多台机器之间同步会话。

中文 | English

在 DSH 里,「会话属于哪个工作区」不是可改字段,而是由会话日志 header 里的 cwd 推导出来的: 宿主既没有 move / reassign API,也不接受项目目录与 cwd 不一致的日志。所以手工搬目录,轻则会话变成 Ungrouped,重则加载时报 corrupt session log。本插件把「改 header cwd + 移动日志目录 + 让宿主当场认下新的归属」 三件事一起做,每次写盘前都会先摆出这份计划、事后能逐字节回滚。(为什么必须这样,见 .agents/notes/implemented/。)

同步走的是同一条路:远端只放 .dshsess 包,拉取时按配置里的映射把 cwd 改写成这台机器的路径, 所以两台机器的同一个项目处在不同目录也能互通。

它能做什么

入口适合什么
设置里的「会话管理」页日常使用:在「会话」分页里逐条归档、重算名称或删除,或者勾上要搬的会话(可以跨目录,源目录被删掉也照旧)、点动作行上那枚「迁移」在弹窗里选目标目录;在「传输」分页里导出 / 导入;在「同步」分页里按 WebDAV 配置同步;在「备份」分页里回滚或恢复
5 个模型工具直接跟会话说「把这个工作区的会话搬到 ~/dev/xxx」或「跟远端同步一下」,由模型先预演再落盘

两个入口的迁移编排是同一份代码,所以弹窗里说会迁移几条,实做就是几条。

页面上的会写盘的动作(删除 / 迁移 / 导入 / 同步 / 回滚)都是一个按钮开一个确认弹窗:弹窗里摆的就是 那份只读计划(哪些会话、落到哪、备份落在哪、有什么问题),按「确认」才落盘,按「取消」则什么都不发生。 模型工具那一路仍然是 plan / apply 两次调用——模型自己先看清楚再落盘。

安装

从 npm 装(推荐):

# 用宿主自己的插件命令:它会替你建/填充 profile,并维护依赖与锁文件
npx @deepseek-ai/dsh@next plugin --profile desktop add @he0119/dsh-session-manager
# 然后重启 DSH

开发时装本目录(lib/ 不进 git,所以先构建):

pnpm install && pnpm run build
npx @deepseek-ai/dsh@next plugin --profile desktop add /path/to/dsh-session-manager
# 然后重启 DSH

装完重启 DSH,设置 的左侧导航里会多出一页「会话管理」(排在官方那几页之后)。 本包不支持 github: 形式的安装:仓库把构建放在 prepublishOnly、lib/ 不入库,git 装法拿不到产物。 宿主版本要 0.2.0-rc.1 及之后的 0.2.x(engines.dsh 与两个 DSH peer 都是 ^0.2.0-rc.1, @deepseek-ai/cordis 另写 ^4.0.4)。宿主按 peer 范围决定装不装:范围收不下运行中的那个版本, 整条 bundle 会被跳过(日志里是 skipping profile bundle … is incompatible with dsh …)。它判范围时带 includePrerelease,所以同一 minor 线上的新 rc(0.2.1-rc.1)照旧收得下;换 minor 线 (0.3.0-rc.1)时要跟着改这一行。

使用

设置 → 会话管理

每个要等一会儿的动作都会报进度:迁移、删除、回滚、导入(预演与落地)的弹窗正文里,归档与打开页面 那次整库扫描的原地,写的是「正在改写日志 8 / 253」+ 一条进度条 + 当前那一条的标题。分母是这一段真正 会做的条数(跳过的那些不算进去,否则条永远走不满);没有分母的段落(读远端索引、写工作区注册表)只说在做什么、不画条。落地那一段里弹窗标题从「将要迁移」换成「迁移中…」,取消键 禁用——中途撒手会在库里留下半截状态。唯一的例外是导出:它的响应体就是那个包,没有"第几条"可讲。

会话——对着整个会话库逐条管理

  • 列表按目录分组,组头可折叠(「全部收起 / 全部展开」在列表上方),组头那一下就是 整组勾选——"把这个旧项目的会话都归档 / 都删掉"因此是一次点击。组头只写着这一组的名字:注册表里的 工作区名称,没加入工作区的目录就取项目身份(有 git remote 时用它的最后一段,如 dsh-session-manager; 没有才退回显示本机路径),所以行里不再重复归属那一列,省下的宽度留给标题;项目身份与本机路径都在 名字那一格的悬浮提示里(两行:身份在上、路径在下——同一个仓库在本机的两个克隆靠路径区分); 有项目身份的目录还在名字后面挂一枚主机名小标签(github.com、git.hehome.xyz),扫一眼就知道 哪些目录是认得出身份的仓库,标签自己的悬浮提示给全整条身份。 落在侧边栏「未分组」那一组里的会话在行上挂「未分组」小标签 (组头说的是目录,标签说的是侧边栏把它放在哪儿)。子智能体会话缩进到父会话的下一级(跟着它、排在 它后面;父会话在别的目录组里时留在自己组里缩进并标明父在哪儿),"这条属于谁"不必读标签猜。每一行 显示标题、字节数与创建时间;宿主侧边栏看不见的会话也在这里(那边 点不到它们),行上挂标签说明原因:「子智能体」/「空白」/「已归档」,还活在宿主内存里的那条挂「活动中」;
  • 筛选:一排小胶囊——子智能体 / 空白 / 已归档 / 未分组 / 活动中,各带库里的条数;勾几枚看几类 (多选=任一命中),「全部」一键清空;下面一行是标题 / id 搜索框("按 id 指名道姓"与"按标题 回忆上次那个问题"都常用,所以两个都搜)。筛过之后头部报「显示 N / M 条」,「全选」选的是眼下列 出来的这些(筛出空白再一键全选就是要删它们),旁边常驻一枚「清空」——它清的是整个勾选集,所以 勾了一部分时也清得掉;勾选不会因为切换筛选而丢;
  • 归档 / 取消归档:勾选后一键收起或放回。走宿主自己的归档能力,即时生效——侧边栏马上跟着变, 不必重启;勾父会话会连它的子智能体一起归档(族是一个单位,理由见下面删除那节);宿主没提供这个 服务(非 Web profile)时按钮置灰并说明原因;
  • 重新生成名称:勾选后一键请宿主的名称服务按整段对话重算名称。历史会话常常停在"首句前几个 词 / 40 字节"的兜底名称上(含网址的首句会被切成半截链接),而名称 provider 只在"又有新的用户消息" 时才修订一次——这一枚按钮就是把它补上。逐条报进度(每条要整份解一次日志再跑一次模型调用),算完 之后分四类说清楚:改了 / 算过但没变 / 跳过 / 失败。跳过不是失败,它是"本来就不该动 它":你手动改过名的(重算会覆盖你起的名字)、子智能体会话(名称在侧边栏里看不见)、还没有 用户消息的、从没有过主模型请求的(没有可用的模型路由)。宿主没有名称服务时按钮置灰并说明原因。 "你手动改过名的"那批想一起算:接口(POST …/retitle)里带 includePinned: true 就放行—— 界面刻意不给这个开关,默认语义就是"别动用户的名字"(一次性清理"名称是脚本或导入写下的"那些会话时用得上);
  • 看内容:鼠标停在某一行上,行尾就出现一枚「内容」(它顶掉那一行的时刻;触屏上一直摆着),点开 就是这条会话的完整内容——日志逐条解析成时间线:人类 消息、模型回答、思考(默认折起)、工具调用与结果、系统消息,以及其余一切事件(压缩、权限、审批、 重试…连名称是怎么定下来的都在)。按轮次分节,点节标题就把这一轮折起来(不属于任何 一轮的会话级事件自己一段,那段没有标题可折,所以不折)。工具结果自己不带工具名,视图按 callId 从调用那条补上;一条事件 一行,seq 会跳号所以只当参照物。分页(一次 80 条起,滚到底自己往下接),单格文本超长时如实 标出"截断了 N 个字符"并给一个「查看全文」;读不出来的坏行照实说有几条、在第几行,其余事件一条不少。 顶上按种类筛选(我 / 模型 / 工具 / 其它)。这一页不经过宿主,所以侧边栏看不见的那些(空会话、 孤儿目录、已归档、子智能体、导入的会话)一样能看;
  • 删除:勾选 → 点「删除所选」,弹窗里列出会被删的每一条、日志数与备份位置 → 确认。删除先备份到本插件的 备份根,再删掉整个会话目录;删完侧边栏要等宿主重新扫描才会少掉那几行。还活在宿主内存里的会话 会被拒绝——重启 DSH 让宿主放手,之后别先点开它再删(Web 端没有单条关闭的动作,见「说明」)。 想反悔就去「备份」分页里点「恢复」;
  • 子智能体跟着父会话走:勾一条父会话,它的子智能体会话(连同更深的那些)会一起删——弹窗里逐条列出 并标明"随父会话删",一个备份装下整族(父日志一没,子会话在侧边栏里就再没有入口,留在盘上等于 变成看不见的残留)。:它的勾选框是灰的(悬浮提示里写着 该勾哪条父会话),直接调接口也会被拒并指名父会话——只删它会在父会话日志里留一条指着不存在会话的 条目。父会话已经不在库里的孤儿例外:没有可跟随的会话,它只能单独收拾。: 它是自洽的一条普通会话(源会话的历史已经拷进它自己的日志),父删掉它照样能打开,所以不带走。

迁移——把勾中的会话搬到一个目录

  • 迁移就在「会话」分页的动作行上,源是你勾的那些会话,可以跨目录:整库本来就是那一页的列表(同一套 按目录分组、行上的标签、类别胶囊与搜索框,勾选也一样),没有单独的迁移分页。所以"项目文件夹被删掉、 项目重建到别处之后把会话迁过去"就是:在列表里勾上那几条(它们在侧边栏里落在「未分组」那一组,那只是 它们的标签),点动作行上那枚「迁移」,在弹窗里选重建后的目录——一次搬完,不必按目录各来一次;
  • 目标目录在弹窗里选:那个字段与「传输」页的落地目录是同一个控件、同一份候选(值控件点开候选 面板,也能手输路径或去文件系统里挑)。候选面板里一行两行字(名字在上,本机路径在下,整串在悬浮提示 里),顶上那个筛选框按路径、工作区名称或项目身份收窄列表——它只筛候选、不改值;点一行就是选中 (随即收起)。盘上已经没有的那些(工作区还登记着,目录被删掉或改了名)照旧列在名单里,但挂一枚 「不存在」的标签、点不动:选它们只会撞上「目标工作区目录不存在」。有 git remote 的目录, 值控件里那一行是项目身份 + 本机路径 (github.com/he0119/dsh-session-manager — /home/uy_sun/dev/dsh-session-manager):这枚控件只有一行 可用,路径不能从这儿消失——同一个仓库在本机的两个克隆只能靠它区分。目标目录必须已存在;旁边是可选的新建 工作区名称与是否连带会话创建过的文件。目标一选好就有一份只读的预演(见下面那条),改目标会重取 一份;
  • 源目录不必还在盘上:清理文件夹时把项目文件夹删掉之后,会话日志还留在库里、header 里的 cwd 也还是那个路径,勾上它们、目标选重建后的目录就迁过去了。从前那种"先选一个源目录"的做法在这里反而没有 落点:那批会话可以散在好几个目录里,而一次迁移只有一个目标,只能按目录各来一次;
  • 勾得动的是"这一页能收拾的",搬得走的是"侧边栏看得见的":清单里唯一勾不动的是挂在父会话下面的 子智能体会话(勾选框是灰的,悬浮提示点名该勾哪条父会话)——空白与已归档的那些在这一页照旧能勾,归档 与删除本来就该收下它们。迁移那一侧因此自己把隐藏的摘出去:宿主对点名的隐藏会话一律拒(工具那一路 点名它们时,计划会如实列出这条拒绝),所以页面只把看得见的那些送进计划,弹窗里多一句"另有 N 条勾中的 会话这次不搬"并写明下一步(已归档的取消归档、空白的删掉、子智能体连着它的父会话一起迁)——说会搬 几条就搬几条,不会因为混进一条而整次落空。它们因此搬不走,而归属还在源工作区里,那块工作区就不会 被删——预演与结果会点名"还剩哪几条、因此不会被删"(工具返回值的 stranded);登记了、盘上却已经没有的那些(插件删除会话时刻意不清注册表留下的悬空登记)不算成员: 宿主本来就不认它们,判"这块工作区搬空了吗"时也不再数它们,并在这一次里顺手从注册表摘掉;
  • 宿主正攥着的会话搬不动:宿主内存里那条(插件列表里带「活动中」标签的那些)还没放下旧路径,挂靠 会被它回绝,日志也不能搬走——计划会把这批摘出去,弹窗里多一句"另有 N 条宿主持在内存里,这次不 搬",它们留在原目录、归属不变。重启 DSH 之后再迁一次即可把剩下那几条收进来;点名点到它们、 或勾中的每一条都还活着时,计划如实拒绝(不是"没选中会话");
  • 子智能体跟着父会话走:勾一条父会话,它的子智能体会话(连同更深的那些)一起搬(归档与导出同样: 勾父会话就把子智能体一起归档 / 一起装进包)——各自的日志 header 改写 cwd、各自的会话目录搬进目标项目目录,一个备份装整族(否则族会被拆到两个目录里,而 子智能体本身是搬不动的:它自己勾不动,点名它又是向上的牵连)。分叉出来的会话不算(它自洽、 父搬走也照样能打开)。搬完成员资格不变:子智能体本来不算工作区成员,搬完还是不算;计划里会报「其中 N 条是 子智能体会话」;
  • 点「迁移」开弹窗,里面是:目标目录那一个字段(以及新建工作区名称、是否连带产物)+逐条列出会被 搬走的会话(子智能体跟着走的那几条缩进一级、挂「随父迁」)、会话数、日志数、字节数、来源项目目录 → 目标项目目录、注册表会怎么变(新建还是复用目标工作区、写进注册表几条、从哪些工作区搬出、是否移除 已空的工作区、顺带摘掉几条悬空登记)、产物计划与跳过原因;搬不走的(宿主持着的、侧边栏不显示的)各多 一句说明——它们留在源工作区里,那块工作区因此不会被删;
  • 按「确认迁移」:改写每个日志 header 的 cwd(只动首帧,其余字节不变)→ 搬会话目录 → 写回注册表 → 交给宿主自己改(复用 / 新建目标工作区、把会话挂过去、摘空就删,见下面「注意事项」)→ 独立复核 (等价于宿主的 corrupt 判据,另加一条"会话 id 不变"的复核)→ 留下字节级备份;完成后页面会告诉你这次 改动是不是即时生效,或者需要重启 DSH。

传输——把会话带走,再带回来

  • 列表里的会话显示的是标题(鼠标悬浮才给出完整标题与 id):uuid 对人是零信息,认会话靠的是 「我上次问那个问题的会话」;读不到标题的老会话回落显示 id。导出列表与「会话」页照单全收、 连侧边栏不显示的会话(子智能体 / 空白 / 已归档)也列出来——"这条会话要不要带走 / 要不要删"是人的 决定,替你预先藏起来只会让"我明明有这条会话"变成找不着的谜;迁移则相反(见下);
  • 导出:勾选会话 → 浏览器下载一个 .dshsess 包。包里是这些会话所有代次日志的原始字节 (逐条带 sha256),加上日志里引用到的图片 / 文件对象——它们在日志里只是一串内容寻址的 id,字节 平时放在本机的附件库里($DSH_HOME/attachments/),不带上的话换台机器打开就全是「图片加载失败」 (同一个对象被多条会话引用只装一份;本机库里找不到的会在导出后那句报告里说清有几个没带走)。不含会话 创建过的普通文件。列表按目录分组(组名是工作区名称;没加入工作区的目录 拿这个目录的项目身份最后一段当组名——有 git remote 就是它,没有才退回显示路径,两种情况都标出来; 身份与本机路径都在组名那一格的悬浮提示里,认得出身份的还挂一枚主机名小标签), 组头那一下就是整组勾选 / 取消——「把这个工作区的会话都带走」因此是一次点击; 组头最前那个尖角是折叠(收起只把组内的行收起来,组头与"这组几条 / 勾了几条"都还在),列表上方那行 「按目录分组」后面是全部收起 / 全部展开:收起是显示上的事,「全选」照旧按列出来的那些算, 不会因为点了箭头就悄悄少导出几条; 两级一眼分得开:组头是带底色的横幅 + 文件夹图形,会话行缩进挂在它下面、挂对话气泡图形(会话名称 是用户写的一句话,很容易长得像个目录名,靠读字分不出来)。子智能体会话再往里缩一级、挂在它父会话 下面(列表里的父子与"删 / 搬父会话会带上谁"是同一棵树);侧边栏「未分组」那一组里的会话在这里 跟着目录走,行上挂一枚「未分组」小标签说明这件事。
  • 筛选:与「会话」页同一套——一排小胶囊(子智能体 / 空白 / 已归档 / 未分组 / 活动中,各带库里的条数, 多选=任一命中,「全部」一键清空)加一个标题 / id 搜索框;两者一起用时是"与"(搜 foo 且只看空白)。 筛过之后头部报「显示 N / M 条」,「全选」选的是眼下列出来的那些(旁边常驻的「清空」清的是整个 勾选集);筛空的那一组整组不画 (组头底下没有行,看着像坏了),组头报的也是筛剩下的条数;
  • 导入:选包 + 选目标目录——那一个字段与迁移的目标目录同一个控件、同一份候选(值控件点开候选 面板,也能手输路径或去文件系统里挑)→ 点「导入」,弹窗里逐条列出会创建什么、cwd 会被改写成什么、哪些会被 跳过、注册表会怎么变,包里还带着附件对象时再多一句"有几个、其中几个本机没有" → 确认才落盘(全是跳过 时确认按钮是灰的,只有附件对象要补时是例外:那种情况下按下去就是"把缺的图补上",会话一条都不动)。 对象与日志都从包里落进本机——先落对象、再写日志,所以不会出现"会话已经是库里的一员、图还是空的" 那一瞬间;对象落不了会单独提醒,不影响会话导入。导出不问:它只打包下载,不改本机任何 东西。导入永不覆盖:库里已有同 id 的会话只跳过并报告;包里没有 cwd 的 会话落 _no-cwd 项目目录,也不进注册表。

备份——那几个会写盘的操作共同的后悔面:列出本插件写过的每一份备份(时间、迁移 / 删除 / 覆盖前、会话数、源 → 目标),点「回滚」/「恢复」先在弹窗里摆出动作清单,确认才动。迁移留下的那份点 回滚:把会话目录、日志字节与工作区注册表一起还原(空掉的目标项目目录也会删掉,与迁移清理空源项目 目录对称)。删除与同步覆盖本机那份留下的点恢复:只把备份里那份搬回原位——那两个操作从头到尾没碰过 注册表。

说明——把名词与代价一次讲清:分类词典(可见 / 子智能体 / 空白 / 已归档 / 活动中 / 未分组,其中「未 分组」就是侧边栏那一组:不属于任何工作区、而且侧边栏会显示它)、每页做什么、数据从哪来(会话库与 注册表两条路径),以及常见疑问(「未分组」到底算哪些会话、删完侧边栏为什么还在、迁移什么时候要重启、 活动中的会话怎么关掉、备份与「回滚」「恢复」的分工、子智能体跟着父会话走、包里有什么)。动作页(会话 / 传输 / 同步 / 备份)上只留"当下要做的决定",那些词条与边界条件都在这页上,所以那几页每段说明都不超过 两行。

宿主没有 webServer 服务时(例如只用工具的前端)这一页不出现,工具照常可用。

页面右下角写着这一页是哪个构建:发布版只有版本号(v0.3.1);从仓库直接 build 出来的产物多一个 短 commit(v0.3.1 · 0b8c452),构建时工作区有未提交改动再挂 -dirty。悬浮提示把这句话说全(是发布 版还是本地构建)。

同步(WebDAV)

多台机器上的同一个项目往往装在不同目录(/home/alice/dev/proj 与 /opt/work/proj),所以不能直接 同步会话库:库里的目录名与 header 的 cwd 绑死,换台机器就对不上。这一块的做法是把远端当中转—— 远端放的是 .dshsess 包,落地仍然走导入那条路,由它把 cwd 改写成这台机器的路径。

附件对象(图片与文件)走远端一个共享目录:dsh-session-manager/attachments/objects/<sha 前两位>/<sha> (文件对象在 file-objects/,两者与机器格同级)。包本身只有日志,所以推送时只补远端缺的那些、拉取时只 取本机缺的那些——同一张图不会因为「一条会话一个包」被反复上传(自动推送每写完一轮就重推一次),也不会 因为两台机器各推一份而存两份。手动点「同步」时还会多跑一趟补对象,专治计划按日志指纹判「两边一样、 谁也不用动」的那些会话:本机缺的就从共享库取回来(早先拉过来、图一直挂着的那批),远端缺的就传上去(旧 版本只推过日志的那批)。远端也没有的那些报一句「本机有 N 个附件对象在远端共享库里也没有,这次补不上」。 宿主没挂 dsh-attachment-local 时这一整块不参与:包与索引与升级前逐字节一致。

配置(插件配置里的 sync 块):

sync:
  url: https://dav.example.com/dsh        # WebDAV 地址(服务器根也行);插件在它下面自建 dsh-session-manager/
  machineId: robot-a                       # 可选,缺省取主机名;两台机器别用同一个 id,也不能叫 attachments
  username: alice                          # 可选(Basic)
  passwordRef: DSH_DAV_PASSWORD            # 可选:**凭据引用名**(环境变量名);留空就用 DSH_DAV_PASSWORD
  mapping:                                 # 显式映射:远端 cwd → 本机目录(两台机器各写各的)
    /home/alice/dev/proj: /opt/work/proj
  timeoutMs: 30000                         # 可选
  autoPush: true                           # 可选:自动推送(写完一轮就推、只推不拉,见下面那一段)
  autoPushIntervalMs: 300000               # 可选:兜底间隔毫秒,缺省 300000(5 分钟),最小 60000

这一节也能在界面里改:设置 →「会话管理」→「同步」分页(这张表单在卡片里),URL、机器名、账号、 密码、超时与映射表都能直接编辑(留空的字段按框里那份灰字生效:机器名留空就是主机名,超时留空就是 30000 毫秒),按「保存」写进 profile 那份配置文档 (~/.dsh/profiles/<name>/cordis.patch.yml)。sync 是活字段,改完不用重启就生效;三个路径字段 (sessionsRoot / registryPath / backupRoot)仍然只在配置文件里。路径映射在表单里逐行增删: 左边是远端记下的 cwd,右边是这台机器上的目录;远端那一栏必须逐字对上别的机器记下的路径,所以同步过 一次(弹窗里那份计划见过它们)之后它会把这些 cwd 列成候选,不用手抄。

自动推送——只推不拉:勾上它,插件就在本机写完一轮时自己推一轮(宿主那边一条会话的 turn/end 到了就动手),只把本机的会话推到远端,一个字节都不拉:不拉取、不覆盖本机那份、不备份、 不写注册表,也就不会动本机任何一条会话。这是一条安全边界,不是"跑得轻一点的同步"——拉取那一侧要动 本机已有内容(远端更新时先备份再顶掉本机那份),那件事得有人看着,界面上它前面还挡着一个确认弹窗, 所以后台这一路只做纯增量的那一半。两栏在「同步设置」表单里填(超时下面):

sync:
  autoPush: true                             # 打开自动推送(缺省不推送)
  autoPushIntervalMs: 300000                 # **兜底**间隔毫秒,缺省 300000(5 分钟),最小 60000(1 分钟)

推的时机是最后一个会话事件之后安静 10 秒:一轮对话不是只发一次 turn/end(自动续轮、子智能体起停 都会各发一次),先攒一攒,所以一串事件只会推一次,不会把整包连发好几遍。这一路绕开下面那道速度闸—— 你刚写完一轮,"距上次才过了 20 秒"不该挡住它。

autoPushIntervalMs 是兜底间隔,不是"每 5 分钟才推一次":它管的是事件层够不到的那三类——别的机器 推上去的东西(本机一个字节都没写,也就不会有事件)、本机删了会话要收尾索引(同样没有新事件)、事件层 没接上(DSH 崩过、没拿到会话服务)。所以它的意思是"最长多久没推就一定补一次"。小于 60000 的值会被抬到 60000(想的是"勤快点"不必报错,但也不让一个 0 把远端打爆);间隔改完不用重启,下一轮就按新值走, 关掉也是立刻停。远端更新的那些不会被拉下来:它只推,所以别的机器的改动要到你手动点「同步」、在 弹窗里确认那一步才会落本机。卡片底部那枚「立即推送一次」(和「同步」同一行,开关开着时才出现)就是 当场跑一轮——它也绕开速度闸,因为你按的那一下不该被"距上次才过了 20 秒"挡住;它仍然只推不拉,所以没有 确认弹窗,进度画在页面横幅上。这一趟跑没跑、为什么会没跑(没开 / 兜底那轮还没到点 / 上一轮还在跑 / 没有要推的)都在横幅里说清,连续失败也会报一句。

密码在那张表单里直接填:那是个只写的输入框,按「保存」把密码写进宿主机凭据库 ($DSH_HOME/.credentials.yaml),配置文件里始终只有引用名——值不回显,界面只报「已配置 / 未配置」。 引用名不在这张表单上:它就是配置里的 sync.passwordRef(缺省 DSH_DAV_PASSWORD),要换名字(或者 本来就用环境变量)在插件配置页那份通用表单里改。启动环境里已有的同名变量会遮住写入,这时界面把密码 框标成不可改。

配完先点一次「测试连接」:它只读探一次远端(两次 PROPFIND,不改远端任何东西),回的是一句结论 ——连得上/认证过不过/地址对不对,认证失败还会分清是「没填用户名」「引用名里没有值」还是「服务器不认 这套用户名密码」。测的是已保存的配置(宿主的运行时按配置现读),所以有未保存的改动时按钮是禁用的。 写权限不在这里验:第一次同步自然会建那一层,测试不该往别人的服务器上放东西。

多机共用一份配置:远端索引里每条会话还记着项目身份——仓库的 git remote(规范化成 host/owner/repo)与会话 cwd 在仓库根之下的相对路径。拉取的时候先用本机的候选目录(会话的 cwd + 注册表里记着的工作区路径)认一遍身份,认出同一个仓库就落 本机仓库根 + 相对路径。于是几台机器可以 用字面一样的配置(url 相同、machineId 缺省取主机名、mapping 留空),同一个仓库克隆在 /home/alice/dev/proj 与 /opt/work/proj 也照样对得上;反过来,两台机器上同名的目录若是两个仓库, 不会被认成同一个项目。没装 git、不是仓库、没有 remote、本机没这个项目——一律退回下面那张映射表, 计划里会点名是哪个仓库没认出来。

mapping 与身份共存:显式映射优先(配了就按配的落),身份是自动那条路。所以非 git 目录、 或者想强制落到别处,仍然靠映射。

远端布局:插件在 url 下面自建一层自己的命名空间,dsh-session-manager/<machineId>/index.json 是 这台机器贡献了哪些会话,dsh-session-manager/<machineId>/<id>.dshsess 是一条会话一个包——所以 url 可以直接填 WebDAV 的服务器根或账号根,不必自己写这一层。一机一格:WebDAV 没有锁,每台机器只写 自己那一格、读别人的全部,就不会互相盖掉。

在「同步」分页:点「同步」先在弹窗里算一份只读计划(读远端,什么都不写),报出「会拉取 N 条 / 会推送 M 条」,三张表(会拉取 / 会推送 / 两边都有、这次不动)各自按项目目录分组——组头是这一组的名字(工 作区标题,没加入工作区就是项目名)+ 一枚主机名小标签 + 这一组几条,项目身份与本机路径在悬浮提示里;行里只 剩动作、会话名与大小,会话名后面跟着与会话列表同一套的类型标签(子智能体 / 空白 / 已归档 / 活着 的,本机已有的那几条才挂得上)。「这次不动」那张表的第三列是哪台机器持有的另一份。按「确认同步」才真 的拉取与推送,而确认用的是预演算出来的那份数据(不再把同一份计划重算一遍):只有预演之后本机或远端 又变了才会从零重算,那时成功那句话里会说明;落地那一段弹窗正文换成进度条与「正在推送 12 / 84」,下面是 当前那一条的标题——分母是这一段真正会做的条数(跳过的那些不算),拉取与推送各自走一遍。规则与边界:

  • 两边都有同一个 id 时,按内容与「最后活动时间」择新:内容逐代一致(cwd 之外)就不动;本机确实 是远端那份的前缀就重推刷新远端;远端领先(本机这份是它的前缀)或两边各自写过而远端更晚,就先备份 本机那份、再换成它(旧的那份随时能从「备份」页里恢复);有一边读不到最后活动时间 (老索引 / 宿主没挂投影缓存)或者两边一样新,就两条都不动,报告里说清是「远端更新」还是「两边各自 写过」。「最后活动时间」取宿主从日志内容里折出来的两枚钟里晚的那枚(最后一次提问、最后一条消息 ——agent 自己写进去的也算,所以"推送之后本机又跑了几轮"照样分得出高下),复制、改写 cwd 都不影响 它。判据是代次指纹, 与 cwd 无关——落地一定会改写别人的 cwd(库目录名与 header 的 cwd 绑死),按字节比会把拉取来的 那份判成"两边各自写过",连"同步下来继续写"都无法再推送回远端;
  • 本机严格领先时重推:两边共有的代次内容一致(cwd 之外逐条一致),说明远端那份确实是本机这份的 前缀,刷新它不会丢任何代次;同一 id 有多台贡献时,拉取的一侧取领先的那份而不是格子名排前面的 那份;
  • 空白会话不参与同步:建出来但一个回合都没聊过的会话不推送(正文里报一句「跳过 N 条空白会话」), 下一次推送还会把它从自己那格索引里撤下来——别的机器因此不再看得到它;本机这条是空白而远端有内容时 以远端为准(空的那份没什么可保护的);
  • 删掉的会话下一次推送就从自己那份索引里消失(远端那份包不主动删),别的机器已取走的副本不受影响;
  • 没有 cwd 的会话不硬塞路径,按 _no-cwd 落地(与导入同一条口径);没配映射的 cwd 会被跳过并列出来;
  • 拉取来的会话要宿主重新扫描才会出现在侧边栏:改完注册表之后插件把活儿交给宿主自己做(与迁移同一套 语义),宿主上没有那套动作时才提示重启——那时确认弹窗里就会先说,落地之后页面上再单独留一条警告 (宿主自己接得住时一个字都不说)。

同步也要跑 plan 那一套:工具 sync_sessions 默认只预演,apply:true 才落盘。

模型工具

工具写盘说明
plan_session_migration否只读计划:会话/文件数、目标项目目录、注册表变更、阻塞问题
migrate_sessions需 apply:true默认 dry-run;执行前做字节级备份,事后自动复核
rollback_session_migration是按备份目录字节级回滚
verify_workspace_sessions否复核某项目目录内日志与 header 的一致性
sync_sessions需 apply:true与 WebDAV 远端同步(拉取别人推送的、推送本机独有的;两边都有同 id 时按内容与最后活动时间择新);默认 dry-run

注意事项

  • 改完注册表交给宿主自己做,正常不必重启:磁盘上的 workspace.json 只是第三份拷贝——宿主内存里那份 才是权威(写入才改它,绝不从磁盘重读),它另有一份启动时建的 header 索引。所以插件改完注册表之后不是 "等宿主自己发现",而是把活儿交给宿主自己做:先让它按磁盘重看一眼(刷新 header 缓存与"这条会话住哪儿" 的索引),再复用 / 新建目标工作区、把会话挂过去、把源侧摘空、删掉已空的工作区。这几件事宿主自己会落盘、 也会通知界面,所以侧边栏立刻按新归属显示——不动进程,也不杀正在跑的回合。会话 id 全程只被挂靠与摘除, 一个都没变;已存在的工作区也是复用自己那条记录,id 不变。 宿主上没有那套动作时(只有工具没有界面的前端、老版本),工具与页面都会如实说 需要重启 DSH,并且 重启前别改任何工作区——新建 / 改名 / 归档都会用内存副本把这处改动覆盖掉。宿主中途拒绝时也 一样:插件会把这次算好的注册表整份写回文件(宿主只要动过手就已经拿内存副本盖过一次),所以重启 后看到的是这次的结果,不是它那份旧归属。
  • 落地之后顺手把列表元数据折好:侧边栏那一行的标题、空白判据与最后活动时间都不来自日志,而是宿主自己那份 投影检查点——它只在活着的会话上被写。所以导入、同步拉取、迁移(改写了 cwd)落地之后,插件会借宿主自己的 两个服务(读完整日志 + 冷读投影)把这些会话各补一次,侧边栏因此不用逐条点开就有名字。代价是每条落地会话多读 一遍日志;读不动的逐条跳过并如实报条数,宿主上没有那套动作时那几条要等你第一次点开。
  • 先看不写:工具与页面上的计划(弹窗里那份)都不写任何字节;每次真正写盘前都会先留一份字节级备份。
  • 同步会按「谁更新」择新:远端那份更新(或两边各自写过而它更晚)时会先备份本机那份、再换成 它;有一边读不到最后活动时间就两条都不动(见「同步」那一节的边界)。
  • 只动首帧:改写只重新压缩 header 那一帧,其余 frame 字节原样保留,回滚可逐字节还原 (连同可选搬迁的会话产物)。
  • 路径:会话 cwd 与注册表路径都不带结尾斜杠;projectKey 会把 /、\、: 折叠成 -, 极少数路径会因此编码碰撞,这种情况计划层会直接拦下。
  • 插件配置:sessionsRoot / registryPath / backupRoot 三个可选字段可覆盖上述默认路径; sync 块配 WebDAV 同步(url / machineId / username / passwordRef / mapping / timeoutMs)。 密码只放引用(环境变量名),配置文件里没有明文:界面上直接填的密码进的是宿主机凭据库 ($DSH_HOME/.credentials.yaml),不是这份配置。

文档

  • .agents/notes/——每条决策的依据与被放弃的做法(中文):多帧 zstd 的静默 丢数据、启动不变式、有损编码的碰撞、.dshsess 容器的取舍、为什么包要带上附件对象、生效模式、 同步怎么判「谁更新」、空白会话为什么不搬,以及界面上的那些取舍
  • docs/internals.md——决策地图:按主题索引到上面那些笔记
  • docs/development.md——本地流程:依赖、构建、测试、开发实例
  • docs/releasing.md——发版流程

许可证

MIT

子智能体不能单独删 / 单独归档 / 单独导出
分叉出来的会话不算