DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

lifeng4127 /

lifeng4127/dsh-github-sync

Verified

同步 DSH 会话到私有 GitHub 仓库,实现跨设备接力

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

dsh-github-sync

把本地部署的 DeepSeek Harness 会话同步到你自己的私有 GitHub 仓,用来在两台(或更多)电脑之间"接力"——在 A 机聊到一半的任务,B 机下载下来能接着聊。

它补的是 DSH 本地版没有云端存储这个缺口:没有服务器、没有账号体系,只有你的仓库和一串令牌。


它做什么

能力 说明
上传会话 勾选本地会话推上去;连带它引用到的图片等附件;一批文件 = 一个原子提交
下载会话 从远端拉回来,改写 cwd 到本机工作区,并自动注册进项目列表——不用重启就能看到、能继续聊
上传文件夹 默认关闭,设置里开启后可用:选中工作区下的任意子目录,按排除清单过滤后推到 <项目>/files/
下载文件 默认关闭,设置里开启后可用:把远端文件写回工作区(同名冲突另存副本,路径做了防穿越)
给 agent 的 3 个工具 你说"把这条会话推到 GitHub",它先出清单、你确认后再动手

同步是纯手动的:不点不动,没有后台定时任务,不会偷偷传东西。

  • 传输有进度:文件级进度条(12/30 · 上传文件 · f12.bin),覆盖预览阶段(读取会话/拉取远端)与提交阶段(读远端 → 上传文件 → 建目录树 → 写提交)。
  • 可展开看明细:点进度条左边的 ▸ 展开文件列表,逐个显示 等待 / 传输中 / 完成 / 已跳过。
  • 单文件可跳过:明细里每个还没开始传的文件都有「跳过」按钮——跳过附件会自动从 manifest.json 里摘掉对应引用,不会留下悬空记录。
  • 随时可取消:取消在文件之间生效,ref 不会被更新,所以仓库里不会留下半个提交。
  • 切屏不丢:传输状态存在宿主进程里,面板切走再回来会自动重新接上进度条;没确认的预览也会恢复出来。
  • 面板状态会保留:工作区选择、勾选框、开关都记在浏览器本地,切走再回来(甚至刷新页面)都还在。

安装

从插件市场安装:在 DSH 的「插件」里搜索 dsh-github-sync,安装后启用即可。

从本地目录安装(开发/自用):

  1. 打开 DSH →「插件」→ 添加插件 → 本地目录,选这个文件夹
  2. 或者由 agent 调用 plugin_manager install_bundle,target 填绝对路径
  3. 或者在 profile 的 package.json 里加 "dsh-github-sync": "link:<路径>",并把它加进 dsh.profile.bundles

装完左侧边栏会出现「GitHub 同步」图标,点开是面板。

大陆网络:如果 npm 官方源很慢,用镜像装: npm install dsh-github-sync --registry=https://registry.npmmirror.com (镜像同步有几分钟到几小时延迟,刚发版时可能还没有)

客户端改动后按 F5 刷新页面即可生效;宿主代码改动需要重载插件(重启 DSH,或在插件管理器里关掉再启用)。


首次配置

📖 需要更细的图文步骤(含截图位置、常见错误对照、令牌轮换)?看 配置教程 TUTORIAL.md。

1. 建一个空的私有仓

GitHub 新建仓库,不要勾 README、不要勾 .gitignore、选 Private。 空仓库很重要:首次提交若已有文件会多一次合并。

2. 建 fine-grained PAT

https://github.com/settings/tokens?type=beta → Generate new token:

字段 值
Repository access Only select repositories → 只勾这一个仓
Contents Read and write
Metadata Read-only(勾 Contents 后自动带上)
其它权限 全部 No access

令牌只显示一次,复制后立刻填进面板。

3. 面板里填

右上角 ⚙ → 仓库地址(owner/repo 或完整 URL 都行)+ 访问令牌 → 测试连接。

  • 成功:连接正常 · owner/repo
  • 报「仓库是公开的」→ 去仓库 Settings 改成 Private(这是硬红线)
  • 401 → 令牌没粘全;404 → 仓库名错或令牌没授权到这个仓

日常使用

面板(推荐)

┌ 设备 / 仓库 / 分支 / 当前工作区 ──────────── ● 已连接 ┐
├──────────────────────┬──────────────────────────────┤
│ 远端仓库              │ 本地会话(工作区下拉)        │
│ 💬 会话(可勾选)      │ ☑ 会话…  大小  时间           │
│ 📄 文件(可勾选)      │                              │
├──────────────────────┴──────────────────────────────┤
│ 已选:本地 N · 远端会话 M · 远端文件 K                │
│ [预览上传] [文件夹…] [下载会话] [下载文件]            │
└──────────────────────────────────────────────────────┘

所有写操作都必须先预览:预览会逐个文件标注 upload / skip / blocked / copy 与原因,确认后才真正执行。被 blocked 远端更新 挡住的行,行尾直接有「先下载」按钮。

一眼看出状态

每条会话右侧有同步徽标,由本地 body 哈希和远端索引对比得出:

徽标 含义
已同步 两边内容一致
本地更新 本地多聊了几轮,可以推
远端更新 远端更新,应先下载
未上传 还没传过
活跃 最近 5 分钟内还在写,推上去的是快照

配合「搜索」「只看未同步」「选未同步」「同步此工作区(N)」,「哪些没传、要不要传」不用再靠记忆。

设备与历史

  • 顶部「设备」:远端索引里记录的各设备与最近活动时间,本机会标出来。
  • 顶部「历史」:最近的上传/下载记录(文件数、commit、失败数),落盘在 $DSH_HOME/github-sync/history.jsonl。

删除:两边互不影响

  • 本地行尾的 🗑:只删本机会话,GitHub 仓库原样保留;顺带回收不再被任何会话引用的本地附件。
  • 远端行尾的 🗑:只删仓库里的会话,本机原样保留;同时删掉没有其他会话引用的远端附件。
  • 两边都是「点两次确认」(4 秒内有效),避免误删。

冲突怎么办

两端都动过同一条会话时,预览里会标 blocked 并给出选择:

按钮 含义
先下载 先把远端版本拉下来,再决定
用本地覆盖远端 / 用远端覆盖本地 明确选一边,直接执行并重新预览

仓库会越变越大,怎么回收

每次提交都会在 git 历史里留下新的 blob,GitHub 不会自动清理。几个建议:

  • 会话在仓库里是固定路径(<项目>/sessions/<id>.jsonl.zstd),所以历史里的旧版本只是占空间,不影响使用;
  • 想真正回收:git clone --mirror 后用 git filter-repo 重写历史,或在 GitHub 上重建一个空仓再推一次;
  • 如果主要用途是接力,一个项目一个仓比所有项目混在一起更容易清理;
  • 面板的「诊断」里能看到当前远端会话/附件数量,便于判断膨胀程度。

第一次配置

没连上仓库时,面板顶部会出现引导条,点「开始配置」进入三步向导:建空私有仓 → 建 fine-grained token → 填入并测试连接,每步都带官方页面链接。

直接对我说

  • "把这条会话推到 GitHub"
  • "远端都有哪些会话"
  • "从远端把某某会话拉到这个工作区"

我会调用 github_sync_transfer:先 phase=preview 给你清单,再 phase=execute 带同一个 token 执行。


仓库长什么样

README.md                        自动生成的项目/会话索引(每次上传重写)
manifest.json                    权威索引:会话 id → 路径、原始 cwd、sha、附件列表
<项目>/sessions/<id>.jsonl.zstd  会话文件本体(跟 DSH 本地格式一致)
<项目>/attachments/<sha>.<ext>   会话引用到的附件(图片/PDF 等)
<项目>/files/<相对路径>           文件夹上传的内容

manifest.json 里保存了原始 cwd,这是跨机还原的关键:下载时它会被改写成目标机器的工作区路径。


关键实现(为什么这么设计)

  1. 会话文件 = JSONL + zstd 多帧,首帧是 header(含绝对路径 cwd),之后每批追加各成一帧。
  2. 路径即身份:宿主会校验"文件实际路径 == 由 header.cwd 推出的期望路径",不等就报 corrupt。所以跨机必须同时改 header 的 cwd 和目录名。
  3. 只重压头帧:改写 cwd 时只重压第一帧,后面的帧字节原样保留——否则会破坏 append-only 字节链,"前缀合并"永远失效。
  4. 冲突只比 body:头帧的 cwd 本来就该因机器而异,所以同步状态与冲突判定都基于"头帧之后的字节"。判定规则:一方是另一方的前缀 → 落后的一方补上;互不为前缀 → 分叉,本地保留、远端另存 *.remote-fork-*。
  5. 注册用服务:下载后通过 workspaceRegistry.attachSession() 登记,不直接改 workspace.json(那是宿主的活,直改会被内存覆盖)。
  6. 空仓库要打种子:零提交的仓库连 POST /git/blobs 都返回 409,首个提交必须用 Contents API。
  7. 附件补传:即使会话内容没变(判定 skip),只要它引用的附件还没在远端登记,上传时也会把附件单独补上去——旧会话不用重传本体。
  8. 附件还原兜底:下载时除了看 manifest 登记的附件,还会直接解析会话本体里引用的 sha256:;旧 manifest 没登记时按 <项目>/attachments/<sha>.* 在远端树里找。
  9. 零网络的前缀判定:manifest 记着每次上传的 body 长度与哈希,本地用切片比对就能判断"是不是只多追加了几帧",不必下载远端文件(一条 2MB 会话的预览从 134 秒降到 5 秒)。

安全

只写这四个位置

位置 用途
$DSH_HOME/github-sync/ 插件自己的状态、历史、诊断报告
$DSH_HOME/sessions/ 下载或删除会话(点按钮才动作)
$DSH_HOME/attachments/v1/objects/ 还原或回收附件
当前工作区(默认关闭) 文件夹/文件同步,需在设置里显式打开

它不会做的事

不启动子进程、不执行外部命令、不读写注册表、不改 hosts、不改系统环境变量、不安装全局代理、不访问 GitHub 以外的域名。这些不变量由 tools/audit-safety.mjs 静态检查,发布前必跑。

其他边界

  • 默认只同步对话。文件夹/文件同步是默认关闭的高级功能,未开启时相关接口直接返回 403。
  • 只允许私有仓,公开仓直接拒绝。
  • 密钥类文件硬阻断:.env*、*.key、*.pem、id_rsa、.npmrc、.netrc,二次确认也传不上去。
  • 路径越界硬拒绝:远端索引里的会话 id / 附件 sha 会先校验再拼路径,仓库被投毒也写不出 $DSH_HOME。
  • token 只在宿主进程:存在 $DSH_HOME/.credentials.yaml(平台机制),面板永远不回显。
  • 删除是两边独立的:删本地不动仓库,删仓库不动本地。

跨平台(Windows / macOS)

插件是纯 JavaScript,没有原生模块、没有平台专属代码,两个系统跑同一份代码。

唯一的平台差异是"会话目录名怎么从路径算出来"。为此:

  • lib/projectkey.mjs 按 DSH 的规则把 cwd 编码成目录名(Windows 的 E:\a\b 和 macOS 的 /Users/x/b 都能编码);
  • lib/sessiondir.mjs 在算出来的目录不存在时,会退回读取会话头里记录的绝对 cwd 来匹配目录——所以即使某个平台的键算法有出入,也不会出现"面板里一条会话都没有"。

跨机接力时,下载会自动把会话 header 里的 cwd 改成目标工作区路径(只重压头帧,其余字节原样保留),所以 Windows 上的会话能被 macOS 直接打开,反之亦然。

发布前的双平台验证清单见 PUBLISHING.md。


排错

现象 原因 / 处理
连接失败 · fetch failed 宿主到 api.github.com 不通(代理/防火墙/DNS)。先确认 HTTPS_PROXY 生效,再重试
UNABLE_TO_VERIFY_LEAF_SIGNATURE / SELF_SIGNED_CERT_IN_CHAIN 本机有软件在解密 HTTPS(SteamTools/Watt Toolkit、Fiddler、安全软件的网页防护)。把 api.github.com 加进它的绕过列表,或按 TUTORIAL 证书错误 处理
Git Repository is empty 409 空仓库,首提交走 Contents API;若反复出现,先在网页建一个 README
its cwd resolves to '...' 同一会话 id 已属于别的工作区,不能重复挂载。这是宿主在保护你
预览里大量 blocked 命中密钥规则或超过 10MB;超限可改 limits
下载后侧栏没有 面板刷新即可;若仍未出现,检查目标工作区是否是同一个
同机删掉再下载,its cwd resolves to '...' 宿主对会话 header 有内存缓存,还记着旧 cwd。重启 DSH 后即可挂载;真实跨机不会遇到(B 机没见过这个 id)
macOS 上面板列出 0 条会话 该平台的会话目录名算法与插件不一致,且回退扫描没命中。点「诊断」→ 复制,把里面的 平台、会话目录(应有)、sessions 根下 N 个目录 三行发出来即可定位

已知限制

  • 附件按会话引用上传,未被任何会话引用的附件不会上传(也就不占仓库)。单个附件超过 25MB 会跳过并在预览里告警。
  • 本地缺失的附件不会阻断上传,但预览里会用 ⚠ 标出来。
  • 只处理 version: 4 的会话格式;其它版本拒绝改写而不是猜。
  • 文件下载是"写到工作区根目录 + 同名另存副本",没有三方合并。
  • 目前按 Windows 路径设计(盘符、反斜杠)。

开发

dsh-github-sync/
├─ index.js             宿主:HTTP 路由 + 3 个工具 + 存储
├─ client.js            浏览器:左栏面板(手写 JS,不经打包)
├─ lib/
│  ├─ zstdframe.mjs     zstd 多帧遍历(自己走帧头)
│  ├─ sessionlog.mjs    读 header/标题、只重压头帧、body 前缀比对
│  ├─ projectkey.mjs    cwd → 目录键
│  ├─ github.mjs        Git Data API 客户端(空仓种子、非快进重试)
│  ├─ sync.mjs          上传/下载计划(纯逻辑 + 只读远端)
│  ├─ folders.mjs       文件夹扫描 + 排除/硬阻断
│  ├─ manifest.mjs      远端索引与 README 生成
│  ├─ localsessions.mjs 本地会话清单(标题优先读宿主投影缓存)
│  └─ paths.mjs         DSH 目录约定
└─ tools/               自测脚本(selftest / test-upload-plan / verify-remap)

自测:

node tools/selftest.mjs          # 内核 24 项
node tools/test-upload-plan.mjs  # 上传计划(假 client,不联网)

卸载

插件管理器里移除即可。数据在:

  • $DSH_HOME/github-sync/state.json(配置与设备 id)
  • $DSH_HOME/.credentials.yaml 里的 GITHUB_TOKEN
  • 远端仓库(自行删除)

本地的会话与工作区不受影响——这个插件从不删本地数据。

—/ 5

No ratings yet

Verified DSH bundle

Commit 9f7bdd9263cc

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