blun-king-cli 9.1.437 → 9.1.439

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.
@@ -1,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const { resolveCognitiveEvidenceGroup } = require('./cognitive-effective-view.cjs');
4
+ const {
5
+ buildCognitiveSalienceIndex,
6
+ cognitiveEntityKey,
7
+ } = require('./cognitive-salience-policy.cjs');
4
8
 
5
9
  const FOCUS_DOMAINS = new Set(['self', 'team', 'goal', 'open_thread', 'next_trigger', 'expected_evidence']);
6
10
  const DOMAIN_WEIGHTS = new Map([
@@ -42,7 +46,9 @@ function normalizeScopes(value) {
42
46
  return scopes.every(Boolean) ? [...new Set(scopes)] : null;
43
47
  }
44
48
 
45
- function buildCognitiveFocusProjection(state, { focusScopes, maxItems = 6, maxChars = 900 } = {}) {
49
+ function buildCognitiveFocusSelection(state, {
50
+ focusScopes, maxItems = 6, maxChars = 900, currentSessionId,
51
+ } = {}) {
46
52
  const scopes = normalizeScopes(focusScopes);
47
53
  if (scopes === null || !Number.isSafeInteger(maxItems) || maxItems < 1 || maxItems > 8
48
54
  || !Number.isSafeInteger(maxChars) || maxChars < 320 || maxChars > 1600
@@ -77,6 +83,7 @@ function buildCognitiveFocusProjection(state, { focusScopes, maxItems = 6, maxCh
77
83
  groups.set(groupKey, existing);
78
84
  });
79
85
 
86
+ const salience = buildCognitiveSalienceIndex(state, { currentSessionId });
80
87
  const ranked = [];
81
88
  for (const entries of groups.values()) {
82
89
  entries.sort((left, right) => left.occurredAt - right.occurredAt || left.index - right.index);
@@ -86,7 +93,9 @@ function buildCognitiveFocusProjection(state, { focusScopes, maxItems = 6, maxCh
86
93
  if (!effective) continue;
87
94
  ranked.push({
88
95
  ...effective,
89
- score: DOMAIN_WEIGHTS.get(effective.domain) + effective.confidence * 10,
96
+ salience: salience.get(cognitiveEntityKey(effective)) ?? 1,
97
+ score: DOMAIN_WEIGHTS.get(effective.domain) + effective.confidence * 10
98
+ + (salience.get(cognitiveEntityKey(effective)) ?? 1) * 5,
90
99
  });
91
100
  }
92
101
  ranked.sort((left, right) => right.score - left.score
@@ -103,7 +112,15 @@ function buildCognitiveFocusProjection(state, { focusScopes, maxItems = 6, maxCh
103
112
  while ([`Current durable focus (${lines.length} items):`, ...lines, SAFETY_LINE].join('\n').length > maxChars
104
113
  && lines.length > 0) lines.pop();
105
114
  if (lines.length === 0) return null;
106
- return [`Current durable focus (${lines.length} items):`, ...lines, SAFETY_LINE].join('\n');
115
+ const kept = selected.slice(0, lines.length);
116
+ return {
117
+ text: [`Current durable focus (${lines.length} items):`, ...lines, SAFETY_LINE].join('\n'),
118
+ selected: kept.map((item) => ({ scope: item.scope, domain: item.domain, key: item.key })),
119
+ };
107
120
  }
108
121
 
109
- module.exports = { buildCognitiveFocusProjection };
122
+ function buildCognitiveFocusProjection(state, options) {
123
+ return buildCognitiveFocusSelection(state, options)?.text ?? null;
124
+ }
125
+
126
+ module.exports = { buildCognitiveFocusProjection, buildCognitiveFocusSelection };
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ const SALIENCE_DECAY = 0.05;
6
+ const SALIENCE_FLOOR = 0.1;
7
+ const SALIENCE_REINFORCEMENT = 0.25;
8
+ const FOCUS_DOMAINS = new Set(['self', 'team', 'goal', 'open_thread', 'next_trigger', 'expected_evidence']);
9
+ const ACCESS_KEY_RE = /^focus:access:([a-f0-9]{40})$/u;
10
+ const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
11
+
12
+ function clean(value, max = 128) {
13
+ const text = String(value ?? '').replace(/[\u0000-\u001f\u007f]+/gu, ' ').replace(/\s+/gu, ' ').trim();
14
+ return text && text.length <= max ? text : '';
15
+ }
16
+
17
+ function safeId(value) {
18
+ const text = String(value ?? '').trim();
19
+ return SAFE_ID_RE.test(text) ? text : '';
20
+ }
21
+
22
+ function cognitiveEntity(value) {
23
+ const scope = clean(value?.scope, 128);
24
+ const domain = String(value?.domain ?? '');
25
+ const key = clean(value?.key, 128);
26
+ return scope && scope !== 'runtime' && FOCUS_DOMAINS.has(domain) && key
27
+ ? { scope, domain, key } : null;
28
+ }
29
+
30
+ function cognitiveEntityKey(value) {
31
+ const entity = cognitiveEntity(value);
32
+ return entity === null ? '' : `${entity.scope}\0${entity.domain}\0${entity.key}`;
33
+ }
34
+
35
+ function accessPayload(entity) {
36
+ return JSON.stringify(entity);
37
+ }
38
+
39
+ function accessDigest(entity) {
40
+ return crypto.createHash('sha256').update(accessPayload(entity)).digest('hex').slice(0, 40);
41
+ }
42
+
43
+ function focusAccessObservation(value) {
44
+ const entity = cognitiveEntity(value);
45
+ if (entity === null) {
46
+ const error = new Error('COGNITIVE_SALIENCE_INVALID_ENTITY');
47
+ error.code = 'COGNITIVE_SALIENCE_INVALID_ENTITY';
48
+ throw error;
49
+ }
50
+ return {
51
+ domain: 'world',
52
+ key: `focus:access:${accessDigest(entity)}`,
53
+ value: accessPayload(entity),
54
+ confidence: 1,
55
+ scope: 'runtime',
56
+ };
57
+ }
58
+
59
+ function parseAccess(item) {
60
+ const match = ACCESS_KEY_RE.exec(String(item?.key ?? ''));
61
+ if (match === null || item?.scope !== 'runtime') return null;
62
+ let parsed;
63
+ try { parsed = JSON.parse(String(item?.value ?? '')); } catch { return null; }
64
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
65
+ || Object.keys(parsed).sort().join('\0') !== 'domain\0key\0scope') return null;
66
+ const entity = cognitiveEntity(parsed);
67
+ return entity !== null && accessDigest(entity) === match[1] ? entity : null;
68
+ }
69
+
70
+ function roundedScore(value) {
71
+ return Math.round(Math.max(SALIENCE_FLOOR, Math.min(1, value)) * 1e12) / 1e12;
72
+ }
73
+
74
+ function buildCognitiveSalienceIndex(state, { currentSessionId } = {}) {
75
+ const observations = Array.isArray(state?.observations) ? state.observations : [];
76
+ const currentSession = safeId(currentSessionId);
77
+ const sessions = new Map();
78
+ const modifications = new Map();
79
+ const accessBySession = new Map();
80
+
81
+ for (const item of observations) {
82
+ const occurredAt = Date.parse(String(item?.occurred_at ?? ''));
83
+ const contextId = safeId(item?.source?.context_id);
84
+ const runtime = item?.scope === 'runtime' && item?.source?.provider === 'runtime' && contextId;
85
+ if (runtime && Number.isFinite(occurredAt)) {
86
+ const previous = sessions.get(contextId);
87
+ if (previous === undefined || occurredAt < previous) sessions.set(contextId, occurredAt);
88
+ const access = parseAccess(item);
89
+ if (access !== null) {
90
+ const keys = accessBySession.get(contextId) ?? new Set();
91
+ keys.add(cognitiveEntityKey(access));
92
+ accessBySession.set(contextId, keys);
93
+ }
94
+ }
95
+
96
+ const entityKey = cognitiveEntityKey(item);
97
+ if (!entityKey || !Number.isFinite(occurredAt)) continue;
98
+ const previous = modifications.get(entityKey);
99
+ if (previous === undefined || occurredAt >= previous.occurredAt) {
100
+ modifications.set(entityKey, { occurredAt, contextId });
101
+ }
102
+ }
103
+
104
+ if (currentSession && !sessions.has(currentSession)) {
105
+ const latest = Math.max(0, ...sessions.values());
106
+ sessions.set(currentSession, latest + 1);
107
+ }
108
+ const timeline = [...sessions.entries()]
109
+ .map(([id, startedAt]) => ({ id, startedAt }))
110
+ .sort((left, right) => left.startedAt - right.startedAt || left.id.localeCompare(right.id));
111
+ const index = new Map();
112
+
113
+ for (const [entityKey, modification] of modifications) {
114
+ let score = 1;
115
+ let active = false;
116
+ const knownModificationSession = modification.contextId && sessions.has(modification.contextId)
117
+ ? modification.contextId : '';
118
+ let inferredModificationSession = '';
119
+ if (!knownModificationSession) {
120
+ for (const session of timeline) {
121
+ if (session.startedAt <= modification.occurredAt) inferredModificationSession = session.id;
122
+ else break;
123
+ }
124
+ }
125
+ const modificationSession = knownModificationSession || inferredModificationSession;
126
+
127
+ for (const session of timeline) {
128
+ if (!active) {
129
+ if (modificationSession) {
130
+ if (session.id !== modificationSession) continue;
131
+ active = true;
132
+ } else {
133
+ if (session.startedAt <= modification.occurredAt) continue;
134
+ active = true;
135
+ score = roundedScore(score - SALIENCE_DECAY);
136
+ }
137
+ } else if (session.id !== modificationSession) {
138
+ score = roundedScore(score - SALIENCE_DECAY);
139
+ }
140
+ if (accessBySession.get(session.id)?.has(entityKey)) {
141
+ score = roundedScore(score + SALIENCE_REINFORCEMENT * (1 - score));
142
+ }
143
+ }
144
+ index.set(entityKey, roundedScore(score));
145
+ }
146
+ return index;
147
+ }
148
+
149
+ module.exports = {
150
+ SALIENCE_DECAY,
151
+ SALIENCE_FLOOR,
152
+ SALIENCE_REINFORCEMENT,
153
+ buildCognitiveSalienceIndex,
154
+ cognitiveEntityKey,
155
+ focusAccessObservation,
156
+ };
@@ -6,7 +6,8 @@ const path = require('node:path');
6
6
  const { openCognitiveMemoryAdapter } = require('./cognitive-memory-adapter.cjs');
7
7
  const { loadConfiguredCognitiveMemoryAdapter } = require('./cognitive-memory-provider.cjs');
8
8
  const { buildCognitiveContextProjection } = require('./cognitive-context-projection.cjs');
9
- const { buildCognitiveFocusProjection } = require('./cognitive-focus-projection.cjs');
9
+ const { buildCognitiveFocusSelection } = require('./cognitive-focus-projection.cjs');
10
+ const { cognitiveEntityKey, focusAccessObservation } = require('./cognitive-salience-policy.cjs');
10
11
  const { authorizeAttentionCandidate } = require('./cognitive-attention-runtime.cjs');
11
12
 
12
13
  const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
@@ -472,8 +473,18 @@ function createCognitiveTurnLifecycle({
472
473
  currentTurnId: turnId,
473
474
  currentRuntimeId: runtime,
474
475
  });
475
- const focus = buildCognitiveFocusProjection(state, { focusScopes: input.focusScopes });
476
- return [focus, continuity].filter(Boolean).join('\n\n') || null;
476
+ const focus = buildCognitiveFocusSelection(state, {
477
+ focusScopes: input.focusScopes,
478
+ currentSessionId: runtime,
479
+ });
480
+ if (focus?.selected.length > 0) {
481
+ const selected = [...focus.selected].sort((left, right) => (
482
+ cognitiveEntityKey(left).localeCompare(cognitiveEntityKey(right))
483
+ ));
484
+ const accessKey = digestId('focusaccess', selected.map(cognitiveEntityKey));
485
+ commitStage(`focus-access-${accessKey}`, selected.map(focusAccessObservation));
486
+ }
487
+ return [focus?.text, continuity].filter(Boolean).join('\n\n') || null;
477
488
  }
478
489
 
479
490
  function authorizeAttention(input) {
package/blun.mjs CHANGED
@@ -509441,6 +509441,7 @@ var SessionReplayRenderer = class {
509441
509441
  await this.renderRecords(main);
509442
509442
  this.applyTerminalBackgroundAgentStatuses(main);
509443
509443
  this.host.mergeAllTurnSteps();
509444
+ this.host.requestTranscriptRender();
509444
509445
  return true;
509445
509446
  } catch (error) {
509446
509447
  const message = formatErrorMessage$2(error);
@@ -518754,9 +518755,10 @@ var BlunTUI = class {
518754
518755
  markTranscriptComponent(component, entry);
518755
518756
  this.state.transcriptContainer.addChild(component);
518756
518757
  }
518757
- const trimmed = this.trimTranscriptWindow();
518758
- const merged = this.mergeCurrentTurnSteps();
518759
- if (component || trimmed || merged) this.state.ui.requestRender();
518758
+ const trimmed = component !== null && this.isTurnBoundaryComponent(component) ? this.trimTranscriptWindow() : false;
518759
+ const deferReplayWork = this.state.appState.isReplaying;
518760
+ const merged = deferReplayWork ? false : this.mergeCurrentTurnSteps();
518761
+ if ((component || trimmed || merged) && !deferReplayWork) this.state.ui.requestRender();
518760
518762
  }
518761
518763
  appendApprovalTranscriptEntry(request, response) {
518762
518764
  if (request.toolName === "ExitPlanMode" || request.display.kind === "plan_review" || request.display.kind === "goal_start") return;
@@ -518968,6 +518970,9 @@ var BlunTUI = class {
518968
518970
  for (const child of toDispose) if (hasDispose(child)) child.dispose();
518969
518971
  children.splice(0, children.length, ...newChildren);
518970
518972
  }
518973
+ requestTranscriptRender() {
518974
+ this.state.ui.requestRender();
518975
+ }
518971
518976
  showStatus(message, color) {
518972
518977
  this.telegramRemoteCommandContext?.responses.push(message);
518973
518978
  this.state.transcriptContainer.addChild(new StatusMessageComponent(message, color));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.437",
3
+ "version": "9.1.439",
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": {