blun-king-cli 9.1.275 → 9.1.276

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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.275",
3
+ "version": "9.1.276",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {