blun-king-cli 9.1.99 → 9.1.100

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.99
12
+ npm install -g blun-king-cli@9.1.100
13
13
 
14
14
  Start
15
15
  -----
@@ -93,6 +93,8 @@ Große textbasierte Werkzeugergebnisse bleiben nicht mehr vollständig im Modell
93
93
 
94
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
95
 
96
+ 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.
97
+
96
98
  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.
97
99
 
98
100
  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.
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.99
12
+ npm install -g blun-king-cli@9.1.100
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -113,6 +113,8 @@ Große textbasierte Werkzeugergebnisse bleiben nicht mehr vollständig im Modell
113
113
 
114
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
115
 
116
+ 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.
117
+
116
118
  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.
117
119
 
118
120
  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.
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ function grepContentRecordLimit(args = {}) {
4
+ const mode = args.output_mode ?? "files_with_matches";
5
+ const headLimit = args.head_limit ?? 250;
6
+ const offset = args.offset ?? 0;
7
+ if (mode !== "content" || !Number.isSafeInteger(headLimit) || headLimit <= 0) return undefined;
8
+ if (!Number.isSafeInteger(offset) || offset < 0) return undefined;
9
+ const limit = offset + headLimit + 1;
10
+ return Number.isSafeInteger(limit) ? limit : undefined;
11
+ }
12
+
13
+ function grepPaginationNotice({ headLimit, offset, afterOffsetLength, earlyStopped = false }) {
14
+ const nextOffset = offset + headLimit;
15
+ const observedTotal = afterOffsetLength + offset;
16
+ const total = earlyStopped ? `at least ${String(observedTotal)}` : String(observedTotal);
17
+ return `Results truncated to ${String(headLimit)} lines (total: ${total}). Use offset=${String(nextOffset)} to see more.`;
18
+ }
19
+
20
+ function sliceThroughNewlineLimit(buffer, remainingNewlines) {
21
+ if (!Number.isSafeInteger(remainingNewlines) || remainingNewlines <= 0) {
22
+ return { accepted: buffer, newlineCount: 0, stopped: false };
23
+ }
24
+ let newlineCount = 0;
25
+ for (let index = 0; index < buffer.length; index += 1) {
26
+ if (buffer[index] !== 10) continue;
27
+ newlineCount += 1;
28
+ if (newlineCount === remainingNewlines) {
29
+ return { accepted: buffer.subarray(0, index + 1), newlineCount, stopped: true };
30
+ }
31
+ }
32
+ return { accepted: buffer, newlineCount, stopped: false };
33
+ }
34
+
35
+ module.exports = {
36
+ grepContentRecordLimit,
37
+ grepPaginationNotice,
38
+ sliceThroughNewlineLimit,
39
+ };
package/blun.mjs CHANGED
@@ -257665,10 +257665,17 @@ async function runRipgrepOnce(kaos, rgArgs, signal, options = {}) {
257665
257665
  let stderrText = "";
257666
257666
  let bufferTruncated = false;
257667
257667
  let stderrTruncated = false;
257668
+ let earlyStopped = false;
257668
257669
  try {
257669
- const isTerminating = () => timedOut || aborted || killed;
257670
+ const isTerminating = () => timedOut || aborted || killed || earlyStopped;
257670
257671
  const [stdoutResult, stderrResult, code] = await Promise.all([
257671
- readStreamWithCap(proc.stdout, MAX_OUTPUT_BYTES$1, isTerminating),
257672
+ readStreamWithCap(proc.stdout, MAX_OUTPUT_BYTES$1, isTerminating, {
257673
+ stopAfterDelimiterCount: options.stdoutRecordLimit,
257674
+ onStop: () => {
257675
+ earlyStopped = true;
257676
+ void killProc();
257677
+ }
257678
+ }),
257672
257679
  readStreamWithCap(proc.stderr, MAX_OUTPUT_BYTES$1, isTerminating),
257673
257680
  proc.wait()
257674
257681
  ]);
@@ -257704,7 +257711,8 @@ async function runRipgrepOnce(kaos, rgArgs, signal, options = {}) {
257704
257711
  stderrText,
257705
257712
  bufferTruncated,
257706
257713
  stderrTruncated,
257707
- timedOut
257714
+ timedOut,
257715
+ earlyStopped
257708
257716
  };
257709
257717
  }
257710
257718
  function shouldRetryRipgrepEagain(result) {
@@ -257713,23 +257721,37 @@ function shouldRetryRipgrepEagain(result) {
257713
257721
  function isEagainRipgrepError(stderr) {
257714
257722
  return stderr.includes("os error 11") || stderr.includes("Resource temporarily unavailable");
257715
257723
  }
257716
- async function readStreamWithCap(stream, maxBytes, suppressPrematureClose) {
257724
+ async function readStreamWithCap(stream, maxBytes, suppressPrematureClose, options = {}) {
257717
257725
  const chunks = [];
257718
257726
  let total = 0;
257719
257727
  let truncated = false;
257728
+ let delimiterCount = 0;
257720
257729
  try {
257721
257730
  for await (const chunk of stream) {
257722
257731
  const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
257723
257732
  if (truncated) continue;
257724
- if (total + buf.length > maxBytes) {
257733
+ let accepted = buf;
257734
+ let stoppedAtRecordLimit = false;
257735
+ const delimiterLimit = options.stopAfterDelimiterCount;
257736
+ if (Number.isSafeInteger(delimiterLimit) && delimiterLimit > 0) {
257737
+ const slice = sliceThroughNewlineLimit(buf, delimiterLimit - delimiterCount);
257738
+ accepted = slice.accepted;
257739
+ delimiterCount += slice.newlineCount;
257740
+ stoppedAtRecordLimit = slice.stopped;
257741
+ }
257742
+ if (total + accepted.length > maxBytes) {
257725
257743
  const remaining = maxBytes - total;
257726
- if (remaining > 0) chunks.push(buf.subarray(0, remaining));
257744
+ if (remaining > 0) chunks.push(accepted.subarray(0, remaining));
257727
257745
  total = maxBytes;
257728
257746
  truncated = true;
257729
257747
  continue;
257730
257748
  }
257731
- chunks.push(buf);
257732
- total += buf.length;
257749
+ chunks.push(accepted);
257750
+ total += accepted.length;
257751
+ if (stoppedAtRecordLimit) {
257752
+ options.onStop?.();
257753
+ break;
257754
+ }
257733
257755
  }
257734
257756
  } catch (error) {
257735
257757
  if (!isPrematureCloseError(error) || suppressPrematureClose?.() !== true) throw error;
@@ -258358,7 +258380,7 @@ function countPayloadFromLegacyLine(line) {
258358
258380
  const idx = line.lastIndexOf(":");
258359
258381
  return idx > 0 ? line.slice(idx + 1) : void 0;
258360
258382
  }
258361
- var GrepInputSchema, RG_MAX_COLUMNS, DEFAULT_HEAD_LIMIT, MTIME_STAT_CONCURRENCY, CONTENT_LINE_RE, GrepTool, GrepAbortedError;
258383
+ var GrepInputSchema, RG_MAX_COLUMNS, DEFAULT_HEAD_LIMIT, MTIME_STAT_CONCURRENCY, CONTENT_LINE_RE, GrepTool, GrepAbortedError, grepContentRecordLimit, grepPaginationNotice, sliceThroughNewlineLimit;
258362
258384
  var init_grep = __esmMin((() => {
258363
258385
  init_dist$6();
258364
258386
  init_zod$1();
@@ -258373,6 +258395,7 @@ var init_grep = __esmMin((() => {
258373
258395
  init_result_builder();
258374
258396
  init_run_rg();
258375
258397
  init_grep$1();
258398
+ ({ grepContentRecordLimit, grepPaginationNotice, sliceThroughNewlineLimit } = createRequire(import.meta.url)("./bin/grep-output-limit-policy.cjs"));
258376
258399
  GrepInputSchema = object({
258377
258400
  pattern: string().describe("Regular expression to search for."),
258378
258401
  path: string().optional().describe("File or directory to search. Accepts an absolute path, or a path relative to the current working directory. Omit to search the current working directory. Use Read instead when you already know a concrete file path and need its contents."),
@@ -258473,15 +258496,21 @@ var init_grep = __esmMin((() => {
258473
258496
  output: rgUnavailableMessage(error)
258474
258497
  };
258475
258498
  }
258476
- let runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths), signal, { abortedMessage: "Grep aborted" });
258499
+ let runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths), signal, {
258500
+ abortedMessage: "Grep aborted",
258501
+ stdoutRecordLimit: grepContentRecordLimit(args)
258502
+ });
258477
258503
  if (runResult.kind === "tool-error") return runResult.result;
258478
258504
  if (shouldRetryRipgrepEagain(runResult)) {
258479
- runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths, true), signal, { abortedMessage: "Grep aborted" });
258505
+ runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths, true), signal, {
258506
+ abortedMessage: "Grep aborted",
258507
+ stdoutRecordLimit: grepContentRecordLimit(args)
258508
+ });
258480
258509
  if (runResult.kind === "tool-error") return runResult.result;
258481
258510
  }
258482
- const { exitCode, stderrText, bufferTruncated, stderrTruncated, timedOut } = runResult;
258511
+ const { exitCode, stderrText, bufferTruncated, stderrTruncated, timedOut, earlyStopped } = runResult;
258483
258512
  let { stdoutText } = runResult;
258484
- if (exitCode !== 0 && exitCode !== 1 && !timedOut) return {
258513
+ if (exitCode !== 0 && exitCode !== 1 && !timedOut && !earlyStopped) return {
258485
258514
  isError: true,
258486
258515
  output: formatRipgrepError(exitCode, stderrText, stderrTruncated)
258487
258516
  };
@@ -258522,9 +258551,12 @@ var init_grep = __esmMin((() => {
258522
258551
  }
258523
258552
  if (mode === "count_matches" && orderedLines.length > 0) headerLines.push(formatCountSummary(orderedLines, filteredSensitive.size > 0));
258524
258553
  if (paginationTruncated) {
258525
- const total = afterOffset.length + offset;
258526
- const nextOffset = offset + headLimit;
258527
- const paginationNotice = `Results truncated to ${String(headLimit)} lines (total: ${String(total)}). Use offset=${String(nextOffset)} to see more.`;
258554
+ const paginationNotice = grepPaginationNotice({
258555
+ headLimit,
258556
+ offset,
258557
+ afterOffsetLength: afterOffset.length,
258558
+ earlyStopped
258559
+ });
258528
258560
  if (mode === "count_matches") headerLines.push(paginationNotice);
258529
258561
  else messages.push(paginationNotice);
258530
258562
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.99",
3
+ "version": "9.1.100",
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": {