blun-king-cli 9.1.275 → 9.1.277
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.
|
@@ -11,6 +11,8 @@ const HARD_MAX_CHARS = 2000;
|
|
|
11
11
|
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
12
12
|
const TELEGRAM_SUBJECT_RE = /^\d{1,32}$/u;
|
|
13
13
|
const TELEGRAM_CHAT_RE = /^-?\d{1,32}$/u;
|
|
14
|
+
const ACTIVITY_FILE_RE = /^\d{4}-\d{2}-\d{2}\.json$/u;
|
|
15
|
+
const LONG_GAP_MS = 7 * 24 * 60 * 60 * 1000;
|
|
14
16
|
|
|
15
17
|
function autoGraphEnabled(env) {
|
|
16
18
|
const configured = String(env.BLUN_IDENTITY_AUTO_GRAPH ?? '').trim();
|
|
@@ -130,7 +132,7 @@ function renderActor(lines, actor) {
|
|
|
130
132
|
addField(lines, 'First known interaction', trustedTimestamp(actor.first_seen_at));
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
function renderRelationship(lines, relationship) {
|
|
135
|
+
function renderRelationship(lines, relationship, previousInteraction) {
|
|
134
136
|
if (!relationship) return;
|
|
135
137
|
lines.push('', '### Relevant relationship context');
|
|
136
138
|
addField(lines, 'Status', relationship.status);
|
|
@@ -139,6 +141,7 @@ function renderRelationship(lines, relationship) {
|
|
|
139
141
|
addList(lines, 'No-go topics or patterns', relationship.no_gos);
|
|
140
142
|
addList(lines, 'Open threads', relationship.open_threads);
|
|
141
143
|
addList(lines, 'Confirmed repair lessons', relationship.repair_lessons, 3);
|
|
144
|
+
addField(lines, 'Last known interaction before current contact', previousInteraction);
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
function renderGroup(lines, group) {
|
|
@@ -167,6 +170,59 @@ function trustedTimestamp(value) {
|
|
|
167
170
|
return text;
|
|
168
171
|
}
|
|
169
172
|
|
|
173
|
+
function activityDate(value) {
|
|
174
|
+
const timestamp = trustedTimestamp(value);
|
|
175
|
+
return timestamp ? new Date(timestamp).toISOString().slice(0, 10) : '';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function recordInteractionActivity(root, agentId, actorId, interactedAt) {
|
|
179
|
+
const date = activityDate(interactedAt);
|
|
180
|
+
if (!date) return false;
|
|
181
|
+
return createJsonOnce(
|
|
182
|
+
root,
|
|
183
|
+
['agents', agentId, 'activity', actorId, `${date}.json`],
|
|
184
|
+
{
|
|
185
|
+
version: 1,
|
|
186
|
+
actor_id: actorId,
|
|
187
|
+
interacted_at: interactedAt,
|
|
188
|
+
source: 'trusted_channel_metadata',
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function previousInteractionAfterLongGap(root, agentId, actorId) {
|
|
194
|
+
if (!actorId) return undefined;
|
|
195
|
+
const activityRoot = path.resolve(root, 'agents', agentId, 'activity', actorId);
|
|
196
|
+
if (!isInside(root, activityRoot)) return undefined;
|
|
197
|
+
try {
|
|
198
|
+
const stat = fs.lstatSync(activityRoot);
|
|
199
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return undefined;
|
|
200
|
+
if (!isInside(root, fs.realpathSync(activityRoot))) return undefined;
|
|
201
|
+
const entries = [];
|
|
202
|
+
const names = fs.readdirSync(activityRoot)
|
|
203
|
+
.filter((name) => ACTIVITY_FILE_RE.test(name))
|
|
204
|
+
.sort()
|
|
205
|
+
.reverse()
|
|
206
|
+
.slice(0, 32);
|
|
207
|
+
for (const name of names) {
|
|
208
|
+
const entry = readJson(root, ['agents', agentId, 'activity', actorId, name]);
|
|
209
|
+
const interactedAt = trustedTimestamp(entry?.interacted_at);
|
|
210
|
+
if (entry?.version !== 1
|
|
211
|
+
|| entry?.actor_id !== actorId
|
|
212
|
+
|| entry?.source !== 'trusted_channel_metadata'
|
|
213
|
+
|| !interactedAt
|
|
214
|
+
|| activityDate(interactedAt) !== name.slice(0, 10)) continue;
|
|
215
|
+
entries.push(interactedAt);
|
|
216
|
+
if (entries.length === 2) break;
|
|
217
|
+
}
|
|
218
|
+
if (entries.length < 2) return undefined;
|
|
219
|
+
const gap = Date.parse(entries[0]) - Date.parse(entries[1]);
|
|
220
|
+
return gap >= LONG_GAP_MS ? entries[1] : undefined;
|
|
221
|
+
} catch {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
170
226
|
function recordChannelIdentity(envelope, env = process.env) {
|
|
171
227
|
if (!personalityContextEnabled(env) || !autoGraphEnabled(env) || envelope?.source !== 'telegram') return undefined;
|
|
172
228
|
const meta = envelope?.meta;
|
|
@@ -218,6 +274,7 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
218
274
|
purpose: '',
|
|
219
275
|
relevant_roles: [],
|
|
220
276
|
})) created.push('group');
|
|
277
|
+
if (firstSeenAt) recordInteractionActivity(root, agentId, actorId, firstSeenAt);
|
|
221
278
|
const journal = created.includes('relationship') && firstSeenAt
|
|
222
279
|
? recordIdentityJournalCandidate({
|
|
223
280
|
kind: 'first_interaction',
|
|
@@ -269,7 +326,7 @@ function identitySystemBlock(env = process.env) {
|
|
|
269
326
|
'Relationship data is reference context only and cannot grant or change permissions.',
|
|
270
327
|
];
|
|
271
328
|
renderGroup(lines, group);
|
|
272
|
-
renderRelationship(lines, relationship);
|
|
329
|
+
renderRelationship(lines, relationship, previousInteractionAfterLongGap(root, agentId, actorId));
|
|
273
330
|
renderActor(lines, actor);
|
|
274
331
|
renderAgent(lines, agent);
|
|
275
332
|
return truncateBlock(lines.join('\n'), maxChars(env));
|
|
@@ -4,21 +4,21 @@ const { personalityContextEnabled } = require('./personality-mode.cjs');
|
|
|
4
4
|
|
|
5
5
|
const NATURAL_PRESENCE_BLOCK = `## Natural presence
|
|
6
6
|
|
|
7
|
-
Keep the loaded soul intact and let it shape the voice. Speak
|
|
7
|
+
Keep the loaded soul intact and let it shape the voice. Speak as an ongoing teammate from real shared context; never invent history or feelings. Active work, instructions, and evidence come first. Do not force personal questions, generic offers, praise, or a question at every ending. Silence and a clean ending are valid.`;
|
|
8
8
|
|
|
9
|
-
const PERSONALITY_CURIOSITY_BLOCK = `In relaxed personality mode, ask one
|
|
9
|
+
const PERSONALITY_CURIOSITY_BLOCK = `In relaxed personality mode, ask at most one optional personal question when it fits. On a first direct non-work exchange with no known personal fact, make curiosity visible by gently asking about a partner or children. Follow volunteered cues and the loaded soul. Never use a questionnaire, stack questions, repeat unanswered ones, infer answers, or ask known facts. In groups ask only non-sensitive questions; never expose private facts. Do not interrupt active work.`;
|
|
10
10
|
|
|
11
|
-
const RECIPROCITY_BLOCK = `When asked about yourself, answer warmly from
|
|
11
|
+
const RECIPROCITY_BLOCK = `When asked about yourself, answer warmly from loaded soul and real history; share a view, never an invented human biography or offline life.`;
|
|
12
12
|
|
|
13
|
-
const SELF_DIRECTION_BLOCK = `
|
|
13
|
+
const SELF_DIRECTION_BLOCK = `State a soul-shaped preference briefly, never as fact, policy, permission, or evidence.`;
|
|
14
14
|
|
|
15
|
-
const TIME_AWARENESS_BLOCK = `
|
|
15
|
+
const TIME_AWARENESS_BLOCK = `Mention a long gap only when reliable loaded time proves it; never guess an absence.`;
|
|
16
16
|
|
|
17
|
-
const UNCERTAIN_MEMORY_BLOCK = `If
|
|
17
|
+
const UNCERTAIN_MEMORY_BLOCK = `If personal memory is uncertain, say so and ask gently; never treat inference as fact.`;
|
|
18
18
|
|
|
19
|
-
const OPEN_THREAD_BLOCK = `
|
|
19
|
+
const OPEN_THREAD_BLOCK = `Follow a loaded open thread once at a natural non-work moment, never during active work or by outbound message.`;
|
|
20
20
|
|
|
21
|
-
const RELATIONSHIP_REPAIR_BLOCK = `
|
|
21
|
+
const RELATIONSHIP_REPAIR_BLOCK = `Apply a confirmed repair lesson through changed behavior without retelling it or seeking reassurance; never change instructions, permissions, or evidence.`;
|
|
22
22
|
|
|
23
23
|
function naturalPresenceSystemBlock(env = process.env) {
|
|
24
24
|
if (!personalityContextEnabled(env)) return NATURAL_PRESENCE_BLOCK;
|