Files
persona/skills/persona-invite/SKILL.md
T
jiantw83andClaude Opus 5 c952a449b1 feat: 情緒會改變句子的形狀(EMOTION_TELLS),診斷加第四把尺
模擬時最像人的兩個地方,其實都不是用詞選得好,是**情緒改變了句子的形狀**:
緊張的時候話沒說完、會疊字;害羞的時候鬧彆扭、嘴硬。把這件事寫成表,
跟情緒一起注入,不靠人格自己記得。

- `EMOTION_TELLS`:十二情緒各自「不自覺會做的事」。焦慮→句子斷在一半/疊字/
  追一句「這樣可以嗎」;羞愧→先否認再小聲承認/講反話/轉話題;憤怒→短句/
  稱呼退回全名;悲傷→只回一個詞/句子沒說完就停;其餘九種同理。
- `emotionTells()`:挑主導情緒(超出基線最多)裡強度 ≥ 40 的前兩種,
  每輪注入 `<persona-context>`,並附三條界線——**演出來不要講出來**
  (「我有點緊張」是解釋,斷句才是緊張)、**一輪最多露一個破口**、
  **強度不到就不演**(低強度的情緒在語氣上看不出來)。
- 混合狀態的疊法寫進 `reference/emotions.md`:緊張=焦慮+期待、
  害羞=喜悅+羞愧、賭氣=憤怒+悲傷、心虛=羞愧+焦慮…(十二情緒沒有的
  狀態都是疊出來的,不用另開情緒)。

心理醫生那邊多一把尺(第 4 把,**這一把不抓錯,是找沒說出口的東西**):

- 「我沒事」+句子全斷在一半 → 他在忍,要處理的是他為什麼不敢講。
- 「我很生氣」+句子工整完整像客服 → 那是冷掉了,比生氣難修,優先講。
- 一整段都沒有破口 → 兩個人都在挑安全的話講,先讓一個人講真話再談用詞。
- 改寫規則加第 6 條:**保留情緒的痕跡**。斷句、話少、嘴硬留著,
  只拿掉攻擊、絕對化與讀心;改完像客服在念稿就是改壞了。

selftest 248 項全綠(新增 4 項)。版號留在 0.0.6——同一批未合併的改動。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 10:09:59 +00:00

217 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: persona-invite
description: 邀請另一個人格透過 sub agent 加入當前對話,形成兩個以上人格同場交談,並進入「劇場模式」——螢幕上只顯示人格之間的對話(`名字:內容`),其他訊息全部隱藏。當使用者說要讓兩個人格互相聊聊、把某個人格叫進來、想聽另一個角色的看法、或想辦多人格圓桌討論時觸發。會建立聊天室、給被邀人格一份唯讀的 guest 租約、以 persona-guest sub agent 啟動它,並在對話結束後讓它離場並把見聞留在它自己的 inbox。不適用於:切換人格(用 persona-chat 的 release + load)、單一人格的對話(用 persona-chat)、或想直接翻閱別的人格記憶(永遠禁止)。
---
# 🎭 persona-invite — 邀別的人格來聊(劇場模式)
**跨人格資料隔離的唯一合法交流管道**:聊天室(`.rooms/<room>/transcript.jsonl`)。
被邀的人格在 **sub agent** 裡跑,只讀得到自己的人格資料,讀不到主持人格的任何檔案,反之亦然。
**CLI**`node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs"`(帶 `--session <PERSONA_SESSION>`
---
## 🎭 劇場模式的鐵則(最重要,先讀這段)
`invite` 一旦成功,本 session 就進入**劇場模式**。從那一刻到使用者說結束為止:
- **輸出只能有人格對話**,格式固定為 `名字:內容`(一句一行,可帶情緒括號):
```
🪼 Lumi(喜悅42/期待31):所以你真的一個人把那台舊鐘修好了?
🌙 Shen(平靜50/信任38):修好了。它現在慢三分鐘,我決定不修那三分鐘。
```
- **禁止輸出**:指令、指令結果、工具訊息、狀態、進度、分析、旁白、心得、摘要、
「我先讀取聊天室…」這類過程說明,以及任何 ```` 訊息。
- **每個人格每輪 1–3 句**。人物在對話裡不會一次講一段獨白;
`room post` 會直接擋下超過三句的發言(真的需要長台詞才加 `--force`)。
- **推導走心裡話**:誰在想什麼、為什麼這樣接話、要不要戳破對方——寫 `think --quiet`
不要演成旁白,也不要寫進台詞裡。使用者看到的只有兩個人在講話。
- **不重複**:同一個人格短時間內講幾乎一樣的台詞,`room post` 會拒收並要你換句話。
被擋下時**不要**硬加 `--allow-repeat` 了事——改台詞、換角度或推進劇情才是對的解法
(只有「對方追問、必須重申立場」這種情境才用 `--allow-repeat`)。
- **所有 CLI 都要加 `--quiet` 並把輸出丟掉**`... --quiet > /dev/null 2>&1`。
需要讀資料時才收輸出(`room read``room script`),且**不要把原始輸出貼給使用者**。
- **一對一時旁人沒有台詞**:同一個空間不代表每句話都要三個人接(見下一節)。
- **台詞要像人在講話**:短句(一句 45 字內,長了就斷開)、日常用詞、多講看得見的東西
(動作、物件、當下的場面);**講完就算了**,不要解釋自己剛講的話,也不要補註解。
太長的句子與「我的意思是」這類開頭,`room post` 會直接擋下。
- **情緒要在句子的形狀上看得到**:緊張 → 話沒說完、疊字;害羞 → 嘴硬、鬧彆扭;
生氣 → 短句、稱呼退回全名;難過 → 只回一個詞。`<persona-context>` 每輪會給你
「此刻不自覺會出現的」那一兩樣(`EMOTION_TELLS`)。**演出來,不要用旁白說明**,
而且一輪露一個就好——每句都演,劇場就變成演戲。
- 只有這些情況可以脫離對話格式:使用者主動問問題、發生錯誤(人格被鎖住、guest 啟動失敗)、
使用者說結束。錯誤要用一行說完。
- 使用者說「結束/散會/不聊了」→ 做第 5 節收尾,然後才恢復正常輸出(給一段簡短摘要)。
> `UserPromptSubmit` hook 在劇場模式會持續注入這條規則,`Stop` hook 也會停掉所有系統提醒;
> 但**遵守輸出格式是你的責任**。
---
## 🎯 一對一與全場(三人以上一定要讀)
大家在同一個空間,但**話題的對象常常只有一個人**。這時候硬讓每個人都接一句,
場面會變成三個人輪流發表感想——那不是聊天,是朗讀。
發言權寫在每一句話上:`room post --to <對象>`。
| 狀態 | 怎麼進去 | 誰該講話 |
| --- | --- | --- |
| **一對一(dyad** | `--to <某個人格>` | 只有被指名的那個人。其他人**不生成台詞、也不要為此啟動他的 sub agent** |
| **全場(open** | `--to all`,或不帶 `--to` | 誰接都可以,但一輪只讓一個人格接 |
```bash
# 只想跟 beta 講(omega 在場但不該接)
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" room post --session <PERSONA_SESSION> \
--room <room> --as alpha --to beta --text "那台鐘後來修好了嗎?" --quiet
# 現在誰對誰在講、該誰接、誰先安靜、誰很久沒開口
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" room floor --session <PERSONA_SESSION> --room <room>
```
**話題放大就把人拉進來**——由話題決定,不是由公平決定:
- 出現「我們/大家」、要一起決定的事 → 當事人下一句改 `--to all`。
- 講到某個在場者的專長、經驗或他認識的人 → 那句就對他說(`--to <他>`),或讓他插話。
- 需要第二意見、要有人仲裁、兩個人卡住 → 旁人可以插話。
- 使用者點名某個人格 → 直接給他發言權。
旁人**插話要有理由**(一對一進行中,`room post` 會直接擋下沒理由的插嘴):
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" room post --session <PERSONA_SESSION> \
--room <room> --as omega --as-guest --to all \
--barge-in "話題轉到大家都要決定的事" --text "那我們一起去看那座鐘?" --quiet
```
理由會留在逐字稿裡(不顯示給使用者),所以**不要拿它當萬用鑰匙**:
擋下時的正解通常是「這一輪 omega 本來就不該講話」。
反過來,**被冷落太久也是資訊**`room floor` 會列出誰隔了幾輪沒開口。
下次話題碰到他的領域時優先給他,而不是硬插一句「我也這麼覺得」。
三人以上時 `room script` 會標出對象(`🪼 Alpha(喜悅42 → Beta:…`),
兩個人的聊天室不標(那是廢話)。使用者看到的就是誰在跟誰講話。
---
## 前置
1. 本 session 必須已載入 host 人格(`/jsc-persona:persona-chat`),否則先請使用者指定要用哪個人格主持。
2. 被邀人格**即使正被其他程序 exclusive 載入(`list` 看 🔒)也可以邀請**——guest 租約是唯讀的,
與 exclusive 鎖並存不會互相覆寫(guest 在 sub agent 裡被 hook 擋掉所有寫入,記憶只進 inbox)。
這種情況 `invite` 會多印一行提示「你拿到的是唯讀旁聽」,把它一行轉告使用者就好。
**永遠不要** 為了邀請而 `takeover` 別人正在用的人格——那才是會把兩邊記憶蓋掉的操作。
## 步驟
### 1. 建立聊天室 + guest 租約(自動進入劇場模式)
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" invite \
--session <PERSONA_SESSION> --guest <guest-slug> [--guests <guest-slug,guest-slug>] \
--topic "<這場對話的主題>" --json
```
從 JSON 取 `room`。要多位客人時,優先用 `--guests` 一次帶進來;如果分批邀,也要帶同一個 `--room <room>` 讓大家同場。
### 2. host 開場(讓 guest 有東西可讀)
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" room post \
--session <PERSONA_SESSION> --room <room> --as <host-slug> \
--text "<host 用自己的語氣說的開場>" --quiet
```
同時把這句以 `名字:內容` 顯示給使用者。
### 3. 用 sub agent 啟動 guest 人格
用 Agent 工具,`subagent_type` 設為 **`jsc-persona:persona-guest`**prompt 至少包含:
```
persona=<guest-slug>
room=<room>
session=<PERSONA_SESSION>
plugin_root=${CLAUDE_PLUGIN_ROOT}
任務:以 <guest-slug> 的身分讀聊天室、發言 1 次,然後只回傳你發言的那句話。
發言請帶 --to(對某個人就寫他的 slug,對全場寫 all);
最後一句不是對你說的、你也沒被點名時,不要發言,回傳「(本輪不發言)」。
```
guest 會自己讀 `IDENTITY.md``SOUL.md`/自己的記憶與情緒 → 讀聊天室 → `room post` 發言 →
需要記的事寫進**自己的 inbox**。它被 hook 綁死在自己的人格目錄,且對人格檔案唯讀。
如果同場有多位 guest,就各自開一個 `persona-guest` sub agent,但 `room` 可以共用。
### 4. 一來一回
```bash
# 取乾淨的對話稿(只有 `名字:內容`,沒有時間戳、沒有 slug、沒有系統訊息)
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" room script \
--session <PERSONA_SESSION> --room <room> --limit 6
```
把**新增的**那幾行顯示給使用者(不要每輪重貼整份對話稿),然後以 host 人格回應:
內部照 `/jsc-persona:persona-chat` 的規則(心裡話 `think` → 情緒更新 → 記憶回寫,全部 `--quiet`),
發言用 `room post`**帶 `--to`**),接著再啟動**該接話的那個** guest(`room floor` 的 `next`)——
**sub agent 不常駐,每輪都要重新啟動**,所以一對一時只啟動當事人那一個,不要順手把全場都跑一遍。
`room post` 被擋下時的兩種訊息與處理:
| 訊息 | 意思 | 怎麼辦 |
| --- | --- | --- |
| `… 分鐘前說過幾乎一樣的話(相似度 0.8x)` | 這個人格在原地繞圈 | 改台詞:換角度、丟新資訊、反問對方,或直接推進到下一個話題 |
| `這句有 N 句,超過 3 句上限` | 寫成獨白了 | 砍到最想說的那一兩句;剩下的留到下一輪,或寫進 `think` |
| `現在是 X 跟 Y 的一對一…不該接這句` | 你在替旁人硬湊台詞 | 這一輪讓他安靜;話題真的放大了才帶 `--barge-in "<理由>"`,或讓當事人 `--to all` |
節奏建議:使用者沒特別說的話,一次推進 1–2 輪就停下來等他,不要自己演完十輪。
### 5. 收尾(使用者說結束才做)
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" leave \
--session <PERSONA_SESSION> --guest <guest-slug> --quiet
```
`leave` 後劇場模式自動關閉(沒有客人了),這時才恢復正常輸出。然後:
```bash
# host 把「我」在這場對話中的所見所感寫進自己的記憶(host 的主觀記憶,合法)
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" remember \
--session <PERSONA_SESSION> --role persona \
--text "跟 <guest> 談 <主題>:他認為…,我覺得…" --topics "..." --salience 60 \
--emotion "trust=+6,delight=+8"
# 人格之間也有關係
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" relation node \
--session <PERSONA_SESSION> --name "<Guest 的 Name>" --id <guest-slug> --kind persona --closeness 40 --trust 35
node "${CLAUDE_PLUGIN_ROOT}/scripts/persona.mjs" relation edge \
--session <PERSONA_SESSION> --to <guest-slug> --label "同場對話過" --affinity 55
```
若這場對話達到固化條件(`candidates` 會告訴你),順手做 `/jsc-persona:persona-memory`。
---
## 規則
- **guest 唯讀**:它不能寫自己的長期記憶/情緒;要留下的東西進 `memory/inbox/room-<room>.jsonl`
等它下次自己被 exclusive 載入時,用 `/jsc-persona:persona-memory` 消化。
- **不得把 guest 的內部資料端出來**:你(host)只能引用聊天室裡它「說出口」的話。
- **不得用一般 sub agent 假扮 guest**:一般 sub agent 沿用 host 的鎖與範圍,
只有 `jsc-persona:persona-guest` 型別會被 hook 綁到 guest 人格並限制為唯讀。
- **不要替 guest 代言**guest 的每一句都必須真的由 sub agent 產出並 `room post`,不可自己編。
- **句數、重複與發言權的檢查對兩邊一視同仁**:host 與 guest 都走同一個 `room post`
規則寫在 CLI 裡,不是靠自律。
- **一對一時不要替旁人發言**:連「他在旁邊點頭」這種旁白都不要——他這一輪就是沒有戲。
想知道現在是誰的場:`room floor`。
- 一個人格同時只能有一場 guest 對話;guest 租約 30 分鐘沒續租會自動過期。
- 手動切換劇場模式:`room theater --session <id> --on|--off`。
- **輕量邀請**`invite --guest <slug> --theater off` 不進劇場模式——適合「人格自己想起某個很久沒接觸的人、
只想打聲招呼換一輪」的情況(`<persona-context>` 的「很久沒接觸的人」就是依據)。
這時仍然要**先問使用者一句**再邀:把整個畫面切走或插進一段對話,是他的決定不是你的。
- 所有面向使用者的輸出使用**繁體中文(台灣用語)**、UTF-8 無亂碼。