blun-king-cli 9.1.39 → 9.1.40

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
@@ -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.36
12
+ npm install -g blun-king-cli@9.1.40
13
13
 
14
14
  Start
15
15
  -----
@@ -20,6 +20,17 @@ Start
20
20
  Beide Startbefehle verwenden dieselbe Version, dasselbe Konto, dasselbe Modell
21
21
  und dieselben Befehle.
22
22
 
23
+ Zug-Wächter
24
+ -----------
25
+ Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
26
+ Stand auch im verbundenen Telegram-Kanal. Fünf aufeinanderfolgende identische
27
+ fehlgeschlagene Werkzeugaufrufe beenden den Zug mit einer Fehlermeldung.
28
+
29
+ Die Grenzwerte lassen sich vor dem Start über diese Umgebungsvariablen ändern:
30
+
31
+ BLUN_TURN_WATCHDOG_IDLE_MINUTES
32
+ BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS
33
+
23
34
  Nachweisbare Arbeitsabläufe
24
35
  ---------------------------
25
36
 
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.36
12
+ npm install -g blun-king-cli@9.1.40
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -35,6 +35,16 @@ an. Version, Konto, Modell und Befehle sind ansonsten identisch. Der Unterschied
35
35
  gilt nur für den laufenden Prozess; die gespeicherte
36
36
  Plugin-Konfiguration wird nicht umgeschrieben.
37
37
 
38
+ ## Zug-Wächter
39
+
40
+ Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
41
+ Stand auch im verbundenen Telegram-Kanal. Fünf aufeinanderfolgende identische
42
+ fehlgeschlagene Werkzeugaufrufe beenden den Zug mit einer Fehlermeldung.
43
+
44
+ Die Grenzwerte lassen sich vor dem Start mit
45
+ `BLUN_TURN_WATCHDOG_IDLE_MINUTES` und
46
+ `BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS` ändern.
47
+
38
48
  Beim ersten Start werden das Telegram-Plugin und die mitgelieferten Skills
39
49
  eingerichtet. Die Anmeldung erfolgt anschließend in der
40
50
  Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:100000256c8742e28ac1b44fd85c258bacd68037bbbe180b9d79717e466d1d89
2
+ // BLUN_BUILD_INPUT_SHA256:8bb145d6572cc54a356af3540ba025543ec8c802f66e02a12df23fcd2fcc2bc0
3
3
  import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
4
4
  import { dirname as __cjsShimDirname } from 'node:path';
5
5
  const __filename = __cjsShimFileURLToPath(import.meta.url);
@@ -259098,6 +259098,20 @@ bytesWritten: number$1().int().nonnegative() });
259098
259098
  continuation: void 0
259099
259099
  }, safePath, true);
259100
259100
  if (result.isError === true) return result;
259101
+ let persistedSha256;
259102
+ try {
259103
+ const persistedContent = await this.kaos.readText(safePath);
259104
+ persistedSha256 = createHash("sha256").update(persistedContent).digest("hex");
259105
+ } catch (error) {
259106
+ return {
259107
+ isError: true,
259108
+ output: `Write continuation wrote ${args.path}, but could not verify the persisted bytes: ${error instanceof Error ? error.message : String(error)}. Inspect the target before continuing.`
259109
+ };
259110
+ }
259111
+ if (persistedSha256 !== actualSha256) return {
259112
+ isError: true,
259113
+ output: `Write continuation integrity failure for ${args.path}: staged SHA-256 ${actualSha256}, persisted SHA-256 ${persistedSha256}. The target may be incomplete; inspect it before continuing.`
259114
+ };
259101
259115
  this.pendingContinuations.delete(safePath);
259102
259116
  const writeOutput = typeof result.output === "string" ? result.output : "Write completed.";
259103
259117
  return {
@@ -502382,7 +502396,9 @@ registerUiCatalogFragment({
502382
502396
  "sessionEvent.mcp.needsAuth": "MCP server \"{name}\" needs OAuth — run /mcp-config login {name}",
502383
502397
  "sessionEvent.mcp.disabled": "MCP server \"{name}\" disabled",
502384
502398
  "sessionEvent.mcp.connecting": "MCP server \"{name}\" connecting…",
502385
- "sessionEvent.skill.activated": "Activated skill: {skillName}"
502399
+ "sessionEvent.skill.activated": "Activated skill: {skillName}",
502400
+ "sessionEvent.watchdog.idle": "Turn running for {minutes} min; last tool: {tool}; repeated failures: {repetitions}.",
502401
+ "sessionEvent.watchdog.aborted": "Turn stopped after {repetitions} repeated failed calls to {tool}."
502386
502402
  },
502387
502403
  de: {
502388
502404
  "sessionEvent.error.mcpSync": "Der MCP-Serverstatus konnte nicht synchronisiert werden: {error}",
@@ -502409,7 +502425,9 @@ registerUiCatalogFragment({
502409
502425
  "sessionEvent.mcp.needsAuth": "MCP-Server „{name}“ benötigt OAuth — /mcp-config login {name} ausführen",
502410
502426
  "sessionEvent.mcp.disabled": "MCP-Server „{name}“ deaktiviert",
502411
502427
  "sessionEvent.mcp.connecting": "MCP-Server „{name}“ wird verbunden…",
502412
- "sessionEvent.skill.activated": "Skill aktiviert: {skillName}"
502428
+ "sessionEvent.skill.activated": "Skill aktiviert: {skillName}",
502429
+ "sessionEvent.watchdog.idle": "Zug läuft seit {minutes} Min.; letztes Werkzeug: {tool}; wiederholte Fehler: {repetitions}.",
502430
+ "sessionEvent.watchdog.aborted": "Zug nach {repetitions} wiederholten fehlgeschlagenen Aufrufen von {tool} beendet."
502413
502431
  },
502414
502432
  es: {
502415
502433
  "sessionEvent.error.mcpSync": "No se pudo sincronizar el estado de los servidores MCP: {error}",
@@ -502436,7 +502454,9 @@ registerUiCatalogFragment({
502436
502454
  "sessionEvent.mcp.needsAuth": "El servidor MCP «{name}» requiere OAuth; ejecuta /mcp-config login {name}",
502437
502455
  "sessionEvent.mcp.disabled": "Servidor MCP «{name}» desactivado",
502438
502456
  "sessionEvent.mcp.connecting": "Conectando el servidor MCP «{name}»…",
502439
- "sessionEvent.skill.activated": "Skill activado: {skillName}"
502457
+ "sessionEvent.skill.activated": "Skill activado: {skillName}",
502458
+ "sessionEvent.watchdog.idle": "El turno lleva {minutes} min en curso; última herramienta: {tool}; fallos repetidos: {repetitions}.",
502459
+ "sessionEvent.watchdog.aborted": "Turno detenido tras {repetitions} llamadas fallidas repetidas a {tool}."
502440
502460
  },
502441
502461
  fr: {
502442
502462
  "sessionEvent.error.mcpSync": "Impossible de synchroniser l’état des serveurs MCP : {error}",
@@ -502463,7 +502483,9 @@ registerUiCatalogFragment({
502463
502483
  "sessionEvent.mcp.needsAuth": "Le serveur MCP « {name} » requiert OAuth — exécutez /mcp-config login {name}",
502464
502484
  "sessionEvent.mcp.disabled": "Serveur MCP « {name} » désactivé",
502465
502485
  "sessionEvent.mcp.connecting": "Connexion du serveur MCP « {name} »…",
502466
- "sessionEvent.skill.activated": "Skill activé: {skillName}"
502486
+ "sessionEvent.skill.activated": "Skill activé : {skillName}",
502487
+ "sessionEvent.watchdog.idle": "Tour en cours depuis {minutes} min ; dernier outil : {tool} ; échecs répétés : {repetitions}.",
502488
+ "sessionEvent.watchdog.aborted": "Tour arrêté après {repetitions} appels répétés ayant échoué pour l'outil {tool}."
502467
502489
  },
502468
502490
  sv: {
502469
502491
  "sessionEvent.error.mcpSync": "Det gick inte att synkronisera MCP-serverstatus: {error}",
@@ -502490,7 +502512,9 @@ registerUiCatalogFragment({
502490
502512
  "sessionEvent.mcp.needsAuth": "MCP-servern ”{name}” behöver OAuth – kör /mcp-config login {name}",
502491
502513
  "sessionEvent.mcp.disabled": "MCP-servern ”{name}” är inaktiverad",
502492
502514
  "sessionEvent.mcp.connecting": "Ansluter MCP-servern ”{name}”…",
502493
- "sessionEvent.skill.activated": "Skill aktiverad: {skillName}"
502515
+ "sessionEvent.skill.activated": "Skill aktiverad: {skillName}",
502516
+ "sessionEvent.watchdog.idle": "Körningen har pågått i {minutes} min. Senaste verktyg: {tool}. Upprepade fel: {repetitions}.",
502517
+ "sessionEvent.watchdog.aborted": "Körningen stoppades efter {repetitions} upprepade misslyckade anrop till {tool}."
502494
502518
  },
502495
502519
  cs: {
502496
502520
  "sessionEvent.error.mcpSync": "Selhala synchronizace stavu serveru MCP: {error}",
@@ -502517,7 +502541,9 @@ registerUiCatalogFragment({
502517
502541
  "sessionEvent.mcp.needsAuth": "Server MCP \"{name}\" vyžaduje přihlášení přes OAuth — spusťte /mcp-config login {name}",
502518
502542
  "sessionEvent.mcp.disabled": "Server MCP \"{name}\" zakázán",
502519
502543
  "sessionEvent.mcp.connecting": "Server MCP \"{name}\" se připojuje…",
502520
- "sessionEvent.skill.activated": "Aktivována dovednost: {skillName}"
502544
+ "sessionEvent.skill.activated": "Aktivována dovednost: {skillName}",
502545
+ "sessionEvent.watchdog.idle": "Kolo běží {minutes} min; poslední nástroj: {tool}; opakovaná selhání: {repetitions}.",
502546
+ "sessionEvent.watchdog.aborted": "Kolo bylo zastaveno po {repetitions} opakovaných neúspěšných voláních nástroje {tool}."
502521
502547
  }
502522
502548
  });
502523
502549
  //#endregion
@@ -503659,6 +503685,140 @@ function isUserCancelledSubagentError(error) {
503659
503685
  }
503660
503686
  }
503661
503687
  //#endregion
503688
+ //#region src/tui/controllers/turn-watchdog.ts
503689
+ const DEFAULT_IDLE_MINUTES = 20;
503690
+ const DEFAULT_MAX_FAILED_REPETITIONS = 5;
503691
+ const MIN_IDLE_MINUTES = 1;
503692
+ const MAX_IDLE_MINUTES = 1440;
503693
+ const MIN_FAILED_REPETITIONS = 2;
503694
+ const MAX_FAILED_REPETITIONS = 100;
503695
+ const TURN_WATCHDOG_IDLE_MINUTES_ENV = "BLUN_TURN_WATCHDOG_IDLE_MINUTES";
503696
+ const TURN_WATCHDOG_MAX_FAILED_REPETITIONS_ENV = "BLUN_TURN_WATCHDOG_MAX_FAILED_REPETITIONS";
503697
+ function resolveTurnWatchdogConfig(env = process.env) {
503698
+ const idleMinutes = parseBoundedInteger(env[TURN_WATCHDOG_IDLE_MINUTES_ENV], DEFAULT_IDLE_MINUTES, MIN_IDLE_MINUTES, MAX_IDLE_MINUTES);
503699
+ const maxFailedRepetitions = parseBoundedInteger(env[TURN_WATCHDOG_MAX_FAILED_REPETITIONS_ENV], DEFAULT_MAX_FAILED_REPETITIONS, MIN_FAILED_REPETITIONS, MAX_FAILED_REPETITIONS);
503700
+ return {
503701
+ idleMs: idleMinutes * 6e4,
503702
+ maxFailedRepetitions
503703
+ };
503704
+ }
503705
+ var TurnWatchdogController = class {
503706
+ config;
503707
+ now;
503708
+ setTimer;
503709
+ clearTimer;
503710
+ onIdle;
503711
+ onAbort;
503712
+ toolCalls = /* @__PURE__ */ new Map();
503713
+ active = false;
503714
+ startedAtMs = 0;
503715
+ lastProgressAtMs = 0;
503716
+ lastToolName = "none";
503717
+ lastFailedKey;
503718
+ failedRepetitions = 0;
503719
+ idleReportedForProgressAtMs;
503720
+ timer;
503721
+ constructor(options) {
503722
+ this.config = options.config ?? resolveTurnWatchdogConfig();
503723
+ this.now = options.now ?? Date.now;
503724
+ this.setTimer = options.setTimer ?? setTimeout;
503725
+ this.clearTimer = options.clearTimer ?? clearTimeout;
503726
+ this.onIdle = options.onIdle;
503727
+ this.onAbort = options.onAbort;
503728
+ }
503729
+ start() {
503730
+ this.stop();
503731
+ const now = this.now();
503732
+ this.active = true;
503733
+ this.startedAtMs = now;
503734
+ this.lastProgressAtMs = now;
503735
+ this.lastToolName = "none";
503736
+ this.lastFailedKey = void 0;
503737
+ this.failedRepetitions = 0;
503738
+ this.idleReportedForProgressAtMs = void 0;
503739
+ this.scheduleIdleCheck();
503740
+ }
503741
+ stop() {
503742
+ this.active = false;
503743
+ this.toolCalls.clear();
503744
+ if (this.timer !== void 0) {
503745
+ this.clearTimer(this.timer);
503746
+ this.timer = void 0;
503747
+ }
503748
+ }
503749
+ recordToolCall(toolCallId, name, args) {
503750
+ if (!this.active) return;
503751
+ this.lastToolName = name;
503752
+ this.toolCalls.set(toolCallId, {
503753
+ name,
503754
+ key: `${name}\0${canonicalJson(args)}`
503755
+ });
503756
+ }
503757
+ recordToolResult(toolCallId, isError) {
503758
+ if (!this.active) return;
503759
+ const toolCall = this.toolCalls.get(toolCallId);
503760
+ this.toolCalls.delete(toolCallId);
503761
+ if (toolCall !== void 0) this.lastToolName = toolCall.name;
503762
+ this.lastProgressAtMs = this.now();
503763
+ this.idleReportedForProgressAtMs = void 0;
503764
+ this.scheduleIdleCheck();
503765
+ if (!isError || toolCall === void 0) {
503766
+ this.lastFailedKey = void 0;
503767
+ this.failedRepetitions = 0;
503768
+ return;
503769
+ }
503770
+ if (toolCall.key === this.lastFailedKey) this.failedRepetitions += 1;
503771
+ else {
503772
+ this.lastFailedKey = toolCall.key;
503773
+ this.failedRepetitions = 1;
503774
+ }
503775
+ if (this.failedRepetitions < this.config.maxFailedRepetitions) return;
503776
+ const snapshot = this.snapshot();
503777
+ this.stop();
503778
+ this.onAbort(snapshot);
503779
+ }
503780
+ scheduleIdleCheck() {
503781
+ if (!this.active) return;
503782
+ if (this.timer !== void 0) this.clearTimer(this.timer);
503783
+ const remaining = Math.max(0, this.config.idleMs - (this.now() - this.lastProgressAtMs));
503784
+ this.timer = this.setTimer(() => {
503785
+ this.timer = void 0;
503786
+ this.checkIdle();
503787
+ }, remaining);
503788
+ }
503789
+ checkIdle() {
503790
+ if (!this.active) return;
503791
+ if (this.now() - this.lastProgressAtMs >= this.config.idleMs && this.idleReportedForProgressAtMs !== this.lastProgressAtMs) {
503792
+ this.idleReportedForProgressAtMs = this.lastProgressAtMs;
503793
+ this.onIdle(this.snapshot());
503794
+ return;
503795
+ }
503796
+ this.scheduleIdleCheck();
503797
+ }
503798
+ snapshot() {
503799
+ return {
503800
+ elapsedMinutes: Math.max(1, Math.floor((this.now() - this.startedAtMs) / 6e4)),
503801
+ lastToolName: this.lastToolName,
503802
+ failedRepetitions: this.failedRepetitions
503803
+ };
503804
+ }
503805
+ };
503806
+ function parseBoundedInteger(raw, fallback, minimum, maximum) {
503807
+ if (raw === void 0 || raw.trim() === "") return fallback;
503808
+ const value = Number(raw);
503809
+ return Number.isSafeInteger(value) && value >= minimum && value <= maximum ? value : fallback;
503810
+ }
503811
+ function canonicalJson(value) {
503812
+ const normalized = normalizeJson(value);
503813
+ return JSON.stringify(normalized) ?? "undefined";
503814
+ }
503815
+ function normalizeJson(value) {
503816
+ if (Array.isArray(value)) return value.map(normalizeJson);
503817
+ if (value === null || typeof value !== "object") return value;
503818
+ const record = value;
503819
+ return Object.fromEntries(Object.keys(record).toSorted().map((key) => [key, normalizeJson(record[key])]));
503820
+ }
503821
+ //#endregion
503662
503822
  //#region src/tui/controllers/session-event-handler.ts
503663
503823
  const MANAGED_TELEGRAM_MCP_SERVER = "plugin-telegram:telegram";
503664
503824
  function isManagedTelegramMissingTokenFailure(server) {
@@ -503667,6 +503827,7 @@ function isManagedTelegramMissingTokenFailure(server) {
503667
503827
  var SessionEventHandler = class {
503668
503828
  host;
503669
503829
  subAgentEventHandler;
503830
+ turnWatchdog;
503670
503831
  constructor(host) {
503671
503832
  this.host = host;
503672
503833
  this.subAgentEventHandler = new SubAgentEventHandler(host, {
@@ -503676,6 +503837,10 @@ var SessionEventHandler = class {
503676
503837
  this.syncBackgroundTaskBadge();
503677
503838
  }
503678
503839
  });
503840
+ this.turnWatchdog = new TurnWatchdogController({
503841
+ onIdle: (snapshot) => this.reportWatchdogIdle(snapshot),
503842
+ onAbort: (snapshot) => this.abortWatchdogLoop(snapshot)
503843
+ });
503679
503844
  }
503680
503845
  backgroundTasks = /* @__PURE__ */ new Map();
503681
503846
  backgroundTaskTranscriptedTerminal = /* @__PURE__ */ new Set();
@@ -503695,6 +503860,7 @@ var SessionEventHandler = class {
503695
503860
  queuedGoalPromotionTimer;
503696
503861
  lastCompactionInstruction;
503697
503862
  resetRuntimeState() {
503863
+ this.turnWatchdog.stop();
503698
503864
  this.backgroundTasks.clear();
503699
503865
  this.backgroundTaskTranscriptedTerminal.clear();
503700
503866
  this.subAgentEventHandler.resetRuntimeState();
@@ -503880,6 +504046,7 @@ var SessionEventHandler = class {
503880
504046
  this.mcpServerStatusSpinners.clear();
503881
504047
  }
503882
504048
  handleTurnBegin(event) {
504049
+ this.turnWatchdog.start();
503883
504050
  const sessionId = this.host.session?.id;
503884
504051
  if (sessionId !== void 0) bindPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
503885
504052
  this.currentTurnHasAssistantText = false;
@@ -503918,6 +504085,7 @@ var SessionEventHandler = class {
503918
504085
  });
503919
504086
  }
503920
504087
  handleTurnEnd(event, sendQueued) {
504088
+ this.turnWatchdog.stop();
503921
504089
  this.host.restoreQueuedSteerAtTurnEnd(event.turnId);
503922
504090
  const sessionId = this.host.session?.id;
503923
504091
  if (sessionId !== void 0) finishPersonalMemoryRememberIntentTurn(sessionId, event.turnId);
@@ -504062,6 +504230,7 @@ var SessionEventHandler = class {
504062
504230
  });
504063
504231
  }
504064
504232
  handleToolCall(event) {
504233
+ this.turnWatchdog.recordToolCall(event.toolCallId, event.name, event.args);
504065
504234
  const { streamingUI } = this.host;
504066
504235
  streamingUI.flushNow();
504067
504236
  const { turnId, step } = streamingUI.getTurnContext();
@@ -504111,6 +504280,7 @@ var SessionEventHandler = class {
504111
504280
  if (event.update.kind === "stdout" || event.update.kind === "stderr") tc.appendLiveOutput(text);
504112
504281
  }
504113
504282
  handleToolResult(event) {
504283
+ this.turnWatchdog.recordToolResult(event.toolCallId, event.isError === true);
504114
504284
  const { streamingUI } = this.host;
504115
504285
  streamingUI.flushNow();
504116
504286
  const resultData = {
@@ -504134,6 +504304,26 @@ var SessionEventHandler = class {
504134
504304
  }
504135
504305
  this.host.patchLivePane({ mode: "waiting" });
504136
504306
  }
504307
+ reportWatchdogIdle(snapshot) {
504308
+ const message = uiText("sessionEvent.watchdog.idle", {
504309
+ minutes: snapshot.elapsedMinutes,
504310
+ tool: snapshot.lastToolName,
504311
+ repetitions: snapshot.failedRepetitions
504312
+ });
504313
+ this.host.showStatus(message, "warning");
504314
+ this.host.sendChannelTurnStatus?.(message);
504315
+ }
504316
+ abortWatchdogLoop(snapshot) {
504317
+ const message = uiText("sessionEvent.watchdog.aborted", {
504318
+ tool: snapshot.lastToolName,
504319
+ repetitions: snapshot.failedRepetitions
504320
+ });
504321
+ this.host.showError(message);
504322
+ this.host.sendChannelTurnStatus?.(message);
504323
+ this.host.session?.cancel().catch((error) => {
504324
+ this.host.showError(formatErrorMessage$2(error));
504325
+ });
504326
+ }
504137
504327
  handleStatusUpdate(event) {
504138
504328
  const shouldRenderSwarmEnded = event.swarmMode === false && this.host.state.appState.swarmMode && this.host.state.appState.swarmModeEntry === "task";
504139
504329
  const patch = {};
@@ -512816,6 +513006,13 @@ var BlunTUI = class {
512816
513006
  this.showError(uiText("blunTui.session.sendFailed", { error: "Telegram media" }));
512817
513007
  });
512818
513008
  }
513009
+ sendChannelTurnStatus(message) {
513010
+ const guard = this.pendingChannelReplyGuard;
513011
+ if (guard === void 0) return;
513012
+ sendReplyFallback(guard.chatId, message, guard.contextOnly).then((sent) => {
513013
+ if (sent && this.pendingChannelReplyGuard === guard) guard.outboxMarker = outboxMarker();
513014
+ });
513015
+ }
512819
513016
  runChannelReplyFallback(reason) {
512820
513017
  const guard = this.pendingChannelReplyGuard;
512821
513018
  if (guard === void 0) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.39",
3
+ "version": "9.1.40",
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": {