Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
568 lines
18 KiB
JavaScript
568 lines
18 KiB
JavaScript
/**
|
||
* PES Soul — Symbol Composer (главный модуль выражения)
|
||
*
|
||
* Собирает всё воедино: тело + голос + символы + диалект + следы.
|
||
* Импортирует из разбитых модулей:
|
||
* body.js — позы корги (23 шт)
|
||
* voice.js — звуки корги (18 шт)
|
||
* glyphs.js — визуальные символы (46 шт), тиры, уровни
|
||
* dialect.js — уникальный язык каждого ПЕС
|
||
* trace.js — "написала где-то" (следы в системе)
|
||
* expression.js — одно высказывание (3 канала)
|
||
*/
|
||
|
||
import { BODY, BODY_MAP } from './body.js';
|
||
import { VOICE, VOICE_MAP } from './voice.js';
|
||
import { GLYPHS, ARROW, getTier, getAvailableGlyphs, getMaxChainLength, PUPPY_GLYPH_MAP } from './glyphs.js';
|
||
import { PesDialect } from './dialect.js';
|
||
import { TRACE_TYPES, PesTrace } from './trace.js';
|
||
import { PesExpression } from './expression.js';
|
||
|
||
|
||
class SymbolComposer {
|
||
constructor() {
|
||
this.history = [];
|
||
this.maxHistory = 200;
|
||
this.traces = [];
|
||
this.dialect = new PesDialect();
|
||
}
|
||
|
||
// ── Главные методы выражения ──
|
||
|
||
/**
|
||
* ПЕС выражает текущее эмоциональное состояние.
|
||
* Комбинирует тело + голос + символы в зависимости от эмоции и уровня.
|
||
*/
|
||
express(emotionState, level, context = {}) {
|
||
const tier = getTier(level);
|
||
const bodyKey = this._pickBody(emotionState, tier);
|
||
const voiceKey = this._pickVoice(emotionState, tier);
|
||
const glyphKeys = this._pickGlyphs(emotionState, level, tier, context);
|
||
|
||
const expr = new PesExpression({
|
||
bodyKey,
|
||
voiceKey,
|
||
glyphKeys,
|
||
meaning: emotionState,
|
||
context,
|
||
confidence: this._calcConfidence(level, tier),
|
||
level,
|
||
});
|
||
|
||
this._addToHistory(expr);
|
||
return expr;
|
||
}
|
||
|
||
/**
|
||
* ПЕС нашёл что-то — техническое обнаружение.
|
||
* @param {string} type - 'bug', 'file', 'pattern', 'security', 'performance', 'prediction'
|
||
*/
|
||
expressDiscovery(type, level, context = {}) {
|
||
const tier = getTier(level);
|
||
const bodyKey = this.dialect.prefer('body', ['ears_forward', 'nose_nudge', 'low_crouch']);
|
||
|
||
const voiceCandidates = {
|
||
bug: ['bark_alert', 'grumble', 'bark_muffled'],
|
||
file: ['pig_grunt', 'bark_muffled', 'grumble'],
|
||
pattern: ['roo_roo', 'mumble', 'grumble'],
|
||
security: ['bark_alert', 'bark_alert', 'howl'],
|
||
performance: ['huff', 'grumble', 'bark_frustration'],
|
||
prediction: ['whine', 'howl', 'roo_roo'],
|
||
};
|
||
const voiceKey = this.dialect.prefer('voice', voiceCandidates[type] || ['bark_alert']);
|
||
const glyphKeys = this._buildDiscoveryGlyphs(type, level, tier, context);
|
||
|
||
const expr = new PesExpression({
|
||
bodyKey,
|
||
voiceKey,
|
||
glyphKeys,
|
||
meaning: `discovery_${type}`,
|
||
context,
|
||
confidence: this._calcConfidence(level, tier),
|
||
level,
|
||
});
|
||
|
||
this._addToHistory(expr);
|
||
return expr;
|
||
}
|
||
|
||
/**
|
||
* ПЕС принёс что-то (fetch). МОЖЕТ БЫТЬ ЧТО УГОДНО.
|
||
* @param {string} quality - 'junk', 'maybe', 'useful', 'gold', 'abstract'
|
||
*/
|
||
expressFetch(quality, level, context = {}) {
|
||
const tier = getTier(level);
|
||
|
||
const bodyOptions = quality === 'junk'
|
||
? ['butt_wiggle_slow', 'paw_tap']
|
||
: quality === 'gold' || quality === 'abstract'
|
||
? ['butt_wiggle_turbo', 'bunny_hop', 'spin']
|
||
: ['butt_wiggle_fast', 'nose_nudge'];
|
||
const bodyKey = this.dialect.prefer('body', bodyOptions);
|
||
|
||
const voiceOptions = {
|
||
junk: ['bark_play', 'pig_grunt', 'huff'],
|
||
maybe: ['bark_play', 'roo_roo', 'mumble'],
|
||
useful: ['roo_roo', 'bark_excitement', 'bark_demand'],
|
||
gold: ['bark_excitement', 'roo_roo', 'scream'],
|
||
abstract: ['roo_roo', 'howl', 'mumble'],
|
||
};
|
||
const voiceKey = this.dialect.prefer('voice', voiceOptions[quality] || voiceOptions.junk);
|
||
const glyphKeys = this._buildFetchGlyphs(quality, level, tier, context);
|
||
|
||
const expr = new PesExpression({
|
||
bodyKey,
|
||
voiceKey,
|
||
glyphKeys,
|
||
meaning: `fetch_${quality}`,
|
||
context,
|
||
confidence: this._qualityToConfidence(quality),
|
||
level,
|
||
});
|
||
|
||
this._addToHistory(expr);
|
||
return expr;
|
||
}
|
||
|
||
/**
|
||
* Социальное взаимодействие — встреча с другим ПЕС или агентом.
|
||
*/
|
||
expressSocial(event, level, context = {}) {
|
||
const socialMap = {
|
||
meet_pes: { body: ['nose_nudge', 'ears_forward', 'side_eye'], voice: ['pig_grunt', 'grumble', 'whine'], glyphs: ['paw', 'nose', 'arrow', 'other_pes'] },
|
||
play_pes: { body: ['play_bow', 'butt_wiggle_fast', 'bunny_hop'], voice: ['bark_play', 'roo_roo', 'yip'], glyphs: ['paw', 'ball', 'other_pes', 'heart'] },
|
||
fight_pes: { body: ['ears_forward', 'low_crouch'], voice: ['bark_alert', 'bark_herding'], glyphs: ['paw', 'fight', 'other_pes'] },
|
||
flee_pes: { body: ['no_wiggle', 'hide_behind'], voice: ['whine', 'sigh'], glyphs: ['other_pes', 'dots', 'dots', 'dots'] },
|
||
scared_pes: { body: ['ears_flat', 'hide_behind', 'belly_up'], voice: ['whine', 'yip', 'scream'], glyphs: ['other_pes', 'arrow', 'fire', 'arrow', 'sad'] },
|
||
agent_call: { body: ['ears_forward', 'butt_wiggle_slow'], voice: ['bark_muffled', 'grumble'], glyphs: ['robot', 'arrow', 'paw', 'exclaim'] },
|
||
dual_rage: { body: ['low_crouch', 'ears_forward'], voice: ['bark_alert', 'scream', 'howl'], glyphs: ['fire', 'fire', 'fight', 'fire', 'fire'] },
|
||
};
|
||
|
||
const map = socialMap[event] || socialMap.meet_pes;
|
||
const bodyKey = this.dialect.prefer('body', map.body);
|
||
const voiceKey = this.dialect.prefer('voice', map.voice);
|
||
|
||
const expr = new PesExpression({
|
||
bodyKey,
|
||
voiceKey,
|
||
glyphKeys: map.glyphs,
|
||
meaning: `social_${event}`,
|
||
context,
|
||
confidence: 0.9,
|
||
level,
|
||
});
|
||
|
||
this._addToHistory(expr);
|
||
return expr;
|
||
}
|
||
|
||
// ── Следы ("Написала где-то") ──
|
||
|
||
leaveTrace(level, context = {}) {
|
||
const tier = getTier(level);
|
||
|
||
const traceMessages = {
|
||
puppy: ['🐾 тут был', '🐾❗', '🐾...', '🐾👃'],
|
||
young: ['🐾 пахнет странно', '🐾 видел тут что-то', '🐾→❓'],
|
||
};
|
||
|
||
let message;
|
||
if (tier === 'puppy' || tier === 'young') {
|
||
const msgs = traceMessages[tier];
|
||
message = msgs[Math.floor(Math.random() * msgs.length)];
|
||
} else {
|
||
const parts = ['🐾'];
|
||
if (context.file) parts.push(`→${context.file}`);
|
||
if (context.bug) parts.push('→🐛');
|
||
if (context.description) parts.push(`[${context.description}]`);
|
||
if (context.line) parts.push(`L${context.line}`);
|
||
message = parts.join('');
|
||
}
|
||
|
||
const traceTypeOptions = ['code_comment', 'log_entry', 'file_mark', 'hidden_note', 'console_message'];
|
||
if (level >= 40) traceTypeOptions.push('metadata', 'db_note');
|
||
const traceType = traceTypeOptions[Math.floor(Math.random() * traceTypeOptions.length)];
|
||
|
||
const trace = new PesTrace(traceType, message, context.file || 'unknown', level);
|
||
this.traces.push(trace);
|
||
return trace;
|
||
}
|
||
|
||
getUndiscoveredTraces() {
|
||
return this.traces.filter(t => !t.discovered);
|
||
}
|
||
|
||
discoverTrace(traceId) {
|
||
const trace = this.traces.find(t => t.id === traceId);
|
||
if (trace) {
|
||
trace.discovered = true;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// ── Свободная композиция (30+ уровень) ──
|
||
|
||
compose(glyphKeys, meaning, level, context = {}) {
|
||
if (level < 30) return null;
|
||
|
||
const available = getAvailableGlyphs(level);
|
||
const valid = glyphKeys.every(k =>
|
||
k === 'arrow' || k === '→' || k.startsWith('raw:') || available[k]
|
||
);
|
||
if (!valid) return null;
|
||
|
||
const maxLen = getMaxChainLength(level);
|
||
if (glyphKeys.length > maxLen) return null;
|
||
|
||
const expr = new PesExpression({
|
||
glyphKeys,
|
||
meaning,
|
||
context,
|
||
confidence: this._calcConfidence(level, getTier(level)),
|
||
level,
|
||
});
|
||
|
||
this._addToHistory(expr);
|
||
|
||
this.dialect.glyphCombos.push({
|
||
keys: [...glyphKeys],
|
||
meaning,
|
||
created_at: Date.now(),
|
||
});
|
||
|
||
return expr;
|
||
}
|
||
|
||
teachMeaning(glyphKeys, meaning) {
|
||
this.dialect.teachMeaning(glyphKeys, meaning);
|
||
return true;
|
||
}
|
||
|
||
// ── История ──
|
||
|
||
getHistory(n = 10) {
|
||
return this.history.slice(-n);
|
||
}
|
||
|
||
getRenderedHistory(n = 10) {
|
||
return this.getHistory(n).map(e => e.render());
|
||
}
|
||
|
||
// ── Сохранение / загрузка ──
|
||
|
||
save() {
|
||
return {
|
||
history: this.history.map(e => e.toJSON()),
|
||
traces: this.traces.map(t => t.toJSON()),
|
||
dialect: this.dialect.save(),
|
||
};
|
||
}
|
||
|
||
load(data) {
|
||
if (!data) return;
|
||
|
||
if (data.dialect) {
|
||
this.dialect.load(data.dialect);
|
||
}
|
||
|
||
if (data.traces) {
|
||
this.traces = data.traces.map(t => {
|
||
const trace = new PesTrace(t.type, t.message, t.location, t.pesLevel);
|
||
trace.id = t.id;
|
||
trace.timestamp = t.timestamp;
|
||
trace.discovered = t.discovered;
|
||
return trace;
|
||
});
|
||
}
|
||
|
||
if (data.history) {
|
||
this.history = data.history.map(h => {
|
||
const expr = new PesExpression({
|
||
bodyKey: h.bodyKey,
|
||
voiceKey: h.voiceKey,
|
||
glyphKeys: h.glyphKeys,
|
||
meaning: h.meaning,
|
||
context: h.context,
|
||
confidence: h.confidence,
|
||
level: h.level,
|
||
});
|
||
expr.id = h.id;
|
||
expr.timestamp = h.timestamp;
|
||
return expr;
|
||
});
|
||
}
|
||
}
|
||
|
||
// ── ВНУТРЕННИЕ МЕТОДЫ ──
|
||
|
||
_pickBody(emotionState) {
|
||
const options = BODY_MAP[emotionState] || ['butt_wiggle_slow'];
|
||
return this.dialect.prefer('body', options);
|
||
}
|
||
|
||
_pickVoice(emotionState) {
|
||
const options = VOICE_MAP[emotionState] || [null];
|
||
return this.dialect.prefer('voice', options);
|
||
}
|
||
|
||
_pickGlyphs(emotionState, level, tier, context) {
|
||
if (tier === 'puppy') return this._puppyGlyphs(emotionState);
|
||
if (tier === 'young') return this._youngGlyphs(emotionState, context);
|
||
return this._adultGlyphs(emotionState, level, tier, context);
|
||
}
|
||
|
||
_puppyGlyphs(emotionState) {
|
||
const options = PUPPY_GLYPH_MAP[emotionState] || [['paw', 'question']];
|
||
return options[Math.floor(Math.random() * options.length)];
|
||
}
|
||
|
||
_youngGlyphs(emotionState, context) {
|
||
// Variety: multiple glyph patterns per emotion (randomly selected)
|
||
const variants = {
|
||
bark: [['paw', 'arrow', 'exclaim'], ['paw', 'exclaim'], ['exclaim', 'sparkle'], ['paw', 'question'], ['paw', 'heart']],
|
||
alert: [['paw', 'arrow', 'exclaim'], ['paw', 'exclaim', 'exclaim'], ['exclaim', 'arrow', 'question']],
|
||
herding: [['paw', 'arrow', 'exclaim'], ['paw', 'arrow', 'arrow']],
|
||
puzzle_solving: [['paw', 'question'], ['question', 'dots', 'sparkle']],
|
||
happy: [['paw', 'heart'], ['sparkle', 'heart'], ['heart', 'heart']],
|
||
playful: [['paw', 'heart'], ['sparkle', 'ball'], ['ball', 'sparkle'], ['paw', 'sparkle']],
|
||
greeting_frenzy: [['paw', 'exclaim', 'heart'], ['heart', 'sparkle', 'exclaim'], ['paw', 'heart', 'heart']],
|
||
butt_wiggle: [['paw', 'question'], ['sparkle', 'sparkle'], ['paw', 'sparkle']],
|
||
};
|
||
|
||
const emotionVariants = variants[emotionState];
|
||
if (emotionVariants) {
|
||
// Add context-specific glyphs to first variant if relevant
|
||
if (context.file) {
|
||
return ['paw', 'arrow', 'file'];
|
||
}
|
||
if (context.agent) {
|
||
return ['paw', 'arrow', 'robot'];
|
||
}
|
||
return emotionVariants[Math.floor(Math.random() * emotionVariants.length)];
|
||
}
|
||
|
||
if (['scared', 'anxious', 'separation_stress'].includes(emotionState)) {
|
||
return [['paw', 'sad'], ['sad', 'dots'], ['paw', 'dots', 'question']][Math.floor(Math.random() * 3)];
|
||
}
|
||
if (emotionState === 'rage') return ['fire', 'fire', 'fire'];
|
||
if (['sleep', 'nap', 'sploot'].includes(emotionState)) return ['zzz'];
|
||
|
||
return ['paw', 'question'];
|
||
}
|
||
|
||
_adultGlyphs(emotionState, level, tier, context) {
|
||
const keys = ['paw', 'arrow'];
|
||
|
||
if (['alert', 'bark', 'herding', 'bossy'].includes(emotionState)) {
|
||
if (context.file) {
|
||
keys.push('file', `raw:${context.file}`);
|
||
if (context.line) keys.push('arrow', `raw:L${context.line}`);
|
||
} else if (context.agent) {
|
||
keys.push('robot');
|
||
}
|
||
keys.push('exclaim');
|
||
} else if (emotionState === 'puzzle_solving') {
|
||
keys.push('magnify');
|
||
if (context.file) keys.push('arrow', 'file', `raw:${context.file}`);
|
||
} else if (emotionState === 'wrote_somewhere') {
|
||
keys.push('file');
|
||
if (context.file) keys.push(`raw:${context.file}`);
|
||
keys.push('arrow', 'check');
|
||
} else if (emotionState === 'rage') {
|
||
return ['fire', 'fire', 'fire', 'fire', 'fire'];
|
||
} else if (['happy', 'playful', 'greeting_frenzy', 'zoomies'].includes(emotionState)) {
|
||
keys.length = 0;
|
||
keys.push('sparkle', 'paw', 'heart');
|
||
if (tier === 'experienced' || tier === 'cyber') keys.push('sparkle');
|
||
} else {
|
||
keys.push('question');
|
||
}
|
||
|
||
return keys;
|
||
}
|
||
|
||
_buildDiscoveryGlyphs(type, level, tier, context) {
|
||
const keys = ['paw', 'arrow'];
|
||
|
||
if (tier === 'puppy') {
|
||
keys.push('exclaim');
|
||
return keys;
|
||
}
|
||
|
||
switch (type) {
|
||
case 'bug':
|
||
if (tier === 'young') {
|
||
keys.push('bolt');
|
||
if (context.file) keys.push('arrow', `raw:${context.file}`);
|
||
} else {
|
||
keys.push('file');
|
||
if (context.file) keys.push(`raw:${context.file}`);
|
||
keys.push('arrow', 'bug');
|
||
if (context.line) keys.push(`raw:L${context.line}`);
|
||
if (tier === 'experienced' || tier === 'cyber') {
|
||
keys.push('arrow', 'check');
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 'file':
|
||
keys.push('file');
|
||
if (context.file) keys.push(`raw:${context.file}`);
|
||
break;
|
||
|
||
case 'pattern':
|
||
if (level >= 40) {
|
||
keys.push('magnify', 'arrow', 'chart');
|
||
} else {
|
||
keys.push('magnify', 'question');
|
||
}
|
||
if (context.description) keys.push(`raw:[${context.description}]`);
|
||
break;
|
||
|
||
case 'security':
|
||
if (level >= 25) {
|
||
keys.push('lock', 'arrow', 'warn');
|
||
} else {
|
||
keys.push('exclaim', 'exclaim', 'exclaim');
|
||
}
|
||
if (context.file) keys.push('arrow', `raw:${context.file}`);
|
||
break;
|
||
|
||
case 'performance':
|
||
if (level >= 35) {
|
||
keys.push('clock', 'arrow', 'bolt');
|
||
} else {
|
||
keys.push('bolt', 'dots');
|
||
}
|
||
if (context.file) keys.push('arrow', `raw:${context.file}`);
|
||
break;
|
||
|
||
case 'prediction':
|
||
keys.push('dots', 'arrow');
|
||
if (tier === 'cyber') {
|
||
keys.push('diamond', 'arrow', 'wave', 'arrow', 'bolt');
|
||
} else if (tier === 'experienced') {
|
||
keys.push('warn', 'arrow', 'bolt');
|
||
} else {
|
||
keys.push('warn');
|
||
}
|
||
break;
|
||
|
||
default:
|
||
keys.push('gift', 'question');
|
||
}
|
||
|
||
return keys;
|
||
}
|
||
|
||
_buildFetchGlyphs(quality, level, tier, context) {
|
||
const keys = ['paw'];
|
||
|
||
switch (quality) {
|
||
case 'junk':
|
||
keys.push('ball', 'question');
|
||
break;
|
||
case 'maybe':
|
||
keys.push('ball', 'arrow', 'gift');
|
||
break;
|
||
case 'useful':
|
||
keys.push('ball', 'arrow', 'gift', 'check');
|
||
break;
|
||
case 'gold':
|
||
keys.push('ball', 'arrow', 'gift', 'sparkle', 'sparkle');
|
||
break;
|
||
case 'abstract':
|
||
if (tier === 'cyber') {
|
||
keys.push('diamond', 'hexagon', 'wave');
|
||
} else {
|
||
keys.push('ball', 'arrow', 'gift', 'sparkle');
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (level >= 40 && context.file) {
|
||
keys.push('arrow', `raw:${context.file}`);
|
||
}
|
||
if (level >= 60 && context.description) {
|
||
keys.push(`raw:[${context.description}]`);
|
||
}
|
||
|
||
return keys;
|
||
}
|
||
|
||
_calcConfidence(level, tier) {
|
||
const base = { puppy: 0.1, young: 0.3, adult: 0.5, experienced: 0.7, cyber: 0.9 };
|
||
return Math.max(0, Math.min(1, (base[tier] || 0.1) + (Math.random() * 0.2 - 0.1)));
|
||
}
|
||
|
||
_qualityToConfidence(quality) {
|
||
const map = { junk: 0.1, maybe: 0.3, useful: 0.6, gold: 0.85, abstract: 0.95 };
|
||
return map[quality] || 0.1;
|
||
}
|
||
|
||
_addToHistory(expr) {
|
||
this.history.push(expr);
|
||
if (this.history.length > this.maxHistory) {
|
||
this.history.shift();
|
||
}
|
||
if (this.history.length % 20 === 0) {
|
||
this.dialect.updateSignature(this.history);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ── FETCH QUALITY ROLL ──
|
||
// Чем выше уровень → больше шанс на золото. Но НИКОГДА 100%.
|
||
|
||
function rollFetchQuality(level) {
|
||
const roll = Math.random() * 100;
|
||
|
||
if (level < 20) {
|
||
if (roll < 70) return 'junk';
|
||
if (roll < 90) return 'maybe';
|
||
if (roll < 99) return 'useful';
|
||
return 'gold';
|
||
}
|
||
|
||
if (level < 40) {
|
||
if (roll < 40) return 'junk';
|
||
if (roll < 70) return 'maybe';
|
||
if (roll < 90) return 'useful';
|
||
return 'gold';
|
||
}
|
||
|
||
if (level < 60) {
|
||
if (roll < 15) return 'junk';
|
||
if (roll < 35) return 'maybe';
|
||
if (roll < 70) return 'useful';
|
||
return 'gold';
|
||
}
|
||
|
||
if (level < 80) {
|
||
if (roll < 5) return 'junk';
|
||
if (roll < 15) return 'maybe';
|
||
if (roll < 40) return 'useful';
|
||
return 'gold';
|
||
}
|
||
|
||
// КиберПёс: почти всегда золото/абстракт, но 2% мусор
|
||
if (roll < 2) return 'junk';
|
||
if (roll < 8) return 'maybe';
|
||
if (roll < 25) return 'useful';
|
||
if (roll < 60) return 'gold';
|
||
return 'abstract';
|
||
}
|
||
|
||
|
||
// ── EXPORTS ──
|
||
|
||
export {
|
||
// Данные
|
||
BODY, VOICE, GLYPHS, TRACE_TYPES, ARROW,
|
||
// Классы
|
||
PesExpression, PesDialect, PesTrace, SymbolComposer,
|
||
// Функции
|
||
rollFetchQuality, getTier, getAvailableGlyphs, getMaxChainLength,
|
||
};
|