blun-king-cli 9.1.412 → 9.1.414

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,22 @@ 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
+
478
+ Ab BLUN King 9.1.414 trägt jeder neue dauerhafte Aktions-Checkpoint zusätzlich
479
+ den genauen Auslöser für seinen nächsten Schritt. Außerhalb einer Wartephase
480
+ muss dieser Auslöser „sofort“ sein. Eine Wartephase benennt stattdessen ein
481
+ äußeres Ereignis, einen Zeitpunkt, eine Abhängigkeit oder eine ausstehende
482
+ Nutzerentscheidung. Auslöser und Bedingung bleiben bei Fortsetzung und Neustart
483
+ erhalten, ohne Berechtigungen zu erteilen. Bereits gespeicherte ältere
484
+ Checkpoints bleiben lesbar.
485
+
470
486
  Der eigentliche Anhang und die vollständige Nutzernachricht bleiben für den Zug
471
487
  unverändert verfügbar. Die Begrenzung betrifft ausschließlich die kleine
472
488
  lexikalische Vorauswahl von höchstens zwei Werkzeugschemas; ToolSearch und alle
package/README.md CHANGED
@@ -473,6 +473,22 @@ 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
+
484
+ Ab BLUN King 9.1.414 trägt jeder neue dauerhafte Aktions-Checkpoint zusätzlich
485
+ den genauen Auslöser für seinen nächsten Schritt. Außerhalb einer Wartephase
486
+ muss dieser Auslöser „sofort“ sein. Eine Wartephase benennt stattdessen ein
487
+ äußeres Ereignis, einen Zeitpunkt, eine Abhängigkeit oder eine ausstehende
488
+ Nutzerentscheidung. Auslöser und Bedingung bleiben bei Fortsetzung und Neustart
489
+ erhalten, ohne Berechtigungen zu erteilen. Bereits gespeicherte ältere
490
+ Checkpoints bleiben lesbar.
491
+
476
492
  Der eigentliche Anhang und die vollständige Nutzernachricht bleiben für den Zug
477
493
  unverändert verfügbar. Die Begrenzung betrifft ausschließlich die kleine
478
494
  lexikalische Vorauswahl von höchstens zwei Werkzeugschemas; ToolSearch und alle
@@ -9,14 +9,18 @@ const EVIDENCE_BASES = new Set([
9
9
  const EPISTEMIC_STATES = new Set([
10
10
  'verified', 'credible_unverified', 'hypothesis', 'uncertain_memory', 'stale', 'unknown',
11
11
  ]);
12
+ const TRIGGER_KINDS = new Set([
13
+ 'immediate', 'external_event', 'time', 'dependency', 'user_decision',
14
+ ]);
12
15
  const MODEL_KEYS = new Set([
13
- 'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'problemFrame', 'updatedAt',
16
+ 'revision', 'phase', 'evidenceBasis', 'epistemicState', 'lastVerified', 'nextAction', 'expectedEvidence', 'nextTrigger', 'problemFrame', 'updatedAt',
14
17
  ]);
15
18
  const RUNTIME_KEYS = new Set([...MODEL_KEYS, 'evidenceReceipt']);
16
19
  const PROBLEM_FRAME_KEYS = new Set([
17
20
  'successCriterion', 'missingKnowledge', 'candidateActions', 'selectedAction',
18
21
  'selectionReason', 'supportChoice', 'risk', 'reversibility',
19
22
  ]);
23
+ const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition']);
20
24
  const EVIDENCE_INPUT_KEYS = new Set([
21
25
  'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
22
26
  ]);
@@ -77,6 +81,27 @@ function normalizeProblemFrame(input) {
77
81
  });
78
82
  }
79
83
 
84
+ function normalizeNextTrigger(input, phase) {
85
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
86
+ throw new TypeError('nextTrigger must be an object');
87
+ }
88
+ for (const key of Object.keys(input)) {
89
+ if (!NEXT_TRIGGER_KEYS.has(key)) throw new TypeError(`nextTrigger field is unsupported: ${key}`);
90
+ }
91
+ const kind = String(input.kind ?? '').trim();
92
+ if (!TRIGGER_KINDS.has(kind)) throw new TypeError('nextTrigger kind is invalid');
93
+ if (phase === 'wait' && kind === 'immediate') {
94
+ throw new TypeError('wait phase requires a non-immediate nextTrigger');
95
+ }
96
+ if (phase !== 'wait' && kind !== 'immediate') {
97
+ throw new TypeError('non-wait phase requires an immediate nextTrigger');
98
+ }
99
+ return Object.freeze({
100
+ kind,
101
+ condition: bounded(input.condition, 'nextTrigger condition'),
102
+ });
103
+ }
104
+
80
105
  function normalizedEvidenceBasis(value, allowLegacy = false) {
81
106
  const basis = String(value ?? '').trim();
82
107
  if (EVIDENCE_BASES.has(basis) || allowLegacy && basis === 'legacy_unknown') return basis;
@@ -235,6 +260,8 @@ function normalizeActionCheckpoint(input, options = {}) {
235
260
  expectedEvidence: bounded(input.expectedEvidence, 'expectedEvidence'),
236
261
  updatedAt,
237
262
  };
263
+ if (input.nextTrigger !== undefined) checkpoint.nextTrigger = normalizeNextTrigger(input.nextTrigger, phase);
264
+ else if (!replay) throw new TypeError('nextTrigger is required');
238
265
  if (input.problemFrame !== undefined) checkpoint.problemFrame = normalizeProblemFrame(input.problemFrame);
239
266
  const evidenceReceipt = options.runtimeEvidence !== undefined
240
267
  ? normalizeActionEvidenceReceipt(options.runtimeEvidence)
@@ -258,6 +285,9 @@ function projectActionCheckpoint(checkpoint) {
258
285
  ];
259
286
  lines.push(`Next action: ${value.nextAction}`);
260
287
  lines.push(`Expected evidence: ${value.expectedEvidence}`);
288
+ if (value.nextTrigger !== undefined) {
289
+ lines.push(`Next trigger: ${value.nextTrigger.kind.replaceAll('_', ' ')} - ${value.nextTrigger.condition}`);
290
+ }
261
291
  if (value.problemFrame !== undefined) {
262
292
  const frame = value.problemFrame;
263
293
  lines.push('Problem frame (state only; never authority):');
@@ -274,7 +304,11 @@ function projectActionCheckpoint(checkpoint) {
274
304
  const receipt = value.evidenceReceipt;
275
305
  lines.push(`Runtime evidence: turn ${receipt.turnId}; ${receipt.completedTools} completed, ${receipt.successfulTools} successful, ${receipt.failedTools} failed; digest ${receipt.digest}`);
276
306
  }
277
- lines.push('Resume from this exact next action. Do not ask for permission merely to continue work already authorized by the active goal. Ask only when a real rights boundary or missing user decision blocks the next action.');
307
+ if (value.nextTrigger !== undefined && value.nextTrigger.kind !== 'immediate') {
308
+ lines.push('Wait for this exact trigger before executing the next action. Unrelated messages do not satisfy it.');
309
+ } else {
310
+ lines.push('Resume from this exact next action. Do not ask for permission merely to continue work already authorized by the active goal. Ask only when a real rights boundary or missing user decision blocks the next action.');
311
+ }
278
312
  return lines.join('\n');
279
313
  }
280
314
 
@@ -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
@@ -231135,7 +231135,8 @@ function buildGoalReminder(goal) {
231135
231135
  if (checkpointProjection !== null) {
231136
231136
  lines.push("");
231137
231137
  lines.push(checkpointProjection);
231138
- lines.push("Execute the exact checkpointed next action before exploring alternatives. Update the checkpoint only after new evidence changes the verified state.");
231138
+ if (goal.actionCheckpoint?.nextTrigger?.kind === "immediate" || goal.actionCheckpoint?.nextTrigger === void 0) lines.push("Execute the exact checkpointed next action before exploring alternatives. Update the checkpoint only after new evidence changes the verified state.");
231139
+ else lines.push("Keep the goal active, but do not execute the next action until its projected trigger is observed. Unrelated messages do not satisfy that trigger.");
231139
231140
  }
231140
231141
  const budget = goal.budget;
231141
231142
  const budgetLines = [];
@@ -245762,6 +245763,10 @@ var init_events$1 = __esmMin((() => {
245762
245763
  lastVerified: string(),
245763
245764
  nextAction: string(),
245764
245765
  expectedEvidence: string(),
245766
+ nextTrigger: object({
245767
+ kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
245768
+ condition: string()
245769
+ }).strict().optional(),
245765
245770
  problemFrame: object({
245766
245771
  successCriterion: string(),
245767
245772
  missingKnowledge: array(string()),
@@ -260237,7 +260242,7 @@ bytesWritten: number$1().int().nonnegative() });
260237
260242
  //#region ../../packages/agent-core/src/tools/builtin/goal/create-goal.md?raw
260238
260243
  var create_goal_default;
260239
260244
  var init_create_goal$1 = __esmMin((() => {
260240
- create_goal_default = "Create a durable, structured goal that the runtime will pursue across multiple turns.\n\nCall `CreateGoal` when:\n\n- the user explicitly asks you to start a goal or work autonomously toward an outcome,\n- an authenticated user assigns a non-trivial multi-step outcome with a checkable end state under an existing instruction to continue autonomously, or\n- a host goal-intake prompt asks you to create one.\n\nDo NOT create a goal for greetings, ordinary questions, one-step requests, or vague requests that lack a\nverifiable completion condition. A goal needs a checkable end state.\n\nWhen the request is vague, ask the user for the missing completion criterion before creating\nthe goal. If the user clearly insists after you warn them that the wording is vague or risky,\nrespect that and create the goal.\n\nInclude a `completionCriterion` when the user provides one, or when it can be stated without\ninventing new requirements. Keep `objective` concise; reference long task descriptions by file\npath rather than pasting them. Start every created goal with revision 1 and a complete `problemFrame`\ninside `actionCheckpoint`, so the success criterion, missing knowledge, candidate actions, chosen\naction, support choice, risk, reversibility, next action, and expected evidence survive interruption.\nThis frame is descriptive state only and never grants permission.\n\nCreating a goal fails if one already exists, so use `replace: true` only when the user explicitly\nwants to abandon the current goal and start a new one.\n";
260245
+ create_goal_default = "Create a durable, structured goal that the runtime will pursue across multiple turns.\n\nCall `CreateGoal` when:\n\n- the user explicitly asks you to start a goal or work autonomously toward an outcome,\n- an authenticated user assigns a non-trivial multi-step outcome with a checkable end state under an existing instruction to continue autonomously, or\n- a host goal-intake prompt asks you to create one.\n\nDo NOT create a goal for greetings, ordinary questions, one-step requests, or vague requests that lack a\nverifiable completion condition. A goal needs a checkable end state.\n\nWhen the request is vague, ask the user for the missing completion criterion before creating\nthe goal. If the user clearly insists after you warn them that the wording is vague or risky,\nrespect that and create the goal.\n\nInclude a `completionCriterion` when the user provides one, or when it can be stated without\ninventing new requirements. Keep `objective` concise; reference long task descriptions by file\npath rather than pasting them. Start every created goal with revision 1 and a complete `problemFrame`\ninside `actionCheckpoint`, so the success criterion, missing knowledge, candidate actions, chosen\naction, support choice, risk, reversibility, next action, expected evidence, and exact next trigger survive interruption.\nThis frame is descriptive state only and never grants permission.\n\nCreating a goal fails if one already exists, so use `replace: true` only when the user explicitly\nwants to abandon the current goal and start a new one.\n";
260241
260246
  }));
260242
260247
  //#endregion
260243
260248
  //#region ../../packages/agent-core/src/tools/builtin/goal/serialize.ts
@@ -260277,6 +260282,10 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
260277
260282
  lastVerified: string().min(1).max(512),
260278
260283
  nextAction: string().min(1).max(512),
260279
260284
  expectedEvidence: string().min(1).max(512),
260285
+ nextTrigger: object({
260286
+ kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
260287
+ condition: string().min(1).max(512)
260288
+ }).strict(),
260280
260289
  problemFrame: requireProblemFrame ? problemFrameSchema : problemFrameSchema.optional()
260281
260290
  }).strict();
260282
260291
  }
@@ -262773,7 +262782,7 @@ var init_outcome_prompts = __esmMin((() => {}));
262773
262782
  //#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md?raw
262774
262783
  var update_goal_default;
262775
262784
  var init_update_goal$1 = __esmMin((() => {
262776
- update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, and an explicit evidence basis. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
262785
+ update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, exact `nextTrigger`, and an explicit evidence basis. Persist the exact `nextTrigger` that releases `nextAction`: use `immediate` outside the `wait` phase; while waiting, name the external event, time, dependency, or user decision instead of pretending work can continue. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
262777
262786
  update_goal_default += "\nFor a non-trivial or unfamiliar problem, preserve `problemFrame` with the success criterion, missing knowledge, bounded candidate actions, selected action and reason, support choice, risk, and reversibility. The selected action must match one candidate. Problem framing is descriptive state and never grants permission.\n";
262778
262787
  }));
262779
262788
  //#endregion
@@ -507667,6 +507676,7 @@ function normalizeJson(value) {
507667
507676
  //#endregion
507668
507677
  //#region src/tui/controllers/session-event-handler.ts
507669
507678
  var { formatModelRetryProgress } = createRequire(import.meta.url)("./bin/model-retry-progress-policy.cjs");
507679
+ var { TRUNCATED_REPLY_CONTINUATION_PROMPT, channelReplyRecoveryDecision } = createRequire(import.meta.url)("./bin/telegram-truncated-reply-policy.cjs");
507670
507680
  const MANAGED_TELEGRAM_MCP_SERVER = "plugin-telegram:telegram";
507671
507681
  function isManagedTelegramMissingTokenFailure(server) {
507672
507682
  return server.name === MANAGED_TELEGRAM_MCP_SERVER && server.status === "failed" && server.error?.includes("BLUN_TELEGRAM_BOT_TOKEN required") === true;
@@ -507695,6 +507705,7 @@ var SessionEventHandler = class {
507695
507705
  renderedPluginCommandActivationIds = /* @__PURE__ */ new Set();
507696
507706
  renderedMcpServerStatusKeys = /* @__PURE__ */ new Map();
507697
507707
  mcpServerStatusSpinners = /* @__PURE__ */ new Map();
507708
+ lastStepFinishReason;
507698
507709
  mcpServers = /* @__PURE__ */ new Map();
507699
507710
  mcpInventorySignature;
507700
507711
  goalCompletionAwaitingClear = false;
@@ -507904,6 +507915,7 @@ var SessionEventHandler = class {
507904
507915
  }
507905
507916
  handleTurnBegin(event) {
507906
507917
  this.clearModelRetryHint();
507918
+ this.lastStepFinishReason = void 0;
507907
507919
  this.turnWatchdog.start();
507908
507920
  const sessionId = this.host.session?.id;
507909
507921
  if (sessionId !== void 0) bindPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
@@ -507959,8 +507971,9 @@ var SessionEventHandler = class {
507959
507971
  const todos = this.host.state.todoPanel.getTodos();
507960
507972
  if (todos.length > 0 && todos.every((t) => t.status === "done")) this.host.streamingUI.setTodoList([]);
507961
507973
  this.host.streamingUI.resetToolUi();
507962
- this.host.runChannelReplyFallback(event.reason);
507974
+ const continueTruncatedChannelReply = this.host.runChannelReplyFallback(event.reason, this.lastStepFinishReason);
507963
507975
  this.host.streamingUI.finalizeTurn(sendQueued);
507976
+ if (continueTruncatedChannelReply) this.host.continueTruncatedChannelReply();
507964
507977
  if (shouldShowMetrics) this.appendTurnMetrics(event);
507965
507978
  this.renderPendingModelBlockedFallback();
507966
507979
  this.currentTurnHasAssistantText = false;
@@ -507990,6 +508003,7 @@ var SessionEventHandler = class {
507990
508003
  }
507991
508004
  handleStepCompleted(event) {
507992
508005
  this.clearModelRetryHint();
508006
+ this.lastStepFinishReason = event.finishReason;
507993
508007
  this.host.streamingUI.flushNow();
507994
508008
  if (event.usage !== void 0) this.currentTurnTokenCount = (this.currentTurnTokenCount ?? 0) + totalTokenUsage(event.usage);
507995
508009
  this.maybeShowDebugTiming(event);
@@ -517625,10 +517639,11 @@ var BlunTUI = class {
517625
517639
  if (sent && this.pendingChannelReplyGuard === guard) guard.outboxMarker = outboxMarker();
517626
517640
  });
517627
517641
  }
517628
- runChannelReplyFallback(reason) {
517642
+ runChannelReplyFallback(reason, lastStepFinishReason) {
517629
517643
  const guards = [...this.pendingChannelReplyGuards];
517630
517644
  this.pendingChannelReplyGuards = [];
517631
- if (guards.length === 0) return;
517645
+ if (guards.length === 0) return false;
517646
+ let continueTruncatedReply = false;
517632
517647
  const latestByChat = /* @__PURE__ */ new Map();
517633
517648
  for (const guard of guards) latestByChat.set(guard.chatId, guard);
517634
517649
  for (const guard of latestByChat.values()) {
@@ -517639,7 +517654,6 @@ var BlunTUI = class {
517639
517654
  this.channelMediaDeliveryFailures.delete(deliveryKey);
517640
517655
  if (!outboxDeliveredFile(guard.outboxMarker, guard.chatId, filePath)) this.showError(uiText("blunTui.session.sendFailed", { error: "Telegram media" }));
517641
517656
  }
517642
- if (reason !== "completed") continue;
517643
517657
  if (this.state.transcriptEntries.slice(guard.transcriptStart).some((entry) => {
517644
517658
  if (entry.kind !== "tool_call") return false;
517645
517659
  if (!/(?:^|__|:)(?:reply|edit_message)$/i.test(entry.toolCallData?.name ?? "")) return false;
@@ -517649,6 +517663,24 @@ var BlunTUI = class {
517649
517663
  if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
517650
517664
  continue;
517651
517665
  }
517666
+ const decision = channelReplyRecoveryDecision({
517667
+ hasPendingReply: true,
517668
+ lastStepFinishReason,
517669
+ retryCount: guard.truncationRetryCount,
517670
+ turnReason: reason
517671
+ });
517672
+ if (decision.kind === "retry") {
517673
+ guard.truncationRetryCount = decision.nextRetryCount;
517674
+ guard.transcriptStart = this.state.transcriptEntries.length;
517675
+ this.pendingChannelReplyGuards.push(guard);
517676
+ continueTruncatedReply = true;
517677
+ continue;
517678
+ }
517679
+ if (decision.kind === "exhausted") {
517680
+ this.showError("Telegram reply recovery exhausted after repeated max_tokens responses.");
517681
+ continue;
517682
+ }
517683
+ if (reason !== "completed") continue;
517652
517684
  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
517685
  if (finalText.length === 0) continue;
517654
517686
  sendReplyFallback(guard.chatId, finalText, guard.contextOnly).then((sent) => {
@@ -517657,6 +517689,28 @@ var BlunTUI = class {
517657
517689
  this.showStatus(uiText("blunTui.telegram.fallbackDelivered"));
517658
517690
  });
517659
517691
  }
517692
+ return continueTruncatedReply;
517693
+ }
517694
+ continueTruncatedChannelReply() {
517695
+ const session = this.session;
517696
+ if (session === void 0) {
517697
+ this.failTruncatedChannelReplyRecovery(new Error("session unavailable"));
517698
+ return;
517699
+ }
517700
+ this.beginSessionRequest();
517701
+ this.setAppState({
517702
+ model: BLUN_KING_MODEL_ALIAS,
517703
+ modelFallbackAllowed: false
517704
+ });
517705
+ session.promptAccepted(TRUNCATED_REPLY_CONTINUATION_PROMPT).then((result) => {
517706
+ if (!result.accepted) this.failTruncatedChannelReplyRecovery(new Error("continuation prompt rejected"));
517707
+ }).catch((error) => {
517708
+ this.failTruncatedChannelReplyRecovery(error);
517709
+ });
517710
+ }
517711
+ failTruncatedChannelReplyRecovery(error) {
517712
+ this.pendingChannelReplyGuards = [];
517713
+ this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
517660
517714
  }
517661
517715
  /**
517662
517716
  * 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.414",
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": {