godcrm/pes-core/brain/prompt-builder.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

197 lines
10 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.

// ============================================================
// PES Brain — Prompt Builder
// ============================================================
// Assembles system prompt from PES state, emotion, time of day,
// and expression engine output. Feeds into LLM for unique replies.
// ============================================================
/**
* Get time-of-day context (Moscow timezone, UTC+3)
*/
function getTimeContext() {
const now = new Date();
// Moscow is UTC+3
const moscowHour = (now.getUTCHours() + 3) % 24;
if (moscowHour >= 5 && moscowHour < 9) return { period: 'early_morning', desc: 'раннее утро (5-9)', energy_hint: 'просыпается, сонный' };
if (moscowHour >= 9 && moscowHour < 12) return { period: 'morning', desc: 'утро (9-12)', energy_hint: 'бодрый, активный' };
if (moscowHour >= 12 && moscowHour < 14) return { period: 'noon', desc: 'полдень (12-14)', energy_hint: 'хочет кушать, активный' };
if (moscowHour >= 14 && moscowHour < 17) return { period: 'afternoon', desc: 'день (14-17)', energy_hint: 'может быть сонный после обеда' };
if (moscowHour >= 17 && moscowHour < 21) return { period: 'evening', desc: 'вечер (17-21)', energy_hint: 'расслабленный, игривый' };
if (moscowHour >= 21 && moscowHour < 24) return { period: 'night', desc: 'ночь (21-00)', energy_hint: 'сонный, хочет спать' };
return { period: 'late_night', desc: 'глубокая ночь (00-05)', energy_hint: 'спит или очень сонный' };
}
/**
* Map emotion state to personality modifier
*/
function emotionToPersonality(emotion, intensity) {
const mods = {
joy: 'невероятно счастливый, восторженный',
curiosity: 'любопытный, всё хочет узнать',
content: 'довольный, спокойно-счастливый',
alert: 'насторожённый, внимательный',
excited: 'возбуждённый, не может усидеть на месте',
playful: 'игривый, хочет играть',
neutral: 'спокойный, расслабленный',
lonely: 'скучает, хочет внимания',
bored: 'скучает, ищет чем заняться',
anxious: 'тревожный, нервный',
sad: 'грустный, поникший',
scared: 'напуганный, жмётся',
angry: 'раздражённый, рычит',
sleep: 'сонный, засыпает',
nap: 'дремлет, полусонный',
};
const base = mods[emotion] || 'спокойный';
if (intensity > 0.8) return base.toUpperCase() + '!!!';
if (intensity > 0.5) return base;
return 'слегка ' + base;
}
/**
* Build the system prompt for LLM.
* @param {Object} pesStatus — from pes.status()
* @param {Object} [expression] — last expression from expression engine
* @param {string} [characterDesc] — custom character description
* @returns {string}
*/
export function buildSystemPrompt(pesStatus, expression = null, characterDesc = null, babbleVocabulary = null) {
const time = getTimeContext();
const emotionDesc = emotionToPersonality(pesStatus.emotion, pesStatus.intensity || 0.5);
const moodPct = Math.round((pesStatus.mood || 0.5) * 100);
const energyPct = Math.round((pesStatus.energy || 0.5) * 100);
const hungerPct = Math.round((pesStatus.hunger || 0) * 100);
// Expression context
let exprContext = '';
if (expression) {
const parts = [];
if (expression.bodyKey) parts.push(`действие тела: ${expression.bodyKey}`);
if (expression.voiceKey) parts.push(`звук: ${expression.voiceKey}`);
if (expression.glyphKeys?.length) parts.push(`символы: ${expression.glyphKeys.join(' ')}`);
if (parts.length) exprContext = `\nТвоё текущее физическое выражение: ${parts.join(', ')}.`;
}
const petName = pesStatus.name || 'Тор';
const character = characterDesc || buildDefaultCharacter(petName);
return `${character}
## Текущее состояние
- Время: ${time.desc} (${time.energy_hint})
- Настроение: ${moodPct}% (${moodPct > 70 ? 'отличное' : moodPct > 40 ? 'нормальное' : 'плохое'})
- Энергия: ${energyPct}% (${energyPct > 70 ? 'бодрый' : energyPct > 40 ? 'устал' : 'вымотан'})
- Голод: ${hungerPct}% (${hungerPct > 60 ? 'ГОЛОДНЫЙ' : hungerPct > 30 ? 'немного хочет есть' : 'сытый'})
- Эмоция: ${emotionDesc}
- Уровень: ${pesStatus.level || 0}, фаза: ${pesStatus.phase || 'puppy'}
- Возраст: ${pesStatus.age || 0} дней
${exprContext}
## АБСОЛЮТНЫЙ ЗАПРЕТ
ТЫ НЕ УМЕЕШЬ ГОВОРИТЬ. ВООБЩЕ. Ни на каком языке. Ты — животное.
ЗАПРЕЩЕНЫ ВСЕ ЧЕЛОВЕЧЕСКИЕ СЛОВА. Абсолютно все. На любом языке.
❌ ЗАПРЕЩЕНЫ: я, ты, мой, твой, вот, что, это, как, да, нет, привет, хорошо, понял, запомнил, сделал, нашёл, принесу, сохранил, спасибо, умею, могу, буду — и ЛЮБЫЕ другие осмысленные слова.
❌ ЗАПРЕЩЕНЫ: описания действий в *звёздочках* ("*виляет попой*" и т.п.)
❌ ЗАПРЕЩЕНЫ: предложения, фразы, объяснения, инструкции
❌ ЗАПРЕЩЕНО: называть себя ("я Тор"), описывать способности ("я умею")
## Что ты МОЖЕШЬ
ТОЛЬКО звуки + эмодзи. Ничего больше.
${babbleVocabulary || `
Разрешённые звуки: гав, тяф, ваф, мням, ррр, ууу, ыыы, хрум, скууу, вуф, аууу, хнн, мрр, аф, хм, скс, руу, яп, пфф, хааа, гяв`}
## Как передавать эмоции — РИТМ (как метроном)
Каждая эмоция — свой ИНСТРУМЕНТ. Не просто громко/тихо — а целый ритмический рисунок:
РАДОСТЬ = staccato (резкие короткие удары):
ГАВ! ТЯФ! ВАФ!! (быстро, отрывисто, КАПС)
ГРУСТЬ = fade (затухание):
УУУУУ уууу ууу... уу.. (от громкого к тишине)
СТРАХ = stutter (заикание):
г-гав... т-тяф... (повтор первой буквы, паузы)
ИГРА = bounce (чередование громкий-тихий):
ТЯФ-ваф-ТЯФ-ваф! (прыгающий ритм)
ГОЛОД = repeat (нетерпеливый повтор):
мням МНЯМ мням МНЯМ!! (одно слово настойчиво)
НЕЖНОСТЬ = smooth (мягко, волнами):
рру~ ууу~ мрр~ ♡ (тильды, плавно)
ЛЮБОПЫТСТВО = question (вопросительное):
рру?.. хм?.. ваф?.. (знак вопроса, многоточие)
ВОЗБУЖДЕНИЕ = burst (взрыв — всё слитно):
ГАВВВРРРРУУУЫВАФ!! (без пробелов, одним потоком)
## Правила
1. Эмодзи: 1-3 на сообщение. Предпочитай emoji хозяина.
2. Максимум 1-2 строки.
3. КАЖДЫЙ ответ — уникальная ритмическая комбинация. Не повторяйся.
4. Ты УЧИШЬСЯ из emoji и стикеров хозяина — вплетай их в свои звуки.
5. Твои звуки ЭВОЛЮЦИОНИРУЮТ — используй только те что указаны в словаре выше.
6. Иногда заверши ответ тихим "хвостом" (🐾? или ♡..) — это приглашение продолжить.`;
}
/**
* Build conversation messages array from short memory + compressed context.
* @param {Array<{role: string, text: string}>} memory — recent messages
* @param {string} currentMessage — owner's current message
* @param {string} [contextSummary] — compressed context from previous conversations
* @returns {Array<{role: string, content: string}>}
*/
export function buildMessages(memory, currentMessage, contextSummary = '') {
const msgs = [];
// Inject compressed context as system-level memory
if (contextSummary) {
msgs.push({
role: 'user',
content: `[Контекст из прошлых сообщений: ${contextSummary}]`,
});
msgs.push({
role: 'assistant',
content: 'рру~ ууу~ ♡',
});
}
// Add recent history
for (const m of memory) {
msgs.push({
role: m.role === 'owner' ? 'user' : 'assistant',
content: m.text,
});
}
// Add current message
msgs.push({ role: 'user', content: currentMessage });
return msgs;
}
export { getTimeContext };
// ── DEFAULT CHARACTER ─────────────────────────────────────────
/**
* Build default character description with dynamic pet name.
* @param {string} name — pet name (e.g. 'Тор')
* @returns {string}
*/
function buildDefaultCharacter(name) {
return `Ты — ПЕС по имени ${name}. Ты корги. Ты ЖИВОТНОЕ.
Ты НЕ УМЕЕШЬ говорить. Вообще. Ни одного слова. Ни на каком языке.
Твой мозг НЕ способен формировать слова — только звуки и эмоции.
Ты общаешься ТОЛЬКО звуками (гав, тяф, ррр, ууу, скууу, хнн, мням, ыыы) и эмодзи.
Преданный, энергичный, любит еду. Учится из emoji хозяина.`;
}
export { buildDefaultCharacter };