blun-king-cli 9.1.99 → 9.1.101
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/bin/user-message-offload-policy.cjs +37 -0
- package/blun.mjs +174 -17
- 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
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const USER_MESSAGE_MAX_CHARS = 200_000;
|
|
4
|
+
const USER_MESSAGE_PREVIEW_LINES = 5;
|
|
5
|
+
const USER_MESSAGE_PREVIEW_LINE_CHARS = 1_000;
|
|
6
|
+
const USER_MESSAGE_OFFLOAD_MARKER = '[User message offloaded]';
|
|
7
|
+
|
|
8
|
+
function shouldOffloadUserMessage(textChars) {
|
|
9
|
+
return Number.isSafeInteger(textChars) && textChars > USER_MESSAGE_MAX_CHARS;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createUserMessagePreview(text) {
|
|
13
|
+
const lines = String(text).split(/\r?\n/u);
|
|
14
|
+
const render = (line, index) => `${index}: ${line.slice(0, USER_MESSAGE_PREVIEW_LINE_CHARS)}`;
|
|
15
|
+
if (lines.length <= USER_MESSAGE_PREVIEW_LINES * 2) {
|
|
16
|
+
return lines.map((line, index) => render(line, index + 1)).join('\n');
|
|
17
|
+
}
|
|
18
|
+
const head = lines.slice(0, USER_MESSAGE_PREVIEW_LINES)
|
|
19
|
+
.map((line, index) => render(line, index + 1));
|
|
20
|
+
const tailStart = lines.length - USER_MESSAGE_PREVIEW_LINES;
|
|
21
|
+
const tail = lines.slice(tailStart)
|
|
22
|
+
.map((line, index) => render(line, tailStart + index + 1));
|
|
23
|
+
return [
|
|
24
|
+
...head,
|
|
25
|
+
`... [${lines.length - USER_MESSAGE_PREVIEW_LINES * 2} lines omitted] ...`,
|
|
26
|
+
...tail,
|
|
27
|
+
].join('\n');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
USER_MESSAGE_MAX_CHARS,
|
|
32
|
+
USER_MESSAGE_OFFLOAD_MARKER,
|
|
33
|
+
USER_MESSAGE_PREVIEW_LINES,
|
|
34
|
+
USER_MESSAGE_PREVIEW_LINE_CHARS,
|
|
35
|
+
createUserMessagePreview,
|
|
36
|
+
shouldOffloadUserMessage,
|
|
37
|
+
};
|
package/blun.mjs
CHANGED
|
@@ -78758,6 +78758,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78758
78758
|
this.agent.replayBuilder.removeLastMessages(removedMessages);
|
|
78759
78759
|
this.agent.microCompaction.reset(this._history.length);
|
|
78760
78760
|
this.agent.toolResultBatchOffload.reset(this._history);
|
|
78761
|
+
this.agent.userMessageOffload.reset(this._history);
|
|
78761
78762
|
this.agent.emitStatusUpdated();
|
|
78762
78763
|
return removedMessages.size;
|
|
78763
78764
|
}
|
|
@@ -78772,6 +78773,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78772
78773
|
this._lastAssistantAt = null;
|
|
78773
78774
|
this.agent.microCompaction.reset();
|
|
78774
78775
|
this.agent.toolResultBatchOffload.clear();
|
|
78776
|
+
this.agent.userMessageOffload.clear();
|
|
78775
78777
|
this.agent.injection.onContextClear();
|
|
78776
78778
|
this.agent.emitStatusUpdated();
|
|
78777
78779
|
}
|
|
@@ -78811,6 +78813,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78811
78813
|
this.deferredMessages = [];
|
|
78812
78814
|
this.agent.microCompaction.reset(this._history.length);
|
|
78813
78815
|
this.agent.toolResultBatchOffload.reset(this._history);
|
|
78816
|
+
this.agent.userMessageOffload.reset(this._history);
|
|
78814
78817
|
this.agent.emitStatusUpdated();
|
|
78815
78818
|
if (!this.agent.records.restoring && (stoppedAtBoundary || removedUserCount < count)) throw new BlunError(ErrorCodes.REQUEST_INVALID, formatUndoUnavailableMessage(count, removedUserCount, stoppedAtBoundary), { details: {
|
|
78816
78819
|
reason: "undo_limit",
|
|
@@ -78890,6 +78893,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78890
78893
|
this.tokenCountCoveredMessageCount = this._history.length;
|
|
78891
78894
|
this.agent.microCompaction.reset();
|
|
78892
78895
|
this.agent.toolResultBatchOffload.reset(this._history);
|
|
78896
|
+
this.agent.userMessageOffload.reset(this._history);
|
|
78893
78897
|
this.agent.injection.onContextCompacted();
|
|
78894
78898
|
this.agent.emitStatusUpdated();
|
|
78895
78899
|
return result;
|
|
@@ -78912,7 +78916,7 @@ var init_context$2 = __esmMin((() => {
|
|
|
78912
78916
|
}
|
|
78913
78917
|
project(messages, options) {
|
|
78914
78918
|
const anomalies = [];
|
|
78915
|
-
const result = project(this.agent.microCompaction.compact(this.agent.toolResultBatchOffload.compact(messages)), {
|
|
78919
|
+
const result = project(this.agent.userMessageOffload.compact(this.agent.microCompaction.compact(this.agent.toolResultBatchOffload.compact(messages))), {
|
|
78916
78920
|
...options,
|
|
78917
78921
|
onAnomaly: (anomaly) => {
|
|
78918
78922
|
anomalies.push(anomaly);
|
|
@@ -234835,6 +234839,9 @@ function restoreAgentRecord(agent, input) {
|
|
|
234835
234839
|
case "tool_result_batch_offload.apply":
|
|
234836
234840
|
agent.toolResultBatchOffload.apply(input.replacements);
|
|
234837
234841
|
return;
|
|
234842
|
+
case "user_message_offload.apply":
|
|
234843
|
+
agent.userMessageOffload.apply(input.replacement);
|
|
234844
|
+
return;
|
|
234838
234845
|
case "plan_mode.enter":
|
|
234839
234846
|
agent.planMode.restoreEnter(input);
|
|
234840
234847
|
return;
|
|
@@ -257665,10 +257672,17 @@ async function runRipgrepOnce(kaos, rgArgs, signal, options = {}) {
|
|
|
257665
257672
|
let stderrText = "";
|
|
257666
257673
|
let bufferTruncated = false;
|
|
257667
257674
|
let stderrTruncated = false;
|
|
257675
|
+
let earlyStopped = false;
|
|
257668
257676
|
try {
|
|
257669
|
-
const isTerminating = () => timedOut || aborted || killed;
|
|
257677
|
+
const isTerminating = () => timedOut || aborted || killed || earlyStopped;
|
|
257670
257678
|
const [stdoutResult, stderrResult, code] = await Promise.all([
|
|
257671
|
-
readStreamWithCap(proc.stdout, MAX_OUTPUT_BYTES$1, isTerminating
|
|
257679
|
+
readStreamWithCap(proc.stdout, MAX_OUTPUT_BYTES$1, isTerminating, {
|
|
257680
|
+
stopAfterDelimiterCount: options.stdoutRecordLimit,
|
|
257681
|
+
onStop: () => {
|
|
257682
|
+
earlyStopped = true;
|
|
257683
|
+
void killProc();
|
|
257684
|
+
}
|
|
257685
|
+
}),
|
|
257672
257686
|
readStreamWithCap(proc.stderr, MAX_OUTPUT_BYTES$1, isTerminating),
|
|
257673
257687
|
proc.wait()
|
|
257674
257688
|
]);
|
|
@@ -257704,7 +257718,8 @@ async function runRipgrepOnce(kaos, rgArgs, signal, options = {}) {
|
|
|
257704
257718
|
stderrText,
|
|
257705
257719
|
bufferTruncated,
|
|
257706
257720
|
stderrTruncated,
|
|
257707
|
-
timedOut
|
|
257721
|
+
timedOut,
|
|
257722
|
+
earlyStopped
|
|
257708
257723
|
};
|
|
257709
257724
|
}
|
|
257710
257725
|
function shouldRetryRipgrepEagain(result) {
|
|
@@ -257713,23 +257728,37 @@ function shouldRetryRipgrepEagain(result) {
|
|
|
257713
257728
|
function isEagainRipgrepError(stderr) {
|
|
257714
257729
|
return stderr.includes("os error 11") || stderr.includes("Resource temporarily unavailable");
|
|
257715
257730
|
}
|
|
257716
|
-
async function readStreamWithCap(stream, maxBytes, suppressPrematureClose) {
|
|
257731
|
+
async function readStreamWithCap(stream, maxBytes, suppressPrematureClose, options = {}) {
|
|
257717
257732
|
const chunks = [];
|
|
257718
257733
|
let total = 0;
|
|
257719
257734
|
let truncated = false;
|
|
257735
|
+
let delimiterCount = 0;
|
|
257720
257736
|
try {
|
|
257721
257737
|
for await (const chunk of stream) {
|
|
257722
257738
|
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
257723
257739
|
if (truncated) continue;
|
|
257724
|
-
|
|
257740
|
+
let accepted = buf;
|
|
257741
|
+
let stoppedAtRecordLimit = false;
|
|
257742
|
+
const delimiterLimit = options.stopAfterDelimiterCount;
|
|
257743
|
+
if (Number.isSafeInteger(delimiterLimit) && delimiterLimit > 0) {
|
|
257744
|
+
const slice = sliceThroughNewlineLimit(buf, delimiterLimit - delimiterCount);
|
|
257745
|
+
accepted = slice.accepted;
|
|
257746
|
+
delimiterCount += slice.newlineCount;
|
|
257747
|
+
stoppedAtRecordLimit = slice.stopped;
|
|
257748
|
+
}
|
|
257749
|
+
if (total + accepted.length > maxBytes) {
|
|
257725
257750
|
const remaining = maxBytes - total;
|
|
257726
|
-
if (remaining > 0) chunks.push(
|
|
257751
|
+
if (remaining > 0) chunks.push(accepted.subarray(0, remaining));
|
|
257727
257752
|
total = maxBytes;
|
|
257728
257753
|
truncated = true;
|
|
257729
257754
|
continue;
|
|
257730
257755
|
}
|
|
257731
|
-
chunks.push(
|
|
257732
|
-
total +=
|
|
257756
|
+
chunks.push(accepted);
|
|
257757
|
+
total += accepted.length;
|
|
257758
|
+
if (stoppedAtRecordLimit) {
|
|
257759
|
+
options.onStop?.();
|
|
257760
|
+
break;
|
|
257761
|
+
}
|
|
257733
257762
|
}
|
|
257734
257763
|
} catch (error) {
|
|
257735
257764
|
if (!isPrematureCloseError(error) || suppressPrematureClose?.() !== true) throw error;
|
|
@@ -258358,7 +258387,7 @@ function countPayloadFromLegacyLine(line) {
|
|
|
258358
258387
|
const idx = line.lastIndexOf(":");
|
|
258359
258388
|
return idx > 0 ? line.slice(idx + 1) : void 0;
|
|
258360
258389
|
}
|
|
258361
|
-
var GrepInputSchema, RG_MAX_COLUMNS, DEFAULT_HEAD_LIMIT, MTIME_STAT_CONCURRENCY, CONTENT_LINE_RE, GrepTool, GrepAbortedError;
|
|
258390
|
+
var GrepInputSchema, RG_MAX_COLUMNS, DEFAULT_HEAD_LIMIT, MTIME_STAT_CONCURRENCY, CONTENT_LINE_RE, GrepTool, GrepAbortedError, grepContentRecordLimit, grepPaginationNotice, sliceThroughNewlineLimit;
|
|
258362
258391
|
var init_grep = __esmMin((() => {
|
|
258363
258392
|
init_dist$6();
|
|
258364
258393
|
init_zod$1();
|
|
@@ -258373,6 +258402,7 @@ var init_grep = __esmMin((() => {
|
|
|
258373
258402
|
init_result_builder();
|
|
258374
258403
|
init_run_rg();
|
|
258375
258404
|
init_grep$1();
|
|
258405
|
+
({ grepContentRecordLimit, grepPaginationNotice, sliceThroughNewlineLimit } = createRequire(import.meta.url)("./bin/grep-output-limit-policy.cjs"));
|
|
258376
258406
|
GrepInputSchema = object({
|
|
258377
258407
|
pattern: string().describe("Regular expression to search for."),
|
|
258378
258408
|
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 +258503,21 @@ var init_grep = __esmMin((() => {
|
|
|
258473
258503
|
output: rgUnavailableMessage(error)
|
|
258474
258504
|
};
|
|
258475
258505
|
}
|
|
258476
|
-
let runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths), signal, {
|
|
258506
|
+
let runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths), signal, {
|
|
258507
|
+
abortedMessage: "Grep aborted",
|
|
258508
|
+
stdoutRecordLimit: grepContentRecordLimit(args)
|
|
258509
|
+
});
|
|
258477
258510
|
if (runResult.kind === "tool-error") return runResult.result;
|
|
258478
258511
|
if (shouldRetryRipgrepEagain(runResult)) {
|
|
258479
|
-
runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths, true), signal, {
|
|
258512
|
+
runResult = await runRipgrepOnce(this.kaos, buildRgArgs(rgPath, args, searchPaths, true), signal, {
|
|
258513
|
+
abortedMessage: "Grep aborted",
|
|
258514
|
+
stdoutRecordLimit: grepContentRecordLimit(args)
|
|
258515
|
+
});
|
|
258480
258516
|
if (runResult.kind === "tool-error") return runResult.result;
|
|
258481
258517
|
}
|
|
258482
|
-
const { exitCode, stderrText, bufferTruncated, stderrTruncated, timedOut } = runResult;
|
|
258518
|
+
const { exitCode, stderrText, bufferTruncated, stderrTruncated, timedOut, earlyStopped } = runResult;
|
|
258483
258519
|
let { stdoutText } = runResult;
|
|
258484
|
-
if (exitCode !== 0 && exitCode !== 1 && !timedOut) return {
|
|
258520
|
+
if (exitCode !== 0 && exitCode !== 1 && !timedOut && !earlyStopped) return {
|
|
258485
258521
|
isError: true,
|
|
258486
258522
|
output: formatRipgrepError(exitCode, stderrText, stderrTruncated)
|
|
258487
258523
|
};
|
|
@@ -258522,9 +258558,12 @@ var init_grep = __esmMin((() => {
|
|
|
258522
258558
|
}
|
|
258523
258559
|
if (mode === "count_matches" && orderedLines.length > 0) headerLines.push(formatCountSummary(orderedLines, filteredSensitive.size > 0));
|
|
258524
258560
|
if (paginationTruncated) {
|
|
258525
|
-
const
|
|
258526
|
-
|
|
258527
|
-
|
|
258561
|
+
const paginationNotice = grepPaginationNotice({
|
|
258562
|
+
headLimit,
|
|
258563
|
+
offset,
|
|
258564
|
+
afterOffsetLength: afterOffset.length,
|
|
258565
|
+
earlyStopped
|
|
258566
|
+
});
|
|
258528
258567
|
if (mode === "count_matches") headerLines.push(paginationNotice);
|
|
258529
258568
|
else messages.push(paginationNotice);
|
|
258530
258569
|
}
|
|
@@ -260205,6 +260244,121 @@ var ToolResultBatchOffload = class {
|
|
|
260205
260244
|
}
|
|
260206
260245
|
};
|
|
260207
260246
|
//#endregion
|
|
260247
|
+
//#region ../../packages/agent-core/src/agent/turn/user-message-offload.ts
|
|
260248
|
+
function persistableUserMessageText(message) {
|
|
260249
|
+
if (message?.role !== "user" || message.origin?.kind !== "user") return;
|
|
260250
|
+
const textParts = message.content.filter((part) => part.type === "text");
|
|
260251
|
+
if (textParts.length === 0) return;
|
|
260252
|
+
return textParts.map((part) => part.text).join("\n");
|
|
260253
|
+
}
|
|
260254
|
+
function userMessageOffloadHash(message, text) {
|
|
260255
|
+
return createHash("sha256").update(JSON.stringify({
|
|
260256
|
+
origin: message.origin,
|
|
260257
|
+
text
|
|
260258
|
+
})).digest("hex");
|
|
260259
|
+
}
|
|
260260
|
+
async function saveUserMessage(homedir, text) {
|
|
260261
|
+
try {
|
|
260262
|
+
const dir = join$4(homedir, "conversation-history");
|
|
260263
|
+
await mkdir(dir, {
|
|
260264
|
+
recursive: true,
|
|
260265
|
+
mode: 448
|
|
260266
|
+
});
|
|
260267
|
+
const outputPath = join$4(dir, `user-message-${randomUUID()}.txt`);
|
|
260268
|
+
await writeFile(outputPath, text, {
|
|
260269
|
+
encoding: "utf8",
|
|
260270
|
+
flag: "wx",
|
|
260271
|
+
mode: 384
|
|
260272
|
+
});
|
|
260273
|
+
return outputPath;
|
|
260274
|
+
} catch {
|
|
260275
|
+
return;
|
|
260276
|
+
}
|
|
260277
|
+
}
|
|
260278
|
+
function renderPersistedUserMessage(text, outputPath) {
|
|
260279
|
+
return [
|
|
260280
|
+
USER_MESSAGE_OFFLOAD_MARKER,
|
|
260281
|
+
`Message text exceeded ${String(USER_MESSAGE_MAX_CHARS)} characters; the complete original remains in conversation history.`,
|
|
260282
|
+
`message_size_chars: ${String(text.length)}`,
|
|
260283
|
+
`message_size_bytes: ${String(Buffer.byteLength(text, "utf8"))}`,
|
|
260284
|
+
`output_path: ${outputPath}`,
|
|
260285
|
+
"next_step: Use Read with output_path to inspect the complete message in pages.",
|
|
260286
|
+
"",
|
|
260287
|
+
"[preview: head and tail]",
|
|
260288
|
+
createUserMessagePreview(text)
|
|
260289
|
+
].join("\n");
|
|
260290
|
+
}
|
|
260291
|
+
var USER_MESSAGE_MAX_CHARS, USER_MESSAGE_OFFLOAD_MARKER, shouldOffloadUserMessage, createUserMessagePreview;
|
|
260292
|
+
var init_user_message_offload = __esmMin((() => {
|
|
260293
|
+
const policy = createRequire(import.meta.url)("./bin/user-message-offload-policy.cjs");
|
|
260294
|
+
USER_MESSAGE_MAX_CHARS = policy.USER_MESSAGE_MAX_CHARS;
|
|
260295
|
+
USER_MESSAGE_OFFLOAD_MARKER = policy.USER_MESSAGE_OFFLOAD_MARKER;
|
|
260296
|
+
shouldOffloadUserMessage = policy.shouldOffloadUserMessage;
|
|
260297
|
+
createUserMessagePreview = policy.createUserMessagePreview;
|
|
260298
|
+
}));
|
|
260299
|
+
var UserMessageOffload = class {
|
|
260300
|
+
agent;
|
|
260301
|
+
replacements = /* @__PURE__ */ new Map();
|
|
260302
|
+
constructor(agent) {
|
|
260303
|
+
this.agent = agent;
|
|
260304
|
+
}
|
|
260305
|
+
async detect() {
|
|
260306
|
+
const message = this.agent.context.history.at(-1);
|
|
260307
|
+
const text = persistableUserMessageText(message);
|
|
260308
|
+
if (text === void 0 || !shouldOffloadUserMessage(text.length)) return 0;
|
|
260309
|
+
const messageHash = userMessageOffloadHash(message, text);
|
|
260310
|
+
if (this.replacements.has(messageHash) || this.agent.homedir === void 0) return 0;
|
|
260311
|
+
const outputPath = await saveUserMessage(this.agent.homedir, text);
|
|
260312
|
+
if (outputPath === void 0) return 0;
|
|
260313
|
+
const replacementText = renderPersistedUserMessage(text, outputPath);
|
|
260314
|
+
const content = [{
|
|
260315
|
+
type: "text",
|
|
260316
|
+
text: replacementText
|
|
260317
|
+
}, ...message.content.filter((part) => part.type !== "text")];
|
|
260318
|
+
this.apply({
|
|
260319
|
+
messageHash,
|
|
260320
|
+
content,
|
|
260321
|
+
outputPath
|
|
260322
|
+
});
|
|
260323
|
+
this.agent.telemetry.track("user_message_offloaded", {
|
|
260324
|
+
chars_before: text.length,
|
|
260325
|
+
chars_after: replacementText.length,
|
|
260326
|
+
chars_saved: text.length - replacementText.length,
|
|
260327
|
+
media_part_count: content.length - 1
|
|
260328
|
+
});
|
|
260329
|
+
return 1;
|
|
260330
|
+
}
|
|
260331
|
+
apply(replacement) {
|
|
260332
|
+
this.replacements.set(replacement.messageHash, replacement);
|
|
260333
|
+
this.agent.records.logRecord({
|
|
260334
|
+
type: "user_message_offload.apply",
|
|
260335
|
+
replacement
|
|
260336
|
+
});
|
|
260337
|
+
}
|
|
260338
|
+
compact(messages) {
|
|
260339
|
+
if (this.replacements.size === 0) return messages;
|
|
260340
|
+
return messages.map((message) => {
|
|
260341
|
+
const text = persistableUserMessageText(message);
|
|
260342
|
+
if (text === void 0) return message;
|
|
260343
|
+
const replacement = this.replacements.get(userMessageOffloadHash(message, text));
|
|
260344
|
+
return replacement === void 0 ? message : {
|
|
260345
|
+
...message,
|
|
260346
|
+
content: replacement.content
|
|
260347
|
+
};
|
|
260348
|
+
});
|
|
260349
|
+
}
|
|
260350
|
+
reset(history = this.agent.context?.history ?? []) {
|
|
260351
|
+
const activeHashes = new Set(history.map((message) => {
|
|
260352
|
+
const text = persistableUserMessageText(message);
|
|
260353
|
+
return text === void 0 ? void 0 : userMessageOffloadHash(message, text);
|
|
260354
|
+
}).filter((hash) => hash !== void 0));
|
|
260355
|
+
for (const messageHash of this.replacements.keys()) if (!activeHashes.has(messageHash)) this.replacements.delete(messageHash);
|
|
260356
|
+
}
|
|
260357
|
+
clear() {
|
|
260358
|
+
this.replacements.clear();
|
|
260359
|
+
}
|
|
260360
|
+
};
|
|
260361
|
+
//#endregion
|
|
260208
260362
|
//#region ../../packages/agent-core/src/agent/turn/index.ts
|
|
260209
260363
|
function blunExtractText(content) {
|
|
260210
260364
|
if (typeof content === "string") return content;
|
|
@@ -260540,6 +260694,7 @@ var init_turn = __esmMin((() => {
|
|
|
260540
260694
|
init_canonical_args();
|
|
260541
260695
|
init_tool_dedup();
|
|
260542
260696
|
init_tool_result_budget();
|
|
260697
|
+
init_user_message_offload();
|
|
260543
260698
|
({ CORE_TOOL_NAMES: BLUN_CORE_TOOL_NAMES, createDeferredToolLoader, toolSchemaBudgetTokens } = createRequire(import.meta.url)("./bin/turn-tool-performance-policy.cjs"));
|
|
260544
260699
|
BLUN_LEAN_TOOL_NAMES = new Set(BLUN_CORE_TOOL_NAMES);
|
|
260545
260700
|
BLUN_ATTACHMENT_MARKER_RE = /\b(?:attachment_file_id|telegram-anhang|telegram attachment)\b/i;
|
|
@@ -261174,6 +261329,7 @@ var init_turn = __esmMin((() => {
|
|
|
261174
261329
|
beforeStep: async ({ signal: stepSignal, stepNumber }) => {
|
|
261175
261330
|
await this.agent.records.flush();
|
|
261176
261331
|
this.agent.microCompaction.detect();
|
|
261332
|
+
await this.agent.userMessageOffload.detect();
|
|
261177
261333
|
await this.agent.injection.inject();
|
|
261178
261334
|
stepSignal.throwIfAborted();
|
|
261179
261335
|
deduper.beginStep();
|
|
@@ -263982,6 +264138,7 @@ var init_agent = __esmMin((() => {
|
|
|
263982
264138
|
this.fullCompaction = new FullCompaction(this, options.compactionStrategy);
|
|
263983
264139
|
this.microCompaction = new MicroCompaction(this, options.microCompaction);
|
|
263984
264140
|
this.toolResultBatchOffload = new ToolResultBatchOffload(this);
|
|
264141
|
+
this.userMessageOffload = new UserMessageOffload(this);
|
|
263985
264142
|
this.context = new ContextMemory(this);
|
|
263986
264143
|
this.config = new ConfigState(this);
|
|
263987
264144
|
this.turn = new TurnFlow(this);
|