blun-king-cli 9.1.169 → 9.1.170

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.
@@ -12,6 +12,7 @@ const SYNTHETIC_TOOL_ARGUMENT_MARKER = JSON.stringify({
12
12
  _blun_compacted: '[Old tool call arguments cleared]',
13
13
  });
14
14
  const TELEGRAM_REPLY_TOOL_RE = /^mcp__[^\s]*telegram[^\s]*__reply$/iu;
15
+ const SYNTHETIC_TOOL_ARGUMENT_SUMMARY_MARKER = '[Historical tool arguments unavailable]';
15
16
 
16
17
  function shouldOffloadHistoricalAssistantMessage(options = {}) {
17
18
  const historyIndex = Number(options.historyIndex);
@@ -109,16 +110,37 @@ function compactHistoricalPostTelegramReplyNarration(messages) {
109
110
  function removeSyntheticToolArgumentFailures(messages) {
110
111
  if (!Array.isArray(messages) || messages.length === 0) return messages;
111
112
 
112
- const syntheticCallIds = new Set(messages.flatMap((message) => (
113
+ let summaryChanged = false;
114
+ const scrubbedMessages = messages.map((message) => {
115
+ if (message?.origin?.kind !== 'compaction_summary' || !Array.isArray(message.content)) {
116
+ return message;
117
+ }
118
+ let contentChanged = false;
119
+ const content = message.content.map((part) => {
120
+ if (part?.type !== 'text' || typeof part.text !== 'string' || !part.text.includes('_blun_compacted')) {
121
+ return part;
122
+ }
123
+ const text = part.text.split(/\r?\n/u).map((line) => (
124
+ line.includes('_blun_compacted') ? SYNTHETIC_TOOL_ARGUMENT_SUMMARY_MARKER : line
125
+ )).join('\n');
126
+ contentChanged = true;
127
+ return { ...part, text };
128
+ });
129
+ if (!contentChanged) return message;
130
+ summaryChanged = true;
131
+ return { ...message, content };
132
+ });
133
+
134
+ const syntheticCallIds = new Set(scrubbedMessages.flatMap((message) => (
113
135
  message?.role === 'assistant' && Array.isArray(message.toolCalls)
114
136
  ? message.toolCalls
115
137
  .filter((call) => call?.arguments === SYNTHETIC_TOOL_ARGUMENT_MARKER)
116
138
  .map((call) => call?.id)
117
139
  : []
118
140
  )).filter((id) => typeof id === 'string' && id.length > 0));
119
- if (syntheticCallIds.size === 0) return messages;
141
+ if (syntheticCallIds.size === 0) return summaryChanged ? scrubbedMessages : messages;
120
142
 
121
- return messages.flatMap((message) => {
143
+ return scrubbedMessages.flatMap((message) => {
122
144
  if (message?.role === 'tool' && syntheticCallIds.has(message.toolCallId)) return [];
123
145
  if (message?.role !== 'assistant' || !Array.isArray(message.toolCalls)) return [message];
124
146
 
@@ -29,13 +29,18 @@ const { compareSemver, runExplicitUpdate, runUpdateNotice } = require('./update-
29
29
  const {
30
30
  RUNNING_UPDATE_HANDOFF_EXIT_CODE,
31
31
  RUNNING_UPDATE_HANDOFF_MESSAGE,
32
+ RUNNING_UPDATE_MODE_MESSAGE,
32
33
  RUNNING_UPDATE_PREPARED_MESSAGE,
33
34
  activateRuntime,
34
35
  handoffRuntime,
35
36
  prepareRunningUpdate,
36
37
  readActiveRuntime,
37
- resumeArgsForHandoff,
38
38
  } = require('./running-update.cjs');
39
+ const {
40
+ RUNNING_UPDATE_MODES,
41
+ normalizeRunningUpdateMode,
42
+ readRunningUpdateMode,
43
+ } = require('./running-update-preference.cjs');
39
44
  const {
40
45
  ensurePrivateDirectory,
41
46
  securePrivateFile,
@@ -224,32 +229,70 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
224
229
  const packageRoot = path.resolve(options.packageRoot || PKG);
225
230
  let preparedTarget;
226
231
  let handoffSessionId;
232
+ let handoffMode;
227
233
  let updateStarted = false;
234
+ let runtimeReady = false;
235
+ let runningUpdateMode = readRunningUpdateMode(env.BLUN_HOME);
236
+ const automaticMode = () => runningUpdateMode === RUNNING_UPDATE_MODES.RESUME
237
+ || runningUpdateMode === RUNNING_UPDATE_MODES.NEW;
238
+ const announcePrepared = (child) => {
239
+ if (preparedTarget === undefined || !automaticMode() || child.connected !== true) return;
240
+ child.send({
241
+ type: RUNNING_UPDATE_PREPARED_MESSAGE,
242
+ version: preparedTarget.version,
243
+ mode: runningUpdateMode,
244
+ });
245
+ };
228
246
  const startPreparation = (child) => {
229
- if (updateStarted || options.runningUpdate === false) return;
247
+ if (updateStarted || options.runningUpdate === false || !automaticMode()) return;
230
248
  updateStarted = true;
231
249
  const sharedHome = env.BLUN_SHARED_HOME;
232
- if (typeof sharedHome !== 'string' || sharedHome.length === 0) return;
250
+ if (typeof sharedHome !== 'string' || sharedHome.length === 0) {
251
+ updateStarted = false;
252
+ return;
253
+ }
233
254
  Promise.resolve((options.prepareRunningUpdate || prepareRunningUpdate)({
234
255
  currentVersion: JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version,
235
256
  sharedHome,
236
257
  env,
237
258
  })).then((target) => {
238
- if (target === null || child.connected !== true) return;
259
+ if (target === null) {
260
+ updateStarted = false;
261
+ return;
262
+ }
239
263
  preparedTarget = target;
240
- child.send({ type: RUNNING_UPDATE_PREPARED_MESSAGE, version: target.version });
264
+ announcePrepared(child);
241
265
  }).catch((error) => {
266
+ updateStarted = false;
242
267
  options.onRunningUpdateError?.(error);
243
268
  });
244
269
  };
245
270
  const core = spawnProtectedCore(args, env, cwd, {
246
271
  packageRoot,
247
272
  onMessage(message, child) {
248
- if (message?.type === RUNTIME_READY_MESSAGE) startPreparation(child);
273
+ if (message?.type === RUNTIME_READY_MESSAGE) {
274
+ runtimeReady = true;
275
+ startPreparation(child);
276
+ }
277
+ if (message?.type === RUNNING_UPDATE_MODE_MESSAGE && typeof message.mode === 'string') {
278
+ runningUpdateMode = normalizeRunningUpdateMode(message.mode);
279
+ handoffSessionId = undefined;
280
+ handoffMode = undefined;
281
+ if (automaticMode()) {
282
+ if (preparedTarget !== undefined) announcePrepared(child);
283
+ else if (runtimeReady) {
284
+ updateStarted = false;
285
+ startPreparation(child);
286
+ }
287
+ }
288
+ }
249
289
  if (message?.type === RUNNING_UPDATE_HANDOFF_MESSAGE
250
290
  && typeof message.sessionId === 'string'
251
- && message.sessionId.length > 0) {
291
+ && message.sessionId.length > 0
292
+ && message.mode === runningUpdateMode
293
+ && automaticMode()) {
252
294
  handoffSessionId = message.sessionId;
295
+ handoffMode = runningUpdateMode;
253
296
  }
254
297
  },
255
298
  });
@@ -259,11 +302,13 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
259
302
  if (result.error) throw result.error;
260
303
  if (result.code === RUNNING_UPDATE_HANDOFF_EXIT_CODE
261
304
  && preparedTarget !== undefined
262
- && handoffSessionId !== undefined) {
305
+ && handoffSessionId !== undefined
306
+ && handoffMode !== undefined) {
263
307
  const handoff = await handoffRuntime({
264
308
  target: preparedTarget,
265
309
  previous: { packageRoot, version: readPackageVersion() },
266
310
  sessionId: handoffSessionId,
311
+ mode: handoffMode,
267
312
  args,
268
313
  probeTarget: options.probeRunningUpdate || (() => true),
269
314
  stopOld: async () => {},
@@ -2,6 +2,7 @@
2
2
 
3
3
  const RECURRING_CRON_CONTEXT_MAX_MESSAGES = 24;
4
4
  const RECURRING_CRON_CONTEXT_MAX_CHARS = 48_000;
5
+ const TRUNCATED_CONTEXT_MARKER = '\n[...context truncated...]';
5
6
 
6
7
  function recurringCronJobId(message) {
7
8
  if (message?.role !== 'user') return null;
@@ -43,6 +44,28 @@ function isRecurringCronSegment(segment) {
43
44
  return recurringCronJobId(segment[0]) !== null;
44
45
  }
45
46
 
47
+ function projectSegmentInstruction(segment, maxChars) {
48
+ const message = segment[0];
49
+ if (message?.role !== 'user' || !Array.isArray(message.content) || maxChars <= 0) return null;
50
+ let remaining = maxChars;
51
+ const content = [];
52
+ for (const part of message.content) {
53
+ if (part?.type !== 'text' || typeof part.text !== 'string' || remaining <= 0) continue;
54
+ if (part.text.length <= remaining) {
55
+ content.push(part);
56
+ remaining -= part.text.length;
57
+ continue;
58
+ }
59
+ const marker = remaining > TRUNCATED_CONTEXT_MARKER.length
60
+ ? TRUNCATED_CONTEXT_MARKER
61
+ : '';
62
+ const text = `${part.text.slice(0, remaining - marker.length)}${marker}`;
63
+ if (text.length > 0) content.push({ ...part, text });
64
+ remaining = 0;
65
+ }
66
+ return content.length === 0 ? null : { ...message, content };
67
+ }
68
+
46
69
  function projectRecurringCronHistory(history, jobId) {
47
70
  if (!Array.isArray(history) || history.length === 0) return history;
48
71
  const normalizedJobId = String(jobId || '').trim();
@@ -75,7 +98,16 @@ function projectRecurringCronHistory(history, jobId) {
75
98
  if (
76
99
  nextMessages > RECURRING_CRON_CONTEXT_MAX_MESSAGES
77
100
  || nextChars > RECURRING_CRON_CONTEXT_MAX_CHARS
78
- ) break;
101
+ ) {
102
+ if (selected.length === 0 && selectedMessages < RECURRING_CRON_CONTEXT_MAX_MESSAGES) {
103
+ const instruction = projectSegmentInstruction(
104
+ segment,
105
+ RECURRING_CRON_CONTEXT_MAX_CHARS - selectedChars,
106
+ );
107
+ if (instruction !== null) selected.unshift([instruction]);
108
+ }
109
+ break;
110
+ }
79
111
  selected.unshift(segment);
80
112
  selectedMessages = nextMessages;
81
113
  selectedChars = nextChars;
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { randomUUID } = require('node:crypto');
6
+
7
+ const { ensurePrivateDirectory, securePrivateFile, writePrivateFile } = require('./private-paths');
8
+
9
+ const RUNNING_UPDATE_PREFERENCE_FILE = 'running-update.json';
10
+ const RUNNING_UPDATE_MODES = Object.freeze({
11
+ MANUAL: 'manual',
12
+ RESUME: 'resume',
13
+ NEW: 'new',
14
+ OFF: 'off',
15
+ });
16
+ const DEFAULT_RUNNING_UPDATE_MODE = RUNNING_UPDATE_MODES.RESUME;
17
+ const VALID_MODES = new Set(Object.values(RUNNING_UPDATE_MODES));
18
+
19
+ function preferencePath(profileHome) {
20
+ return path.join(path.resolve(profileHome), RUNNING_UPDATE_PREFERENCE_FILE);
21
+ }
22
+
23
+ function normalizeRunningUpdateMode(value) {
24
+ return typeof value === 'string' && VALID_MODES.has(value)
25
+ ? value
26
+ : DEFAULT_RUNNING_UPDATE_MODE;
27
+ }
28
+
29
+ function readRunningUpdateMode(profileHome) {
30
+ try {
31
+ const filePath = preferencePath(profileHome);
32
+ const stat = fs.lstatSync(filePath);
33
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16 * 1024) {
34
+ return DEFAULT_RUNNING_UPDATE_MODE;
35
+ }
36
+ const record = JSON.parse(fs.readFileSync(filePath, 'utf8'));
37
+ return normalizeRunningUpdateMode(record?.mode);
38
+ } catch {
39
+ return DEFAULT_RUNNING_UPDATE_MODE;
40
+ }
41
+ }
42
+
43
+ function writeRunningUpdateMode(profileHome, mode) {
44
+ if (!VALID_MODES.has(mode)) throw new Error('RUNNING_UPDATE_MODE_INVALID');
45
+ const home = path.resolve(profileHome);
46
+ const filePath = preferencePath(home);
47
+ const temporaryPath = path.join(home, `.${RUNNING_UPDATE_PREFERENCE_FILE}.${process.pid}.${randomUUID()}.tmp`);
48
+ ensurePrivateDirectory(home);
49
+ try {
50
+ writePrivateFile(temporaryPath, `${JSON.stringify({ mode })}\n`);
51
+ fs.renameSync(temporaryPath, filePath);
52
+ securePrivateFile(filePath);
53
+ } catch (error) {
54
+ fs.rmSync(temporaryPath, { force: true });
55
+ throw error;
56
+ }
57
+ return mode;
58
+ }
59
+
60
+ module.exports = {
61
+ DEFAULT_RUNNING_UPDATE_MODE,
62
+ RUNNING_UPDATE_MODES,
63
+ RUNNING_UPDATE_PREFERENCE_FILE,
64
+ normalizeRunningUpdateMode,
65
+ preferencePath,
66
+ readRunningUpdateMode,
67
+ writeRunningUpdateMode,
68
+ };
@@ -16,6 +16,7 @@ const ACTIVE_RUNTIME_FILE = 'active-runtime.json';
16
16
  const RUNNING_UPDATE_HANDOFF_EXIT_CODE = 76;
17
17
  const RUNNING_UPDATE_PREPARED_MESSAGE = 'blun-running-update-prepared';
18
18
  const RUNNING_UPDATE_HANDOFF_MESSAGE = 'blun-running-update-handoff';
19
+ const RUNNING_UPDATE_MODE_MESSAGE = 'blun-running-update-mode';
19
20
  const RUNTIME_READY_MESSAGE = 'blun-runtime-session-ready';
20
21
  const MAX_TARBALL_BYTES = 128 * 1024 * 1024;
21
22
 
@@ -77,8 +78,13 @@ function resumeArgsForHandoff(args, sessionId) {
77
78
  return [...filtered, '--resume', sessionId];
78
79
  }
79
80
 
81
+ function handoffArgsForMode(args, mode, sessionId) {
82
+ const withoutSession = resumeArgsForHandoff(args, sessionId).slice(0, -2);
83
+ return mode === 'new' ? withoutSession : [...withoutSession, '--resume', sessionId];
84
+ }
85
+
80
86
  async function handoffRuntime(options) {
81
- const handoffArgs = resumeArgsForHandoff(options.args, options.sessionId);
87
+ const handoffArgs = handoffArgsForMode(options.args, options.mode || 'resume', options.sessionId);
82
88
  if (!await options.probeTarget(options.target)) {
83
89
  return { kind: 'probe-failed', runtime: options.previous };
84
90
  }
@@ -374,11 +380,13 @@ module.exports = {
374
380
  ACTIVE_RUNTIME_FILE,
375
381
  RUNNING_UPDATE_HANDOFF_EXIT_CODE,
376
382
  RUNNING_UPDATE_HANDOFF_MESSAGE,
383
+ RUNNING_UPDATE_MODE_MESSAGE,
377
384
  RUNNING_UPDATE_PREPARED_MESSAGE,
378
385
  RUNTIME_READY_MESSAGE,
379
386
  activateRuntime,
380
387
  activeRuntimePath,
381
388
  defaultProbeRuntime,
389
+ handoffArgsForMode,
382
390
  handoffRuntime,
383
391
  isSafeRuntimeBoundary,
384
392
  prepareRunningUpdate,
@@ -13,6 +13,14 @@ const TOOL_SEARCH_RELATED_TERM_GROUPS = Object.freeze([
13
13
  'recent', 'update', 'updates',
14
14
  ]),
15
15
  Object.freeze(['history', 'log', 'logs', 'protokoll', 'transcript', 'verlauf']),
16
+ Object.freeze([
17
+ 'fact', 'facts', 'fakt', 'fakten', 'memory', 'memories', 'merk', 'merken', 'note', 'notes',
18
+ 'persist', 'persistent', 'profile', 'remember', 'save', 'speichern', 'store',
19
+ ]),
20
+ ]);
21
+ const TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS = new Set([
22
+ 'fact', 'facts', 'fakt', 'fakten', 'memory', 'memories', 'merk', 'merken', 'note', 'notes',
23
+ 'persist', 'persistent', 'profile', 'remember', 'save', 'speichern', 'store',
16
24
  ]);
17
25
  const CORE_TOOL_NAMES = Object.freeze([
18
26
  'Bash',
@@ -89,8 +97,8 @@ function searchRelatedDeferredTools(tools, query) {
89
97
  const searchTokens = tokens
90
98
  .filter((token) => !token.startsWith('+'))
91
99
  .filter((token) => !TOOL_SEARCH_INTENT_WORDS.has(token));
92
- if (searchTokens.length < 2) return [];
93
- const minimumMatchedTokens = Math.max(2, Math.ceil(searchTokens.length * 0.75));
100
+ if (searchTokens.length < 2 && !TOOL_SEARCH_SINGLE_TERM_RELATED_FALLBACKS.has(searchTokens[0])) return [];
101
+ const minimumMatchedTokens = searchTokens.length === 1 ? 1 : Math.max(2, Math.ceil(searchTokens.length * 0.75));
94
102
 
95
103
  return tools.map((tool) => {
96
104
  const name = String(tool?.name || '').toLowerCase();
package/blun.mjs CHANGED
@@ -340604,6 +340604,110 @@ registerUiCatalogFragment({
340604
340604
  "auth.warning.oauthCleanupFailed": "Klíč API je aktivní, ale předchozí přihlašovací údaje OAuth nebyly zcela odstraněny."
340605
340605
  }
340606
340606
  });
340607
+ registerUiCatalogFragment({
340608
+ en: {
340609
+ "command.update.description": "Configure updates",
340610
+ "command.premortem.description": "Stress-test a plan before execution",
340611
+ "premortem.usage": "Usage: /premortem <plan or decision>",
340612
+ "update.title": "Update behavior",
340613
+ "update.hint": "↑↓ navigate · Enter select · Esc cancel",
340614
+ "update.manual.label": "Manual updates",
340615
+ "update.manual.description": "Only update when you run the update command.",
340616
+ "update.resume.label": "Automatic: resume session",
340617
+ "update.resume.description": "When idle, install the update and resume the current session.",
340618
+ "update.new.label": "Automatic: new session",
340619
+ "update.new.description": "When idle, install the update and start a new session.",
340620
+ "update.off.label": "Automatic updates off",
340621
+ "update.off.description": "Do not check for or install updates automatically.",
340622
+ "update.saved": "Update behavior saved: {mode}",
340623
+ "update.saveFailed": "Could not save update behavior: {error}"
340624
+ },
340625
+ de: {
340626
+ "command.update.description": "Updates konfigurieren",
340627
+ "command.premortem.description": "Einen Plan vor der Umsetzung auf Schwachstellen prüfen",
340628
+ "premortem.usage": "Verwendung: /premortem <Plan oder Entscheidung>",
340629
+ "update.title": "Update-Verhalten",
340630
+ "update.hint": "↑↓ navigieren · Enter auswählen · Esc abbrechen",
340631
+ "update.manual.label": "Updates von Hand",
340632
+ "update.manual.description": "Nur aktualisieren, wenn du den Update-Befehl startest.",
340633
+ "update.resume.label": "Automatisch: Sitzung fortsetzen",
340634
+ "update.resume.description": "Im Leerlauf das Update installieren und die laufende Sitzung fortsetzen.",
340635
+ "update.new.label": "Automatisch: neue Sitzung",
340636
+ "update.new.description": "Im Leerlauf das Update installieren und eine neue Sitzung starten.",
340637
+ "update.off.label": "Automatische Updates aus",
340638
+ "update.off.description": "Nicht automatisch nach Updates suchen oder sie installieren.",
340639
+ "update.saved": "Update-Verhalten gespeichert: {mode}",
340640
+ "update.saveFailed": "Update-Verhalten konnte nicht gespeichert werden: {error}"
340641
+ },
340642
+ es: {
340643
+ "command.update.description": "Configurar actualizaciones",
340644
+ "command.premortem.description": "Someter un plan a una prueba de estrés antes de ejecutarlo",
340645
+ "premortem.usage": "Uso: /premortem <plan o decisión>",
340646
+ "update.title": "Comportamiento de las actualizaciones",
340647
+ "update.hint": "↑↓ navegar · Enter seleccionar · Esc cancelar",
340648
+ "update.manual.label": "Actualizaciones manuales",
340649
+ "update.manual.description": "Actualizar solo cuando ejecutes el comando de actualización.",
340650
+ "update.resume.label": "Automático: reanudar sesión",
340651
+ "update.resume.description": "Cuando esté inactivo, instalar la actualización y reanudar la sesión actual.",
340652
+ "update.new.label": "Automático: nueva sesión",
340653
+ "update.new.description": "Cuando esté inactivo, instalar la actualización e iniciar una sesión nueva.",
340654
+ "update.off.label": "Actualizaciones automáticas desactivadas",
340655
+ "update.off.description": "No buscar ni instalar actualizaciones automáticamente.",
340656
+ "update.saved": "Comportamiento de las actualizaciones guardado: {mode}",
340657
+ "update.saveFailed": "No se pudo guardar el comportamiento de las actualizaciones: {error}"
340658
+ },
340659
+ fr: {
340660
+ "command.update.description": "Configurer les mises à jour",
340661
+ "command.premortem.description": "Mettre un plan à l’épreuve avant son exécution",
340662
+ "premortem.usage": "Utilisation : /premortem <plan ou décision>",
340663
+ "update.title": "Comportement des mises à jour",
340664
+ "update.hint": "↑↓ naviguer · Entrée sélectionner · Échap annuler",
340665
+ "update.manual.label": "Mises à jour manuelles",
340666
+ "update.manual.description": "Mettre à jour uniquement lorsque vous exécutez la commande de mise à jour.",
340667
+ "update.resume.label": "Automatique : reprendre la session",
340668
+ "update.resume.description": "En cas d’inactivité, installer la mise à jour et reprendre la session en cours.",
340669
+ "update.new.label": "Automatique : nouvelle session",
340670
+ "update.new.description": "En cas d’inactivité, installer la mise à jour et démarrer une nouvelle session.",
340671
+ "update.off.label": "Mises à jour automatiques désactivées",
340672
+ "update.off.description": "Ne pas rechercher ni installer automatiquement les mises à jour.",
340673
+ "update.saved": "Comportement des mises à jour enregistré : {mode}",
340674
+ "update.saveFailed": "Impossible d’enregistrer le comportement des mises à jour : {error}"
340675
+ },
340676
+ sv: {
340677
+ "command.update.description": "Konfigurera uppdateringar",
340678
+ "command.premortem.description": "Stresstesta en plan före genomförandet",
340679
+ "premortem.usage": "Användning: /premortem <plan eller beslut>",
340680
+ "update.title": "Uppdateringsbeteende",
340681
+ "update.hint": "↑↓ navigera · Enter välj · Esc avbryt",
340682
+ "update.manual.label": "Manuella uppdateringar",
340683
+ "update.manual.description": "Uppdatera endast när du kör uppdateringskommandot.",
340684
+ "update.resume.label": "Automatiskt: återuppta sessionen",
340685
+ "update.resume.description": "Installera uppdateringen vid inaktivitet och återuppta den aktuella sessionen.",
340686
+ "update.new.label": "Automatiskt: ny session",
340687
+ "update.new.description": "Installera uppdateringen vid inaktivitet och starta en ny session.",
340688
+ "update.off.label": "Automatiska uppdateringar av",
340689
+ "update.off.description": "Sök inte efter och installera inte uppdateringar automatiskt.",
340690
+ "update.saved": "Uppdateringsbeteende sparat: {mode}",
340691
+ "update.saveFailed": "Det gick inte att spara uppdateringsbeteendet: {error}"
340692
+ },
340693
+ cs: {
340694
+ "command.update.description": "Nastavit aktualizace",
340695
+ "command.premortem.description": "Prověřit plán před provedením zátěžovým testem",
340696
+ "premortem.usage": "Použití: /premortem <plán nebo rozhodnutí>",
340697
+ "update.title": "Chování aktualizací",
340698
+ "update.hint": "↑↓ navigace · Enter vybrat · Esc zrušit",
340699
+ "update.manual.label": "Ruční aktualizace",
340700
+ "update.manual.description": "Aktualizovat pouze po spuštění příkazu pro aktualizaci.",
340701
+ "update.resume.label": "Automaticky: pokračovat v relaci",
340702
+ "update.resume.description": "Při nečinnosti nainstalovat aktualizaci a pokračovat v aktuální relaci.",
340703
+ "update.new.label": "Automaticky: nová relace",
340704
+ "update.new.description": "Při nečinnosti nainstalovat aktualizaci a spustit novou relaci.",
340705
+ "update.off.label": "Automatické aktualizace vypnuty",
340706
+ "update.off.description": "Nevyhledávat ani neinstalovat aktualizace automaticky.",
340707
+ "update.saved": "Chování aktualizací uloženo: {mode}",
340708
+ "update.saveFailed": "Chování aktualizací se nepodařilo uložit: {error}"
340709
+ }
340710
+ });
340607
340711
  //#endregion
340608
340712
  //#region src/tui/i18n/catalogs/common.ts
340609
340713
  registerUiCatalogFragment({
@@ -402579,6 +402683,20 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
402579
402683
  priority: 100,
402580
402684
  availability: "always"
402581
402685
  },
402686
+ {
402687
+ name: "update",
402688
+ aliases: [],
402689
+ descriptionKey: "command.update.description",
402690
+ priority: 100,
402691
+ availability: "always"
402692
+ },
402693
+ {
402694
+ name: "premortem",
402695
+ aliases: [],
402696
+ descriptionKey: "command.premortem.description",
402697
+ priority: 95,
402698
+ availability: "idle-only"
402699
+ },
402582
402700
  {
402583
402701
  name: "output-style",
402584
402702
  aliases: ["style"],
@@ -494677,6 +494795,63 @@ async function executeSlashCommand(host, input) {
494677
494795
  return;
494678
494796
  }
494679
494797
  }
494798
+ async function handleUpdateCommand(host) {
494799
+ const profileHome = process.env["BLUN_HOME"];
494800
+ if (typeof profileHome !== "string" || profileHome.length === 0) {
494801
+ host.showError(uiText("update.saveFailed", { error: "BLUN_HOME" }));
494802
+ return;
494803
+ }
494804
+ const currentMode = readRunningUpdateMode(profileHome);
494805
+ const selectedMode = await new Promise((resolve) => {
494806
+ host.mountEditorReplacement(new ChoicePickerComponent({
494807
+ title: uiText("update.title"),
494808
+ hint: uiText("update.hint"),
494809
+ currentValue: currentMode,
494810
+ options: Object.values(RUNNING_UPDATE_MODES).map((mode) => ({
494811
+ value: mode,
494812
+ label: uiText(`update.${mode}.label`),
494813
+ description: uiText(`update.${mode}.description`)
494814
+ })),
494815
+ onSelect: resolve,
494816
+ onCancel: () => resolve(void 0)
494817
+ }));
494818
+ });
494819
+ host.restoreEditor();
494820
+ if (selectedMode === void 0) return;
494821
+ try {
494822
+ writeRunningUpdateMode(profileHome, selectedMode);
494823
+ } catch (error) {
494824
+ host.showError(uiText("update.saveFailed", { error: formatErrorMessage$2(error) }));
494825
+ return;
494826
+ }
494827
+ host.runningUpdatePreparedVersion = void 0;
494828
+ host.runningUpdatePreparedMode = void 0;
494829
+ host.runningUpdateHandoffStarted = false;
494830
+ if (process.connected) try {
494831
+ process.send({ type: RUNNING_UPDATE_MODE_MESSAGE, mode: selectedMode });
494832
+ } catch {}
494833
+ host.showStatus(uiText("update.saved", { mode: uiText(`update.${selectedMode}.label`) }), "success");
494834
+ }
494835
+ function buildPremortemPrompt(plan) {
494836
+ return `<premortem-mode>
494837
+ Assume this plan failed six months from now. Identify the most plausible failure modes, their early warning signs, concrete preventive measures, and explicit stop or decision points. Rank findings by impact and likelihood. Separate evidence from assumptions. End with the three actions that reduce the most risk.
494838
+
494839
+ Plan or decision:
494840
+ ${plan}
494841
+ </premortem-mode>`;
494842
+ }
494843
+ async function handlePremortemCommand(host, args) {
494844
+ const plan = args.trim();
494845
+ if (plan.length === 0) {
494846
+ host.showError(uiText("premortem.usage"));
494847
+ return;
494848
+ }
494849
+ if (host.state.appState.model.trim().length === 0 || host.session === void 0) {
494850
+ host.showError(uiText("goal.error.modelNotSet"));
494851
+ return;
494852
+ }
494853
+ host.sendMessage(host.requireSession(), plan, { parts: buildPremortemPrompt(plan) });
494854
+ }
494680
494855
  async function handleBuiltInSlashCommand(host, name, args) {
494681
494856
  switch (name) {
494682
494857
  case "exit":
@@ -494740,6 +494915,12 @@ async function handleBuiltInSlashCommand(host, name, args) {
494740
494915
  case "settings":
494741
494916
  showSettingsSelector(host);
494742
494917
  return;
494918
+ case "update":
494919
+ await handleUpdateCommand(host);
494920
+ return;
494921
+ case "premortem":
494922
+ await handlePremortemCommand(host, args);
494923
+ return;
494743
494924
  case "usage":
494744
494925
  await showUsage(host);
494745
494926
  return;
@@ -509383,6 +509564,9 @@ var LoopChatIndicatorComponent = class {
509383
509564
  this.state = state;
509384
509565
  this.syncTimer(state.loop);
509385
509566
  }
509567
+ setRefreshHandler(onRefresh) {
509568
+ this.onRefresh = onRefresh;
509569
+ }
509386
509570
  render(width) {
509387
509571
  const indicator = formatLoopChatIndicator(this.state.loop, currentTheme.palette);
509388
509572
  return indicator === null ? [] : [truncateToWidth(indicator, width, "…")];
@@ -514605,10 +514789,16 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
514605
514789
  const {
514606
514790
  RUNNING_UPDATE_HANDOFF_EXIT_CODE,
514607
514791
  RUNNING_UPDATE_HANDOFF_MESSAGE,
514792
+ RUNNING_UPDATE_MODE_MESSAGE,
514608
514793
  RUNNING_UPDATE_PREPARED_MESSAGE,
514609
514794
  RUNTIME_READY_MESSAGE,
514610
514795
  isSafeRuntimeBoundary
514611
514796
  } = __require("./bin/running-update.cjs");
514797
+ const {
514798
+ RUNNING_UPDATE_MODES,
514799
+ readRunningUpdateMode,
514800
+ writeRunningUpdateMode
514801
+ } = __require("./bin/running-update-preference.cjs");
514612
514802
  function notifyRunningRuntimeReady(tui) {
514613
514803
  if (!process.connected) return;
514614
514804
  const sessionId = tui.getCurrentSessionId();
@@ -514621,7 +514811,7 @@ function notifyRunningRuntimeReady(tui) {
514621
514811
  } catch {}
514622
514812
  }
514623
514813
  function requestRunningUpdateAtSafeBoundary(tui) {
514624
- if (tui.runningUpdatePreparedVersion === void 0 || tui.runningUpdateHandoffStarted || !process.connected) return false;
514814
+ if (tui.runningUpdatePreparedVersion === void 0 || tui.runningUpdatePreparedMode === void 0 || tui.runningUpdateHandoffStarted || !process.connected) return false;
514625
514815
  if (!isSafeRuntimeBoundary({
514626
514816
  isShuttingDown: tui.isShuttingDown,
514627
514817
  streamingPhase: tui.state.appState.streamingPhase,
@@ -514638,7 +514828,8 @@ function requestRunningUpdateAtSafeBoundary(tui) {
514638
514828
  process.send({
514639
514829
  type: RUNNING_UPDATE_HANDOFF_MESSAGE,
514640
514830
  sessionId,
514641
- version: tui.runningUpdatePreparedVersion
514831
+ version: tui.runningUpdatePreparedVersion,
514832
+ mode: tui.runningUpdatePreparedMode
514642
514833
  });
514643
514834
  } catch {
514644
514835
  tui.runningUpdateHandoffStarted = false;
@@ -514649,8 +514840,9 @@ function requestRunningUpdateAtSafeBoundary(tui) {
514649
514840
  }
514650
514841
  function installRunningUpdateListener(tui) {
514651
514842
  const handler = (message) => {
514652
- if (message?.type !== RUNNING_UPDATE_PREPARED_MESSAGE || typeof message.version !== "string") return;
514843
+ if (message?.type !== RUNNING_UPDATE_PREPARED_MESSAGE || typeof message.version !== "string" || message.mode !== RUNNING_UPDATE_MODES.RESUME && message.mode !== RUNNING_UPDATE_MODES.NEW) return;
514653
514844
  tui.runningUpdatePreparedVersion = message.version;
514845
+ tui.runningUpdatePreparedMode = message.mode;
514654
514846
  requestRunningUpdateAtSafeBoundary(tui);
514655
514847
  };
514656
514848
  process.on("message", handler);
@@ -514741,6 +514933,7 @@ var BlunTUI = class {
514741
514933
  startupWorkspaceSelectionPending = false;
514742
514934
  startupGoalPromptedSessionId;
514743
514935
  runningUpdatePreparedVersion;
514936
+ runningUpdatePreparedMode;
514744
514937
  runningUpdateHandoffStarted = false;
514745
514938
  runningUpdateMessageDispose;
514746
514939
  startupPhaseMs = {};
@@ -514810,6 +515003,9 @@ var BlunTUI = class {
514810
515003
  this.startupNotice = invalidAppearance ? combineStartupNotice(startupInput.startupNotice, new TuiConfigParseError(DEFAULT_TUI_CONFIG).message) : startupInput.startupNotice;
514811
515004
  this.onStartupNoticeShown = startupInput.onStartupNoticeShown;
514812
515005
  this.state = createTUIState(tuiOptions);
515006
+ this.state.loopIndicator.setRefreshHandler(() => {
515007
+ void this.refreshLoopState();
515008
+ });
514813
515009
  this.appearanceBasePalette = currentTheme.palette;
514814
515010
  this.resolvedAutoTheme = currentTheme.palette === lightColors ? "light" : "dark";
514815
515011
  currentTheme.setPalette(applyAppearanceToPalette(currentTheme.palette, initialAppState.appearance));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.169",
3
+ "version": "9.1.170",
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": {