blun-king-cli 9.1.576 → 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 +16 -0
- package/bin/telegram-mcp-compatibility.cjs +49 -0
- package/blun.mjs +33 -6
- package/package.json +1 -1
- package/telegram-plugin/bin/telegram-direct-reply-policy.cjs +48 -0
- package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +1 -0
- package/telegram-plugin/compat/mcp-server-fa511cd1.mjs +73825 -0
- package/telegram-plugin/dist/bridge.mjs +3 -3
- package/telegram-plugin/dist/mcp-server.mjs +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
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
|
+
|
|
9
|
+
## 9.1.577 - 2026-09-07
|
|
10
|
+
|
|
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.
|
|
12
|
+
- Group replies must be sent deliberately through the Telegram reply tool. Normal input, active steering and standalone group prompts describe the same delivery rule.
|
|
13
|
+
- Preserve private-chat replies, explicit reply tools, required goal results, command/status messages and media delivery. Mention rules, access settings and managed-plugin resolution are unchanged.
|
|
14
|
+
- This does not guarantee that a model will never make an unnecessary explicit reply, and it does not stop unrelated group input from reaching the model. No AgentSpine or language-guard update is included.
|
|
15
|
+
- Running sessions are not restarted. The updated core must be loaded by a new process. Existing user profiles and session history are retained.
|
|
16
|
+
|
|
1
17
|
## 9.1.576 - 2026-09-07
|
|
2
18
|
|
|
3
19
|
- Preserve standalone C++ and C# as distinct terms in local research recall,
|
|
@@ -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.
|
|
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;
|
|
@@ -519177,6 +519189,17 @@ async function saveAutoRetrievedMedia(result) {
|
|
|
519177
519189
|
function isGroupChat(chatId) {
|
|
519178
519190
|
return chatId.startsWith("-");
|
|
519179
519191
|
}
|
|
519192
|
+
function withTelegramGroupReplyPolicy(chatId, text) {
|
|
519193
|
+
if (chatId === void 0 || !isGroupChat(chatId)) return text;
|
|
519194
|
+
return [
|
|
519195
|
+
"Telegram group delivery: use the telegram reply tool only for an intended, useful group answer.",
|
|
519196
|
+
"Ordinary final assistant text is not forwarded automatically to this group.",
|
|
519197
|
+
"If there is no useful contribution, make no reply call and do not announce silence or lack of work.",
|
|
519198
|
+
"Do not respond merely to explain that a message addresses someone else.",
|
|
519199
|
+
"",
|
|
519200
|
+
text
|
|
519201
|
+
].join("\n");
|
|
519202
|
+
}
|
|
519180
519203
|
function normalizeGroupNoise(text) {
|
|
519181
519204
|
return text
|
|
519182
519205
|
.toLowerCase()
|
|
@@ -521670,11 +521693,11 @@ const telegramBoundary = this.captureTelegramQueueBoundary();
|
|
|
521670
521693
|
}, ...items.flatMap((queued) => {
|
|
521671
521694
|
const textPart = {
|
|
521672
521695
|
type: "text",
|
|
521673
|
-
text: queued.text.trim()
|
|
521696
|
+
text: withTelegramGroupReplyPolicy(queued.channelChatId, queued.text.trim())
|
|
521674
521697
|
};
|
|
521675
521698
|
const imagePart = queued.channelImagePath === void 0 ? void 0 : buildChannelImagePart(queued.channelImagePath);
|
|
521676
521699
|
return imagePart === void 0 ? [textPart] : [textPart, imagePart];
|
|
521677
|
-
})] : `${notice}\n\n${items.map((queued) => queued.text.trim()).join("\n\n")}`;
|
|
521700
|
+
})] : `${notice}\n\n${items.map((queued) => withTelegramGroupReplyPolicy(queued.channelChatId, queued.text.trim())).join("\n\n")}`;
|
|
521678
521701
|
const expectedTurnId = turnId !== void 0 && /^\d+$/.test(turnId) ? Number(turnId) : void 0;
|
|
521679
521702
|
const restoreHead = (error) => {
|
|
521680
521703
|
return this.restoreQueuedSteer(inFlight, error);
|
|
@@ -522365,7 +522388,7 @@ const telegramBoundary = this.captureTelegramQueueBoundary();
|
|
|
522365
522388
|
if (installedGuard !== void 0) this.pendingChannelReplyGuard = installedGuard;
|
|
522366
522389
|
const imagePart = channelImagePath !== void 0 ? buildChannelImagePart(channelImagePath) : void 0;
|
|
522367
522390
|
const directNotice = channelDirect === true ? ["A private Telegram DM has priority over background, group, and loop work.", channelDirectResume === true ? "The user explicitly resumed work. Answer any remaining direct content, then continue the exact saved work checkpoint." : "The runtime saved the active work checkpoint. Answer the private conversation naturally, without exposing internal task, cron, checkpoint, queue, or lane narration. Unless the user explicitly asks to pause, stop, or wait, continue the exact saved work checkpoint automatically after the direct conversation."].join("\n") : void 0;
|
|
522368
|
-
const focusedModelInput = directNotice === void 0 ? modelInput : `${directNotice}\n\n${modelInput}`;
|
|
522391
|
+
const focusedModelInput = directNotice === void 0 ? withTelegramGroupReplyPolicy(channelChatId, modelInput) : `${directNotice}\n\n${modelInput}`;
|
|
522369
522392
|
const promptInput = imagePart !== void 0 && this.canReadImages() ? [{
|
|
522370
522393
|
type: "text",
|
|
522371
522394
|
text: focusedModelInput
|
|
@@ -522834,6 +522857,10 @@ if (this.session !== session) return;
|
|
|
522834
522857
|
if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
|
|
522835
522858
|
continue;
|
|
522836
522859
|
}
|
|
522860
|
+
if (isGroupChat(guard.chatId)) {
|
|
522861
|
+
this.clearAddressedChannelFocusIds?.([guard.channelFocusId]);
|
|
522862
|
+
continue;
|
|
522863
|
+
}
|
|
522837
522864
|
const decision = channelReplyRecoveryDecision({
|
|
522838
522865
|
hasPendingReply: true,
|
|
522839
522866
|
lastStepFinishReason,
|
package/package.json
CHANGED
|
@@ -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 };
|