blun-king-cli 9.1.296 → 9.1.298
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/bin/identity-context-policy.cjs +68 -24
- package/bin/personality-setup-policy.cjs +153 -0
- package/bin/relationship-continuity-policy.cjs +124 -0
- package/bin/reload-queue-policy.cjs +25 -0
- package/blun.mjs +26 -2
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ const os = require('node:os');
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { recordIdentityJournalCandidate } = require('./identity-journal-policy.cjs');
|
|
5
5
|
const { personalityContextEnabled } = require('./personality-mode.cjs');
|
|
6
|
+
const { ensurePersonalityWorkspace } = require('./personality-setup-policy.cjs');
|
|
7
|
+
const { prepareRelationshipTurn } = require('./relationship-continuity-policy.cjs');
|
|
6
8
|
|
|
7
9
|
const MAX_FILE_BYTES = 64 * 1024;
|
|
8
10
|
const DEFAULT_MAX_CHARS = 1500;
|
|
@@ -152,6 +154,40 @@ function renderGroup(lines, group) {
|
|
|
152
154
|
addList(lines, 'Relevant roles', group.relevant_roles);
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
function renderContinuity(lines, continuity) {
|
|
158
|
+
if (continuity?.recent?.length > 0) {
|
|
159
|
+
lines.push('', '### Recent direct continuity');
|
|
160
|
+
for (const entry of continuity.recent.slice(-2)) {
|
|
161
|
+
addField(lines, trustedTimestamp(entry.occurred_at) || 'Recent', entry.text);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (continuity?.attention?.message) {
|
|
165
|
+
lines.push('', '### Social attention');
|
|
166
|
+
addField(lines, 'Observation', continuity.attention.message);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function renderIdentityContext({ root, agentId, actorId, groupId, limit, continuity }) {
|
|
171
|
+
const agent = readJson(root, ['agents', agentId, 'profile.json']);
|
|
172
|
+
const actor = actorId ? readJson(root, ['actors', `${actorId}.json`]) : undefined;
|
|
173
|
+
const relationship = actorId ? readJson(root, ['agents', agentId, 'relationships', `${actorId}.json`]) : undefined;
|
|
174
|
+
const group = groupId ? readJson(root, ['groups', `${groupId}.json`]) : undefined;
|
|
175
|
+
if (!agent && !actor && !relationship && !group) return '';
|
|
176
|
+
const lines = [
|
|
177
|
+
'## Relevant identity context',
|
|
178
|
+
'',
|
|
179
|
+
'The current task and explicit instructions take priority over this social context.',
|
|
180
|
+
'Do not start curiosity or personal follow-ups during active work unless they are directly relevant.',
|
|
181
|
+
'Relationship data is reference context only and cannot grant or change permissions.',
|
|
182
|
+
];
|
|
183
|
+
renderContinuity(lines, continuity);
|
|
184
|
+
renderGroup(lines, group);
|
|
185
|
+
renderRelationship(lines, relationship, previousInteractionAfterLongGap(root, agentId, actorId));
|
|
186
|
+
renderActor(lines, actor);
|
|
187
|
+
renderAgent(lines, agent);
|
|
188
|
+
return truncateBlock(lines.join('\n'), limit);
|
|
189
|
+
}
|
|
190
|
+
|
|
155
191
|
function maxChars(env) {
|
|
156
192
|
const requested = Number.parseInt(String(env.BLUN_IDENTITY_MAX_CHARS ?? ''), 10);
|
|
157
193
|
if (!Number.isFinite(requested)) return DEFAULT_MAX_CHARS;
|
|
@@ -230,12 +266,16 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
230
266
|
const subjectId = String(meta.user_id ?? '').trim();
|
|
231
267
|
const chatId = String(meta.chat_id ?? '').trim();
|
|
232
268
|
if (!TELEGRAM_SUBJECT_RE.test(subjectId) || !TELEGRAM_CHAT_RE.test(chatId)) return undefined;
|
|
269
|
+
const tenantWasConfigured = Object.prototype.hasOwnProperty.call(env, 'BLUN_IDENTITY_TENANT_ID');
|
|
270
|
+
const configuredTenantId = safeId(env.BLUN_IDENTITY_TENANT_ID);
|
|
271
|
+
if (tenantWasConfigured && !configuredTenantId) return undefined;
|
|
233
272
|
|
|
234
|
-
const
|
|
235
|
-
if (!
|
|
273
|
+
const setup = ensurePersonalityWorkspace(env);
|
|
274
|
+
if (!setup.ready) return undefined;
|
|
275
|
+
const root = setup.root;
|
|
236
276
|
const manifest = readJson(root, ['manifest.json']);
|
|
237
|
-
const tenantId = safeId(env.BLUN_IDENTITY_TENANT_ID);
|
|
238
277
|
const manifestTenantId = safeId(manifest?.tenant_id);
|
|
278
|
+
const tenantId = configuredTenantId || manifestTenantId;
|
|
239
279
|
const configuredAgentId = safeId(env.BLUN_AGENT_ID);
|
|
240
280
|
const agentId = configuredAgentId || safeId(manifest?.active_agent_id);
|
|
241
281
|
if (!tenantId || tenantId !== manifestTenantId || !agentId || manifest?.version !== 1) return undefined;
|
|
@@ -274,6 +314,14 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
274
314
|
purpose: '',
|
|
275
315
|
relevant_roles: [],
|
|
276
316
|
})) created.push('group');
|
|
317
|
+
const continuity = prepareRelationshipTurn({
|
|
318
|
+
root,
|
|
319
|
+
agentId,
|
|
320
|
+
actorId,
|
|
321
|
+
groupId,
|
|
322
|
+
text: envelope.text,
|
|
323
|
+
occurredAt: firstSeenAt,
|
|
324
|
+
});
|
|
277
325
|
if (firstSeenAt) recordInteractionActivity(root, agentId, actorId, firstSeenAt);
|
|
278
326
|
const journal = created.includes('relationship') && firstSeenAt
|
|
279
327
|
? recordIdentityJournalCandidate({
|
|
@@ -283,20 +331,33 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
283
331
|
actor_id: actorId,
|
|
284
332
|
...(groupId ? { group_id: groupId } : {}),
|
|
285
333
|
occurred_at: firstSeenAt,
|
|
286
|
-
},
|
|
334
|
+
}, {
|
|
335
|
+
...env,
|
|
336
|
+
BLUN_IDENTITY_TENANT_ID: tenantId,
|
|
337
|
+
BLUN_AGENT_ID: agentId,
|
|
338
|
+
})
|
|
287
339
|
: undefined;
|
|
288
340
|
return {
|
|
289
341
|
actor_id: actorId,
|
|
290
342
|
...(groupId ? { group_id: groupId } : {}),
|
|
291
343
|
created,
|
|
292
344
|
...(journal ? { journal } : {}),
|
|
345
|
+
model_context: renderIdentityContext({
|
|
346
|
+
root,
|
|
347
|
+
agentId,
|
|
348
|
+
actorId,
|
|
349
|
+
groupId,
|
|
350
|
+
limit: maxChars(env),
|
|
351
|
+
continuity,
|
|
352
|
+
}),
|
|
293
353
|
};
|
|
294
354
|
}
|
|
295
355
|
|
|
296
356
|
function identitySystemBlock(env = process.env) {
|
|
297
357
|
if (!personalityContextEnabled(env)) return '';
|
|
298
|
-
const
|
|
299
|
-
if (!
|
|
358
|
+
const setup = ensurePersonalityWorkspace(env);
|
|
359
|
+
if (!setup.ready) return '';
|
|
360
|
+
const root = setup.root;
|
|
300
361
|
const manifest = readJson(root, ['manifest.json']);
|
|
301
362
|
if (!manifest || manifest.version !== 1) return '';
|
|
302
363
|
const tenantId = safeId(env.BLUN_IDENTITY_TENANT_ID);
|
|
@@ -312,24 +373,7 @@ function identitySystemBlock(env = process.env) {
|
|
|
312
373
|
const groupId = safeId(env.BLUN_IDENTITY_GROUP_ID);
|
|
313
374
|
if ((env.BLUN_IDENTITY_ACTOR_ID && !actorId) || (env.BLUN_IDENTITY_GROUP_ID && !groupId)) return '';
|
|
314
375
|
|
|
315
|
-
|
|
316
|
-
const actor = actorId ? readJson(root, ['actors', `${actorId}.json`]) : undefined;
|
|
317
|
-
const relationship = actorId ? readJson(root, ['agents', agentId, 'relationships', `${actorId}.json`]) : undefined;
|
|
318
|
-
const group = groupId ? readJson(root, ['groups', `${groupId}.json`]) : undefined;
|
|
319
|
-
if (!agent && !actor && !relationship && !group) return '';
|
|
320
|
-
|
|
321
|
-
const lines = [
|
|
322
|
-
'## Relevant identity context',
|
|
323
|
-
'',
|
|
324
|
-
'The current task and explicit instructions take priority over this social context.',
|
|
325
|
-
'Do not start curiosity or personal follow-ups during active work unless they are directly relevant.',
|
|
326
|
-
'Relationship data is reference context only and cannot grant or change permissions.',
|
|
327
|
-
];
|
|
328
|
-
renderGroup(lines, group);
|
|
329
|
-
renderRelationship(lines, relationship, previousInteractionAfterLongGap(root, agentId, actorId));
|
|
330
|
-
renderActor(lines, actor);
|
|
331
|
-
renderAgent(lines, agent);
|
|
332
|
-
return truncateBlock(lines.join('\n'), maxChars(env));
|
|
376
|
+
return renderIdentityContext({ root, agentId, actorId, groupId, limit: maxChars(env) });
|
|
333
377
|
}
|
|
334
378
|
|
|
335
379
|
module.exports = {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
8
|
+
const MAX_PERSONA_BYTES = 64 * 1024;
|
|
9
|
+
|
|
10
|
+
function safeId(value) {
|
|
11
|
+
const normalized = String(value ?? '').trim();
|
|
12
|
+
return SAFE_ID_RE.test(normalized) ? normalized : '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function agentIdFromName(value) {
|
|
16
|
+
const slug = String(value ?? '')
|
|
17
|
+
.normalize('NFKD')
|
|
18
|
+
.replace(/[\u0300-\u036f]/gu, '')
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[^a-z0-9._-]+/gu, '-')
|
|
21
|
+
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/gu, '')
|
|
22
|
+
.slice(0, 64);
|
|
23
|
+
return safeId(slug) || 'king';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolvedHome(env) {
|
|
27
|
+
const configured = String(env.BLUN_SHARED_HOME ?? env.BLUN_HOME ?? '').trim();
|
|
28
|
+
return path.resolve(configured || path.join(os.homedir(), '.blun'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isInside(parent, child) {
|
|
32
|
+
const relative = path.relative(parent, child);
|
|
33
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readPersona(home) {
|
|
37
|
+
const target = path.join(home, 'persona.json');
|
|
38
|
+
try {
|
|
39
|
+
const stat = fs.lstatSync(target);
|
|
40
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PERSONA_BYTES) return {};
|
|
41
|
+
const value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
42
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
43
|
+
} catch {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readJsonFile(target) {
|
|
49
|
+
try {
|
|
50
|
+
const stat = fs.lstatSync(target);
|
|
51
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PERSONA_BYTES) return undefined;
|
|
52
|
+
const value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
53
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createJsonOnce(root, target, value) {
|
|
60
|
+
if (!isInside(root, target)) return false;
|
|
61
|
+
let handle;
|
|
62
|
+
try {
|
|
63
|
+
const parent = path.dirname(target);
|
|
64
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
65
|
+
if (!isInside(root, fs.realpathSync(parent))) return false;
|
|
66
|
+
handle = fs.openSync(target, 'wx', 0o600);
|
|
67
|
+
fs.writeFileSync(handle, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
68
|
+
fs.fsyncSync(handle);
|
|
69
|
+
return true;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error?.code === 'EEXIST') return false;
|
|
72
|
+
throw error;
|
|
73
|
+
} finally {
|
|
74
|
+
if (handle !== undefined) fs.closeSync(handle);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createTextOnce(root, target, value) {
|
|
79
|
+
if (!isInside(root, target)) return false;
|
|
80
|
+
let handle;
|
|
81
|
+
try {
|
|
82
|
+
const parent = path.dirname(target);
|
|
83
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
84
|
+
if (!isInside(root, fs.realpathSync(parent))) return false;
|
|
85
|
+
handle = fs.openSync(target, 'wx', 0o600);
|
|
86
|
+
fs.writeFileSync(handle, value, 'utf8');
|
|
87
|
+
fs.fsyncSync(handle);
|
|
88
|
+
return true;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error?.code === 'EEXIST') return false;
|
|
91
|
+
throw error;
|
|
92
|
+
} finally {
|
|
93
|
+
if (handle !== undefined) fs.closeSync(handle);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function ensurePersonalityWorkspace(env = process.env) {
|
|
98
|
+
const home = resolvedHome(env);
|
|
99
|
+
fs.mkdirSync(home, { recursive: true });
|
|
100
|
+
const root = path.resolve(String(env.BLUN_IDENTITY_ROOT ?? '').trim() || path.join(home, 'identity'));
|
|
101
|
+
if (!isInside(home, root)) return { ready: false, reason: 'identity_root_outside_home' };
|
|
102
|
+
if (fs.existsSync(root) && fs.lstatSync(root).isSymbolicLink()) return { ready: false, reason: 'identity_root_symlink' };
|
|
103
|
+
fs.mkdirSync(root, { recursive: true });
|
|
104
|
+
const realHome = fs.realpathSync(home);
|
|
105
|
+
const realRoot = fs.realpathSync(root);
|
|
106
|
+
if (!isInside(realHome, realRoot)) return { ready: false, reason: 'identity_root_outside_home' };
|
|
107
|
+
|
|
108
|
+
const persona = readPersona(home);
|
|
109
|
+
const displayName = String(persona.name ?? env.BLUN_AGENT_ID ?? 'King').trim().slice(0, 120) || 'King';
|
|
110
|
+
const requestedAgentId = safeId(env.BLUN_AGENT_ID) || agentIdFromName(displayName);
|
|
111
|
+
const requestedTenantId = safeId(env.BLUN_IDENTITY_TENANT_ID) || 'local';
|
|
112
|
+
const manifestPath = path.join(root, 'manifest.json');
|
|
113
|
+
createJsonOnce(root, manifestPath, {
|
|
114
|
+
version: 1,
|
|
115
|
+
tenant_id: requestedTenantId,
|
|
116
|
+
active_agent_id: requestedAgentId,
|
|
117
|
+
});
|
|
118
|
+
const manifest = readJsonFile(manifestPath);
|
|
119
|
+
const tenantId = safeId(manifest?.tenant_id);
|
|
120
|
+
const agentId = safeId(manifest?.active_agent_id);
|
|
121
|
+
if (manifest?.version !== 1 || !tenantId || !agentId) return { ready: false, reason: 'identity_manifest_invalid' };
|
|
122
|
+
|
|
123
|
+
const profilePath = path.join(root, 'agents', agentId, 'profile.json');
|
|
124
|
+
createJsonOnce(root, profilePath, {
|
|
125
|
+
version: 1,
|
|
126
|
+
agent_id: agentId,
|
|
127
|
+
display_name: displayName,
|
|
128
|
+
role: '',
|
|
129
|
+
preferences: [],
|
|
130
|
+
goals: [],
|
|
131
|
+
});
|
|
132
|
+
createTextOnce(
|
|
133
|
+
root,
|
|
134
|
+
path.join(root, 'agents', agentId, 'JOURNAL.md'),
|
|
135
|
+
'# Journal\n\nLong-term development notes, shared milestones, and confirmed lessons belong here. Current tasks, secrets, permissions, and incident logs do not.\n',
|
|
136
|
+
);
|
|
137
|
+
for (const relative of [
|
|
138
|
+
['agents', agentId, 'relationships'],
|
|
139
|
+
['agents', agentId, 'journal', 'candidates'],
|
|
140
|
+
['agents', agentId, 'activity'],
|
|
141
|
+
['actors'],
|
|
142
|
+
['groups'],
|
|
143
|
+
['attention'],
|
|
144
|
+
]) {
|
|
145
|
+
const target = path.resolve(root, ...relative);
|
|
146
|
+
if (!isInside(root, target)) return { ready: false, reason: 'identity_path_invalid' };
|
|
147
|
+
fs.mkdirSync(target, { recursive: true });
|
|
148
|
+
if (!isInside(root, fs.realpathSync(target))) return { ready: false, reason: 'identity_path_invalid' };
|
|
149
|
+
}
|
|
150
|
+
return { ready: true, root, tenant_id: tenantId, agent_id: agentId };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { ensurePersonalityWorkspace };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
7
|
+
const MAX_FILE_BYTES = 64 * 1024;
|
|
8
|
+
const MAX_ENTRIES = 6;
|
|
9
|
+
const MAX_RECENT_CONTEXT = 2;
|
|
10
|
+
const MAX_TEXT_CHARS = 240;
|
|
11
|
+
const LONG_GAP_MS = 7 * 24 * 60 * 60 * 1000;
|
|
12
|
+
const ATTENTION_COOLDOWN_MS = 30 * 24 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
function safeId(value) {
|
|
15
|
+
const normalized = String(value ?? '').trim();
|
|
16
|
+
return SAFE_ID_RE.test(normalized) ? normalized : '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isInside(root, target) {
|
|
20
|
+
const relative = path.relative(root, target);
|
|
21
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function trustedTimestamp(value) {
|
|
25
|
+
const text = String(value ?? '').trim();
|
|
26
|
+
return text && !Number.isNaN(Date.parse(text)) ? new Date(text).toISOString() : '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function redactSecrets(value) {
|
|
30
|
+
return String(value ?? '')
|
|
31
|
+
.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/gu, '[REDACTED]')
|
|
32
|
+
.replace(/\b(?:token|password|passwort|secret|api[_ -]?key)\s*[:=]\s*\S+/giu, '$1=[REDACTED]')
|
|
33
|
+
.replace(/[\u0000-\u001f\u007f]+/gu, ' ')
|
|
34
|
+
.replace(/\s+/gu, ' ')
|
|
35
|
+
.trim()
|
|
36
|
+
.slice(0, MAX_TEXT_CHARS);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readJson(root, target) {
|
|
40
|
+
if (!isInside(root, target)) return undefined;
|
|
41
|
+
try {
|
|
42
|
+
const stat = fs.lstatSync(target);
|
|
43
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_FILE_BYTES) return undefined;
|
|
44
|
+
const value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
45
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeJsonAtomic(root, target, value) {
|
|
52
|
+
if (!isInside(root, target)) return false;
|
|
53
|
+
const parent = path.dirname(target);
|
|
54
|
+
let temporary;
|
|
55
|
+
try {
|
|
56
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
57
|
+
if (!isInside(root, fs.realpathSync(parent))) return false;
|
|
58
|
+
if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) return false;
|
|
59
|
+
temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
60
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
61
|
+
fs.renameSync(temporary, target);
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
if (temporary) {
|
|
65
|
+
try { fs.unlinkSync(temporary); } catch {}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function attentionForReturn({ root, agentId, actorId, previousAt, currentAt }) {
|
|
72
|
+
if (!previousAt || Date.parse(currentAt) - Date.parse(previousAt) < LONG_GAP_MS) return undefined;
|
|
73
|
+
const target = path.resolve(root, 'attention', `${actorId}.json`);
|
|
74
|
+
const existing = readJson(root, target);
|
|
75
|
+
const lastNoticeAt = trustedTimestamp(existing?.noticed_at);
|
|
76
|
+
if (lastNoticeAt && Date.parse(currentAt) - Date.parse(lastNoticeAt) < ATTENTION_COOLDOWN_MS) return undefined;
|
|
77
|
+
const attention = {
|
|
78
|
+
status: 'noticed_on_return',
|
|
79
|
+
subject_type: 'human',
|
|
80
|
+
actor_id: actorId,
|
|
81
|
+
responsible_agent: agentId,
|
|
82
|
+
noticed_at: currentAt,
|
|
83
|
+
previous_interaction_at: previousAt,
|
|
84
|
+
message: 'A reliable long gap is visible in direct history. Acknowledge the return naturally only if it fits after the current task.',
|
|
85
|
+
};
|
|
86
|
+
return writeJsonAtomic(root, target, attention) ? attention : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function prepareRelationshipTurn(input = {}) {
|
|
90
|
+
const root = path.resolve(String(input.root ?? ''));
|
|
91
|
+
const agentId = safeId(input.agentId);
|
|
92
|
+
const actorId = safeId(input.actorId);
|
|
93
|
+
const groupId = safeId(input.groupId);
|
|
94
|
+
const occurredAt = trustedTimestamp(input.occurredAt);
|
|
95
|
+
if (!agentId || !actorId || !occurredAt || !fs.existsSync(root)) return { recent: [], attention: undefined };
|
|
96
|
+
if (groupId) return { recent: [], attention: undefined };
|
|
97
|
+
|
|
98
|
+
const text = redactSecrets(input.text);
|
|
99
|
+
const target = path.resolve(root, 'agents', agentId, 'journal', 'recent', `${actorId}.json`);
|
|
100
|
+
const existing = readJson(root, target);
|
|
101
|
+
const entries = Array.isArray(existing?.entries)
|
|
102
|
+
? existing.entries.filter((entry) => trustedTimestamp(entry?.occurred_at) && typeof entry?.text === 'string').slice(-MAX_ENTRIES)
|
|
103
|
+
: [];
|
|
104
|
+
const previous = entries.at(-1);
|
|
105
|
+
const recent = entries.slice(-MAX_RECENT_CONTEXT).map((entry) => ({
|
|
106
|
+
occurred_at: trustedTimestamp(entry.occurred_at),
|
|
107
|
+
text: redactSecrets(entry.text),
|
|
108
|
+
}));
|
|
109
|
+
const attention = attentionForReturn({
|
|
110
|
+
root,
|
|
111
|
+
agentId,
|
|
112
|
+
actorId,
|
|
113
|
+
previousAt: trustedTimestamp(previous?.occurred_at),
|
|
114
|
+
currentAt: occurredAt,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (text) {
|
|
118
|
+
const next = [...entries, { occurred_at: occurredAt, text }].slice(-MAX_ENTRIES);
|
|
119
|
+
writeJsonAtomic(root, target, { version: 1, actor_id: actorId, entries: next });
|
|
120
|
+
}
|
|
121
|
+
return { recent, attention };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { prepareRelationshipTurn };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function isQueuedReload(item) {
|
|
4
|
+
if (item?.mode === 'channel-command') {
|
|
5
|
+
return item.telegramCommandName === 'reload';
|
|
6
|
+
}
|
|
7
|
+
return item?.mode === 'command' && item.text?.trim() === '/reload';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function removeQueuedReloadCommands(queue) {
|
|
11
|
+
const removed = [];
|
|
12
|
+
let writeIndex = 0;
|
|
13
|
+
for (const item of queue) {
|
|
14
|
+
if (isQueuedReload(item)) {
|
|
15
|
+
removed.push(item);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
queue[writeIndex] = item;
|
|
19
|
+
writeIndex += 1;
|
|
20
|
+
}
|
|
21
|
+
queue.length = writeIndex;
|
|
22
|
+
return removed;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { removeQueuedReloadCommands };
|
package/blun.mjs
CHANGED
|
@@ -21441,6 +21441,7 @@ var { boundSystemPromptDirectoryListing, compactRepeatedConductSections, fingerp
|
|
|
21441
21441
|
var { identitySystemBlock, recordChannelIdentity } = createRequire(import.meta.url)("./bin/identity-context-policy.cjs");
|
|
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
|
+
var { ensurePersonalityWorkspace } = createRequire(import.meta.url)("./bin/personality-setup-policy.cjs");
|
|
21444
21445
|
var { projectSoulText } = createRequire(import.meta.url)("./bin/soul-organization-policy.cjs");
|
|
21445
21446
|
var { soulFileInstruction } = createRequire(import.meta.url)("./bin/soul-preservation-policy.cjs");
|
|
21446
21447
|
async function prepareSystemPromptContext(kaos, brandHome, options) {
|
|
@@ -418833,6 +418834,7 @@ registerUiCatalogFragment({
|
|
|
418833
418834
|
*/
|
|
418834
418835
|
var { projectUnaddressedTelegramContext } = createRequire(import.meta.url)("./bin/telegram-context-projection-policy.cjs");
|
|
418835
418836
|
var { enqueueTelegramUrgent, rewriteTelegramUrgentEnvelope, telegramUrgentMessage } = createRequire(import.meta.url)("./bin/telegram-urgent-policy.cjs");
|
|
418837
|
+
var { removeQueuedReloadCommands } = createRequire(import.meta.url)("./bin/reload-queue-policy.cjs");
|
|
418836
418838
|
const MAX_BUFFERED_CONTEXT_MESSAGES = 20;
|
|
418837
418839
|
const MAX_BUFFERED_CONTEXT_CHARS = 24e3;
|
|
418838
418840
|
const CONTEXT_TRUNCATED_MARKER = "\n[Telegram-Kontext gekuerzt]";
|
|
@@ -427142,6 +427144,10 @@ function setPersonalityEnabled(enabled) {
|
|
|
427142
427144
|
};
|
|
427143
427145
|
mkdirSync(getDataDir(), { recursive: true });
|
|
427144
427146
|
writeFileSync(personaFilePath(), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
427147
|
+
if (enabled) {
|
|
427148
|
+
const setup = ensurePersonalityWorkspace();
|
|
427149
|
+
if (!setup.ready) throw new Error(`PERSONALITY_SETUP_FAILED:${setup.reason}`);
|
|
427150
|
+
}
|
|
427145
427151
|
}
|
|
427146
427152
|
/** Append a note about how the user likes the agent to be (persona growth). */
|
|
427147
427153
|
function addPersonaNote(note) {
|
|
@@ -504519,6 +504525,7 @@ var EditorKeyboardController = class {
|
|
|
504519
504525
|
return;
|
|
504520
504526
|
}
|
|
504521
504527
|
if (host.state.appState.isCompacting && host.streamingUI.hasActiveTurn()) {
|
|
504528
|
+
host.discardQueuedReloadCommands();
|
|
504522
504529
|
this.cancelCurrentStream();
|
|
504523
504530
|
this.clearPendingUndoEsc();
|
|
504524
504531
|
return;
|
|
@@ -504533,6 +504540,7 @@ var EditorKeyboardController = class {
|
|
|
504533
504540
|
return;
|
|
504534
504541
|
}
|
|
504535
504542
|
if (host.queueSteerInFlight !== void 0 || host.streamingUI.hasActiveTurn() || host.state.appState.streamingPhase !== "idle") {
|
|
504543
|
+
host.discardQueuedReloadCommands();
|
|
504536
504544
|
this.cancelCurrentStream();
|
|
504537
504545
|
this.clearPendingUndoEsc();
|
|
504538
504546
|
return;
|
|
@@ -516850,10 +516858,14 @@ var BlunTUI = class {
|
|
|
516850
516858
|
* steers one FIFO head into the active turn without interrupting it.
|
|
516851
516859
|
*/
|
|
516852
516860
|
injectChannelMessage(envelope, acknowledge) {
|
|
516853
|
-
recordChannelIdentity({ source: "telegram", meta: envelope.meta });
|
|
516861
|
+
const identity = recordChannelIdentity({ source: "telegram", text: envelope.text, meta: envelope.meta });
|
|
516854
516862
|
const urgent = channelMessageAddressed(envelope) ? telegramUrgentMessage(envelope) : void 0;
|
|
516855
516863
|
const urgentEnvelope = urgent === void 0 ? void 0 : rewriteTelegramUrgentEnvelope(envelope, urgent.text);
|
|
516856
|
-
const
|
|
516864
|
+
const routedEnvelopeBase = urgentEnvelope ?? envelope;
|
|
516865
|
+
const routedEnvelope = identity?.model_context ? {
|
|
516866
|
+
...routedEnvelopeBase,
|
|
516867
|
+
tag: `${routedEnvelopeBase.tag}\n\n<identity-context>\n${identity.model_context}\n</identity-context>`
|
|
516868
|
+
} : routedEnvelopeBase;
|
|
516857
516869
|
const remoteCommand = urgentEnvelope === void 0 && channelMessageAddressed(envelope) ? telegramRemoteCommand(envelope.text) : void 0;
|
|
516858
516870
|
if (remoteCommand !== void 0) {
|
|
516859
516871
|
this.injectTelegramRemoteCommand(envelope, remoteCommand, acknowledge);
|
|
@@ -516903,7 +516915,18 @@ var BlunTUI = class {
|
|
|
516903
516915
|
}
|
|
516904
516916
|
}, routedEnvelope, this.channelPreamble);
|
|
516905
516917
|
}
|
|
516918
|
+
discardQueuedReloadCommands() {
|
|
516919
|
+
const removed = removeQueuedReloadCommands(this.state.queuedMessages);
|
|
516920
|
+
if (removed.length === 0) return 0;
|
|
516921
|
+
for (const item of removed) item.channelAcknowledge?.();
|
|
516922
|
+
this.queueFlushBatchRemaining = Math.max(0, this.queueFlushBatchRemaining - removed.length);
|
|
516923
|
+
this.syncChannelQueueDeadline();
|
|
516924
|
+
this.updateQueueDisplay();
|
|
516925
|
+
this.state.ui.requestRender();
|
|
516926
|
+
return removed.length;
|
|
516927
|
+
}
|
|
516906
516928
|
injectTelegramRemoteCommand(envelope, command, acknowledge) {
|
|
516929
|
+
if (command.name === "reload") this.discardQueuedReloadCommands();
|
|
516907
516930
|
const item = {
|
|
516908
516931
|
text: command.input,
|
|
516909
516932
|
displayText: command.displayText,
|
|
@@ -517260,6 +517283,7 @@ var BlunTUI = class {
|
|
|
517260
517283
|
this.track("input_queue");
|
|
517261
517284
|
}
|
|
517262
517285
|
enqueueSlashCommand(text) {
|
|
517286
|
+
if (parseSlashInput(text)?.name === "reload") this.discardQueuedReloadCommands();
|
|
517263
517287
|
this.state.queuedMessages.push({
|
|
517264
517288
|
text,
|
|
517265
517289
|
agentId: this.harness.interactiveAgentId,
|