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:
@@ -1387,6 +1387,35 @@ check("sleeper 型別存在且宣告了唯讀工具限制", (() => {
|
||||
md.includes("回傳值裡不得出現任何記憶內容");
|
||||
})());
|
||||
|
||||
console.log("\n劇場模式:心裡話不能外流");
|
||||
{
|
||||
const S_INNER = "sess-inner-8888";
|
||||
cli(["load", "--persona", "alpha", "--session", S_INNER, "--takeover"]);
|
||||
const room3 = JSON.parse(cli(["invite", "--session", S_INNER, "--guest", "beta", "--topic", "心裡話測試", "--json"]).stdout).room;
|
||||
cli(["think", "--persona", "alpha", "--session", S_INNER, "--text", "我其實一直在等他先開口問我那件事"]);
|
||||
const leak = cli(["room", "post", "--session", S_INNER, "--room", room3, "--as", "alpha",
|
||||
"--text", "我其實一直在等你先開口問我那件事。"], { expectOk: false });
|
||||
check("台詞跟心裡話太像 → room post 擋下(心裡話不能搬上台面)",
|
||||
leak.status !== 0 && leak.stderr.includes("心裡話"), leak.stderr.slice(0, 160));
|
||||
check("--force 仍可放行(真的要講開的時候)",
|
||||
cli(["room", "post", "--session", S_INNER, "--room", room3, "--as", "alpha",
|
||||
"--text", "我其實一直在等你先開口問我那件事。", "--force"]).status === 0);
|
||||
check("換句話、講別的就過得去",
|
||||
cli(["room", "post", "--session", S_INNER, "--room", room3, "--as", "alpha",
|
||||
"--text", "外面雨停了。要不要走走?"]).status === 0);
|
||||
// 逐字稿裡只有說出口的東西,沒有任何心裡話的欄位
|
||||
const rows = pl.roomRead(room3, 20);
|
||||
check("聊天室逐字稿不含心裡話欄位",
|
||||
rows.length > 0 && rows.every((r) => !("inner" in r) && !("think" in r)), JSON.stringify(Object.keys(rows[0] || {})));
|
||||
check("別的人格讀不到你的 inner.jsonl(hook 擋)",
|
||||
guard({ session_id: S_OTHER, tool_name: "Read",
|
||||
tool_input: { file_path: `${H}/alpha/state/inner.jsonl` } }) === "deny");
|
||||
check("別的人格也不能用 grep 掃出來",
|
||||
guard({ session_id: S_OTHER, tool_name: "Grep", tool_input: { path: `${H}/alpha/state` } }) === "deny");
|
||||
cli(["leave", "--session", S_INNER, "--guest", "beta"]);
|
||||
cli(["release", "--session", S_INNER]);
|
||||
}
|
||||
|
||||
console.log(`\n${"=".repeat(60)}\n通過 ${passed} 項,失敗 ${failed} 項 → ${failed === 0 ? "全部通過 ✅" : "有測試失敗 ❌"}`);
|
||||
console.log(`(暫存倉庫留在 ${STORE},可自行刪除)`);
|
||||
process.exit(failed ? 1 : 0);
|
||||
|
||||
Reference in New Issue
Block a user