blun-king-cli 9.1.415 → 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
@@ -59,6 +59,22 @@ konkurrierenden Start zurückgewiesen und bleiben an der Spitze der
59
59
  FIFO-Warteschlange. Für diesen normalen Wartestatus erscheint kein
60
60
  `turn.agent_busy`-Fehler.
61
61
 
62
+ Ab BLUN King 9.1.416 bleiben alle Ergebnisse des jüngsten Werkzeugaufrufs bis
63
+ zur nächsten Assistentenantwort vollständig im Modellkontext. Das gilt auch für
64
+ große parallele Read-, Grep- und Bash-Aufrufe sowie für nachträglich
65
+ eingespielte Telegram- oder Steuerungsnachrichten. Offloader, historische
66
+ Bereinigung und Mikrokompaktierung verwenden dafür dieselbe semantische Grenze.
67
+ Erst nach der nachweislichen Auswertung darf ein Ergebnis archiviert oder
68
+ gekürzt werden.
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
+
62
78
  Zuverlässiger King-Start
63
79
  -----------------------
64
80
  Bei einer ausdrücklich als wiederholbar gekennzeichneten Serverüberlastung
package/README.md CHANGED
@@ -73,6 +73,23 @@ Telegram-Nachrichten, die bei bereits aktivem Kernzug eintreffen, werden ohne
73
73
  konkurrierenden Start zurückgewiesen und bleiben an der Spitze der
74
74
  FIFO-Warteschlange. Für diesen normalen Wartestatus erscheint kein
75
75
  `turn.agent_busy`-Fehler.
76
+
77
+ Ab BLUN King 9.1.416 bleiben alle Ergebnisse des jüngsten Werkzeugaufrufs bis
78
+ zur nächsten Assistentenantwort vollständig im Modellkontext. Das gilt auch für
79
+ große parallele Read-, Grep- und Bash-Aufrufe sowie für nachträglich
80
+ eingespielte Telegram- oder Steuerungsnachrichten. Offloader, historische
81
+ Bereinigung und Mikrokompaktierung verwenden dafür dieselbe semantische Grenze.
82
+ Erst nach der nachweislichen Auswertung darf ein Ergebnis archiviert oder
83
+ gekürzt werden.
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
+
76
93
  `Strg+C` und `Esc` brechen einen aktiven Zug zuverlässig ab, ohne den bereits
77
94
  geschriebenen Entwurf zu löschen. Das gilt auch bei Autovervollständigung,
78
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
+ };
@@ -43,6 +43,32 @@ function isPersistedToolResultReference(content) {
43
43
  });
44
44
  }
45
45
 
46
+ function freshToolResultIds(messages) {
47
+ if (!Array.isArray(messages) || messages.length === 0) return new Set();
48
+
49
+ let assistantIndex = -1;
50
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
51
+ if (messages[index]?.role !== 'assistant') continue;
52
+ assistantIndex = index;
53
+ break;
54
+ }
55
+ if (assistantIndex < 0 || !Array.isArray(messages[assistantIndex].toolCalls)) return new Set();
56
+
57
+ const requestedIds = new Set(messages[assistantIndex].toolCalls
58
+ .map((call) => call?.id ?? call?.toolCallId)
59
+ .filter((id) => typeof id === 'string' && id.length > 0));
60
+ if (requestedIds.size === 0) return new Set();
61
+
62
+ const resultIds = new Set();
63
+ for (let index = assistantIndex + 1; index < messages.length; index += 1) {
64
+ const message = messages[index];
65
+ if (message?.role === 'tool' && requestedIds.has(message.toolCallId)) {
66
+ resultIds.add(message.toolCallId);
67
+ }
68
+ }
69
+ return resultIds;
70
+ }
71
+
46
72
  function compactPersistedToolResultReference(content) {
47
73
  if (!isPersistedToolResultReference(content)) return content;
48
74
 
@@ -84,11 +110,13 @@ function compactHistoricalSuccessfulToolResults(messages) {
84
110
  if (!Array.isArray(messages) || messages.length === 0) return messages;
85
111
 
86
112
  const recentStart = Math.max(0, messages.length - TOOL_RESULT_SUCCESS_KEEP_RECENT_MESSAGES);
113
+ const freshIds = freshToolResultIds(messages);
87
114
  let changed = false;
88
115
  const projected = messages.map((message, index) => {
89
116
  if (
90
117
  index >= recentStart
91
118
  || message?.role !== 'tool'
119
+ || freshIds.has(message.toolCallId)
92
120
  || message.isError === true
93
121
  || !Array.isArray(message.content)
94
122
  || isPersistedToolResultReference(message.content)
@@ -268,6 +296,7 @@ module.exports = {
268
296
  compactPersistedToolResultReference,
269
297
  createToolResultPreview,
270
298
  dedupeRepeatedSuccessfulToolResults,
299
+ freshToolResultIds,
271
300
  isPersistedToolResultReference,
272
301
  selectHistoricalToolResultOffloads,
273
302
  selectToolResultBatchOffloads,
package/blun.mjs CHANGED
@@ -75994,10 +75994,10 @@ var init_full = __esmMin((() => {
75994
75994
  }));
75995
75995
  //#endregion
75996
75996
  //#region ../../packages/agent-core/src/agent/compaction/micro.ts
75997
- var selectMicroCompactionCutoff, MICRO_COMPACTION_RECENT_MESSAGES, MICRO_COMPACTION_TOOL_ARGUMENTS_ENABLED, MICRO_COMPACTION_TOOL_ARGUMENT_MIN_TOKENS, isPersistedToolResultReference, projectHistoricalUnaddressedTelegramMessages, dedupeRepeatedUserMessages, projectRepeatedAssistantResponses, compactHistoricalSkillActivations, dedupeRecurringCronWakeups, dedupeRepeatedInjections, projectLoopEventForRecord, projectUsageModelForRecord, projectUsageForRecord, resolveCompletedStepUuid, resolveStepEventUuid, restoreUsageFromRecord, restoreUsageModelFromRecord, DEFAULT_CONFIG, MicroCompaction;
75997
+ var selectMicroCompactionCutoff, MICRO_COMPACTION_RECENT_MESSAGES, MICRO_COMPACTION_TOOL_ARGUMENTS_ENABLED, MICRO_COMPACTION_TOOL_ARGUMENT_MIN_TOKENS, isPersistedToolResultReference, freshToolResultIds, projectHistoricalUnaddressedTelegramMessages, dedupeRepeatedUserMessages, projectRepeatedAssistantResponses, compactHistoricalSkillActivations, dedupeRecurringCronWakeups, dedupeRepeatedInjections, projectLoopEventForRecord, projectUsageModelForRecord, projectUsageForRecord, resolveCompletedStepUuid, resolveStepEventUuid, restoreUsageFromRecord, restoreUsageModelFromRecord, DEFAULT_CONFIG, MicroCompaction;
75998
75998
  var init_micro = __esmMin((() => {
75999
75999
  ({ selectMicroCompactionCutoff, MICRO_COMPACTION_RECENT_MESSAGES, MICRO_COMPACTION_TOOL_ARGUMENTS_ENABLED, MICRO_COMPACTION_TOOL_ARGUMENT_MIN_TOKENS } = createRequire(import.meta.url)("./bin/micro-compaction-policy.cjs"));
76000
- ({ isPersistedToolResultReference } = createRequire(import.meta.url)("./bin/tool-result-offload-policy.cjs"));
76000
+ ({ isPersistedToolResultReference, freshToolResultIds } = createRequire(import.meta.url)("./bin/tool-result-offload-policy.cjs"));
76001
76001
  ({ projectHistoricalUnaddressedTelegramMessages } = createRequire(import.meta.url)("./bin/telegram-context-projection-policy.cjs"));
76002
76002
  ({ dedupeRepeatedUserMessages } = createRequire(import.meta.url)("./bin/repeated-user-message-projection.cjs"));
76003
76003
  ({ projectRepeatedAssistantResponses } = createRequire(import.meta.url)("./bin/repeated-assistant-response-policy.cjs"));
@@ -76093,11 +76093,12 @@ var init_micro = __esmMin((() => {
76093
76093
  const config = this.config;
76094
76094
  const historicalArgumentCutoff = Math.max(this.cutoff, messages.length - config.keepRecentMessages);
76095
76095
  const completedToolCallIds = new Set(messages.filter((message) => message?.role === "tool" && message.toolCallId !== void 0).map((message) => message.toolCallId));
76096
+ const protectedToolResultIds = freshToolResultIds(messages);
76096
76097
  const serializedArgumentsMarker = JSON.stringify({ _blun_compacted: config.truncatedArgumentsMarker });
76097
76098
  const result = [];
76098
76099
  let i = 0;
76099
76100
  for (const msg of messages) {
76100
- if (i < this.cutoff && msg.role === "tool" && msg.toolCallId !== void 0 && !isPersistedToolResultReference(msg.content) && estimateTokensForContentParts(msg.content) >= config.minContentTokens) result.push({
76101
+ if (i < this.cutoff && msg.role === "tool" && msg.toolCallId !== void 0 && !protectedToolResultIds.has(msg.toolCallId) && !isPersistedToolResultReference(msg.content) && estimateTokensForContentParts(msg.content) >= config.minContentTokens) result.push({
76101
76102
  ...msg,
76102
76103
  content: [{
76103
76104
  type: "text",
@@ -76139,10 +76140,11 @@ var init_micro = __esmMin((() => {
76139
76140
  let truncatedToolArgumentTokensBefore = 0;
76140
76141
  let truncatedToolArgumentTokensAfter = 0;
76141
76142
  const completedToolCallIds = new Set(messages.filter((message) => message?.role === "tool" && message.toolCallId !== void 0).map((message) => message.toolCallId));
76143
+ const protectedToolResultIds = freshToolResultIds(messages);
76142
76144
  const serializedArgumentsMarker = JSON.stringify({ _blun_compacted: this.config.truncatedArgumentsMarker });
76143
76145
  for (let i = 0; i < messages.length && i < cutoff; i++) {
76144
76146
  const message = messages[i];
76145
- if (message?.role === "tool" && message.toolCallId !== void 0 && !isPersistedToolResultReference(message.content)) {
76147
+ if (message?.role === "tool" && message.toolCallId !== void 0 && !protectedToolResultIds.has(message.toolCallId) && !isPersistedToolResultReference(message.content)) {
76146
76148
  const contentTokens = estimateTokensForContentParts(message.content);
76147
76149
  if (contentTokens >= this.config.minContentTokens) {
76148
76150
  markerTokenCount ??= estimateTokensForContentParts([{
@@ -260827,6 +260829,7 @@ var init_tool_result_budget = __esmMin((() => {
260827
260829
  compactPersistedToolResultReference = toolResultOffloadPolicy.compactPersistedToolResultReference;
260828
260830
  compactHistoricalSuccessfulToolResults = toolResultOffloadPolicy.compactHistoricalSuccessfulToolResults;
260829
260831
  dedupeRepeatedSuccessfulToolResults = toolResultOffloadPolicy.dedupeRepeatedSuccessfulToolResults;
260832
+ freshToolResultIds = toolResultOffloadPolicy.freshToolResultIds;
260830
260833
  selectToolResultBatchOffloads = toolResultOffloadPolicy.selectToolResultBatchOffloads;
260831
260834
  selectHistoricalToolResultOffloads = toolResultOffloadPolicy.selectHistoricalToolResultOffloads;
260832
260835
  }));
@@ -260840,13 +260843,7 @@ var ToolResultBatchOffload = class {
260840
260843
  }
260841
260844
  async detect() {
260842
260845
  const history = this.agent.context.history;
260843
- const tail = [];
260844
- for (let index = history.length - 1; index >= 0; index -= 1) {
260845
- const message = history[index];
260846
- if (message?.role !== "tool") break;
260847
- tail.unshift(message);
260848
- }
260849
- const tailIds = new Set(tail.map((message) => message.toolCallId).filter((id) => id !== void 0));
260846
+ const tailIds = freshToolResultIds(history);
260850
260847
  const recentStart = Math.max(0, history.length - TOOL_RESULT_HISTORICAL_KEEP_RECENT_MESSAGES);
260851
260848
  const historical = history.slice(0, recentStart).filter((message) => message?.role === "tool" && !tailIds.has(message.toolCallId));
260852
260849
  const candidateBatches = [{
@@ -514833,6 +514830,7 @@ var createMediaAutoRetrievalController, parseAcceptedMediaJob, validateCompleted
514833
514830
  ({ createMediaAutoRetrievalController, parseAcceptedMediaJob } = createRequire(import.meta.url)("./bin/media-auto-retrieval-policy.cjs"));
514834
514831
  ({ validateCompletedMedia } = createRequire(import.meta.url)("./bin/media-result-policy.cjs"));
514835
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");
514836
514834
  const TELEGRAM_TEXT_LIMIT = 4096;
514837
514835
  const TELEGRAM_ATTACHMENT_LIMIT = 50 * 1024 * 1024;
514838
514836
  function mediaTelegramTarget(filePath) {
@@ -514973,25 +514971,29 @@ async function sendReplyFallback(chatId, text, contextOnly = false) {
514973
514971
  }
514974
514972
  const token = botToken();
514975
514973
  if (token === void 0) return false;
514976
- const body = privateSafeText.length > TELEGRAM_TEXT_LIMIT ? `${privateSafeText.slice(0, TELEGRAM_TEXT_LIMIT - 1)}…` : privateSafeText;
514974
+ const messageIds = [];
514977
514975
  try {
514978
- const payload = await (await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
514979
- method: "POST",
514980
- headers: { "Content-Type": "application/json" },
514981
- body: JSON.stringify({
514982
- chat_id: chatId,
514983
- text: body
514984
- })
514985
- })).json();
514986
- 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);
514987
514989
  try {
514988
514990
  appendFileSync(outboxPath(), `${JSON.stringify({
514989
514991
  ts: (/* @__PURE__ */ new Date()).toISOString(),
514990
514992
  direction: "out",
514991
514993
  kind: "reply-fallback",
514992
514994
  chat_id: String(chatId),
514993
- message_ids: payload.result?.message_id !== void 0 ? [payload.result.message_id] : [],
514994
- text: body
514995
+ message_ids: messageIds,
514996
+ text: privateSafeText
514995
514997
  })}\n`);
514996
514998
  } catch {}
514997
514999
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.415",
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;