blun-king-cli 9.1.497 → 9.1.498

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.1.498 - 2026-08-30
4
+
5
+ - Shows prompt-cache hit rate plus cache-read and cache-write tokens per model in `/usage`.
6
+ - Adds `/tokens` as a short alias for the existing detailed context doctor without introducing another model call.
7
+ - Adds `/offload` as an alias for the existing archive-backed context compaction command.
8
+ - Preserves every distinct authorized Telegram bot message instead of dropping same-sender messages that arrive within a fixed 15-second window.
9
+ - Preserves the exact TodoList through full compaction and adds a one-shot recovery reminder to read the archived history path before broad searches when an exact path, command, hash, or requirement is uncertain.
10
+ - Keeps completed TodoList evidence append-only: later uncertainty becomes a separate in-progress step instead of silently reverting or removing completed work.
11
+ - Defers recurring session-loop wakeups while the maintained TodoList still contains active or pending work, coalescing missed intervals until the assignment is genuinely idle without delaying unrelated scheduled reminders.
12
+
3
13
  ## 9.1.497 - 2026-08-30
4
14
 
5
15
  - Prioritizes Telegram bot messages from shared groups behind direct and explicitly addressed traffic but ahead of ordinary queued context, without forcing a reply.
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.497
12
+ npm install -g blun-king-cli@9.1.498
13
13
 
14
14
  Start
15
15
  -----
@@ -65,6 +65,23 @@ 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.498 zeigt in /usage die gemessene Prompt-Cache-Trefferquote sowie
69
+ gelesene und geschriebene Cache-Token je Modell. /tokens ist ein kurzer Alias
70
+ fuer die bereits vorhandene detaillierte Kontextdiagnose; /offload ruft die
71
+ vorhandene archivierende Kontextverdichtung auf. Unterschiedliche autorisierte
72
+ Botnachrichten werden nicht mehr durch ein starres 15-Sekunden-Fenster verworfen.
73
+ Nach einer vollständigen Verdichtung bleibt die exakte Todo-Liste erhalten. Ein
74
+ einmaliger Wiederherstellungshinweis verlangt bei unsicheren Pfaden, Befehlen,
75
+ Hashes oder Anforderungen zuerst das Lesen des archivierten Verlaufs, bevor eine
76
+ breite Dateisuche oder ein Neubeginn zulässig ist.
77
+ Erledigte Todo-Schritte bleiben als Nachweis erhalten. Neue widersprüchliche
78
+ Erkenntnisse werden als eigener laufender Schritt mit Begründung ergänzt, statt
79
+ einen bereits belegten Abschluss still zurückzustufen oder zu entfernen.
80
+ Wiederkehrende Session-Loops warten, solange die gepflegte Todo-Liste noch
81
+ laufende oder ausstehende Arbeit enthält. Verpasste Intervalle werden bis zum
82
+ echten Leerlauf zusammengefasst; andere geplante Erinnerungen bleiben davon
83
+ unberührt.
84
+
68
85
  Version 9.1.497 priorisiert Telegram-Botnachrichten aus gemeinsamen Gruppen
69
86
  hinter privaten und ausdrücklich adressierten Nachrichten, aber vor normalem
70
87
  Kontext. Die Priorisierung erzwingt keine Antwort. Eine angenommene
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.497
12
+ npm install -g blun-king-cli@9.1.498
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -81,6 +81,23 @@ 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.498 zeigt in `/usage` die gemessene Prompt-Cache-Trefferquote sowie
85
+ gelesene und geschriebene Cache-Token je Modell. `/tokens` ist ein kurzer Alias
86
+ fuer die bereits vorhandene detaillierte Kontextdiagnose; `/offload` ruft die
87
+ vorhandene archivierende Kontextverdichtung auf. Unterschiedliche autorisierte
88
+ Botnachrichten werden nicht mehr durch ein starres 15-Sekunden-Fenster verworfen.
89
+ Nach einer vollständigen Verdichtung bleibt die exakte Todo-Liste erhalten. Ein
90
+ einmaliger Wiederherstellungshinweis verlangt bei unsicheren Pfaden, Befehlen,
91
+ Hashes oder Anforderungen zuerst das Lesen des archivierten Verlaufs, bevor eine
92
+ breite Dateisuche oder ein Neubeginn zulässig ist.
93
+ Erledigte Todo-Schritte bleiben als Nachweis erhalten. Neue widersprüchliche
94
+ Erkenntnisse werden als eigener laufender Schritt mit Begründung ergänzt, statt
95
+ einen bereits belegten Abschluss still zurückzustufen oder zu entfernen.
96
+ Wiederkehrende Session-Loops warten außerdem, solange die gepflegte Todo-Liste
97
+ noch laufende oder ausstehende Arbeit enthält. Verpasste Intervalle werden bis
98
+ zum echten Leerlauf zusammengefasst; andere geplante Erinnerungen bleiben davon
99
+ unberührt.
100
+
84
101
  Version 9.1.497 priorisiert Telegram-Botnachrichten aus gemeinsamen Gruppen
85
102
  hinter privaten und ausdrücklich adressierten Nachrichten, aber vor normalem
86
103
  Kontext. Die Priorisierung erzwingt keine Antwort. Eine angenommene
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ function wholeNonNegative(value) {
4
+ if (!Number.isFinite(value) || value <= 0) return 0;
5
+ return Math.floor(value);
6
+ }
7
+
8
+ function buildCacheEfficiency(usage = {}) {
9
+ const uncachedTokens = wholeNonNegative(usage.inputOther);
10
+ const readTokens = wholeNonNegative(usage.inputCacheRead);
11
+ const writeTokens = wholeNonNegative(usage.inputCacheCreation);
12
+ const inputTokens = uncachedTokens + readTokens + writeTokens;
13
+
14
+ return {
15
+ hasActivity: readTokens > 0 || writeTokens > 0,
16
+ hitRate: inputTokens > 0 ? Math.min(1, readTokens / inputTokens) : 0,
17
+ inputTokens,
18
+ readTokens,
19
+ writeTokens,
20
+ };
21
+ }
22
+
23
+ module.exports = {
24
+ buildCacheEfficiency,
25
+ wholeNonNegative,
26
+ };
package/blun.mjs CHANGED
@@ -75375,6 +75375,7 @@ var init_full = __esmMin((() => {
75375
75375
  "permission_mode",
75376
75376
  "plan_mode",
75377
75377
  "plugin_session_start",
75378
+ "compaction_recovery",
75378
75379
  "todo_list_reminder",
75379
75380
  "tool-awareness"
75380
75381
  ]);
@@ -77053,6 +77054,7 @@ function createCronScheduler(opts) {
77053
77054
  const now = clocks.wallNow();
77054
77055
  try {
77055
77056
  for (const task of tasks) try {
77057
+ if (!isIdle(task)) continue;
77056
77058
  if (inFlight.has(task.id)) continue;
77057
77059
  if (task.paused === true) continue;
77058
77060
  const parsed = getParsed(task.cron);
@@ -77180,6 +77182,10 @@ var init_telemetry_events = __esmMin((() => {
77180
77182
  }));
77181
77183
  //#endregion
77182
77184
  //#region ../../packages/agent-core/src/agent/cron/manager.ts
77185
+ function hasUnfinishedTodoWork(agent) {
77186
+ const todos = agent.tools.storeData()["todo"];
77187
+ return Array.isArray(todos) && todos.some((todo) => todo?.status === "in_progress" || todo?.status === "pending");
77188
+ }
77183
77189
  var STALE_THRESHOLD_MS, CronManager;
77184
77190
  var init_manager$3 = __esmMin((() => {
77185
77191
  init_clock();
@@ -77239,7 +77245,7 @@ var init_manager$3 = __esmMin((() => {
77239
77245
  this.scheduler = createCronScheduler({
77240
77246
  clocks: this.clocks,
77241
77247
  source: () => this.store.list(),
77242
- isIdle: () => !agent.turn.hasActiveTurn,
77248
+ isIdle: (task) => !agent.turn.hasActiveTurn && (task?.owner !== "session-loop" || !hasUnfinishedTodoWork(agent)),
77243
77249
  isKilled: () => process.env["BLUN_DISABLE_CRON"] === "1",
77244
77250
  onFire: (task, ctx) => {
77245
77251
  this.handleFire(task, ctx);
@@ -233121,7 +233127,7 @@ var init_mission_contract_bridge = __esmMin((() => {
233121
233127
  }));
233122
233128
  //#endregion
233123
233129
  //#region ../../packages/agent-core/src/agent/injection/manager.ts
233124
- var ACTIVE_BACKGROUND_TASK_GUIDANCE, InjectionManager;
233130
+ var ACTIVE_BACKGROUND_TASK_GUIDANCE, POST_COMPACTION_RECOVERY_GUIDANCE, InjectionManager;
233125
233131
  var init_manager$2 = __esmMin((() => {
233126
233132
  init_task_list();
233127
233133
  init_action_style();
@@ -233136,6 +233142,7 @@ var init_manager$2 = __esmMin((() => {
233136
233142
  init_validated_learning_signal();
233137
233143
  init_tool_awareness();
233138
233144
  ACTIVE_BACKGROUND_TASK_GUIDANCE = "The conversation was compacted, so the earlier messages that started these background tasks are gone — but the tasks are still running from before. Do not start duplicates. Use TaskOutput to fetch a task’s result, TaskList to list them, TaskUpdate to send additional instructions to a running agent task, and TaskStop to cancel one.";
233145
+ POST_COMPACTION_RECOVERY_GUIDANCE = "The conversation was compacted. Continue from the persisted TodoList and summary. If an exact file path, command, hash, requirement, or other working detail is missing or uncertain, use Read on the summary's history_path before broad filesystem searches, guessing, or starting work over.";
233139
233146
  InjectionManager = class {
233140
233147
  agent;
233141
233148
  injectors;
@@ -233193,9 +233200,16 @@ var init_manager$2 = __esmMin((() => {
233193
233200
  }
233194
233201
  async injectAfterCompaction() {
233195
233202
  await this.injectGoal();
233203
+ this.injectCompactionRecoveryGuidance();
233196
233204
  this.injectActiveBackgroundTasks();
233197
233205
  await this.inject();
233198
233206
  }
233207
+ injectCompactionRecoveryGuidance() {
233208
+ this.agent.context.appendSystemReminder(POST_COMPACTION_RECOVERY_GUIDANCE, {
233209
+ kind: "injection",
233210
+ variant: "compaction_recovery"
233211
+ });
233212
+ }
233199
233213
  /**
233200
233214
  * Post-compaction only: re-surface still-running background tasks. Folding the
233201
233215
  * live context to [recent user prompts, summary] drops the messages that
@@ -403833,7 +403847,7 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
403833
403847
  },
403834
403848
  {
403835
403849
  name: "compact",
403836
- aliases: [],
403850
+ aliases: ["offload"],
403837
403851
  descriptionKey: "command.compact.description",
403838
403852
  priority: 80,
403839
403853
  argumentHint: "<instruction>"
@@ -403976,7 +403990,7 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
403976
403990
  },
403977
403991
  {
403978
403992
  name: "context-doctor",
403979
- aliases: [],
403993
+ aliases: ["tokens"],
403980
403994
  descriptionKey: "contextDoctor.description",
403981
403995
  priority: 66,
403982
403996
  availability: "always"
@@ -417895,9 +417909,11 @@ function buildSessionUsageSection(usage, error, value, muted, errorStyle) {
417895
417909
  for (const [model, row] of entries) {
417896
417910
  const input = usageInputTotal$2(row);
417897
417911
  const output = usageNumber(row.output);
417912
+ const cache = buildCacheEfficiency(row);
417898
417913
  totalInput += input;
417899
417914
  totalOutput += output;
417900
417915
  lines.push(` ${muted(sanitizeTerminalText(model))} ${uiText("usage.input")} ${value(formatExactTokenCount(input, locale))} ${uiText("usage.output")} ${value(formatExactTokenCount(output, locale))} ${uiText("usage.total")} ${value(formatExactTokenCount(input + output, locale))}`);
417916
+ if (cache.hasActivity) lines.push(` ${muted("CACHE")} ${value(`${Math.round(cache.hitRate * 100)}%`)} | R ${value(formatExactTokenCount(cache.readTokens, locale))} | W ${value(formatExactTokenCount(cache.writeTokens, locale))}`);
417901
417917
  }
417902
417918
  if (entries.length > 1) lines.push(` ${muted(uiText("usage.total"))} ${uiText("usage.input")} ${value(formatExactTokenCount(totalInput, locale))} ${uiText("usage.output")} ${value(formatExactTokenCount(totalOutput, locale))} ${uiText("usage.total")} ${value(formatExactTokenCount(totalInput + totalOutput, locale))}`);
417903
417919
  return lines;
@@ -417964,6 +417980,7 @@ var { buildApprovalRejectionStop } = createRequire(import.meta.url)("./bin/appro
417964
417980
  var { reconcileContextBudget } = createRequire(import.meta.url)("./bin/context-budget-ledger.cjs");
417965
417981
  var { buildContextInsight } = createRequire(import.meta.url)("./bin/context-insight-policy.cjs");
417966
417982
  var { buildContextDoctor } = createRequire(import.meta.url)("./bin/context-doctor-policy.cjs");
417983
+ var { buildCacheEfficiency } = createRequire(import.meta.url)("./bin/usage-cache-efficiency-policy.cjs");
417967
417984
  var { buildValidatedLearningInsight } = createRequire(import.meta.url)("./bin/validated-learning-insight-policy.cjs");
417968
417985
  var { readValidatedLearningCandidates } = createRequire(import.meta.url)("./bin/validated-learning-signal.cjs");
417969
417986
  function buildUsageReportLines(options) {
@@ -423231,6 +423248,12 @@ function enforceGoalTodoPolicy(agent, context) {
423231
423248
  if (context.toolCall.name === "TodoList") {
423232
423249
  const nextTodos = context.args?.todos;
423233
423250
  if (!Array.isArray(nextTodos)) return;
423251
+ const normalizeTitle = (value) => String(value ?? "").trim().toLowerCase().replace(/\s+/gu, " ");
423252
+ const missingCompleted = todos.filter((todo, index) => todo?.status === "done" && !nextTodos.some((nextTodo) => nextTodo?.status === "done" && normalizeTitle(nextTodo.title) === normalizeTitle(todo.title)) && nextTodos[index]?.status !== "done");
423253
+ if (missingCompleted.length > 0) return {
423254
+ block: true,
423255
+ reason: `Completed TodoList evidence is append-only. Keep each completed item present and done. If later evidence reopens work, retain the completed item and add a separate in_progress item with the reason. Missing completed item: ${String(missingCompleted[0]?.title ?? "").trim()}`
423256
+ };
423234
423257
  if (nextTodos.length === 0) {
423235
423258
  if (activeGoal !== null) return {
423236
423259
  block: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.497",
3
+ "version": "9.1.498",
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": {
@@ -130,25 +130,6 @@ function matchesAny(text, patterns) {
130
130
  } catch {}
131
131
  return false;
132
132
  }
133
- /**
134
- * Bot-to-bot loop guard (Telegram's own safety requirement for Bot API 10.0
135
- * bot-to-bot mode): once King can receive other bots' messages, an unthrottled
136
- * exchange could ping-pong forever. We throttle deliveries FROM a given bot in
137
- * a given group to at most one per BOT_COOLDOWN_MS, so two bots can never fast-
138
- * loop. In-memory per bridge process — resets on restart, which is fine.
139
- */
140
- const BOT_COOLDOWN_MS = 15e3;
141
- const lastBotDelivery = /* @__PURE__ */ new Map();
142
- /** Exposed for tests; drops stale entries so the map can't grow unbounded. */
143
- function botLoopGuardAllows(senderId, now = Date.now(), cooldownMs = BOT_COOLDOWN_MS) {
144
- const last = lastBotDelivery.get(senderId);
145
- if (last !== void 0 && now - last < cooldownMs) return false;
146
- lastBotDelivery.set(senderId, now);
147
- if (lastBotDelivery.size > 200) {
148
- for (const [key, ts] of lastBotDelivery) if (now - ts > cooldownMs) lastBotDelivery.delete(key);
149
- }
150
- return true;
151
- }
152
133
  function gate(ctx, botUsername) {
153
134
  const access = readAccess();
154
135
  if (pruneExpired(access)) saveAccess(access);
@@ -227,7 +208,6 @@ function gate(ctx, botUsername) {
227
208
  if (groupAllowFrom.length > 0 && !groupAllowFrom.includes(senderId)) return { action: "drop" };
228
209
  const mentionBypass = from.isBot !== true && (policy.alwaysAllowFrom ?? []).includes(senderId);
229
210
  const addressed = !matchesAny(ctx.text, access.ignorePatterns) && (mentionBypass || isMentioned(ctx, botUsername, access.mentionPatterns));
230
- if (!addressed && from.isBot === true && !botLoopGuardAllows(senderId)) return { action: "drop" };
231
211
  return {
232
212
  action: "deliver",
233
213
  access,