blun-king-cli 9.1.197 → 9.1.199

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.
@@ -5,6 +5,10 @@ const MAX_RECENT_ASSISTANT_RESPONSES = 8;
5
5
  const REQUIRED_MATCHING_RESPONSES = 3;
6
6
  const RESPONSE_SIMILARITY_THRESHOLD = 0.78;
7
7
  const REPEATED_RESPONSE_PLACEHOLDER = '[Historical repeated assistant response omitted; newer user corrections and instructions supersede it.]';
8
+ const INTRA_MESSAGE_MIN_REPEATS = 3;
9
+ const INTRA_MESSAGE_MIN_BLOCK_CHARS = 40;
10
+ const INTRA_MESSAGE_MIN_SAVED_CHARS = 1_000;
11
+ const INTRA_MESSAGE_REPEAT_PLACEHOLDER = '[Exact repeated assistant paragraphs omitted from model projection; raw history preserved.]';
8
12
 
9
13
  function isUserPrompt(message) {
10
14
  if (message?.role !== 'user') return false;
@@ -100,12 +104,74 @@ function detectRepeatedAssistantResponse(history) {
100
104
  };
101
105
  }
102
106
 
107
+ function compactRepeatedAssistantParagraphs(message) {
108
+ if (
109
+ message?.role !== 'assistant'
110
+ || (message.toolCalls?.length ?? 0) > 0
111
+ || message.origin?.kind === 'injection'
112
+ || !Array.isArray(message.content)
113
+ ) return null;
114
+
115
+ const textParts = message.content.filter(
116
+ (part) => part?.type === 'text' && typeof part.text === 'string',
117
+ );
118
+ if (textParts.length !== 1 || message.content.some(
119
+ (part) => part?.type !== 'text' && part?.type !== 'think',
120
+ )) return null;
121
+
122
+ const text = textParts[0].text;
123
+ if (/```|~~~/u.test(text)) return null;
124
+ const blocks = text.split(/\n\s*\n/u).map((block) => block.trim()).filter(Boolean);
125
+ if (blocks.length < INTRA_MESSAGE_MIN_REPEATS + 1) return null;
126
+
127
+ const counts = new Map();
128
+ for (const block of blocks) counts.set(block, (counts.get(block) ?? 0) + 1);
129
+ const repeated = new Set([...counts]
130
+ .filter(([block, count]) => (
131
+ block.length >= INTRA_MESSAGE_MIN_BLOCK_CHARS
132
+ && count >= INTRA_MESSAGE_MIN_REPEATS
133
+ ))
134
+ .map(([block]) => block));
135
+ if (repeated.size === 0) return null;
136
+
137
+ const seen = new Set();
138
+ const projectedBlocks = [];
139
+ let markerAdded = false;
140
+ for (const block of blocks) {
141
+ if (!repeated.has(block) || !seen.has(block)) {
142
+ projectedBlocks.push(block);
143
+ if (repeated.has(block)) seen.add(block);
144
+ continue;
145
+ }
146
+ if (!markerAdded) {
147
+ projectedBlocks.push(INTRA_MESSAGE_REPEAT_PLACEHOLDER);
148
+ markerAdded = true;
149
+ }
150
+ }
151
+
152
+ const projectedText = projectedBlocks.join('\n\n');
153
+ if (text.length - projectedText.length < INTRA_MESSAGE_MIN_SAVED_CHARS) return null;
154
+ return {
155
+ ...message,
156
+ content: message.content.map((part) => (
157
+ part === textParts[0] ? { ...part, text: projectedText } : part
158
+ )),
159
+ };
160
+ }
161
+
103
162
  function projectRepeatedAssistantResponses(history) {
104
163
  const detection = detectRepeatedAssistantResponse(history);
105
- if (detection === null) return history;
164
+ let changed = false;
165
+ const intraMessageProjected = history.map((message) => {
166
+ const projected = compactRepeatedAssistantParagraphs(message);
167
+ if (projected === null) return message;
168
+ changed = true;
169
+ return projected;
170
+ });
171
+ if (detection === null) return changed ? intraMessageProjected : history;
106
172
 
107
173
  const repeatedIndexes = new Set(detection.indexes);
108
- return history.map((message, index) => {
174
+ return intraMessageProjected.map((message, index) => {
109
175
  if (!repeatedIndexes.has(index)) return message;
110
176
  return {
111
177
  ...message,
@@ -115,12 +181,17 @@ function projectRepeatedAssistantResponses(history) {
115
181
  }
116
182
 
117
183
  module.exports = {
184
+ INTRA_MESSAGE_MIN_BLOCK_CHARS,
185
+ INTRA_MESSAGE_MIN_REPEATS,
186
+ INTRA_MESSAGE_MIN_SAVED_CHARS,
187
+ INTRA_MESSAGE_REPEAT_PLACEHOLDER,
118
188
  MAX_RECENT_ASSISTANT_RESPONSES,
119
189
  MIN_RESPONSE_CHARS,
120
190
  REQUIRED_MATCHING_RESPONSES,
121
191
  REPEATED_RESPONSE_PLACEHOLDER,
122
192
  RESPONSE_SIMILARITY_THRESHOLD,
123
193
  assistantResponseText,
194
+ compactRepeatedAssistantParagraphs,
124
195
  detectRepeatedAssistantResponse,
125
196
  projectRepeatedAssistantResponses,
126
197
  responseSimilarity,
package/blun.mjs CHANGED
@@ -260297,7 +260297,7 @@ async function budgetToolResultForModel(options) {
260297
260297
  return persistToolResultForModel(options, text);
260298
260298
  }
260299
260299
  function reusableReadSourcePath(options) {
260300
- if (options.toolName !== "Read" || options.result.isError === true || options.result.truncated === true) return;
260300
+ if (options.toolName !== "Read" || options.result.isError === true) return;
260301
260301
  const path = options.toolArgs?.path;
260302
260302
  if (typeof path !== "string" || path.trim().length === 0) return;
260303
260303
  return path;
@@ -260319,7 +260319,6 @@ function referenceReadSourceForModel(options, text, outputPath) {
260319
260319
  async function persistToolResultForModel(options, knownText) {
260320
260320
  const text = knownText ?? persistableToolResultText(options.result.output);
260321
260321
  if (text === void 0) return options.result;
260322
- if (options.result.truncated === true) return options.result;
260323
260322
  if (options.homedir === void 0) return options.result;
260324
260323
  const outputPath = await saveToolResult({
260325
260324
  homedir: options.homedir,
@@ -260332,7 +260331,7 @@ async function persistToolResultForModel(options, knownText) {
260332
260331
  text,
260333
260332
  previewChars: TOOL_RESULT_PREVIEW_CHARS
260334
260333
  }));
260335
- const output = renderPersistedToolResult(options.toolName, options.toolCallId, text, outputPath);
260334
+ const output = renderPersistedToolResult(options.toolName, options.toolCallId, text, outputPath, options.result.truncated === true);
260336
260335
  return options.result.isError === true ? {
260337
260336
  ...options.result,
260338
260337
  output,
@@ -260365,10 +260364,14 @@ async function saveToolResult(options, text) {
260365
260364
  return;
260366
260365
  }
260367
260366
  }
260368
- function renderPersistedToolResult(toolName, toolCallId, text, outputPath) {
260367
+ function renderPersistedToolResult(toolName, toolCallId, text, outputPath, toolTruncated = false) {
260369
260368
  const lines = [
260370
260369
  TOOL_RESULT_OFFLOAD_MARKER,
260371
260370
  `Tool output exceeded ${String(TOOL_RESULT_MAX_CHARS)} characters; showing a preview only.`,
260371
+ ...(toolTruncated ? [
260372
+ "The tool had already truncated its raw output; the complete returned result was stored.",
260373
+ "stored_result_scope: tool_truncated_output"
260374
+ ] : []),
260372
260375
  `tool_name: ${toolName}`,
260373
260376
  `tool_call_id: ${toolCallId}`,
260374
260377
  `output_size_chars: ${String(text.length)}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.197",
3
+ "version": "9.1.199",
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": {