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:
@@ -1967,6 +1967,33 @@ export function roomRepeat(room, speaker, text, opts = {}) {
|
||||
return findRepeat(rows, text, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 這句台詞是不是把心裡話搬上台面了。
|
||||
*
|
||||
* 心裡話外流有兩種:一種是別的人格去讀你的檔案(那個 hook 早就擋死了),
|
||||
* 另一種比較難防——**你自己把心裡想的原封不動講出來**。劇場模式尤其危險,
|
||||
* 因為那裡只有台詞,別的人格看得到的就只有你說出口的東西。
|
||||
*
|
||||
* 「台詞不可以是心裡話的摘要」原本只是寫在規則裡靠自律,這裡把它變成機械檢查:
|
||||
* 跟最近的心裡話太像就擋下來。害羞的人格更要擋——他們心裡那句才是真的。
|
||||
*/
|
||||
export const INNER_LEAK_THRESHOLD = 0.62;
|
||||
export const INNER_LEAK_WINDOW = 12;
|
||||
|
||||
export function innerLeak(slug, text, { threshold = INNER_LEAK_THRESHOLD } = {}) {
|
||||
const norm = normalizeSpeech(text);
|
||||
if (norm.length < REPEAT_MIN_CHARS) return null;
|
||||
let best = null;
|
||||
for (const row of readJsonl(innerPath(slug), INNER_LEAK_WINDOW)) {
|
||||
const score = similarity(text, row.text || "");
|
||||
if (score < threshold) continue;
|
||||
if (!best || score > best.similarity) {
|
||||
best = { similarity: score, at: row.ts, kind: row.kind || "infer", text: String(row.text || "").slice(0, 120) };
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/** 心裡話:只進自己的 inner.jsonl,永遠不回顯給使用者。 */
|
||||
export function recordInner(slug, text, { kind = "infer", room = null } = {}) {
|
||||
const entry = { ts: nowIso(), kind, room, text: String(text ?? "").slice(0, 1200) };
|
||||
|
||||
Reference in New Issue
Block a user