blun-king-cli 9.1.577 → 9.1.578

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 9.1.578 - 2026-09-08
2
+
3
+ - Bound thinking-only recovery to one additional attempt per step. A second thinking-only timeout stops that step instead of repeatedly issuing the same request. The chosen reasoning effort and existing timeout remain unchanged; this is not a promise of faster first-token response times.
4
+ - Preserve short Telegram group presence answers when reply_to binds them to a recent same-chat input explicitly addressed to the agent, or a supported German/English presence question beginning with the agent's own configured name. Unsolicited presence and silence-only narration remain filtered; ordinary automatic group fallback stays disabled.
5
+ - Include reply_to in outgoing reply and suppression diagnostics. Missing or ambiguous context does not grant a presence exception. This does not add general recipient recognition or prevent turns caused by legacy semantic addressing.
6
+ - Apply the outbound repair to two exact known managed Telegram MCP revisions, including the final plugin configuration merge. The legacy sender receives a narrowly changed compatibility copy. Existing incoming bridges, access settings, profiles and registers are not rewritten; unknown/custom senders and explicit overrides are preserved. A newly started King process is required.
7
+
8
+
1
9
  ## 9.1.577 - 2026-09-07
2
10
 
3
11
  - Stop automatically forwarding ordinary assistant turn-end text to Telegram groups, in both the CLI core and the standalone bridge. This prevents the automatic fallback from posting explanations that there is nothing to report or that a message addresses someone else.
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const { createHash } = require('node:crypto');
5
+ const REPAIRS = {
6
+ fa511cd1e2a0a911aafa100d7a41b3ebd0abd0599c3e2e66daca1c8421298573: {
7
+ file: 'compat/mcp-server-fa511cd1.mjs', sha256: '0d2d078722245e1e559ea146ba5139d999f9ec1f3d4f21ce2dc6d68f6783d0a9',
8
+ },
9
+ '4ed83af87c8d4d89b1f21172aeabfd609bd7772242a9f7d046685acdd24d316c': {
10
+ file: 'dist/mcp-server.mjs', sha256: 'fa9ab7a79a0a98dad505d761f5e1e20a8f72ae640f67b0e9151bcce2bccd0998',
11
+ },
12
+ };
13
+ function fileHash(file) {
14
+ const info = fs.lstatSync(file);
15
+ if (!info.isFile() || info.size > 3 * 1024 * 1024) return null;
16
+ return createHash('sha256').update(fs.readFileSync(file)).digest('hex');
17
+ }
18
+ function resolveKnownTelegramMcp(selected, { explicitOverride = false } = {}) {
19
+ if (explicitOverride) return selected;
20
+ try {
21
+ const repair = REPAIRS[fileHash(selected)];
22
+ if (!repair) return selected;
23
+ const replacement = path.join(__dirname, '..', 'telegram-plugin', repair.file);
24
+ // Never replace unknown/custom code or launch a damaged compatibility payload.
25
+ return fileHash(replacement) === repair.sha256 ? replacement : selected;
26
+ } catch {
27
+ return selected;
28
+ }
29
+ }
30
+ function repairKnownTelegramPluginConfig(entry, { recipientName = '' } = {}) {
31
+ const config = entry.config;
32
+ if (entry.pluginId !== 'telegram' || entry.serverName !== 'telegram' || config?.transport !== 'stdio') return config;
33
+ if (process.env.BLUN_TELEGRAM_MCP_SERVER) return config;
34
+ const args = config.args;
35
+ if (!Array.isArray(args) || typeof config.cwd !== 'string') return config;
36
+ const native = args.length === 2 && args[0] === '__plugin_run_node';
37
+ if (!native && !(args.length === 1 && /^node(?:\.exe)?$/iu.test(path.basename(config.command || '')))) return config;
38
+ const index = native ? 1 : 0;
39
+ if (typeof args[index] !== 'string') return config;
40
+ const selected = path.resolve(config.cwd, args[index]);
41
+ const replacement = resolveKnownTelegramMcp(selected);
42
+ if (selected === replacement) return config;
43
+ return { ...config, args: args.map((value, i) => i === index ? replacement : value), env: {
44
+ ...config.env,
45
+ BLUN_TELEGRAM_PERSONA_NAME: typeof recipientName === 'string' ? recipientName : '',
46
+ ...(native ? { BLUN_PLUGIN_ROOT: path.dirname(path.dirname(replacement)) } : {}),
47
+ } };
48
+ }
49
+ module.exports = { resolveKnownTelegramMcp, repairKnownTelegramPluginConfig };
package/blun.mjs CHANGED
@@ -245623,6 +245623,7 @@ async function chatWithLiveResponseRepetitionRecovery(deps) {
245623
245623
  const { retryInput, params, stepEvents, signal, log } = deps;
245624
245624
  let currentParams = params;
245625
245625
  let repetitionRetries = 0;
245626
+ let thinkingRetries = 0;
245626
245627
  for (let attempt = 1;; attempt++) {
245627
245628
  try {
245628
245629
  const response = await chatWithRetry({
@@ -245640,6 +245641,15 @@ async function chatWithLiveResponseRepetitionRecovery(deps) {
245640
245641
  const thinkingTimeout = stepEvents.thinkingOnlyTimeoutResult;
245641
245642
  stepEvents.finishLiveResponseAttempt();
245642
245643
  if (thinkingTimeout !== null && !signal.aborted) {
245644
+ if (thinkingRetries >= 1) {
245645
+ log?.warn("thinking-only recovery exhausted; stopping this step", {
245646
+ elapsedMs: thinkingTimeout.elapsedMs,
245647
+ turnStep: `${retryInput.turnId}.${String(retryInput.currentStep)}`,
245648
+ thinkingRetries
245649
+ });
245650
+ throw error;
245651
+ }
245652
+ thinkingRetries += 1;
245643
245653
  log?.warn("thinking-only phase exceeded limit; forcing concrete action", {
245644
245654
  elapsedMs: thinkingTimeout.elapsedMs,
245645
245655
  turnStep: `${retryInput.turnId}.${String(retryInput.currentStep)}`
@@ -319635,7 +319645,7 @@ agentSpineRuntime: this.agentSpineRuntimeRecipients(pluginHooks, mcpConfig),
319635
319645
  mergePluginMcpConfig(base) {
319636
319646
  const pluginEntries = this.plugins.mcpServerConfigs();
319637
319647
  if (pluginEntries.length === 0) return base;
319638
- const pluginServers = this.withManagedBlunPluginEnv(Object.fromEntries(pluginEntries.map((entry) => [entry.runtimeName, entry.config])));
319648
+ const pluginServers = this.withManagedBlunPluginEnv(Object.fromEntries(pluginEntries.map((entry) => [entry.runtimeName, repairKnownTelegramPluginConfig(entry, { recipientName: readProfilePersona({ ...process.env, BLUN_HOME: this.homeDir }).persona?.name || "" })])));
319639
319649
  return {
319640
319650
  servers: {
319641
319651
  ...base?.servers,
@@ -422188,7 +422198,8 @@ function telegramChannelMcpServers(dir = telegramStateDir()) {
422188
422198
  args: launch.args,
422189
422199
  env: {
422190
422200
  ...launch.env,
422191
- BLUN_TELEGRAM_STATE_DIR: dir
422201
+ BLUN_TELEGRAM_STATE_DIR: dir,
422202
+ BLUN_TELEGRAM_PERSONA_NAME: readPersona$1()?.name || ""
422192
422203
  }
422193
422204
  } };
422194
422205
  }
@@ -422209,6 +422220,7 @@ function managedTelegramPluginRoot() {
422209
422220
  return void 0;
422210
422221
  }
422211
422222
  }
422223
+ var { resolveKnownTelegramMcp, repairKnownTelegramPluginConfig } = createRequire(import.meta.url)("./bin/telegram-mcp-compatibility.cjs");
422212
422224
  function resolveTelegramMcpEntry() {
422213
422225
  const main = process.argv[1];
422214
422226
  const managedRoot = managedTelegramPluginRoot();
@@ -422218,7 +422230,7 @@ function resolveTelegramMcpEntry() {
422218
422230
  join(homedir(), ".blun", "plugins", "managed", "telegram", "dist", "mcp-server.mjs"),
422219
422231
  main === void 0 ? void 0 : join(dirname(main), "..", "..", "..", "plugins", "telegram", "dist", "mcp-server.mjs")
422220
422232
  ];
422221
- for (const candidate of candidates) if (candidate !== void 0 && existsSync(candidate)) return candidate;
422233
+ for (const candidate of candidates) if (candidate !== void 0 && existsSync(candidate)) return resolveKnownTelegramMcp(candidate, { explicitOverride: candidate === process.env["BLUN_TELEGRAM_MCP_SERVER"] });
422222
422234
  }
422223
422235
  var TelegramChannelController = class {
422224
422236
  host;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.577",
3
+ "version": "9.1.578",
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": {
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ const { readJsonlTail } = require('./telegram-private-conversation-policy.cjs');
4
+ const MAX_AGE_MS = 30 * 60 * 1000;
5
+ const PRESENCE_ANSWER = /^(?:(?:ja )?(?:bin da|bin hier|ich bin da|hier bin ich|ich bin hier|bereit|i am here|i m here|here|ready))(?: (?:was brauchst du|was gibt es|was liegt an|wie kann ich helfen|what do you need|how can i help))?$/u;
6
+ const PRESENCE_QUESTION = /^(?:bist du da|bist du hier|bist du erreichbar|are you there|are you here)$/u;
7
+
8
+ function normalize(value) {
9
+ return String(value ?? '').normalize('NFKC').toLocaleLowerCase('de-DE')
10
+ .replace(/[^\p{L}\p{N}\s]/gu, ' ').replace(/\s+/gu, ' ').trim();
11
+ }
12
+
13
+ function messageId(value) {
14
+ if (typeof value !== 'string' && typeof value !== 'number') return null;
15
+ if (!/^[1-9]\d*$/u.test(String(value))) return null;
16
+ const number = Number(value);
17
+ return Number.isSafeInteger(number) ? number : null;
18
+ }
19
+
20
+ function ownPresenceQuestion(content, recipientName) {
21
+ if (typeof content !== 'string' || content.length > 512) return false;
22
+ if (typeof recipientName !== 'string' || recipientName.length > 128) return false;
23
+ const name = normalize(recipientName), text = normalize(content);
24
+ if (!name) return false;
25
+ if (text === name) return true;
26
+ return text.startsWith(name + ' ') && PRESENCE_QUESTION.test(text.slice(name.length + 1));
27
+ }
28
+
29
+ function permitsPresenceReply({ text, chatId, replyTo, inboxFile,
30
+ recipientName = process.env.BLUN_TELEGRAM_PERSONA_NAME || '', now = Date.now() }) {
31
+ if (typeof text !== 'string' || text.length > 160 || !PRESENCE_ANSWER.test(normalize(text))) return false;
32
+ const wantedId = messageId(replyTo), wantedChat = String(chatId ?? '');
33
+ if (wantedId === null || !/^-[1-9]\d*$/u.test(wantedChat) || !Number.isFinite(now)) return false;
34
+ // A semantic marker is shared by legacy recipients; only a bound own-name ping can refine it.
35
+ const records = readJsonlTail(inboxFile);
36
+ for (let index = records.length - 1; index >= 0; index -= 1) {
37
+ const record = records[index], meta = record.meta;
38
+ if (record.direction !== 'in' || !meta || String(meta.chat_id) !== wantedChat || messageId(meta.message_id) !== wantedId) continue;
39
+ const timestamp = Date.parse(String(meta.ts ?? record.ts ?? ''));
40
+ if (!Number.isFinite(timestamp) || timestamp > now || now - timestamp > MAX_AGE_MS) return false;
41
+ return meta.addressed === true || meta.addressed === 'true'
42
+ || meta.addressed === 1 || meta.addressed === '1'
43
+ || (meta.addressed === 'semantic' && ownPresenceQuestion(record.content, recipientName));
44
+ }
45
+ return false;
46
+ }
47
+
48
+ module.exports = { MAX_AGE_MS, messageId, ownPresenceQuestion, permitsPresenceReply };
@@ -174,6 +174,7 @@ function isPrivateInternalStatusReply(chatId, text) {
174
174
  }
175
175
 
176
176
  module.exports = {
177
+ readJsonlTail,
177
178
  DUPLICATE_LOG_TAIL_BYTES,
178
179
  DUPLICATE_MAX_AGE_MS,
179
180
  NEAR_DUPLICATE_MIN_CHARS,