`icon generate|show`:由**編號、Name、Emoji** 雜湊出配色與圖案,產出 512×512 的幾何徽章 icon.svg + icon.png,設為 Gitea 存取庫頭像並同步到 Wiki 區。 圖案(SVG 與 PNG 共用同一組單位座標與同一份 5×7 點陣字,**輸出的是同一張圖**): * 雙色對角漸層底 * 5×5 左右對稱點陣紋(identicon 式) * 中央兩個字母=編號前兩字(ASUNA-01 → AS),依背景亮度自動選黑/白 同一個人格永遠得到同一張圖(純函數,無隨機);ASUNA-01 與 ASUNA-02 明顯不同。 為什麼自己畫:這台機器(及多數伺服器)沒有 rsvg/inkscape/imagemagick, 沒有影像函式庫,也沒有 emoji 字型,而本專案禁止 npm 依賴。所以 scripts/persona-icon.mjs 自己柵格化(3× 超取樣 + 盒式縮減當反鋸齒), 再用內建 zlib 手工組出 IHDR/IDAT/IEND 與 CRC32。 emoji 無法柵格化,因此不作為圖形,但仍參與配色雜湊。 時機:**資料補齊之後才產生**——配色與字母綁在最終身分上,太早跑會對不上。 persona-create 第 7 步、persona-anime 第 8 步都補了這個步驟;改過身分用 --force 重畫。 selftest 128 項全綠(新增第 ⑭ 節:PNG chunk 合法性、決定性、不同人格不同圖、 圖示屬 Wiki 區、匯出用 base64 帶走二進位、guest 不得重畫)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
433 lines
25 KiB
Markdown
433 lines
25 KiB
Markdown
# 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. 建立並補齊資料後產生圖示** | `icon generate` 由**編號、Name、Emoji** 雜湊出配色與圖案,產出 `icon.svg` + `icon.png`(512×512)並設為 Gitea 存取庫頭像。同一個人格永遠得到同一張圖;PNG 是**自寫柵格器 + zlib 手工編碼**,不依賴任何影像函式庫 |
|
||
|
||
---
|
||
|
||
## 架構
|
||
|
||
```mermaid
|
||
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 之後**跑 `icon generate`,得到一張 512×512 的幾何徽章:
|
||
|
||
| 元素 | 怎麼決定 |
|
||
| --- | --- |
|
||
| 雙色對角漸層 | `sha256(編號|Name|Emoji)` → 色相/飽和度/明度 |
|
||
| 5×5 左右對稱點陣紋 | 同一組雜湊的位元 |
|
||
| 中央兩個字母 | 編號的前兩個字(`ASUNA-01` → `AS`),5×7 點陣,自動選黑或白確保對比 |
|
||
|
||
- **同一個人格永遠得到同一張圖**(純函數,沒有隨機);`ASUNA-01` 與 `ASUNA-02` 明顯不同。
|
||
- SVG 與 PNG **是同一張圖**:兩者共用同一組單位座標與同一份點陣字資料。
|
||
- PNG 由**自寫的柵格器**畫出(3× 超取樣 + 盒式縮減當反鋸齒),再用 `zlib` 手工組出
|
||
IHDR/IDAT/IEND 與 CRC32。這台機器沒有 rsvg/inkscape/imagemagick,也沒有影像函式庫,
|
||
而本專案禁止 npm 依賴——所以就自己畫。
|
||
- **為什麼沒有 emoji**:把 emoji 畫進 PNG 需要字型柵格化(機器上連 emoji 字型都沒有)。
|
||
emoji 仍然參與配色的雜湊,只是不作為圖形。
|
||
- 圖示屬於低頻資料 → 同步到 **Wiki 區**,並自動設成 Gitea **存取庫頭像**。
|
||
|
||
```bash
|
||
node scripts/persona.mjs icon generate --session <id> [--size 512] [--force] # 改過身分要重畫就 --force
|
||
node scripts/persona.mjs icon show --session <id>
|
||
```
|
||
|
||
## 人格編號與 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 首頁自動列出所有長期記憶的連結,變成真的讀得下去的「設定百科」。
|
||
|
||
```bash
|
||
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`)。
|
||
|
||
## 匯出 / 匯入(人格搬家)
|
||
|
||
```bash
|
||
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` 手動切):
|
||
|
||
- `UserPromptSubmit` hook 每輪注入強制規則:輸出**只能**是 `名字:內容`,
|
||
不得出現指令、指令輸出、狀態、分析、旁白、摘要。
|
||
- `Stop` hook 在劇場模式**完全不發系統訊息**(提醒會破壞畫面)。
|
||
- 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 目錄
|
||
|
||
<!-- JSC-SKILLS:START -->
|
||
|
||
### `persona-create`
|
||
|
||
建立人格:以 OpenClaw 相同的五個身分欄位與 SOUL 段落訪談使用者,初始化情緒基線、記憶、心智圖與關係圖,並立即取得載入鎖。
|
||
|
||
- **Claude Code / Antigravity**:`/jsc-persona:persona-create` **Codex**:`$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-transfer` **Codex**:`$persona-transfer`
|
||
|
||
### `persona-sync`
|
||
|
||
人格編號與 Gitea 儲存:指派編號(`ASUNA-01`)、開以編號命名的私有存取庫、高頻活狀態同步到檔案區、低頻身分與長期記憶同步到 Wiki 區,並處理既有人格遷移與同步衝突。
|
||
|
||
- **Claude Code / Antigravity**:`/jsc-persona:persona-sync` **Codex**:`$persona-sync`
|
||
|
||
### `persona-memory`
|
||
|
||
記憶固化:依 R1–R6 條件把短期記憶轉入長期(一則一檔)、淘汰雜訊、更新心智圖與思維導圖、消化 guest inbox、重建索引。
|
||
|
||
- **Claude Code / Antigravity**:`/jsc-persona:persona-memory` **Codex**:`$persona-memory`
|
||
|
||
### `persona-relation`
|
||
|
||
人際關係圖維護:節點/連線、親近度與信任度調整、輸出 Mermaid 關係圖。
|
||
|
||
- **Claude Code / Antigravity**:`/jsc-persona:persona-relation` **Codex**:`$persona-relation`
|
||
|
||
### `persona-status`
|
||
|
||
載入狀態與鎖管理:誰被哪個程序鎖住、guest 租約、釋放、接手死鎖、清理殘留。
|
||
|
||
- **Claude Code / Antigravity**:`/jsc-persona:persona-status` **Codex**:`$persona-status`
|
||
|
||
<!-- JSC-SKILLS:END -->
|
||
|
||
### Agents
|
||
|
||
- `persona-guest`(`agents/persona-guest.md`)— 受邀人格的 sub agent,唯讀、被綁死在自己的人格目錄。
|
||
|
||
### CLI 與自我測試
|
||
|
||
所有狀態變更都經過 `scripts/persona.mjs`(**Node.js ≥ 18**,只用內建模組,無 npm 依賴):
|
||
|
||
```bash
|
||
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 # 128 項驗證:鎖、隔離、情緒、固化、說話節制、劇場模式、匯出匯入、編號與 Gitea 分區、圖示、hooks
|
||
```
|
||
|
||
檔案結構:`scripts/persona-lib.mjs`(核心:鎖/隔離/情緒/記憶)、`scripts/persona.mjs`(CLI)、
|
||
`scripts/persona-gitea.mjs`(編號與 Gitea 同步)、`scripts/persona-icon.mjs`(圖示:SVG + 自寫 PNG 編碼)、`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.git`
|
||
> **Claude / Codex 從 git URL 安裝(會 clone 遠端),請先把本 repo `push` 到 gitea。**
|
||
|
||
### Claude Code
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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 後用本地路徑。
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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
|
||
|
||
1. 複製一個現有 skill 目錄,改 `SKILL.md` 的 `name` 與 `description`(描述要寫清楚何時用、何時不用)。
|
||
2. 需要新的狀態操作 → 加到 `scripts/persona.mjs` 的子指令,並在 `scripts/selftest.mjs` 補測試。
|
||
3. 動到隔離規則 → 一定要在 `selftest.mjs` 的第 ③(隔離)/⑧(guest 與劇場模式)區加對應案例,跑到全綠。
|
||
4. 把 skill 補進上方「Skills 目錄」區塊。
|
||
5. bump `.claude-plugin/plugin.json`、`.codex-plugin/plugin.json`、`plugin.json` 三處 `version`,commit 後 push。
|