底層本來就走得通(`pullArea` 的 restore 會把本機缺少的檔案全部補進來), 擋住的是上層的雞生蛋:`sync` 先走 `requireOwner`,而 `requireOwner` 第一件事 就是「本機沒有這個人格就 die」。本機沒有它 → load 不了它 → sync pull 被擋 → 永遠拉不回來。所以照 `import` 的模式另開一個只驗 session、不驗 host 的入口。 * `clone --code <編號>`:兩區都拉回來(Wiki 區給身分與長期記憶,檔案區給活狀態), 然後補上 `pullArea` 不管的那幾件事——驗 IDENTITY.md(`validateBundle` 明文的 人格最低要件,Gitea 這條路上原本不存在)、補寫 config.code/來歷、 `rebuildIndex()`、`renderRelations()`。拉回來不成人格就中止並清掉半成品。 * `clone`(不帶 --code):列出遠端有哪些人格、哪些本機還沒有。 整個 codebase 原本沒有任何「列出 owner 底下的存取庫」的呼叫,新增 `listRemotePersonas()`:分頁打 `GET /user/repos`(他人/組織走 `/users/<owner>/repos`), 用編號格式過濾——存取庫名稱就是人格編號,所以那份清單就是遠端的人格清單。 * 本機已有同名人格時**預設不覆蓋**;`--force` 才蓋(沿用 `import` 的兩道保護: 不得覆寫別人、不得覆寫正被其他程序載入的人格),`--persona` 可並存兩份。 * 加進 `OWNER_EXEMPT_SUBCOMMANDS`,否則已載入其他人格時會被 hook deny。 * 編號衝突:`nextCode()` 只掃本機,換機器會重複發號。新增 `nextCodeAcrossMachines()`,發號前先問遠端已經用掉哪些編號;Gitea 連不上 就退回本機答案並在輸出明講「只對過本機」。`create` 與 `code assign/next` 都改用它。 * 順手修正 `ensureRepo` 的建庫路由:`me` 取自 `resolveOwner()`,而它在有 `PERSONA_GITEA_OWNER` 時只會把那個值原封不動還回來,於是組織永遠走成 `/user/repos`(建到 token 本人底下)。改用不受該環境變數影響的 `giteaLogin()`。 測試:selftest 新增第 ㉒ 區,用 file:// 的裸倉庫當「假的 Gitea」跑完整往返 (推兩區 → 刪掉本機人格 → clone 回來 → 驗身分/長期記憶/活狀態/索引/關係圖), 並涵蓋前兩個修正(子資料夾與非 ASCII 檔名真的進了存取庫、push 覆蓋遠端的回報 與 Stop hook 只吵一次)。345 → 373 項全過。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
86 lines
4.6 KiB
Markdown
86 lines
4.6 KiB
Markdown
---
|
||
name: persona-transfer
|
||
description: 匯出與匯入人格:把一個人格(身分、靈魂、十二情緒、短期與長期記憶、心智圖、思維導圖、人際關係圖)打包成單一 bundle 檔,或從 bundle 還原/複製成新人格。當使用者說要備份人格、把人格搬到另一台機器或另一個 AI 助理、把人格分享給別人、從檔案還原人格、複製一份人格來做實驗、或問「這個人格能不能帶走」時觸發。不適用於:建立全新人格(用 persona-create 或 persona-anime)、載入既有人格聊天(用 persona-chat)、整理記憶(用 persona-memory)。
|
||
---
|
||
|
||
# 📦 persona-transfer — 人格搬家(匯出/匯入)
|
||
|
||
**CLI**:`node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs"`(一律帶 `--session <PERSONA_SESSION>`)
|
||
|
||
bundle 是**單一 JSON 檔**(可 gzip),不依賴任何外部工具,複製到隨身碟、貼進 git、
|
||
傳給另一台機器都可以。
|
||
|
||
> 只是要在另一台機器接續同一個人格、而且兩邊都連得到 Gitea 的話,**不必經過檔案**:
|
||
> 用 `/jsc-persona:persona-sync` 的 `clone --code <編號>` 直接從存取庫拉一份回來。
|
||
> 這裡的 bundle 是給離線、換助理、或想留一份快照的情境。
|
||
|
||
---
|
||
|
||
## bundle 帶走什麼、不帶什麼
|
||
|
||
| 帶走 | 不帶 |
|
||
| --- | --- |
|
||
| `IDENTITY.md` / `SOUL.md` / `AGENTS.md` / `USER.md` | `state/lock.json`、`state/guests.json`(載入鎖與 guest 租約屬於「那台機器的那個程序」) |
|
||
| `state/config.json`、`state/emotion.json`(十二情緒的 levels/baseline/半衰期) | `journal/`(原始逐字稿:量大且最私密,要帶請明確加 `--with-journal`) |
|
||
| `state/inner.jsonl`(心裡話)、`state/said.jsonl`(說過的話) | 任何**其他人格**的資料——一個 bundle 只有一個人格 |
|
||
| `memory/`(短期、長期一則一檔、INDEX、inbox) | |
|
||
| `mindmap/`(心智圖與思維導圖)、`relations/`(關係圖) | |
|
||
|
||
檔案裡有 `checksum`(sha256),匯入時會驗;對不上就是被改過或損毀。
|
||
|
||
---
|
||
|
||
## 匯出
|
||
|
||
**只能匯出「本 session 目前載入的那個人格」**——這是隔離規則的一部分,
|
||
不然匯出就成了把別人的記憶讀出來的後門。所以要先載入它:
|
||
|
||
```bash
|
||
# 1) 先載入(若還沒載入)
|
||
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" load --persona <slug> --session <PERSONA_SESSION>
|
||
|
||
# 2) 匯出
|
||
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" export \
|
||
--session <PERSONA_SESSION> --out ~/persona-backup/<slug>.persona.json
|
||
```
|
||
|
||
選項:
|
||
|
||
- `--with-journal`:連逐字稿一起帶(會大很多,而且很私密——**要先問使用者**)。
|
||
- `--gzip`:輸出 `.json.gz`(大人格建議加;匯入會自動辨識)。
|
||
- `--force`:覆寫已存在的輸出檔。
|
||
- 不給 `--out` 就寫到現在的工作目錄,檔名 `<slug>-<時間戳>.persona.json`。
|
||
|
||
**不要把 bundle 寫進人格倉庫裡**(`~/.claude/personas/`)——hook 會擋,而且那裡是人格的家,不是備份區。
|
||
|
||
## 匯入
|
||
|
||
```bash
|
||
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" import \
|
||
--session <PERSONA_SESSION> --file ~/persona-backup/<slug>.persona.json
|
||
```
|
||
|
||
| 情況 | 做法 |
|
||
| --- | --- |
|
||
| 這台機器還沒有這個人格 | 直接匯入,slug 沿用 bundle 內的名字 |
|
||
| 想同時保留兩份(原版 + 實驗版) | `--persona <新 slug>` 換名匯入;`config.json` 會記下它來自誰 |
|
||
| 要用匯入的資料**覆蓋**既有人格 | `--force`——**這會蓋掉現有記憶與情緒,先問過使用者**;且該人格不能正被其他程序載入 |
|
||
| 匯入後想馬上聊 | 加 `--load`(本 session 沒有載入別的人格時才會生效) |
|
||
|
||
checksum 對不上會被擋下;要硬吃得加 `--force`,並**主動告訴使用者這份檔案可能被改過**。
|
||
|
||
## 跟使用者互動的原則
|
||
|
||
1. **匯出前確認範圍**:要不要帶 `journal/`(逐字稿)。預設不帶。
|
||
2. **匯出後回報實際路徑與大小**,還有帶了幾則長期記憶——他要拿去搬家,得知道搬了什麼。
|
||
3. **匯入前先 `list`**,看目標 slug 是否已存在:存在就給他「換名匯入 / 覆寫」兩個選擇,
|
||
不要自己決定 `--force`。
|
||
4. 匯入後告訴他 `/jsc-persona:persona-chat <slug>` 就能開始聊。
|
||
|
||
## 邊界
|
||
|
||
- 一次一個人格。要搬多個就跑多次(每次都要先 `release` 再 `load` 下一個)。
|
||
- bundle 內的相對路徑會被驗證,`../` 之類的逃逸路徑一律拒收。
|
||
- 匯入不會帶進鎖:新機器上的人格是空閒的,等使用者自己載入。
|
||
- 所有面向使用者的輸出使用**繁體中文(台灣用語)**、UTF-8 無亂碼。
|