blun-king-cli 9.1.119 → 9.1.121

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
@@ -117,6 +117,10 @@ Ab BLUN King 9.1.118 bleibt der Sitzungszeitstempel im Systemprompt während ein
117
117
 
118
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
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
+
122
+ Ab BLUN King 9.1.121 begrenzt die Modellprojektion auch die Gesamtgröße älterer, mittelgroßer Werkzeugergebnisse, die ausschließlich Text enthalten. Überschreiten Werkzeugergebnisse außerhalb der letzten 20 Nachrichten zusammen 12.000 Zeichen, archiviert King so viele der größten geeigneten Ergebnisse wie nötig und behält nur kompakte, lesbare Dateiverweise im Modellkontext. Der nur ergänzte Rohverlauf und die vollständigen Ergebnisse bleiben unverändert. Gemischte Medienergebnisse und die letzten 20 Nachrichten werden nicht angetastet. In einer gemessenen Sitzung von Fredrik wählte dieser Schnitt 12 ältere Ergebnisse aus und verringerte die projizierte alte Werkzeugausgabe um mindestens 63.444 Zeichen, also um etwa 15.861 geschätzte Token.
123
+
120
124
  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.
121
125
 
122
126
  Rein lesende Sitzungsdiagnose
package/README.md CHANGED
@@ -123,6 +123,10 @@ Ab BLUN King 9.1.118 bleibt der Sitzungszeitstempel im Systemprompt während ein
123
123
 
124
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
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
+
128
+ Ab BLUN King 9.1.121 begrenzt die Modellprojektion auch die Gesamtgröße älterer, mittelgroßer Werkzeugergebnisse, die ausschließlich Text enthalten. Überschreiten Werkzeugergebnisse außerhalb der letzten 20 Nachrichten zusammen 12.000 Zeichen, archiviert King so viele der größten geeigneten Ergebnisse wie nötig und behält nur kompakte, lesbare Dateiverweise im Modellkontext. Der nur ergänzte Rohverlauf und die vollständigen Ergebnisse bleiben unverändert. Gemischte Medienergebnisse und die letzten 20 Nachrichten werden nicht angetastet. In einer gemessenen Sitzung von Fredrik wählte dieser Schnitt 12 ältere Ergebnisse aus und verringerte die projizierte alte Werkzeugausgabe um mindestens 63.444 Zeichen, also um etwa 15.861 geschätzte Token.
129
+
126
130
  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.
127
131
 
128
132
  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
+ };
@@ -6,6 +6,10 @@ const TOOL_RESULT_OFFLOAD_MARKER = '[Tool result offloaded]';
6
6
  const TOOL_RESULT_BATCH_MAX_CHARS = TOOL_RESULT_MAX_CHARS;
7
7
  const TOOL_RESULT_BATCH_MIN_ITEM_CHARS = 3_000;
8
8
  const TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS = 2_500;
9
+ const TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES = 20;
10
+ const TOOL_RESULT_HISTORICAL_MAX_CHARS = TOOL_RESULT_BATCH_MAX_CHARS;
11
+ const TOOL_RESULT_HISTORICAL_MIN_ITEM_CHARS = 600;
12
+ const TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS = 600;
9
13
 
10
14
  function shouldOffloadToolResult(textLength) {
11
15
  return Number.isFinite(textLength) && textLength > TOOL_RESULT_MAX_CHARS;
@@ -62,15 +66,50 @@ function selectToolResultBatchOffloads(textLengths) {
62
66
  : [];
63
67
  }
64
68
 
69
+ function selectHistoricalToolResultOffloads(textLengths) {
70
+ if (!Array.isArray(textLengths) || textLengths.length === 0) return [];
71
+
72
+ const normalized = textLengths.map((length) => (
73
+ Number.isFinite(length) && length > 0 ? Math.floor(length) : 0
74
+ ));
75
+ const totalChars = normalized.reduce((total, length) => total + length, 0);
76
+ if (totalChars <= TOOL_RESULT_HISTORICAL_MAX_CHARS) return [];
77
+
78
+ let projectedChars = totalChars;
79
+ const selected = [];
80
+ const candidates = normalized
81
+ .map((length, index) => ({ index, length }))
82
+ .filter(({ length }) => (
83
+ length >= TOOL_RESULT_HISTORICAL_MIN_ITEM_CHARS
84
+ && length > TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS
85
+ ))
86
+ .sort((left, right) => right.length - left.length || left.index - right.index);
87
+
88
+ for (const candidate of candidates) {
89
+ if (projectedChars <= TOOL_RESULT_HISTORICAL_MAX_CHARS) break;
90
+ projectedChars -= candidate.length - TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS;
91
+ selected.push(candidate.index);
92
+ }
93
+
94
+ return projectedChars <= TOOL_RESULT_HISTORICAL_MAX_CHARS
95
+ ? selected.sort((left, right) => left - right)
96
+ : [];
97
+ }
98
+
65
99
  module.exports = {
66
100
  TOOL_RESULT_BATCH_MAX_CHARS,
67
101
  TOOL_RESULT_BATCH_MIN_ITEM_CHARS,
68
102
  TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS,
103
+ TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES,
104
+ TOOL_RESULT_HISTORICAL_MAX_CHARS,
105
+ TOOL_RESULT_HISTORICAL_MIN_ITEM_CHARS,
106
+ TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS,
69
107
  TOOL_RESULT_MAX_CHARS,
70
108
  TOOL_RESULT_PREVIEW_CHARS,
71
109
  TOOL_RESULT_OFFLOAD_MARKER,
72
110
  createToolResultPreview,
73
111
  isPersistedToolResultReference,
112
+ selectHistoricalToolResultOffloads,
74
113
  selectToolResultBatchOffloads,
75
114
  shouldOffloadToolResult,
76
115
  };
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;
@@ -260322,10 +260329,53 @@ function renderReadSourceReference(toolName, toolCallId, text, outputPath) {
260322
260329
  lines.push("", "[preview: head and tail]", createToolResultPreview(text));
260323
260330
  return lines.join("\n");
260324
260331
  }
260332
+ async function persistHistoricalToolResultForModel(options, knownText) {
260333
+ const text = knownText ?? persistableToolResultText(options.result.output);
260334
+ if (text === void 0 || options.result.truncated === true) return options.result;
260335
+ const readSourcePath = reusableReadSourcePath(options);
260336
+ let outputPath = readSourcePath;
260337
+ let storageMode = "original_file";
260338
+ if (outputPath === void 0) {
260339
+ if (options.homedir === void 0) return options.result;
260340
+ outputPath = await saveToolResult({
260341
+ homedir: options.homedir,
260342
+ toolName: options.toolName,
260343
+ toolCallId: options.toolCallId
260344
+ }, text);
260345
+ storageMode = "private_archive";
260346
+ }
260347
+ if (outputPath === void 0) return options.result;
260348
+ options.telemetry?.track("tool_result_historical_offloaded", {
260349
+ ...buildToolResultOffloadTelemetry({
260350
+ toolName: options.toolName,
260351
+ text,
260352
+ previewChars: 0
260353
+ }),
260354
+ storage_mode: storageMode
260355
+ });
260356
+ return {
260357
+ ...options.result,
260358
+ output: renderHistoricalToolResultReference(options.toolName, options.toolCallId, text, outputPath, storageMode),
260359
+ ...(options.result.isError === true ? { isError: true } : {})
260360
+ };
260361
+ }
260362
+ function renderHistoricalToolResultReference(toolName, toolCallId, text, outputPath, storageMode) {
260363
+ return [
260364
+ TOOL_RESULT_OFFLOAD_MARKER,
260365
+ "An older tool result was archived to keep the active context fast.",
260366
+ `storage_mode: ${storageMode}`,
260367
+ `tool_name: ${toolName}`,
260368
+ `tool_call_id: ${toolCallId}`,
260369
+ `output_size_chars: ${String(text.length)}`,
260370
+ `output_size_bytes: ${String(Buffer.byteLength(text, "utf8"))}`,
260371
+ `output_path: ${outputPath}`,
260372
+ "next_step: Use Read with output_path if the full result is needed again."
260373
+ ].join("\n");
260374
+ }
260325
260375
  function safeToolResultFileStem(toolName, toolCallId) {
260326
260376
  return `${toolName}-${toolCallId}`.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80) || "tool-result";
260327
260377
  }
260328
- var TOOL_RESULT_MAX_CHARS, TOOL_RESULT_PREVIEW_CHARS, TOOL_RESULT_OFFLOAD_MARKER, shouldOffloadToolResult, createToolResultPreview, selectToolResultBatchOffloads, buildToolResultOffloadTelemetry;
260378
+ var TOOL_RESULT_MAX_CHARS, TOOL_RESULT_PREVIEW_CHARS, TOOL_RESULT_OFFLOAD_MARKER, TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES, shouldOffloadToolResult, createToolResultPreview, selectToolResultBatchOffloads, selectHistoricalToolResultOffloads, buildToolResultOffloadTelemetry;
260329
260379
  var init_tool_result_budget = __esmMin((() => {
260330
260380
  init_dist$6();
260331
260381
  const toolResultOffloadPolicy = createRequire(import.meta.url)("./bin/tool-result-offload-policy.cjs");
@@ -260333,9 +260383,11 @@ var init_tool_result_budget = __esmMin((() => {
260333
260383
  TOOL_RESULT_MAX_CHARS = toolResultOffloadPolicy.TOOL_RESULT_MAX_CHARS;
260334
260384
  TOOL_RESULT_PREVIEW_CHARS = toolResultOffloadPolicy.TOOL_RESULT_PREVIEW_CHARS;
260335
260385
  TOOL_RESULT_OFFLOAD_MARKER = toolResultOffloadPolicy.TOOL_RESULT_OFFLOAD_MARKER;
260386
+ TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES = toolResultOffloadPolicy.TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES;
260336
260387
  shouldOffloadToolResult = toolResultOffloadPolicy.shouldOffloadToolResult;
260337
260388
  createToolResultPreview = toolResultOffloadPolicy.createToolResultPreview;
260338
260389
  selectToolResultBatchOffloads = toolResultOffloadPolicy.selectToolResultBatchOffloads;
260390
+ selectHistoricalToolResultOffloads = toolResultOffloadPolicy.selectHistoricalToolResultOffloads;
260339
260391
  }));
260340
260392
  //#endregion
260341
260393
  //#region ../../packages/agent-core/src/agent/turn/tool-result-batch-offload.ts
@@ -260353,7 +260405,20 @@ var ToolResultBatchOffload = class {
260353
260405
  if (message?.role !== "tool") break;
260354
260406
  tail.unshift(message);
260355
260407
  }
260356
- const candidates = tail.map((message) => {
260408
+ const tailIds = new Set(tail.map((message) => message.toolCallId).filter((id) => id !== void 0));
260409
+ const recentStart = Math.max(0, history.length - TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES);
260410
+ const historical = history.slice(0, recentStart).filter((message) => message?.role === "tool" && !tailIds.has(message.toolCallId));
260411
+ const candidateBatches = [{
260412
+ messages: tail,
260413
+ historical: false,
260414
+ select: selectToolResultBatchOffloads
260415
+ }, {
260416
+ messages: historical,
260417
+ historical: true,
260418
+ select: selectHistoricalToolResultOffloads
260419
+ }].map((batch) => ({
260420
+ ...batch,
260421
+ candidates: batch.messages.map((message) => {
260357
260422
  if (message.toolCallId === void 0 || this.replacements.has(message.toolCallId) || isPersistedToolResultReference(message.content)) return;
260358
260423
  const text = persistableToolResultText(message.content);
260359
260424
  if (text === void 0) return;
@@ -260364,15 +260429,19 @@ var ToolResultBatchOffload = class {
260364
260429
  toolName: toolCall?.name ?? "Tool",
260365
260430
  toolArgs: this.toolArgsFor(toolCall)
260366
260431
  };
260367
- }).filter((candidate) => candidate !== void 0);
260368
- const selected = selectToolResultBatchOffloads(candidates.map((candidate) => candidate.text.length));
260369
- if (selected.length === 0) return 0;
260432
+ }).filter((candidate) => candidate !== void 0)
260433
+ }));
260434
+ const selectedCandidates = candidateBatches.flatMap((batch) => batch.select(batch.candidates.map((candidate) => candidate.text.length)).map((index) => {
260435
+ const candidate = batch.candidates[index];
260436
+ return candidate === void 0 ? void 0 : { ...candidate, historical: batch.historical };
260437
+ }).filter((candidate) => candidate !== void 0));
260438
+ if (selectedCandidates.length === 0) return 0;
260370
260439
  const replacements = [];
260371
260440
  let charsBefore = 0;
260372
260441
  let charsAfter = 0;
260373
- for (const index of selected) {
260374
- const candidate = candidates[index];
260375
- if (candidate === void 0) continue;
260442
+ let historicalResultCount = 0;
260443
+ let tailResultCount = 0;
260444
+ for (const candidate of selectedCandidates) {
260376
260445
  const original = {
260377
260446
  output: candidate.message.content,
260378
260447
  isError: candidate.message.isError
@@ -260386,7 +260455,7 @@ var ToolResultBatchOffload = class {
260386
260455
  telemetry: this.agent.telemetry
260387
260456
  };
260388
260457
  const readSourcePath = reusableReadSourcePath(resultOptions);
260389
- const persisted = readSourcePath === void 0 ? await persistToolResultForModel(resultOptions, candidate.text) : referenceReadSourceForModel(resultOptions, candidate.text, readSourcePath);
260458
+ const persisted = candidate.historical ? await persistHistoricalToolResultForModel(resultOptions, candidate.text) : readSourcePath === void 0 ? await persistToolResultForModel(resultOptions, candidate.text) : referenceReadSourceForModel(resultOptions, candidate.text, readSourcePath);
260390
260459
  if (persisted === original || typeof persisted.output !== "string" || persisted.output.length >= candidate.text.length) continue;
260391
260460
  const content = [{
260392
260461
  type: "text",
@@ -260398,11 +260467,15 @@ var ToolResultBatchOffload = class {
260398
260467
  });
260399
260468
  charsBefore += candidate.text.length;
260400
260469
  charsAfter += persisted.output.length;
260470
+ if (candidate.historical) historicalResultCount += 1;
260471
+ else tailResultCount += 1;
260401
260472
  }
260402
260473
  if (replacements.length === 0) return 0;
260403
260474
  this.apply(replacements);
260404
260475
  this.agent.telemetry.track("tool_result_batch_offloaded", {
260405
260476
  result_count: replacements.length,
260477
+ historical_result_count: historicalResultCount,
260478
+ tail_result_count: tailResultCount,
260406
260479
  chars_before: charsBefore,
260407
260480
  chars_after: charsAfter,
260408
260481
  chars_saved: charsBefore - charsAfter
@@ -260584,6 +260657,129 @@ var UserMessageOffload = class {
260584
260657
  }
260585
260658
  };
260586
260659
  //#endregion
260660
+ //#region ../../packages/agent-core/src/agent/turn/assistant-message-offload.ts
260661
+ function persistableAssistantMessageText(message) {
260662
+ if (message?.role !== "assistant" || message.toolCalls.length > 0) return;
260663
+ const textParts = message.content.filter((part) => part.type === "text");
260664
+ if (textParts.length === 0) return;
260665
+ return textParts.map((part) => part.text).join("\n");
260666
+ }
260667
+ function assistantMessageOffloadHash(message, text) {
260668
+ return createHash("sha256").update(JSON.stringify({ text })).digest("hex");
260669
+ }
260670
+ async function saveAssistantMessage(homedir, text) {
260671
+ try {
260672
+ const dir = join$4(homedir, "conversation-history");
260673
+ await mkdir(dir, {
260674
+ recursive: true,
260675
+ mode: 448
260676
+ });
260677
+ const outputPath = join$4(dir, `assistant-message-${randomUUID()}.txt`);
260678
+ await writeFile(outputPath, text, {
260679
+ encoding: "utf8",
260680
+ flag: "wx",
260681
+ mode: 384
260682
+ });
260683
+ return outputPath;
260684
+ } catch {
260685
+ return;
260686
+ }
260687
+ }
260688
+ function renderPersistedAssistantMessage(text, outputPath) {
260689
+ return [
260690
+ ASSISTANT_MESSAGE_OFFLOAD_MARKER,
260691
+ "The complete original was archived to keep the active context fast and remains available on disk.",
260692
+ `message_size_chars: ${String(text.length)}`,
260693
+ `message_size_bytes: ${String(Buffer.byteLength(text, "utf8"))}`,
260694
+ `output_path: ${outputPath}`,
260695
+ "next_step: Use Read with output_path to inspect the complete message in pages.",
260696
+ "",
260697
+ "[preview: head and tail]",
260698
+ createAssistantMessagePreview(text)
260699
+ ].join("\n");
260700
+ }
260701
+ var ASSISTANT_MESSAGE_MAX_CHARS, ASSISTANT_MESSAGE_OFFLOAD_MARKER, shouldOffloadHistoricalAssistantMessage, createAssistantMessagePreview;
260702
+ var init_assistant_message_offload = __esmMin((() => {
260703
+ const policy = createRequire(import.meta.url)("./bin/assistant-message-offload-policy.cjs");
260704
+ ASSISTANT_MESSAGE_MAX_CHARS = policy.ASSISTANT_MESSAGE_MAX_CHARS;
260705
+ ASSISTANT_MESSAGE_OFFLOAD_MARKER = policy.ASSISTANT_MESSAGE_OFFLOAD_MARKER;
260706
+ shouldOffloadHistoricalAssistantMessage = policy.shouldOffloadHistoricalAssistantMessage;
260707
+ createAssistantMessagePreview = policy.createAssistantMessagePreview;
260708
+ }));
260709
+ var AssistantMessageOffload = class {
260710
+ agent;
260711
+ replacements = /* @__PURE__ */ new Map();
260712
+ constructor(agent) {
260713
+ this.agent = agent;
260714
+ }
260715
+ async detect() {
260716
+ const history = this.agent.context.history;
260717
+ if (this.agent.homedir === void 0) return 0;
260718
+ let offloaded = 0;
260719
+ for (let historyIndex = 0; historyIndex < history.length; historyIndex++) {
260720
+ const message = history[historyIndex];
260721
+ const text = persistableAssistantMessageText(message);
260722
+ if (text === void 0 || !shouldOffloadHistoricalAssistantMessage({
260723
+ historyIndex,
260724
+ historyLength: history.length,
260725
+ textChars: text.length
260726
+ })) continue;
260727
+ const messageHash = assistantMessageOffloadHash(message, text);
260728
+ if (this.replacements.has(messageHash)) continue;
260729
+ const outputPath = await saveAssistantMessage(this.agent.homedir, text);
260730
+ if (outputPath === void 0) continue;
260731
+ const replacementText = renderPersistedAssistantMessage(text, outputPath);
260732
+ const content = [{
260733
+ type: "text",
260734
+ text: replacementText
260735
+ }, ...message.content.filter((part) => part.type !== "text")];
260736
+ this.apply({
260737
+ messageHash,
260738
+ content,
260739
+ outputPath
260740
+ });
260741
+ this.agent.telemetry.track("assistant_message_offloaded", {
260742
+ chars_before: text.length,
260743
+ chars_after: replacementText.length,
260744
+ chars_saved: text.length - replacementText.length,
260745
+ non_text_part_count: content.length - 1,
260746
+ reason: "historical_oversize"
260747
+ });
260748
+ offloaded++;
260749
+ }
260750
+ return offloaded;
260751
+ }
260752
+ apply(replacement) {
260753
+ this.replacements.set(replacement.messageHash, replacement);
260754
+ this.agent.records.logRecord({
260755
+ type: "assistant_message_offload.apply",
260756
+ replacement
260757
+ });
260758
+ }
260759
+ compact(messages) {
260760
+ if (this.replacements.size === 0) return messages;
260761
+ return messages.map((message) => {
260762
+ const text = persistableAssistantMessageText(message);
260763
+ if (text === void 0) return message;
260764
+ const replacement = this.replacements.get(assistantMessageOffloadHash(message, text));
260765
+ return replacement === void 0 ? message : {
260766
+ ...message,
260767
+ content: replacement.content
260768
+ };
260769
+ });
260770
+ }
260771
+ reset(history = this.agent.context?.history ?? []) {
260772
+ const activeHashes = new Set(history.map((message) => {
260773
+ const text = persistableAssistantMessageText(message);
260774
+ return text === void 0 ? void 0 : assistantMessageOffloadHash(message, text);
260775
+ }).filter((hash) => hash !== void 0));
260776
+ for (const messageHash of this.replacements.keys()) if (!activeHashes.has(messageHash)) this.replacements.delete(messageHash);
260777
+ }
260778
+ clear() {
260779
+ this.replacements.clear();
260780
+ }
260781
+ };
260782
+ //#endregion
260587
260783
  //#region ../../packages/agent-core/src/agent/turn/index.ts
260588
260784
  function blunExtractText(content) {
260589
260785
  if (typeof content === "string") return content;
@@ -260920,6 +261116,7 @@ var init_turn = __esmMin((() => {
260920
261116
  init_tool_dedup();
260921
261117
  init_tool_result_budget();
260922
261118
  init_user_message_offload();
261119
+ init_assistant_message_offload();
260923
261120
  ({ CORE_TOOL_NAMES: BLUN_CORE_TOOL_NAMES, createDeferredToolLoader, toolSchemaBudgetTokens } = createRequire(import.meta.url)("./bin/turn-tool-performance-policy.cjs"));
260924
261121
  BLUN_LEAN_TOOL_NAMES = new Set(BLUN_CORE_TOOL_NAMES);
260925
261122
  BLUN_ATTACHMENT_MARKER_RE = /\b(?:attachment_file_id|telegram-anhang|telegram attachment)\b/i;
@@ -261555,6 +261752,7 @@ var init_turn = __esmMin((() => {
261555
261752
  await this.agent.records.flush();
261556
261753
  this.agent.microCompaction.detect();
261557
261754
  await this.agent.userMessageOffload.detect();
261755
+ await this.agent.assistantMessageOffload.detect();
261558
261756
  await this.agent.injection.inject();
261559
261757
  stepSignal.throwIfAborted();
261560
261758
  deduper.beginStep();
@@ -264332,6 +264530,7 @@ var init_agent = __esmMin((() => {
264332
264530
  fullCompaction;
264333
264531
  microCompaction;
264334
264532
  toolResultBatchOffload;
264533
+ assistantMessageOffload;
264335
264534
  context;
264336
264535
  config;
264337
264536
  turn;
@@ -264392,6 +264591,7 @@ var init_agent = __esmMin((() => {
264392
264591
  this.microCompaction = new MicroCompaction(this, options.microCompaction);
264393
264592
  this.toolResultBatchOffload = new ToolResultBatchOffload(this);
264394
264593
  this.userMessageOffload = new UserMessageOffload(this);
264594
+ this.assistantMessageOffload = new AssistantMessageOffload(this);
264395
264595
  this.context = new ContextMemory(this);
264396
264596
  this.config = new ConfigState(this);
264397
264597
  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.119",
3
+ "version": "9.1.121",
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": {