1. 不再直接使用網路上找到的圖片
舊版把裁下來的官方美術當圖示(photo 樣式)。現在改成:
找圖 → `icon headshot` 裁出**大頭照當底稿** → AI 用 Read 親眼看過 →
讀出髮型/瀏海/眼型/表情/髮飾/領口等特徵 → **由本工具重新繪製**。
* 底稿寫在 <人格>/.sync/headshot.png,**不是圖示、不同步、不發佈**。
* 產出的 SVG 不得有 <image>/base64/外連(selftest 會擋)。
* 移除 photo 樣式與 photoSvg;config 裡殘留的舊樣式會被忽略而非退回徽章。
2. 重繪引擎:五官與造型可參數化
新增 --features:hairstyle(5) / length(4) / fringe(4) / eyes(4) /
expression(4) / accessory(5) / side / collar(4) / ahoge。
渲染器新增 polygon 圖元(呆毛、緞帶、V 領、銳利眼角),SVG 與自寫柵格器
仍共用同一份圖形清單,兩邊必然一致。
另外調了臉部比例:眉毛用「髮色偏膚色」避免深髮角色眉毛與瀏海連成黑帶、
加了鼻子(否則嘴會被看成鼻子)、眼與嘴的縱向配置重排。
3. Wiki 形象圖必須同步(並且會被驗證)
新增 `sync verify`(不一致以非零結束)與 verifyIconInWiki();
`icon generate` 推完 Wiki 會自動回頭確認 icon.svg + icon.png 真的在遠端
且與本機一致。
修掉一個會讓驗證永遠失敗的 bug:Wiki 首頁內嵌了 `最後同步 ${now}`,
每次產生都不同 → 永遠 dirty、每次 push 都多一個 commit。改用圖示的
generated_at。porcelain 解析也從固定位移改為正規式。
已重繪兩個真實人格(皆為 portrait 樣式,並通過 Wiki 同步驗證):
ASUNA-01 底稿=《Unanswered//butterfly》(2026) 主視覺左側;金蜜色極長直髮、
中分瀏海、呆毛、紅褐杏眼、沉靜神情、紅上衣 V 領
YUI-01 底稿=AniList 官方角色圖;藍黑極長直髮、齊瀏海、圓大棕眼、燦爛笑容,
髮飾與配色採現行 Unital Ring 導航妖精造型(淡粉洋裝、藍花)
selftest 161 項全綠(新增第 ⑰ 節:特徵解析、換髮型/眼型/表情會畫出不同的圖、
polygon 兩邊一致、舊樣式不污染、SVG 無內嵌影像、Wiki 首頁無時間戳)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
29 KiB
jsc-persona — AI 人格化記憶聊天
一套讓 AI 成為某個人、而不只是回答問題的 plugin。
以 OpenClaw 相同的人格描述(IDENTITY.md 五欄位 + SOUL.md 四段落)建立人格,
再用 hook 強制的人格鎖與跨人格隔離,把 十二情緒、語意分析、
短期/長期記憶、心智圖/思維導圖、人際關係圖 綁在一起。
可同時安裝於 Claude Code、Codex、Antigravity、OpenCode; skills 是共通標準,鎖與隔離的強制執行需要 hook,目前只有 Claude Code 支援(見「跨助理支援度」)。
十三條硬規則
| 規則 | 怎麼做到 |
|---|---|
| 1a. 用與 OpenClaw 相同的描述建立人格 | persona-create 逐項索取 Name / Creature / Vibe / Emoji / Avatar(連括號提示文字都照 OpenClaw 原文),SOUL.md 沿用 Core Truths / Boundaries / Vibe / Continuity 段落結構 |
| 1b. 動漫作品+角色名快速建人格 | persona-anime 先上網蒐集該角色的公開設定(至少 3 個獨立來源),映射成上述五欄位與 SOUL,再把設定固化成 canon 基礎記憶(每則帶來源 URL)+原作人際關係圖+情緒基線 |
| 2. 同一個人格只能被一個程序載入(Sub Agent 不限) | state/lock.json 以 session_id 為主鍵、15 分鐘心跳租約;同一 session 的 sub agent 沿用同一把鎖,跨 session 搶佔會被拒;租約過期才可接手(並強制回報) |
| 3. 禁止跨人格讀取資料 | PreToolUse hook 對 Read/Write/Edit/Glob/Grep/Bash 做路徑判定(含 ../、symlink、$PERSONA_HOME 繞路),非當前人格一律 deny;CLI 也驗 --session 防止冒用身分 |
| 4. 邀請人格用 Sub Agent 一起聊,且只顯示對話 | persona-invite 建聊天室 + guest 唯讀租約 + persona-guest sub agent;同時開啟劇場模式:hook 每輪強制「只輸出 名字:內容」、停掉所有系統提醒,CLI 有 --quiet 與 room script(乾淨對話稿) |
| 5. 腳本用 Node.js | scripts/*.mjs、hooks/*.mjs,只用 Node 內建模組(fs/path/os/crypto),無 npm 依賴 |
| 6. 由使用者呼叫才載入並鎖定 | 人格不會自動附身:SessionStart hook 只列出可用人格,等使用者下 /jsc-persona:persona-chat <slug>;載入即取得獨占鎖並綁定該 session |
| 7. 短期記憶轉入長期記憶有成文條件 | R1–R6 六條規則寫在程式裡(promotionCandidates),candidates 子指令會列出達標的候選與依據,hook 在達標時提醒固化 |
| 8. 講話像人:推導藏起來、一到三句、不重複 | 推導寫進心裡話 think(只回報「💭 心想 N 句」,永不回顯內容);說出口的話進 said.jsonl,下一輪注入「最近說過的話」提醒別重講;room post 直接擋下近似重複(字元 bigram+字集合相似度 ≥ 0.72)與超過三句的發言——劇場模式同樣適用 |
| 9. 人格可以匯出匯入 | export 把身分/情緒/記憶/心智圖/關係圖打包成單一 JSON bundle(可 --gzip、附 sha256),import 還原或換名複製;不帶載入鎖與 guest 租約,journal/ 要明確 --with-journal 才帶走 |
| 10. 人格有編號 | 編號 = 英文名全大寫 + 兩位索引(同名才遞增):ASUNA-01、YUI-01、ASUNA-02。編號同時是新人格的本機目錄名與 Gitea 存取庫名稱;中文名先轉羅馬拼音並跟使用者確認拼法 |
| 11. 人格存在 Gitea,依更新頻率分區 | 每個人格一個私有存取庫(庫名=編號)。檔案區放每輪都在變的活狀態(情緒/短期記憶/心裡話/說過的話/逐字),Wiki 區放低頻的身分與長期結構(IDENTITY/SOUL/長期記憶/心智圖/關係圖)當設定百科。本機仍是工作副本,同步失敗不阻斷對話 |
| 12. 圖示是重新繪製的人物形象圖(有臉) | 上網找出該人格最新一次登場的官方視覺 → 裁出大頭照當底稿 → 親眼看過 → 讀出髮型/瀏海/眼型/表情/髮飾等特徵 → 由本工具依人格資料重新繪製。絕不把找到的圖片當圖示:SVG 裡沒有 <image>、沒有 base64、沒有外連,底稿只留在 .sync/ 不同步。來源網址與重繪依據存進 config.json 備查 |
| 13. Wiki 必須保存並同步形象圖 | icon.svg 與 icon.png 都同步到 Wiki 區,另有自動產生的 Icon 頁展示兩種格式與來源;icon generate 推完會回頭驗證 Wiki 真的有這兩個檔案且與本機一致,另有 sync verify 可隨時檢查(不一致以非零結束) |
架構
flowchart TB
subgraph P["主程序(一個 session = 一個人格)"]
U["使用者訊息"] --> H1["UserPromptSubmit hook<br/>注入 情緒+短期記憶+命中的長期記憶+關係<br/>+心裡話+最近說過的話"]
H1 --> A["語意分析:意圖/主題/實體/情感/需求"]
A --> T["心裡話 think<br/>(推導只留在 inner.jsonl,不輸出)"]
T --> E["情緒評估 → 十二情緒 deltas"]
E --> R["以人格語氣回覆(1–3 句、不重複說過的話)"]
R --> W["記憶回寫(短期)"]
W --> H2["Stop hook:情緒衰減+續租+記說過的話+固化提醒"]
end
subgraph G["Sub Agent(受邀人格,唯讀)"]
GA["persona-guest"]
end
subgraph S["人格倉庫 ~/.claude/personas(工作副本)"]
PA["ASUNA-01/|IDENTITY SOUL 記憶 情緒 心智圖 關係圖"]
PB["YUI-01/|…"]
RM[".rooms/room/transcript.jsonl"]
end
subgraph GT["Gitea(存取庫名稱 = 人格編號)"]
GF["ASUNA-01 檔案區<br/>高頻活狀態"]
GW["ASUNA-01 Wiki<br/>低頻設定百科"]
end
P -->|"只能碰自己"| PA
GA -->|"只能碰自己"| PB
P <-->|"唯一合法交流管道"| RM
GA <--> RM
PA -->|"每輪背景 push"| GF
PA -->|"固化/改身分/release"| GW
GF -.->|"載入時 pull"| PA
GW -.-> PA
人格倉庫(預設 ~/.claude/personas/<slug>/,可用 PERSONA_HOME 覆寫)
<slug>/
├── IDENTITY.md # 身分卡:Name / Creature / Vibe / Emoji / Avatar(OpenClaw 同欄位)
├── SOUL.md # 靈魂:Core Truths / Boundaries / Vibe / Continuity + 情緒傾向
├── AGENTS.md # 操作規則(與個性分離)
├── icon.svg / icon.png # 人格圖示(由編號/名字/emoji 決定,也是 Gitea 存取庫頭像)
├── USER.md # 對使用者的畫像(事實/推測分開)
├── state/
│ ├── lock.json # 載入鎖(session_id + 心跳租約)
│ ├── guests.json # guest 唯讀租約
│ ├── emotion.json # 十二情緒 levels / baseline / 半衰期
│ ├── inner.jsonl # 心裡話(推導過程;只回報「心想 N 句」,不說出口)
│ ├── said.jsonl # 說過的話(用來擋短時間內的重複發言)
│ ├── sync.json # Gitea 同步狀態(最後 push / pull)
│ └── config.json # 含人格編號 code
├── memory/
│ ├── short-term.jsonl # 短期記憶(語意分析後;上限 240 筆 / 14 天)
│ ├── long-term/*.md # 長期記憶(一則一檔 + frontmatter)
│ ├── INDEX.md # 長期記憶索引(自動產生)
│ └── inbox/room-*.jsonl # 當 guest 時留下的見聞,待本體消化
├── mindmap/
│ ├── semantic.mmd # 心智圖:概念的長期關聯(Mermaid mindmap)
│ └── threads/*.mmd # 思維導圖:單一話題的推理鏈(Mermaid graph,短期)
├── relations/
│ ├── graph.json # 人際關係圖(親近度/信任度/連線)
│ └── graph.mmd # Mermaid 呈現(自動產生)
└── journal/YYYY-MM.jsonl # 原始逐字 + 情緒史(hook 自動寫)
(另有 `.sync/files/`、`.sync/wiki/`:兩個同步區的 git clone 快取,可安全刪除)
十二情緒
| 六正向 | 六負向 |
|---|---|
喜悅 joy、信任 trust、期待 anticipation、感激 gratitude、平靜 serenity、驚喜 delight |
憤怒 anger、悲傷 sadness、恐懼 fear、厭惡 disgust、羞愧 shame、焦慮 anxiety |
- 每種 0–100,各有不同半衰期(驚喜 60 分最快、信任 720 分最慢),每輪自動朝
baseline指數衰減。 - 由十二情緒推導
valence(正向/中性/負向)與arousal(高張/平穩/低張),決定語氣與句長。 - 主導情緒取「超出基線最多」的前三名,所以個性底色不會永遠霸榜。
- 觸發規則與事件→delta 對照表:
skills/persona-chat/reference/emotions.md。
短期 → 長期的轉入條件(R1–R6)
寫在 scripts/persona-lib.mjs 的 promotionCandidates(),用 candidates 子指令查:
| 規則 | 條件 | 建議固化為 |
|---|---|---|
| R1 | 單筆顯著度 ≥ 60 | event / fact |
| R2 | 同一 topic ≥ 3 筆,或 ≥ 2 筆且平均顯著度 ≥ 45 | preference |
| R3 | 單筆情緒變動總量 ≥ 25 | event(帶情緒錨點) |
| R4 | intent=commit 或命中承諾/界線關鍵詞 |
promise / boundary(salience ≥ 80,不可遺忘) |
| R5 | 同一人物(entity)≥ 2 筆 | relationship(並更新關係圖) |
| R6 | 短期記憶 ≥ 40 筆(容量壓力) | 依顯著度清出空間 |
沒命中任何規則的就讓它被裁掉——遺忘是功能。達標時 Stop 與 remember 都會提醒去跑
/jsc-persona:persona-memory。
講話像人(心裡話 / 一到三句 / 不重複)
AI 最容易露餡的三件事:把推理過程講出來、一次講一大段、換句話說同一件事。三個對策:
| 機制 | 怎麼運作 |
|---|---|
心裡話 think |
語意分析、推論、盤算全寫進 state/inner.jsonl;這個指令只印「💭 心想 N 句」,內容永不回顯。下一輪 <persona-context> 會帶回最近三句,推論因此有連續性,但使用者只看得到狀態 |
| 一到三句 | <persona-context> 每輪注入上限;room post 對超過三句的發言直接拒收(--force 例外) |
| 不重複 | 說出口的話由 Stop hook 自動記進 state/said.jsonl;said check 可事前確認,room post 事中攔截。相似度=字元 bigram Jaccard(0.4)+字集合 Jaccard(0.6),≥ 0.72 視為同一句 |
字集合權重較高,是為了分開「重排語序」與「換掉關鍵詞」這兩種很像但意義完全不同的情況:
「我等一下把報告寄給你」vs「等一下我會把報告寄給你」→ 0.779 擋下(用字幾乎相同=同一件事換句話說)
「你今天看起來很累」 vs「你今天看起來很開心」 → 0.687 放行(換了關鍵詞=新資訊)
視窗預設 120 分鐘、少於 8 個字的短附和(「嗯」「好啊」)不算重複。
人格圖示(SVG + PNG,零外部依賴)
建立人格並補齊 IDENTITY/SOUL 之後產生,512×512。優先是人物形象圖(看得到臉), 依環境有無工具分三種樣式:
流程是 找圖 → 裁大頭照當底稿 → 親眼看過 → 讀出特徵 → 重新繪製:
| 步驟 | 指令 | 產出 |
|---|---|---|
| 1. 找臉 | icon faces --photo <圖> |
圖裡所有臉的座標(多角色務必先看) |
| 2. 裁底稿 | icon headshot --photo <圖> --pick N |
.sync/headshot.png(不是圖示、不同步) |
| 3. 看 | Read 打開底稿 + 讀 IDENTITY/SOUL | 髮型、瀏海、眼型、表情、髮飾、領口 |
| 4. 重繪 | icon generate --palette ... --features ... |
icon.svg + icon.png |
| 樣式 | 條件 | 長什麼樣 |
|---|---|---|
portrait |
有底稿(給了調色盤)→ 預設 | 依特徵重繪的人物頭像,五官俱全 |
badge |
完全沒有參考圖 | 雙色漸層 + 編號前兩個字母 |
找到的圖片只能當底稿:產出的 SVG 沒有 <image>、沒有 base64、沒有外連,每個像素都是畫出來的。
裁底稿需要的工具缺了,CLI 會印出安裝指令,不會靜默降級:
python3 -m venv ~/.cache/jsc-persona/venv
~/.cache/jsc-persona/venv/bin/pip install pillow "opencv-python-headless<5" # OpenCV 5 拿掉了 CascadeClassifier
curl -sL -o ~/.cache/jsc-persona/lbpcascade_animeface.xml \
https://raw.githubusercontent.com/nagadomi/lbpcascade_animeface/master/lbpcascade_animeface.xml
(venv 放這個路徑會被自動偵測;也可用 PERSONA_PYTHON 指定。)
配色一律取自實際看過的參考圖
| 元素 | 取自照片的哪裡 |
|---|---|
| 對角漸層 | hair(髮色)→ accent(服裝主色) |
| 外框 | eye(瞳色) |
| 點陣紋 | light(最亮的部位) |
| 中央兩個字母 | 編號前兩字(ASUNA-01 → AS) |
--palette 必須配 --source-url(CLI 強制):配色是從哪張圖來的要留得下來。
兩個顏色一深一淺時(藍黑髮 + 淡粉洋裝),程式會把較亮的一端往較暗的壓到對比 ≥ 3.2,
確保字讀得到——顏色仍然是照片來的,只是收斂色階。
沒有參考圖時(原創人格)才退回雜湊配色:sha256(編號|Name|Emoji) 決定漸層與點陣紋。
- 同一個人格永遠得到同一張圖(純函數,沒有隨機);
ASUNA-01與ASUNA-02明顯不同。 - SVG 與 PNG 是同一張圖:兩者共用同一組單位座標與同一份點陣字資料。
- PNG 由自寫的柵格器畫出(3× 超取樣 + 盒式縮減當反鋸齒),再用
zlib手工組出 IHDR/IDAT/IEND 與 CRC32。這台機器沒有 rsvg/inkscape/imagemagick,也沒有影像函式庫, 而本專案禁止 npm 依賴——所以就自己畫。 - 為什麼不直接用找到的圖:那是別人的美術作品。底稿只用來「看」,圖示由本工具重畫。
- 為什麼沒有 emoji:把 emoji 畫進 PNG 需要字型柵格化,環境裡連 emoji 字型都沒有; emoji 仍參與雜湊配色。
- 圖示屬於低頻資料 →
icon.svg與icon.png都同步到 Wiki 區,並自動產生一頁 Icon 展示兩種格式與來源;PNG 同時設成 Gitea 存取庫頭像。
node scripts/persona.mjs icon faces --session <id> --photo "<圖片網址>"
node scripts/persona.mjs icon headshot --session <id> --photo "<圖片網址>" --pick 1
node scripts/persona.mjs icon generate --session <id> --force \
--palette "hair=#d9a45b,eye=#9e5b3e,accent=#c0392b,secondary=#e77a8e,light=#f2ebe3,skin=#f7ddc4" \
--features "hairstyle=straight,length=very-long,fringe=parted,eyes=almond,expression=calm,collar=v,ahoge=yes" \
--source-url "<底稿那張圖的網址>" --source-note "<作品(年份)+重繪依據>"
node scripts/persona.mjs icon show --session <id> # 樣式、配色、特徵、來源
node scripts/persona.mjs sync verify --session <id> --area wiki # 確認 Wiki 真的同步了
人格編號與 Gitea 儲存
編號 = 英文名全大寫 + 兩位索引,同名才遞增,也就是 Gitea 存取庫的名稱:
亞絲娜(第一個) → ASUNA-01 結衣 → YUI-01 另一個亞絲娜 → ASUNA-02
每個人格一個私有存取庫,內容依更新頻率分兩區:
| 區 | 放什麼 | 何時 push |
|---|---|---|
| 檔案區(主存取庫) | 高頻活狀態:emotion.json、short-term.jsonl、inner.jsonl、said.jsonl、inbox/、mindmap/threads/、journal/ |
每輪對話後由 Stop hook 背景推送(PERSONA_SYNC_MIN_SECONDS 節流) |
| Wiki 區 | 低頻設定:IDENTITY/SOUL/AGENTS/USER、長期記憶、INDEX、心智圖、關係圖 |
記憶固化、改身分/關係圖、release 時 |
- 本機永遠是工作副本:hook 每輪讀寫本機檔案,不經網路;Gitea 掛掉照樣能聊天。 同步失敗永遠不阻斷對話。
- 載入人格時會先
pull;兩邊都改過同一個檔案就停下來不覆蓋本機,由使用者決定保留哪一邊。 - Gitea 的 wiki 只有根目錄的
.md會變成頁面(1.27 實測子目錄頁面 404),所以memory/long-term/xxx.md攤平成Memory-xxx.md,原始路徑記在_paths.json; Wiki 首頁自動列出所有長期記憶的連結,變成真的讀得下去的「設定百科」。
export GITEA_HOST=https://gitea.example.com
export GITEA_TOKEN=<個人存取權杖>
export PERSONA_GITEA_OWNER=<帳號或組織> # 選填,預設 token 本人
export PERSONA_GITEA=off # 需要時整個關掉
node scripts/persona.mjs code assign --session <id> --romaji Asuna --rename # 既有人格遷移
node scripts/persona.mjs sync status|init|push|pull --session <id> [--area files|wiki|all]
存取庫預設私有——人格裡是使用者的個人記憶,公開必須由使用者明講(--public)。
匯出 / 匯入(人格搬家)
node scripts/persona.mjs export --session <id> --out ~/backup/lumi.persona.json [--gzip] [--with-journal]
node scripts/persona.mjs import --session <id> --file ~/backup/lumi.persona.json [--persona lumi-copy] [--load]
- bundle = 單一 JSON(可 gzip)+ sha256 checksum,無外部工具依賴。
- 只能匯出本 session 目前載入的人格——否則就是跨人格外洩的後門(
guard會 deny)。 - 不帶
state/lock.json/guests.json(鎖屬於那台機器的那個程序);journal/預設不帶。 - 換名匯入(
--persona <新 slug>)可讓同一個人格並存兩份,config.json會記下來歷。 - bundle 內的
../逃逸路徑一律拒收;checksum 不符要--force才吃。
劇場模式(多人格對話只顯示對話)
invite 成功即開啟(leave 沒有客人時自動關閉,也可 room theater --on/--off 手動切):
UserPromptSubmithook 每輪注入強制規則:輸出只能是名字:內容, 不得出現指令、指令輸出、狀態、分析、旁白、摘要。Stophook 在劇場模式完全不發系統訊息(提醒會破壞畫面)。- CLI 提供
--quiet(成功時零輸出)與room script(只有emoji 名字(情緒):內容的乾淨對話稿)。 - 「講話像人」的三條規則在這裡一樣生效:每個人格每輪 1–3 句、推導走
think、 近似重複的台詞被room post拒收(host 與 guest 走同一支 CLI,一視同仁)。
🪼 Lumi(喜悅42/期待31):所以你真的一個人把那台舊鐘修好了?
🌙 Shen(平靜50/信任38):修好了。它現在慢三分鐘,我決定不修那三分鐘。
Hooks(Claude Code)
| Hook | 做什麼 |
|---|---|
SessionStart |
清死鎖、接續人格、把 PERSONA_SESSION=<session_id> 與規則注入上下文 |
UserPromptSubmit |
注入 <persona-context>:身分、情緒、短期記憶、關鍵詞命中的長期記憶、相關人際關係;劇場模式時追加「只輸出人格對話」的強制規則;並記原始逐字 |
PreToolUse |
人格隔離與鎖驗證的唯一強制點(deny 帶原因) |
Stop |
情緒隨時間衰減、續租、記錄回覆、達固化條件時提醒(劇場模式時完全靜音) |
SubagentStop |
解除 guest sub agent 的 pin |
SessionEnd |
釋放鎖與 guest 租約,人格才能被下一個程序載入 |
session_id只有 hook 拿得到 → 注入上下文 → skills 呼叫 CLI 時必須帶--session, hook 會驗證是否相符。這是「一人格一程序」與「跨人格隔離」不能被繞過的關鍵。
Skills 目錄
persona-create
建立人格:以 OpenClaw 相同的五個身分欄位與 SOUL 段落訪談使用者,初始化情緒基線、記憶、心智圖與關係圖,並立即取得載入鎖。
- Claude Code / Antigravity:
/jsc-persona:persona-createCodex:$persona-create
persona-anime
用「動漫作品+角色名」建立人格:上網蒐集角色公開設定 → 映射成 OpenClaw 五欄位與 SOUL → 固化成 canon 基礎記憶(附來源)+原作關係圖+情緒基線。
- Claude Code / Antigravity:
/jsc-persona:persona-anime 《作品》 角色名Codex:$persona-anime
persona-chat
載入人格並對話:取得獨占鎖 → 每輪做語意分析(推導寫進心裡話,不說出口)→ 更新十二情緒 → 回想記憶與關係 → 以人格語氣回覆(1–3 句、不重複說過的話)→ 寫回記憶。
- Claude Code / Antigravity:
/jsc-persona:persona-chat <slug>Codex:$persona-chat
persona-invite
邀請另一個人格透過 persona-guest sub agent 加入聊天室,進入劇場模式(畫面只留 名字:內容 的人格對話);結束後讓它離場並把見聞留在它自己的 inbox。
- Claude Code / Antigravity:
/jsc-persona:persona-invite <slug>Codex:$persona-invite
persona-transfer
人格搬家:把身分、十二情緒、短期/長期記憶、心智圖與關係圖打包成單一 bundle 檔(可 gzip、附 checksum),或從 bundle 還原/換名複製成新人格。
- Claude Code / Antigravity:
/jsc-persona:persona-transferCodex:$persona-transfer
persona-icon
依人格最新一次登場的官方視覺產生圖示:上網查最新造型 → 下載並親眼看過參考圖 → 取髮色/瞳色/服裝色 → 繪製 icon.svg + icon.png,來源網址一併存證。
- Claude Code / Antigravity:
/jsc-persona:persona-iconCodex:$persona-icon
persona-sync
人格編號與 Gitea 儲存:指派編號(ASUNA-01)、開以編號命名的私有存取庫、高頻活狀態同步到檔案區、低頻身分與長期記憶同步到 Wiki 區,並處理既有人格遷移與同步衝突。
- Claude Code / Antigravity:
/jsc-persona:persona-syncCodex:$persona-sync
persona-memory
記憶固化:依 R1–R6 條件把短期記憶轉入長期(一則一檔)、淘汰雜訊、更新心智圖與思維導圖、消化 guest inbox、重建索引。
- Claude Code / Antigravity:
/jsc-persona:persona-memoryCodex:$persona-memory
persona-relation
人際關係圖維護:節點/連線、親近度與信任度調整、輸出 Mermaid 關係圖。
- Claude Code / Antigravity:
/jsc-persona:persona-relationCodex:$persona-relation
persona-status
載入狀態與鎖管理:誰被哪個程序鎖住、guest 租約、釋放、接手死鎖、清理殘留。
- Claude Code / Antigravity:
/jsc-persona:persona-statusCodex:$persona-status
Agents
persona-guest(agents/persona-guest.md)— 受邀人格的 sub agent,唯讀、被綁死在自己的人格目錄。
CLI 與自我測試
所有狀態變更都經過 scripts/persona.mjs(Node.js ≥ 18,只用內建模組,無 npm 依賴):
node scripts/persona.mjs --help
node scripts/persona.mjs list
node scripts/persona.mjs candidates --session <PERSONA_SESSION> # 看哪些短期記憶該固化
node scripts/persona.mjs think --session <id> --text "<推導>" # 心裡話(只回報「心想 N 句」)
node scripts/persona.mjs said check --session <id> --text "<話>" # 這句是不是又要說一次?
node scripts/persona.mjs room script --session <id> --room <room> # 乾淨對話稿(劇場模式用)
node scripts/persona.mjs export --session <id> --out lumi.json # 離線搬家(單檔)
node scripts/persona.mjs sync status --session <id> # Gitea 同步狀態
node scripts/selftest.mjs # 161 項驗證:鎖、隔離、情緒、固化、說話節制、劇場模式、匯出匯入、編號與 Gitea、形象圖重繪與 Wiki 同步、hooks
檔案結構:scripts/persona-lib.mjs(核心:鎖/隔離/情緒/記憶)、scripts/persona.mjs(CLI)、
scripts/persona-gitea.mjs(編號與 Gitea 同步)、scripts/persona-icon.mjs(形象圖:SVG + 自寫 PNG 編碼)、scripts/portrait.py(選用:照片裁臉)、hooks/*.mjs(六個 hook)、scripts/selftest.mjs(自我測試)。
跨助理支援度
| 助理 | skills | hooks(鎖/隔離強制) | 受邀人格 sub agent |
|---|---|---|---|
| Claude Code | ✅ /jsc-persona:<name> |
✅ 完整 | ✅ jsc-persona:persona-guest |
| Codex | ✅ $<name> |
❌ | ⚠ 需自行以子任務模擬 |
| Antigravity | ✅ /jsc-persona:<name> |
❌ | ⚠ |
| OpenCode | ✅ 依描述自動觸發 | ❌ | ⚠ |
沒有 hook 的助理仍會遵守 CLI 層的檢查(
--session綁定、require_owner/require_member、 guest 唯讀),但那是自律而非強制:真正的 deny 只有 Claude Code 的PreToolUse做得到。
安裝 / 更新 / 移除
指令中的 repo 網址換成你的:
https://gitea.jsc.idv.tw/plugins/persona.gitClaude / Codex 從 git URL 安裝(會 clone 遠端),請先把本 repopush到 gitea。
Claude Code
claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/persona.git
claude plugin install jsc-persona@jsc-plugins
# 更新
claude plugin marketplace update jsc-plugins
claude plugin update jsc-persona@jsc-plugins
# 移除
claude plugin uninstall jsc-persona@jsc-plugins
- 工作階段內 slash 版(等價):把
claude plugin換成/plugin。 - 本機開發(免 push):
claude plugin marketplace add /home/coder/plugins/persona後再 install。 - 安裝後重啟工作階段讓 hooks 生效;用
/hooks確認六個 hook 都在。
Codex
codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/persona.git
codex plugin add jsc-persona@jsc-plugins
codex plugin marketplace upgrade jsc-plugins # 更新
codex plugin remove jsc-persona@jsc-plugins # 移除
Antigravity(agy)
agy plugin install <url>目前只支援 github.com;gitea 請 clone 後用本地路徑。
git clone https://gitea.jsc.idv.tw/plugins/persona.git ~/plugins/persona
agy plugin install ~/plugins/persona
# 更新:git -C ~/plugins/persona pull && agy plugin uninstall jsc-persona && agy plugin install ~/plugins/persona
OpenCode
git clone https://gitea.jsc.idv.tw/plugins/persona.git ~/plugins/persona
mkdir -p ~/.config/opencode/skills
cp -r ~/plugins/persona/skills/* ~/.config/opencode/skills/
Windows PowerShell:
cp -r A B→Copy-Item A B -Recurse -Force、~→$HOME。
headless 一次性執行
| 助理 | 指令 |
|---|---|
| Claude Code | claude -p "/jsc-persona:persona-chat lumi" |
| Codex | codex exec '$persona-chat lumi' |
| Antigravity | agy -p "/jsc-persona:persona-chat lumi" |
| OpenCode | opencode run "用 lumi 這個人格跟我聊聊" |
設計取捨(讀之前先知道)
- 記憶是被策展的,不是全存:逐字稿進
journal/,但只有經過語意分析、有顯著度的內容才進短期記憶, 再由persona-memory決定什麼值得成為長期記憶。遺忘是功能。 - 事實與推測分離:推論走思維導圖(
mindmap/threads/),驗證後才升格長期記憶。 - 鎖的擁有者是 session 不是 process:CLI 跑完就結束,所以租約判定只看心跳。
- guest 唯讀:受邀人格不能在別人的 session 裡改自己的長期記憶(避免兩個程序同時寫),
只能把見聞放進
memory/inbox/,等它自己被載入時消化。 - hook 是強制、SKILL 是引導:SKILL.md 寫的規則模型可能忘記,hook 不會。
- 「不要說出來」要靠設計而不是靠忍住:所以心裡話有自己的指令與檔案, 而且那個指令印不出內容——就算不小心把輸出貼上去,使用者也只看到「💭 心想 N 句」。
- 重複用相似度擋、不用語意判斷:字元層級的比對沒有模型成本、行為可預期,
誤判時有
--allow-repeat可救;漏判的代價(人格自我重複)比誤判高。
新增/修改 skill
- 複製一個現有 skill 目錄,改
SKILL.md的name與description(描述要寫清楚何時用、何時不用)。 - 需要新的狀態操作 → 加到
scripts/persona.mjs的子指令,並在scripts/selftest.mjs補測試。 - 動到隔離規則 → 一定要在
selftest.mjs的第 ③(隔離)/⑧(guest 與劇場模式)區加對應案例,跑到全綠。 - 把 skill 補進上方「Skills 目錄」區塊。
- bump
.claude-plugin/plugin.json、.codex-plugin/plugin.json、plugin.json三處version,commit 後 push。