blun-king-cli 9.1.171 → 9.1.173

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.
@@ -58,6 +58,25 @@ const PROFILE = parseProfileLaunchArgs(RAW_ARGS, launcherModeFromArgv(process.ar
58
58
  const ARGS = PROFILE.args;
59
59
  const CORE_LOAD_TIMEOUT_MS = 30_000;
60
60
  const RUNTIME_READY_TIMEOUT_MS = 60_000;
61
+ const RUNNING_UPDATE_RECHECK_MS = 60_000;
62
+ const RUNNING_UPDATE_RECHECK_JITTER_MS = 30_000;
63
+
64
+ function runningUpdateRecheckDelay(randomValue = Math.random()) {
65
+ const normalized = Number.isFinite(randomValue)
66
+ ? Math.min(Math.max(randomValue, 0), 0.999999999)
67
+ : 0;
68
+ return RUNNING_UPDATE_RECHECK_MS
69
+ + Math.floor(normalized * RUNNING_UPDATE_RECHECK_JITTER_MS);
70
+ }
71
+
72
+ function scheduleRunningUpdateRecheck(callback, options = {}) {
73
+ const timer = (options.setTimeoutImpl || setTimeout)(
74
+ callback,
75
+ options.delayMs ?? runningUpdateRecheckDelay((options.randomImpl || Math.random)()),
76
+ );
77
+ timer.unref?.();
78
+ return timer;
79
+ }
61
80
 
62
81
  function normalizeWindowsPath(value) {
63
82
  return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
@@ -232,9 +251,23 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
232
251
  let handoffMode;
233
252
  let updateStarted = false;
234
253
  let runtimeReady = false;
254
+ let runningUpdatePollTimer;
235
255
  let runningUpdateMode = readRunningUpdateMode(env.BLUN_HOME);
236
256
  const automaticMode = () => runningUpdateMode === RUNNING_UPDATE_MODES.RESUME
237
257
  || runningUpdateMode === RUNNING_UPDATE_MODES.NEW;
258
+ const clearRunningUpdatePoll = () => {
259
+ if (runningUpdatePollTimer === undefined) return;
260
+ (options.clearTimeoutImpl || clearTimeout)(runningUpdatePollTimer);
261
+ runningUpdatePollTimer = undefined;
262
+ };
263
+ const scheduleNextPreparation = (child) => {
264
+ clearRunningUpdatePoll();
265
+ if (preparedTarget !== undefined || updateStarted || !automaticMode()) return;
266
+ runningUpdatePollTimer = (options.scheduleRunningUpdateRecheck || scheduleRunningUpdateRecheck)(() => {
267
+ runningUpdatePollTimer = undefined;
268
+ startPreparation(child);
269
+ });
270
+ };
238
271
  const announcePrepared = (child) => {
239
272
  if (preparedTarget === undefined || !automaticMode() || child.connected !== true) return;
240
273
  child.send({
@@ -249,6 +282,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
249
282
  const sharedHome = env.BLUN_SHARED_HOME;
250
283
  if (typeof sharedHome !== 'string' || sharedHome.length === 0) {
251
284
  updateStarted = false;
285
+ scheduleNextPreparation(child);
252
286
  return;
253
287
  }
254
288
  Promise.resolve((options.prepareRunningUpdate || prepareRunningUpdate)({
@@ -258,16 +292,19 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
258
292
  })).then((target) => {
259
293
  if (target === null) {
260
294
  updateStarted = false;
295
+ scheduleNextPreparation(child);
261
296
  return;
262
297
  }
263
298
  preparedTarget = target;
299
+ clearRunningUpdatePoll();
264
300
  announcePrepared(child);
265
301
  }).catch((error) => {
266
302
  updateStarted = false;
267
303
  options.onRunningUpdateError?.(error);
304
+ scheduleNextPreparation(child);
268
305
  });
269
306
  };
270
- const core = spawnProtectedCore(args, env, cwd, {
307
+ const core = (options.spawnProtectedCore || spawnProtectedCore)(args, env, cwd, {
271
308
  packageRoot,
272
309
  onMessage(message, child) {
273
310
  if (message?.type === RUNTIME_READY_MESSAGE) {
@@ -278,6 +315,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
278
315
  runningUpdateMode = normalizeRunningUpdateMode(message.mode);
279
316
  handoffSessionId = undefined;
280
317
  handoffMode = undefined;
318
+ clearRunningUpdatePoll();
281
319
  if (automaticMode()) {
282
320
  if (preparedTarget !== undefined) announcePrepared(child);
283
321
  else if (runtimeReady) {
@@ -299,6 +337,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
299
337
  const loaded = await core.loaded;
300
338
  await releaseNotice();
301
339
  const result = await core.completed;
340
+ clearRunningUpdatePoll();
302
341
  if (result.error) throw result.error;
303
342
  if (result.code === RUNNING_UPDATE_HANDOFF_EXIT_CODE
304
343
  && preparedTarget !== undefined
@@ -656,9 +695,14 @@ async function runLauncher(options = {}) {
656
695
  }
657
696
 
658
697
  module.exports = {
698
+ RUNNING_UPDATE_RECHECK_JITTER_MS,
699
+ RUNNING_UPDATE_RECHECK_MS,
659
700
  createManagedNodeEnvironment,
660
701
  resolveLauncherPrivatePaths,
702
+ runningUpdateRecheckDelay,
661
703
  runLauncher,
704
+ scheduleRunningUpdateRecheck,
662
705
  shouldDetachProtectedCore,
663
706
  spawnManagedLauncher,
707
+ superviseProtectedCore,
664
708
  };
@@ -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.171",
3
+ "version": "9.1.173",
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": {