WorkBuddy 跨设备迁移实战:47 条会话无缝续接的全流程缝合术

WorkBuddy 跨设备迁移实战:47 条会话无缝续接的全流程缝合术

换电脑是常事,但 AI 助手的"记忆"怎么搬?市面上的指南要么停留在"复制 .workbuddy 文件夹"的粗粒度,要么用泛泛的"路径替换"带过关键坑。本文记录一次真实的跨设备迁移:从旧电脑 Windows 用户 yanjing / D 盘工作空间,到新电脑 Administrator / E 盘工作空间,47 条历史会话如何无缝续接,期间 9 条被覆盖冲掉的会话如何从 jsonl 恢复,以及最终把整套缝合流程交给 AI 自主完成的全过程。

一、背景:为什么 WorkBuddy 迁移是个真问题

WorkBuddy 作为本地优先的 AI 编码助手,几乎所有状态都存在本地:会话历史、技能、连接器、自定义模型、人设、长期记忆、Python/Node 运行时......账号只携带"身份",不带数据。这意味着换电脑时:

  • 复制 .workbuddy 文件夹 → 数据物理到位了,但路径全错
  • 旧机用户名 yanjing 在新机不存在,jsonl 里的所有文件引用失效
  • 旧机 D 盘工作空间在新机变成 E 盘,DB 里 cwd 字段全部指向幽灵路径
  • 直接整库覆盖 workbuddy.db冲掉新机已产生的会话索引(实测踩坑)

更麻烦的是,这些残留分布在五个不同层次,每一层有不同转义变体,靠"全局替换"根本清不干净。

二、WorkBuddy 本地数据全景

先看 .workbuddy/ 目录的全貌,明确每一块的迁移策略:

目录 / 文件 内容 迁移策略
workbuddy.db 会话索引、工作空间、自动化任务(SQLite) 合并式导入,禁止整库覆盖
projects/ 会话正文(jsonl + meta.json) 整目录复制,需重命名 + 正文路径替换
skills/ 用户级技能 整目录复制,meta 中 iconLocalPath 需修
memory/ 云端画像本地缓存 + MEMORY.md 整目录复制
connectors/ MCP 连接器配置(mcp.json) 整目录复制,凭据可能需重新授权
binaries/ Python / Node / Git / ffmpeg 整目录复制,pyvenv.cfg 必修
SOUL/IDENTITY/USER/MEMORY.md 个性化四件套 整复制,默认模板则覆盖
settings.json / models.json 客户端设置、自定义模型 整复制
local_storage/ / user-state.json 客户端运行时状态 不迁(会被内存态回写)
app/sessions.json UI 会话列表状态 不迁(启动时重建)

注意最后一行------sessions.json 不迁是因为它只是 UI 状态,真正的会话索引在 workbuddy.dbsessions 表里。这是个容易踩错的认知点。

三、为什么"复制粘贴"不够:五层路径残留

.workbuddy 文件夹复制到新机后,旧路径残留分布在五个层次,每一层需要不同的处理方式:

第 1 层:数据库字段

sessions 表的 cwd 字段、workspaces 表的路径字段,全部指向旧机路径:

sql 复制代码
-- 修复前的 sessions 表
SELECT cwd, COUNT(*) FROM sessions GROUP BY cwd;
-- C:\Users\yanjing\Desktop\...      → 20 条
-- C:\Users\yanjing\WorkBuddy\...    → 5 条
-- D:\computerSoftware\workbuddy_workspace\...  → 12 条

修复方式:SQL UPDATE,把 yanjing 替换为 AdministratorD:\computerSoftware 替换为 E:\computerSoftware

第 2 层:projects/ 目录名

WorkBuddy 用 cwd 路径生成目录名(盘符和反斜杠转成连字符):

复制代码
c-Users-yanjing-Desktop-售前支持相关-南昌金控相关     ← 旧机目录名
c-Users-Administrator-Desktop-售前支持相关-南昌金控相关  ← 新机应有的目录名

修复方式:os.rename,38 个目录全部重命名。

第 3 层:jsonl 正文(最坑的一层)

会话正文里嵌入了大量旧路径,且转义变体极多。我实测扫到的形态:

形态 示例 出现场景
标准反斜杠 C:\Users\yanjing\... 工具调用参数
双反斜杠 C:\\Users\\yanjing\\... JSON 字符串内嵌
正斜杠 C:/Users/yanjing/... 跨平台代码
小写盘符 + 双反斜杠 c:\\Users\\yanjing\\... 每行末尾的 cwd 元字段
D 盘同上四种变体 D:\ / D:\\ / D:/ / d:\\ 工作空间路径

只替换第一种会漏掉后四种,只查 yanjing 关键词会发现"清不干净"------其实残留的是小写盘符 + 双反斜杠形态。必须把全部八种变体列入替换清单

第 4 层:pyvenv.cfg(Python venv 启动器)

复制代码
home = C:\Users\yanjing\.workbuddy\binaries\python\versions\3.13.12

venv 启动器指向旧机 Python 路径,新机直接报错。不用重装 venv,改这一行即可:

python 复制代码
from pathlib import Path
cfg = Path(r'C:\Users\Administrator\.workbuddy\binaries\python\envs\default\pyvenv.cfg')
text = cfg.read_text(encoding='utf-8')
new = text.replace(r'C:\Users\yanjing', r'C:\Users\Administrator')
cfg.write_text(new, encoding='utf-8')
# 验证
import sys, openpyxl, pandas
print('venv OK:', sys.version.split()[0])

第 5 层:skills meta 的 iconLocalPath

部分技能的 _skillhub_meta.jsoniconLocalPath 字段写死了旧机绝对路径:

json 复制代码
{"iconLocalPath": "C:\\Users\\yanjing\\Desktop\\xxx.png"}

修复方式:遍历 skills/*/_skillhub_meta.json,路径替换。

四、踩坑实录:理论派指南 vs 实测真相

迁移前我参考了一份 AI 生成的通用指南,对照实操发现了几处关键偏差:

通用指南说法 实测真相
workbuddy.db "直接覆盖" ⚠️ 整库覆盖会冲掉新机已产生的会话索引,必须合并式导入
sessions.json "不要覆盖,让 AI 重建" 会话索引实际在 DB 的 sessions 表,sessions.json 只是 UI 状态,不迁即可
路径替换 "全局替换" 五层残留 + 八种转义变体,必须分层处理
账号机制 "同一账号登录" 多账号共用 ~/.workbuddy,会话按 user_id 隔离,切换账号看不到属正常
venv 报错 "重装 Python" pyvenv.cfg 一行即可,不用重装
.skillhub 目录 "需创建" 不必建,技能本体已随 skills/ 迁入
automations "需迁移" 实测为空表,无定时任务需处理

最有价值的一条教训:先迁移,后使用。新机装好 WorkBuddy 后先彻底退出,再做迁移,再启动。否则新机已经产生了新会话,整库覆盖会把它们冲掉------我就因此丢了 9 条,靠 jsonl 才恢复。

五、WAL 模式启示:运行时能不能动数据库

这是这次迁移里最技术性的一段。执行迁移的 AI 本身就住在 WorkBuddy 客户端里------这意味着"先关客户端再操作"对全自动迁移是个悖论。那运行时能改数据库吗?

SQLite 默认 journal 模式有 DELETE/TRUNCATE/WAL 等。WorkBuddy 用的是 WAL(Write-Ahead Logging),实测:

bash 复制代码
$ ls -la ~/.workbuddy/workbuddy.db*
workbuddy.db      2.5 MB
workbuddy.db-wal  3.1 MB    ← 客户端运行时实时产生,未 checkpoint
workbuddy.db-shm

WAL 模式的核心特性:

操作 客户端运行时 原因
SQL 变更(INSERT/UPDATE,短事务) ✅ 安全 WAL 设计初衷就是多连接并发,读写互不阻塞
文件级替换(覆盖 db 文件) ❌ 绝对禁止 主库文件不含 WAL 中未 checkpoint 的数据,覆盖即丢失,新旧 WAL/shm 错位会损坏库
备份这个库 ⚠️ 不能只拷文件 必须走 SQLite backup API,否则缺 WAL 数据

结论:迁移时数据库操作改用"合并式导入"------只读打开备份库 → 读出旧会话行 → INSERT 进活动库。这恰好支持运行时执行,且比整库覆盖更安全。

六、AI 自主缝合六阶段流程

理解了上述认知后,整个迁移可以交给 AI 自主完成。流程如下:
flowchart TD A阶段1: 只读探查 --> B阶段2: 备份 B --> C阶段3: 数据搬运 C --> D阶段4: 路径缝合 D --> E阶段5: 会话合并注册 E --> F阶段6: 校验与报告 A -.-> |孤儿会话检测| A1有jsonl无DB行 = 覆盖过db C -.-> |全量差异比对| C1捞回 credentials/ 等易漏项 E -.-> |急救模式| E1从jsonl重建索引行 F -.-> |七项验证| F1目录存在性/路径残留/venv/...

各阶段要点:

  1. 只读探查 :扫描 .workbuddy、projects/、workbuddy.db、桌面工作目录、binaries/,找出旧路径残留层次;检测孤儿会话(有 jsonl 无 DB 行 = 整库覆盖过的典型症状)
  2. 备份:用 SQLite backup API 做一致性备份(比直接拷 db+wal 可靠),个性化文件单独备份
  3. 数据搬运 :合并式导入,禁止整库覆盖 ;projects/skills/memory 整目录复制;全量差异比对旧机副本,捞回易漏项(如 credentials/ 子目录、cloudstudio-deploy-history);跳过 vendor 空壳、.deprecated 标记
  4. 路径缝合:五层残留逐层处理,jsonl 替换必须覆盖全部八种转义变体
  5. 会话合并注册 :合并式 INSERT 缺失的会话行;若有孤儿会话,从 jsonl 重建索引行(schema 模仿现有行、cwd 重映射、user_id 按归属确认、标题从首条 user 消息剥离 system-reminder 和 <user_query> 标签提取)
  6. 校验与报告:七项验证清单,输出结构化报告

七、可执行代码片段

以下是这次迁移中验证过的核心代码,稍作参数化即可复用:

7.1 数据库合并式导入(运行时安全)

python 复制代码
import sqlite3
from pathlib import Path

BACKUP_DB = Path(r'C:\Users\Administrator\数据迁移\.workbuddy\workbuddy.db')
LIVE_DB = Path(r'C:\Users\Administrator\.workbuddy\workbuddy.db')

# 只读打开备份库
src = sqlite3.connect(f'file:{BACKUP_DB}?mode=ro', uri=True)
# 读写打开活动库(设 busy_timeout 应对偶发锁)
dst = sqlite3.connect(str(LIVE_DB))
dst.execute('PRAGMA busy_timeout = 5000')

# 路径重映射函数
def remap(s):
    if not s: return s
    return (s.replace('C:\\Users\\yanjing', 'C:\\Users\\Administrator')
             .replace('D:\\computerSoftware', 'E:\\computerSoftware'))

# 合并式导入:只 INSERT 缺失的会话行
existing = {r[0] for r in dst.execute('SELECT id FROM sessions')}
for row in src.execute('SELECT * FROM sessions'):
    sid = row[0]
    if sid in existing:
        continue  # 已存在,跳过(不覆盖)
    # 重映射 cwd 等路径字段
    row = list(row)
    # 假设 cwd 是第 N 列,按实际 schema 调整
    # row[N] = remap(row[N])
    placeholders = ','.join('?' * len(row))
    dst.execute(f'INSERT INTO sessions VALUES ({placeholders})', row)

dst.commit()
print(f'合并完成,当前会话数: {dst.execute("SELECT COUNT(*) FROM sessions").fetchone()[0]}')

7.2 jsonl 路径替换(覆盖全部转义变体)

python 复制代码
import re
from pathlib import Path

PROJECTS = Path(r'C:\Users\Administrator\.workbuddy\projects')

# 八种转义变体清单(必须全部覆盖)
REPLACEMENTS = [
    # 标准反斜杠
    (r'C:\Users\yanjing', r'C:\Users\Administrator'),
    (r'D:\computerSoftware', r'E:\computerSoftware'),
    # 双反斜杠(JSON 字符串内嵌)
    (r'C:\\Users\\yanjing', r'C:\\Users\\Administrator'),
    (r'D:\\computerSoftware', r'E:\\computerSoftware'),
    # 正斜杠
    ('C:/Users/yanjing', 'C:/Users/Administrator'),
    ('D:/computerSoftware', 'E:/computerSoftware'),
    # 小写盘符 + 双反斜杠(每行末尾 cwd 元字段常见形态)
    (r'c:\\Users\\yanjing', r'c:\\Users\\Administrator'),
    (r'd:\\computerSoftware', r'e:\\computerSoftware'),
]

total_replaced = 0
for jsonl in PROJECTS.rglob('*.jsonl'):
    text = jsonl.read_text(encoding='utf-8', errors='ignore')
    original = text
    for old, new in REPLACEMENTS:
        text = text.replace(old, new)
    if text != original:
        jsonl.write_text(text, encoding='utf-8')
        total_replaced += 1

print(f'处理文件数: {total_replaced}')

7.3 pyvenv.cfg 修复(不用重装 venv)

python 复制代码
from pathlib import Path

cfg = Path(r'C:\Users\Administrator\.workbuddy\binaries\python\envs\default\pyvenv.cfg')
text = cfg.read_text(encoding='utf-8')
new = text.replace(r'C:\Users\yanjing', r'C:\Users\Administrator')
cfg.write_text(new, encoding='utf-8')

# 验证
import subprocess
r = subprocess.run([
    str(Path(r'C:\Users\Administrator\.workbuddy\binaries\python\envs\default\Scripts\python.exe')),
    '-c', 'import sys, openpyxl, pandas; print("venv OK:", sys.version.split()[0])'
], capture_output=True, text=True)
print(r.stdout, r.stderr)

7.4 孤儿会话恢复(从 jsonl 重建索引行)

python 复制代码
import sqlite3, json, re, uuid
from pathlib import Path
from datetime import datetime

LIVE_DB = Path(r'C:\Users\Administrator\.workbuddy\workbuddy.db')
PROJECTS = Path(r'C:\Users\Administrator\.workbuddy\projects')
USER_ID = '11586a3d-ff86-4d41-aecc-bcbe0b991179'  # 当前账号

con = sqlite3.connect(str(LIVE_DB))
existing = {r[0] for r in con.execute('SELECT id FROM sessions')}

for proj_dir in PROJECTS.iterdir():
    if not proj_dir.is_dir(): continue
    for meta in proj_dir.glob('*.meta.json'):
        sid = meta.stem
        if sid in existing: continue  # 已注册,跳过
        m = json.loads(meta.read_text(encoding='utf-8'))
        # 从 jsonl 首条 user 消息提取标题
        jsonl = next(proj_dir.glob(f'{sid}.jsonl'), None)
        title = '未命名会话'
        if jsonl:
            for line in jsonl.read_text(encoding='utf-8').splitlines():
                try:
                    obj = json.loads(line)
                except: continue
                if obj.get('role') == 'user':
                    content = obj.get('content', '')
                    if isinstance(content, list):
                        content = ''.join(b.get('text', '') for b in content if isinstance(b, dict))
                    # 剥离 system-reminder 和 user_query 标签
                    content = re.sub(r'<system-reminder>.*?</system-reminder>', '', content, flags=re.DOTALL)
                    content = re.sub(r'<[^>]+>', '', content).strip()
                    title = content[:80] or '未命名会话'
                    break
        # 重映射 cwd
        cwd = m.get('cwd', '').replace('C:\\Users\\yanjing', 'C:\\Users\\Administrator')\
                            .replace('D:\\computerSoftware', 'E:\\computerSoftware')
        now = int(datetime.now().timestamp() * 1000)
        con.execute('''INSERT INTO sessions 
            (id, user_id, title, cwd, source_mode, created_at, last_activity_at, is_background_automation)
            VALUES (?, ?, ?, ?, ?, ?, ?, 0)''',
            (sid, USER_ID, title, cwd, 'agent', now, now))
        print(f'恢复: {sid} | {title[:50]}')

con.commit()

八、验证清单与最终结果

迁移完成后按以下清单逐项验证:

# 验证项 方法 期望结果
1 会话总数 SELECT COUNT(*) FROM sessions 等于旧机 + 新机会话数之和
2 cwd 目录存在性 对每条会话的 cwd 检查 os.path.exists 100% 存在
3 projects 目录名编码 目录名与 DB cwd 编码一致 一一对应
4 功能性路径残留 grep jsonl 中的功能性路径 0 处
5 venv 可用 import openpyxl, pandas 无报错
6 连接器配置 mcp.json server 名与旧机一致 完全覆盖
7 个性化四文件 SOUL/IDENTITY/USER/MEMORY 非默认模板 已定制

本次实测结果

指标 数值
迁移会话总数 47 条(37 旧 + 9 恢复 + 1 新建)
projects 目录重命名 37 个
jsonl 路径替换 约 1.17 万行
功能性路径残留 0 处
venv 修复 1 行配置改动
skills 修复 4 个文件
总耗时 约 1.5 小时(AI 自主完成)

唯一需要说明的"残留":26 个 jsonl 文件里仍有单词级 yanjing,这些是历史讨论文本(如"我在 yanjing 这台电脑上做过 XX"),不是功能性路径,官方脚本惯例保留,不影响功能。

九、经验沉淀:人机两份文档的分工

这次迁移最终产出两份文档,分工明确:

文档 读者 形态 内容侧重
《WorkBuddy 数据迁移指南(实践版)》 docx 认知 + 操作步骤 + 排查表,详尽到每步
《WorkBuddy 自动迁移指令书》 AI md 六阶段执行流程 + 安全铁律 + 技术备忘,可被 AI 整份读入

为什么需要两份?人需要"为什么这么做"的认知铺垫和"具体怎么点"的操作步骤;AI 需要"六阶段流程 + 安全铁律 + 坑位备忘"的执行手册。把执行细节塞进 docx 会增加人的认知负担,把认知铺垫塞进指令书会稀释 AI 的执行密度。

下次迁移时,新机 WorkBuddy 里发一句话即可启动全自动流程:

复制代码
请读取并严格执行 C:\Users\Administrator\Desktop\WorkBuddy自动迁移指令书.md,
按其中六阶段流程完成迁移。遇到指令书未覆盖的情况,先停下向我确认。

十、可复用经验提炼

最后提炼几条可复用的经验,适用于所有"AI 助手本地数据跨设备迁移"场景:

  1. 数据在本地还是云端,决定迁移复杂度------本地优先的助手(WorkBuddy、Claude Code、Codex)迁移复杂度高,云端优先的(ChatGPT、Gemini 网页版)几乎无需迁移
  2. 路径残留是主要矛盾,且分层分布------不要指望"全局替换"一招通吃,DB 字段、目录名、jsonl 正文、配置文件各有各的转义变体
  3. 运行中的 SQLite 用 SQL 操作安全,文件级覆盖危险------WAL 模式下合并式导入是运行时迁移的正确姿势
  4. 先迁移,后使用------新机装好后先别启动,否则产生的新会话会被整库覆盖冲掉
  5. AI 自主缝合是可行的------只要把流程、铁律、坑位备忘固化成指令书,AI 能自主完成 95% 的工作,剩下 5%(连接器重授权、系统定时任务、客户端重启)需用户手动
  6. 个性化配置要分清本地 vs 云端------SOUL/IDENTITY/MEMORY 是本地文件可迁,自定义指令纯云端只能 UI 粘贴,管理记忆云端每晚重生成属正常
  7. 备份保留 1-2 周再清理------日常使用无异常前别删备份

写在最后:这次迁移最让我感慨的是------执行迁移的 AI 就住在被迁移的客户端里,这本身是个有趣的递归。把"怎么做迁移"的方法论固化成 AI 能读的指令书,让 AI 自己执行,是这种递归场景下最自然的解法。希望这份记录能帮到同样要换电脑的 WorkBuddy 用户。

完整指南文档和自动迁移指令书已开源在桌面,欢迎参考。