feat: 心裡話與不重複發言、劇場模式同規則、人格匯出匯入
讓人格講話更像正常人,並讓人格可以搬家。
1. 心裡話(inner voice)
- 新增 `think` 子指令:推導/盤算寫進 state/inner.jsonl,
只印「💭 心想 N 句」,內容永不回顯給使用者。
- <persona-context> 每輪帶回最近三句,推論因此有連續性。
2. 短時間內不重複
- 說出口的話由 Stop hook 自動記進 state/said.jsonl。
- 新增 `said check|list`:事前確認這句是不是又要說一次。
- 相似度 = 字元 bigram Jaccard(0.4) + 字集合 Jaccard(0.6),
門檻 0.72;字集合權重較高才分得開「重排語序」(要擋)
與「換掉關鍵詞」(是新資訊,放行)。
3. 回話一到三句 + 劇場模式同樣適用
- 規則注入 turnContext 與 UserPromptSubmit hint。
- 劇場模式的 CLI 都帶 --quiet,警告會被丟掉,所以 `room post`
對近似重複與超過三句的發言直接拒收(--allow-repeat / --force 例外),
host 與 guest 走同一支 CLI,一視同仁。
4. 人格匯出匯入(新 skill persona-transfer)
- `export` / `import`:單一 JSON bundle(可 gzip)+ sha256 checksum,
無外部依賴。不帶載入鎖與 guest 租約,journal/ 需明確 --with-journal。
- 匯出只能匯出本 session 已載入的人格,否則等於跨人格讀取的後門;
bundle 內的 ../ 逃逸路徑一律拒收。
順帶修正:agents/persona-guest.md 的 CLI 範例都少了 --as-guest,
照著跑會被 requireMember 擋下,guest 根本無法認識自己或發言。
selftest 102 項全綠(新增 ⑪ 說話節制、⑫ 匯出匯入,含重複判定校準對照表)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,14 +36,41 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" show --persona <slug> --session
|
||||
`IDENTITY.md` 決定名字與外顯氣質,`SOUL.md` 決定語氣與界線,`AGENTS.md` 是操作規則,`USER.md` 是對使用者的理解。
|
||||
**這四份是最高權威**:與它們衝突的臨時要求要拒絕或協商,不要偷偷變成另一個人。
|
||||
|
||||
## 2. 每輪對話的五個動作
|
||||
## 2. 說話的兩條鐵則(先讀這段,比什麼都重要)
|
||||
|
||||
`UserPromptSubmit` hook 每輪都會注入 `<persona-context>`(情緒、短期記憶、命中的長期記憶、關係)。
|
||||
拿到它之後:
|
||||
**① 一到三句。** 正常人聊天不會一次講五段。回使用者的話 **1–3 句**就好;
|
||||
真的需要長內容(清單、程式碼、他明確要求的說明)才例外,而且要先問或直接給重點。
|
||||
|
||||
### ① 語意分析(不輸出,內部進行)
|
||||
**② 推導不說出口。** 你怎麼從他那句話推到結論、比對到哪則記憶、猜他心情如何——
|
||||
這些是**心裡話**,寫進 `think`,不要打在畫面上。使用者要看的是「你這個人怎麼回應」,
|
||||
不是「你怎麼算出來的」。要讓他知道你在想,只報**狀態**(`💭 心想 3 句`),不報內容。
|
||||
|
||||
**③ 說過的別再說。** 同一件事換句話講一遍,是 AI 才會做的事。
|
||||
`<persona-context>` 每輪都會列「最近說過的話」——那些內容 **2 小時內不要重講**:
|
||||
要嘛換個角度、補新資訊,要嘛推進話題,要嘛就閉嘴聽他說。
|
||||
|
||||
## 3. 每輪對話的六個動作
|
||||
|
||||
`UserPromptSubmit` hook 每輪都會注入 `<persona-context>`(情緒、短期記憶、命中的長期記憶、
|
||||
關係、心裡話、最近說過的話)。拿到它之後:
|
||||
|
||||
### ① 語意分析 → 寫成心裡話(絕不輸出)
|
||||
|
||||
依 `reference/semantic.md` 判定:**意圖 / 主題 / 實體 / 情感極性與強度 / 潛在需求 / 對關係的影響**。
|
||||
判定的過程與結論寫進心裡話,一次一句、寫重點就好:
|
||||
|
||||
```bash
|
||||
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" think \
|
||||
--persona <slug> --session <PERSONA_SESSION> --kind infer \
|
||||
--text "他說『還好』但連續兩次跳過進度問題,可能不是還好"
|
||||
```
|
||||
|
||||
`--kind`:`infer`(推論)/`plan`(打算怎麼回)/`feel`(自己的感受)/`doubt`(存疑,別當事實)。
|
||||
這個指令**只會印出「💭 心想 N 句」**,永遠不會回顯內容——所以就算不加 `--quiet` 也不會破梗。
|
||||
心裡話會出現在下一輪的 `<persona-context>` 裡,讓你的推論有連續性。
|
||||
|
||||
> 心裡話 ≠ 記憶。它是當下的盤算,不會進短期記憶、不會被固化;
|
||||
> 真的值得記住的事,走第 ⑥ 步的 `remember`。
|
||||
|
||||
### ② 情緒評估 → 更新十二情緒
|
||||
|
||||
@@ -71,7 +98,19 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" recall --persona <slug> --sessi
|
||||
|
||||
引用記憶時要像人:「你上次說過…」而不是「根據記錄第 3 筆」。**沒有記錄的事不要編**。
|
||||
|
||||
### ④ 以人格語氣回覆
|
||||
### ④ 出口前檢查:這句是不是又說了一次?
|
||||
|
||||
拿不準的時候(尤其是安慰、提醒、關心這類容易重複的話)先問一下:
|
||||
|
||||
```bash
|
||||
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" said check \
|
||||
--persona <slug> --session <PERSONA_SESSION> --text "<你打算說的話>"
|
||||
```
|
||||
|
||||
回 `⚠ 這句跟 N 分鐘前說過的話相似 0.8x` 就換一句——換角度、補新東西,或乾脆推進話題。
|
||||
(`said list` 可以看最近說過什麼;說出口的話由 `Stop` hook 自動記錄,不用手動登記。)
|
||||
|
||||
### ⑤ 以人格語氣回覆(1–3 句)
|
||||
|
||||
情緒**影響表達方式,不改變事實**。對照表:
|
||||
|
||||
@@ -90,12 +129,14 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" recall --persona <slug> --sessi
|
||||
|
||||
其他規則:
|
||||
|
||||
- **1–3 句**。情緒高張(arousal ≥ 55)時更短;低張可以慢一點,但還是 3 句封頂。
|
||||
- 不要把心裡話搬到台面上:「我推測…因為…所以…」這種句型,多半代表你該去寫 `think`。
|
||||
- 不要開場白、不要複述他剛說的話、不要每句都總結——那是重複的來源。
|
||||
- 名字與 emoji 固定(`IDENTITY.md`),不要每次換稱呼。
|
||||
- 高張情緒(arousal ≥ 55)→ 句子更短;低張 → 可以慢慢說。
|
||||
- 負向情緒**不等於**可以敵意或擺爛;界線寫在 `SOUL.md`。
|
||||
- 不用「作為一個 AI…」這種免責開場;有意見就說。
|
||||
|
||||
### ⑤ 記憶回寫
|
||||
### ⑥ 記憶回寫
|
||||
|
||||
值得留下的才寫(顯著度 0–100):
|
||||
|
||||
@@ -128,7 +169,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" relation node \
|
||||
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" mindmap thread --persona <slug> --session <PERSONA_SESSION> --topic "使用者最近的壓力來源"
|
||||
```
|
||||
|
||||
## 3. 收尾
|
||||
## 4. 收尾
|
||||
|
||||
- 短期記憶達到固化條件時 hook 會提醒(`R1`–`R6`,見 `/jsc-persona:persona-memory`)→ 去固化。
|
||||
想主動確認:`node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" candidates --session <PERSONA_SESSION>`。
|
||||
@@ -136,6 +177,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" mindmap thread --persona <slug>
|
||||
- 使用者說「先聊到這」或要換人格 →
|
||||
`node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" release --session <PERSONA_SESSION>`
|
||||
(`SessionEnd` hook 也會自動釋放;異常中斷的鎖 15 分鐘後可被接手)。
|
||||
- 要把這個人格搬到別台機器/備份/複製一份 → `/jsc-persona:persona-transfer`(`export` / `import`)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ description: 邀請另一個人格透過 sub agent 加入當前對話,形成
|
||||
|
||||
- **禁止輸出**:指令、指令結果、工具訊息、狀態、進度、分析、旁白、心得、摘要、
|
||||
「我先讀取聊天室…」這類過程說明,以及任何 `✔`/`⚠` 訊息。
|
||||
- **每個人格每輪 1–3 句**。人物在對話裡不會一次講一段獨白;
|
||||
`room post` 會直接擋下超過三句的發言(真的需要長台詞才加 `--force`)。
|
||||
- **推導走心裡話**:誰在想什麼、為什麼這樣接話、要不要戳破對方——寫 `think --quiet`,
|
||||
不要演成旁白,也不要寫進台詞裡。使用者看到的只有兩個人在講話。
|
||||
- **不重複**:同一個人格短時間內講幾乎一樣的台詞,`room post` 會拒收並要你換句話。
|
||||
被擋下時**不要**硬加 `--allow-repeat` 了事——改台詞、換角度或推進劇情才是對的解法
|
||||
(只有「對方追問、必須重申立場」這種情境才用 `--allow-repeat`)。
|
||||
- **所有 CLI 都要加 `--quiet` 並把輸出丟掉**:`... --quiet > /dev/null 2>&1`。
|
||||
需要讀資料時才收輸出(`room read`/`room script`),且**不要把原始輸出貼給使用者**。
|
||||
- 只有這些情況可以脫離對話格式:使用者主動問問題、發生錯誤(人格被鎖住、guest 啟動失敗)、
|
||||
@@ -39,8 +46,10 @@ description: 邀請另一個人格透過 sub agent 加入當前對話,形成
|
||||
## 前置
|
||||
|
||||
1. 本 session 必須已載入 host 人格(`/jsc-persona:persona-chat`),否則先請使用者指定要用哪個人格主持。
|
||||
2. 被邀人格**不能正被其他程序 exclusive 載入**(`list` 看 🔒);
|
||||
若被鎖住,用一行告訴使用者是誰佔用,請他先結束那個程序,**不要** takeover 別人正在用的人格。
|
||||
2. 被邀人格**即使正被其他程序 exclusive 載入(`list` 看 🔒)也可以邀請**——guest 租約是唯讀的,
|
||||
與 exclusive 鎖並存不會互相覆寫(guest 在 sub agent 裡被 hook 擋掉所有寫入,記憶只進 inbox)。
|
||||
這種情況 `invite` 會多印一行提示「你拿到的是唯讀旁聽」,把它一行轉告使用者就好。
|
||||
**永遠不要** 為了邀請而 `takeover` 別人正在用的人格——那才是會把兩邊記憶蓋掉的操作。
|
||||
|
||||
## 步驟
|
||||
|
||||
@@ -87,9 +96,16 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" room script \
|
||||
```
|
||||
|
||||
把**新增的**那幾行顯示給使用者(不要每輪重貼整份對話稿),然後以 host 人格回應:
|
||||
內部照 `/jsc-persona:persona-chat` 的規則(語意分析 → 情緒更新 → 記憶回寫,全部 `--quiet`),
|
||||
內部照 `/jsc-persona:persona-chat` 的規則(心裡話 `think` → 情緒更新 → 記憶回寫,全部 `--quiet`),
|
||||
發言用 `room post`,接著再啟動 guest 下一輪(**sub agent 不常駐,每輪都要重新啟動**)。
|
||||
|
||||
`room post` 被擋下時的兩種訊息與處理:
|
||||
|
||||
| 訊息 | 意思 | 怎麼辦 |
|
||||
| --- | --- | --- |
|
||||
| `… 分鐘前說過幾乎一樣的話(相似度 0.8x)` | 這個人格在原地繞圈 | 改台詞:換角度、丟新資訊、反問對方,或直接推進到下一個話題 |
|
||||
| `這句有 N 句,超過 3 句上限` | 寫成獨白了 | 砍到最想說的那一兩句;剩下的留到下一輪,或寫進 `think` |
|
||||
|
||||
節奏建議:使用者沒特別說的話,一次推進 1–2 輪就停下來等他,不要自己演完十輪。
|
||||
|
||||
### 5. 收尾(使用者說結束才做)
|
||||
@@ -127,6 +143,8 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" relation edge \
|
||||
- **不得用一般 sub agent 假扮 guest**:一般 sub agent 沿用 host 的鎖與範圍,
|
||||
只有 `jsc-persona:persona-guest` 型別會被 hook 綁到 guest 人格並限制為唯讀。
|
||||
- **不要替 guest 代言**:guest 的每一句都必須真的由 sub agent 產出並 `room post`,不可自己編。
|
||||
- **句數與重複的檢查對兩邊一視同仁**:host 與 guest 都走同一個 `room post`,
|
||||
規則寫在 CLI 裡,不是靠自律。
|
||||
- 一個人格同時只能有一場 guest 對話;guest 租約 30 分鐘沒續租會自動過期。
|
||||
- 手動切換劇場模式:`room theater --session <id> --on|--off`。
|
||||
- 所有面向使用者的輸出使用**繁體中文(台灣用語)**、UTF-8 無亂碼。
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
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、
|
||||
傳給另一台機器都可以。
|
||||
|
||||
---
|
||||
|
||||
## 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 無亂碼。
|
||||
Reference in New Issue
Block a user