@evomap/evolver 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +290 -0
- package/README.zh-CN.md +236 -0
- package/SKILL.md +132 -0
- package/assets/gep/capsules.json +79 -0
- package/assets/gep/events.jsonl +7 -0
- package/assets/gep/genes.json +108 -0
- package/index.js +479 -0
- package/package.json +38 -0
- package/src/canary.js +13 -0
- package/src/evolve.js +1704 -0
- package/src/gep/a2a.js +173 -0
- package/src/gep/a2aProtocol.js +736 -0
- package/src/gep/analyzer.js +35 -0
- package/src/gep/assetCallLog.js +130 -0
- package/src/gep/assetStore.js +297 -0
- package/src/gep/assets.js +36 -0
- package/src/gep/bridge.js +71 -0
- package/src/gep/candidates.js +142 -0
- package/src/gep/contentHash.js +65 -0
- package/src/gep/deviceId.js +209 -0
- package/src/gep/envFingerprint.js +68 -0
- package/src/gep/hubReview.js +206 -0
- package/src/gep/hubSearch.js +237 -0
- package/src/gep/issueReporter.js +262 -0
- package/src/gep/llmReview.js +92 -0
- package/src/gep/memoryGraph.js +771 -0
- package/src/gep/memoryGraphAdapter.js +203 -0
- package/src/gep/mutation.js +186 -0
- package/src/gep/narrativeMemory.js +108 -0
- package/src/gep/paths.js +113 -0
- package/src/gep/personality.js +355 -0
- package/src/gep/prompt.js +566 -0
- package/src/gep/questionGenerator.js +212 -0
- package/src/gep/reflection.js +127 -0
- package/src/gep/sanitize.js +67 -0
- package/src/gep/selector.js +250 -0
- package/src/gep/signals.js +417 -0
- package/src/gep/skillDistiller.js +499 -0
- package/src/gep/solidify.js +1681 -0
- package/src/gep/strategy.js +126 -0
- package/src/gep/taskReceiver.js +528 -0
- package/src/gep/validationReport.js +55 -0
- package/src/ops/cleanup.js +80 -0
- package/src/ops/commentary.js +60 -0
- package/src/ops/health_check.js +106 -0
- package/src/ops/index.js +11 -0
- package/src/ops/innovation.js +67 -0
- package/src/ops/lifecycle.js +168 -0
- package/src/ops/self_repair.js +72 -0
- package/src/ops/skills_monitor.js +143 -0
- package/src/ops/trigger.js +33 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getMemoryDir } = require('./paths');
|
|
4
|
+
const { hasOpportunitySignal } = require('./mutation');
|
|
5
|
+
|
|
6
|
+
function nowIso() {
|
|
7
|
+
return new Date().toISOString();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function clamp01(x) {
|
|
11
|
+
const n = Number(x);
|
|
12
|
+
if (!Number.isFinite(n)) return 0;
|
|
13
|
+
return Math.max(0, Math.min(1, n));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ensureDir(dir) {
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
19
|
+
} catch (e) {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readJsonIfExists(filePath, fallback) {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(filePath)) return fallback;
|
|
25
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
26
|
+
if (!raw.trim()) return fallback;
|
|
27
|
+
return JSON.parse(raw);
|
|
28
|
+
} catch {
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeJsonAtomic(filePath, obj) {
|
|
34
|
+
const dir = path.dirname(filePath);
|
|
35
|
+
ensureDir(dir);
|
|
36
|
+
const tmp = `${filePath}.tmp`;
|
|
37
|
+
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
|
38
|
+
fs.renameSync(tmp, filePath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function personalityFilePath() {
|
|
42
|
+
const memoryDir = getMemoryDir();
|
|
43
|
+
const { getEvolutionDir } = require('./paths'); return path.join(getEvolutionDir(), 'personality_state.json');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function defaultPersonalityState() {
|
|
47
|
+
// Conservative defaults: protocol-first, safe, low-risk.
|
|
48
|
+
return {
|
|
49
|
+
type: 'PersonalityState',
|
|
50
|
+
rigor: 0.7,
|
|
51
|
+
creativity: 0.35,
|
|
52
|
+
verbosity: 0.25,
|
|
53
|
+
risk_tolerance: 0.4,
|
|
54
|
+
obedience: 0.85,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizePersonalityState(state) {
|
|
59
|
+
const s = state && typeof state === 'object' ? state : {};
|
|
60
|
+
return {
|
|
61
|
+
type: 'PersonalityState',
|
|
62
|
+
rigor: clamp01(s.rigor),
|
|
63
|
+
creativity: clamp01(s.creativity),
|
|
64
|
+
verbosity: clamp01(s.verbosity),
|
|
65
|
+
risk_tolerance: clamp01(s.risk_tolerance),
|
|
66
|
+
obedience: clamp01(s.obedience),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isValidPersonalityState(obj) {
|
|
71
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
72
|
+
if (obj.type !== 'PersonalityState') return false;
|
|
73
|
+
for (const k of ['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience']) {
|
|
74
|
+
const v = obj[k];
|
|
75
|
+
if (!Number.isFinite(Number(v))) return false;
|
|
76
|
+
const n = Number(v);
|
|
77
|
+
if (n < 0 || n > 1) return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function roundToStep(x, step) {
|
|
83
|
+
const s = Number(step);
|
|
84
|
+
if (!Number.isFinite(s) || s <= 0) return x;
|
|
85
|
+
return Math.round(Number(x) / s) * s;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function personalityKey(state) {
|
|
89
|
+
const s = normalizePersonalityState(state);
|
|
90
|
+
const step = 0.1;
|
|
91
|
+
const r = roundToStep(s.rigor, step).toFixed(1);
|
|
92
|
+
const c = roundToStep(s.creativity, step).toFixed(1);
|
|
93
|
+
const v = roundToStep(s.verbosity, step).toFixed(1);
|
|
94
|
+
const rt = roundToStep(s.risk_tolerance, step).toFixed(1);
|
|
95
|
+
const o = roundToStep(s.obedience, step).toFixed(1);
|
|
96
|
+
return `rigor=${r}|creativity=${c}|verbosity=${v}|risk_tolerance=${rt}|obedience=${o}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getParamDeltas(fromState, toState) {
|
|
100
|
+
const a = normalizePersonalityState(fromState);
|
|
101
|
+
const b = normalizePersonalityState(toState);
|
|
102
|
+
const deltas = [];
|
|
103
|
+
for (const k of ['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience']) {
|
|
104
|
+
deltas.push({ param: k, delta: Number(b[k]) - Number(a[k]) });
|
|
105
|
+
}
|
|
106
|
+
deltas.sort((x, y) => Math.abs(y.delta) - Math.abs(x.delta));
|
|
107
|
+
return deltas;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function personalityScore(statsEntry) {
|
|
111
|
+
const e = statsEntry && typeof statsEntry === 'object' ? statsEntry : {};
|
|
112
|
+
const succ = Number(e.success) || 0;
|
|
113
|
+
const fail = Number(e.fail) || 0;
|
|
114
|
+
const total = succ + fail;
|
|
115
|
+
// Laplace-smoothed success probability
|
|
116
|
+
const p = (succ + 1) / (total + 2);
|
|
117
|
+
// Penalize tiny-sample overconfidence
|
|
118
|
+
const sampleWeight = Math.min(1, total / 8);
|
|
119
|
+
// Use avg_score (if present) as mild quality proxy
|
|
120
|
+
const avg = Number.isFinite(Number(e.avg_score)) ? Number(e.avg_score) : null;
|
|
121
|
+
const q = avg == null ? 0.5 : clamp01(avg);
|
|
122
|
+
return p * 0.75 + q * 0.25 * sampleWeight;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function chooseBestKnownPersonality(statsByKey) {
|
|
126
|
+
const stats = statsByKey && typeof statsByKey === 'object' ? statsByKey : {};
|
|
127
|
+
let best = null;
|
|
128
|
+
for (const [k, entry] of Object.entries(stats)) {
|
|
129
|
+
const e = entry || {};
|
|
130
|
+
const total = (Number(e.success) || 0) + (Number(e.fail) || 0);
|
|
131
|
+
if (total < 3) continue;
|
|
132
|
+
const sc = personalityScore(e);
|
|
133
|
+
if (!best || sc > best.score) best = { key: k, score: sc, entry: e };
|
|
134
|
+
}
|
|
135
|
+
return best;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseKeyToState(key) {
|
|
139
|
+
// key format: rigor=0.7|creativity=0.3|...
|
|
140
|
+
const out = defaultPersonalityState();
|
|
141
|
+
const parts = String(key || '').split('|').map(s => s.trim()).filter(Boolean);
|
|
142
|
+
for (const p of parts) {
|
|
143
|
+
const [k, v] = p.split('=').map(x => String(x || '').trim());
|
|
144
|
+
if (!k) continue;
|
|
145
|
+
if (!['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience'].includes(k)) continue;
|
|
146
|
+
out[k] = clamp01(Number(v));
|
|
147
|
+
}
|
|
148
|
+
return normalizePersonalityState(out);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function applyPersonalityMutations(state, mutations) {
|
|
152
|
+
let cur = normalizePersonalityState(state);
|
|
153
|
+
const muts = Array.isArray(mutations) ? mutations : [];
|
|
154
|
+
const applied = [];
|
|
155
|
+
let count = 0;
|
|
156
|
+
for (const m of muts) {
|
|
157
|
+
if (!m || typeof m !== 'object') continue;
|
|
158
|
+
const param = String(m.param || '').trim();
|
|
159
|
+
if (!['rigor', 'creativity', 'verbosity', 'risk_tolerance', 'obedience'].includes(param)) continue;
|
|
160
|
+
const delta = Number(m.delta);
|
|
161
|
+
if (!Number.isFinite(delta)) continue;
|
|
162
|
+
const clipped = Math.max(-0.2, Math.min(0.2, delta));
|
|
163
|
+
cur[param] = clamp01(Number(cur[param]) + clipped);
|
|
164
|
+
applied.push({ type: 'PersonalityMutation', param, delta: clipped, reason: String(m.reason || '').slice(0, 140) });
|
|
165
|
+
count += 1;
|
|
166
|
+
if (count >= 2) break;
|
|
167
|
+
}
|
|
168
|
+
return { state: cur, applied };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function proposeMutations({ baseState, reason, driftEnabled, signals }) {
|
|
172
|
+
const s = normalizePersonalityState(baseState);
|
|
173
|
+
const sig = Array.isArray(signals) ? signals.map(x => String(x || '')) : [];
|
|
174
|
+
const muts = [];
|
|
175
|
+
|
|
176
|
+
const r = String(reason || '');
|
|
177
|
+
if (driftEnabled) {
|
|
178
|
+
muts.push({ type: 'PersonalityMutation', param: 'creativity', delta: +0.1, reason: r || 'drift enabled' });
|
|
179
|
+
// Keep risk bounded under drift by default.
|
|
180
|
+
muts.push({ type: 'PersonalityMutation', param: 'risk_tolerance', delta: -0.05, reason: 'drift safety clamp' });
|
|
181
|
+
} else if (sig.includes('protocol_drift')) {
|
|
182
|
+
muts.push({ type: 'PersonalityMutation', param: 'obedience', delta: +0.1, reason: r || 'protocol drift' });
|
|
183
|
+
muts.push({ type: 'PersonalityMutation', param: 'rigor', delta: +0.05, reason: 'tighten protocol compliance' });
|
|
184
|
+
} else if (sig.includes('log_error') || sig.some(x => x.startsWith('errsig:') || x.startsWith('errsig_norm:'))) {
|
|
185
|
+
muts.push({ type: 'PersonalityMutation', param: 'rigor', delta: +0.1, reason: r || 'repair instability' });
|
|
186
|
+
muts.push({ type: 'PersonalityMutation', param: 'risk_tolerance', delta: -0.1, reason: 'reduce risky changes under errors' });
|
|
187
|
+
} else if (hasOpportunitySignal(sig)) {
|
|
188
|
+
// Opportunity detected: nudge towards creativity to enable innovation.
|
|
189
|
+
muts.push({ type: 'PersonalityMutation', param: 'creativity', delta: +0.1, reason: r || 'opportunity signal detected' });
|
|
190
|
+
muts.push({ type: 'PersonalityMutation', param: 'risk_tolerance', delta: +0.05, reason: 'allow exploration for innovation' });
|
|
191
|
+
} else {
|
|
192
|
+
// Plateau-like generic: slightly increase rigor, slightly decrease verbosity (more concise execution).
|
|
193
|
+
muts.push({ type: 'PersonalityMutation', param: 'rigor', delta: +0.05, reason: r || 'stability bias' });
|
|
194
|
+
muts.push({ type: 'PersonalityMutation', param: 'verbosity', delta: -0.05, reason: 'reduce noise' });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// If already very high obedience, avoid pushing it further; swap second mutation to creativity.
|
|
198
|
+
if (s.obedience >= 0.95) {
|
|
199
|
+
const idx = muts.findIndex(x => x.param === 'obedience');
|
|
200
|
+
if (idx >= 0) muts[idx] = { type: 'PersonalityMutation', param: 'creativity', delta: +0.05, reason: 'obedience saturated' };
|
|
201
|
+
}
|
|
202
|
+
return muts;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function shouldTriggerPersonalityMutation({ driftEnabled, recentEvents }) {
|
|
206
|
+
if (driftEnabled) return { ok: true, reason: 'drift enabled' };
|
|
207
|
+
const list = Array.isArray(recentEvents) ? recentEvents : [];
|
|
208
|
+
const tail = list.slice(-6);
|
|
209
|
+
const outcomes = tail
|
|
210
|
+
.map(e => (e && e.outcome && e.outcome.status ? String(e.outcome.status) : null))
|
|
211
|
+
.filter(Boolean);
|
|
212
|
+
if (outcomes.length >= 4) {
|
|
213
|
+
const recentFailed = outcomes.slice(-4).filter(x => x === 'failed').length;
|
|
214
|
+
if (recentFailed >= 3) return { ok: true, reason: 'long failure streak' };
|
|
215
|
+
}
|
|
216
|
+
// Mutation consecutive failure proxy: last 3 events that have mutation_id.
|
|
217
|
+
const withMut = tail.filter(e => e && typeof e.mutation_id === 'string' && e.mutation_id);
|
|
218
|
+
if (withMut.length >= 3) {
|
|
219
|
+
const last3 = withMut.slice(-3);
|
|
220
|
+
const fail3 = last3.filter(e => e && e.outcome && e.outcome.status === 'failed').length;
|
|
221
|
+
if (fail3 >= 3) return { ok: true, reason: 'mutation consecutive failures' };
|
|
222
|
+
}
|
|
223
|
+
return { ok: false, reason: '' };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function loadPersonalityModel() {
|
|
227
|
+
const p = personalityFilePath();
|
|
228
|
+
const fallback = {
|
|
229
|
+
version: 1,
|
|
230
|
+
current: defaultPersonalityState(),
|
|
231
|
+
stats: {},
|
|
232
|
+
history: [],
|
|
233
|
+
updated_at: nowIso(),
|
|
234
|
+
};
|
|
235
|
+
const raw = readJsonIfExists(p, fallback);
|
|
236
|
+
const cur = normalizePersonalityState(raw && raw.current ? raw.current : defaultPersonalityState());
|
|
237
|
+
const stats = raw && typeof raw.stats === 'object' ? raw.stats : {};
|
|
238
|
+
const history = Array.isArray(raw && raw.history) ? raw.history : [];
|
|
239
|
+
return { version: 1, current: cur, stats, history, updated_at: raw && raw.updated_at ? raw.updated_at : nowIso() };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function savePersonalityModel(model) {
|
|
243
|
+
const m = model && typeof model === 'object' ? model : {};
|
|
244
|
+
const out = {
|
|
245
|
+
version: 1,
|
|
246
|
+
current: normalizePersonalityState(m.current || defaultPersonalityState()),
|
|
247
|
+
stats: m.stats && typeof m.stats === 'object' ? m.stats : {},
|
|
248
|
+
history: Array.isArray(m.history) ? m.history.slice(-120) : [],
|
|
249
|
+
updated_at: nowIso(),
|
|
250
|
+
};
|
|
251
|
+
writeJsonAtomic(personalityFilePath(), out);
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function selectPersonalityForRun({ driftEnabled, signals, recentEvents } = {}) {
|
|
256
|
+
const model = loadPersonalityModel();
|
|
257
|
+
const base = normalizePersonalityState(model.current);
|
|
258
|
+
const stats = model.stats || {};
|
|
259
|
+
|
|
260
|
+
const best = chooseBestKnownPersonality(stats);
|
|
261
|
+
let naturalSelectionApplied = [];
|
|
262
|
+
|
|
263
|
+
// Natural selection: nudge towards the best-known configuration (small, max 2 params).
|
|
264
|
+
if (best && best.key) {
|
|
265
|
+
const bestState = parseKeyToState(best.key);
|
|
266
|
+
const diffs = getParamDeltas(base, bestState).filter(d => Math.abs(d.delta) >= 0.05);
|
|
267
|
+
const muts = [];
|
|
268
|
+
for (const d of diffs.slice(0, 2)) {
|
|
269
|
+
const clipped = Math.max(-0.1, Math.min(0.1, d.delta));
|
|
270
|
+
muts.push({ type: 'PersonalityMutation', param: d.param, delta: clipped, reason: 'natural_selection' });
|
|
271
|
+
}
|
|
272
|
+
const applied = applyPersonalityMutations(base, muts);
|
|
273
|
+
model.current = applied.state;
|
|
274
|
+
naturalSelectionApplied = applied.applied;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Triggered personality mutation (explicit rule-based).
|
|
278
|
+
const trig = shouldTriggerPersonalityMutation({ driftEnabled: !!driftEnabled, recentEvents });
|
|
279
|
+
let triggeredApplied = [];
|
|
280
|
+
if (trig.ok) {
|
|
281
|
+
const props = proposeMutations({
|
|
282
|
+
baseState: model.current,
|
|
283
|
+
reason: trig.reason,
|
|
284
|
+
driftEnabled: !!driftEnabled,
|
|
285
|
+
signals,
|
|
286
|
+
});
|
|
287
|
+
const applied = applyPersonalityMutations(model.current, props);
|
|
288
|
+
model.current = applied.state;
|
|
289
|
+
triggeredApplied = applied.applied;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Persist updated current state.
|
|
293
|
+
const saved = savePersonalityModel(model);
|
|
294
|
+
const key = personalityKey(saved.current);
|
|
295
|
+
const known = !!(saved.stats && saved.stats[key]);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
personality_state: saved.current,
|
|
299
|
+
personality_key: key,
|
|
300
|
+
personality_known: known,
|
|
301
|
+
personality_mutations: [...naturalSelectionApplied, ...triggeredApplied],
|
|
302
|
+
model_meta: {
|
|
303
|
+
best_known_key: best && best.key ? best.key : null,
|
|
304
|
+
best_known_score: best && Number.isFinite(Number(best.score)) ? Number(best.score) : null,
|
|
305
|
+
triggered: trig.ok ? { reason: trig.reason } : null,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function updatePersonalityStats({ personalityState, outcome, score, notes } = {}) {
|
|
311
|
+
const model = loadPersonalityModel();
|
|
312
|
+
const st = normalizePersonalityState(personalityState || model.current);
|
|
313
|
+
const key = personalityKey(st);
|
|
314
|
+
if (!model.stats || typeof model.stats !== 'object') model.stats = {};
|
|
315
|
+
const cur = model.stats[key] && typeof model.stats[key] === 'object' ? model.stats[key] : { success: 0, fail: 0, avg_score: 0.5, n: 0 };
|
|
316
|
+
|
|
317
|
+
const out = String(outcome || '').toLowerCase();
|
|
318
|
+
if (out === 'success') cur.success = (Number(cur.success) || 0) + 1;
|
|
319
|
+
else if (out === 'failed') cur.fail = (Number(cur.fail) || 0) + 1;
|
|
320
|
+
|
|
321
|
+
const sc = Number.isFinite(Number(score)) ? clamp01(Number(score)) : null;
|
|
322
|
+
if (sc != null) {
|
|
323
|
+
const n = (Number(cur.n) || 0) + 1;
|
|
324
|
+
const prev = Number.isFinite(Number(cur.avg_score)) ? Number(cur.avg_score) : 0.5;
|
|
325
|
+
cur.avg_score = prev + (sc - prev) / n;
|
|
326
|
+
cur.n = n;
|
|
327
|
+
}
|
|
328
|
+
cur.updated_at = nowIso();
|
|
329
|
+
model.stats[key] = cur;
|
|
330
|
+
|
|
331
|
+
model.history = Array.isArray(model.history) ? model.history : [];
|
|
332
|
+
model.history.push({
|
|
333
|
+
at: nowIso(),
|
|
334
|
+
key,
|
|
335
|
+
outcome: out === 'success' || out === 'failed' ? out : 'unknown',
|
|
336
|
+
score: sc,
|
|
337
|
+
notes: notes ? String(notes).slice(0, 220) : null,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
savePersonalityModel(model);
|
|
341
|
+
return { key, stats: cur };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
module.exports = {
|
|
345
|
+
clamp01,
|
|
346
|
+
defaultPersonalityState,
|
|
347
|
+
normalizePersonalityState,
|
|
348
|
+
isValidPersonalityState,
|
|
349
|
+
personalityKey,
|
|
350
|
+
loadPersonalityModel,
|
|
351
|
+
savePersonalityModel,
|
|
352
|
+
selectPersonalityForRun,
|
|
353
|
+
updatePersonalityStats,
|
|
354
|
+
};
|
|
355
|
+
|