blun-king-cli 9.1.362 → 9.1.364

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.
@@ -14,8 +14,12 @@ function compareVariants(left, right) {
14
14
 
15
15
  function resolveCognitiveEvidenceGroup(entries) {
16
16
  if (!Array.isArray(entries) || entries.length === 0) return null;
17
- const superseded = new Set(entries.map((item) => item.supersedes).filter(Boolean));
18
- const active = entries.filter((item) => !superseded.has(item.observationId));
17
+ const withdrawals = entries.filter((item) => Boolean(item.withdraws));
18
+ const deletionCutoff = withdrawals.length > 0
19
+ ? Math.max(...withdrawals.map((item) => item.occurredAt)) : Number.NEGATIVE_INFINITY;
20
+ const evidence = entries.filter((item) => !item.withdraws && item.occurredAt > deletionCutoff);
21
+ const superseded = new Set(evidence.map((item) => item.supersedes).filter(Boolean));
22
+ const active = evidence.filter((item) => !superseded.has(item.observationId));
19
23
  if (active.length === 0) return null;
20
24
  const ordered = [...active].sort((left, right) => left.occurredAt - right.occurredAt
21
25
  || left.index - right.index);
@@ -49,6 +49,8 @@ function buildCognitiveFocusProjection(state, { focusScopes, maxItems = 6, maxCh
49
49
  const observationId = clean(item?.observation_id, 128);
50
50
  const supersedes = item?.supersedes === null || item?.supersedes === undefined
51
51
  ? null : clean(item.supersedes, 128);
52
+ const withdraws = item?.withdraws === null || item?.withdraws === undefined
53
+ ? null : clean(item.withdraws, 128);
52
54
  const confidence = Number(item?.confidence);
53
55
  const occurredAt = Date.parse(String(item?.occurred_at ?? ''));
54
56
  if (!FOCUS_DOMAINS.has(domain) || !key || !value || !scope || scope === 'runtime'
@@ -56,7 +58,7 @@ function buildCognitiveFocusProjection(state, { focusScopes, maxItems = 6, maxCh
56
58
  || !Number.isFinite(occurredAt)) return;
57
59
  const groupKey = `${scope}\0${domain}\0${key}`;
58
60
  const existing = groups.get(groupKey) ?? [];
59
- existing.push({ domain, key, value, scope, observationId, supersedes, confidence, occurredAt, index });
61
+ existing.push({ domain, key, value, scope, observationId, supersedes, withdraws, confidence, occurredAt, index });
60
62
  groups.set(groupKey, existing);
61
63
  });
62
64
 
@@ -52,7 +52,7 @@ function normalizeSource(source) {
52
52
  }
53
53
 
54
54
  function normalizeObservation(value) {
55
- const allowed = new Set(['observation_id', 'domain', 'key', 'value', 'confidence', 'scope', 'supersedes']);
55
+ const allowed = new Set(['observation_id', 'domain', 'key', 'value', 'confidence', 'scope', 'supersedes', 'withdraws']);
56
56
  if (!exactKeys(value, allowed) || hasForbiddenKey(value)) fail('COGNITIVE_FORBIDDEN_FIELD');
57
57
  const observation = {
58
58
  observation_id: safeId(value.observation_id),
@@ -72,6 +72,13 @@ function normalizeObservation(value) {
72
72
  observation.supersedes = safeId(value.supersedes);
73
73
  if (!observation.supersedes || observation.confidence !== 1) fail('COGNITIVE_INVALID_CORRECTION');
74
74
  }
75
+ if (value.withdraws !== undefined && value.withdraws !== null) {
76
+ observation.withdraws = safeId(value.withdraws);
77
+ if (!observation.withdraws || observation.confidence !== 1 || observation.value !== 'withdrawn') {
78
+ fail('COGNITIVE_INVALID_WITHDRAWAL');
79
+ }
80
+ }
81
+ if (observation.supersedes && observation.withdraws) fail('COGNITIVE_INVALID_EVENT');
75
82
  return observation;
76
83
  }
77
84
 
@@ -148,7 +155,8 @@ function initialize(db) {
148
155
  scope TEXT NOT NULL,
149
156
  source_json TEXT NOT NULL,
150
157
  occurred_at TEXT NOT NULL,
151
- supersedes_observation_id TEXT
158
+ supersedes_observation_id TEXT,
159
+ withdraws_observation_id TEXT
152
160
  );
153
161
  CREATE INDEX IF NOT EXISTS cognitive_observations_stream
154
162
  ON cognitive_observations (tenant_id, agent_id, occurred_at, observation_id);
@@ -176,6 +184,9 @@ function initialize(db) {
176
184
  if (!observationColumns.some((column) => column.name === 'supersedes_observation_id')) {
177
185
  db.exec('ALTER TABLE cognitive_observations ADD COLUMN supersedes_observation_id TEXT');
178
186
  }
187
+ if (!observationColumns.some((column) => column.name === 'withdraws_observation_id')) {
188
+ db.exec('ALTER TABLE cognitive_observations ADD COLUMN withdraws_observation_id TEXT');
189
+ }
179
190
  }
180
191
 
181
192
  function openCognitiveStateStore({ home } = {}) {
@@ -207,8 +218,8 @@ function openCognitiveStateStore({ home } = {}) {
207
218
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
208
219
  .run(normalized.event_id, normalized.tenant_id, normalized.agent_id, actualVersion, newVersion, normalized.occurred_at, payload, previousHash, eventHash);
209
220
  const insertObservation = db.prepare(`INSERT INTO cognitive_observations
210
- (observation_id, event_id, tenant_id, agent_id, domain, fact_key, value_text, confidence, scope, source_json, occurred_at, supersedes_observation_id)
211
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
221
+ (observation_id, event_id, tenant_id, agent_id, domain, fact_key, value_text, confidence, scope, source_json, occurred_at, supersedes_observation_id, withdraws_observation_id)
222
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
212
223
  const sourceJson = JSON.stringify(normalized.source);
213
224
  for (const observation of normalized.observations) {
214
225
  if (observation.supersedes) {
@@ -218,9 +229,16 @@ function openCognitiveStateStore({ home } = {}) {
218
229
  || target.domain !== observation.domain || target.fact_key !== observation.key
219
230
  || target.scope !== observation.scope) fail('COGNITIVE_INVALID_CORRECTION');
220
231
  }
232
+ if (observation.withdraws) {
233
+ const target = db.prepare(`SELECT tenant_id, agent_id, domain, fact_key, scope
234
+ FROM cognitive_observations WHERE observation_id = ?`).get(observation.withdraws);
235
+ if (!target || target.tenant_id !== normalized.tenant_id || target.agent_id !== normalized.agent_id
236
+ || target.domain !== observation.domain || target.fact_key !== observation.key
237
+ || target.scope !== observation.scope) fail('COGNITIVE_INVALID_WITHDRAWAL');
238
+ }
221
239
  insertObservation.run(observation.observation_id, normalized.event_id, normalized.tenant_id, normalized.agent_id,
222
240
  observation.domain, observation.key, observation.value, observation.confidence, observation.scope, sourceJson,
223
- normalized.occurred_at, observation.supersedes ?? null);
241
+ normalized.occurred_at, observation.supersedes ?? null, observation.withdraws ?? null);
224
242
  }
225
243
  db.prepare(`INSERT INTO cognitive_streams (tenant_id, agent_id, version, updated_at) VALUES (?, ?, ?, ?)
226
244
  ON CONFLICT(tenant_id, agent_id) DO UPDATE SET version = excluded.version, updated_at = excluded.updated_at`)
@@ -239,7 +257,7 @@ function openCognitiveStateStore({ home } = {}) {
239
257
  if (!tenant || !agent) fail('COGNITIVE_INVALID_EVENT');
240
258
  const stream = db.prepare('SELECT version, updated_at FROM cognitive_streams WHERE tenant_id = ? AND agent_id = ?').get(tenant, agent);
241
259
  const rows = db.prepare(`SELECT domain, fact_key, value_text, confidence, scope, source_json, occurred_at, observation_id,
242
- supersedes_observation_id
260
+ supersedes_observation_id, withdraws_observation_id
243
261
  FROM cognitive_observations WHERE tenant_id = ? AND agent_id = ? ORDER BY occurred_at, rowid`).all(tenant, agent);
244
262
  return {
245
263
  version: Number(stream?.version ?? 0),
@@ -254,6 +272,7 @@ function openCognitiveStateStore({ home } = {}) {
254
272
  source: JSON.parse(row.source_json),
255
273
  occurred_at: row.occurred_at,
256
274
  supersedes: row.supersedes_observation_id ?? null,
275
+ withdraws: row.withdraws_observation_id ?? null,
257
276
  })),
258
277
  };
259
278
  }
@@ -230,6 +230,94 @@ function createCognitiveTurnLifecycle({ home, tenantId, agentId, runtimeId, now
230
230
  return commitDurableStage(`focus-${input.snapshotId}`, observations);
231
231
  }
232
232
 
233
+ function commitExplicitFocusRevision(kind, input) {
234
+ const correction = kind === 'correction';
235
+ const allowed = correction
236
+ ? new Set(['requestId', 'targetObservationId', 'domain', 'key', 'value', 'scope', 'authority', 'confirmation', 'occurredAt', 'source'])
237
+ : new Set(['requestId', 'targetObservationId', 'domain', 'key', 'scope', 'authority', 'confirmation', 'occurredAt', 'source']);
238
+ if (!exactKeys(input, allowed)) fail('COGNITIVE_REVISION_INVALID');
239
+ const requestId = safeId(input.requestId);
240
+ const targetObservationId = safeId(input.targetObservationId);
241
+ const domain = String(input.domain ?? '');
242
+ const key = cleanLabel(input.key, 128);
243
+ const scope = cleanLabel(input.scope, 128);
244
+ const occurredAt = String(input.occurredAt ?? '').trim();
245
+ const source = input.source;
246
+ if (input.authority !== 'runtime_user_prompt_hook' || input.confirmation !== 'explicit_user_request') {
247
+ fail('COGNITIVE_REVISION_EXPLICIT_USER_REQUIRED');
248
+ }
249
+ if (!requestId || !targetObservationId || !FOCUS_DOMAINS.has(domain) || !key
250
+ || AUTHORITY_KEY_RE.test(key) || !scope || scope === 'runtime'
251
+ || Number.isNaN(Date.parse(occurredAt))
252
+ || !exactKeys(source, new Set(['provider', 'actorId', 'contextId', 'messageId']))) {
253
+ fail('COGNITIVE_REVISION_INVALID');
254
+ }
255
+ const normalizedSource = {
256
+ provider: safeId(source.provider),
257
+ actor_id: safeId(source.actorId),
258
+ context_id: safeId(source.contextId),
259
+ message_id: safeId(source.messageId),
260
+ };
261
+ if (Object.values(normalizedSource).some((value) => !value)) fail('COGNITIVE_REVISION_INVALID');
262
+ const value = correction ? cleanLabel(input.value, 512) : 'withdrawn';
263
+ if (!value) fail('COGNITIVE_REVISION_INVALID');
264
+ const eventId = digestId('revision', [tenant, agent, kind, requestId]);
265
+ const observationId = digestId('revisionobs', [eventId]);
266
+ const observation = {
267
+ observation_id: observationId,
268
+ domain,
269
+ key,
270
+ value,
271
+ confidence: 1,
272
+ scope,
273
+ ...(correction ? { supersedes: targetObservationId } : { withdraws: targetObservationId }),
274
+ };
275
+ const normalizedOccurredAt = new Date(occurredAt).toISOString();
276
+ for (let attempt = 0; attempt < 4; attempt += 1) {
277
+ const current = store.read({ tenantId: tenant, agentId: agent });
278
+ const existing = current.observations.find((item) => item.observation_id === observationId);
279
+ if (existing) {
280
+ const matches = existing.domain === observation.domain && existing.key === observation.key
281
+ && existing.value === observation.value && existing.confidence === 1
282
+ && existing.scope === observation.scope && existing.occurred_at === normalizedOccurredAt
283
+ && existing.supersedes === (observation.supersedes ?? null)
284
+ && existing.withdraws === (observation.withdraws ?? null)
285
+ && existing.source.provider === normalizedSource.provider
286
+ && existing.source.actor_id === normalizedSource.actor_id
287
+ && existing.source.context_id === normalizedSource.context_id
288
+ && existing.source.message_id === normalizedSource.message_id;
289
+ if (!matches) fail('COGNITIVE_REVISION_REQUEST_REUSE');
290
+ return { version: current.version, idempotent: true };
291
+ }
292
+ const target = current.observations.find((item) => item.observation_id === targetObservationId);
293
+ if (target && Date.parse(normalizedOccurredAt) < Date.parse(target.occurred_at)) {
294
+ fail('COGNITIVE_REVISION_TIME_ORDER');
295
+ }
296
+ try {
297
+ return store.commit({
298
+ tenantId: tenant,
299
+ agentId: agent,
300
+ eventId,
301
+ expectedVersion: current.version,
302
+ occurredAt: normalizedOccurredAt,
303
+ source: normalizedSource,
304
+ observations: [observation],
305
+ });
306
+ } catch (error) {
307
+ if (error?.code !== 'COGNITIVE_VERSION_CONFLICT' || attempt === 3) throw error;
308
+ }
309
+ }
310
+ fail('COGNITIVE_VERSION_CONFLICT');
311
+ }
312
+
313
+ function correctFocusObservation(input) {
314
+ return commitExplicitFocusRevision('correction', input);
315
+ }
316
+
317
+ function withdrawFocusObservation(input) {
318
+ return commitExplicitFocusRevision('withdrawal', input);
319
+ }
320
+
233
321
  function toolFields(input, allowedKeys) {
234
322
  if (!exactKeys(input, allowedKeys)) fail('COGNITIVE_LIFECYCLE_INVALID');
235
323
  const turnId = safeTurnId(input.turnId);
@@ -337,6 +425,8 @@ function createCognitiveTurnLifecycle({ home, tenantId, agentId, runtimeId, now
337
425
  recordToolResult,
338
426
  recordToolBatch,
339
427
  recordFocusSnapshot,
428
+ correctFocusObservation,
429
+ withdrawFocusObservation,
340
430
  authorizeAttention,
341
431
  projectForTurn,
342
432
  endTurn,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.362",
3
+ "version": "9.1.364",
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": {