blun-king-cli 9.1.495 → 9.1.496

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.1.496 - 2026-08-30
4
+
5
+ - Delivers Telegram user messages automatically one at a time without depending on the manual Ctrl+S or Escape release counter.
6
+ - Retires only the exact accepted or answered Telegram message ID, preserving newer messages from the same chat and removing delivered items from the visible queue.
7
+ - Enforces truthful progress for every existing visible TodoList, including sessions without an active Goal, and rejects unchanged refreshes after extended work.
8
+ - Hides successful internal hook payloads from visible output while keeping blocking hook failures visible.
9
+
3
10
  ## 9.1.495 - 2026-08-30
4
11
 
5
12
  - Starts every new or replaced goal with a fresh visible TodoList and requires a truthful refresh after at most eight work batches.
package/LIESMICH.txt CHANGED
@@ -9,7 +9,7 @@ Installation
9
9
  ------------
10
10
  Die geprüfte Version exakt global installieren:
11
11
 
12
- npm install -g blun-king-cli@9.1.495
12
+ npm install -g blun-king-cli@9.1.496
13
13
 
14
14
  Start
15
15
  -----
@@ -65,6 +65,14 @@ geschriebenen Entwurf zu löschen. Das gilt auch bei
65
65
  Autovervollständigung, Geistervorschlägen, Bash-Eingabe und einer offenen
66
66
  Mehrzeileneingabe.
67
67
 
68
+ Version 9.1.496 stellt Telegram-Nutzernachrichten automatisch einzeln zu, ohne
69
+ die automatische Zustellung an den manuellen Strg+S-/Escape-Zähler zu koppeln.
70
+ Nach Annahme oder Antwort wird nur die exakt verarbeitete Nachrichten-ID aus der
71
+ sichtbaren Queue entfernt; spätere Meldungen desselben Chats bleiben erhalten.
72
+ Vorhandene Todo-Listen bleiben auch ohne aktives Ziel verbindlich und müssen nach
73
+ längerer Arbeit belegbaren Fortschritt zeigen. Erfolgreiche interne Hook-Ausgaben
74
+ bleiben unsichtbar, blockierende Hook-Fehler werden weiterhin angezeigt.
75
+
68
76
  Version 9.1.495 startet jedes neue Ziel mit einer frischen sichtbaren Todo-Liste,
69
77
  erzwingt regelmäßige wahrheitsgemäße Fortschrittsstände und quittiert normale
70
78
  Telegram-Nachrichten erst am Zugende. Dadurch bleiben sie nach einem Neustart
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.495
12
+ npm install -g blun-king-cli@9.1.496
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -81,6 +81,14 @@ konkurrierenden Start zurückgewiesen und bleiben an der Spitze der
81
81
  FIFO-Warteschlange. Für diesen normalen Wartestatus erscheint kein
82
82
  `turn.agent_busy`-Fehler.
83
83
 
84
+ Version 9.1.496 stellt Telegram-Nutzernachrichten automatisch einzeln zu, ohne
85
+ die automatische Zustellung an den manuellen Strg+S-/Escape-Zähler zu koppeln.
86
+ Nach Annahme oder Antwort wird nur die exakt verarbeitete Nachrichten-ID aus der
87
+ sichtbaren Queue entfernt; spätere Meldungen desselben Chats bleiben erhalten.
88
+ Vorhandene Todo-Listen bleiben auch ohne aktives Ziel verbindlich und müssen nach
89
+ längerer Arbeit belegbaren Fortschritt zeigen. Erfolgreiche interne Hook-Ausgaben
90
+ bleiben unsichtbar, blockierende Hook-Fehler werden weiterhin angezeigt.
91
+
84
92
  Version 9.1.495 startet jedes neue Ziel mit einer frischen sichtbaren Todo-Liste,
85
93
  erzwingt regelmäßige wahrheitsgemäße Fortschrittsstände und quittiert normale
86
94
  Telegram-Nachrichten erst am Zugende. Dadurch bleiben sie nach einem Neustart
@@ -36,6 +36,16 @@ function createAddressedChannelFocusStore(options = {}) {
36
36
  return true;
37
37
  },
38
38
 
39
+ clearIds(ids) {
40
+ const expected = new Set(Array.from(ids ?? [], (id) => String(id)));
41
+ if (expected.size === 0) return false;
42
+ const next = items.filter((item) => !expected.has(item.id));
43
+ if (next.length === items.length) return false;
44
+ items = next;
45
+ publish();
46
+ return true;
47
+ },
48
+
39
49
  snapshot,
40
50
  };
41
51
  }
package/blun.mjs CHANGED
@@ -423203,13 +423203,15 @@ function validInitialIdeaPlan(value) {
423203
423203
  return active === 1;
423204
423204
  }
423205
423205
  function enforceGoalTodoPolicy(agent, context) {
423206
- if (isIdeaGoal(agent) || agent.goal.getActiveGoal() === null) return;
423206
+ if (isIdeaGoal(agent)) return;
423207
+ const activeGoal = agent.goal.getActiveGoal();
423207
423208
  const todos = ideaTodos(agent);
423208
423209
  const progress = agent.goalTodoPolicyState ??= {
423209
423210
  workCallsSinceRefresh: 0,
423210
423211
  refreshRequired: false
423211
423212
  };
423212
423213
  if (todos.length === 0) {
423214
+ if (activeGoal === null) return;
423213
423215
  const firstCall = context.toolCalls[0];
423214
423216
  if (context.toolCall.id !== firstCall?.id || context.toolCall.name !== "TodoList") return {
423215
423217
  block: true,
@@ -423224,11 +423226,37 @@ function enforceGoalTodoPolicy(agent, context) {
423224
423226
  return;
423225
423227
  }
423226
423228
  if (context.toolCall.name === "TodoList") {
423229
+ const nextTodos = context.args?.todos;
423230
+ if (!Array.isArray(nextTodos)) return;
423231
+ if (nextTodos.length === 0) {
423232
+ if (activeGoal !== null) return {
423233
+ block: true,
423234
+ reason: "An active goal keeps its TodoList until UpdateGoal records a truthful terminal state. Reconcile the list instead of clearing it."
423235
+ };
423236
+ if (todos.some((todo) => !IDEA_TERMINAL_TODO_STATUSES.includes(todo.status))) return {
423237
+ block: true,
423238
+ reason: "Do not clear unfinished TodoList work. Record a truthful terminal status for every item first."
423239
+ };
423240
+ } else {
423241
+ const inProgressCount = nextTodos.filter((todo) => todo?.status === "in_progress").length;
423242
+ const allTerminal = nextTodos.every((todo) => IDEA_TERMINAL_TODO_STATUSES.includes(todo?.status));
423243
+ if (!allTerminal && inProgressCount !== 1) return {
423244
+ block: true,
423245
+ reason: "A maintained TodoList must keep exactly one item in_progress while unfinished work remains."
423246
+ };
423247
+ }
423248
+ if (progress.refreshRequired) {
423249
+ const normalized = (value) => value.map((todo) => ({ title: String(todo?.title ?? "").trim(), status: todo?.status }));
423250
+ if (JSON.stringify(normalized(nextTodos)) === JSON.stringify(normalized(todos))) return {
423251
+ block: true,
423252
+ reason: "TodoList is unchanged after extended work. Mark evidenced progress, advance the active item, or refine its title to the concrete verified substep before continuing."
423253
+ };
423254
+ }
423227
423255
  progress.workCallsSinceRefresh = 0;
423228
423256
  progress.refreshRequired = false;
423229
423257
  return;
423230
423258
  }
423231
- if (context.toolCall.name === "UpdateGoal" && (context.args?.status === "complete" || context.args?.status === "blocked")) {
423259
+ if (activeGoal !== null && context.toolCall.name === "UpdateGoal" && (context.args?.status === "complete" || context.args?.status === "blocked")) {
423232
423260
  if (todos.some((todo) => !IDEA_TERMINAL_TODO_STATUSES.includes(todo.status))) return {
423233
423261
  block: true,
423234
423262
  reason: "The goal cannot finish while a visible TodoList item has no truthful terminal status. Reconcile the list first."
@@ -497076,7 +497104,7 @@ function runPromptTurn(session, prompt, outputFormat, stdout, stderr, model) {
497076
497104
  outputWriter.writeAssistantDelta(event.delta);
497077
497105
  return;
497078
497106
  case "hook.result":
497079
- outputWriter.writeHookResult(event);
497107
+ if (shouldDisplayHookResult(event)) outputWriter.writeHookResult(event);
497080
497108
  return;
497081
497109
  case "thinking.delta":
497082
497110
  outputWriter.writeThinkingDelta(event.delta);
@@ -497140,6 +497168,9 @@ function promptInputText(input) {
497140
497168
  const text = input.filter((part) => part.type === "text").map((part) => part.text).join("\n\n");
497141
497169
  return text.trim().length > 0 ? text : void 0;
497142
497170
  }
497171
+ function shouldDisplayHookResult(event) {
497172
+ return event.blocked === true;
497173
+ }
497143
497174
  var PromptTranscriptWriter = class {
497144
497175
  assistantWriter;
497145
497176
  thinkingWriter;
@@ -503884,6 +503915,7 @@ var BtwPanelController = class {
503884
503915
  this.armInactivityTimer(this.activeForAgent(event.agentId));
503885
503916
  return true;
503886
503917
  case "hook.result":
503918
+ if (!shouldDisplayHookResult(event)) return true;
503887
503919
  this.clearInactivityTimer(this.activeForAgent(event.agentId));
503888
503920
  panel.appendAnswer(formatHookResultPlain(event));
503889
503921
  this.host.state.ui.requestRender();
@@ -507960,7 +507992,7 @@ var SubAgentEventHandler = class {
507960
507992
  const toolCall = this.host.streamingUI.getToolComponent(parentToolCallId);
507961
507993
  if (toolCall === void 0) return true;
507962
507994
  toolCall.setSubagentMeta(childAgentId, info.name);
507963
- if (event.type === "hook.result") toolCall.appendSubagentText(formatHookResultPlain(event), "text");
507995
+ if (event.type === "hook.result" && shouldDisplayHookResult(event)) toolCall.appendSubagentText(formatHookResultPlain(event), "text");
507964
507996
  else if (event.type === "assistant.delta") toolCall.appendSubagentText(event.delta, "text");
507965
507997
  else if (event.type === "thinking.delta") toolCall.appendSubagentText(event.delta, "thinking");
507966
507998
  else if (event.type === "tool.call.started") toolCall.appendSubToolCall({
@@ -508996,6 +509028,7 @@ var SessionEventHandler = class {
508996
509028
  streamingUI.scheduleFlush();
508997
509029
  }
508998
509030
  handleHookResult(event) {
509031
+ if (!shouldDisplayHookResult(event)) return;
508999
509032
  this.host.streamingUI.flushNow();
509000
509033
  if (this.host.streamingUI.hasThinkingDraft()) this.host.streamingUI.flushThinkingToTranscript("idle");
509001
509034
  this.host.streamingUI.finalizeAssistantStream();
@@ -510580,6 +510613,7 @@ var SessionReplayRenderer = class {
510580
510613
  }
510581
510614
  renderHookResult(context, message) {
510582
510615
  if (message.origin?.kind !== "hook_result") return;
510616
+ if (message.origin.blocked !== true) return;
510583
510617
  this.flushAssistant(context);
510584
510618
  this.host.appendTranscriptEntry(replayEntry(context, "assistant", formatHookResultMessageForTranscript(contentPartsToText(message.content), message.origin.event, message.origin.blocked === true), "markdown"));
510585
510619
  }
@@ -517969,7 +518003,7 @@ var BlunTUI = class {
517969
518003
  canDeliverQueuedChannelHead() {
517970
518004
  const item = this.state.queuedMessages[0];
517971
518005
  const hasActiveTurn = this.streamingUI.hasActiveTurn() || (this.state.appState.streamingPhase !== "idle" && this.state.appState.streamingPhase !== "shell");
517972
- if (this.isShuttingDown || this.queueCommandRunning || this.queueSteerInFlight !== void 0 || this.queueFlushBatchRemaining === 0 || this.editorReplacementActive || this.deferUserMessages || this.session === void 0 || this.state.appState.model.trim().length === 0 || !hasActiveTurn || item?.mode !== "channel") return false;
518006
+ if (this.isShuttingDown || this.queueCommandRunning || this.queueSteerInFlight !== void 0 || this.editorReplacementActive || this.deferUserMessages || this.session === void 0 || this.state.appState.model.trim().length === 0 || !hasActiveTurn || item?.mode !== "channel") return false;
517973
518007
  return true;
517974
518008
  }
517975
518009
  async deliverQueuedChannelHead() {
@@ -517998,7 +518032,8 @@ var BlunTUI = class {
517998
518032
  }
517999
518033
  const batchEnd = item.channelAttention === true ? 1 : item.channelUrgent === true ? this.state.queuedMessages.findIndex((queued) => queued.channelUrgent !== true) : item.channelDirect === true ? this.state.queuedMessages.findIndex((queued) => queued.channelDirect !== true) : item.channelAddressed === true ? this.state.queuedMessages.findIndex((queued) => queued.channelAddressed !== true) : this.state.queuedMessages.findIndex((queued) => queued.mode !== "channel" || queued.channelContextOnly === true || queued.channelAttention === true);
518000
518034
  const candidateCount = batchEnd < 0 ? this.state.queuedMessages.length : batchEnd;
518001
- const items = this.state.queuedMessages.slice(0, Math.min(candidateCount, this.queueFlushBatchRemaining));
518035
+ const deliveryLimit = this.queueFlushBatchRemaining > 0 ? this.queueFlushBatchRemaining : 1;
518036
+ const items = this.state.queuedMessages.slice(0, Math.min(candidateCount, deliveryLimit));
518002
518037
  if (items.length === 0) return false;
518003
518038
  this.state.queuedMessages = this.state.queuedMessages.slice(items.length);
518004
518039
  this.updateQueueDisplay();
@@ -518037,6 +518072,7 @@ var BlunTUI = class {
518037
518072
  stepStarted: false,
518038
518073
  turnEnded: false,
518039
518074
  onCommit: () => {
518075
+ this.clearAddressedChannelFocusIds(items.map((queued) => queued.channelFocusId));
518040
518076
  renderTranscripts();
518041
518077
  for (const queued of queuedItems) queued.channelTranscriptRendered = true;
518042
518078
  const acknowledge = () => {
@@ -518112,7 +518148,7 @@ var BlunTUI = class {
518112
518148
  const delivered = this.pendingChannelReplyGuards.filter((guard) => guard.chatId === chatId);
518113
518149
  if (delivered.length === 0) return;
518114
518150
  this.pendingChannelReplyGuards = this.pendingChannelReplyGuards.filter((guard) => guard.chatId !== chatId);
518115
- this.clearAddressedChannelFocus(chatId);
518151
+ this.clearAddressedChannelFocusIds(delivered.map((guard) => guard.channelFocusId));
518116
518152
  for (const guard of delivered) if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(chatId);
518117
518153
  this.updateAddressedChannelFocus();
518118
518154
  }
@@ -518122,6 +518158,9 @@ var BlunTUI = class {
518122
518158
  clearAddressedChannelFocus(chatId) {
518123
518159
  return this.addressedChannelFocusStore.clearChat(chatId);
518124
518160
  }
518161
+ clearAddressedChannelFocusIds(ids) {
518162
+ return this.addressedChannelFocusStore.clearIds(ids.filter((id) => id !== void 0));
518163
+ }
518125
518164
  updateAddressedChannelFocus() {
518126
518165
  this.state.channelFocusContainer.clear();
518127
518166
  const queuedFocusIds = new Set([
@@ -518500,9 +518539,7 @@ var BlunTUI = class {
518500
518539
  else if (directEnvelope !== void 0) enqueueTelegramDirect(this.state.queuedMessages, item);
518501
518540
  else if (addressed) enqueueTelegramAddressed(this.state.queuedMessages, item);
518502
518541
  else this.state.queuedMessages.push(item);
518503
- const activeTurn = this.streamingUI.hasActiveTurn() || this.state.appState.streamingPhase !== "idle" && this.state.appState.streamingPhase !== "shell";
518504
- if (activeTurn) this.releaseNextQueuedMessage();
518505
- if (this.queueFlushBatchRemaining > 0) this.channelQueueDeadline.requestDeliveryNow();
518542
+ this.channelQueueDeadline.requestDeliveryNow();
518506
518543
  this.scheduleQueueDrain();
518507
518544
  this.track("input_queue");
518508
518545
  this.updateQueueDisplay();
@@ -518697,6 +518734,7 @@ var BlunTUI = class {
518697
518734
  }, imagePart] : focusedModelInput;
518698
518735
  session.promptAccepted(promptInput, channelPromptOrigin(channelReportSources)).then((result) => {
518699
518736
  if (result.duplicate === true) {
518737
+ if (channelFocusId !== void 0) this.clearAddressedChannelFocusIds([channelFocusId]);
518700
518738
  finishPersonalMemoryRememberIntentTurn(session.id);
518701
518739
  if (installedGuard !== void 0 && this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
518702
518740
  this.setAppState({ streamingPhase: "idle" });
@@ -518708,6 +518746,7 @@ var BlunTUI = class {
518708
518746
  return;
518709
518747
  }
518710
518748
  if (result.accepted) {
518749
+ if (channelFocusId !== void 0) this.clearAddressedChannelFocusIds([channelFocusId]);
518711
518750
  if (acknowledge !== void 0 && result.turnId !== null && result.turnId !== void 0) {
518712
518751
  this.channelPromptInFlight = {
518713
518752
  turnId: String(result.turnId),
@@ -518841,7 +518880,7 @@ var BlunTUI = class {
518841
518880
  const args = entry.toolCallData?.args;
518842
518881
  return String(args?.["chat_id"] ?? args?.["chatId"] ?? "") === guard.chatId;
518843
518882
  }) || outboxGrewForChat(guard.outboxMarker, guard.chatId)) {
518844
- this.clearAddressedChannelFocus?.(guard.chatId);
518883
+ this.clearAddressedChannelFocusIds?.([guard.channelFocusId]);
518845
518884
  if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
518846
518885
  continue;
518847
518886
  }
@@ -518867,7 +518906,7 @@ var BlunTUI = class {
518867
518906
  if (finalText.length === 0) continue;
518868
518907
  sendReplyFallback(guard.chatId, finalText, guard.contextOnly).then((sent) => {
518869
518908
  if (!sent) return;
518870
- this.clearAddressedChannelFocus?.(guard.chatId);
518909
+ this.clearAddressedChannelFocusIds?.([guard.channelFocusId]);
518871
518910
  if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
518872
518911
  this.showStatus(uiText("blunTui.telegram.fallbackDelivered"));
518873
518912
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.495",
3
+ "version": "9.1.496",
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": {