WorkBuddy 会话跨账号迁移 / 复制
在同一台机器、同一份本地数据库 (~/.workbuddy/workbuddy.db) 上,把一条或多条会话在两个账号(user_id)之间转移(如个人版 ↔ 企业版)。侧边栏按 user_id + deleted_at IS NULL 过滤会话,因此改归属即可让会话在目标账号下出现。支持两种语义:
- 移动(migrate):改原会话的
user_id,会话从源账号消失、出现在目标账号。 - 复制(copy):源账号会话保留,在目标账号新增一份独立副本(新 id + 复制正文)。
原理
- 会话列表来自
sessions表,每行一条会话,user_id标识归属账号;id为主键。 - 对话正文 jsonl 按「cwd 路径目录 + session id」组织;文件名 =
<id>.jsonl,每行顶层含sessionId字段(等于会话 id)。正文内容与 user_id 无关。 session_usage等关联表仅按session_id关联、不含user_id,无需处理。
因此:移动本质是一条 UPDATE sessions SET user_id=... WHERE id=...(正文不动);复制需生成新 id、克隆 sessions 行、复制 jsonl 并改名 + 替换其中的 sessionId 字段。
强制执行流程(务必按顺序,缺一不可)
> 这是一个直接改写生产数据库的非官方操作。必须严格遵循下面 4 步,任何一步不满足都不得继续。
第 0 步 · 前置探测(只读)
用 list-users / list-sessions 帮用户确定源账号、目标账号、要处理的 session id,以及是移动还是复制。
python scripts/migrate_sessions.py list-users
python scripts/migrate_sessions.py list-sessions --user <SRC_USER_ID>
第 1 步 · 让用户手动确认注意事项(必须,且必须逐项列出)
在做任何写操作前,必须使用 AskUserQuestion 或明确文字把下列注意事项完整呈现给用户,并等待用户显式确认。未确认不得进入第 2 步:
- 完全退出 WorkBuddy 桌面端(含托盘进程)。App 运行时热改数据库可能损坏数据。
- 确认操作语义(移动还是复制)、源/目标 user_id 及待处理 session id 准确无误(逐一念出具体值请用户核对)。
- 确认两个账号均为本人所有、且符合所在组织的数据合规要求——个人版与企业版隔离本是合规设计,跨账号转移属人为打破隔离。
- 知悉这是非官方操作,产品升级/schema 变更时存在兼容风险,官方不背书。
- 知悉处理后会话将出现在目标账号下、其原 cwd 工作区分组中(不是当前目录);复制模式下源账号仍保留原会话。
第 2 步 · 自动打包压缩备份到桌面(用户确认后立即执行)
用户确认后,先备份再执行。备份脚本会把 workbuddy.db 快照 + 待处理会话的 jsonl 原文件打包成带时间戳的 zip 放到桌面:
# 批量:备份整个源账号
python scripts/backup_sessions.py --from <SRC_USER_ID>
# 或指定会话
python scripts/backup_sessions.py --sessions <SID1,SID2,...>
记录输出的 BACKUP_ZIP=<路径>,确认 zip 已生成、大小正常后方可继续。
第 3 步 · 校验 → 迁移/复制 → 复核
先决定语义:migrate(移动,原会话从源账号消失)还是 copy(复制,源账号保留、目标账号新增副本)。默认多数跨账号需求是 copy。
# 只读校验(确认目标账号有效、待处理会话存在)
python scripts/migrate_sessions.py verify --to <DST_USER_ID> --from <SRC_USER_ID>
python scripts/migrate_sessions.py verify --to <DST_USER_ID> --sessions <SID1,SID2,...>
# 方式 A · 移动(改归属,原会话消失)——必须带 --backup-verified
python scripts/migrate_sessions.py migrate --to <DST_USER_ID> --from <SRC_USER_ID> --backup-verified
python scripts/migrate_sessions.py migrate --to <DST_USER_ID> --sessions <SID1,SID2,...> --backup-verified
# 方式 B · 复制(原会话保留,目标账号多一份副本)
# 先干跑演练(只读,不写库/不拷文件):
python scripts/migrate_sessions.py copy --to <DST_USER_ID> --from <SRC_USER_ID> --dry-run
# 确认无误后正式执行(必须带 --backup-verified):
python scripts/migrate_sessions.py copy --to <DST_USER_ID> --from <SRC_USER_ID> --backup-verified
python scripts/migrate_sessions.py copy --to <DST_USER_ID> --sessions <SID1,SID2,...> --backup-verified
migrate/copy 完成后脚本会自动复核并打印结果(移动为「N/N 条现归属目标账号」,复制为「N 条副本现归属目标账号」)。
移动 vs 复制(migrate vs copy)
| migrate(移动) | copy(复制) | |
|---|---|---|
| 数据库 | 改 1 行的 user_id |
新插入 1 行(新 UUID + 目标 user_id,其余字段克隆、刷新 updated_at) |
| jsonl 正文 | 不动 | 复制一份,文件名换新 id,逐行把顶层 sessionId 字段替换为新 id;content 正文原样保留 |
| 原会话 | 从源账号消失 | 源账号保留,目标账号多一份副本 |
| session_usage | 无需处理(按 session_id 关联,不含 user_id) | 副本从零计数,不复制用量 |
复制的技术依据(探测得出):sessions.id 是主键 → 副本必须换新 UUID;jsonl 文件名 = <id>.jsonl 且每行顶层含 sessionId 字段(等于会话 id)→ 换 id 后需复制文件、改名、替换该字段。copy 用事务写库,失败自动回滚并清理已拷出的孤儿 jsonl;--dry-run 可先只读演练。
第 4 步 · 用户在 WorkBuddy 验证
用目标账号登录 → 到会话原 cwd 工作区分组 → 确认会话出现且正文可加载。
批量说明
--from <SRC_USER>:处理该源账号下全部未删除会话。--sessions a,b,c:处理指定的多条会话(可跨不同源账号混合)。- 两种模式在
backup_sessions.py、verify、migrate、copy中通用,保持参数一致即可。 - 幂等:
migrate中已属目标账号的会话会被自动跳过;数据库中不存在的 id 记为缺失,不影响其余处理。copy每次都生成全新副本(多次运行会产生多份副本)。
回滚
- 撤销移动(migrate):反向
migrate --to <原账号> --sessions <...> --backup-verified(可复用已存的桌面 zip 作为"备份已就绪"依据,无需重新备份)。 - 撤销复制(copy):直接删除生成的副本——在 WorkBuddy 里删除对应会话,或按脚本打印的新 id 删除
sessions行及其<新id>.jsonl文件(原会话不受影响)。 - 整体还原:退出 App → 用桌面 zip 内
db/workbuddy.db覆盖~/.workbuddy/workbuddy.db→ 重开 App。
安全红线(不可跨越)
- 未获用户对第 1 步注意事项的显式确认,不得备份、迁移或复制。
- 未生成桌面 zip 备份,不得执行
migrate/copy(脚本用--backup-verified硬性拦截;--dry-run除外,因其不写入)。 - 写操作前,必须把具体的源 user_id、目标 user_id、session id 念给用户核对,不得用推断值直接执行。
- App 未退出时不得写库。
关键路径
- 数据库:
~/.workbuddy/workbuddy.db - 会话正文:
~/.workbuddy/projects/<cwd转换目录>/<session_id>.jsonl - 桌面备份:
<桌面>/workbuddy-session-backup-<时间戳>.zip