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 +3 -1
- package/README.md +3 -1
- package/bin/grep-output-limit-policy.cjs +39 -0
- package/blun.mjs +48 -16
- package/package.json +1 -1
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
257732
|
-
total +=
|
|
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, {
|
|
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, {
|
|
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
|
|
258526
|
-
|
|
258527
|
-
|
|
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
|
}
|