blun-king-cli 9.1.172 → 9.1.174

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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const DEFAULT_MAX_CHARS = 4_000;
3
+ const DEFAULT_MAX_CHARS = 3_000;
4
4
  const DEFAULT_MAX_SECTIONS = 5;
5
5
  const RECENT_USER_MESSAGES = 4;
6
6
  const STOP_WORDS = new Set([
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ const MIN_RESPONSE_CHARS = 160;
4
+ const MAX_RECENT_ASSISTANT_RESPONSES = 8;
5
+ const REQUIRED_MATCHING_RESPONSES = 3;
6
+ const RESPONSE_SIMILARITY_THRESHOLD = 0.78;
7
+
8
+ function isUserPrompt(message) {
9
+ if (message?.role !== 'user') return false;
10
+ const kind = message.origin?.kind;
11
+ return kind !== 'injection' && kind !== 'compaction_summary' && kind !== 'system_trigger';
12
+ }
13
+
14
+ function assistantResponseText(message) {
15
+ if (
16
+ message?.role !== 'assistant'
17
+ || (message.toolCalls?.length ?? 0) > 0
18
+ || message.origin?.kind === 'injection'
19
+ || !Array.isArray(message.content)
20
+ ) return '';
21
+
22
+ const text = message.content
23
+ .filter((part) => part?.type === 'text' && typeof part.text === 'string')
24
+ .map((part) => part.text)
25
+ .join('\n')
26
+ .trim();
27
+ return text.length >= MIN_RESPONSE_CHARS ? text : '';
28
+ }
29
+
30
+ function normalizedResponseTokens(text) {
31
+ return String(text)
32
+ .normalize('NFKC')
33
+ .toLocaleLowerCase('en-US')
34
+ .match(/[\p{L}\p{N}_]+/gu)
35
+ ?.map((token) => /\p{N}/u.test(token) ? '#' : token) ?? [];
36
+ }
37
+
38
+ function tokenBigrams(tokens) {
39
+ const result = new Set();
40
+ for (let index = 0; index + 1 < tokens.length; index += 1) {
41
+ result.add(`${tokens[index]}\u0000${tokens[index + 1]}`);
42
+ }
43
+ return result;
44
+ }
45
+
46
+ function responseSimilarity(left, right) {
47
+ const leftBigrams = tokenBigrams(normalizedResponseTokens(left));
48
+ const rightBigrams = tokenBigrams(normalizedResponseTokens(right));
49
+ if (leftBigrams.size < 8 || rightBigrams.size < 8) return 0;
50
+
51
+ let intersection = 0;
52
+ for (const bigram of leftBigrams) {
53
+ if (rightBigrams.has(bigram)) intersection += 1;
54
+ }
55
+ return (2 * intersection) / (leftBigrams.size + rightBigrams.size);
56
+ }
57
+
58
+ function hasUserPromptBetween(history, leftIndex, rightIndex) {
59
+ for (let index = leftIndex + 1; index < rightIndex; index += 1) {
60
+ if (isUserPrompt(history[index])) return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ function detectRepeatedAssistantResponse(history) {
66
+ if (!Array.isArray(history) || history.length === 0) return null;
67
+
68
+ let latestUserIndex = -1;
69
+ for (let index = history.length - 1; index >= 0; index -= 1) {
70
+ if (isUserPrompt(history[index])) {
71
+ latestUserIndex = index;
72
+ break;
73
+ }
74
+ }
75
+ if (latestUserIndex < 0) return null;
76
+
77
+ const responses = [];
78
+ for (let index = latestUserIndex - 1; index >= 0 && responses.length < MAX_RECENT_ASSISTANT_RESPONSES; index -= 1) {
79
+ const text = assistantResponseText(history[index]);
80
+ if (text) responses.push({ index, text });
81
+ }
82
+ if (responses.length < REQUIRED_MATCHING_RESPONSES) return null;
83
+
84
+ const latest = responses[0];
85
+ const matches = [latest];
86
+ for (const candidate of responses.slice(1)) {
87
+ if (!hasUserPromptBetween(history, candidate.index, latest.index)) continue;
88
+ const similarity = responseSimilarity(latest.text, candidate.text);
89
+ if (similarity >= RESPONSE_SIMILARITY_THRESHOLD) {
90
+ matches.push({ ...candidate, similarity });
91
+ }
92
+ }
93
+ if (matches.length < REQUIRED_MATCHING_RESPONSES) return null;
94
+
95
+ return {
96
+ count: matches.length,
97
+ minimumSimilarity: Math.min(...matches.slice(1).map((match) => match.similarity)),
98
+ };
99
+ }
100
+
101
+ module.exports = {
102
+ MAX_RECENT_ASSISTANT_RESPONSES,
103
+ MIN_RESPONSE_CHARS,
104
+ REQUIRED_MATCHING_RESPONSES,
105
+ RESPONSE_SIMILARITY_THRESHOLD,
106
+ assistantResponseText,
107
+ detectRepeatedAssistantResponse,
108
+ responseSimilarity,
109
+ };
package/blun.mjs CHANGED
@@ -232089,6 +232089,47 @@ var init_tool_awareness = __esmMin((() => {
232089
232089
  };
232090
232090
  }));
232091
232091
  //#endregion
232092
+ //#region ../../packages/agent-core/src/agent/injection/repeated-assistant-response.ts
232093
+ function isRepeatedAssistantResponseReminder(message) {
232094
+ return message.origin?.kind === "injection" && message.origin.variant === "repeated_assistant_response";
232095
+ }
232096
+ function buildRepeatedAssistantResponseReminder() {
232097
+ return [
232098
+ "<response-loop-check>",
232099
+ "Your latest text-only answer substantially repeats at least two earlier answers despite newer user messages between them.",
232100
+ "Do not repeat the earlier question, list, or waiting state again.",
232101
+ "Re-read the latest non-injection user message and every newer correction, then take a different concrete action that advances that request.",
232102
+ "If action is genuinely blocked, report the exact current blocker and its evidence once instead of returning to the old answer.",
232103
+ "</response-loop-check>"
232104
+ ].join("\n");
232105
+ }
232106
+ var RepeatedAssistantResponseInjector, detectRepeatedAssistantResponse;
232107
+ var init_repeated_assistant_response = __esmMin((() => {
232108
+ init_injector();
232109
+ ({ detectRepeatedAssistantResponse } = createRequire(import.meta.url)("./bin/repeated-assistant-response-policy.cjs"));
232110
+ RepeatedAssistantResponseInjector = class extends DynamicInjector {
232111
+ injectionVariant = "repeated_assistant_response";
232112
+ async inject() {
232113
+ const detection = detectRepeatedAssistantResponse(this.agent.context.history);
232114
+ const existing = this.agent.context.history.filter(isRepeatedAssistantResponseReminder);
232115
+ if (detection === null) {
232116
+ if (existing.length > 0) this.agent.context.removeSystemRemindersMatching(isRepeatedAssistantResponseReminder);
232117
+ return;
232118
+ }
232119
+ const injection = buildRepeatedAssistantResponseReminder();
232120
+ const expected = `<system-reminder>\n${injection}\n</system-reminder>`;
232121
+ if (existing.length === 1 && reminderText(existing[0]) === expected) return;
232122
+ this.agent.context.removeSystemRemindersMatching(isRepeatedAssistantResponseReminder);
232123
+ this.injectedAt = this.agent.context.history.length;
232124
+ this.lastInjection = injection;
232125
+ this.agent.context.appendSystemReminder(injection, {
232126
+ kind: "injection",
232127
+ variant: this.injectionVariant
232128
+ });
232129
+ }
232130
+ };
232131
+ }));
232132
+ //#endregion
232092
232133
  //#region ../../packages/agent-core/src/agent/injection/mission-contract-evidence.js
232093
232134
  function createEvidenceApi(dependencies) {
232094
232135
  "use strict";
@@ -232887,6 +232928,7 @@ var init_manager$2 = __esmMin((() => {
232887
232928
  init_personal_memory_recall();
232888
232929
  init_plugin_session_start();
232889
232930
  init_plan_mode();
232931
+ init_repeated_assistant_response();
232890
232932
  init_tool_awareness();
232891
232933
  ACTIVE_BACKGROUND_TASK_GUIDANCE = "The conversation was compacted, so the earlier messages that started these background tasks are gone — but the tasks are still running from before. Do not start duplicates. Use TaskOutput to fetch a task’s result, TaskList to list them, TaskUpdate to send additional instructions to a running agent task, and TaskStop to cancel one.";
232892
232934
  InjectionManager = class {
@@ -232902,6 +232944,7 @@ var init_manager$2 = __esmMin((() => {
232902
232944
  new ErrorMemoryInjector(agent),
232903
232945
  new ToolAwarenessInjector(agent),
232904
232946
  new MistakeMdInjector(agent),
232947
+ new RepeatedAssistantResponseInjector(agent),
232905
232948
  new ActionStyleInjector(agent),
232906
232949
  new PlanModeInjector(agent),
232907
232950
  new PermissionModeInjector(agent)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.172",
3
+ "version": "9.1.174",
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": {