Files
persona/hooks/turn_end.mjs
T
jiantw83andClaude Opus 5 52a73e81c8 feat: 心裡話與不重複發言、劇場模式同規則、人格匯出匯入
讓人格講話更像正常人,並讓人格可以搬家。

1. 心裡話(inner voice)
   - 新增 `think` 子指令:推導/盤算寫進 state/inner.jsonl,
     只印「💭 心想 N 句」,內容永不回顯給使用者。
   - <persona-context> 每輪帶回最近三句,推論因此有連續性。

2. 短時間內不重複
   - 說出口的話由 Stop hook 自動記進 state/said.jsonl。
   - 新增 `said check|list`:事前確認這句是不是又要說一次。
   - 相似度 = 字元 bigram Jaccard(0.4) + 字集合 Jaccard(0.6),
     門檻 0.72;字集合權重較高才分得開「重排語序」(要擋)
     與「換掉關鍵詞」(是新資訊,放行)。

3. 回話一到三句 + 劇場模式同樣適用
   - 規則注入 turnContext 與 UserPromptSubmit hint。
   - 劇場模式的 CLI 都帶 --quiet,警告會被丟掉,所以 `room post`
     對近似重複與超過三句的發言直接拒收(--allow-repeat / --force 例外),
     host 與 guest 走同一支 CLI,一視同仁。

4. 人格匯出匯入(新 skill persona-transfer)
   - `export` / `import`:單一 JSON bundle(可 gzip)+ sha256 checksum,
     無外部依賴。不帶載入鎖與 guest 租約,journal/ 需明確 --with-journal。
   - 匯出只能匯出本 session 已載入的人格,否則等於跨人格讀取的後門;
     bundle 內的 ../ 逃逸路徑一律拒收。

順帶修正:agents/persona-guest.md 的 CLI 範例都少了 --as-guest,
照著跑會被 requireMember 擋下,guest 根本無法認識自己或發言。

selftest 102 項全綠(新增 ⑪ 說話節制、⑫ 匯出匯入,含重複判定校準對照表)。

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

48 lines
1.8 KiB
JavaScript
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.
#!/usr/bin/env node
// Stop:情緒隨時間衰減、續租、記錄逐字,並在達到「短期→長期」條件時提醒固化。
// 劇場模式進行中不發任何提醒(那會破壞「只顯示人格對話」)。
import { readEvent, respond } from "./_hook.mjs";
import * as pl from "../scripts/persona-lib.mjs";
const event = readEvent();
if (!event) process.exit(0);
const sessionId = event.session_id || "unknown";
const data = pl.loadSession(sessionId);
const host = data.host;
if (!host || !pl.personaExists(host)) process.exit(0);
pl.heartbeatLock(host, sessionId);
const state = pl.decayEmotion(pl.loadEmotion(host));
pl.writeJson(pl.emotionPath(host), state);
const theater = Boolean(data.theater) && (data.rooms || []).length > 0;
const message = event.last_assistant_message || "";
if (message) {
pl.appendJsonl(pl.journalPath(host), {
ts: pl.nowIso(),
kind: "utterance",
role: "persona",
text: message.slice(0, 4000),
mood: pl.mood(state),
});
// 說出口的話存進 said.jsonl,下一輪的 <persona-context> 會提醒「這些別再說一次」。
// recordSaid 內建 10 分鐘去重,所以劇場模式裡 room post 已記過的台詞不會重覆記。
for (const line of pl.spokenLines(message, { theater })) {
pl.recordSaid(host, line, { kind: theater ? "room" : "reply" });
}
}
const out = { suppressOutput: true };
if (!theater) {
const { total, candidates } = pl.promotionCandidates(host);
if (candidates.length) {
const rules = [...new Set(candidates.flatMap((c) => c.rules))].sort().join("/");
out.systemMessage =
`[jsc-persona] \`${host}\` 短期記憶 ${total} 筆,${candidates.length} 組已達固化條件(${rules}` +
"→ 建議執行 /jsc-persona:persona-memory。";
}
}
respond(out);