blun-king-cli 9.1.412 → 9.1.413

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
@@ -467,6 +467,14 @@ Weiterarbeiten nutzen. Begrüßungen, gewöhnliche Einzelschrittanfragen und vag
467
467
  Aufgaben erzeugen weiterhin kein Ziel. Der Problemrahmen bleibt beschreibender
468
468
  Arbeitsstand und erteilt niemals Berechtigungen.
469
469
 
470
+ Ab BLUN King 9.1.413 werden Antworten aus Telegram-Zügen nicht mehr als fertig
471
+ versendet, wenn der letzte Modellschritt am Ausgabelimit max_tokens endete. Der
472
+ unvollständige Entwurf bleibt ausschließlich im Sitzungskontext. King erstellt
473
+ daraus automatisch eine kurze, vollständige Antwort und sendet erst diese.
474
+ Bereits über das Reply-Werkzeug zugestellte Antworten werden dabei nicht
475
+ wiederholt. Die Fortsetzung ist auf drei Versuche begrenzt; danach wird niemals
476
+ ein abgeschnittener Text als Ergebnis ausgegeben.
477
+
470
478
  Der eigentliche Anhang und die vollständige Nutzernachricht bleiben für den Zug
471
479
  unverändert verfügbar. Die Begrenzung betrifft ausschließlich die kleine
472
480
  lexikalische Vorauswahl von höchstens zwei Werkzeugschemas; ToolSearch und alle
package/README.md CHANGED
@@ -473,6 +473,14 @@ Weiterarbeiten nutzen. Begrüßungen, gewöhnliche Einzelschrittanfragen und vag
473
473
  Aufgaben erzeugen weiterhin kein Ziel. Der Problemrahmen bleibt beschreibender
474
474
  Arbeitsstand und erteilt niemals Berechtigungen.
475
475
 
476
+ Ab BLUN King 9.1.413 werden Antworten aus Telegram-Zügen nicht mehr als fertig
477
+ versendet, wenn der letzte Modellschritt am Ausgabelimit `max_tokens` endete.
478
+ Der unvollständige Entwurf bleibt ausschließlich im Sitzungskontext. King
479
+ erstellt daraus automatisch eine kurze, vollständige Antwort und sendet erst
480
+ diese. Bereits über das Reply-Werkzeug zugestellte Antworten werden dabei nicht
481
+ wiederholt. Die Fortsetzung ist auf drei Versuche begrenzt; danach wird niemals
482
+ ein abgeschnittener Text als Ergebnis ausgegeben.
483
+
476
484
  Der eigentliche Anhang und die vollständige Nutzernachricht bleiben für den Zug
477
485
  unverändert verfügbar. Die Begrenzung betrifft ausschließlich die kleine
478
486
  lexikalische Vorauswahl von höchstens zwei Werkzeugschemas; ToolSearch und alle
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const MAX_TRUNCATED_REPLY_RETRIES = 3;
4
+
5
+ const TRUNCATED_REPLY_CONTINUATION_PROMPT = [
6
+ 'The previous channel reply hit the model output limit and was not delivered.',
7
+ 'Produce one complete, concise final answer now from the preserved context.',
8
+ 'Include every result the user needs, omit internal reasoning, do not mention this recovery,',
9
+ 'and keep the visible answer under 3000 characters.',
10
+ 'Do not call tools unless a missing fact must be verified.',
11
+ ].join(' ');
12
+
13
+ function channelReplyRecoveryDecision({
14
+ hasPendingReply = false,
15
+ lastStepFinishReason,
16
+ retryCount = 0,
17
+ turnReason,
18
+ } = {}) {
19
+ if (!hasPendingReply || turnReason !== 'completed' || lastStepFinishReason !== 'max_tokens') {
20
+ return { kind: 'fallback' };
21
+ }
22
+ const boundedRetryCount = Number.isSafeInteger(retryCount) && retryCount > 0
23
+ ? retryCount
24
+ : 0;
25
+ if (boundedRetryCount >= MAX_TRUNCATED_REPLY_RETRIES) return { kind: 'exhausted' };
26
+ return {
27
+ kind: 'retry',
28
+ nextRetryCount: boundedRetryCount + 1,
29
+ prompt: TRUNCATED_REPLY_CONTINUATION_PROMPT,
30
+ };
31
+ }
32
+
33
+ module.exports = {
34
+ MAX_TRUNCATED_REPLY_RETRIES,
35
+ TRUNCATED_REPLY_CONTINUATION_PROMPT,
36
+ channelReplyRecoveryDecision,
37
+ };
package/blun.mjs CHANGED
@@ -507667,6 +507667,7 @@ function normalizeJson(value) {
507667
507667
  //#endregion
507668
507668
  //#region src/tui/controllers/session-event-handler.ts
507669
507669
  var { formatModelRetryProgress } = createRequire(import.meta.url)("./bin/model-retry-progress-policy.cjs");
507670
+ var { TRUNCATED_REPLY_CONTINUATION_PROMPT, channelReplyRecoveryDecision } = createRequire(import.meta.url)("./bin/telegram-truncated-reply-policy.cjs");
507670
507671
  const MANAGED_TELEGRAM_MCP_SERVER = "plugin-telegram:telegram";
507671
507672
  function isManagedTelegramMissingTokenFailure(server) {
507672
507673
  return server.name === MANAGED_TELEGRAM_MCP_SERVER && server.status === "failed" && server.error?.includes("BLUN_TELEGRAM_BOT_TOKEN required") === true;
@@ -507695,6 +507696,7 @@ var SessionEventHandler = class {
507695
507696
  renderedPluginCommandActivationIds = /* @__PURE__ */ new Set();
507696
507697
  renderedMcpServerStatusKeys = /* @__PURE__ */ new Map();
507697
507698
  mcpServerStatusSpinners = /* @__PURE__ */ new Map();
507699
+ lastStepFinishReason;
507698
507700
  mcpServers = /* @__PURE__ */ new Map();
507699
507701
  mcpInventorySignature;
507700
507702
  goalCompletionAwaitingClear = false;
@@ -507904,6 +507906,7 @@ var SessionEventHandler = class {
507904
507906
  }
507905
507907
  handleTurnBegin(event) {
507906
507908
  this.clearModelRetryHint();
507909
+ this.lastStepFinishReason = void 0;
507907
507910
  this.turnWatchdog.start();
507908
507911
  const sessionId = this.host.session?.id;
507909
507912
  if (sessionId !== void 0) bindPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
@@ -507959,8 +507962,9 @@ var SessionEventHandler = class {
507959
507962
  const todos = this.host.state.todoPanel.getTodos();
507960
507963
  if (todos.length > 0 && todos.every((t) => t.status === "done")) this.host.streamingUI.setTodoList([]);
507961
507964
  this.host.streamingUI.resetToolUi();
507962
- this.host.runChannelReplyFallback(event.reason);
507965
+ const continueTruncatedChannelReply = this.host.runChannelReplyFallback(event.reason, this.lastStepFinishReason);
507963
507966
  this.host.streamingUI.finalizeTurn(sendQueued);
507967
+ if (continueTruncatedChannelReply) this.host.continueTruncatedChannelReply();
507964
507968
  if (shouldShowMetrics) this.appendTurnMetrics(event);
507965
507969
  this.renderPendingModelBlockedFallback();
507966
507970
  this.currentTurnHasAssistantText = false;
@@ -507990,6 +507994,7 @@ var SessionEventHandler = class {
507990
507994
  }
507991
507995
  handleStepCompleted(event) {
507992
507996
  this.clearModelRetryHint();
507997
+ this.lastStepFinishReason = event.finishReason;
507993
507998
  this.host.streamingUI.flushNow();
507994
507999
  if (event.usage !== void 0) this.currentTurnTokenCount = (this.currentTurnTokenCount ?? 0) + totalTokenUsage(event.usage);
507995
508000
  this.maybeShowDebugTiming(event);
@@ -517625,10 +517630,11 @@ var BlunTUI = class {
517625
517630
  if (sent && this.pendingChannelReplyGuard === guard) guard.outboxMarker = outboxMarker();
517626
517631
  });
517627
517632
  }
517628
- runChannelReplyFallback(reason) {
517633
+ runChannelReplyFallback(reason, lastStepFinishReason) {
517629
517634
  const guards = [...this.pendingChannelReplyGuards];
517630
517635
  this.pendingChannelReplyGuards = [];
517631
- if (guards.length === 0) return;
517636
+ if (guards.length === 0) return false;
517637
+ let continueTruncatedReply = false;
517632
517638
  const latestByChat = /* @__PURE__ */ new Map();
517633
517639
  for (const guard of guards) latestByChat.set(guard.chatId, guard);
517634
517640
  for (const guard of latestByChat.values()) {
@@ -517639,7 +517645,6 @@ var BlunTUI = class {
517639
517645
  this.channelMediaDeliveryFailures.delete(deliveryKey);
517640
517646
  if (!outboxDeliveredFile(guard.outboxMarker, guard.chatId, filePath)) this.showError(uiText("blunTui.session.sendFailed", { error: "Telegram media" }));
517641
517647
  }
517642
- if (reason !== "completed") continue;
517643
517648
  if (this.state.transcriptEntries.slice(guard.transcriptStart).some((entry) => {
517644
517649
  if (entry.kind !== "tool_call") return false;
517645
517650
  if (!/(?:^|__|:)(?:reply|edit_message)$/i.test(entry.toolCallData?.name ?? "")) return false;
@@ -517649,6 +517654,24 @@ var BlunTUI = class {
517649
517654
  if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
517650
517655
  continue;
517651
517656
  }
517657
+ const decision = channelReplyRecoveryDecision({
517658
+ hasPendingReply: true,
517659
+ lastStepFinishReason,
517660
+ retryCount: guard.truncationRetryCount,
517661
+ turnReason: reason
517662
+ });
517663
+ if (decision.kind === "retry") {
517664
+ guard.truncationRetryCount = decision.nextRetryCount;
517665
+ guard.transcriptStart = this.state.transcriptEntries.length;
517666
+ this.pendingChannelReplyGuards.push(guard);
517667
+ continueTruncatedReply = true;
517668
+ continue;
517669
+ }
517670
+ if (decision.kind === "exhausted") {
517671
+ this.showError("Telegram reply recovery exhausted after repeated max_tokens responses.");
517672
+ continue;
517673
+ }
517674
+ if (reason !== "completed") continue;
517652
517675
  const finalText = this.state.transcriptEntries.slice(guard.transcriptStart).filter((entry) => entry.kind === "assistant" && entry.content.trim().length > 0).map((entry) => entry.content.trim()).at(-1) ?? "";
517653
517676
  if (finalText.length === 0) continue;
517654
517677
  sendReplyFallback(guard.chatId, finalText, guard.contextOnly).then((sent) => {
@@ -517657,6 +517680,28 @@ var BlunTUI = class {
517657
517680
  this.showStatus(uiText("blunTui.telegram.fallbackDelivered"));
517658
517681
  });
517659
517682
  }
517683
+ return continueTruncatedReply;
517684
+ }
517685
+ continueTruncatedChannelReply() {
517686
+ const session = this.session;
517687
+ if (session === void 0) {
517688
+ this.failTruncatedChannelReplyRecovery(new Error("session unavailable"));
517689
+ return;
517690
+ }
517691
+ this.beginSessionRequest();
517692
+ this.setAppState({
517693
+ model: BLUN_KING_MODEL_ALIAS,
517694
+ modelFallbackAllowed: false
517695
+ });
517696
+ session.promptAccepted(TRUNCATED_REPLY_CONTINUATION_PROMPT).then((result) => {
517697
+ if (!result.accepted) this.failTruncatedChannelReplyRecovery(new Error("continuation prompt rejected"));
517698
+ }).catch((error) => {
517699
+ this.failTruncatedChannelReplyRecovery(error);
517700
+ });
517701
+ }
517702
+ failTruncatedChannelReplyRecovery(error) {
517703
+ this.pendingChannelReplyGuards = [];
517704
+ this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
517660
517705
  }
517661
517706
  /**
517662
517707
  * Attach the Telegram channel to this TUI when a bot token is configured.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.412",
3
+ "version": "9.1.413",
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": {