feat(theater): 台詞不可以是心裡話搬上台面(機械擋下)(v0.0.8 同批)

心裡話外流有兩種路徑。第一種是別的人格去讀你的檔案——那個 hook 早就擋死了
(本次補上測試:跨 session Read `state/inner.jsonl`、Grep `state/` 都是 deny,
聊天室逐字稿也只有 ts/speaker/kind/text/emotion/to,沒有任何心裡話欄位)。

第二種比較難防:**你自己把心裡想的原封不動講出來**。劇場模式尤其危險,
因為別的人格看得到的就只有你說出口的東西——那句一旦是心裡話的複述,
等於自己把它交出去了。「台詞不可以是心裡話的摘要」原本只寫在規則裡靠自律,
現在變成機械檢查:`innerLeak()` 拿這句台詞去比最近 12 則心裡話,
相似度 ≥ 0.62 就擋(`--force` 可以放行,真的決定要講開的時候)。

用的是既有的 `similarity()`(字元 bigram + 字集合),跟擋重複發言同一把尺。

測試:334 項全過(新增 6 項)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 06:23:19 +00:00
co-authored by Claude Opus 5
parent ab4ddb734d
commit 646fd33541
3 changed files with 68 additions and 0 deletions
+12
View File
@@ -1361,6 +1361,18 @@ commands.room = ({ flags, positional }) => {
"挑最想說的那一兩句就好,剩下的寫進心裡話。(真的需要長台詞才加 `--force`。)",
);
}
// 台詞不可以是心裡話搬上台面:劇場模式裡別的人格只看得到你說出口的東西,
// 心裡話外流最可能的路徑就是你自己把它講出來。
if (speakerRole !== "guest" && pl.personaExists(speaker)) {
const leak = pl.innerLeak(speaker, text);
if (leak && !flags.force) {
die(
`這句跟你剛才的心裡話太像了(${leak.similarity}):「${leak.text.slice(0, 40)}…」。` +
"心裡話是只有你自己知道的東西——說出口的那句應該是你**決定要讓他聽到**的版本," +
"不是把心裡那句唸出來。(真的要講開才加 `--force`。)",
);
}
}
// 短句、日常話、講完不解釋、不說 AI 才會說的話——講話的樣子也擋在 CLI 裡,不靠自律。
const lint = pl.speechBlockers(text);
if (lint.length && !flags.force) {