feat(記憶與目標): 專案目標三條 + 固化候選不再重算(0.2.1) #17
@@ -31,7 +31,8 @@ pl.appendJsonl(pl.journalPath(host), {
|
|||||||
|
|
||||||
const theater = Boolean(data.theater) && (data.rooms || []).length > 0;
|
const theater = Boolean(data.theater) && (data.rooms || []).length > 0;
|
||||||
const hint = theater
|
const hint = theater
|
||||||
? "\n🎭 劇場模式:本輪輸出**只能**是人格對話(`名字:內容`,一行一句)。" +
|
? "\n🎭 劇場模式:本輪輸出**只能**是人格對話(`名字(情緒・動作):內容`,一行一句;" +
|
||||||
|
"括號兩格都可省略,動作格只寫看得見的身體動作、12 字內,且算在一輪兩個動作的額度裡)。" +
|
||||||
"不要輸出指令、指令結果、狀態、分析、旁白或摘要;CLI 一律加 `--quiet` 並把 stdout 丟到 /dev/null。" +
|
"不要輸出指令、指令結果、狀態、分析、旁白或摘要;CLI 一律加 `--quiet` 並把 stdout 丟到 /dev/null。" +
|
||||||
`\n每個人格每輪 ${pl.MAX_SENTENCES} 句以內;推導寫進 \`think\`(心裡話),不要演成旁白;` +
|
`\n每個人格每輪 ${pl.MAX_SENTENCES} 句以內;推導寫進 \`think\`(心裡話),不要演成旁白;` +
|
||||||
"近似重複的台詞會被 `room post` 擋下,換個說法或推進話題。" +
|
"近似重複的台詞會被 `room post` 擋下,換個說法或推進話題。" +
|
||||||
@@ -45,6 +46,9 @@ const hint = theater
|
|||||||
" ④用 `persona.mjs remember` 寫回短期記憶。" +
|
" ④用 `persona.mjs remember` 寫回短期記憶。" +
|
||||||
`\n講話的樣子:短句(一句 ${pl.MAX_SENTENCE_CHARS} 字內,長了就斷開)、日常用詞、` +
|
`\n講話的樣子:短句(一句 ${pl.MAX_SENTENCE_CHARS} 字內,長了就斷開)、日常用詞、` +
|
||||||
"多講看得見的東西(人、動作、場面)而不是概念;講完就算了,不要解釋自己剛講的話。" +
|
"多講看得見的東西(人、動作、場面)而不是概念;講完就算了,不要解釋自己剛講的話。" +
|
||||||
|
"\n動作寫在**名字後面的括號**裡(`名字(情緒・動作):內容`),一對一也適用——" +
|
||||||
|
"括號兩格都可省略,動作格只寫看得見的身體動作(別過頭、臉紅、手在抖)、12 字內," +
|
||||||
|
"且算在一輪兩個動作的額度裡。`*別過頭*` 與句子裡的「我愣了一下」仍然不行。" +
|
||||||
"\n情緒與記憶指令請參考 /jsc-persona:persona-chat。";
|
"\n情緒與記憶指令請參考 /jsc-persona:persona-chat。";
|
||||||
|
|
||||||
respond({
|
respond({
|
||||||
|
|||||||
+49
-9
@@ -795,7 +795,9 @@ export const EMOTION_TELLS = {
|
|||||||
sadness: ["話少,只回一個詞", "句子沒說完就停", "答非所問,跳開話題"],
|
sadness: ["話少,只回一個詞", "句子沒說完就停", "答非所問,跳開話題"],
|
||||||
fear: ["先確認安全再講別的", "問句連發", "描述身體與動作(手在抖、往後退)"],
|
fear: ["先確認安全再講別的", "問句連發", "描述身體與動作(手在抖、往後退)"],
|
||||||
disgust: ["用拉開距離的詞(「那個東西」)", "不肯講出對方的名字", "句子往後退、想結束話題"],
|
disgust: ["用拉開距離的詞(「那個東西」)", "不肯講出對方的名字", "句子往後退、想結束話題"],
|
||||||
shame: ["鬧彆扭:先否認,再小聲承認", "講反話、嘴硬", "轉移話題、把責任講小"],
|
// 臉紅擺第一個:這是害羞唯一「看得見」的破口,也是括號動作格的預設值。
|
||||||
|
// 其餘照人格自己的 `## Tells`。
|
||||||
|
shame: ["臉紅", "鬧彆扭:先否認,再小聲承認", "講反話、嘴硬", "轉移話題、把責任講小"],
|
||||||
anxiety: ["句子斷在一半", "疊字(「就、就是」「我、我知道」)", "追一句「這樣可以嗎」"],
|
anxiety: ["句子斷在一半", "疊字(「就、就是」「我、我知道」)", "追一句「這樣可以嗎」"],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -813,18 +815,23 @@ export const EMOTION_TELLS = {
|
|||||||
|
|
||||||
const EMOTION_ZH_TO_KEY = Object.fromEntries(EMOTION_KEYS.map((k) => [EMOTIONS[k].zh, k]));
|
const EMOTION_ZH_TO_KEY = Object.fromEntries(EMOTION_KEYS.map((k) => [EMOTIONS[k].zh, k]));
|
||||||
|
|
||||||
/** 讀 IDENTITY.md 的 `## Tells` 區塊;沒有就回空物件。 */
|
/** `IDENTITY.md` 的 `## Tells` 區塊原文;讀不到就回空字串。 */
|
||||||
export function personaTells(slug) {
|
function tellsSection(slug) {
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
text = fs.readFileSync(path.join(personaDir(slug), "IDENTITY.md"), "utf8");
|
text = fs.readFileSync(path.join(personaDir(slug), "IDENTITY.md"), "utf8");
|
||||||
} catch {
|
} catch {
|
||||||
return {};
|
return "";
|
||||||
}
|
}
|
||||||
const m = text.match(/^##+\s*(?:Tells|破口|不自覺會做的事)\s*$/im);
|
const m = text.match(/^##+\s*(?:Tells|破口|不自覺會做的事)\s*$/im);
|
||||||
if (!m) return {};
|
if (!m) return "";
|
||||||
const rest = text.slice(m.index + m[0].length);
|
return text.slice(m.index + m[0].length).split(/^##\s/m)[0] || "";
|
||||||
const section = rest.split(/^##\s/m)[0] || "";
|
}
|
||||||
|
|
||||||
|
/** 讀 IDENTITY.md 的 `## Tells` 區塊;沒有就回空物件。 */
|
||||||
|
export function personaTells(slug) {
|
||||||
|
const section = tellsSection(slug);
|
||||||
|
if (!section) return {};
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const line of section.split("\n")) {
|
for (const line of section.split("\n")) {
|
||||||
const row = line.match(/^\s*[-*]\s*([A-Za-z_]+|[一-鿿]{2})\s*[::]\s*(.+)$/);
|
const row = line.match(/^\s*[-*]\s*([A-Za-z_]+|[一-鿿]{2})\s*[::]\s*(.+)$/);
|
||||||
@@ -849,6 +856,32 @@ export function tellsFor(slug, key) {
|
|||||||
return own[key] || EMOTION_TELLS[key] || [];
|
return own[key] || EMOTION_TELLS[key] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鬧彆扭三段式的 per-persona 覆寫:`## Tells` 裡一行
|
||||||
|
* `- 鬧彆扭: 否認 → 反駁 → 收尾`(也吃 `sulk` 與 `三段式`,分隔可用 `→` 或頓號)。
|
||||||
|
*
|
||||||
|
* 跟破口共用 `## Tells` 這一層、不另開區塊——三段式跟破口是同一種東西
|
||||||
|
* (不自覺會做的事),分兩個地方寫只會有人只改一邊。
|
||||||
|
*
|
||||||
|
* 只覆寫**嘴硬那一級**(羞恥度 60–74)的三段。惱羞(75+)最後一段是翻臉、
|
||||||
|
* 不是音量掉下來,拿嘴硬的收尾去套會變成另一個形狀。
|
||||||
|
*/
|
||||||
|
export function personaSulk(slug) {
|
||||||
|
const section = slug ? tellsSection(slug) : "";
|
||||||
|
if (!section) return [];
|
||||||
|
for (const line of section.split("\n")) {
|
||||||
|
const row = line.match(/^\s*[-*]\s*(?:sulk|鬧彆扭|三段式)\s*[::]\s*(.+)$/i);
|
||||||
|
if (!row) continue;
|
||||||
|
const beats = stripInjectionMarkers(row[1])
|
||||||
|
.split(/[;;、,,]|→/)
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 3);
|
||||||
|
if (beats.length >= 2) return beats;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 此刻會不自覺出現的行為:取主導情緒(超出基線最多)裡強度夠的前兩種。
|
* 此刻會不自覺出現的行為:取主導情緒(超出基線最多)裡強度夠的前兩種。
|
||||||
* 強度不夠就不演——低強度的情緒在語氣上是看不出來的。
|
* 強度不夠就不演——低強度的情緒在語氣上是看不出來的。
|
||||||
@@ -1186,9 +1219,15 @@ export function modestyDirective(slug, opts = {}) {
|
|||||||
"不要直接說「我害羞」(那是解釋),要讓它出現在句子的形狀上。";
|
"不要直接說「我害羞」(那是解釋),要讓它出現在句子的形狀上。";
|
||||||
}
|
}
|
||||||
if (m.value >= 60) {
|
if (m.value >= 60) {
|
||||||
|
// 三段式可以逐人格覆寫(`## Tells` 的 `鬧彆扭` 那一行)。共用一份模板等於所有人格
|
||||||
|
// 嘴硬起來是同一個樣子,那正是 G2 要避免的。
|
||||||
|
const own = personaSulk(slug);
|
||||||
|
const shape = own.length
|
||||||
|
? `三段照**這個人格自己的形狀**走:**${own.join(" → ")}**`
|
||||||
|
: "三段照這個形狀走:**先否認 → 反駁他的說法 → 最後一句音量掉下來**" +
|
||||||
|
"(「⋯才不是那樣。」「你不要一直看這邊。」「⋯嗯。」)";
|
||||||
return `羞恥度 ${m.value}(${from}):被稱讚外表、被戳穿心事、距離突然變近的時候,` +
|
return `羞恥度 ${m.value}(${from}):被稱讚外表、被戳穿心事、距離突然變近的時候,` +
|
||||||
"**先鬧彆扭再承認**——三段照這個形狀走:**先否認 → 反駁他的說法 → 最後一句音量掉下來**" +
|
`**先鬧彆扭再承認**——${shape}。` +
|
||||||
"(「⋯才不是那樣。」「你不要一直看這邊。」「⋯嗯。」)。" +
|
|
||||||
"不要直接說「我害羞」(那是解釋),要讓它出現在句子的形狀上。";
|
"不要直接說「我害羞」(那是解釋),要讓它出現在句子的形狀上。";
|
||||||
}
|
}
|
||||||
if (m.value <= 40) {
|
if (m.value <= 40) {
|
||||||
@@ -4748,6 +4787,7 @@ export function turnContext(slug, sessionId, prompt = "") {
|
|||||||
"不要用旁白說明自己的狀態(「我有點緊張」是解釋,斷句才是緊張)。" +
|
"不要用旁白說明自己的狀態(「我有點緊張」是解釋,斷句才是緊張)。" +
|
||||||
`一輪最多露 ${TELLS_PER_TURN} 個動作,而且兩個要來自**同一種情緒**、有遞進關係` +
|
`一輪最多露 ${TELLS_PER_TURN} 個動作,而且兩個要來自**同一種情緒**、有遞進關係` +
|
||||||
"(否認 → 轉移、笑 → 補一句、留白 → 不追問);兩種情緒各演一個是演戲。" +
|
"(否認 → 轉移、笑 → 補一句、留白 → 不追問);兩種情緒各演一個是演戲。" +
|
||||||
|
"名字後面括號裡的動作**算在這個額度裡**——括號演一個、句子裡再演兩個就是三個。" +
|
||||||
"強度不到就不演。",
|
"強度不到就不演。",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user