blun-king-cli 9.1.98 → 9.1.99

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
@@ -9,7 +9,7 @@ Installation
9
9
  ------------
10
10
  Die geprüfte Version exakt global installieren:
11
11
 
12
- npm install -g blun-king-cli@9.1.98
12
+ npm install -g blun-king-cli@9.1.99
13
13
 
14
14
  Start
15
15
  -----
@@ -91,10 +91,14 @@ Ab BLUN King 9.1.98 kann King einen abgeschlossenen Arbeitsabschnitt verdichten,
91
91
 
92
92
  Große textbasierte Werkzeugergebnisse bleiben nicht mehr vollständig im Modellkontext. Ab 12.001 Zeichen speichert BLUN King das vollständige Ergebnis in einer privaten Datei im Sitzungsordner `tool-results`. Im Modellkontext verbleiben die ersten 1.000 und die letzten 1.000 Zeichen, die genaue Zahl der ausgelassenen Zeichen und der `output_path`. Der Agent kann das vollständige Ergebnis anschließend mit `Read` seitenweise über diesen Pfad lesen. Ergebnisse bis einschließlich 12.000 Zeichen, gemischte Medienergebnisse und bereits gekürzte Ergebnisse bleiben unverändert. Auch eine spätere Mikroverdichtung bewahrt den Dateiverweis. Beispiel: Bei einem Suchergebnis mit 30.000 Zeichen sehen folgende Modellanfragen den Anfang und das abschließende Ergebnis oder den Fehler; der vollständige Text bleibt lokal verfügbar.
93
93
 
94
+ 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.
95
+
94
96
  Große Dateien werden weiterhin seitenweise gelesen. Erreicht `Read` seine interne Grenze von 1.000 Zeilen und enthält die Datei weitere Zeilen, nennt das Ergebnis jetzt den exakten nächsten `line_offset`. Beispiel: Ein Lesevorgang ab Zeile 2001 wird mit `line_offset=3001` fortgesetzt. Am Dateiende und bei einem bewusst kleineren Leseausschnitt erscheint kein Fortsetzungshinweis. Dadurch zieht der Agent keine Schlüsse aus einem unvollständigen Ausschnitt und liest die Datei nicht erneut ab der ersten Zeile.
95
97
 
96
98
  Das Telemetrieereignis `tool_result_offloaded` enthält ausschließlich `tool_name`, `output_size_chars`, `output_size_bytes` und `preview_size_chars`. Es enthält weder den Inhalt noch den Speicherpfad. Beispiel: Eine Ausgabe mit 80.000 Zeichen erzeugt eine Vorschau mit 2.000 Zeichen; die Telemetrie zeigt die Größenersparnis, ohne Nutzdaten zu protokollieren.
97
99
 
100
+ Das Telemetrieereignis `tool_result_batch_offloaded` meldet ausschließlich die Zahl der Ergebnisse sowie die Zeichenzahlen vor und nach der Entlastung und die eingesparte Zeichenzahl. Es enthält weder Ergebnisinhalte noch Speicherpfade.
101
+
98
102
  Reguläre `Agent`-Teilaufgaben verwenden eine eigene `ContextMemory` und eine eigene `wire.jsonl` in einem getrennten Agentenverzeichnis. Der Hauptagent erhält nur die Ergebniszusammenfassung, sodass lange Teilaufgaben nicht den Hauptverlauf füllen. `TodoList` speichert Pläne weiterhin maschinenlesbar im Sitzungs-Wire; `blun handoff` übergibt sie zusammen mit prüfbaren Hashes zwischen CLI, Desktop und Web.
99
103
 
100
104
  Rein lesende Sitzungsdiagnose
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.98
12
+ npm install -g blun-king-cli@9.1.99
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -111,10 +111,14 @@ Ab BLUN King 9.1.98 kann King einen abgeschlossenen Arbeitsabschnitt verdichten,
111
111
 
112
112
  Große textbasierte Werkzeugergebnisse bleiben nicht mehr vollständig im Modellkontext. Ab 12.001 Zeichen speichert BLUN King das vollständige Ergebnis in einer privaten Datei im Sitzungsordner `tool-results`. Im Modellkontext verbleiben die ersten 1.000 und die letzten 1.000 Zeichen, die genaue Zahl der ausgelassenen Zeichen und der `output_path`. Der Agent kann das vollständige Ergebnis anschließend mit `Read` seitenweise über diesen Pfad lesen. Ergebnisse bis einschließlich 12.000 Zeichen, gemischte Medienergebnisse und bereits gekürzte Ergebnisse bleiben unverändert. Auch eine spätere Mikroverdichtung bewahrt den Dateiverweis. Beispiel: Bei einem Suchergebnis mit 30.000 Zeichen sehen folgende Modellanfragen den Anfang und das abschließende Ergebnis oder den Fehler; der vollständige Text bleibt lokal verfügbar.
113
113
 
114
+ 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.
115
+
114
116
  Große Dateien werden weiterhin seitenweise gelesen. Erreicht `Read` seine interne Grenze von 1.000 Zeilen und enthält die Datei weitere Zeilen, nennt das Ergebnis jetzt den exakten nächsten `line_offset`. Beispiel: Ein Lesevorgang ab Zeile 2001 wird mit `line_offset=3001` fortgesetzt. Am Dateiende und bei einem bewusst kleineren Leseausschnitt erscheint kein Fortsetzungshinweis. Dadurch zieht der Agent keine Schlüsse aus einem unvollständigen Ausschnitt und liest die Datei nicht erneut ab der ersten Zeile.
115
117
 
116
118
  Das Telemetrieereignis `tool_result_offloaded` enthält ausschließlich `tool_name`, `output_size_chars`, `output_size_bytes` und `preview_size_chars`. Es enthält weder den Inhalt noch den Speicherpfad. Beispiel: Eine Ausgabe mit 80.000 Zeichen erzeugt eine Vorschau mit 2.000 Zeichen; die Telemetrie zeigt die Größenersparnis, ohne Nutzdaten zu protokollieren.
117
119
 
120
+ Das Telemetrieereignis `tool_result_batch_offloaded` meldet ausschließlich die Zahl der Ergebnisse sowie die Zeichenzahlen vor und nach der Entlastung und die eingesparte Zeichenzahl. Es enthält weder Ergebnisinhalte noch Speicherpfade.
121
+
118
122
  Reguläre `Agent`-Teilaufgaben verwenden eine eigene `ContextMemory` und eine eigene `wire.jsonl` in einem getrennten Agentenverzeichnis. Der Hauptagent erhält nur die Ergebniszusammenfassung, sodass lange Teilaufgaben nicht den Hauptverlauf füllen. `TodoList` speichert Pläne weiterhin maschinenlesbar im Sitzungs-Wire; `blun handoff` übergibt sie zusammen mit prüfbaren Hashes zwischen CLI, Desktop und Web.
119
123
 
120
124
  ## Rein lesende Sitzungsdiagnose
@@ -3,6 +3,9 @@
3
3
  const TOOL_RESULT_MAX_CHARS = 12_000;
4
4
  const TOOL_RESULT_PREVIEW_CHARS = 2_000;
5
5
  const TOOL_RESULT_OFFLOAD_MARKER = '[Tool result offloaded]';
6
+ const TOOL_RESULT_BATCH_MAX_CHARS = TOOL_RESULT_MAX_CHARS;
7
+ const TOOL_RESULT_BATCH_MIN_ITEM_CHARS = 3_000;
8
+ const TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS = 2_500;
6
9
 
7
10
  function shouldOffloadToolResult(textLength) {
8
11
  return Number.isFinite(textLength) && textLength > TOOL_RESULT_MAX_CHARS;
@@ -28,11 +31,46 @@ function isPersistedToolResultReference(content) {
28
31
  });
29
32
  }
30
33
 
34
+ function selectToolResultBatchOffloads(textLengths) {
35
+ if (!Array.isArray(textLengths) || textLengths.length < 2) return [];
36
+
37
+ const normalized = textLengths.map((length) => (
38
+ Number.isFinite(length) && length > 0 ? Math.floor(length) : 0
39
+ ));
40
+ const totalChars = normalized.reduce((total, length) => total + length, 0);
41
+ if (totalChars <= TOOL_RESULT_BATCH_MAX_CHARS) return [];
42
+
43
+ let projectedChars = totalChars;
44
+ const selected = [];
45
+ const candidates = normalized
46
+ .map((length, index) => ({ index, length }))
47
+ .filter(({ length }) => (
48
+ length >= TOOL_RESULT_BATCH_MIN_ITEM_CHARS
49
+ && length <= TOOL_RESULT_MAX_CHARS
50
+ && length > TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS
51
+ ))
52
+ .sort((left, right) => right.length - left.length || left.index - right.index);
53
+
54
+ for (const candidate of candidates) {
55
+ if (projectedChars <= TOOL_RESULT_BATCH_MAX_CHARS) break;
56
+ projectedChars -= candidate.length - TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS;
57
+ selected.push(candidate.index);
58
+ }
59
+
60
+ return projectedChars <= TOOL_RESULT_BATCH_MAX_CHARS
61
+ ? selected.sort((left, right) => left - right)
62
+ : [];
63
+ }
64
+
31
65
  module.exports = {
66
+ TOOL_RESULT_BATCH_MAX_CHARS,
67
+ TOOL_RESULT_BATCH_MIN_ITEM_CHARS,
68
+ TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS,
32
69
  TOOL_RESULT_MAX_CHARS,
33
70
  TOOL_RESULT_PREVIEW_CHARS,
34
71
  TOOL_RESULT_OFFLOAD_MARKER,
35
72
  createToolResultPreview,
36
73
  isPersistedToolResultReference,
74
+ selectToolResultBatchOffloads,
37
75
  shouldOffloadToolResult,
38
76
  };
package/blun.mjs CHANGED
@@ -78757,6 +78757,7 @@ var init_context$2 = __esmMin((() => {
78757
78757
  if (removedMessages.size === 0) return 0;
78758
78758
  this.agent.replayBuilder.removeLastMessages(removedMessages);
78759
78759
  this.agent.microCompaction.reset(this._history.length);
78760
+ this.agent.toolResultBatchOffload.reset(this._history);
78760
78761
  this.agent.emitStatusUpdated();
78761
78762
  return removedMessages.size;
78762
78763
  }
@@ -78770,6 +78771,7 @@ var init_context$2 = __esmMin((() => {
78770
78771
  this.deferredMessages = [];
78771
78772
  this._lastAssistantAt = null;
78772
78773
  this.agent.microCompaction.reset();
78774
+ this.agent.toolResultBatchOffload.clear();
78773
78775
  this.agent.injection.onContextClear();
78774
78776
  this.agent.emitStatusUpdated();
78775
78777
  }
@@ -78808,6 +78810,7 @@ var init_context$2 = __esmMin((() => {
78808
78810
  this.pendingToolResultIds.clear();
78809
78811
  this.deferredMessages = [];
78810
78812
  this.agent.microCompaction.reset(this._history.length);
78813
+ this.agent.toolResultBatchOffload.reset(this._history);
78811
78814
  this.agent.emitStatusUpdated();
78812
78815
  if (!this.agent.records.restoring && (stoppedAtBoundary || removedUserCount < count)) throw new BlunError(ErrorCodes.REQUEST_INVALID, formatUndoUnavailableMessage(count, removedUserCount, stoppedAtBoundary), { details: {
78813
78816
  reason: "undo_limit",
@@ -78886,6 +78889,7 @@ var init_context$2 = __esmMin((() => {
78886
78889
  this._tokenCount = result.tokensAfter;
78887
78890
  this.tokenCountCoveredMessageCount = this._history.length;
78888
78891
  this.agent.microCompaction.reset();
78892
+ this.agent.toolResultBatchOffload.reset(this._history);
78889
78893
  this.agent.injection.onContextCompacted();
78890
78894
  this.agent.emitStatusUpdated();
78891
78895
  return result;
@@ -78908,7 +78912,7 @@ var init_context$2 = __esmMin((() => {
78908
78912
  }
78909
78913
  project(messages, options) {
78910
78914
  const anomalies = [];
78911
- const result = project(this.agent.microCompaction.compact(messages), {
78915
+ const result = project(this.agent.microCompaction.compact(this.agent.toolResultBatchOffload.compact(messages)), {
78912
78916
  ...options,
78913
78917
  onAnomaly: (anomaly) => {
78914
78918
  anomalies.push(anomaly);
@@ -234828,6 +234832,9 @@ function restoreAgentRecord(agent, input) {
234828
234832
  case "micro_compaction.apply":
234829
234833
  agent.microCompaction.apply(input.cutoff);
234830
234834
  return;
234835
+ case "tool_result_batch_offload.apply":
234836
+ agent.toolResultBatchOffload.apply(input.replacements);
234837
+ return;
234831
234838
  case "plan_mode.enter":
234832
234839
  agent.planMode.restoreEnter(input);
234833
234840
  return;
@@ -260015,6 +260022,11 @@ var init_tool_dedup = __esmMin((() => {
260015
260022
  async function budgetToolResultForModel(options) {
260016
260023
  const text = persistableToolResultText(options.result.output);
260017
260024
  if (text === void 0 || !shouldOffloadToolResult(text.length)) return options.result;
260025
+ return persistToolResultForModel(options, text);
260026
+ }
260027
+ async function persistToolResultForModel(options, knownText) {
260028
+ const text = knownText ?? persistableToolResultText(options.result.output);
260029
+ if (text === void 0) return options.result;
260018
260030
  if (options.result.truncated === true) return options.result;
260019
260031
  if (options.homedir === void 0) return options.result;
260020
260032
  const outputPath = await saveToolResult({
@@ -260078,7 +260090,7 @@ function renderPersistedToolResult(toolName, toolCallId, text, outputPath) {
260078
260090
  function safeToolResultFileStem(toolName, toolCallId) {
260079
260091
  return `${toolName}-${toolCallId}`.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80) || "tool-result";
260080
260092
  }
260081
- var TOOL_RESULT_MAX_CHARS, TOOL_RESULT_PREVIEW_CHARS, TOOL_RESULT_OFFLOAD_MARKER, shouldOffloadToolResult, createToolResultPreview, buildToolResultOffloadTelemetry;
260093
+ var TOOL_RESULT_MAX_CHARS, TOOL_RESULT_PREVIEW_CHARS, TOOL_RESULT_OFFLOAD_MARKER, shouldOffloadToolResult, createToolResultPreview, selectToolResultBatchOffloads, buildToolResultOffloadTelemetry;
260082
260094
  var init_tool_result_budget = __esmMin((() => {
260083
260095
  init_dist$6();
260084
260096
  const toolResultOffloadPolicy = createRequire(import.meta.url)("./bin/tool-result-offload-policy.cjs");
@@ -260088,7 +260100,110 @@ var init_tool_result_budget = __esmMin((() => {
260088
260100
  TOOL_RESULT_OFFLOAD_MARKER = toolResultOffloadPolicy.TOOL_RESULT_OFFLOAD_MARKER;
260089
260101
  shouldOffloadToolResult = toolResultOffloadPolicy.shouldOffloadToolResult;
260090
260102
  createToolResultPreview = toolResultOffloadPolicy.createToolResultPreview;
260091
- }));
260103
+ selectToolResultBatchOffloads = toolResultOffloadPolicy.selectToolResultBatchOffloads;
260104
+ }));
260105
+ //#endregion
260106
+ //#region ../../packages/agent-core/src/agent/turn/tool-result-batch-offload.ts
260107
+ var ToolResultBatchOffload = class {
260108
+ agent;
260109
+ replacements = /* @__PURE__ */ new Map();
260110
+ constructor(agent) {
260111
+ this.agent = agent;
260112
+ }
260113
+ async detect() {
260114
+ const history = this.agent.context.history;
260115
+ const tail = [];
260116
+ for (let index = history.length - 1; index >= 0; index -= 1) {
260117
+ const message = history[index];
260118
+ if (message?.role !== "tool") break;
260119
+ tail.unshift(message);
260120
+ }
260121
+ const candidates = tail.map((message) => {
260122
+ if (message.toolCallId === void 0 || this.replacements.has(message.toolCallId) || isPersistedToolResultReference(message.content)) return;
260123
+ const text = persistableToolResultText(message.content);
260124
+ if (text === void 0) return;
260125
+ return {
260126
+ message,
260127
+ text,
260128
+ toolName: this.toolNameFor(history, message.toolCallId)
260129
+ };
260130
+ }).filter((candidate) => candidate !== void 0);
260131
+ const selected = selectToolResultBatchOffloads(candidates.map((candidate) => candidate.text.length));
260132
+ if (selected.length === 0) return 0;
260133
+ const replacements = [];
260134
+ let charsBefore = 0;
260135
+ let charsAfter = 0;
260136
+ for (const index of selected) {
260137
+ const candidate = candidates[index];
260138
+ if (candidate === void 0) continue;
260139
+ const original = {
260140
+ output: candidate.message.content,
260141
+ isError: candidate.message.isError
260142
+ };
260143
+ const persisted = await persistToolResultForModel({
260144
+ homedir: this.agent.homedir,
260145
+ toolName: candidate.toolName,
260146
+ toolCallId: candidate.message.toolCallId,
260147
+ result: original,
260148
+ telemetry: this.agent.telemetry
260149
+ }, candidate.text);
260150
+ if (persisted === original || typeof persisted.output !== "string" || persisted.output.length >= candidate.text.length) continue;
260151
+ const content = [{
260152
+ type: "text",
260153
+ text: persisted.output
260154
+ }];
260155
+ replacements.push({
260156
+ toolCallId: candidate.message.toolCallId,
260157
+ content
260158
+ });
260159
+ charsBefore += candidate.text.length;
260160
+ charsAfter += persisted.output.length;
260161
+ }
260162
+ if (replacements.length === 0) return 0;
260163
+ this.apply(replacements);
260164
+ this.agent.telemetry.track("tool_result_batch_offloaded", {
260165
+ result_count: replacements.length,
260166
+ chars_before: charsBefore,
260167
+ chars_after: charsAfter,
260168
+ chars_saved: charsBefore - charsAfter
260169
+ });
260170
+ return replacements.length;
260171
+ }
260172
+ apply(replacements) {
260173
+ for (const replacement of replacements) this.replacements.set(replacement.toolCallId, replacement.content);
260174
+ this.agent.records.logRecord({
260175
+ type: "tool_result_batch_offload.apply",
260176
+ replacements
260177
+ });
260178
+ }
260179
+ compact(messages) {
260180
+ if (this.replacements.size === 0) return messages;
260181
+ return messages.map((message) => {
260182
+ if (message.role !== "tool" || message.toolCallId === void 0) return message;
260183
+ const content = this.replacements.get(message.toolCallId);
260184
+ return content === void 0 ? message : {
260185
+ ...message,
260186
+ content
260187
+ };
260188
+ });
260189
+ }
260190
+ reset(history = this.agent.context?.history ?? []) {
260191
+ const activeIds = new Set(history.filter((message) => message.role === "tool" && message.toolCallId !== void 0).map((message) => message.toolCallId));
260192
+ for (const toolCallId of this.replacements.keys()) if (!activeIds.has(toolCallId)) this.replacements.delete(toolCallId);
260193
+ }
260194
+ clear() {
260195
+ this.replacements.clear();
260196
+ }
260197
+ toolNameFor(history, toolCallId) {
260198
+ for (let index = history.length - 1; index >= 0; index -= 1) {
260199
+ const message = history[index];
260200
+ if (message?.role !== "assistant" || !Array.isArray(message.toolCalls)) continue;
260201
+ const call = message.toolCalls.find((candidate) => candidate.id === toolCallId);
260202
+ if (call !== void 0) return call.name;
260203
+ }
260204
+ return "Tool";
260205
+ }
260206
+ };
260092
260207
  //#endregion
260093
260208
  //#region ../../packages/agent-core/src/agent/turn/index.ts
260094
260209
  function blunExtractText(content) {
@@ -261110,6 +261225,7 @@ var init_turn = __esmMin((() => {
261110
261225
  previousStepToolOutcome = currentStepHadTool ? currentStepHadFailure ? "failure" : "success" : "none";
261111
261226
  this.agent.usage.record(this.agent.activeResponderModel ?? model, usage, "turn");
261112
261227
  if (stopForGoalBudget) this.setActiveSteerAcceptance(turnId, false);
261228
+ await this.agent.toolResultBatchOffload.detect();
261113
261229
  await this.agent.fullCompaction.afterStep(signal);
261114
261230
  if (compactConversationTool !== void 0 && this.agent.fullCompaction.isProactiveCompactionEligible() && !selectedTools.includes(compactConversationTool)) selectedTools.push(compactConversationTool);
261115
261231
  deduper.endStep();
@@ -263807,6 +263923,7 @@ var init_agent = __esmMin((() => {
263807
263923
  records;
263808
263924
  fullCompaction;
263809
263925
  microCompaction;
263926
+ toolResultBatchOffload;
263810
263927
  context;
263811
263928
  config;
263812
263929
  turn;
@@ -263864,6 +263981,7 @@ var init_agent = __esmMin((() => {
263864
263981
  }) : void 0));
263865
263982
  this.fullCompaction = new FullCompaction(this, options.compactionStrategy);
263866
263983
  this.microCompaction = new MicroCompaction(this, options.microCompaction);
263984
+ this.toolResultBatchOffload = new ToolResultBatchOffload(this);
263867
263985
  this.context = new ContextMemory(this);
263868
263986
  this.config = new ConfigState(this);
263869
263987
  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.98",
3
+ "version": "9.1.99",
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": {