blun-king-cli 9.1.132 → 9.1.134

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.
package/LIESMICH.txt CHANGED
@@ -123,6 +123,10 @@ Ab BLUN King 9.1.130 verdichtet die Modellprojektion zusätzlich die erläuternd
123
123
 
124
124
  Ab BLUN King 9.1.132 wird der schlanke Basissystemprompt nach der generierten Initialisierung des Standardprofils wiederhergestellt. Zuvor überschrieb der Initialisierer den vorhandenen Prompt mit 4.850 Zeichen durch einen eingebetteten älteren Prompt mit 23.109 Zeichen. Der zur Laufzeit verwendete Prompt bleibt nun tatsächlich bei 4.850 Zeichen. Das spart pro Modellanfrage 18.259 Zeichen, also etwa 4.565 geschätzte Token. Anweisungen oder Werkzeuge werden nicht entfernt; die Änderung korrigiert ausschließlich die Initialisierungsreihenfolge.
125
125
 
126
+ Ab BLUN King 9.1.133 werden die projektlokale und die gemeinsame `Mistake.md` nicht mehr als zwei getrennte Promptblöcke gesendet. King wählt aus beiden Quellen höchstens fünf relevante vollständige Einträge innerhalb eines gemeinsamen Budgets von 6.000 Zeichen aus. Die vollständigen Dateien, Writer, Archive, Register, der Rohverlauf und Exporte bleiben unverändert. Die Auswahl erfolgt lokal und deterministisch und benötigt keinen zusätzlichen Modell-, Embedding- oder Netzwerkaufruf. Mit Fredriks aktuellen Dateien sinkt die wiederholte Modellsicht um 12.917 bis 16.811 Zeichen, also um etwa 3.230 bis 4.203 geschätzte Token pro Anfrage.
127
+
128
+ Ab BLUN King 9.1.134 begrenzt die Modellprojektion nicht adressierte Telegram-Gruppennachrichten auf eine nachvollziehbare Vorschau von 2.000 Zeichen. Anfang und Ende bleiben erhalten, eine Markierung nennt die ursprüngliche Länge. Direkt an den Agenten gerichtete Nachrichten, die sichtbare Telegram-Historie, der Rohverlauf und Exporte bleiben vollständig. In Fredriks realem Verlauf betraf die Grenze 211 von 517 verschiedenen mitgelesenen Gruppenmeldungen und hätte 102.466 wiederholt übertragene Zeichen eingespart, also rund 25.617 geschätzte Token über die gemessenen Modellanfragen.
129
+
126
130
  Ab BLUN King 9.1.131 entfernt die Modellprojektion zwei ältere Regelkopien aus dem Systemprompt, sobald gleichwertige Regeln im Conduct-Abschnitt vorhanden sind. Betroffen sind ausschließlich die doppelten Hinweise zu Ehrlichkeit und Zugangsdaten; die vollständigen Conduct-Regeln und alle übrigen Prompt-Abschnitte bleiben unverändert. In der ausgelieferten Standardvorlage sinkt der wiederholt gesendete Prompt dadurch um 1.010 Zeichen, also um etwa 253 geschätzte Token pro Modellanfrage. Fehlt eines der Conduct-Gegenstücke, bleibt die entsprechende ältere Regel unverändert erhalten.
127
131
 
128
132
  Ab BLUN King 9.1.118 bleibt der Sitzungszeitstempel im Systemprompt während einer laufenden Sitzung unverändert. Das erneute Einlesen von Verzeichnisübersicht, AGENTS.md-Dateien, Zusatzverzeichnissen und Skillinformationen kann den Prompt weiterhin ändern, wenn sich die jeweiligen Quellen tatsächlich verändert haben. Bleiben diese Quellen gleich, entwertet die Aktualisierung nach einer Verdichtung den Präfix-Cache des Anbieters nicht mehr allein durch einen neuen Zeitstempel. In Fredriks gemessener Sitzung unterschieden sich die beiden letzten Systemprompts mit jeweils 68.631 Zeichen nur in der Zeitangabe, und zwar erst nach einem gemeinsamen Präfix von 27.111 Zeichen. Der geänderte Zeitstempel verhinderte damit die Wiederverwendung der übrigen 41.512 unveränderten Zeichen aus dem Präfix-Cache.
package/README.md CHANGED
@@ -129,6 +129,10 @@ Ab BLUN King 9.1.130 verdichtet die Modellprojektion zusätzlich die erläuternd
129
129
 
130
130
  Ab BLUN King 9.1.132 wird der schlanke Basissystemprompt nach der generierten Initialisierung des Standardprofils wiederhergestellt. Zuvor überschrieb der Initialisierer den vorhandenen Prompt mit 4.850 Zeichen durch einen eingebetteten älteren Prompt mit 23.109 Zeichen. Der zur Laufzeit verwendete Prompt bleibt nun tatsächlich bei 4.850 Zeichen. Das spart pro Modellanfrage 18.259 Zeichen, also etwa 4.565 geschätzte Token. Anweisungen oder Werkzeuge werden nicht entfernt; die Änderung korrigiert ausschließlich die Initialisierungsreihenfolge.
131
131
 
132
+ Ab BLUN King 9.1.133 werden die projektlokale und die gemeinsame `Mistake.md` nicht mehr als zwei getrennte Promptblöcke gesendet. King wählt aus beiden Quellen höchstens fünf relevante vollständige Einträge innerhalb eines gemeinsamen Budgets von 6.000 Zeichen aus. Die vollständigen Dateien, Writer, Archive, Register, der Rohverlauf und Exporte bleiben unverändert. Die Auswahl erfolgt lokal und deterministisch und benötigt keinen zusätzlichen Modell-, Embedding- oder Netzwerkaufruf. Mit Fredriks aktuellen Dateien sinkt die wiederholte Modellsicht um 12.917 bis 16.811 Zeichen, also um etwa 3.230 bis 4.203 geschätzte Token pro Anfrage.
133
+
134
+ Ab BLUN King 9.1.134 begrenzt die Modellprojektion nicht adressierte Telegram-Gruppennachrichten auf eine nachvollziehbare Vorschau von 2.000 Zeichen. Anfang und Ende bleiben erhalten, eine Markierung nennt die ursprüngliche Länge. Direkt an den Agenten gerichtete Nachrichten, die sichtbare Telegram-Historie, der Rohverlauf und Exporte bleiben vollständig. In Fredriks realem Verlauf betraf die Grenze 211 von 517 verschiedenen mitgelesenen Gruppenmeldungen und hätte 102.466 wiederholt übertragene Zeichen eingespart, also rund 25.617 geschätzte Token über die gemessenen Modellanfragen.
135
+
132
136
  Ab BLUN King 9.1.131 entfernt die Modellprojektion zwei ältere Regelkopien aus dem Systemprompt, sobald gleichwertige Regeln im Conduct-Abschnitt vorhanden sind. Betroffen sind ausschließlich die doppelten Hinweise zu Ehrlichkeit und Zugangsdaten; die vollständigen Conduct-Regeln und alle übrigen Prompt-Abschnitte bleiben unverändert. In der ausgelieferten Standardvorlage sinkt der wiederholt gesendete Prompt dadurch um 1.010 Zeichen, also um etwa 253 geschätzte Token pro Modellanfrage. Fehlt eines der Conduct-Gegenstücke, bleibt die entsprechende ältere Regel unverändert erhalten.
133
137
 
134
138
  Ab BLUN King 9.1.118 bleibt der Sitzungszeitstempel im Systemprompt während einer laufenden Sitzung unverändert. Das erneute Einlesen von Verzeichnisübersicht, AGENTS.md-Dateien, Zusatzverzeichnissen und Skillinformationen kann den Prompt weiterhin ändern, wenn sich die jeweiligen Quellen tatsächlich verändert haben. Bleiben diese Quellen gleich, entwertet die Aktualisierung nach einer Verdichtung den Präfix-Cache des Anbieters nicht mehr allein durch einen neuen Zeitstempel. In Fredriks gemessener Sitzung unterschieden sich die beiden letzten Systemprompts mit jeweils 68.631 Zeichen nur in der Zeitangabe, und zwar erst nach einem gemeinsamen Präfix von 27.111 Zeichen. Der geänderte Zeitstempel verhinderte damit die Wiederverwendung der übrigen 41.512 unveränderten Zeichen aus dem Präfix-Cache.
@@ -44,9 +44,8 @@ function recentUserText(history, limit = RECENT_USER_MESSAGES) {
44
44
  function splitSections(source) {
45
45
  const text = String(source || '').replace(/\r\n?/gu, '\n').trim();
46
46
  if (!text) return [];
47
- const headings = [...text.matchAll(/^#{2,3}\s+.+$/gmu)];
47
+ const headings = [...text.matchAll(/^##\s+.+$/gmu)];
48
48
  const sections = [];
49
- let parent = '';
50
49
  const preambleEnd = headings[0]?.index ?? text.length;
51
50
  const preamble = text.slice(0, preambleEnd).trim();
52
51
  if (preamble) {
@@ -56,13 +55,11 @@ function splitSections(source) {
56
55
  }
57
56
  headings.forEach((match, index) => {
58
57
  const heading = match[0].trim();
59
- const level = heading.startsWith('### ') ? 3 : 2;
60
- if (level === 2) parent = heading;
61
58
  const start = match.index + match[0].length;
62
59
  const end = headings[index + 1]?.index ?? text.length;
63
60
  sections.push({
64
61
  heading,
65
- parent: level === 3 ? parent : '',
62
+ parent: '',
66
63
  body: text.slice(start, end).trim(),
67
64
  index,
68
65
  });
@@ -70,10 +67,14 @@ function splitSections(source) {
70
67
  return sections;
71
68
  }
72
69
 
70
+ function isReusableSection(section) {
71
+ return REUSABLE_HEADING.test(`${section.heading}\n${section.body}`);
72
+ }
73
+
73
74
  function scoreSection(section, queryTerms) {
74
75
  const heading = normalizeText(`${section.parent} ${section.heading}`);
75
76
  const body = normalizeText(section.body);
76
- let score = REUSABLE_HEADING.test(section.heading) ? 1 : 0;
77
+ let score = isReusableSection(section) ? 1 : 0;
77
78
  for (const term of queryTerms) {
78
79
  if (heading.includes(term)) score += 6;
79
80
  if (body.includes(term)) score += 4;
@@ -94,26 +95,49 @@ function compactSection(section, maxChars) {
94
95
  return `${full.slice(0, headChars).trimEnd()}${marker}${full.slice(-tailChars).trimStart()}`;
95
96
  }
96
97
 
97
- function selectRelevantMistakeContent(source, query, options = {}) {
98
+ function selectRelevantMistakeSources(sources, query, options = {}) {
98
99
  const maxChars = Math.max(256, Number(options.maxChars) || DEFAULT_MAX_CHARS);
99
100
  const maxSections = Math.max(1, Number(options.maxSections) || DEFAULT_MAX_SECTIONS);
100
- const sourceText = String(source || '').trim();
101
- const sections = splitSections(sourceText);
101
+ const normalizedSources = (Array.isArray(sources) ? sources : [])
102
+ .map((source, sourceIndex) => ({
103
+ content: String(source?.content || '').trim(),
104
+ label: String(source?.label || `source-${sourceIndex + 1}`).trim(),
105
+ reference: String(source?.reference || source?.label || `source-${sourceIndex + 1}`).trim(),
106
+ sourceIndex,
107
+ }))
108
+ .filter((source) => source.content.length > 0);
109
+ let ordinal = 0;
110
+ const sections = normalizedSources.flatMap((source) => (
111
+ splitSections(source.content).map((section) => ({
112
+ ...section,
113
+ ordinal: ordinal++,
114
+ sourceIndex: source.sourceIndex,
115
+ }))
116
+ ));
102
117
  const queryTerms = terms(query);
103
- const ranked = sections.map((section) => ({
104
- ...section,
105
- score: scoreSection(section, queryTerms),
106
- }));
107
- const hasMatch = ranked.some((section) => section.score > (REUSABLE_HEADING.test(section.heading) ? 1 : 0));
118
+ const ranked = sections.map((section) => {
119
+ const reusable = isReusableSection(section);
120
+ const score = scoreSection(section, queryTerms);
121
+ return {
122
+ ...section,
123
+ matched: score > (reusable ? 1 : 0),
124
+ reusable,
125
+ score,
126
+ };
127
+ });
128
+ const hasMatch = ranked.some((section) => section.matched);
108
129
  ranked.sort((left, right) => {
109
130
  if (hasMatch && right.score !== left.score) return right.score - left.score;
110
- const rightReusable = REUSABLE_HEADING.test(right.heading) ? 1 : 0;
111
- const leftReusable = REUSABLE_HEADING.test(left.heading) ? 1 : 0;
131
+ const rightReusable = right.reusable ? 1 : 0;
132
+ const leftReusable = left.reusable ? 1 : 0;
112
133
  if (rightReusable !== leftReusable) return rightReusable - leftReusable;
113
- return right.index - left.index;
134
+ return right.ordinal - left.ordinal;
114
135
  });
115
136
 
116
- const header = '# Mistake.md\n\nRelevant verified lessons for the current turn.';
137
+ const references = options.includeSourceReferences === false
138
+ ? ''
139
+ : `\nFull sources: ${normalizedSources.map((source) => source.reference).join(', ')}`;
140
+ const header = `# Mistake.md\n\nRelevant verified lessons for the current turn.${references}`;
117
141
  const selected = [];
118
142
  let remaining = maxChars - header.length - 2;
119
143
  const perSectionMax = Math.max(
@@ -122,7 +146,7 @@ function selectRelevantMistakeContent(source, query, options = {}) {
122
146
  );
123
147
  for (const section of ranked) {
124
148
  if (selected.length >= maxSections || remaining < 64) break;
125
- if (hasMatch && section.score === 0) continue;
149
+ if (hasMatch && !section.matched) continue;
126
150
  const rendered = compactSection(section, Math.min(remaining, perSectionMax));
127
151
  if (!rendered) continue;
128
152
  selected.push({ ...section, rendered });
@@ -132,21 +156,29 @@ function selectRelevantMistakeContent(source, query, options = {}) {
132
156
  const fallback = sections.at(-1);
133
157
  selected.push({ ...fallback, rendered: compactSection(fallback, remaining) });
134
158
  }
135
- selected.sort((left, right) => left.index - right.index);
159
+ selected.sort((left, right) => left.ordinal - right.ordinal);
136
160
  const text = [header, ...selected.map((section) => section.rendered)].join('\n\n').slice(0, maxChars);
137
161
  return {
138
162
  selectedChars: text.length,
139
163
  selectedSections: selected.length,
140
164
  text,
141
- totalChars: sourceText.length,
165
+ totalChars: normalizedSources.reduce((sum, source) => sum + source.content.length, 0),
142
166
  totalSections: sections.length,
167
+ totalSources: normalizedSources.length,
143
168
  };
144
169
  }
145
170
 
171
+ function selectRelevantMistakeContent(source, query, options = {}) {
172
+ return selectRelevantMistakeSources([
173
+ { content: source, label: 'mistake', reference: 'Mistake.md' },
174
+ ], query, { ...options, includeSourceReferences: false });
175
+ }
176
+
146
177
  module.exports = {
147
178
  DEFAULT_MAX_CHARS,
148
179
  DEFAULT_MAX_SECTIONS,
149
180
  RECENT_USER_MESSAGES,
150
181
  recentUserText,
151
182
  selectRelevantMistakeContent,
183
+ selectRelevantMistakeSources,
152
184
  };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const MAX_UNADDRESSED_TELEGRAM_CONTEXT_CHARS = 2000;
4
+
5
+ function trimDanglingHighSurrogate(value) {
6
+ if (value.length === 0) return value;
7
+ const last = value.charCodeAt(value.length - 1);
8
+ return last >= 0xd800 && last <= 0xdbff ? value.slice(0, -1) : value;
9
+ }
10
+
11
+ function trimLeadingLowSurrogate(value) {
12
+ if (value.length === 0) return value;
13
+ const first = value.charCodeAt(0);
14
+ return first >= 0xdc00 && first <= 0xdfff ? value.slice(1) : value;
15
+ }
16
+
17
+ function projectUnaddressedTelegramContext(
18
+ tag,
19
+ maxChars = MAX_UNADDRESSED_TELEGRAM_CONTEXT_CHARS,
20
+ ) {
21
+ if (typeof tag !== 'string') return '';
22
+ if (!Number.isInteger(maxChars) || maxChars < 1 || tag.length <= maxChars) return tag;
23
+
24
+ const marker = `\n[Telegram-Kontext gekuerzt; Original: ${tag.length} Zeichen]\n`;
25
+ if (marker.length >= maxChars) return marker.slice(0, maxChars);
26
+
27
+ const retainedChars = maxChars - marker.length;
28
+ const headChars = Math.ceil(retainedChars * 0.6);
29
+ const tailChars = retainedChars - headChars;
30
+ const head = trimDanglingHighSurrogate(tag.slice(0, headChars));
31
+ const tail = trimLeadingLowSurrogate(tag.slice(tag.length - tailChars));
32
+
33
+ return `${head}${marker}${tail}`;
34
+ }
35
+
36
+ module.exports = {
37
+ MAX_UNADDRESSED_TELEGRAM_CONTEXT_CHARS,
38
+ projectUnaddressedTelegramContext,
39
+ };
package/blun.mjs CHANGED
@@ -21481,7 +21481,6 @@ async function loadAgentsMdForRoots(kaos, brandHome, workDirs) {
21481
21481
  await collect(join$4(dir, ".blun", "AGENTS.md"));
21482
21482
  for (const fileName of ["AGENTS.md", "agents.md"]) if (await collect(join$4(dir, fileName))) break;
21483
21483
  }
21484
- for (const dir of dirs) await collect(join$4(dir, "Mistake.md"));
21485
21484
  }
21486
21485
  const content = renderAgentFiles(await Promise.all(discovered.map((f) => resolveAgentFileImports(kaos, f, 0, /* @__PURE__ */ new Set()))));
21487
21486
  const totalBytes = byteLength(content);
@@ -231447,11 +231446,29 @@ var init_mistake_md_writer = __esmMin((() => {
231447
231446
  function isMistakeMdReminder(message) {
231448
231447
  return message.origin?.kind === "injection" && message.origin.variant === "mistake_md";
231449
231448
  }
231450
- var MistakeMdInjector, recentUserText, selectRelevantMistakeContent;
231449
+ async function readProjectMistakeSources(agent) {
231450
+ const kaos = agent.kaos;
231451
+ const cwd = kaos.getcwd();
231452
+ const dirs = dirsRootToLeaf(kaos, cwd, await findProjectRoot$2(kaos, cwd));
231453
+ const sources = [];
231454
+ for (const dir of dirs) {
231455
+ const mistakePath = join$4(dir, "Mistake.md");
231456
+ try {
231457
+ const content = (await kaos.readText(mistakePath)).trim();
231458
+ if (content) sources.push({
231459
+ label: "project",
231460
+ reference: mistakePath,
231461
+ content
231462
+ });
231463
+ } catch {}
231464
+ }
231465
+ return sources;
231466
+ }
231467
+ var MistakeMdInjector, recentUserText, selectRelevantMistakeSources;
231451
231468
  var init_mistake_md = __esmMin((() => {
231452
231469
  init_injector();
231453
231470
  init_mistake_md_writer();
231454
- ({ recentUserText, selectRelevantMistakeContent } = createRequire(import.meta.url)("./bin/mistake-relevance-policy.cjs"));
231471
+ ({ recentUserText, selectRelevantMistakeSources } = createRequire(import.meta.url)("./bin/mistake-relevance-policy.cjs"));
231455
231472
  MistakeMdInjector = class extends DynamicInjector {
231456
231473
  injectionVariant = "mistake_md";
231457
231474
  reserveChecked = false;
@@ -231485,26 +231502,30 @@ var init_mistake_md = __esmMin((() => {
231485
231502
  }
231486
231503
  async getInjection() {
231487
231504
  try {
231488
- return await withMistakeLock(this.mistakeDir, async () => {
231489
- let content;
231505
+ const sources = await readProjectMistakeSources(this.agent);
231506
+ const sharedContent = await withMistakeLock(this.mistakeDir, async () => {
231490
231507
  try {
231491
- content = await readFile(this.mistakeFile, "utf-8");
231508
+ return (await readFile(this.mistakeFile, "utf-8")).trim();
231492
231509
  } catch {
231493
- return;
231494
- }
231495
- const trimmed = content.trim();
231496
- if (trimmed.length === 0) return void 0;
231497
- const query = recentUserText(this.agent.context.history);
231498
- const selected = selectRelevantMistakeContent(trimmed, query);
231499
- if (!selected.text) return void 0;
231500
- let prefix = "";
231501
- if (!this.reserveChecked) {
231502
- this.reserveChecked = true;
231503
- const reserveWarning = await checkReserve(this.mistakeDir);
231504
- if (reserveWarning) prefix = `<!-- D2a: ${reserveWarning} -->\n\n`;
231510
+ return "";
231505
231511
  }
231506
- return prefix + selected.text;
231507
231512
  });
231513
+ if (sharedContent) sources.push({
231514
+ label: "shared",
231515
+ reference: this.mistakeFile,
231516
+ content: sharedContent
231517
+ });
231518
+ if (sources.length === 0) return void 0;
231519
+ const query = recentUserText(this.agent.context.history);
231520
+ const selected = selectRelevantMistakeSources(sources, query);
231521
+ if (!selected.text) return void 0;
231522
+ let prefix = "";
231523
+ if (!this.reserveChecked && sharedContent) {
231524
+ this.reserveChecked = true;
231525
+ const reserveWarning = await checkReserve(this.mistakeDir);
231526
+ if (reserveWarning) prefix = `<!-- D2a: ${reserveWarning} -->\n\n`;
231527
+ }
231528
+ return prefix + selected.text;
231508
231529
  } catch {
231509
231530
  return;
231510
231531
  }
@@ -418031,6 +418052,7 @@ registerUiCatalogFragment({
418031
418052
  * This module is dependency-free on purpose: verify-telegram.ts drives the
418032
418053
  * real logic here with a mocked host (no TUI, no session, no Telegram).
418033
418054
  */
418055
+ var { projectUnaddressedTelegramContext } = createRequire(import.meta.url)("./bin/telegram-context-projection-policy.cjs");
418034
418056
  const MAX_BUFFERED_CONTEXT_MESSAGES = 20;
418035
418057
  const MAX_BUFFERED_CONTEXT_CHARS = 24e3;
418036
418058
  const CONTEXT_TRUNCATED_MARKER = "\n[Telegram-Kontext gekuerzt]";
@@ -418056,7 +418078,8 @@ function bufferContext(state, chatId, tag) {
418056
418078
  const buffers = state.bufferedByChat ?? /* @__PURE__ */ new Map();
418057
418079
  state.bufferedByChat = buffers;
418058
418080
  const messages = buffers.get(chatId) ?? [];
418059
- const boundedTag = tag.length <= MAX_BUFFERED_CONTEXT_CHARS ? tag : `${tag.slice(0, MAX_BUFFERED_CONTEXT_CHARS - 28)}${CONTEXT_TRUNCATED_MARKER}`;
418081
+ const projectedTag = projectUnaddressedTelegramContext(tag);
418082
+ const boundedTag = projectedTag.length <= MAX_BUFFERED_CONTEXT_CHARS ? projectedTag : `${projectedTag.slice(0, MAX_BUFFERED_CONTEXT_CHARS - 28)}${CONTEXT_TRUNCATED_MARKER}`;
418060
418083
  messages.push(boundedTag);
418061
418084
  let chars = messages.reduce((sum, message) => sum + message.length, 0);
418062
418085
  while (messages.length > MAX_BUFFERED_CONTEXT_MESSAGES || chars > MAX_BUFFERED_CONTEXT_CHARS) chars -= messages.shift()?.length ?? 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.132",
3
+ "version": "9.1.134",
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": {