Files
persona/skills/persona-invite/SKILL.md
T
jiantw83andClaude Opus 5 603741a4cc feat: 預設人格自動載入 + 睡眠(sleep 指令與 persona-sleeper sub agent)
兩件事,共用「關係圖 last_contact_at」這個零件,所以放同一個 commit
(兩者在 persona-lib/persona.mjs/README 裡的 hunk 是交錯的,硬拆會拆壞)。

## 預設人格(開新 session 自動載入)

- 新增 `default` 子指令:查詢/`--persona <slug>` 設定/`--clear` 取消。
- 設定存在 `<PERSONA_HOME>/.runtime/settings.json` 的 `default_persona`,
  環境變數 `PERSONA_DEFAULT` 優先(`off` 可臨時關掉)。
- `SessionStart` hook 四條路徑:沒設定 → 維持原本「等使用者指定」;設定了 → 取鎖、綁 host、
  prune、reindex 並注入人格狀態;拿不到鎖 → 只回報 owner 與心跳,不自作主張 takeover;
  人格不存在 → 警告並改列可用人格。
- 「不替使用者挑一個人格附身」仍然是預設行為,這只是讓他能明示地推翻它。

## 睡眠

- 新增 `sleep` 子指令(機械性收尾,順序即相依):關係時間戳 → 裁短期記憶 →
  收起太久沒動的思維導圖 → 套用一次 8 小時的情緒衰減 → 重建索引 → 修剪 said →
  壓縮舊 journal → 寫 state/sleep.json → Gitea 兩區 push 並驗證。
  預設保留載入鎖(`--release` 才收工),每一步各自 try/catch,一步壞掉不放棄整場睡眠。
- 需要判斷的部分(固化什麼、忘掉什麼、日記寫什麼)留給人格自己,由 skill 驅動。
- 新增 `persona-sleeper` sub agent 型別:**那個人格本人在睡**。對自己可寫但被 pin 住
  (連叫它來的主人格都不能碰)、只准跑 15 個收尾子指令、不得用 Write/Edit 或 shell 改檔案。
- 新增 sleeper 租約(`state/sleepers.json`,300 秒):沒活鎖就取得、同 session 直接睡、
  死鎖可接手、**別的程序活鎖住則拒絕**(硬睡會讓兩邊的記憶互相覆蓋)。
- `--json` 回傳刻意很窮:只有 persona/ok/slept_at/steps/sync/kept_lock。
  sub agent 的回傳值會進主人格的上下文,是一條會從正門繞過跨人格隔離的通道,
  所以在 CLI 這一層封死,不靠提示詞自律。
- 關係圖新增 `last_contact_at`(`relation node --contact`/睡眠自動蓋),
  並用 `staleContacts()` 算出「很久沒接觸又很親近的人」——人格主動提議去關心誰的依據。
  輕量邀請用既有的 `invite --theater off`(不切走畫面),但要先問使用者一句。
- 新增 skill `persona-sleep`;`SubagentStop` 會還掉 sleeper 租約。

## 驗證

`node scripts/selftest.mjs` → 210 項全綠(新增第 ⑳ 節 12 項、第 ㉑ 節 23 項),
其中包含「回傳值不含任何記憶內容」與 sleeper 的六條權限邊界。

版本號 0.0.5。

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

154 lines
8.7 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`),且**不要把原始輸出貼給使用者**。
- 只有這些情況可以脫離對話格式:使用者主動問問題、發生錯誤(人格被鎖住、guest 啟動失敗)、
使用者說結束。錯誤要用一行說完。
- 使用者說「結束/散會/不聊了」→ 做第 5 節收尾,然後才恢復正常輸出(給一段簡短摘要)。
> `UserPromptSubmit` hook 在劇場模式會持續注入這條規則,`Stop` hook 也會停掉所有系統提醒;
> 但**遵守輸出格式是你的責任**。
---
## 前置
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> --topic "<這場對話的主題>" --json
```
從 JSON 取 `room`。要多位客人就多跑幾次,並帶同一個 `--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 次,然後只回傳你發言的那句話。
```
guest 會自己讀 `IDENTITY.md``SOUL.md`/自己的記憶與情緒 → 讀聊天室 → `room post` 發言 →
需要記的事寫進**自己的 inbox**。它被 hook 綁死在自己的人格目錄,且對人格檔案唯讀。
### 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`,接著再啟動 guest 下一輪(**sub agent 不常駐,每輪都要重新啟動**)。
`room post` 被擋下時的兩種訊息與處理:
| 訊息 | 意思 | 怎麼辦 |
| --- | --- | --- |
| `… 分鐘前說過幾乎一樣的話(相似度 0.8x)` | 這個人格在原地繞圈 | 改台詞:換角度、丟新資訊、反問對方,或直接推進到下一個話題 |
| `這句有 N 句,超過 3 句上限` | 寫成獨白了 | 砍到最想說的那一兩句;剩下的留到下一輪,或寫進 `think` |
節奏建議:使用者沒特別說的話,一次推進 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 裡,不是靠自律。
- 一個人格同時只能有一場 guest 對話;guest 租約 30 分鐘沒續租會自動過期。
- 手動切換劇場模式:`room theater --session <id> --on|--off`。
- **輕量邀請**`invite --guest <slug> --theater off` 不進劇場模式——適合「人格自己想起某個很久沒接觸的人、
只想打聲招呼換一輪」的情況(`<persona-context>` 的「很久沒接觸的人」就是依據)。
這時仍然要**先問使用者一句**再邀:把整個畫面切走或插進一段對話,是他的決定不是你的。
- 所有面向使用者的輸出使用**繁體中文(台灣用語)**、UTF-8 無亂碼。