blun-king-cli 9.1.100 → 9.1.102
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 +2 -0
- package/bin/subagent-timeout-policy.cjs +90 -2
- package/bin/user-message-offload-policy.cjs +37 -0
- package/blun.mjs +136 -4
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
|
@@ -103,6 +103,8 @@ Das Telemetrieereignis `tool_result_batch_offloaded` meldet ausschließlich die
|
|
|
103
103
|
|
|
104
104
|
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.
|
|
105
105
|
|
|
106
|
+
Verliert ein delegierter Einzelagent die Provider-Verbindung, erhält er eine leere Provider-Antwort, trifft er auf einen Leerlaufabbruch des Streams oder meldet der Server einen wiederholbaren Status, setzt BLUN King denselben Agenten anhand seines dauerhaften Verlaufs fort. Die erste Fortsetzung wartet zwei Sekunden, die zweite fünf Sekunden; danach bleibt der ursprüngliche Fehler sichtbar. Die Fortsetzungsanweisung fordert den Agenten ausdrücklich auf, den erhaltenen Arbeitsstand zu prüfen und abgeschlossene Schritte nicht zu wiederholen. Authentifizierungs-, Guthaben-, Sicherheits-, Kontext- sowie beliebige Code- und Werkzeugfehler und manuelle Abbrüche bleiben endgültig. Dieser Schutz gilt für einen delegierten Einzelagenten; für Schwärme gelten weiterhin deren bestehende Zeitgrenzen- und Überlastungsregeln.
|
|
107
|
+
|
|
106
108
|
Rein lesende Sitzungsdiagnose
|
|
107
109
|
--------------------------------
|
|
108
110
|
|
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
const DEFAULT_SUBAGENT_TIMEOUT_MS = 4 * 60 * 60 * 1000;
|
|
4
4
|
const MAX_SUBAGENT_TIMEOUT_MINUTES = 7 * 24 * 60;
|
|
5
5
|
const SUBAGENT_TIMEOUT_ENV = "BLUN_SUBAGENT_TIMEOUT_MINUTES";
|
|
6
|
+
const MAX_TRANSIENT_SUBAGENT_CONTINUATIONS = 2;
|
|
7
|
+
const DEFAULT_TRANSIENT_RETRY_DELAYS_MS = Object.freeze([2000, 5000]);
|
|
8
|
+
const TERMINAL_ERROR_CODES = new Set([
|
|
9
|
+
"auth.login_required",
|
|
10
|
+
"context.overflow",
|
|
11
|
+
"provider.auth_error",
|
|
12
|
+
"provider.quota_exhausted",
|
|
13
|
+
]);
|
|
14
|
+
const RETRYABLE_ERROR_CODES = new Set([
|
|
15
|
+
"compaction.stalled",
|
|
16
|
+
"provider.connection_error",
|
|
17
|
+
"provider.rate_limit",
|
|
18
|
+
]);
|
|
19
|
+
const RETRYABLE_ERROR_NAMES = new Set([
|
|
20
|
+
"APIConnectionError",
|
|
21
|
+
"APIEmptyResponseError",
|
|
22
|
+
"APITimeoutError",
|
|
23
|
+
"CompactionStallError",
|
|
24
|
+
]);
|
|
25
|
+
const RETRYABLE_MESSAGE_RE = /(?:\[provider\.(?:connection_error|rate_limit)\]|\[compaction\.stalled\]|no provider data arrived|stream was aborted to prevent an indefinite hang|\b(?:econnreset|econnrefused|etimedout)\b|fetch failed|socket hang up|connection (?:closed|reset|terminated|timed out)|network error)/iu;
|
|
6
26
|
|
|
7
27
|
function resolveSubagentTimeoutMs({ perRunMinutes, env = process.env } = {}) {
|
|
8
28
|
const raw = perRunMinutes ?? env[SUBAGENT_TIMEOUT_ENV];
|
|
@@ -24,14 +44,63 @@ function abortError(reason) {
|
|
|
24
44
|
return new Error(reason === undefined ? "Subagent interrupted." : String(reason));
|
|
25
45
|
}
|
|
26
46
|
|
|
47
|
+
function errorField(error, field) {
|
|
48
|
+
return typeof error === "object" && error !== null ? error[field] : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRetryableSubagentFailure(error) {
|
|
52
|
+
const code = errorField(error, "code");
|
|
53
|
+
const statusCode = errorField(error, "statusCode") ?? errorField(error, "status");
|
|
54
|
+
const name = errorField(error, "name");
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
|
|
57
|
+
if (typeof code === "string" && TERMINAL_ERROR_CODES.has(code)) return false;
|
|
58
|
+
if (statusCode === 401 || statusCode === 402 || statusCode === 403
|
|
59
|
+
|| statusCode === 413 || statusCode === 422) return false;
|
|
60
|
+
if (/safety policy|filtered|payment|required quota|context (?:window )?(?:overflow|too long)/iu.test(message)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (typeof code === "string" && RETRYABLE_ERROR_CODES.has(code)) return true;
|
|
64
|
+
if (typeof name === "string" && RETRYABLE_ERROR_NAMES.has(name)) return true;
|
|
65
|
+
if (statusCode === 408 || statusCode === 429
|
|
66
|
+
|| statusCode === 500 || statusCode === 502 || statusCode === 503 || statusCode === 504) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (errorField(error, "retryable") === true
|
|
70
|
+
&& typeof code === "string"
|
|
71
|
+
&& (code.startsWith("provider.") || code.startsWith("compaction."))) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return RETRYABLE_MESSAGE_RE.test(message);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function waitForRetryDelay(delayMs, signal) {
|
|
78
|
+
if (delayMs <= 0) return Promise.resolve();
|
|
79
|
+
if (signal.aborted) return Promise.reject(abortError(signal.reason));
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const timeout = setTimeout(() => {
|
|
82
|
+
signal.removeEventListener("abort", onAbort);
|
|
83
|
+
resolve();
|
|
84
|
+
}, delayMs);
|
|
85
|
+
const onAbort = () => {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
signal.removeEventListener("abort", onAbort);
|
|
88
|
+
reject(abortError(signal.reason));
|
|
89
|
+
};
|
|
90
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
27
94
|
async function runAgentWithTimeoutContinuation({
|
|
28
95
|
handle,
|
|
29
96
|
timeoutMs,
|
|
30
97
|
signal,
|
|
31
98
|
abortCurrent,
|
|
32
99
|
retrySameAgent,
|
|
100
|
+
retryDelaysMs = DEFAULT_TRANSIENT_RETRY_DELAYS_MS,
|
|
33
101
|
}) {
|
|
34
102
|
let currentHandle = handle;
|
|
103
|
+
let transientContinuations = 0;
|
|
35
104
|
|
|
36
105
|
while (true) {
|
|
37
106
|
let timeout;
|
|
@@ -58,7 +127,24 @@ async function runAgentWithTimeoutContinuation({
|
|
|
58
127
|
removeAbortListener();
|
|
59
128
|
|
|
60
129
|
if (result.kind === "completed") return { handle: currentHandle, outcome: result.outcome };
|
|
61
|
-
if (result.kind === "failed")
|
|
130
|
+
if (result.kind === "failed") {
|
|
131
|
+
if (signal.aborted) throw abortError(signal.reason);
|
|
132
|
+
if (!isRetryableSubagentFailure(result.error)
|
|
133
|
+
|| transientContinuations >= MAX_TRANSIENT_SUBAGENT_CONTINUATIONS) {
|
|
134
|
+
throw result.error;
|
|
135
|
+
}
|
|
136
|
+
const attempt = transientContinuations + 1;
|
|
137
|
+
const delayMs = retryDelaysMs[transientContinuations] ?? retryDelaysMs.at(-1) ?? 0;
|
|
138
|
+
transientContinuations = attempt;
|
|
139
|
+
await waitForRetryDelay(delayMs, signal);
|
|
140
|
+
currentHandle = await retrySameAgent(currentHandle.agentId, {
|
|
141
|
+
kind: "transient_provider",
|
|
142
|
+
attempt,
|
|
143
|
+
maxAttempts: MAX_TRANSIENT_SUBAGENT_CONTINUATIONS,
|
|
144
|
+
error: result.error,
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
62
148
|
if (result.kind === "interrupted") {
|
|
63
149
|
abortCurrent(result.reason);
|
|
64
150
|
await Promise.resolve(currentHandle.completion).catch(() => {});
|
|
@@ -68,12 +154,14 @@ async function runAgentWithTimeoutContinuation({
|
|
|
68
154
|
const reason = new Error("Subagent continuation interval elapsed.");
|
|
69
155
|
abortCurrent(reason);
|
|
70
156
|
await Promise.resolve(currentHandle.completion).catch(() => {});
|
|
71
|
-
currentHandle = await retrySameAgent(currentHandle.agentId);
|
|
157
|
+
currentHandle = await retrySameAgent(currentHandle.agentId, { kind: "wall_clock" });
|
|
72
158
|
}
|
|
73
159
|
}
|
|
74
160
|
|
|
75
161
|
module.exports = {
|
|
76
162
|
DEFAULT_SUBAGENT_TIMEOUT_MS,
|
|
163
|
+
MAX_TRANSIENT_SUBAGENT_CONTINUATIONS,
|
|
164
|
+
isRetryableSubagentFailure,
|
|
77
165
|
resolveSubagentTimeoutMs,
|
|
78
166
|
runAgentWithTimeoutContinuation,
|
|
79
167
|
};
|
|
@@ -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
|
@@ -29058,11 +29058,11 @@ var init_agent_task = __esmMin((() => {
|
|
|
29058
29058
|
abortCurrent: (reason) => {
|
|
29059
29059
|
this.abortController.abort(reason);
|
|
29060
29060
|
},
|
|
29061
|
-
retrySameAgent: async (agentId) => {
|
|
29061
|
+
retrySameAgent: async (agentId, continuation) => {
|
|
29062
29062
|
this.abortController = new AbortController();
|
|
29063
29063
|
this.handle = await this.subagentHost.retry(agentId, {
|
|
29064
29064
|
parentToolCallId: this.parentToolCallId,
|
|
29065
|
-
prompt: "Continue from the preserved context after the wall-clock interval.",
|
|
29065
|
+
prompt: continuation.kind === "transient_provider" ? "Continue from the preserved context after a transient provider interruption. Do not repeat completed work; inspect the preserved wire and continue from the last durable boundary." : "Continue from the preserved context after the wall-clock interval.",
|
|
29066
29066
|
description: this.description,
|
|
29067
29067
|
runInBackground: this.runInBackground,
|
|
29068
29068
|
signal: this.abortController.signal
|
|
@@ -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;
|
|
@@ -251762,7 +251769,14 @@ async function runChildTurnToCompletion(child, signal) {
|
|
|
251762
251769
|
if (turnEnded.reason !== "completed") {
|
|
251763
251770
|
if (turnEnded.reason === "filtered") throw new Error("Subagent turn blocked by provider safety policy");
|
|
251764
251771
|
if (turnEnded.error?.code === ErrorCodes.PROVIDER_RATE_LIMIT) throw providerRateLimitErrorFromPayload(turnEnded.error);
|
|
251765
|
-
|
|
251772
|
+
if (turnEnded.error === void 0) throw new Error(`Subagent turn ${turnEnded.reason}`);
|
|
251773
|
+
const error = new Error(`[${turnEnded.error.code}] ${turnEnded.error.message}`);
|
|
251774
|
+
error.name = turnEnded.error.name ?? "Error";
|
|
251775
|
+
error.code = turnEnded.error.code;
|
|
251776
|
+
error.retryable = turnEnded.error.retryable;
|
|
251777
|
+
const statusCode = turnEnded.error.details?.["statusCode"];
|
|
251778
|
+
if (typeof statusCode === "number") error.statusCode = statusCode;
|
|
251779
|
+
throw error;
|
|
251766
251780
|
}
|
|
251767
251781
|
if (completion.stopReason === "max_tokens") throw new Error(`${SUBAGENT_MAX_TOKENS_ERROR}.`);
|
|
251768
251782
|
}
|
|
@@ -260237,6 +260251,121 @@ var ToolResultBatchOffload = class {
|
|
|
260237
260251
|
}
|
|
260238
260252
|
};
|
|
260239
260253
|
//#endregion
|
|
260254
|
+
//#region ../../packages/agent-core/src/agent/turn/user-message-offload.ts
|
|
260255
|
+
function persistableUserMessageText(message) {
|
|
260256
|
+
if (message?.role !== "user" || message.origin?.kind !== "user") return;
|
|
260257
|
+
const textParts = message.content.filter((part) => part.type === "text");
|
|
260258
|
+
if (textParts.length === 0) return;
|
|
260259
|
+
return textParts.map((part) => part.text).join("\n");
|
|
260260
|
+
}
|
|
260261
|
+
function userMessageOffloadHash(message, text) {
|
|
260262
|
+
return createHash("sha256").update(JSON.stringify({
|
|
260263
|
+
origin: message.origin,
|
|
260264
|
+
text
|
|
260265
|
+
})).digest("hex");
|
|
260266
|
+
}
|
|
260267
|
+
async function saveUserMessage(homedir, text) {
|
|
260268
|
+
try {
|
|
260269
|
+
const dir = join$4(homedir, "conversation-history");
|
|
260270
|
+
await mkdir(dir, {
|
|
260271
|
+
recursive: true,
|
|
260272
|
+
mode: 448
|
|
260273
|
+
});
|
|
260274
|
+
const outputPath = join$4(dir, `user-message-${randomUUID()}.txt`);
|
|
260275
|
+
await writeFile(outputPath, text, {
|
|
260276
|
+
encoding: "utf8",
|
|
260277
|
+
flag: "wx",
|
|
260278
|
+
mode: 384
|
|
260279
|
+
});
|
|
260280
|
+
return outputPath;
|
|
260281
|
+
} catch {
|
|
260282
|
+
return;
|
|
260283
|
+
}
|
|
260284
|
+
}
|
|
260285
|
+
function renderPersistedUserMessage(text, outputPath) {
|
|
260286
|
+
return [
|
|
260287
|
+
USER_MESSAGE_OFFLOAD_MARKER,
|
|
260288
|
+
`Message text exceeded ${String(USER_MESSAGE_MAX_CHARS)} characters; the complete original remains in conversation history.`,
|
|
260289
|
+
`message_size_chars: ${String(text.length)}`,
|
|
260290
|
+
`message_size_bytes: ${String(Buffer.byteLength(text, "utf8"))}`,
|
|
260291
|
+
`output_path: ${outputPath}`,
|
|
260292
|
+
"next_step: Use Read with output_path to inspect the complete message in pages.",
|
|
260293
|
+
"",
|
|
260294
|
+
"[preview: head and tail]",
|
|
260295
|
+
createUserMessagePreview(text)
|
|
260296
|
+
].join("\n");
|
|
260297
|
+
}
|
|
260298
|
+
var USER_MESSAGE_MAX_CHARS, USER_MESSAGE_OFFLOAD_MARKER, shouldOffloadUserMessage, createUserMessagePreview;
|
|
260299
|
+
var init_user_message_offload = __esmMin((() => {
|
|
260300
|
+
const policy = createRequire(import.meta.url)("./bin/user-message-offload-policy.cjs");
|
|
260301
|
+
USER_MESSAGE_MAX_CHARS = policy.USER_MESSAGE_MAX_CHARS;
|
|
260302
|
+
USER_MESSAGE_OFFLOAD_MARKER = policy.USER_MESSAGE_OFFLOAD_MARKER;
|
|
260303
|
+
shouldOffloadUserMessage = policy.shouldOffloadUserMessage;
|
|
260304
|
+
createUserMessagePreview = policy.createUserMessagePreview;
|
|
260305
|
+
}));
|
|
260306
|
+
var UserMessageOffload = class {
|
|
260307
|
+
agent;
|
|
260308
|
+
replacements = /* @__PURE__ */ new Map();
|
|
260309
|
+
constructor(agent) {
|
|
260310
|
+
this.agent = agent;
|
|
260311
|
+
}
|
|
260312
|
+
async detect() {
|
|
260313
|
+
const message = this.agent.context.history.at(-1);
|
|
260314
|
+
const text = persistableUserMessageText(message);
|
|
260315
|
+
if (text === void 0 || !shouldOffloadUserMessage(text.length)) return 0;
|
|
260316
|
+
const messageHash = userMessageOffloadHash(message, text);
|
|
260317
|
+
if (this.replacements.has(messageHash) || this.agent.homedir === void 0) return 0;
|
|
260318
|
+
const outputPath = await saveUserMessage(this.agent.homedir, text);
|
|
260319
|
+
if (outputPath === void 0) return 0;
|
|
260320
|
+
const replacementText = renderPersistedUserMessage(text, outputPath);
|
|
260321
|
+
const content = [{
|
|
260322
|
+
type: "text",
|
|
260323
|
+
text: replacementText
|
|
260324
|
+
}, ...message.content.filter((part) => part.type !== "text")];
|
|
260325
|
+
this.apply({
|
|
260326
|
+
messageHash,
|
|
260327
|
+
content,
|
|
260328
|
+
outputPath
|
|
260329
|
+
});
|
|
260330
|
+
this.agent.telemetry.track("user_message_offloaded", {
|
|
260331
|
+
chars_before: text.length,
|
|
260332
|
+
chars_after: replacementText.length,
|
|
260333
|
+
chars_saved: text.length - replacementText.length,
|
|
260334
|
+
media_part_count: content.length - 1
|
|
260335
|
+
});
|
|
260336
|
+
return 1;
|
|
260337
|
+
}
|
|
260338
|
+
apply(replacement) {
|
|
260339
|
+
this.replacements.set(replacement.messageHash, replacement);
|
|
260340
|
+
this.agent.records.logRecord({
|
|
260341
|
+
type: "user_message_offload.apply",
|
|
260342
|
+
replacement
|
|
260343
|
+
});
|
|
260344
|
+
}
|
|
260345
|
+
compact(messages) {
|
|
260346
|
+
if (this.replacements.size === 0) return messages;
|
|
260347
|
+
return messages.map((message) => {
|
|
260348
|
+
const text = persistableUserMessageText(message);
|
|
260349
|
+
if (text === void 0) return message;
|
|
260350
|
+
const replacement = this.replacements.get(userMessageOffloadHash(message, text));
|
|
260351
|
+
return replacement === void 0 ? message : {
|
|
260352
|
+
...message,
|
|
260353
|
+
content: replacement.content
|
|
260354
|
+
};
|
|
260355
|
+
});
|
|
260356
|
+
}
|
|
260357
|
+
reset(history = this.agent.context?.history ?? []) {
|
|
260358
|
+
const activeHashes = new Set(history.map((message) => {
|
|
260359
|
+
const text = persistableUserMessageText(message);
|
|
260360
|
+
return text === void 0 ? void 0 : userMessageOffloadHash(message, text);
|
|
260361
|
+
}).filter((hash) => hash !== void 0));
|
|
260362
|
+
for (const messageHash of this.replacements.keys()) if (!activeHashes.has(messageHash)) this.replacements.delete(messageHash);
|
|
260363
|
+
}
|
|
260364
|
+
clear() {
|
|
260365
|
+
this.replacements.clear();
|
|
260366
|
+
}
|
|
260367
|
+
};
|
|
260368
|
+
//#endregion
|
|
260240
260369
|
//#region ../../packages/agent-core/src/agent/turn/index.ts
|
|
260241
260370
|
function blunExtractText(content) {
|
|
260242
260371
|
if (typeof content === "string") return content;
|
|
@@ -260572,6 +260701,7 @@ var init_turn = __esmMin((() => {
|
|
|
260572
260701
|
init_canonical_args();
|
|
260573
260702
|
init_tool_dedup();
|
|
260574
260703
|
init_tool_result_budget();
|
|
260704
|
+
init_user_message_offload();
|
|
260575
260705
|
({ CORE_TOOL_NAMES: BLUN_CORE_TOOL_NAMES, createDeferredToolLoader, toolSchemaBudgetTokens } = createRequire(import.meta.url)("./bin/turn-tool-performance-policy.cjs"));
|
|
260576
260706
|
BLUN_LEAN_TOOL_NAMES = new Set(BLUN_CORE_TOOL_NAMES);
|
|
260577
260707
|
BLUN_ATTACHMENT_MARKER_RE = /\b(?:attachment_file_id|telegram-anhang|telegram attachment)\b/i;
|
|
@@ -261206,6 +261336,7 @@ var init_turn = __esmMin((() => {
|
|
|
261206
261336
|
beforeStep: async ({ signal: stepSignal, stepNumber }) => {
|
|
261207
261337
|
await this.agent.records.flush();
|
|
261208
261338
|
this.agent.microCompaction.detect();
|
|
261339
|
+
await this.agent.userMessageOffload.detect();
|
|
261209
261340
|
await this.agent.injection.inject();
|
|
261210
261341
|
stepSignal.throwIfAborted();
|
|
261211
261342
|
deduper.beginStep();
|
|
@@ -264014,6 +264145,7 @@ var init_agent = __esmMin((() => {
|
|
|
264014
264145
|
this.fullCompaction = new FullCompaction(this, options.compactionStrategy);
|
|
264015
264146
|
this.microCompaction = new MicroCompaction(this, options.microCompaction);
|
|
264016
264147
|
this.toolResultBatchOffload = new ToolResultBatchOffload(this);
|
|
264148
|
+
this.userMessageOffload = new UserMessageOffload(this);
|
|
264017
264149
|
this.context = new ContextMemory(this);
|
|
264018
264150
|
this.config = new ConfigState(this);
|
|
264019
264151
|
this.turn = new TurnFlow(this);
|