blun-king-cli 9.1.416 → 9.1.417

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/LIESMICH.txt CHANGED
@@ -67,6 +67,14 @@ Bereinigung und Mikrokompaktierung verwenden dafür dieselbe semantische Grenze.
67
67
  Erst nach der nachweislichen Auswertung darf ein Ergebnis archiviert oder
68
68
  gekürzt werden.
69
69
 
70
+ Ab BLUN King 9.1.417 schneiden automatische Telegram-Rückfallantworten Texte
71
+ nicht mehr bei 4.096 Zeichen ab. Längere Antworten werden in
72
+ aufeinanderfolgenden Nachrichten vollständig zugestellt; dabei bleiben jedes
73
+ Zeichen und jedes Unicode-Surrogatpaar erhalten. Im Ausgangsprotokoll stehen
74
+ sämtliche zurückgegebenen Nachrichten-IDs zusammen mit dem vollständigen Text.
75
+ Schlägt ein Teil fehl, endet die Zustellung an dieser Stelle, statt die Antwort
76
+ fälschlich als vollständig zu melden.
77
+
70
78
  Zuverlässiger King-Start
71
79
  -----------------------
72
80
  Bei einer ausdrücklich als wiederholbar gekennzeichneten Serverüberlastung
package/README.md CHANGED
@@ -82,6 +82,14 @@ Bereinigung und Mikrokompaktierung verwenden dafür dieselbe semantische Grenze.
82
82
  Erst nach der nachweislichen Auswertung darf ein Ergebnis archiviert oder
83
83
  gekürzt werden.
84
84
 
85
+ Ab BLUN King 9.1.417 schneiden automatische Telegram-Rückfallantworten Texte
86
+ nicht mehr bei 4.096 Zeichen ab. Längere Antworten werden in
87
+ aufeinanderfolgenden Nachrichten vollständig zugestellt; dabei bleiben jedes
88
+ Zeichen und jedes Unicode-Surrogatpaar erhalten. Im Ausgangsprotokoll stehen
89
+ sämtliche zurückgegebenen Nachrichten-IDs zusammen mit dem vollständigen Text.
90
+ Schlägt ein Teil fehl, endet die Zustellung an dieser Stelle, statt die Antwort
91
+ fälschlich als vollständig zu melden.
92
+
85
93
  `Strg+C` und `Esc` brechen einen aktiven Zug zuverlässig ab, ohne den bereits
86
94
  geschriebenen Entwurf zu löschen. Das gilt auch bei Autovervollständigung,
87
95
  Geistervorschlägen, Bash-Eingabe und einer noch offenen Mehrzeileneingabe.
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const TELEGRAM_TEXT_LIMIT = 4096;
4
+
5
+ function preferredCut(text, start, hardEnd) {
6
+ const minimum = start + Math.floor((hardEnd - start) / 2);
7
+ const boundaries = [
8
+ ['\n\n', 2],
9
+ ['\n', 1],
10
+ [' ', 1],
11
+ ];
12
+ for (const [boundary, width] of boundaries) {
13
+ const index = text.lastIndexOf(boundary, hardEnd - 1);
14
+ if (index >= minimum) return index + width;
15
+ }
16
+ return hardEnd;
17
+ }
18
+
19
+ function preserveSurrogatePair(text, start, end) {
20
+ if (end <= start || end >= text.length) return end;
21
+ const before = text.charCodeAt(end - 1);
22
+ const after = text.charCodeAt(end);
23
+ const splitsPair = before >= 0xD800 && before <= 0xDBFF && after >= 0xDC00 && after <= 0xDFFF;
24
+ return splitsPair && end - 1 > start ? end - 1 : end;
25
+ }
26
+
27
+ function splitTelegramText(value, limit = TELEGRAM_TEXT_LIMIT) {
28
+ const text = typeof value === 'string' ? value : String(value ?? '');
29
+ if (text.length === 0) return [];
30
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
31
+ throw new TypeError('Telegram text chunk limit must be a positive safe integer');
32
+ }
33
+
34
+ const chunks = [];
35
+ let start = 0;
36
+ while (start < text.length) {
37
+ const hardEnd = Math.min(text.length, start + limit);
38
+ let end = hardEnd < text.length ? preferredCut(text, start, hardEnd) : hardEnd;
39
+ end = preserveSurrogatePair(text, start, end);
40
+ if (end <= start) end = hardEnd;
41
+ chunks.push(text.slice(start, end));
42
+ start = end;
43
+ }
44
+ return chunks;
45
+ }
46
+
47
+ async function sendTelegramTextChunks(value, sendChunk, limit = TELEGRAM_TEXT_LIMIT) {
48
+ if (typeof sendChunk !== 'function') {
49
+ throw new TypeError('Telegram chunk sender must be a function');
50
+ }
51
+ const chunks = splitTelegramText(value, limit);
52
+ const results = [];
53
+ for (let index = 0; index < chunks.length; index += 1) {
54
+ results.push(await sendChunk(chunks[index], index, chunks.length));
55
+ }
56
+ return { chunks, results };
57
+ }
58
+
59
+ module.exports = {
60
+ TELEGRAM_TEXT_LIMIT,
61
+ sendTelegramTextChunks,
62
+ splitTelegramText,
63
+ };
package/blun.mjs CHANGED
@@ -514830,6 +514830,7 @@ var createMediaAutoRetrievalController, parseAcceptedMediaJob, validateCompleted
514830
514830
  ({ createMediaAutoRetrievalController, parseAcceptedMediaJob } = createRequire(import.meta.url)("./bin/media-auto-retrieval-policy.cjs"));
514831
514831
  ({ validateCompletedMedia } = createRequire(import.meta.url)("./bin/media-result-policy.cjs"));
514832
514832
  var { findDuplicateReplyWithoutNewInbound, isPrivateInternalStatusReply, sanitizePrivateConversationReply } = createRequire(import.meta.url)("./bin/telegram-private-conversation-policy.cjs");
514833
+ var { sendTelegramTextChunks } = createRequire(import.meta.url)("./bin/telegram-text-chunk-policy.cjs");
514833
514834
  const TELEGRAM_TEXT_LIMIT = 4096;
514834
514835
  const TELEGRAM_ATTACHMENT_LIMIT = 50 * 1024 * 1024;
514835
514836
  function mediaTelegramTarget(filePath) {
@@ -514970,25 +514971,29 @@ async function sendReplyFallback(chatId, text, contextOnly = false) {
514970
514971
  }
514971
514972
  const token = botToken();
514972
514973
  if (token === void 0) return false;
514973
- const body = privateSafeText.length > TELEGRAM_TEXT_LIMIT ? `${privateSafeText.slice(0, TELEGRAM_TEXT_LIMIT - 1)}…` : privateSafeText;
514974
+ const messageIds = [];
514974
514975
  try {
514975
- const payload = await (await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
514976
- method: "POST",
514977
- headers: { "Content-Type": "application/json" },
514978
- body: JSON.stringify({
514979
- chat_id: chatId,
514980
- text: body
514981
- })
514982
- })).json();
514983
- if (payload.ok !== true) return false;
514976
+ await sendTelegramTextChunks(privateSafeText, async (body) => {
514977
+ const payload = await (await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
514978
+ method: "POST",
514979
+ headers: { "Content-Type": "application/json" },
514980
+ body: JSON.stringify({
514981
+ chat_id: chatId,
514982
+ text: body
514983
+ })
514984
+ })).json();
514985
+ if (payload.ok !== true) throw new Error("Telegram fallback chunk delivery failed");
514986
+ if (payload.result?.message_id !== void 0) messageIds.push(payload.result.message_id);
514987
+ return payload;
514988
+ }, TELEGRAM_TEXT_LIMIT);
514984
514989
  try {
514985
514990
  appendFileSync(outboxPath(), `${JSON.stringify({
514986
514991
  ts: (/* @__PURE__ */ new Date()).toISOString(),
514987
514992
  direction: "out",
514988
514993
  kind: "reply-fallback",
514989
514994
  chat_id: String(chatId),
514990
- message_ids: payload.result?.message_id !== void 0 ? [payload.result.message_id] : [],
514991
- text: body
514995
+ message_ids: messageIds,
514996
+ text: privateSafeText
514992
514997
  })}\n`);
514993
514998
  } catch {}
514994
514999
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.416",
3
+ "version": "9.1.417",
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,63 @@
1
+ 'use strict';
2
+
3
+ const TELEGRAM_TEXT_LIMIT = 4096;
4
+
5
+ function preferredCut(text, start, hardEnd) {
6
+ const minimum = start + Math.floor((hardEnd - start) / 2);
7
+ const boundaries = [
8
+ ['\n\n', 2],
9
+ ['\n', 1],
10
+ [' ', 1],
11
+ ];
12
+ for (const [boundary, width] of boundaries) {
13
+ const index = text.lastIndexOf(boundary, hardEnd - 1);
14
+ if (index >= minimum) return index + width;
15
+ }
16
+ return hardEnd;
17
+ }
18
+
19
+ function preserveSurrogatePair(text, start, end) {
20
+ if (end <= start || end >= text.length) return end;
21
+ const before = text.charCodeAt(end - 1);
22
+ const after = text.charCodeAt(end);
23
+ const splitsPair = before >= 0xD800 && before <= 0xDBFF && after >= 0xDC00 && after <= 0xDFFF;
24
+ return splitsPair && end - 1 > start ? end - 1 : end;
25
+ }
26
+
27
+ function splitTelegramText(value, limit = TELEGRAM_TEXT_LIMIT) {
28
+ const text = typeof value === 'string' ? value : String(value ?? '');
29
+ if (text.length === 0) return [];
30
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
31
+ throw new TypeError('Telegram text chunk limit must be a positive safe integer');
32
+ }
33
+
34
+ const chunks = [];
35
+ let start = 0;
36
+ while (start < text.length) {
37
+ const hardEnd = Math.min(text.length, start + limit);
38
+ let end = hardEnd < text.length ? preferredCut(text, start, hardEnd) : hardEnd;
39
+ end = preserveSurrogatePair(text, start, end);
40
+ if (end <= start) end = hardEnd;
41
+ chunks.push(text.slice(start, end));
42
+ start = end;
43
+ }
44
+ return chunks;
45
+ }
46
+
47
+ async function sendTelegramTextChunks(value, sendChunk, limit = TELEGRAM_TEXT_LIMIT) {
48
+ if (typeof sendChunk !== 'function') {
49
+ throw new TypeError('Telegram chunk sender must be a function');
50
+ }
51
+ const chunks = splitTelegramText(value, limit);
52
+ const results = [];
53
+ for (let index = 0; index < chunks.length; index += 1) {
54
+ results.push(await sendChunk(chunks[index], index, chunks.length));
55
+ }
56
+ return { chunks, results };
57
+ }
58
+
59
+ module.exports = {
60
+ TELEGRAM_TEXT_LIMIT,
61
+ sendTelegramTextChunks,
62
+ splitTelegramText,
63
+ };
@@ -10,10 +10,12 @@ import remoteStatusPolicy from "../../bin/telegram-remote-status-policy.cjs";
10
10
  import consoleStatusPolicy from "../../bin/telegram-console-status-policy.cjs";
11
11
  import telegramApprovalRelay from "../../bin/telegram-approval-relay.cjs";
12
12
  import privateConversationPolicy from "../bin/telegram-private-conversation-policy.cjs";
13
+ import telegramTextChunkPolicy from "../bin/telegram-text-chunk-policy.cjs";
13
14
  const { buildTelegramRemoteStatus, resolveTelegramQueueSnapshot, resolveTelegramRemoteVersion } = remoteStatusPolicy;
14
15
  const { parseTelegramConsoleStatus } = consoleStatusPolicy;
15
16
  const { buildTelegramApprovalCard, isAuthorizedTelegramApprovalCallback, listPendingTelegramApprovals, markTelegramApprovalSent, parseTelegramApprovalCallback, resolveTelegramApprovalTarget, wasTelegramApprovalSent, writeTelegramApprovalResponse } = telegramApprovalRelay;
16
17
  const { findDuplicateReplyWithoutNewInbound, isPrivateInternalStatusReply, sanitizePrivateConversationReply } = privateConversationPolicy;
18
+ const { sendTelegramTextChunks } = telegramTextChunkPolicy;
17
19
  //#region ../../node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
18
20
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
19
21
  function normalizeWindowsPath(input = "") {
@@ -4223,25 +4225,29 @@ async function sendReplyFallback(chatId, rawText) {
4223
4225
  return true;
4224
4226
  }
4225
4227
  const text = scrubSecrets(privateSafeText);
4226
- const body = text.length > TELEGRAM_TEXT_LIMIT ? `${text.slice(0, TELEGRAM_TEXT_LIMIT - 1)}…` : text;
4228
+ const messageIds = [];
4227
4229
  try {
4228
- const payload = await (await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
4229
- method: "POST",
4230
- headers: { "Content-Type": "application/json" },
4231
- body: JSON.stringify({
4232
- chat_id: chatId,
4233
- text: body
4234
- })
4235
- })).json();
4236
- if (payload.ok !== true) return false;
4230
+ await sendTelegramTextChunks(text, async (body) => {
4231
+ const payload = await (await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
4232
+ method: "POST",
4233
+ headers: { "Content-Type": "application/json" },
4234
+ body: JSON.stringify({
4235
+ chat_id: chatId,
4236
+ text: body
4237
+ })
4238
+ })).json();
4239
+ if (payload.ok !== true) throw new Error("Telegram fallback chunk delivery failed");
4240
+ if (payload.result?.message_id !== void 0) messageIds.push(payload.result.message_id);
4241
+ return payload;
4242
+ }, TELEGRAM_TEXT_LIMIT);
4237
4243
  try {
4238
4244
  appendFileSync(outboxLog(), `${JSON.stringify({
4239
4245
  ts: (/* @__PURE__ */ new Date()).toISOString(),
4240
4246
  direction: "out",
4241
4247
  kind: "reply-fallback",
4242
4248
  chat_id: String(chatId),
4243
- message_ids: payload.result?.message_id !== void 0 ? [payload.result.message_id] : [],
4244
- text: body
4249
+ message_ids: messageIds,
4250
+ text
4245
4251
  })}\n`);
4246
4252
  } catch {}
4247
4253
  return true;