blun-king-cli 9.1.320 → 9.1.322
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.
|
@@ -239,6 +239,12 @@ function openCognitiveStateStore({ home } = {}) {
|
|
|
239
239
|
};
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
function hasEvent(eventId) {
|
|
243
|
+
const event = safeId(eventId);
|
|
244
|
+
if (!event) fail('COGNITIVE_INVALID_EVENT');
|
|
245
|
+
return Boolean(db.prepare('SELECT 1 AS found FROM cognitive_events WHERE event_id = ?').get(event));
|
|
246
|
+
}
|
|
247
|
+
|
|
242
248
|
function verify({ tenantId, agentId } = {}) {
|
|
243
249
|
const tenant = safeId(tenantId);
|
|
244
250
|
const agent = safeId(agentId);
|
|
@@ -374,6 +380,7 @@ function openCognitiveStateStore({ home } = {}) {
|
|
|
374
380
|
|
|
375
381
|
return {
|
|
376
382
|
commit,
|
|
383
|
+
hasEvent,
|
|
377
384
|
read,
|
|
378
385
|
verify,
|
|
379
386
|
claimAttention,
|
|
@@ -5,6 +5,7 @@ const { recordIdentityJournalCandidate } = require('./identity-journal-policy.cj
|
|
|
5
5
|
const { personalityContextEnabled } = require('./personality-mode.cjs');
|
|
6
6
|
const { ensurePersonalityWorkspace } = require('./personality-setup-policy.cjs');
|
|
7
7
|
const { prepareRelationshipTurn } = require('./relationship-continuity-policy.cjs');
|
|
8
|
+
const { prepareCuriosityOpportunity } = require('./relationship-curiosity-policy.cjs');
|
|
8
9
|
const { recordExplicitRelationshipCandidate } = require('./relationship-learning-policy.cjs');
|
|
9
10
|
|
|
10
11
|
const MAX_FILE_BYTES = 64 * 1024;
|
|
@@ -169,6 +170,26 @@ function renderContinuity(lines, continuity) {
|
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
function renderCuriosity(lines, curiosity) {
|
|
174
|
+
if (!curiosity?.topic) return;
|
|
175
|
+
const labels = {
|
|
176
|
+
birthday: 'birthday',
|
|
177
|
+
family: 'family',
|
|
178
|
+
getting_to_know_person: 'getting to know the person',
|
|
179
|
+
personal_goals: 'personal goals',
|
|
180
|
+
pets: 'pets',
|
|
181
|
+
relationship: 'relationship',
|
|
182
|
+
};
|
|
183
|
+
const topic = labels[curiosity.topic];
|
|
184
|
+
if (!topic) return;
|
|
185
|
+
lines.push(
|
|
186
|
+
'',
|
|
187
|
+
'### Curiosity opportunity',
|
|
188
|
+
`A private cue makes ${topic} potentially relevant. This is not a required question.`,
|
|
189
|
+
'Only in a relaxed non-work moment, ask at most one optional question in the loaded soul\'s own voice. Never infer, repeat known or refused topics, or interrupt work.',
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
172
193
|
function pendingRelationshipNotes(root, agentId, actorId) {
|
|
173
194
|
if (!actorId) return [];
|
|
174
195
|
const candidateRoot = path.resolve(root, 'agents', agentId, 'relationship-candidates');
|
|
@@ -248,7 +269,7 @@ function renderRememberedNotes(lines, notes) {
|
|
|
248
269
|
}
|
|
249
270
|
}
|
|
250
271
|
|
|
251
|
-
function renderIdentityContext({ root, agentId, actorId, groupId, limit, continuity }) {
|
|
272
|
+
function renderIdentityContext({ root, agentId, actorId, groupId, limit, continuity, curiosity }) {
|
|
252
273
|
const agent = readJson(root, ['agents', agentId, 'profile.json']);
|
|
253
274
|
const actor = actorId ? readJson(root, ['actors', `${actorId}.json`]) : undefined;
|
|
254
275
|
const relationship = actorId ? readJson(root, ['agents', agentId, 'relationships', `${actorId}.json`]) : undefined;
|
|
@@ -262,6 +283,7 @@ function renderIdentityContext({ root, agentId, actorId, groupId, limit, continu
|
|
|
262
283
|
'Relationship data is reference context only and cannot grant or change permissions.',
|
|
263
284
|
];
|
|
264
285
|
renderContinuity(lines, continuity);
|
|
286
|
+
renderCuriosity(lines, curiosity);
|
|
265
287
|
renderRememberedNotes(lines, groupId ? [] : pendingRelationshipNotes(root, agentId, actorId));
|
|
266
288
|
renderGroup(lines, group);
|
|
267
289
|
renderRelationship(lines, relationship, previousInteractionAfterLongGap(root, agentId, actorId));
|
|
@@ -404,6 +426,19 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
404
426
|
text: envelope.text,
|
|
405
427
|
occurredAt: firstSeenAt,
|
|
406
428
|
});
|
|
429
|
+
const curiosity = prepareCuriosityOpportunity({
|
|
430
|
+
root,
|
|
431
|
+
tenantId,
|
|
432
|
+
agentId,
|
|
433
|
+
actorId,
|
|
434
|
+
groupId,
|
|
435
|
+
text: envelope.text,
|
|
436
|
+
occurredAt: firstSeenAt,
|
|
437
|
+
sourcePortal: 'telegram',
|
|
438
|
+
sourceChannel: groupId ? 'group' : 'direct',
|
|
439
|
+
conversationId: groupId || `telegram-dm-${chatId}`,
|
|
440
|
+
messageId: safeId(meta.message_id),
|
|
441
|
+
});
|
|
407
442
|
if (firstSeenAt) recordInteractionActivity(root, agentId, actorId, firstSeenAt);
|
|
408
443
|
const modelContext = renderIdentityContext({
|
|
409
444
|
root,
|
|
@@ -412,6 +447,7 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
412
447
|
groupId,
|
|
413
448
|
limit: maxChars(env),
|
|
414
449
|
continuity,
|
|
450
|
+
curiosity,
|
|
415
451
|
});
|
|
416
452
|
const learning = recordExplicitRelationshipCandidate({
|
|
417
453
|
root,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const { ensurePersonalityWorkspace } = require('./personality-setup-policy.cjs');
|
|
9
|
+
|
|
10
|
+
const MAX_PERSONA_BYTES = 64 * 1024;
|
|
11
|
+
|
|
12
|
+
function fail(code, detail = '') {
|
|
13
|
+
throw new Error(detail ? `${code}:${detail}` : code);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolvedHome(env) {
|
|
17
|
+
const configured = String(env.BLUN_SHARED_HOME ?? env.BLUN_HOME ?? '').trim();
|
|
18
|
+
return path.resolve(configured || path.join(os.homedir(), '.blun'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function safeHome(env) {
|
|
22
|
+
const home = resolvedHome(env);
|
|
23
|
+
fs.mkdirSync(home, { recursive: true });
|
|
24
|
+
const stat = fs.lstatSync(home);
|
|
25
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) fail('PERSONALITY_CHOICE_UNSAFE_HOME');
|
|
26
|
+
const realHome = fs.realpathSync(home);
|
|
27
|
+
if (path.resolve(realHome) !== home) fail('PERSONALITY_CHOICE_UNSAFE_HOME');
|
|
28
|
+
return home;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readPersonaState(target) {
|
|
32
|
+
try {
|
|
33
|
+
const stat = fs.lstatSync(target);
|
|
34
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PERSONA_BYTES) {
|
|
35
|
+
fail('PERSONALITY_CHOICE_UNSAFE_PERSONA');
|
|
36
|
+
}
|
|
37
|
+
const raw = fs.readFileSync(target, 'utf8');
|
|
38
|
+
const value = JSON.parse(raw);
|
|
39
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
40
|
+
fail('PERSONALITY_CHOICE_UNSAFE_PERSONA');
|
|
41
|
+
}
|
|
42
|
+
return { existed: true, raw, value };
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error?.code === 'ENOENT') return { existed: false, raw: '', value: {} };
|
|
45
|
+
if (String(error?.message ?? '').startsWith('PERSONALITY_CHOICE_')) throw error;
|
|
46
|
+
fail('PERSONALITY_CHOICE_UNSAFE_PERSONA');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function atomicWrite(target, raw) {
|
|
51
|
+
const parent = path.dirname(target);
|
|
52
|
+
const temp = path.join(parent, `.persona-${process.pid}-${crypto.randomBytes(8).toString('hex')}.tmp`);
|
|
53
|
+
let handle;
|
|
54
|
+
try {
|
|
55
|
+
handle = fs.openSync(temp, 'wx', 0o600);
|
|
56
|
+
fs.writeFileSync(handle, raw, 'utf8');
|
|
57
|
+
fs.fsyncSync(handle);
|
|
58
|
+
fs.closeSync(handle);
|
|
59
|
+
handle = undefined;
|
|
60
|
+
fs.renameSync(temp, target);
|
|
61
|
+
} finally {
|
|
62
|
+
if (handle !== undefined) fs.closeSync(handle);
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(temp);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function setPersonalityEnabledAtomic(enabled, env = process.env) {
|
|
72
|
+
if (typeof enabled !== 'boolean') fail('PERSONALITY_CHOICE_INVALID_VALUE');
|
|
73
|
+
const home = safeHome(env);
|
|
74
|
+
const target = path.join(home, 'persona.json');
|
|
75
|
+
const previous = readPersonaState(target);
|
|
76
|
+
|
|
77
|
+
if (enabled) {
|
|
78
|
+
const setup = ensurePersonalityWorkspace(env);
|
|
79
|
+
if (!setup.ready) fail('PERSONALITY_SETUP_FAILED', setup.reason);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const nextRaw = `${JSON.stringify({ ...previous.value, personalityEnabled: enabled }, null, 2)}\n`;
|
|
83
|
+
atomicWrite(target, nextRaw);
|
|
84
|
+
return Object.freeze({ target, previousRaw: previous.raw, previousExisted: previous.existed, nextRaw });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function rollbackPersonalityChoice(receipt) {
|
|
88
|
+
if (!receipt || typeof receipt !== 'object') fail('PERSONALITY_CHOICE_INVALID_RECEIPT');
|
|
89
|
+
const target = path.resolve(String(receipt.target ?? ''));
|
|
90
|
+
const current = readPersonaState(target);
|
|
91
|
+
if (!current.existed || current.raw !== receipt.nextRaw) fail('PERSONALITY_CHOICE_CONFLICT');
|
|
92
|
+
|
|
93
|
+
if (receipt.previousExisted) {
|
|
94
|
+
atomicWrite(target, String(receipt.previousRaw ?? ''));
|
|
95
|
+
} else {
|
|
96
|
+
fs.unlinkSync(target);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { rollbackPersonalityChoice, setPersonalityEnabledAtomic };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { openCognitiveStateStore } = require('./cognitive-state-store.cjs');
|
|
7
|
+
|
|
8
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
9
|
+
const SENSITIVITIES = new Set(['low', 'medium']);
|
|
10
|
+
const INPUT_KEYS = [
|
|
11
|
+
'actorId', 'agentId', 'conversationId', 'cooldownMs', 'messageId', 'occurredAt',
|
|
12
|
+
'sensitivity', 'sourceChannel', 'sourcePortal', 'tenantId', 'topic',
|
|
13
|
+
];
|
|
14
|
+
const MAX_COOLDOWN_MS = 90 * 24 * 60 * 60 * 1000;
|
|
15
|
+
|
|
16
|
+
function fail(code) {
|
|
17
|
+
const error = new Error(code);
|
|
18
|
+
error.code = code;
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function exactKeys(value, expected) {
|
|
23
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
24
|
+
&& Object.keys(value).sort().join('\0') === expected.join('\0');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function safeId(value) {
|
|
28
|
+
const text = String(value ?? '').trim();
|
|
29
|
+
return SAFE_ID_RE.test(text) ? text : '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function digest(prefix, values) {
|
|
33
|
+
return `${prefix}-${crypto.createHash('sha256').update(values.join('\0')).digest('hex').slice(0, 40)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function relationshipScope(actorId) {
|
|
37
|
+
const direct = `relationship:${actorId}:private`;
|
|
38
|
+
return direct.length <= 128 ? direct : `relationship:${digest('actor', [actorId]).slice(0, 30)}:private`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizePresentation(input) {
|
|
42
|
+
if (!exactKeys(input, INPUT_KEYS)) fail('PERSONALITY_MEMORY_FORBIDDEN_FIELD');
|
|
43
|
+
const normalized = {
|
|
44
|
+
tenantId: safeId(input.tenantId),
|
|
45
|
+
agentId: safeId(input.agentId),
|
|
46
|
+
actorId: safeId(input.actorId),
|
|
47
|
+
sourcePortal: safeId(input.sourcePortal),
|
|
48
|
+
sourceChannel: safeId(input.sourceChannel),
|
|
49
|
+
conversationId: safeId(input.conversationId),
|
|
50
|
+
messageId: safeId(input.messageId),
|
|
51
|
+
occurredAt: String(input.occurredAt ?? '').trim(),
|
|
52
|
+
topic: safeId(input.topic),
|
|
53
|
+
sensitivity: String(input.sensitivity ?? ''),
|
|
54
|
+
cooldownMs: Number(input.cooldownMs),
|
|
55
|
+
};
|
|
56
|
+
if (!normalized.tenantId || !normalized.agentId || !normalized.actorId
|
|
57
|
+
|| !normalized.sourcePortal || !normalized.sourceChannel || !normalized.conversationId
|
|
58
|
+
|| !normalized.messageId || !normalized.topic || !SENSITIVITIES.has(normalized.sensitivity)
|
|
59
|
+
|| Number.isNaN(Date.parse(normalized.occurredAt))
|
|
60
|
+
|| !Number.isSafeInteger(normalized.cooldownMs) || normalized.cooldownMs < 1000
|
|
61
|
+
|| normalized.cooldownMs > MAX_COOLDOWN_MS) {
|
|
62
|
+
fail('PERSONALITY_MEMORY_INVALID_INPUT');
|
|
63
|
+
}
|
|
64
|
+
normalized.occurredAt = new Date(normalized.occurredAt).toISOString();
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createPersonalityMemoryAdapter({ identityRoot } = {}) {
|
|
69
|
+
const root = path.resolve(String(identityRoot ?? ''));
|
|
70
|
+
try {
|
|
71
|
+
const stat = fs.lstatSync(root);
|
|
72
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || path.basename(root).toLowerCase() !== 'identity') {
|
|
73
|
+
fail('PERSONALITY_MEMORY_UNSAFE_ROOT');
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error?.code === 'PERSONALITY_MEMORY_UNSAFE_ROOT') throw error;
|
|
77
|
+
fail('PERSONALITY_MEMORY_UNSAFE_ROOT');
|
|
78
|
+
}
|
|
79
|
+
const realRoot = fs.realpathSync(root);
|
|
80
|
+
const home = path.dirname(realRoot);
|
|
81
|
+
const store = openCognitiveStateStore({ home });
|
|
82
|
+
|
|
83
|
+
function claimCuriosityPresentation(input) {
|
|
84
|
+
const value = normalizePresentation(input);
|
|
85
|
+
const candidateId = digest('curiosity', [value.tenantId, value.agentId, value.actorId]);
|
|
86
|
+
const eventId = digest('personality', [
|
|
87
|
+
value.tenantId, value.agentId, value.actorId, value.sourcePortal, value.sourceChannel,
|
|
88
|
+
value.conversationId, value.messageId, value.occurredAt, value.topic,
|
|
89
|
+
]);
|
|
90
|
+
if (store.hasEvent(eventId)) return { claimed: true, idempotent: true };
|
|
91
|
+
const claim = store.claimAttention({
|
|
92
|
+
tenantId: value.tenantId,
|
|
93
|
+
candidate: {
|
|
94
|
+
candidate_id: candidateId,
|
|
95
|
+
subject_kind: 'human',
|
|
96
|
+
subject_id: value.actorId,
|
|
97
|
+
reason: 'social_curiosity',
|
|
98
|
+
responsible_agent: value.agentId,
|
|
99
|
+
evidence_id: eventId,
|
|
100
|
+
evidence_scope: relationshipScope(value.actorId),
|
|
101
|
+
evidence_at: value.occurredAt,
|
|
102
|
+
confidence: 1,
|
|
103
|
+
allowed_channel: value.sourceChannel,
|
|
104
|
+
detected_at: value.occurredAt,
|
|
105
|
+
cooldown_ms: value.cooldownMs,
|
|
106
|
+
requires_runtime_authorization: true,
|
|
107
|
+
},
|
|
108
|
+
claimedBy: value.agentId,
|
|
109
|
+
claimedAt: value.occurredAt,
|
|
110
|
+
});
|
|
111
|
+
if (!claim.claimed) return { claimed: false, next_allowed_at: claim.next_allowed_at };
|
|
112
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
113
|
+
const current = store.read({ tenantId: value.tenantId, agentId: value.agentId });
|
|
114
|
+
try {
|
|
115
|
+
store.commit({
|
|
116
|
+
tenantId: value.tenantId,
|
|
117
|
+
agentId: value.agentId,
|
|
118
|
+
eventId,
|
|
119
|
+
expectedVersion: current.version,
|
|
120
|
+
occurredAt: value.occurredAt,
|
|
121
|
+
source: {
|
|
122
|
+
provider: value.sourcePortal,
|
|
123
|
+
actor_id: value.actorId,
|
|
124
|
+
context_id: value.conversationId,
|
|
125
|
+
message_id: value.messageId,
|
|
126
|
+
},
|
|
127
|
+
observations: [{
|
|
128
|
+
observation_id: digest('curiosity-observation', [eventId]),
|
|
129
|
+
domain: 'open_thread',
|
|
130
|
+
key: `relationship:${digest('actor', [value.actorId]).slice(0, 30)}:curiosity-presentation`,
|
|
131
|
+
value: value.topic,
|
|
132
|
+
confidence: 1,
|
|
133
|
+
scope: relationshipScope(value.actorId),
|
|
134
|
+
}],
|
|
135
|
+
});
|
|
136
|
+
return { claimed: true, idempotent: false };
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error?.code !== 'COGNITIVE_VERSION_CONFLICT' || attempt === 2) throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
fail('PERSONALITY_MEMORY_VERSION_CONFLICT');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
kind: 'local-cognitive-projection-v1',
|
|
146
|
+
claimCuriosityPresentation,
|
|
147
|
+
close: () => store.close(),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { createPersonalityMemoryAdapter };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const { createPersonalityMemoryAdapter } = require('./personality-memory-adapter.cjs');
|
|
5
|
+
|
|
6
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
7
|
+
const COOLDOWN_MS = 30 * 24 * 60 * 60 * 1000;
|
|
8
|
+
const WORK_OR_STRESS_RE = /(?:\b(?:auftrag|bauen|build|deploy|dringend|error|fehler|fix|funktioniert\s+nicht|problem|release|reparier|task|ticket|update|urgent)\b|https?:\/\/|[A-Za-z]:\\|```)/iu;
|
|
9
|
+
const CUES = [
|
|
10
|
+
{ topic: 'pets', sensitivity: 'low', pattern: /\b(?:dog|dogs|hund|hunde|katze|katzen|pet|pets|haustier|haustiere)\b/iu },
|
|
11
|
+
{ topic: 'family', sensitivity: 'medium', pattern: /\b(?:child|children|daughter|family|familie|kind|kinder|sohn|tochter)\b/iu },
|
|
12
|
+
{ topic: 'relationship', sensitivity: 'medium', pattern: /\b(?:beziehung|married|partner|partnerin|relationship|verheiratet)\b/iu },
|
|
13
|
+
{ topic: 'birthday', sensitivity: 'medium', pattern: /\b(?:birthday|geburtstag)\b/iu },
|
|
14
|
+
{ topic: 'personal_goals', sensitivity: 'low', pattern: /\b(?:in\s+einem\s+jahr|langfristig(?:es|e|en)?\s+ziel|long[- ]term\s+goal|personal\s+goal)\b/iu },
|
|
15
|
+
];
|
|
16
|
+
const RELAXED_FIRST_CONTACT_RE = /^(?:hallo|hey|hi|guten\s+(?:morgen|abend|tag)|moin|servus|wie\s+geht(?:'s|\s+es)?\s+dir|hello|how\s+are\s+you)[\s,.!?-]*.*$/iu;
|
|
17
|
+
|
|
18
|
+
function safeId(value) {
|
|
19
|
+
const normalized = String(value ?? '').trim();
|
|
20
|
+
return SAFE_ID_RE.test(normalized) ? normalized : '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizedText(value) {
|
|
24
|
+
return String(value ?? '')
|
|
25
|
+
.replace(/[\u0000-\u001f\u007f]+/gu, ' ')
|
|
26
|
+
.replace(/\s+/gu, ' ')
|
|
27
|
+
.trim()
|
|
28
|
+
.slice(0, 500);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function trustedTimestamp(value) {
|
|
32
|
+
const text = String(value ?? '').trim();
|
|
33
|
+
return text && !Number.isNaN(Date.parse(text)) ? new Date(text).toISOString() : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function digestId(prefix, values) {
|
|
37
|
+
return `${prefix}-${crypto.createHash('sha256').update(values.join('\0')).digest('hex').slice(0, 40)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function curiosityCue(value) {
|
|
41
|
+
const text = normalizedText(value);
|
|
42
|
+
if (!text || WORK_OR_STRESS_RE.test(text)) return undefined;
|
|
43
|
+
for (const cue of CUES) {
|
|
44
|
+
if (cue.pattern.test(text)) return cue;
|
|
45
|
+
}
|
|
46
|
+
return RELAXED_FIRST_CONTACT_RE.test(text)
|
|
47
|
+
? { topic: 'getting_to_know_person', sensitivity: 'low' }
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function opportunity(cue, idempotent = false) {
|
|
52
|
+
return {
|
|
53
|
+
topic: cue.topic,
|
|
54
|
+
sensitivity: cue.sensitivity,
|
|
55
|
+
source: 'direct_message_cue',
|
|
56
|
+
requires_question: false,
|
|
57
|
+
...(idempotent ? { idempotent: true } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function prepareCuriosityOpportunity(input = {}) {
|
|
62
|
+
if (input.groupId) return undefined;
|
|
63
|
+
const tenantId = safeId(input.tenantId);
|
|
64
|
+
const agentId = safeId(input.agentId);
|
|
65
|
+
const actorId = safeId(input.actorId);
|
|
66
|
+
const occurredAt = trustedTimestamp(input.occurredAt);
|
|
67
|
+
const sourcePortal = safeId(input.sourcePortal) || 'telegram';
|
|
68
|
+
const sourceChannel = safeId(input.sourceChannel) || 'direct';
|
|
69
|
+
const conversationId = safeId(input.conversationId) || digestId('conversation', [actorId]);
|
|
70
|
+
const cue = curiosityCue(input.text);
|
|
71
|
+
if (!tenantId || !agentId || !actorId || !occurredAt || !cue) return undefined;
|
|
72
|
+
const messageId = safeId(input.messageId) || digestId('message', [
|
|
73
|
+
sourcePortal, sourceChannel, conversationId, actorId, occurredAt, normalizedText(input.text),
|
|
74
|
+
]);
|
|
75
|
+
let memory = input.memoryAdapter;
|
|
76
|
+
let ownsMemory = false;
|
|
77
|
+
try {
|
|
78
|
+
if (!memory) {
|
|
79
|
+
memory = createPersonalityMemoryAdapter({ identityRoot: input.root });
|
|
80
|
+
ownsMemory = true;
|
|
81
|
+
}
|
|
82
|
+
const result = memory.claimCuriosityPresentation({
|
|
83
|
+
tenantId,
|
|
84
|
+
agentId,
|
|
85
|
+
actorId,
|
|
86
|
+
sourcePortal,
|
|
87
|
+
sourceChannel,
|
|
88
|
+
conversationId,
|
|
89
|
+
messageId,
|
|
90
|
+
occurredAt,
|
|
91
|
+
topic: cue.topic,
|
|
92
|
+
sensitivity: cue.sensitivity,
|
|
93
|
+
cooldownMs: COOLDOWN_MS,
|
|
94
|
+
});
|
|
95
|
+
return result?.claimed === true ? opportunity(cue, result.idempotent === true) : undefined;
|
|
96
|
+
} catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
} finally {
|
|
99
|
+
if (ownsMemory) {
|
|
100
|
+
try { memory.close(); } catch {}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { COOLDOWN_MS, prepareCuriosityOpportunity };
|
package/blun.mjs
CHANGED
|
@@ -21442,6 +21442,7 @@ var { identitySystemBlock, recordChannelIdentity } = createRequire(import.meta.u
|
|
|
21442
21442
|
var { naturalPresenceSystemBlock } = createRequire(import.meta.url)("./bin/natural-presence-policy.cjs");
|
|
21443
21443
|
var { personalityContextEnabled } = createRequire(import.meta.url)("./bin/personality-mode.cjs");
|
|
21444
21444
|
var { ensurePersonalityWorkspace } = createRequire(import.meta.url)("./bin/personality-setup-policy.cjs");
|
|
21445
|
+
var { rollbackPersonalityChoice, setPersonalityEnabledAtomic } = createRequire(import.meta.url)("./bin/personality-choice-policy.cjs");
|
|
21445
21446
|
var { projectSoulText } = createRequire(import.meta.url)("./bin/soul-organization-policy.cjs");
|
|
21446
21447
|
var { soulFileInstruction } = createRequire(import.meta.url)("./bin/soul-preservation-policy.cjs");
|
|
21447
21448
|
async function prepareSystemPromptContext(kaos, brandHome, options) {
|
|
@@ -420827,10 +420828,19 @@ function showPersonalityPicker(host) {
|
|
|
420827
420828
|
}));
|
|
420828
420829
|
}
|
|
420829
420830
|
async function applyPersonalityChoice(host, enabled) {
|
|
420831
|
+
let receipt;
|
|
420830
420832
|
try {
|
|
420831
|
-
setPersonalityEnabled(enabled);
|
|
420832
|
-
await refreshPersonaInSession(host);
|
|
420833
|
+
receipt = setPersonalityEnabled(enabled);
|
|
420834
|
+
await refreshPersonaInSession(host, true);
|
|
420833
420835
|
} catch (error) {
|
|
420836
|
+
if (receipt !== void 0) {
|
|
420837
|
+
try {
|
|
420838
|
+
rollbackPersonalityChoice(receipt);
|
|
420839
|
+
} catch (rollbackError) {
|
|
420840
|
+
host.showError(uiText("session.persona.note.saveFailed", { error: formatErrorMessage$2(rollbackError) }));
|
|
420841
|
+
return;
|
|
420842
|
+
}
|
|
420843
|
+
}
|
|
420834
420844
|
host.showError(uiText("session.persona.note.saveFailed", { error: formatErrorMessage$2(error) }));
|
|
420835
420845
|
return;
|
|
420836
420846
|
}
|
|
@@ -427281,16 +427291,7 @@ function setLanguage(language) {
|
|
|
427281
427291
|
writeFileSync(personaFilePath(), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
427282
427292
|
}
|
|
427283
427293
|
function setPersonalityEnabled(enabled) {
|
|
427284
|
-
|
|
427285
|
-
...readPersona(),
|
|
427286
|
-
personalityEnabled: enabled
|
|
427287
|
-
};
|
|
427288
|
-
mkdirSync(getDataDir(), { recursive: true });
|
|
427289
|
-
writeFileSync(personaFilePath(), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
427290
|
-
if (enabled) {
|
|
427291
|
-
const setup = ensurePersonalityWorkspace();
|
|
427292
|
-
if (!setup.ready) throw new Error(`PERSONALITY_SETUP_FAILED:${setup.reason}`);
|
|
427293
|
-
}
|
|
427294
|
+
return setPersonalityEnabledAtomic(enabled);
|
|
427294
427295
|
}
|
|
427295
427296
|
/** Append a note about how the user likes the agent to be (persona growth). */
|
|
427296
427297
|
function addPersonaNote(note) {
|
|
@@ -427549,12 +427550,14 @@ async function handleNameCommand(host, args) {
|
|
|
427549
427550
|
* The system prompt is cached at session start; reloadSession rebuilds it, and
|
|
427550
427551
|
* `personaSystemBlock` re-reads persona.json at render time.
|
|
427551
427552
|
*/
|
|
427552
|
-
async function refreshPersonaInSession(host) {
|
|
427553
|
+
async function refreshPersonaInSession(host, strict = false) {
|
|
427553
427554
|
const session = host.session;
|
|
427554
427555
|
if (session === void 0) return;
|
|
427555
427556
|
try {
|
|
427556
427557
|
await session.reloadSession({ mcpServers: sessionMcpServers() });
|
|
427557
|
-
} catch {
|
|
427558
|
+
} catch (error) {
|
|
427559
|
+
if (strict) throw error;
|
|
427560
|
+
}
|
|
427558
427561
|
}
|
|
427559
427562
|
async function handlePersonaCommand(host, args) {
|
|
427560
427563
|
const trimmed = args.trim();
|