blun-king-cli 9.1.225 → 9.1.227

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.
@@ -12,7 +12,9 @@ const TOOL_SEARCH_RELATED_TERM_GROUPS = Object.freeze([
12
12
  'current', 'inbox', 'latest', 'message', 'messages', 'nachricht', 'nachrichten', 'queue', 'queued',
13
13
  'recent', 'update', 'updates',
14
14
  ]),
15
- Object.freeze(['history', 'log', 'logs', 'protokoll', 'transcript', 'verlauf']),
15
+ Object.freeze([
16
+ 'chat', 'conversation', 'history', 'log', 'logs', 'processed', 'protokoll', 'transcript', 'verlauf',
17
+ ]),
16
18
  Object.freeze([
17
19
  'fact', 'facts', 'fakt', 'fakten', 'memory', 'memories', 'merk', 'merken', 'note', 'notes',
18
20
  'persist', 'persistent', 'profile', 'remember', 'save', 'speichern', 'store',
@@ -20,7 +22,7 @@ const TOOL_SEARCH_RELATED_TERM_GROUPS = Object.freeze([
20
22
  ]);
21
23
  const TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS = new Set([
22
24
  'fact', 'facts', 'fakt', 'fakten', 'memory', 'memories', 'merk', 'merken', 'note', 'notes',
23
- 'persist', 'persistent', 'profile', 'remember', 'save', 'speichern', 'store',
25
+ 'persist', 'persistent', 'profile', 'remember', 'save', 'speichern', 'store', 'transcript',
24
26
  ]);
25
27
  const CORE_TOOL_NAMES = Object.freeze([
26
28
  'Bash',
@@ -129,18 +131,26 @@ function relatedSearchTerms(token) {
129
131
  return group || [token];
130
132
  }
131
133
 
132
- function searchRelatedDeferredTools(tools, query) {
134
+ function searchRelatedDeferredTools(tools, query, options = {}) {
133
135
  const normalized = String(query || '').trim().toLowerCase();
134
136
  if (!normalized || normalized.startsWith('select:')) return [];
135
137
  const tokens = normalized.split(/\s+/).filter(Boolean);
136
- const requiredNameTokens = tokens.filter((token) => token.startsWith('+')).map((token) => token.slice(1)).filter(Boolean);
137
- const searchTokens = tokens
138
- .filter((token) => !token.startsWith('+'))
138
+ const requestedNameTokens = tokens
139
+ .filter((token) => token.startsWith('+'))
140
+ .map((token) => token.slice(1))
141
+ .filter(Boolean);
142
+ const relaxRequiredNameTokens = options.relaxRequiredNameTokens === true
143
+ && requestedNameTokens.length > 0;
144
+ const requiredNameTokens = relaxRequiredNameTokens ? [] : requestedNameTokens;
145
+ const searchTokens = [
146
+ ...tokens.filter((token) => !token.startsWith('+')),
147
+ ...(relaxRequiredNameTokens ? requestedNameTokens : []),
148
+ ]
139
149
  .filter((token) => !TOOL_SEARCH_INTENT_WORDS.has(token));
140
150
  if (searchTokens.length < 2 && !TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS.has(searchTokens[0])) return [];
141
151
  const minimumMatchedTokens = searchTokens.length === 1 ? 1 : Math.max(2, Math.ceil(searchTokens.length * 0.75));
142
152
 
143
- return tools.map((tool) => {
153
+ const ranked = tools.map((tool) => {
144
154
  const name = String(tool?.name || '').toLowerCase();
145
155
  const description = String(tool?.description || '').toLowerCase();
146
156
  if (!requiredNameTokens.every((token) => name.includes(token))) return null;
@@ -171,7 +181,14 @@ function searchRelatedDeferredTools(tools, query) {
171
181
  right.matchedTokens - left.matchedTokens
172
182
  || right.score - left.score
173
183
  || String(left.tool.name).localeCompare(String(right.tool.name))
174
- )).slice(0, 5).map((entry) => entry.tool);
184
+ ));
185
+ if (relaxRequiredNameTokens && ranked.length > 0) {
186
+ const best = ranked[0];
187
+ return ranked.filter((entry) => (
188
+ entry.matchedTokens === best.matchedTokens && entry.score === best.score
189
+ )).slice(0, 5).map((entry) => entry.tool);
190
+ }
191
+ return ranked.slice(0, 5).map((entry) => entry.tool);
175
192
  }
176
193
 
177
194
  function rememberLoadedTool(loadedToolNames, name) {
@@ -223,6 +240,12 @@ function createDeferredToolLoader(selectedTools, deferredTools, loadedToolNames
223
240
  matches = searchRelatedDeferredTools([...available.values()], query);
224
241
  relatedFallback = matches.length > 0;
225
242
  }
243
+ if (matches.length === 0) {
244
+ matches = searchRelatedDeferredTools([...available.values()], query, {
245
+ relaxRequiredNameTokens: true,
246
+ });
247
+ relatedFallback = matches.length > 0;
248
+ }
226
249
  if (matches.length === 0) {
227
250
  const alreadyLoaded = query.toLowerCase().startsWith('select:')
228
251
  ? searchDeferredTools(
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const CANDIDATE_ID = /^vl-[a-f0-9]{32}$/iu;
4
+ const COMMAND_HASH = /^[a-f0-9]{64}$/iu;
5
+ const TOOL_NAME = /^[a-z0-9._ -]{1,64}$/iu;
6
+
7
+ function safeLatestCandidate(candidate) {
8
+ const id = typeof candidate?.id === 'string' && CANDIDATE_ID.test(candidate.id)
9
+ ? candidate.id
10
+ : 'unknown';
11
+ const toolName = typeof candidate?.verification?.toolName === 'string'
12
+ && TOOL_NAME.test(candidate.verification.toolName)
13
+ ? candidate.verification.toolName
14
+ : 'unknown';
15
+ const commandHash = typeof candidate?.verification?.commandHash === 'string'
16
+ && COMMAND_HASH.test(candidate.verification.commandHash)
17
+ ? candidate.verification.commandHash.slice(0, 12)
18
+ : 'unknown';
19
+ return { id, toolName, commandHash };
20
+ }
21
+
22
+ function buildValidatedLearningInsight(candidates) {
23
+ const insight = {
24
+ pending: 0,
25
+ recorded: 0,
26
+ expired: 0,
27
+ latestPending: null,
28
+ };
29
+ let latestCandidate = null;
30
+ let latestTime = Number.NEGATIVE_INFINITY;
31
+
32
+ for (const candidate of Array.isArray(candidates) ? candidates : []) {
33
+ if (!candidate || typeof candidate !== 'object') continue;
34
+ if (candidate.status === 'recorded') {
35
+ insight.recorded += 1;
36
+ continue;
37
+ }
38
+ if (candidate.status === 'expired') {
39
+ insight.expired += 1;
40
+ continue;
41
+ }
42
+ if (candidate.status !== 'pending') continue;
43
+
44
+ insight.pending += 1;
45
+ const candidateTime = Date.parse(candidate.createdAt);
46
+ if (latestCandidate === null || (Number.isFinite(candidateTime) && candidateTime >= latestTime)) {
47
+ latestCandidate = candidate;
48
+ latestTime = Number.isFinite(candidateTime) ? candidateTime : latestTime;
49
+ }
50
+ }
51
+
52
+ if (latestCandidate !== null) insight.latestPending = safeLatestCandidate(latestCandidate);
53
+ return insight;
54
+ }
55
+
56
+ module.exports = {
57
+ buildValidatedLearningInsight,
58
+ };
package/blun.mjs CHANGED
@@ -340810,7 +340810,13 @@ registerUiCatalogFragment({
340810
340810
  en: {
340811
340811
  "command.update.description": "Configure updates",
340812
340812
  "command.premortem.description": "Stress-test a plan before execution",
340813
+ "command.improve.description": "Show validated learning status",
340813
340814
  "premortem.usage": "Usage: /premortem <plan or decision>",
340815
+ "learning.panel.title": "Validated learning",
340816
+ "learning.counts": "Pending: {pending} · Recorded: {recorded} · Expired: {expired}",
340817
+ "learning.latest": "Latest candidate",
340818
+ "learning.verification": "Verification",
340819
+ "learning.empty": "No validated learning candidates yet.",
340814
340820
  "update.title": "Update behavior",
340815
340821
  "update.hint": "↑↓ navigate · Enter select · Esc cancel",
340816
340822
  "update.manual.label": "Manual updates",
@@ -340827,7 +340833,13 @@ registerUiCatalogFragment({
340827
340833
  de: {
340828
340834
  "command.update.description": "Updates konfigurieren",
340829
340835
  "command.premortem.description": "Einen Plan vor der Umsetzung auf Schwachstellen prüfen",
340836
+ "command.improve.description": "Status des validierten Lernens anzeigen",
340830
340837
  "premortem.usage": "Verwendung: /premortem <Plan oder Entscheidung>",
340838
+ "learning.panel.title": "Validiertes Lernen",
340839
+ "learning.counts": "Offen: {pending} · Übernommen: {recorded} · Abgelaufen: {expired}",
340840
+ "learning.latest": "Neuester Kandidat",
340841
+ "learning.verification": "Prüfung",
340842
+ "learning.empty": "Noch keine validierten Lernkandidaten vorhanden.",
340831
340843
  "update.title": "Update-Verhalten",
340832
340844
  "update.hint": "↑↓ navigieren · Enter auswählen · Esc abbrechen",
340833
340845
  "update.manual.label": "Updates von Hand",
@@ -340844,7 +340856,13 @@ registerUiCatalogFragment({
340844
340856
  es: {
340845
340857
  "command.update.description": "Configurar actualizaciones",
340846
340858
  "command.premortem.description": "Someter un plan a una prueba de estrés antes de ejecutarlo",
340859
+ "command.improve.description": "Mostrar el estado del aprendizaje validado",
340847
340860
  "premortem.usage": "Uso: /premortem <plan o decisión>",
340861
+ "learning.panel.title": "Aprendizaje validado",
340862
+ "learning.counts": "Pendientes: {pending} · Registrados: {recorded} · Caducados: {expired}",
340863
+ "learning.latest": "Candidato más reciente",
340864
+ "learning.verification": "Verificación",
340865
+ "learning.empty": "Todavía no hay candidatos de aprendizaje validados.",
340848
340866
  "update.title": "Comportamiento de las actualizaciones",
340849
340867
  "update.hint": "↑↓ navegar · Enter seleccionar · Esc cancelar",
340850
340868
  "update.manual.label": "Actualizaciones manuales",
@@ -340861,7 +340879,13 @@ registerUiCatalogFragment({
340861
340879
  fr: {
340862
340880
  "command.update.description": "Configurer les mises à jour",
340863
340881
  "command.premortem.description": "Mettre un plan à l’épreuve avant son exécution",
340882
+ "command.improve.description": "Afficher l’état de l’apprentissage validé",
340864
340883
  "premortem.usage": "Utilisation : /premortem <plan ou décision>",
340884
+ "learning.panel.title": "Apprentissage validé",
340885
+ "learning.counts": "En attente : {pending} · Enregistrés : {recorded} · Expirés : {expired}",
340886
+ "learning.latest": "Candidat le plus récent",
340887
+ "learning.verification": "Vérification",
340888
+ "learning.empty": "Aucun candidat d’apprentissage validé pour le moment.",
340865
340889
  "update.title": "Comportement des mises à jour",
340866
340890
  "update.hint": "↑↓ naviguer · Entrée sélectionner · Échap annuler",
340867
340891
  "update.manual.label": "Mises à jour manuelles",
@@ -340878,7 +340902,13 @@ registerUiCatalogFragment({
340878
340902
  sv: {
340879
340903
  "command.update.description": "Konfigurera uppdateringar",
340880
340904
  "command.premortem.description": "Stresstesta en plan före genomförandet",
340905
+ "command.improve.description": "Visa status för validerat lärande",
340881
340906
  "premortem.usage": "Användning: /premortem <plan eller beslut>",
340907
+ "learning.panel.title": "Validerat lärande",
340908
+ "learning.counts": "Väntande: {pending} · Registrerade: {recorded} · Utgångna: {expired}",
340909
+ "learning.latest": "Senaste kandidaten",
340910
+ "learning.verification": "Verifiering",
340911
+ "learning.empty": "Det finns inga validerade inlärningskandidater ännu.",
340882
340912
  "update.title": "Uppdateringsbeteende",
340883
340913
  "update.hint": "↑↓ navigera · Enter välj · Esc avbryt",
340884
340914
  "update.manual.label": "Manuella uppdateringar",
@@ -340895,7 +340925,13 @@ registerUiCatalogFragment({
340895
340925
  cs: {
340896
340926
  "command.update.description": "Nastavit aktualizace",
340897
340927
  "command.premortem.description": "Prověřit plán před provedením zátěžovým testem",
340928
+ "command.improve.description": "Zobrazit stav ověřeného učení",
340898
340929
  "premortem.usage": "Použití: /premortem <plán nebo rozhodnutí>",
340930
+ "learning.panel.title": "Ověřené učení",
340931
+ "learning.counts": "Čekající: {pending} · Zaznamenané: {recorded} · Prošlé: {expired}",
340932
+ "learning.latest": "Nejnovější kandidát",
340933
+ "learning.verification": "Ověření",
340934
+ "learning.empty": "Zatím nejsou k dispozici žádní kandidáti ověřeného učení.",
340899
340935
  "update.title": "Chování aktualizací",
340900
340936
  "update.hint": "↑↓ navigace · Enter vybrat · Esc zrušit",
340901
340937
  "update.manual.label": "Ruční aktualizace",
@@ -403162,6 +403198,13 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
403162
403198
  priority: 65,
403163
403199
  availability: "always"
403164
403200
  },
403201
+ {
403202
+ name: "improve",
403203
+ aliases: ["learning"],
403204
+ descriptionKey: "command.improve.description",
403205
+ priority: 64,
403206
+ availability: "always"
403207
+ },
403165
403208
  {
403166
403209
  name: "status",
403167
403210
  aliases: [],
@@ -416901,6 +416944,8 @@ function buildManagedUsageReportLines(options) {
416901
416944
  var { buildApprovalRejectionStop } = createRequire(import.meta.url)("./bin/approval-rejection-stop.cjs");
416902
416945
  var { reconcileContextBudget } = createRequire(import.meta.url)("./bin/context-budget-ledger.cjs");
416903
416946
  var { buildContextInsight } = createRequire(import.meta.url)("./bin/context-insight-policy.cjs");
416947
+ var { buildValidatedLearningInsight } = createRequire(import.meta.url)("./bin/validated-learning-insight-policy.cjs");
416948
+ var { readValidatedLearningCandidates } = createRequire(import.meta.url)("./bin/validated-learning-signal.cjs");
416904
416949
  function buildUsageReportLines(options) {
416905
416950
  const accent = (text) => currentTheme.boldFg("primary", text);
416906
416951
  const value = (text) => currentTheme.fg("text", text);
@@ -417962,6 +418007,29 @@ function buildContextInsightLines(insight) {
417962
418007
  ];
417963
418008
  return lines;
417964
418009
  }
418010
+ function buildValidatedLearningInsightLines(insight) {
418011
+ const value = (text) => currentTheme.fg("text", text);
418012
+ const muted = (text) => currentTheme.fg("textDim", text);
418013
+ const lines = [
418014
+ ` ${value(uiText("learning.counts", insight))}`
418015
+ ];
418016
+ if (insight.latestPending === null) {
418017
+ lines.push(` ${muted(uiText("learning.empty"))}`);
418018
+ return lines;
418019
+ }
418020
+ lines.push(` ${muted(`${uiText("learning.latest")}:`)} ${value(insight.latestPending.id)}`);
418021
+ lines.push(` ${muted(`${uiText("learning.verification")}:`)} ${value(`${insight.latestPending.toolName} · ${insight.latestPending.commandHash}`)}`);
418022
+ return lines;
418023
+ }
418024
+ function showValidatedLearningInsight(host) {
418025
+ const homeDir = process.env["BLUN_SHARED_HOME"] ?? process.env["BLUN_HOME"] ?? "";
418026
+ const profile = process.env["BLUN_PROFILE"] ?? "default";
418027
+ const candidates = readValidatedLearningCandidates({ homeDir, profile });
418028
+ const insight = buildValidatedLearningInsight(candidates);
418029
+ const panel = new UsagePanelComponent(() => buildValidatedLearningInsightLines(insight), "primary", uiText("learning.panel.title"));
418030
+ host.state.transcriptContainer.addChild(panel);
418031
+ host.state.ui.requestRender();
418032
+ }
417965
418033
  async function showContextInsight(host) {
417966
418034
  const insight = buildContextInsight(await host.requireSession().getContext());
417967
418035
  const panel = new UsagePanelComponent(() => buildContextInsightLines(insight), "primary", uiText("usage.context.title"));
@@ -495178,6 +495246,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
495178
495246
  case "premortem":
495179
495247
  await handlePremortemCommand(host, args);
495180
495248
  return;
495249
+ case "improve":
495250
+ showValidatedLearningInsight(host);
495251
+ return;
495181
495252
  case "context":
495182
495253
  await showContextInsight(host);
495183
495254
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.225",
3
+ "version": "9.1.227",
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": {