blun-king-cli 9.1.17 → 9.1.19

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/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.1
12
+ npm install -g blun-king-cli@9.1.18
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
package/bin/blun.js CHANGED
File without changes
File without changes
package/bin/king.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:39e4b4359446de9a47446e631b853adaafc78c8097e76296c6826ec91ee9902d
2
+ // BLUN_BUILD_INPUT_SHA256:b991ad9b5744a4c7edb806fc71b837f5d58205791bc5979c4e05b6b5f3564456
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);
@@ -74394,8 +74394,8 @@ var init_kosong_llm = __esmMin((() => {
74394
74394
  //#endregion
74395
74395
  //#region ../../packages/agent-core/src/agent/compaction/typical-duration.ts
74396
74396
  /**
74397
- * Experience-based expectation for compaction duration (Mayk/Dieter/Angel
74398
- * 03.08.2026): instead of a fabricated percentage, the compaction indicator
74397
+ * Experience-based expectation for compaction duration: instead of a
74398
+ * fabricated percentage, the compaction indicator
74399
74399
  * may show the median duration of past completed runs for the same
74400
74400
  * provider+model — but only when the data actually supports it.
74401
74401
  *
@@ -232242,9 +232242,14 @@ var WINDOWS_MCP_TOOL_PREFIX, DesktopControlAlwaysAskPermissionPolicy;
232242
232242
  var init_desktop_control_always_ask = __esmMin((() => {
232243
232243
  WINDOWS_MCP_TOOL_PREFIX = "mcp__windows-mcp__";
232244
232244
  DesktopControlAlwaysAskPermissionPolicy = class {
232245
+ agent;
232245
232246
  name = "desktop-control-always-ask";
232247
+ constructor(agent) {
232248
+ this.agent = agent;
232249
+ }
232246
232250
  evaluate(context) {
232247
232251
  if (!context.toolCall.name.toLowerCase().startsWith(WINDOWS_MCP_TOOL_PREFIX)) return;
232252
+ if (this.agent.permission.mode === "yolo") return;
232248
232253
  return { kind: "ask" };
232249
232254
  }
232250
232255
  };
@@ -232873,7 +232878,7 @@ function createPermissionDecisionPolicies(agent) {
232873
232878
  new PlanModeGuardDenyPermissionPolicy(agent),
232874
232879
  new UserConfiguredDenyPermissionPolicy(agent),
232875
232880
  new PersonalMemoryMutationAlwaysAskPermissionPolicy(),
232876
- new DesktopControlAlwaysAskPermissionPolicy(),
232881
+ new DesktopControlAlwaysAskPermissionPolicy(agent),
232877
232882
  new AutoModeApprovePermissionPolicy(agent),
232878
232883
  new SessionApprovalHistoryPermissionPolicy(agent),
232879
232884
  new UserConfiguredAskPermissionPolicy(agent),
@@ -336447,6 +336452,8 @@ function buildSkillSlashCommands(skills) {
336447
336452
  //#region src/constant/account-memory.ts
336448
336453
  const ACCOUNT_MEMORY_READ_URL = "https://chat.blun.ai/api/blun-memory?persona_id=king&surface=global";
336449
336454
  const ACCOUNT_MEMORY_EXTRACT_URL = "https://chat.blun.ai/api/blun-memory/extract?persona_id=king&surface=global";
336455
+ const ACCOUNT_MEMORY_PREFERENCES_URL = "https://account.blun.ai/api/me/preferences";
336456
+ const ACCOUNT_MEMORY_SETTINGS_URL = "https://account.blun.ai/api/account/settings";
336450
336457
  const ACCOUNT_MEMORY_MAX_FACT_CHARS = 1e3;
336451
336458
  //#endregion
336452
336459
  //#region src/utils/account-memory.ts
@@ -336463,11 +336470,34 @@ function describeAccountMemoryError(error) {
336463
336470
  function createAccountMemoryClient(auth, options = {}) {
336464
336471
  const fetchImpl = options.fetch ?? globalThis.fetch;
336465
336472
  const timeoutMs = options.timeoutMs ?? 4e3;
336473
+ let consentStatus;
336474
+ const getConsentStatus = async () => {
336475
+ consentStatus = readAccountMemoryConsentStatus(await requestWithOAuth(auth, fetchImpl, ACCOUNT_MEMORY_PREFERENCES_URL, { method: "GET" }, timeoutMs, readBoundedJson));
336476
+ return consentStatus;
336477
+ };
336478
+ const hasExplicitConsent = async () => {
336479
+ return (consentStatus ?? await getConsentStatus()) === "enabled";
336480
+ };
336466
336481
  return {
336482
+ getConsentStatus,
336483
+ async setConsent(enabled) {
336484
+ const stored = readAccountMemoryConsentStatus(await requestWithOAuth(auth, fetchImpl, ACCOUNT_MEMORY_SETTINGS_URL, {
336485
+ method: "PATCH",
336486
+ headers: { "Content-Type": "application/json" },
336487
+ body: JSON.stringify({ data_controls: {
336488
+ allow_memory: enabled,
336489
+ memory_consent_asked: true
336490
+ } })
336491
+ }, timeoutMs, readBoundedJson));
336492
+ if (stored !== (enabled ? "enabled" : "disabled")) throw new Error("Account memory returned an invalid response.");
336493
+ consentStatus = stored;
336494
+ },
336467
336495
  async loadSystemContext() {
336496
+ if (!await hasExplicitConsent()) return void 0;
336468
336497
  return renderAccountMemoryContext(await requestWithOAuth(auth, fetchImpl, ACCOUNT_MEMORY_READ_URL, { method: "GET" }, timeoutMs, readBoundedJson));
336469
336498
  },
336470
336499
  async extract(text) {
336500
+ if (!await hasExplicitConsent()) return;
336471
336501
  await requestWithOAuth(auth, fetchImpl, ACCOUNT_MEMORY_EXTRACT_URL, {
336472
336502
  method: "POST",
336473
336503
  headers: { "Content-Type": "application/json" },
@@ -336476,6 +336506,15 @@ function createAccountMemoryClient(auth, options = {}) {
336476
336506
  }
336477
336507
  };
336478
336508
  }
336509
+ function readAccountMemoryConsentStatus(payload) {
336510
+ const settings = unwrapPayload(payload)?.["settings"];
336511
+ const dataControls = isRecord$13(settings) ? settings["data_controls"] : void 0;
336512
+ if (!isRecord$13(dataControls)) throw new Error("Account memory returned an invalid response.");
336513
+ if (dataControls["memory_consent_asked"] !== true) return "never_asked";
336514
+ if (dataControls["allow_memory"] === true) return "enabled";
336515
+ if (dataControls["allow_memory"] === false) return "disabled";
336516
+ throw new Error("Account memory returned an invalid response.");
336517
+ }
336479
336518
  async function requestWithOAuth(auth, fetchImpl, url, init, timeoutMs, consume) {
336480
336519
  const controller = new AbortController();
336481
336520
  let timeout;
@@ -397147,42 +397186,42 @@ registerUiCatalogFragment({
397147
397186
  registerUiCatalogFragment({
397148
397187
  en: {
397149
397188
  "startupPersonalMemory.title": "Personal memory",
397150
- "startupPersonalMemory.details": "When you ask BLUN to remember information from a chat, BLUN can use it in future conversations. This is off until you enable it.",
397189
+ "startupPersonalMemory.details": "When personal memory is enabled, BLUN automatically extracts useful information from your chats and can use it in future conversations. This stays off until you enable it.",
397151
397190
  "startupPersonalMemory.enable": "Enable memory",
397152
397191
  "startupPersonalMemory.decline": "Do not enable",
397153
397192
  "startupPersonalMemory.persistenceFailed": "Memory settings could not be saved. Personal memory remains off."
397154
397193
  },
397155
397194
  de: {
397156
397195
  "startupPersonalMemory.title": "Persönliches Gedächtnis",
397157
- "startupPersonalMemory.details": "Wenn du BLUN bittest, sich Informationen aus einem Chat zu merken, kann BLUN sie in späteren Gesprächen verwenden. Diese Funktion bleibt deaktiviert, bis du sie aktivierst.",
397196
+ "startupPersonalMemory.details": "Wenn das persönliche Gedächtnis aktiviert ist, übernimmt BLUN automatisch nützliche Informationen aus deinen Chats und kann sie in späteren Gesprächen verwenden. Die Funktion bleibt deaktiviert, bis du sie einschaltest.",
397158
397197
  "startupPersonalMemory.enable": "Gedächtnis aktivieren",
397159
397198
  "startupPersonalMemory.decline": "Nicht aktivieren",
397160
397199
  "startupPersonalMemory.persistenceFailed": "Die Gedächtniseinstellungen konnten nicht gespeichert werden. Das persönliche Gedächtnis bleibt deaktiviert."
397161
397200
  },
397162
397201
  es: {
397163
397202
  "startupPersonalMemory.title": "Memoria personal",
397164
- "startupPersonalMemory.details": "Cuando le pides a BLUN que recuerde información de un chat, BLUN puede usarla en conversaciones futuras. Esta función está desactivada hasta que la actives.",
397203
+ "startupPersonalMemory.details": "Cuando activas la memoria personal, BLUN extrae automáticamente información útil de tus chats y puede utilizarla en conversaciones futuras. La función permanece desactivada hasta que la actives.",
397165
397204
  "startupPersonalMemory.enable": "Activar la memoria",
397166
397205
  "startupPersonalMemory.decline": "No activar",
397167
397206
  "startupPersonalMemory.persistenceFailed": "No se pudo guardar la configuración de la memoria. La memoria personal permanece desactivada."
397168
397207
  },
397169
397208
  fr: {
397170
397209
  "startupPersonalMemory.title": "Mémoire personnelle",
397171
- "startupPersonalMemory.details": "Lorsque vous demandez à BLUN de mémoriser des informations provenant d’une conversation, BLUN peut les utiliser lors de conversations ultérieures. Cette fonction reste désactivée tant que vous ne l’activez pas.",
397210
+ "startupPersonalMemory.details": "Lorsque vous activez la mémoire personnelle, BLUN extrait automatiquement les informations utiles de vos conversations et peut les réutiliser ultérieurement. La fonction reste désactivée tant que vous ne l’activez pas.",
397172
397211
  "startupPersonalMemory.enable": "Activer la mémoire",
397173
397212
  "startupPersonalMemory.decline": "Ne pas activer",
397174
397213
  "startupPersonalMemory.persistenceFailed": "Les réglages de la mémoire n’ont pas pu être enregistrés. La mémoire personnelle reste désactivée."
397175
397214
  },
397176
397215
  sv: {
397177
397216
  "startupPersonalMemory.title": "Personligt minne",
397178
- "startupPersonalMemory.details": "När du ber BLUN att komma ihåg information från en chatt kan BLUN använda den i framtida samtal. Funktionen är avstängd tills du aktiverar den.",
397217
+ "startupPersonalMemory.details": "När du aktiverar det personliga minnet hämtar BLUN automatiskt användbar information från dina chattar och kan använda den i framtida samtal. Funktionen är avstängd tills du aktiverar den.",
397179
397218
  "startupPersonalMemory.enable": "Aktivera minnet",
397180
397219
  "startupPersonalMemory.decline": "Aktivera inte",
397181
397220
  "startupPersonalMemory.persistenceFailed": "Minnesinställningarna kunde inte sparas. Det personliga minnet förblir avstängt."
397182
397221
  },
397183
397222
  cs: {
397184
397223
  "startupPersonalMemory.title": "Osobní paměť",
397185
- "startupPersonalMemory.details": "Když požádáte BLUN, aby si zapamatoval informace z chatu, BLUN je může použít v budoucích konverzacích. Tato funkce zůstane vypnutá, dokud ji nezapnete.",
397224
+ "startupPersonalMemory.details": "Když zapnete osobní paměť, BLUN automaticky získává užitečné informace z vašich chatů a může je použít v budoucích konverzacích. Funkce zůstává vypnutá, dokud ji nezapnete.",
397186
397225
  "startupPersonalMemory.enable": "Zapnout paměť",
397187
397226
  "startupPersonalMemory.decline": "Nezapínat",
397188
397227
  "startupPersonalMemory.persistenceFailed": "Nastavení paměti se nepodařilo uložit. Osobní paměť zůstává vypnutá."
@@ -397441,8 +397480,8 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
397441
397480
  aliases: [],
397442
397481
  descriptionKey: "startupPersonalMemory.title",
397443
397482
  priority: 60,
397444
- argumentHint: "remember <text>",
397445
- availability: "idle-only"
397483
+ argumentHint: "status|on|off",
397484
+ availability: "always"
397446
397485
  },
397447
397486
  {
397448
397487
  name: "plugins",
@@ -407807,12 +407846,20 @@ function installRainbowDance(requestRender) {
407807
407846
  if (currentDanceController === dance) setRainbowDance(void 0);
407808
407847
  };
407809
407848
  }
407849
+ function getRainbowDanceView() {
407850
+ return currentDanceView;
407851
+ }
407810
407852
  function isRainbowDancing() {
407811
407853
  return currentDanceView?.colored === true;
407812
407854
  }
407813
407855
  function renderDanceFooterModel(modelLabel) {
407814
407856
  return rainbowText(modelLabel, getDanceRainbowPalette(), currentDanceView?.phase ?? 0);
407815
407857
  }
407858
+ function renderDanceWelcomeWordmark(rows) {
407859
+ const phase = currentDanceView?.phase ?? 0;
407860
+ const palette = getDanceRainbowPalette();
407861
+ return rows.map((row, index) => rainbowText(row, palette, phase + index * 3, true));
407862
+ }
407816
407863
  /**
407817
407864
  * Drives the rainbow: a single timer advances a shared `phase` and asks the UI
407818
407865
  * to repaint. Lives independently of any component, so the welcome banner
@@ -412627,12 +412674,12 @@ registerUiCatalogFragment({
412627
412674
  //#region src/tui/utils/channel-injection.ts
412628
412675
  /**
412629
412676
  * Channel inbound injection into the VISIBLE interactive TUI session
412630
- * (Otto gate 49 / Angel spec 51).
412677
+ * for channel delivery.
412631
412678
  *
412632
412679
  * A remote message (Telegram) must NEVER travel through handleUserInput():
412633
412680
  * no slash-command dispatch (a Telegram "/new" must not open a session), no
412634
412681
  * bash mode, no local editor history — and the transcript shows a clean user
412635
- * line ("Telegram · Mayk <text>") while the MODEL receives the full
412682
+ * line ("Telegram · User <text>") while the MODEL receives the full
412636
412683
  * <channel …> payload (plus the channel preamble once per TUI process).
412637
412684
  *
412638
412685
  * This module is dependency-free on purpose: verify-telegram.ts drives the
@@ -412669,7 +412716,7 @@ function bufferContext(state, chatId, tag) {
412669
412716
  while (messages.length > MAX_BUFFERED_CONTEXT_MESSAGES || chars > MAX_BUFFERED_CONTEXT_CHARS) chars -= messages.shift()?.length ?? 0;
412670
412717
  buffers.set(chatId, messages);
412671
412718
  }
412672
- /** Muted origin prefix for the transcript line, e.g. "Telegram · Mayk". */
412719
+ /** Muted origin prefix for the transcript line, e.g. "Telegram · User". */
412673
412720
  function channelOrigin(envelope) {
412674
412721
  return `Telegram · ${envelope.meta.user ?? envelope.meta.chat_id}`;
412675
412722
  }
@@ -412768,7 +412815,7 @@ registerUiCatalogFragment({
412768
412815
  /**
412769
412816
  * Telegram channel attach mode — the visible TUI session takes over delivery.
412770
412817
  *
412771
- * Design (Otto gate 49, "ein Poller, Lease-Routing"):
412818
+ * Design: one poller with lease routing.
412772
412819
  * - The bridge (plugins/telegram dist/bridge.mjs) stays the ONLY getUpdates
412773
412820
  * poller at all times. The TUI never talks to Telegram itself.
412774
412821
  * - While this controller runs, it holds a lease: tui.pid in the channel
@@ -413228,7 +413275,7 @@ var TelegramChannelController = class {
413228
413275
  for (const candidate of candidates) if (candidate !== void 0 && existsSync(candidate)) return candidate;
413229
413276
  }
413230
413277
  /**
413231
- * The reply path must live in THIS session (Otto gate 49): without the
413278
+ * The reply path must live in THIS session: without the
413232
413279
  * telegram MCP the model can read channel messages but its answers never
413233
413280
  * reach the chat — warn loudly instead of failing silently.
413234
413281
  */
@@ -416497,37 +416544,26 @@ registerUiCatalogFragment({
416497
416544
  //#region src/tui/commands/memory.ts
416498
416545
  async function handleMemoryCommand(host, args, dependencies) {
416499
416546
  const parsed = parseMemoryCommand(args);
416500
- if (parsed === void 0) throw new Error("PERSONAL_MEMORY_INVALID_COMMAND");
416501
- const deps = dependencies ?? defaultDependencies(host);
416502
- const client = deps.createClient(host);
416503
- await deps.remember({
416504
- content: parsed.content,
416505
- getManagedQuota: deps.getManagedQuota,
416506
- getSettings: client.getSettings,
416507
- request: client.request
416508
- });
416509
- host.showNotice(uiText("startupPersonalMemory.title"), uiText("startupPersonalMemory.details"));
416547
+ if (parsed === void 0) {
416548
+ host.showStatus("/memory status|on|off");
416549
+ return;
416550
+ }
416551
+ const client = (dependencies ?? defaultDependencies()).createClient(host);
416552
+ if (parsed.action === "status") {
416553
+ host.showStatus(`/memory ${await client.getConsentStatus()}`);
416554
+ return;
416555
+ }
416556
+ const enabled = parsed.action === "on";
416557
+ await client.setConsent(enabled);
416558
+ await host.refreshPersonalMemory?.(false);
416559
+ host.showStatus(`/memory ${enabled ? "enabled" : "disabled"}`);
416510
416560
  }
416511
416561
  function parseMemoryCommand(args) {
416512
- const content = args.match(/^remember(?:\s+([\s\S]+))?$/i)?.[1]?.trim();
416513
- if (content === void 0 || content.length === 0) return void 0;
416514
- return {
416515
- action: "remember",
416516
- content
416517
- };
416562
+ const action = args.trim().toLowerCase();
416563
+ if (action === "status" || action === "on" || action === "off") return { action };
416518
416564
  }
416519
- function defaultDependencies(host) {
416520
- return {
416521
- remember: rememberExplicitPersonalMemory,
416522
- createClient: () => {
416523
- const client = createPersonalMemoryBrokerClient(host.harness);
416524
- return {
416525
- getSettings: () => client.getSettings(),
416526
- request: (method, path, body) => client.request(method, path, body)
416527
- };
416528
- },
416529
- getManagedQuota: () => host.harness.auth.getManagedQuota(DEFAULT_OAUTH_PROVIDER_NAME)
416530
- };
416565
+ function defaultDependencies() {
416566
+ return { createClient: (host) => createAccountMemoryClient(host.harness.auth) };
416531
416567
  }
416532
416568
  //#endregion
416533
416569
  //#region src/tui/utils/sanitize-mcp-display.copy.ts
@@ -417612,9 +417648,9 @@ function pluginTrustLabel(plugin) {
417612
417648
  if (plugin.source !== "zip-url" || plugin.originalSource === void 0) return "third-party";
417613
417649
  try {
417614
417650
  const url = new URL(plugin.originalSource);
417615
- if (url.protocol !== "https:" || url.hostname !== "blun.ai") return "third-party";
417616
- if (url.pathname.startsWith("/blun/plugins/official/")) return "official";
417617
- if (url.pathname.startsWith("/blun/plugins/curated/")) return "curated";
417651
+ if (url.protocol !== "https:") return "third-party";
417652
+ if (isOfficialPluginUrl(url)) return "official";
417653
+ if (url.hostname === "blun.ai" && url.pathname.startsWith("/blun/plugins/curated/") || url.hostname === "cdn.blun.ai" && url.pathname.startsWith("/blun-king/plugins/curated/")) return "curated";
417618
417654
  return "third-party";
417619
417655
  } catch {
417620
417656
  return "third-party";
@@ -417630,12 +417666,14 @@ function isOfficialPluginSource(source) {
417630
417666
  const trimmed = source.trim();
417631
417667
  if (!trimmed.startsWith("https://")) return false;
417632
417668
  try {
417633
- const url = new URL(trimmed);
417634
- return url.hostname === "blun.ai" && url.pathname.startsWith("/blun/plugins/official/");
417669
+ return isOfficialPluginUrl(new URL(trimmed));
417635
417670
  } catch {
417636
417671
  return false;
417637
417672
  }
417638
417673
  }
417674
+ function isOfficialPluginUrl(url) {
417675
+ return url.hostname === "blun.ai" && url.pathname.startsWith("/blun/plugins/official/") || url.hostname === "cdn.blun.ai" && url.pathname.startsWith("/blun-king/plugins/official/");
417676
+ }
417639
417677
  function hostFromUrl(raw) {
417640
417678
  try {
417641
417679
  const url = new URL(raw);
@@ -422061,17 +422099,23 @@ const BLUN_GLYPHS = {
422061
422099
  "█ █"
422062
422100
  ]
422063
422101
  };
422064
- function renderBlunWordmark(blueHex) {
422065
- const white = chalk.bold.hex("#ffffff");
422066
- const blue = chalk.bold.hex(blueHex);
422102
+ function blunWordmarkParts() {
422067
422103
  const rows = [];
422068
422104
  for (let i = 0; i < 5; i++) {
422069
422105
  const bl = `${BLUN_GLYPHS["B"][i]} ${BLUN_GLYPHS["L"][i]}`;
422070
422106
  const un = `${BLUN_GLYPHS["U"][i]} ${BLUN_GLYPHS["N"][i]}`;
422071
- rows.push(white(bl) + " " + blue(un));
422107
+ rows.push({
422108
+ bl,
422109
+ un
422110
+ });
422072
422111
  }
422073
422112
  return rows;
422074
422113
  }
422114
+ function renderBlunWordmark(blueHex) {
422115
+ const white = chalk.bold.hex("#ffffff");
422116
+ const blue = chalk.bold.hex(blueHex);
422117
+ return blunWordmarkParts().map(({ bl, un }) => white(bl) + " " + blue(un));
422118
+ }
422075
422119
  function renderHelpLine(text, dim, command) {
422076
422120
  const token = "/help";
422077
422121
  const tokenIndex = text.indexOf(token);
@@ -422102,7 +422146,7 @@ var WelcomeComponent = class {
422102
422146
  const pad = " ";
422103
422147
  const dim = chalk.hex(currentTheme.palette.textDim);
422104
422148
  const labelStyle = chalk.hex(currentTheme.palette.textDim);
422105
- const wordmarkRows = renderBlunWordmark(primaryHex);
422149
+ const wordmarkRows = getRainbowDanceView()?.colored === true ? renderDanceWelcomeWordmark(blunWordmarkParts().map(({ bl, un }) => `${bl} ${un}`)) : renderBlunWordmark(primaryHex);
422106
422150
  const tagline = chalk.bold.hex(currentTheme.palette.textStrong)(uiText("welcome.tagline"));
422107
422151
  const helpLine = isLoggedOut ? chalk.hex(currentTheme.palette.warning)(uiText("welcome.start")) : renderHelpLine(uiText("welcome.help"), dim, chalk.bold.hex(primaryHex));
422108
422152
  const directoryLabel = uiText("welcome.label.directory");
@@ -422122,7 +422166,7 @@ var WelcomeComponent = class {
422122
422166
  renderLabel(directoryLabel) + chalk.hex(currentTheme.palette.text)(this.state.workDir),
422123
422167
  renderLabel(sessionLabel) + chalk.hex(currentTheme.palette.text)(this.state.sessionId),
422124
422168
  renderLabel(modelLabel) + chalk.bold.hex(primaryHex)(modelValue),
422125
- renderLabel(versionLabel) + chalk.hex(currentTheme.palette.text)(`${PRODUCT_NAME} 1.0`)
422169
+ renderLabel(versionLabel) + chalk.hex(currentTheme.palette.text)(`${PRODUCT_NAME} ${this.state.version}`)
422126
422170
  ];
422127
422171
  if (this.state.mcpServersSummary) infoLines.push(renderLabel(mcpLabel) + chalk.hex(currentTheme.palette.text)(this.state.mcpServersSummary));
422128
422172
  const contentLines = [
@@ -486823,8 +486867,8 @@ var PrefixedWrappedLine = class {
486823
486867
  };
486824
486868
  /** BLUN code surface — the same colored full-width band the ActivityPane uses
486825
486869
  * (WORK_BG #16233a), wrapped around a Write preview so code King writes sits on
486826
- * a coloured background over the FULL width (Papa 05.07: "der bunte Hintergrund
486827
- * beim Coden"). applyBackgroundToLine pads to width and survives the inner
486870
+ * a coloured background over the FULL width. applyBackgroundToLine pads to
486871
+ * width and survives the inner
486828
486872
  * syntax-highlight ANSI resets; the line count is unchanged so the pinned
486829
486873
  * editor below never moves. */
486830
486874
  const CODE_SURFACE_BG = "#16233a";
@@ -486832,7 +486876,7 @@ var CodeSurfaceComponent = class extends Container {
486832
486876
  fixedLines;
486833
486877
  fixedRows;
486834
486878
  /**
486835
- * Fixed mode (Papa 05.07: "chatfenster wandert auf und ab beim coden"):
486879
+ * Fixed mode prevents the chat area from moving while tools run:
486836
486880
  * while Write args STREAM, the preview must have a CONSTANT physical height —
486837
486881
  * wrapped long lines and collapsing blank lines made the band grow/shrink
486838
486882
  * every delta, which bounced the whole frame once the transcript filled the
@@ -486865,7 +486909,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
486865
486909
  * True once the STREAMING preview rendered the fixed-height code band.
486866
486910
  * The collapsed FINAL preview then keeps exactly that height (padded band),
486867
486911
  * so the result landing never changes the block height — no short transcript
486868
- * jump per tool call (Papa 05.07: "bleibt manchmal kurz nicht stehen").
486912
+ * jump per tool call.
486869
486913
  */
486870
486914
  streamedFixedBand = false;
486871
486915
  toolCall;
@@ -487041,7 +487085,7 @@ var ToolCallComponent = class ToolCallComponent extends Container {
487041
487085
  * rief SOFORT rebuildBody+requestRender. Weil die Body-Hoehe sich dabei um
487042
487086
  * eine Zeile aendert, verschob der BottomPinnedTUI-Filler die Eingabe fuer
487043
487087
  * einen Frame — das sporadische 1-Zeilen-Zucken NUR waehrend Tool-Aktivitaet
487044
- * (Papa: "das passiert nicht immer"). appendLiveOutput hatte den Debounce
487088
+ * appendLiveOutput previously had the debounce
487045
487089
  * schon; hier fehlte er. Daten sofort, Neu-Render coalesced (50ms).
487046
487090
  */
487047
487091
  progressRenderTimer;
@@ -488710,9 +488754,11 @@ function webSessionUrl(origin, sessionId, token) {
488710
488754
  //#endregion
488711
488755
  //#region src/tui/commands/dispatch.ts
488712
488756
  function dispatchInput(host, text) {
488713
- if (parseSlashInput(text) !== null) {
488757
+ const parsed = parseSlashInput(text);
488758
+ if (parsed !== null) {
488714
488759
  const isBusy = host.state.appState.streamingPhase !== "idle" || host.state.appState.isCompacting;
488715
- if (host.deferUserMessages || isBusy) {
488760
+ const canRunAlongsideMain = (parsed.name === "btw" || parsed.name === "memory") && !host.state.appState.isCompacting;
488761
+ if (host.deferUserMessages || isBusy && !canRunAlongsideMain) {
488716
488762
  host.enqueueSlashCommand(text);
488717
488763
  return Promise.resolve();
488718
488764
  }
@@ -496055,7 +496101,7 @@ function isRateLimitError(error) {
496055
496101
  /**
496056
496102
  * F2 Tippen-mit-Vorschlag — Completion-Client.
496057
496103
  *
496058
- * ANBIETERNEUTRAL seit 04.08.2026 (Papa: "Wir bauen alles selbst").
496104
+ * Provider-neutral since 2026-08-04.
496059
496105
  * Vorher stand hier eine fest verdrahtete Route zu einem Fremdanbieter:
496060
496106
  * Bei jedem Tippen ging ein Datei-Ausschnitt dorthin, ohne dass der Nutzer
496061
496107
  * etwas abgeschickt hat. Das widersprach der Zusage, dass nichts den Rechner
@@ -497334,7 +497380,11 @@ var BottomPinnedTUI = class extends TUI {
497334
497380
  }
497335
497381
  paintSurface(lines, width) {
497336
497382
  if (!this.surfaceEnabled) return lines;
497337
- return lines.map((line) => applyBackgroundToLine(line, width, (text) => currentTheme.bg("surface", text)));
497383
+ try {
497384
+ return lines.map((line) => applyBackgroundToLine(line, width, (text) => currentTheme.bg("surface", text)));
497385
+ } catch {
497386
+ return lines;
497387
+ }
497338
497388
  }
497339
497389
  /**
497340
497390
  * Children from this index on (editor, footer) are pinned to the bottom.
@@ -497354,7 +497404,7 @@ var BottomPinnedTUI = class extends TUI {
497354
497404
  chromeFromIndex = Number.POSITIVE_INFINITY;
497355
497405
  /**
497356
497406
  * Rows of the pinned block that are an OVERLAY (the editor's autocomplete
497357
- * menu). The menu must not shift the transcript (Papa 05.07: "aber / das
497407
+ * menu). The menu must not shift the transcript (the
497358
497408
  * ändert es trotzdem noch") — instead it covers the bottommost transcript
497359
497409
  * rows: we drop that many content rows so every remaining row keeps its
497360
497410
  * exact position while the menu is open.
@@ -497437,7 +497487,7 @@ var BottomPinnedTUI = class extends TUI {
497437
497487
  const pinAt = this.pinFromIndex;
497438
497488
  if (!Number.isFinite(pinAt) || pinAt < 0 || pinAt >= this.children.length) {
497439
497489
  this.setMouseTracking(false);
497440
- return this.paintSurface(super.render(width), width);
497490
+ return super.render(width);
497441
497491
  }
497442
497492
  try {
497443
497493
  const blocks = this.children.map((child) => child.render(width));
@@ -507184,6 +507234,7 @@ var BlunTUI = class {
507184
507234
  scrollbackController;
507185
507235
  personalMemoryController;
507186
507236
  accountMemory;
507237
+ accountMemoryEnabledForSession = false;
507187
507238
  pendingAccountMemoryExtractions = /* @__PURE__ */ new Set();
507188
507239
  managedQuotaWarningController;
507189
507240
  managedQuotaWarningPersistence = Promise.resolve();
@@ -507489,7 +507540,10 @@ var BlunTUI = class {
507489
507540
  }
507490
507541
  if (this.aborted) return;
507491
507542
  if (this.session === void 0 && this.state.startupState !== "picker") return;
507492
- if (this.session !== void 0) await this.applyAccountMemoryContext(this.session);
507543
+ if (this.session !== void 0) {
507544
+ await this.refreshPersonalMemory(true);
507545
+ await this.authFlow.refreshManagedQuotaWindows();
507546
+ }
507493
507547
  this.showTmuxKeyboardWarningIfNeeded();
507494
507548
  this.startTelegramChannel();
507495
507549
  if (this.state.startupState === "picker") {
@@ -507748,8 +507802,8 @@ var BlunTUI = class {
507748
507802
  ui.addChild(this.state.activityContainer);
507749
507803
  ui.addChild(this.state.todoPanelContainer);
507750
507804
  ui.addChild(this.state.queueContainer);
507751
- ui.addChild(this.state.btwPanelContainer);
507752
507805
  ui.addChild(this.state.quotaWarningContainer);
507806
+ ui.addChild(this.state.btwPanelContainer);
507753
507807
  ui.addChild(this.state.editorContainer);
507754
507808
  if (ui instanceof BottomPinnedTUI) {
507755
507809
  ui.pinFromIndex = ui.children.indexOf(this.state.quotaWarningContainer);
@@ -508093,10 +508147,10 @@ var BlunTUI = class {
508093
508147
  this.state.ui.requestRender();
508094
508148
  }
508095
508149
  /**
508096
- * Remote channel inbound (Telegram) into THIS visible session — Otto gate 49.
508150
+ * Remote channel inbound (Telegram) into THIS visible session.
508097
508151
  * Never routes through handleUserInput: no slash/bash dispatch (a Telegram
508098
508152
  * "/new" is just text for the model), no local editor history. The transcript
508099
- * shows a clean user line with a muted origin prefix (Angel spec 51); the
508153
+ * shows a clean user line with a muted origin prefix; the
508100
508154
  * model receives the full <channel …> payload. Busy turns queue via the same
508101
508155
  * queuedMessages mechanic as typed input; a completed tool/step boundary
508102
508156
  * steers one FIFO head into the active turn without interrupting it.
@@ -508522,12 +508576,34 @@ var BlunTUI = class {
508522
508576
  refreshManagedQuotaWindows() {
508523
508577
  this.authFlow.refreshManagedQuotaWindows();
508524
508578
  }
508525
- async refreshPersonalMemory(_promptIfNeverAsked = false) {
508526
- if (this.session !== void 0) await this.applyAccountMemoryContext(this.session);
508579
+ async refreshPersonalMemory(promptIfNeverAsked = false) {
508580
+ const session = this.session;
508581
+ if (session === void 0) return;
508582
+ this.accountMemoryEnabledForSession = false;
508583
+ try {
508584
+ let consentStatus = await this.accountMemory.getConsentStatus();
508585
+ if (consentStatus === "never_asked" && promptIfNeverAsked) {
508586
+ const consent = await ensureStartupPersonalMemoryConsent({
508587
+ host: this,
508588
+ consentStatus,
508589
+ copy: startupPersonalMemoryConsentCopy(),
508590
+ updateSettings: async (patch) => {
508591
+ await this.accountMemory.setConsent(patch.memory_enabled === true);
508592
+ }
508593
+ });
508594
+ if (consent === "persistence-failed") this.showStatus(startupPersonalMemoryConsentCopy().persistenceFailed, "warning");
508595
+ consentStatus = consent === "enabled" || consent === "disabled" ? consent : "never_asked";
508596
+ }
508597
+ this.accountMemoryEnabledForSession = consentStatus === "enabled";
508598
+ } catch (error) {
508599
+ this.reportAccountMemoryIssue("Account memory consent unavailable", error);
508600
+ }
508601
+ await this.applyAccountMemoryContext(session);
508527
508602
  }
508528
508603
  async applyAccountMemoryContext(session) {
508529
508604
  try {
508530
- await session.setAccountMemoryContext(await this.accountMemory.loadSystemContext());
508605
+ const context = this.accountMemoryEnabledForSession ? await this.accountMemory.loadSystemContext() : void 0;
508606
+ await session.setAccountMemoryContext(context);
508531
508607
  } catch (error) {
508532
508608
  try {
508533
508609
  await session.setAccountMemoryContext(void 0);
@@ -508536,7 +508612,7 @@ var BlunTUI = class {
508536
508612
  }
508537
508613
  }
508538
508614
  extractAccountMemory(text) {
508539
- if (text.trim().length === 0) return;
508615
+ if (!this.accountMemoryEnabledForSession || text.trim().length === 0) return;
508540
508616
  let pending;
508541
508617
  pending = this.accountMemory.extract(text).catch((error) => {
508542
508618
  this.reportAccountMemoryIssue("Account memory update failed", error);
@@ -509464,7 +509540,7 @@ var BlunTUI = class {
509464
509540
  /**
509465
509541
  * Presence label next to the thinking spinner — uses the persona name the
509466
509542
  * user gave the agent, falling back to the model name "King". E.g. "Sven
509467
- * denkt…" / "King arbeitet…". Angel's persona statusline spec.
509543
+ * denkt…" / "King arbeitet…" persona status line.
509468
509544
  */
509469
509545
  personaVerbLabel(mode) {
509470
509546
  return uiText(mode === "thinking" ? "blunTui.activity.thinking" : "blunTui.activity.working", { name: personaName() ?? "King" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.17",
3
+ "version": "9.1.19",
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": {
@@ -22,6 +22,7 @@
22
22
  "x64",
23
23
  "arm64"
24
24
  ],
25
+ "dependencies": {},
25
26
  "optionalDependencies": {
26
27
  "@mariozechner/clipboard": "^0.3.9",
27
28
  "node-pty": "^1.1.0"
@@ -44,5 +45,5 @@
44
45
  "cli",
45
46
  "assistant"
46
47
  ],
47
- "author": "BLUN / Mayk Biletti"
48
+ "author": "BLUN"
48
49
  }
@@ -67311,7 +67311,7 @@ function normalize(s) {
67311
67311
  }
67312
67312
  const STRONG_META = /\b(keine (gruppen ?)?antwort|gar keine antwort|halte (mich|die klappe)|(bin|bleibe) (jetzt )?still|ich schweige|schweige|stille|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht (direkt )?an mich( gerichtet)?|nicht meine lane|keine reaktion|no ?action|kein wort|silence|staying (silent|out|quiet)|keeping (quiet|out)|no (response|reply|answer|comment)|nothing (to (add|do|report)|for me)|not (for me|my lane|addressed to me|directed at me)|holding back|standing by|no need to (respond|reply))\b/;
67313
67313
  const SHORT_CONFIRM = /^(verstanden|kapiert|ok|okay|alles klar|geht klar|angekommen|seh sie|ich seh sie|gemerkt|passt|erledigt notiert|notiert|no ?action|understood|got it|noted|acknowledged|roger|received|silence|still)\b[\s\S]{0,48}$/;
67314
- const PRESENCE_ONLY = /^(ja +papa|hier +papa|bin +da|bin +hier|hier +bin +ich|ich +bin +da|hi +papa|hallo +papa|was +brauchst +du|was +gibt +es|was +liegt +an|wie +kann +ich +helfen|bereit|ich +warte|warte +auf|melde +mich|danke +papa|freut +mich|yes +papa|here|standing +by|ready|awaiting|what +do +you +need)\b[\s\S]{0,40}$/;
67314
+ const PRESENCE_ONLY = /^(bin +da|bin +hier|hier +bin +ich|ich +bin +da|was +brauchst +du|was +gibt +es|was +liegt +an|wie +kann +ich +helfen|bereit|ich +warte|warte +auf|melde +mich|freut +mich|here|standing +by|ready|awaiting|what +do +you +need)\b[\s\S]{0,40}$/;
67315
67315
  /**
67316
67316
  * True when this reply text, sent into a GROUP, is pure meta/acknowledgement
67317
67317
  * noise that must not be delivered. Length-guarded so genuine longer answers
@@ -73467,7 +73467,7 @@ function normalize(s) {
73467
73467
  }
73468
73468
  const STRONG_META = /\b(keine (gruppen ?)?antwort|gar keine antwort|halte (mich|die klappe)|(bin|bleibe) (jetzt )?still|ich schweige|schweige|stille|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht (direkt )?an mich( gerichtet)?|nicht meine lane|keine reaktion|no ?action|kein wort|silence|staying (silent|out|quiet)|keeping (quiet|out)|no (response|reply|answer|comment)|nothing (to (add|do|report)|for me)|not (for me|my lane|addressed to me|directed at me)|holding back|standing by|no need to (respond|reply))\b/;
73469
73469
  const SHORT_CONFIRM = /^(verstanden|kapiert|ok|okay|alles klar|geht klar|angekommen|seh sie|ich seh sie|gemerkt|passt|erledigt notiert|notiert|no ?action|understood|got it|noted|acknowledged|roger|received|silence|still)\b[\s\S]{0,48}$/;
73470
- const PRESENCE_ONLY = /^(ja +papa|hier +papa|bin +da|bin +hier|hier +bin +ich|ich +bin +da|hi +papa|hallo +papa|was +brauchst +du|was +gibt +es|was +liegt +an|wie +kann +ich +helfen|bereit|ich +warte|warte +auf|melde +mich|danke +papa|freut +mich|yes +papa|here|standing +by|ready|awaiting|what +do +you +need)\b[\s\S]{0,40}$/;
73470
+ const PRESENCE_ONLY = /^(bin +da|bin +hier|hier +bin +ich|ich +bin +da|was +brauchst +du|was +gibt +es|was +liegt +an|wie +kann +ich +helfen|bereit|ich +warte|warte +auf|melde +mich|freut +mich|here|standing +by|ready|awaiting|what +do +you +need)\b[\s\S]{0,40}$/;
73471
73471
  /**
73472
73472
  * True when this reply text, sent into a GROUP, is pure meta/acknowledgement
73473
73473
  * noise that must not be delivered. Length-guarded so genuine longer answers
@@ -22,7 +22,7 @@ function normalize(s) {
22
22
  }
23
23
  const STRONG_META = /\b(keine (gruppen ?)?antwort|gar keine antwort|halte (mich|die klappe)|(bin|bleibe) (jetzt )?still|ich schweige|schweige|stille|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht (direkt )?an mich( gerichtet)?|nicht meine lane|keine reaktion|no ?action|kein wort|silence|staying (silent|out|quiet)|keeping (quiet|out)|no (response|reply|answer|comment)|nothing (to (add|do|report)|for me)|not (for me|my lane|addressed to me|directed at me)|holding back|standing by|no need to (respond|reply))\b/;
24
24
  const SHORT_CONFIRM = /^(verstanden|kapiert|ok|okay|alles klar|geht klar|angekommen|seh sie|ich seh sie|gemerkt|passt|erledigt notiert|notiert|no ?action|understood|got it|noted|acknowledged|roger|received|silence|still)\b[\s\S]{0,48}$/;
25
- const PRESENCE_ONLY = /^(ja +papa|hier +papa|bin +da|bin +hier|hier +bin +ich|ich +bin +da|hi +papa|hallo +papa|was +brauchst +du|was +gibt +es|was +liegt +an|wie +kann +ich +helfen|bereit|ich +warte|warte +auf|melde +mich|danke +papa|freut +mich|yes +papa|here|standing +by|ready|awaiting|what +do +you +need)\b[\s\S]{0,40}$/;
25
+ const PRESENCE_ONLY = /^(bin +da|bin +hier|hier +bin +ich|ich +bin +da|was +brauchst +du|was +gibt +es|was +liegt +an|wie +kann +ich +helfen|bereit|ich +warte|warte +auf|melde +mich|freut +mich|here|standing +by|ready|awaiting|what +do +you +need)\b[\s\S]{0,40}$/;
26
26
  /**
27
27
  * True when this reply text, sent into a GROUP, is pure meta/acknowledgement
28
28
  * noise that must not be delivered. Length-guarded so genuine longer answers