blun-king-cli 9.1.118 → 9.1.120

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
@@ -115,6 +115,10 @@ Version 9.1.109 hält nur noch die neun Werkzeuge dauerhaft in der Modellanfrage
115
115
 
116
116
  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.
117
117
 
118
+ Ab BLUN King 9.1.119 enthält der bei jedem Modellschritt erneut gesendete Prompt nur noch eine kleine Auswahl häufig benötigter Skills. Alle übrigen registrierten Skills bleiben verfügbar und lassen sich über das Skill-Werkzeug anhand ihres Namens oder Zwecks suchen; der ausgewählte Skill wird anschließend bei Bedarf geladen. Dadurch sinkt die wiederholt übertragene Promptlast, ohne Skills zu löschen oder die vollständige Ansicht unter `/skills` einzuschränken.
119
+
120
+ Ab BLUN King 9.1.120 lagert die Konsole auch große ältere Assistentenantworten aus dem aktiven Modellkontext in private Sitzungsdateien aus. Der vollständige Gesprächsverlauf bleibt erhalten; im Modellkontext verbleiben eine begrenzte Vorschau und der Dateipfad. Die 20 neuesten Nachrichten sowie Assistentenantworten mit Werkzeugaufrufen bleiben unverändert, damit laufende Arbeit und Aufrufketten vollständig verfügbar sind.
121
+
118
122
  Verliert ein delegierter Einzelagent oder ein Mitglied eines Agentenschwarms die Provider-Verbindung, erhält es eine leere Provider-Antwort, bricht sein Stream wegen Leerlaufs ab oder meldet der Server HTTP 408/500/502/503/504, setzt BLUN King denselben Agenten anhand seines dauerhaften Verlaufs fort. Bei Einzelagenten sowie bei Schwarm-Unterbrechungen außerhalb von HTTP 429 wartet die erste Fortsetzung zwei Sekunden und die zweite fünf Sekunden; danach bleibt der ursprüngliche Fehler sichtbar. HTTP 429 behält im Schwarm seine getrennte, längere Überlastungsregel. Authentifizierungs-, Zahlungs- oder Kontingent-, Richtlinien-, Kontext-, Werkzeug- und Codefehler sowie manuelle Abbrüche lösen keine automatische Fortsetzung aus. Jeder Fortsetzungsversuch verwendet die bestehende Agenten-ID, damit bereits abgeschlossene Arbeit nicht wiederholt wird.
119
123
 
120
124
  Rein lesende Sitzungsdiagnose
package/README.md CHANGED
@@ -121,6 +121,10 @@ Version 9.1.109 hält nur noch die neun Werkzeuge dauerhaft in der Modellanfrage
121
121
 
122
122
  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.
123
123
 
124
+ Ab BLUN King 9.1.119 enthält der bei jedem Modellschritt erneut gesendete Prompt nur noch eine kleine Auswahl häufig benötigter Skills. Alle übrigen registrierten Skills bleiben verfügbar und lassen sich über das Skill-Werkzeug anhand ihres Namens oder Zwecks suchen; der ausgewählte Skill wird anschließend bei Bedarf geladen. Dadurch sinkt die wiederholt übertragene Promptlast, ohne Skills zu löschen oder die vollständige Ansicht unter `/skills` einzuschränken.
125
+
126
+ Ab BLUN King 9.1.120 lagert die Konsole auch große ältere Assistentenantworten aus dem aktiven Modellkontext in private Sitzungsdateien aus. Der vollständige Gesprächsverlauf bleibt erhalten; im Modellkontext verbleiben eine begrenzte Vorschau und der Dateipfad. Die 20 neuesten Nachrichten sowie Assistentenantworten mit Werkzeugaufrufen bleiben unverändert, damit laufende Arbeit und Aufrufketten vollständig verfügbar sind.
127
+
124
128
  Ab BLUN King 9.1.99 werden auch direkt aufeinanderfolgende reine Textergebnisse als Stapel betrachtet. Enthalten sie zusammen mehr als 12.000 Zeichen, obwohl kein einzelnes Ergebnis diese Grenze überschreitet, speichert King so viele der größten geeigneten Ergebnisse wie nötig in privaten Dateien. In der Modellprojektion verbleiben lesbare Verweise. Der unveränderte Sitzungsrohverlauf bleibt vollständig erhalten. Gemischte Medienergebnisse, einzelne Ergebnisse unter 3.000 Zeichen und Stapel bis einschließlich 12.000 Zeichen bleiben unverändert. Beispiel: Bei zwei Suchergebnissen mit 7.000 und 6.000 Zeichen wird das größere Ergebnis privat gespeichert; Anfang, Ende, ausgelassene Zeichenzahl und `output_path` bleiben für den Agenten sichtbar.
125
129
 
126
130
  Ab BLUN King 9.1.100 beendet eine begrenzte Grep-Inhaltssuche ripgrep, sobald der Versatz, die angeforderten Zeilen und eine zusätzliche Vorschauzeile vollständig vorliegen. Die Vorschauzeile belegt, ob eine weitere Seite existiert, ohne vorher bis zu 10 MB einzulesen. Unbegrenzte Suchen, Trefferzählungen und nach Änderungszeit sortierte Dateilisten laufen weiterhin vollständig durch.
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const ASSISTANT_MESSAGE_MAX_CHARS = 4_000;
4
+ const ASSISTANT_MESSAGE_KEEP_RECENT_MESSAGES = 20;
5
+ const ASSISTANT_MESSAGE_PREVIEW_LINES = 5;
6
+ const ASSISTANT_MESSAGE_PREVIEW_LINE_CHARS = 1_000;
7
+ const ASSISTANT_MESSAGE_OFFLOAD_MARKER = '[Assistant message offloaded]';
8
+
9
+ function shouldOffloadHistoricalAssistantMessage(options = {}) {
10
+ const historyIndex = Number(options.historyIndex);
11
+ const historyLength = Number(options.historyLength);
12
+ const textChars = Number(options.textChars);
13
+ if (
14
+ !Number.isSafeInteger(historyIndex)
15
+ || !Number.isSafeInteger(historyLength)
16
+ || !Number.isSafeInteger(textChars)
17
+ || historyIndex < 0
18
+ || historyLength < 0
19
+ || historyIndex >= historyLength
20
+ ) return false;
21
+ const cutoff = Math.max(0, historyLength - ASSISTANT_MESSAGE_KEEP_RECENT_MESSAGES);
22
+ return historyIndex < cutoff && textChars > ASSISTANT_MESSAGE_MAX_CHARS;
23
+ }
24
+
25
+ function createAssistantMessagePreview(text) {
26
+ const lines = String(text).split(/\r?\n/u);
27
+ const render = (line, index) => `${index}: ${line.slice(0, ASSISTANT_MESSAGE_PREVIEW_LINE_CHARS)}`;
28
+ if (lines.length <= ASSISTANT_MESSAGE_PREVIEW_LINES * 2) {
29
+ return lines.map((line, index) => render(line, index + 1)).join('\n');
30
+ }
31
+ const head = lines.slice(0, ASSISTANT_MESSAGE_PREVIEW_LINES)
32
+ .map((line, index) => render(line, index + 1));
33
+ const tailStart = lines.length - ASSISTANT_MESSAGE_PREVIEW_LINES;
34
+ const tail = lines.slice(tailStart)
35
+ .map((line, index) => render(line, tailStart + index + 1));
36
+ return [
37
+ ...head,
38
+ `... [${lines.length - ASSISTANT_MESSAGE_PREVIEW_LINES * 2} lines omitted] ...`,
39
+ ...tail,
40
+ ].join('\n');
41
+ }
42
+
43
+ module.exports = {
44
+ ASSISTANT_MESSAGE_KEEP_RECENT_MESSAGES,
45
+ ASSISTANT_MESSAGE_MAX_CHARS,
46
+ ASSISTANT_MESSAGE_OFFLOAD_MARKER,
47
+ ASSISTANT_MESSAGE_PREVIEW_LINES,
48
+ ASSISTANT_MESSAGE_PREVIEW_LINE_CHARS,
49
+ createAssistantMessagePreview,
50
+ shouldOffloadHistoricalAssistantMessage,
51
+ };
@@ -9,6 +9,8 @@ const FIRST_USE_SKILLS = new Set([
9
9
  'blun-app-design-system',
10
10
  ]);
11
11
 
12
+ const DEFAULT_SEARCH_LIMIT = 12;
13
+
12
14
  function compactDescription(value) {
13
15
  const normalized = String(value || '').replace(/\s+/g, ' ').trim();
14
16
  if (normalized.length <= SKILL_DESCRIPTION_MAX_CHARS) return normalized;
@@ -26,8 +28,64 @@ function renderCompactSkillListing(skills) {
26
28
  return (Array.isArray(skills) ? skills : []).map(renderCompactSkillEntry).join('\n');
27
29
  }
28
30
 
31
+ function isResidentSkill(skill) {
32
+ return FIRST_USE_SKILLS.has(String(skill?.name || '').trim().toLowerCase());
33
+ }
34
+
35
+ function renderResidentSkillListing(skills) {
36
+ return (Array.isArray(skills) ? skills : [])
37
+ .filter(isResidentSkill)
38
+ .map(renderCompactSkillEntry)
39
+ .join('\n');
40
+ }
41
+
42
+ function normalizeSearchText(value) {
43
+ return String(value || '')
44
+ .normalize('NFKD')
45
+ .replace(/[\u0300-\u036f]/g, '')
46
+ .toLowerCase()
47
+ .replace(/[^a-z0-9]+/g, ' ')
48
+ .trim();
49
+ }
50
+
51
+ function searchSkills(skills, query, limit = DEFAULT_SEARCH_LIMIT) {
52
+ const normalizedQuery = normalizeSearchText(query);
53
+ const queryTokens = normalizedQuery.split(/\s+/).filter(Boolean);
54
+ if (queryTokens.length === 0 || !Number.isFinite(limit) || limit <= 0) return [];
55
+
56
+ return (Array.isArray(skills) ? skills : [])
57
+ .filter((skill) => skill?.metadata?.disableModelInvocation !== true)
58
+ .map((skill) => {
59
+ const name = normalizeSearchText(skill?.name);
60
+ const description = normalizeSearchText(skill?.description);
61
+ const searchable = `${name} ${description}`.trim();
62
+ if (!queryTokens.every((token) => searchable.includes(token))) return null;
63
+ const score = name === normalizedQuery ? 0 : name.startsWith(normalizedQuery) ? 1 : name.includes(normalizedQuery) ? 2 : 3;
64
+ return { skill, name, score };
65
+ })
66
+ .filter(Boolean)
67
+ .sort((left, right) => left.score - right.score || left.name.localeCompare(right.name))
68
+ .slice(0, Math.floor(limit))
69
+ .map((entry) => entry.skill);
70
+ }
71
+
72
+ function renderSkillSearchResult(query, skills) {
73
+ const lines = [`No exact skill named "${String(query || '').trim()}" was found. Matching skills:`];
74
+ for (const skill of skills) {
75
+ const description = compactDescription(skill?.description);
76
+ lines.push(description ? `- ${skill.name}: ${description}` : `- ${skill.name}`);
77
+ }
78
+ lines.push('Call the Skill tool again with one exact skill name from this list.');
79
+ return lines.join('\n');
80
+ }
81
+
29
82
  module.exports = {
83
+ DEFAULT_SEARCH_LIMIT,
30
84
  SKILL_DESCRIPTION_MAX_CHARS,
85
+ isResidentSkill,
31
86
  renderCompactSkillEntry,
32
87
  renderCompactSkillListing,
88
+ renderResidentSkillListing,
89
+ renderSkillSearchResult,
90
+ searchSkills,
33
91
  };
package/blun.mjs CHANGED
@@ -78799,6 +78799,7 @@ var init_context$2 = __esmMin((() => {
78799
78799
  this.agent.microCompaction.reset(this._history.length);
78800
78800
  this.agent.toolResultBatchOffload.reset(this._history);
78801
78801
  this.agent.userMessageOffload.reset(this._history);
78802
+ this.agent.assistantMessageOffload.reset(this._history);
78802
78803
  this.agent.emitStatusUpdated();
78803
78804
  return removedMessages.size;
78804
78805
  }
@@ -78814,6 +78815,7 @@ var init_context$2 = __esmMin((() => {
78814
78815
  this.agent.microCompaction.reset();
78815
78816
  this.agent.toolResultBatchOffload.clear();
78816
78817
  this.agent.userMessageOffload.clear();
78818
+ this.agent.assistantMessageOffload.clear();
78817
78819
  this.agent.injection.onContextClear();
78818
78820
  this.agent.emitStatusUpdated();
78819
78821
  }
@@ -78854,6 +78856,7 @@ var init_context$2 = __esmMin((() => {
78854
78856
  this.agent.microCompaction.reset(this._history.length);
78855
78857
  this.agent.toolResultBatchOffload.reset(this._history);
78856
78858
  this.agent.userMessageOffload.reset(this._history);
78859
+ this.agent.assistantMessageOffload.reset(this._history);
78857
78860
  this.agent.emitStatusUpdated();
78858
78861
  if (!this.agent.records.restoring && (stoppedAtBoundary || removedUserCount < count)) throw new BlunError(ErrorCodes.REQUEST_INVALID, formatUndoUnavailableMessage(count, removedUserCount, stoppedAtBoundary), { details: {
78859
78862
  reason: "undo_limit",
@@ -78934,6 +78937,7 @@ var init_context$2 = __esmMin((() => {
78934
78937
  this.agent.microCompaction.reset();
78935
78938
  this.agent.toolResultBatchOffload.reset(this._history);
78936
78939
  this.agent.userMessageOffload.reset(this._history);
78940
+ this.agent.assistantMessageOffload.reset(this._history);
78937
78941
  this.agent.injection.onContextCompacted();
78938
78942
  this.agent.emitStatusUpdated();
78939
78943
  return result;
@@ -78956,7 +78960,7 @@ var init_context$2 = __esmMin((() => {
78956
78960
  }
78957
78961
  project(messages, options) {
78958
78962
  const anomalies = [];
78959
- const result = project(this.agent.userMessageOffload.compact(this.agent.microCompaction.compact(this.agent.toolResultBatchOffload.compact(dedupeRepeatedInjections(messages)))), {
78963
+ const result = project(this.agent.assistantMessageOffload.compact(this.agent.userMessageOffload.compact(this.agent.microCompaction.compact(this.agent.toolResultBatchOffload.compact(dedupeRepeatedInjections(messages))))), {
78960
78964
  ...options,
78961
78965
  onAnomaly: (anomaly) => {
78962
78966
  anomalies.push(anomaly);
@@ -234883,6 +234887,9 @@ function restoreAgentRecord(agent, input) {
234883
234887
  case "user_message_offload.apply":
234884
234888
  agent.userMessageOffload.apply(input.replacement);
234885
234889
  return;
234890
+ case "assistant_message_offload.apply":
234891
+ agent.assistantMessageOffload.apply(input.replacement);
234892
+ return;
234886
234893
  case "plan_mode.enter":
234887
234894
  agent.planMode.restoreEnter(input);
234888
234895
  return;
@@ -235656,13 +235663,13 @@ function truncate$2(value, max) {
235656
235663
  }
235657
235664
  return `${result}…`;
235658
235665
  }
235659
- var LISTING_DESC_MAX, renderCompactSkillEntry, SessionSkillRegistry, SOURCE_GROUPS, graphemeSegmenter$2;
235666
+ var LISTING_DESC_MAX, isResidentSkill, renderCompactSkillEntry, renderSkillSearchResult, searchSkills, SessionSkillRegistry, SOURCE_GROUPS, graphemeSegmenter$2;
235660
235667
  var init_registry = __esmMin((() => {
235661
235668
  init_parser$1();
235662
235669
  init_scanner();
235663
235670
  init_types$15();
235664
235671
  init_xml_escape();
235665
- ({ renderCompactSkillEntry } = createRequire(import.meta.url)("./bin/skill-listing-performance-policy.cjs"));
235672
+ ({ isResidentSkill, renderCompactSkillEntry, renderSkillSearchResult, searchSkills } = createRequire(import.meta.url)("./bin/skill-listing-performance-policy.cjs"));
235666
235673
  LISTING_DESC_MAX = 250;
235667
235674
  SessionSkillRegistry = class {
235668
235675
  byName = /* @__PURE__ */ new Map();
@@ -235741,10 +235748,13 @@ var init_registry = __esmMin((() => {
235741
235748
  return rendered.length === 0 ? "No skills" : rendered;
235742
235749
  }
235743
235750
  getModelSkillListing() {
235744
- const lines = ["DISREGARD any earlier skill listings. Current available skills:"];
235745
- const listing = renderGroupedSkills(this.listInvocableSkills().filter((skill) => skill.metadata.isSubSkill !== true), formatModelSkill);
235751
+ const lines = [
235752
+ "DISREGARD any earlier skill listings. Frequently used skills are listed below.",
235753
+ "Other registered skills remain available: call the Skill tool with search words to discover their exact names."
235754
+ ];
235755
+ const listing = renderGroupedSkills(this.listInvocableSkills().filter((skill) => skill.metadata.isSubSkill !== true && isResidentSkill(skill)), formatModelSkill);
235746
235756
  if (listing.length > 0) lines.push(listing);
235747
- return lines.length === 1 ? "" : lines.join("\n");
235757
+ return lines.join("\n");
235748
235758
  }
235749
235759
  };
235750
235760
  SOURCE_GROUPS = [
@@ -252921,7 +252931,7 @@ var init_skill_tool = __esmMin((() => {
252921
252931
  }
252922
252932
  };
252923
252933
  SkillToolInputSchema = object({
252924
- skill: string().describe("The exact name of the skill to invoke, spelled as it appears in the current skill listing (e.g. \"commit\", \"pdf\")."),
252934
+ skill: string().describe("An exact skill name to invoke, or search words to discover matching registered skills when the exact name is unknown."),
252925
252935
  args: string().optional().describe("Optional argument string for the skill, written like a command line (e.g. `-m \"fix bug\"`, `123`, a file path). It is split on whitespace (quotes group a token) and expanded into the skill's placeholders ($NAME, $1, $ARGUMENTS); if the skill body has no placeholders, the whole string is still appended as a trailing `ARGUMENTS:` line. Omit it only when there is nothing to pass.")
252926
252936
  });
252927
252937
  SkillTool = class SkillTool {
@@ -252959,7 +252969,11 @@ var init_skill_tool = __esmMin((() => {
252959
252969
  const skills = this.agent.skills;
252960
252970
  if (skills === null) return errorResult(`Skill "${args.skill}" not found in the current skill listing.`);
252961
252971
  const skill = skills.registry.getSkill(args.skill);
252962
- if (skill === void 0) return errorResult(`Skill "${args.skill}" not found in the current skill listing.`);
252972
+ if (skill === void 0) {
252973
+ const matches = searchSkills(skills.registry.listInvocableSkills().filter((candidate) => candidate.metadata.isSubSkill !== true), args.skill);
252974
+ if (matches.length === 0) return errorResult(`No registered skill matched "${args.skill}". Try broader search words.`);
252975
+ return { output: renderSkillSearchResult(args.skill, matches) };
252976
+ }
252963
252977
  if (skill.metadata.disableModelInvocation === true) return errorResult(`Skill "${args.skill}" can only be triggered by the user (model invocation is disabled).`);
252964
252978
  const skillArgs = args.args ?? "";
252965
252979
  if (!isInlineSkillType(skill.metadata.type)) return errorResult(`Skill "${skill.name}" is not an inline skill and cannot be invoked by the model in v1.`);
@@ -260577,6 +260591,129 @@ var UserMessageOffload = class {
260577
260591
  }
260578
260592
  };
260579
260593
  //#endregion
260594
+ //#region ../../packages/agent-core/src/agent/turn/assistant-message-offload.ts
260595
+ function persistableAssistantMessageText(message) {
260596
+ if (message?.role !== "assistant" || message.toolCalls.length > 0) return;
260597
+ const textParts = message.content.filter((part) => part.type === "text");
260598
+ if (textParts.length === 0) return;
260599
+ return textParts.map((part) => part.text).join("\n");
260600
+ }
260601
+ function assistantMessageOffloadHash(message, text) {
260602
+ return createHash("sha256").update(JSON.stringify({ text })).digest("hex");
260603
+ }
260604
+ async function saveAssistantMessage(homedir, text) {
260605
+ try {
260606
+ const dir = join$4(homedir, "conversation-history");
260607
+ await mkdir(dir, {
260608
+ recursive: true,
260609
+ mode: 448
260610
+ });
260611
+ const outputPath = join$4(dir, `assistant-message-${randomUUID()}.txt`);
260612
+ await writeFile(outputPath, text, {
260613
+ encoding: "utf8",
260614
+ flag: "wx",
260615
+ mode: 384
260616
+ });
260617
+ return outputPath;
260618
+ } catch {
260619
+ return;
260620
+ }
260621
+ }
260622
+ function renderPersistedAssistantMessage(text, outputPath) {
260623
+ return [
260624
+ ASSISTANT_MESSAGE_OFFLOAD_MARKER,
260625
+ "The complete original was archived to keep the active context fast and remains available on disk.",
260626
+ `message_size_chars: ${String(text.length)}`,
260627
+ `message_size_bytes: ${String(Buffer.byteLength(text, "utf8"))}`,
260628
+ `output_path: ${outputPath}`,
260629
+ "next_step: Use Read with output_path to inspect the complete message in pages.",
260630
+ "",
260631
+ "[preview: head and tail]",
260632
+ createAssistantMessagePreview(text)
260633
+ ].join("\n");
260634
+ }
260635
+ var ASSISTANT_MESSAGE_MAX_CHARS, ASSISTANT_MESSAGE_OFFLOAD_MARKER, shouldOffloadHistoricalAssistantMessage, createAssistantMessagePreview;
260636
+ var init_assistant_message_offload = __esmMin((() => {
260637
+ const policy = createRequire(import.meta.url)("./bin/assistant-message-offload-policy.cjs");
260638
+ ASSISTANT_MESSAGE_MAX_CHARS = policy.ASSISTANT_MESSAGE_MAX_CHARS;
260639
+ ASSISTANT_MESSAGE_OFFLOAD_MARKER = policy.ASSISTANT_MESSAGE_OFFLOAD_MARKER;
260640
+ shouldOffloadHistoricalAssistantMessage = policy.shouldOffloadHistoricalAssistantMessage;
260641
+ createAssistantMessagePreview = policy.createAssistantMessagePreview;
260642
+ }));
260643
+ var AssistantMessageOffload = class {
260644
+ agent;
260645
+ replacements = /* @__PURE__ */ new Map();
260646
+ constructor(agent) {
260647
+ this.agent = agent;
260648
+ }
260649
+ async detect() {
260650
+ const history = this.agent.context.history;
260651
+ if (this.agent.homedir === void 0) return 0;
260652
+ let offloaded = 0;
260653
+ for (let historyIndex = 0; historyIndex < history.length; historyIndex++) {
260654
+ const message = history[historyIndex];
260655
+ const text = persistableAssistantMessageText(message);
260656
+ if (text === void 0 || !shouldOffloadHistoricalAssistantMessage({
260657
+ historyIndex,
260658
+ historyLength: history.length,
260659
+ textChars: text.length
260660
+ })) continue;
260661
+ const messageHash = assistantMessageOffloadHash(message, text);
260662
+ if (this.replacements.has(messageHash)) continue;
260663
+ const outputPath = await saveAssistantMessage(this.agent.homedir, text);
260664
+ if (outputPath === void 0) continue;
260665
+ const replacementText = renderPersistedAssistantMessage(text, outputPath);
260666
+ const content = [{
260667
+ type: "text",
260668
+ text: replacementText
260669
+ }, ...message.content.filter((part) => part.type !== "text")];
260670
+ this.apply({
260671
+ messageHash,
260672
+ content,
260673
+ outputPath
260674
+ });
260675
+ this.agent.telemetry.track("assistant_message_offloaded", {
260676
+ chars_before: text.length,
260677
+ chars_after: replacementText.length,
260678
+ chars_saved: text.length - replacementText.length,
260679
+ non_text_part_count: content.length - 1,
260680
+ reason: "historical_oversize"
260681
+ });
260682
+ offloaded++;
260683
+ }
260684
+ return offloaded;
260685
+ }
260686
+ apply(replacement) {
260687
+ this.replacements.set(replacement.messageHash, replacement);
260688
+ this.agent.records.logRecord({
260689
+ type: "assistant_message_offload.apply",
260690
+ replacement
260691
+ });
260692
+ }
260693
+ compact(messages) {
260694
+ if (this.replacements.size === 0) return messages;
260695
+ return messages.map((message) => {
260696
+ const text = persistableAssistantMessageText(message);
260697
+ if (text === void 0) return message;
260698
+ const replacement = this.replacements.get(assistantMessageOffloadHash(message, text));
260699
+ return replacement === void 0 ? message : {
260700
+ ...message,
260701
+ content: replacement.content
260702
+ };
260703
+ });
260704
+ }
260705
+ reset(history = this.agent.context?.history ?? []) {
260706
+ const activeHashes = new Set(history.map((message) => {
260707
+ const text = persistableAssistantMessageText(message);
260708
+ return text === void 0 ? void 0 : assistantMessageOffloadHash(message, text);
260709
+ }).filter((hash) => hash !== void 0));
260710
+ for (const messageHash of this.replacements.keys()) if (!activeHashes.has(messageHash)) this.replacements.delete(messageHash);
260711
+ }
260712
+ clear() {
260713
+ this.replacements.clear();
260714
+ }
260715
+ };
260716
+ //#endregion
260580
260717
  //#region ../../packages/agent-core/src/agent/turn/index.ts
260581
260718
  function blunExtractText(content) {
260582
260719
  if (typeof content === "string") return content;
@@ -260913,6 +261050,7 @@ var init_turn = __esmMin((() => {
260913
261050
  init_tool_dedup();
260914
261051
  init_tool_result_budget();
260915
261052
  init_user_message_offload();
261053
+ init_assistant_message_offload();
260916
261054
  ({ CORE_TOOL_NAMES: BLUN_CORE_TOOL_NAMES, createDeferredToolLoader, toolSchemaBudgetTokens } = createRequire(import.meta.url)("./bin/turn-tool-performance-policy.cjs"));
260917
261055
  BLUN_LEAN_TOOL_NAMES = new Set(BLUN_CORE_TOOL_NAMES);
260918
261056
  BLUN_ATTACHMENT_MARKER_RE = /\b(?:attachment_file_id|telegram-anhang|telegram attachment)\b/i;
@@ -261548,6 +261686,7 @@ var init_turn = __esmMin((() => {
261548
261686
  await this.agent.records.flush();
261549
261687
  this.agent.microCompaction.detect();
261550
261688
  await this.agent.userMessageOffload.detect();
261689
+ await this.agent.assistantMessageOffload.detect();
261551
261690
  await this.agent.injection.inject();
261552
261691
  stepSignal.throwIfAborted();
261553
261692
  deduper.beginStep();
@@ -264325,6 +264464,7 @@ var init_agent = __esmMin((() => {
264325
264464
  fullCompaction;
264326
264465
  microCompaction;
264327
264466
  toolResultBatchOffload;
264467
+ assistantMessageOffload;
264328
264468
  context;
264329
264469
  config;
264330
264470
  turn;
@@ -264385,6 +264525,7 @@ var init_agent = __esmMin((() => {
264385
264525
  this.microCompaction = new MicroCompaction(this, options.microCompaction);
264386
264526
  this.toolResultBatchOffload = new ToolResultBatchOffload(this);
264387
264527
  this.userMessageOffload = new UserMessageOffload(this);
264528
+ this.assistantMessageOffload = new AssistantMessageOffload(this);
264388
264529
  this.context = new ContextMemory(this);
264389
264530
  this.config = new ConfigState(this);
264390
264531
  this.turn = new TurnFlow(this);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.118",
3
+ "version": "9.1.120",
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": {