blun-king-cli 9.1.388 → 9.1.390

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.
@@ -2,8 +2,9 @@
2
2
 
3
3
  const { openCognitiveStateStore } = require('./cognitive-state-store.cjs');
4
4
 
5
- const COGNITIVE_MEMORY_ADAPTER_VERSION = 3;
5
+ const COGNITIVE_MEMORY_ADAPTER_VERSION = 5;
6
6
  const KIND_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
7
+ const RETENTION_CLASSES = new Set(['operational', 'durable', 'relationship', 'audit']);
7
8
  const ACCESS_KEYS = Object.freeze([
8
9
  'actorId', 'agentId', 'channelId', 'conversationId', 'permissionContext',
9
10
  'portalId', 'requestedScope', 'tenantId', 'userId',
@@ -86,12 +87,18 @@ function assertVisibleScope(scope, access) {
86
87
  }
87
88
 
88
89
  function assertCommitAccess(input, access) {
90
+ if (Number.isNaN(Date.parse(String(input?.occurredAt ?? '')))
91
+ || Number.isNaN(Date.parse(String(input?.receivedAt ?? '')))
92
+ || !RETENTION_CLASSES.has(String(input?.retentionClass ?? ''))) {
93
+ fail('COGNITIVE_MEMORY_EVENT_ENVELOPE_INVALID');
94
+ }
89
95
  if (isInternalAgentAccess(access)) return;
90
96
  if (!Array.isArray(input?.observations) || input.observations.length < 1) {
91
97
  fail('COGNITIVE_MEMORY_VISIBILITY_DENIED');
92
98
  }
93
99
  for (const observation of input.observations) assertVisibleScope(observation?.scope, access);
94
100
  if (safeId(input?.source?.provider) !== access.portalId
101
+ || safeId(input?.source?.channel_id) !== access.channelId
95
102
  || safeId(input?.source?.actor_id) !== access.actorId
96
103
  || safeId(input?.source?.context_id) !== access.conversationId) {
97
104
  fail('COGNITIVE_MEMORY_SOURCE_MISMATCH');
@@ -110,6 +110,7 @@ function matchesRevisionReplay(items, target, command, commandSource) {
110
110
  : item.withdraws === target.observationId && item.value === 'withdrawn';
111
111
  return linked
112
112
  && item.source?.provider === commandSource.source.provider
113
+ && item.source?.channel_id === commandSource.source.channelId
113
114
  && item.source?.actor_id === commandSource.source.actorId
114
115
  && item.source?.context_id === commandSource.source.contextId
115
116
  && item.source?.message_id === commandSource.source.messageId
@@ -133,6 +134,7 @@ function normalizeTelegramSource(source) {
133
134
  occurredAt: timestamp,
134
135
  source: {
135
136
  provider: 'telegram',
137
+ channelId: 'direct',
136
138
  actorId: `tg_user_${userId}`,
137
139
  contextId: `tg_chat_${chatId}`,
138
140
  messageId: `tg_message_${messageId}`,
@@ -153,6 +155,7 @@ function localSource({ env, sessionId, occurredAt, nonce }) {
153
155
  occurredAt,
154
156
  source: {
155
157
  provider: 'cli',
158
+ channelId: 'terminal',
156
159
  actorId: digestId('localuser', [actor]),
157
160
  contextId: digestId('clisession', [session]),
158
161
  messageId: digestId('cliinput', [unique]),
@@ -10,6 +10,7 @@ const DOMAINS = new Set(['self', 'world', 'team', 'goal', 'open_thread', 'assump
10
10
  const EPISTEMIC_STATES = new Set([
11
11
  'verified', 'credible_unverified', 'hypothesis', 'uncertain_memory', 'stale', 'unknown', 'legacy_unknown',
12
12
  ]);
13
+ const RETENTION_CLASSES = new Set(['operational', 'durable', 'relationship', 'audit']);
13
14
  const FORBIDDEN_KEY_RE = /(?:^|_)(?:acl|api_key|capability|password|permission|secret|token)(?:_|$)/iu;
14
15
  const MAX_EVENT_BYTES = 64 * 1024;
15
16
  const MAX_OBSERVATIONS = 32;
@@ -42,10 +43,11 @@ function exactKeys(value, keys) {
42
43
  }
43
44
 
44
45
  function normalizeSource(source) {
45
- const allowed = new Set(['provider', 'actor_id', 'context_id', 'message_id']);
46
+ const allowed = new Set(['provider', 'channel_id', 'actor_id', 'context_id', 'message_id']);
46
47
  if (!exactKeys(source, allowed) || hasForbiddenKey(source)) fail('COGNITIVE_FORBIDDEN_FIELD');
47
48
  const normalized = {
48
49
  provider: safeId(source.provider),
50
+ channel_id: safeId(source.channel_id),
49
51
  actor_id: safeId(source.actor_id),
50
52
  context_id: safeId(source.context_id),
51
53
  message_id: safeId(source.message_id),
@@ -89,13 +91,21 @@ function normalizeObservation(value) {
89
91
  }
90
92
 
91
93
  function normalizeEvent(input) {
94
+ const allowed = new Set([
95
+ 'tenantId', 'agentId', 'eventId', 'expectedVersion', 'occurredAt', 'receivedAt',
96
+ 'retentionClass', 'source', 'observations',
97
+ ]);
98
+ if (!exactKeys(input, allowed) || hasForbiddenKey(input)) fail('COGNITIVE_FORBIDDEN_FIELD');
92
99
  const tenantId = safeId(input.tenantId);
93
100
  const agentId = safeId(input.agentId);
94
101
  const eventId = safeId(input.eventId);
95
102
  const expectedVersion = Number(input.expectedVersion);
96
103
  const occurredAt = String(input.occurredAt ?? '').trim();
104
+ const receivedAt = String(input.receivedAt ?? '').trim();
105
+ const retentionClass = String(input.retentionClass ?? '').trim();
97
106
  if (!tenantId || !agentId || !eventId || !Number.isSafeInteger(expectedVersion)
98
107
  || expectedVersion < 0 || Number.isNaN(Date.parse(occurredAt))
108
+ || Number.isNaN(Date.parse(receivedAt)) || !RETENTION_CLASSES.has(retentionClass)
99
109
  || !Array.isArray(input.observations) || input.observations.length < 1
100
110
  || input.observations.length > MAX_OBSERVATIONS) fail('COGNITIVE_INVALID_EVENT');
101
111
  const normalized = {
@@ -103,7 +113,9 @@ function normalizeEvent(input) {
103
113
  agent_id: agentId,
104
114
  event_id: eventId,
105
115
  expected_version: expectedVersion,
106
- occurred_at: occurredAt,
116
+ occurred_at: new Date(occurredAt).toISOString(),
117
+ received_at: new Date(receivedAt).toISOString(),
118
+ retention_class: retentionClass,
107
119
  source: normalizeSource(input.source),
108
120
  observations: input.observations.map(normalizeObservation),
109
121
  };
@@ -145,6 +157,8 @@ function initialize(db) {
145
157
  expected_version INTEGER NOT NULL,
146
158
  new_version INTEGER NOT NULL,
147
159
  occurred_at TEXT NOT NULL,
160
+ received_at TEXT NOT NULL,
161
+ retention_class TEXT NOT NULL,
148
162
  payload_json TEXT NOT NULL,
149
163
  previous_hash TEXT NOT NULL,
150
164
  event_hash TEXT NOT NULL UNIQUE
@@ -162,6 +176,8 @@ function initialize(db) {
162
176
  scope TEXT NOT NULL,
163
177
  source_json TEXT NOT NULL,
164
178
  occurred_at TEXT NOT NULL,
179
+ received_at TEXT NOT NULL,
180
+ retention_class TEXT NOT NULL,
165
181
  supersedes_observation_id TEXT,
166
182
  withdraws_observation_id TEXT
167
183
  );
@@ -199,6 +215,19 @@ function initialize(db) {
199
215
  if (!observationColumns.some((column) => column.name === 'epistemic_state')) {
200
216
  db.exec("ALTER TABLE cognitive_observations ADD COLUMN epistemic_state TEXT NOT NULL DEFAULT 'legacy_unknown'");
201
217
  }
218
+ if (!observationColumns.some((column) => column.name === 'received_at')) {
219
+ db.exec("ALTER TABLE cognitive_observations ADD COLUMN received_at TEXT NOT NULL DEFAULT ''");
220
+ }
221
+ if (!observationColumns.some((column) => column.name === 'retention_class')) {
222
+ db.exec("ALTER TABLE cognitive_observations ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'legacy_unknown'");
223
+ }
224
+ const eventColumns = db.prepare('PRAGMA table_info(cognitive_events)').all();
225
+ if (!eventColumns.some((column) => column.name === 'received_at')) {
226
+ db.exec("ALTER TABLE cognitive_events ADD COLUMN received_at TEXT NOT NULL DEFAULT ''");
227
+ }
228
+ if (!eventColumns.some((column) => column.name === 'retention_class')) {
229
+ db.exec("ALTER TABLE cognitive_events ADD COLUMN retention_class TEXT NOT NULL DEFAULT 'legacy_unknown'");
230
+ }
202
231
  }
203
232
 
204
233
  function openCognitiveStateStore({ home } = {}) {
@@ -226,12 +255,13 @@ function openCognitiveStateStore({ home } = {}) {
226
255
  const eventHash = crypto.createHash('sha256').update(`${previousHash}\0${payload}`).digest('hex');
227
256
  const newVersion = actualVersion + 1;
228
257
  db.prepare(`INSERT INTO cognitive_events
229
- (event_id, tenant_id, agent_id, expected_version, new_version, occurred_at, payload_json, previous_hash, event_hash)
230
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
231
- .run(normalized.event_id, normalized.tenant_id, normalized.agent_id, actualVersion, newVersion, normalized.occurred_at, payload, previousHash, eventHash);
258
+ (event_id, tenant_id, agent_id, expected_version, new_version, occurred_at, received_at, retention_class, payload_json, previous_hash, event_hash)
259
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
260
+ .run(normalized.event_id, normalized.tenant_id, normalized.agent_id, actualVersion, newVersion,
261
+ normalized.occurred_at, normalized.received_at, normalized.retention_class, payload, previousHash, eventHash);
232
262
  const insertObservation = db.prepare(`INSERT INTO cognitive_observations
233
- (observation_id, event_id, tenant_id, agent_id, domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at, supersedes_observation_id, withdraws_observation_id)
234
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
263
+ (observation_id, event_id, tenant_id, agent_id, domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at, received_at, retention_class, supersedes_observation_id, withdraws_observation_id)
264
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
235
265
  const sourceJson = JSON.stringify(normalized.source);
236
266
  for (const observation of normalized.observations) {
237
267
  if (observation.supersedes) {
@@ -251,7 +281,8 @@ function openCognitiveStateStore({ home } = {}) {
251
281
  insertObservation.run(observation.observation_id, normalized.event_id, normalized.tenant_id, normalized.agent_id,
252
282
  observation.domain, observation.key, observation.value, observation.confidence, observation.epistemic_state,
253
283
  observation.scope, sourceJson,
254
- normalized.occurred_at, observation.supersedes ?? null, observation.withdraws ?? null);
284
+ normalized.occurred_at, normalized.received_at, normalized.retention_class,
285
+ observation.supersedes ?? null, observation.withdraws ?? null);
255
286
  }
256
287
  db.prepare(`INSERT INTO cognitive_streams (tenant_id, agent_id, version, updated_at) VALUES (?, ?, ?, ?)
257
288
  ON CONFLICT(tenant_id, agent_id) DO UPDATE SET version = excluded.version, updated_at = excluded.updated_at`)
@@ -272,7 +303,8 @@ function openCognitiveStateStore({ home } = {}) {
272
303
  fail('COGNITIVE_INVALID_EVENT');
273
304
  }
274
305
  const stream = db.prepare('SELECT version, updated_at FROM cognitive_streams WHERE tenant_id = ? AND agent_id = ?').get(tenant, agent);
275
- const select = `SELECT domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at, observation_id,
306
+ const select = `SELECT domain, fact_key, value_text, confidence, epistemic_state, scope, source_json, occurred_at, received_at,
307
+ retention_class, observation_id,
276
308
  supersedes_observation_id, withdraws_observation_id FROM cognitive_observations`;
277
309
  const rows = requestedVisibility && requestedVisibility !== 'agent:memory'
278
310
  ? db.prepare(`${select} WHERE tenant_id = ? AND agent_id = ? AND scope = ? ORDER BY occurred_at, rowid`)
@@ -291,6 +323,8 @@ function openCognitiveStateStore({ home } = {}) {
291
323
  scope: row.scope,
292
324
  source: JSON.parse(row.source_json),
293
325
  occurred_at: row.occurred_at,
326
+ received_at: row.received_at || row.occurred_at,
327
+ retention_class: row.retention_class || 'legacy_unknown',
294
328
  supersedes: row.supersedes_observation_id ?? null,
295
329
  withdraws: row.withdraws_observation_id ?? null,
296
330
  })),
@@ -127,8 +127,11 @@ function createCognitiveTurnLifecycle({
127
127
  eventId,
128
128
  expectedVersion,
129
129
  occurredAt,
130
+ receivedAt: occurredAt,
131
+ retentionClass: 'operational',
130
132
  source: {
131
133
  provider: 'runtime',
134
+ channel_id: 'runtime',
132
135
  actor_id: agent,
133
136
  context_id: runtime,
134
137
  message_id: stageKey,
@@ -175,8 +178,11 @@ function createCognitiveTurnLifecycle({
175
178
  eventId,
176
179
  expectedVersion,
177
180
  occurredAt,
181
+ receivedAt: occurredAt,
182
+ retentionClass: 'durable',
178
183
  source: {
179
184
  provider: 'runtime',
185
+ channel_id: 'runtime',
180
186
  actor_id: agent,
181
187
  context_id: 'durable-focus',
182
188
  message_id: stageKey,
@@ -282,11 +288,12 @@ function createCognitiveTurnLifecycle({
282
288
  if (!requestId || !targetObservationId || !FOCUS_DOMAINS.has(domain) || !key
283
289
  || AUTHORITY_KEY_RE.test(key) || !scope || scope === 'runtime'
284
290
  || Number.isNaN(Date.parse(occurredAt))
285
- || !exactKeys(source, new Set(['provider', 'actorId', 'contextId', 'messageId']))) {
291
+ || !exactKeys(source, new Set(['provider', 'channelId', 'actorId', 'contextId', 'messageId']))) {
286
292
  fail('COGNITIVE_REVISION_INVALID');
287
293
  }
288
294
  const normalizedSource = {
289
295
  provider: safeId(source.provider),
296
+ channel_id: safeId(source.channelId),
290
297
  actor_id: safeId(source.actorId),
291
298
  context_id: safeId(source.contextId),
292
299
  message_id: safeId(source.messageId),
@@ -307,13 +314,14 @@ function createCognitiveTurnLifecycle({
307
314
  ...(correction ? { supersedes: targetObservationId } : { withdraws: targetObservationId }),
308
315
  };
309
316
  const normalizedOccurredAt = new Date(occurredAt).toISOString();
317
+ const receivedAt = stageTime(`revision:${kind}:${requestId}`);
310
318
  const revisionMemoryAccess = {
311
319
  tenantId: tenant,
312
320
  userId: normalizedSource.actor_id,
313
321
  actorId: normalizedSource.actor_id,
314
322
  agentId: agent,
315
323
  portalId: normalizedSource.provider,
316
- channelId: normalizedSource.provider,
324
+ channelId: normalizedSource.channel_id,
317
325
  conversationId: normalizedSource.context_id,
318
326
  requestedScope: scope,
319
327
  permissionContext: {
@@ -332,6 +340,7 @@ function createCognitiveTurnLifecycle({
332
340
  && existing.supersedes === (observation.supersedes ?? null)
333
341
  && existing.withdraws === (observation.withdraws ?? null)
334
342
  && existing.source.provider === normalizedSource.provider
343
+ && existing.source.channel_id === normalizedSource.channel_id
335
344
  && existing.source.actor_id === normalizedSource.actor_id
336
345
  && existing.source.context_id === normalizedSource.context_id
337
346
  && existing.source.message_id === normalizedSource.message_id;
@@ -349,6 +358,8 @@ function createCognitiveTurnLifecycle({
349
358
  eventId,
350
359
  expectedVersion: current.version,
351
360
  occurredAt: normalizedOccurredAt,
361
+ receivedAt,
362
+ retentionClass: 'durable',
352
363
  source: normalizedSource,
353
364
  observations: [observation],
354
365
  }, revisionMemoryAccess);
@@ -462,6 +462,7 @@ function recordChannelIdentity(envelope, env = process.env) {
462
462
  const groupId = chatId.startsWith('-') ? `telegram-chat-${chatId.slice(1)}` : undefined;
463
463
  const displayName = cleanText(meta.user, 120) || actorId;
464
464
  const firstSeenAt = trustedTimestamp(meta.ts);
465
+ const receivedAt = trustedTimestamp(meta.received_at) || firstSeenAt;
465
466
  const created = [];
466
467
  if (createJsonOnce(root, ['actors', `${actorId}.json`], {
467
468
  version: 1,
@@ -517,6 +518,7 @@ function recordChannelIdentity(envelope, env = process.env) {
517
518
  sourceChannel: groupId ? 'group' : 'direct',
518
519
  conversationId: groupId || `telegram-dm-${chatId}`,
519
520
  messageId: safeId(meta.message_id),
521
+ receivedAt,
520
522
  });
521
523
  if (firstSeenAt) recordInteractionActivity(root, agentId, actorId, firstSeenAt);
522
524
  const modelContext = renderIdentityContext({
@@ -9,7 +9,7 @@ const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
9
9
  const SENSITIVITIES = new Set(['low', 'medium']);
10
10
  const INPUT_KEYS = [
11
11
  'actorId', 'agentId', 'conversationId', 'cooldownMs', 'messageId', 'occurredAt',
12
- 'sensitivity', 'sourceChannel', 'sourcePortal', 'tenantId', 'topic',
12
+ 'receivedAt', 'sensitivity', 'sourceChannel', 'sourcePortal', 'tenantId', 'topic',
13
13
  ];
14
14
  const MAX_COOLDOWN_MS = 90 * 24 * 60 * 60 * 1000;
15
15
 
@@ -49,6 +49,7 @@ function normalizePresentation(input) {
49
49
  conversationId: safeId(input.conversationId),
50
50
  messageId: safeId(input.messageId),
51
51
  occurredAt: String(input.occurredAt ?? '').trim(),
52
+ receivedAt: String(input.receivedAt ?? '').trim(),
52
53
  topic: safeId(input.topic),
53
54
  sensitivity: String(input.sensitivity ?? ''),
54
55
  cooldownMs: Number(input.cooldownMs),
@@ -57,11 +58,13 @@ function normalizePresentation(input) {
57
58
  || !normalized.sourcePortal || !normalized.sourceChannel || !normalized.conversationId
58
59
  || !normalized.messageId || !normalized.topic || !SENSITIVITIES.has(normalized.sensitivity)
59
60
  || Number.isNaN(Date.parse(normalized.occurredAt))
61
+ || Number.isNaN(Date.parse(normalized.receivedAt))
60
62
  || !Number.isSafeInteger(normalized.cooldownMs) || normalized.cooldownMs < 1000
61
63
  || normalized.cooldownMs > MAX_COOLDOWN_MS) {
62
64
  fail('PERSONALITY_MEMORY_INVALID_INPUT');
63
65
  }
64
66
  normalized.occurredAt = new Date(normalized.occurredAt).toISOString();
67
+ normalized.receivedAt = new Date(normalized.receivedAt).toISOString();
65
68
  return normalized;
66
69
  }
67
70
 
@@ -138,8 +141,11 @@ function createPersonalityMemoryAdapter({ identityRoot, memoryAdapter } = {}) {
138
141
  eventId,
139
142
  expectedVersion: current.version,
140
143
  occurredAt: value.occurredAt,
144
+ receivedAt: value.receivedAt,
145
+ retentionClass: 'relationship',
141
146
  source: {
142
147
  provider: value.sourcePortal,
148
+ channel_id: value.sourceChannel,
143
149
  actor_id: value.actorId,
144
150
  context_id: value.conversationId,
145
151
  message_id: value.messageId,
@@ -64,11 +64,12 @@ function prepareCuriosityOpportunity(input = {}) {
64
64
  const agentId = safeId(input.agentId);
65
65
  const actorId = safeId(input.actorId);
66
66
  const occurredAt = trustedTimestamp(input.occurredAt);
67
+ const receivedAt = trustedTimestamp(input.receivedAt);
67
68
  const sourcePortal = safeId(input.sourcePortal) || 'telegram';
68
69
  const sourceChannel = safeId(input.sourceChannel) || 'direct';
69
70
  const conversationId = safeId(input.conversationId) || digestId('conversation', [actorId]);
70
71
  const cue = curiosityCue(input.text);
71
- if (!tenantId || !agentId || !actorId || !occurredAt || !cue) return undefined;
72
+ if (!tenantId || !agentId || !actorId || !occurredAt || !receivedAt || !cue) return undefined;
72
73
  const messageId = safeId(input.messageId) || digestId('message', [
73
74
  sourcePortal, sourceChannel, conversationId, actorId, occurredAt, normalizedText(input.text),
74
75
  ]);
@@ -88,6 +89,7 @@ function prepareCuriosityOpportunity(input = {}) {
88
89
  conversationId,
89
90
  messageId,
90
91
  occurredAt,
92
+ receivedAt,
91
93
  topic: cue.topic,
92
94
  sensitivity: cue.sensitivity,
93
95
  cooldownMs: COOLDOWN_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.388",
3
+ "version": "9.1.390",
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": {