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 +8 -0
- package/README.md +8 -0
- package/bin/telegram-truncated-reply-policy.cjs +37 -0
- package/blun.mjs +49 -4
- package/package.json +1 -1
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.
|