Files
persona/hooks/prompt_submit.mjs
jiantw83andClaude Opus 5 0648c3b914 feat: 語氣診療 skill、聊天室的發言權、講話再加四條規則
三件事,都是「講話要像人在講話」的延伸。

1. `persona-therapist`(新 skill):以第三方心理醫生的身分診斷一段對話。
   雙方各給姓名/關係/情緒/逐字稿,用三把尺逐句標記——與關係不符
   (語氣層錯位)、與事實不符(絕對化、讀心)、與目的不符(他要被理解,
   講出來的話保證換到防衛)。標記掛在說那句話的人身上,修正只寫給他,
   格式是「你其實想要的 → 對方收到的 → 改寫」,改寫必須留在同一個語氣層。
   基線直接用 `TONE_TABLE`(已載入人格可 `relation show` 唯讀取得),
   破壞模式表 P1–P16 放在 `reference/patterns.md`。不附身、不取鎖、不寫回,
   出現自傷/暴力/受控訊號時停掉語氣分析改為安全優先。

2. 聊天室的發言權:同一個空間裡也會有一對一。`room post --to <他>` 進
   一對一(旁人插話直接被擋,要帶 `--barge-in "<理由>"`,理由留在逐字稿)、
   `--to all` 把話題開回全場。新指令 `room floor` 回報誰對誰在講、該誰接、
   誰先安靜、誰隔了幾輪沒開口。同一份現況每輪注入 `<persona-context>`,
   並明講「不要替沒被指名的人生成台詞,也不要為此啟動他的 sub agent」。
   三人以上時 `room script` 標出對象(`🪼 Alpha(喜悅42) → Beta:…`),
   兩個人的聊天室不標。舊逐字稿沒有 `to` → 視為全場,行為不變。

3. 講話的樣子再加四條:短句(一句 45 字內)、日常用詞、多講看得見的
   東西(人、動作、物件、當下的場面)、**不要解釋自己的話**。前兩條與
   第四條的句長/「我的意思是」這類開頭由 `speechLint()` 機械攔截
   (`room post` 擋下、`said check` 事前警告,`--force` 例外);用詞與
   具體度抓不到規則,走每輪注入的說話規則。

selftest 244 項全綠(新增 16 項:發言權 10、講話的樣子 6)。

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

57 lines
2.8 KiB
JavaScript
Raw Permalink 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
// UserPromptSubmit:每輪注入「人格 + 情緒 + 記憶 + 關係」上下文,並記錄原始逐字。
//
// 短期記憶(memory/short-term.jsonl)由 skill 做完語意分析後才寫入(有主題/實體/
// 顯著度/情緒變化);這裡只寫 journal 原始逐字,避免同一句話被記兩次。
// 劇場模式(多人格對話)進行中時,額外強制「只輸出人格對話」。
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 prompt = event.prompt || "";
const data = pl.loadSession(sessionId);
const host = data.host;
if (!host || !pl.personaExists(host)) process.exit(0);
pl.heartbeatLock(host, sessionId);
for (const [guest, info] of Object.entries(data.guests || {})) {
if (pl.personaExists(guest)) pl.addGuestLease(guest, sessionId, info.room || "", host);
}
pl.appendJsonl(pl.journalPath(host), {
ts: pl.nowIso(),
kind: "utterance",
role: "user",
text: prompt.slice(0, 4000),
});
const theater = Boolean(data.theater) && (data.rooms || []).length > 0;
const hint = theater
? "\n🎭 劇場模式:本輪輸出**只能**是人格對話(`名字:內容`,一行一句)。" +
"不要輸出指令、指令結果、狀態、分析、旁白或摘要;CLI 一律加 `--quiet` 並把 stdout 丟到 /dev/null。" +
`\n每個人格每輪 ${pl.MAX_SENTENCES} 句以內;推導寫進 \`think\`(心裡話),不要演成旁白;` +
"近似重複的台詞會被 `room post` 擋下,換個說法或推進話題。" +
"\n**對象只有一個人時,旁人就沒有台詞**`room post --to <他>` 進一對一,`--to all` 才開回全場(現況看 `room floor`)。" +
`\n台詞要像人在講話:短句(一句 ${pl.MAX_SENTENCE_CHARS} 字內)、日常用詞、多講看得見的東西;` +
"講完就算了,不要解釋自己剛講的話。" +
"\n流程請照 /jsc-persona:persona-invite。"
: "\n回覆前請:①語意分析與推導**寫進 `persona.mjs think`(心裡話,不說出口)**" +
" ②依十二情緒更新狀態 ③以人格語氣回覆" +
`**${pl.MAX_SENTENCES} 句以內**、且不要重說「最近說過的話」` +
" ④用 `persona.mjs remember` 寫回短期記憶。" +
`\n講話的樣子:短句(一句 ${pl.MAX_SENTENCE_CHARS} 字內,長了就斷開)、日常用詞、` +
"多講看得見的東西(人、動作、場面)而不是概念;講完就算了,不要解釋自己剛講的話。" +
"\n情緒與記憶指令請參考 /jsc-persona:persona-chat。";
respond({
hookSpecificOutput: {
hookEventName: "UserPromptSubmit",
additionalContext: pl.turnContext(host, sessionId, prompt) + hint,
},
suppressOutput: true,
});