blun-king-cli 9.1.60 → 9.1.62

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/blun.mjs CHANGED
@@ -503027,7 +503027,7 @@ var PersonalMemoryController = class {
503027
503027
  //#endregion
503028
503028
  //#region src/tui/constant/scrollback.ts
503029
503029
  function shouldEnableScrollbackMouseTracking(buffer) {
503030
- return buffer !== void 0 && buffer.isActive();
503030
+ return buffer !== void 0 && buffer.totalLines > 0;
503031
503031
  }
503032
503032
  const ENABLE_SCROLLBACK_MOUSE_TRACKING = "\x1B[?1000h\x1B[?1006h";
503033
503033
  const DISABLE_SCROLLBACK_MOUSE_TRACKING = "\x1B[?1000l\x1B[?1006l";
@@ -503099,8 +503099,8 @@ registerUiCatalogFragment({
503099
503099
  * 0 = most recent clipped lines visible
503100
503100
  * N = N lines up from the bottom
503101
503101
  */
503102
- /** Maximum lines retained before the oldest are evicted. */
503103
- const MAX_BUFFER_LINES = 1e4;
503102
+ /** Retain the complete rendered session unless the process itself ends. */
503103
+ const MAX_BUFFER_LINES = Number.POSITIVE_INFINITY;
503104
503104
  var ScrollbackBuffer = class {
503105
503105
  lines = [];
503106
503106
  scrollOffset = 0;
@@ -505264,6 +505264,7 @@ var SessionEventHandler = class {
505264
505264
  this.goalCompletionTurnEnded = true;
505265
505265
  this.scheduleQueuedGoalPromotion();
505266
505266
  if (event.reason === "completed") this.host.refreshManagedQuotaWindows();
505267
+ requestRunningUpdateAtSafeBoundary(this.host);
505267
505268
  }
505268
505269
  handleStepBegin(event) {
505269
505270
  this.host.commitQueuedSteerAtStepStart(event.turnId);
@@ -505311,12 +505312,13 @@ var SessionEventHandler = class {
505311
505312
  const duration = durationMs === void 0 ? "—" : formatResponseDuration(durationMs);
505312
505313
  const tokens = this.currentTurnTokenCount === void 0 ? "—" : formatTokenCount(this.currentTurnTokenCount);
505313
505314
  const tokenKey = this.currentTurnTokenCount === 1 ? "thinking.tokens.one" : "thinking.tokens.other";
505315
+ const startedAt = formatLiveStartedAt(this.currentTurnStartedAtMs ?? Date.now());
505314
505316
  this.host.appendTranscriptEntry({
505315
505317
  id: nextTranscriptId(),
505316
505318
  kind: "status",
505317
505319
  turnId: String(event.turnId),
505318
505320
  renderMode: "plain",
505319
- content: `${duration} · ${uiText(tokenKey, { count: tokens })}`
505321
+ content: `${duration} · ${uiText(tokenKey, { count: tokens })} · ${startedAt}`
505320
505322
  });
505321
505323
  }
505322
505324
  markActiveAgentSwarmsCancelled() {
@@ -506297,12 +506299,6 @@ function createReplayRenderContext() {
506297
506299
  suppressNextPlanModeOffNotice: false
506298
506300
  };
506299
506301
  }
506300
- function limitReplayRecordsByTurn(records, maxTurns) {
506301
- if (maxTurns <= 0) return [];
506302
- const turnStarts = records.flatMap((record, index) => isReplayUserTurnRecord(record) ? [index] : []);
506303
- if (turnStarts.length <= maxTurns) return records;
506304
- return records.slice(turnStarts[turnStarts.length - maxTurns]);
506305
- }
506306
506302
  function replayEntry(context, kind, content, renderMode, extras = {}) {
506307
506303
  return {
506308
506304
  id: nextTranscriptId(),
@@ -506630,7 +506626,7 @@ var SessionReplayRenderer = class {
506630
506626
  }
506631
506627
  renderRecords(agent) {
506632
506628
  const context = createReplayRenderContext();
506633
- for (const record of limitReplayRecordsByTurn(agent.replay, 10)) this.renderRecord(context, record);
506629
+ for (const record of agent.replay) this.renderRecord(context, record);
506634
506630
  this.flushAssistant(context);
506635
506631
  this.cleanupRuntime(context);
506636
506632
  }
@@ -507717,7 +507713,7 @@ function formatLoopChatIndicator(loop, colors, nowMs = Date.now()) {
507717
507713
  numeric: "always",
507718
507714
  style: "short"
507719
507715
  }).format(value, unit);
507720
- return chalk.hex(colors.primary)("\u25cf") + chalk.hex(colors.textMuted)(` Loop ${relative}`);
507716
+ return chalk.hex(colors.textMuted)(`Loop ${relative}`);
507721
507717
  }
507722
507718
  function formatBadgeElapsed(ms) {
507723
507719
  const totalSeconds = Math.round(ms / 1e3);
@@ -508032,9 +508028,9 @@ var FooterComponent = class {
508032
508028
  if (model) {
508033
508029
  const sep = chalk.hex(colors.textMuted)(" · ");
508034
508030
  const persona = personaName();
508035
- const profileLabel = process.env["BLUN_PROFILE"] ?? "default";
508036
- const profile = chalk.hex(colors.textDim)(profileLabel) + sep;
508037
- const brand = chalk.hex(colors.primary).bold("● BLUN") + sep + profile + (persona !== void 0 ? chalk.hex(colors.text)(persona) + sep : "");
508031
+ const profileLabel = process.env["BLUN_PROFILE"]?.trim();
508032
+ const profile = profileLabel !== void 0 && profileLabel.length > 0 && profileLabel.toLowerCase() !== "default" ? chalk.hex(colors.textDim)(profileLabel) + sep : "";
508033
+ const brand = chalk.hex(colors.primary).bold("● BL") + chalk.hex(colors.text)("UN") + sep + profile + (persona !== void 0 ? chalk.hex(colors.text)(persona) + sep : "");
508038
508034
  const modelLabel = model;
508039
508035
  const modeLabel = uiText(state.permissionMode === "yolo" ? "footer.mode.god" : state.permissionMode === "auto" ? "footer.mode.auto" : "footer.mode.manual");
508040
508036
  const modeSuffix = sep + chalk.hex(colors.textDim)(uiText("footer.mode", { mode: modeLabel }));
@@ -511396,12 +511392,18 @@ var CustomEditor = class extends Editor {
511396
511392
  if (isKeyRelease(normalized)) return;
511397
511393
  if (!matchesKey(normalized, Key.escape)) this.onNonEscapeInput?.();
511398
511394
  if (this.consumingPaste) {
511399
- this.consumeBuffer += normalized;
511400
- if (this.consumeBuffer.includes(BRACKET_PASTE_END)) {
511395
+ const cancelPaste = matchesKey(normalized, Key.ctrl("c")) || matchesKey(normalized, Key.escape);
511396
+ if (cancelPaste) {
511401
511397
  this.consumingPaste = false;
511402
511398
  this.consumeBuffer = "";
511399
+ } else {
511400
+ this.consumeBuffer += normalized;
511401
+ if (this.consumeBuffer.includes(BRACKET_PASTE_END)) {
511402
+ this.consumingPaste = false;
511403
+ this.consumeBuffer = "";
511404
+ }
511405
+ return;
511403
511406
  }
511404
- return;
511405
511407
  }
511406
511408
  if (normalized.includes(BRACKET_PASTE_START) && this.expandPasteMarkerAtCursor()) {
511407
511409
  if (!normalized.includes(BRACKET_PASTE_END)) this.consumingPaste = true;
@@ -511456,6 +511458,7 @@ var CustomEditor = class extends Editor {
511456
511458
  if (this.inputMode === "bash" && this.getText().length === 0 && (matchesKey(normalized, Key.escape) || matchesKey(normalized, Key.backspace))) {
511457
511459
  this.inputMode = "prompt";
511458
511460
  this.onInputModeChange?.("prompt");
511461
+ if (matchesKey(normalized, Key.escape)) this.onEscape?.(true);
511459
511462
  return;
511460
511463
  }
511461
511464
  if (matchesKey(normalized, Key.up)) {
@@ -511742,6 +511745,7 @@ var GhostSuggestEditor = class extends CustomEditor {
511742
511745
  if (matchesKey(normalized, Key.escape)) {
511743
511746
  this.ghostSuggestion = null;
511744
511747
  this.onGhostDismissed?.();
511748
+ this.onEscape?.(true);
511745
511749
  return;
511746
511750
  }
511747
511751
  if (this.isCursorAtTextEnd() && !this.isShowingAutocomplete()) {
@@ -513049,13 +513053,13 @@ function readEnvInt(name, fallback) {
513049
513053
  return value;
513050
513054
  }
513051
513055
  /** Keep the most recent N turns. `0` disables trimming. */
513052
- const TRANSCRIPT_MAX_TURNS = readEnvInt("BLUN_TUI_MAX_TURNS", 15);
513056
+ const TRANSCRIPT_MAX_TURNS = readEnvInt("BLUN_TUI_MAX_TURNS", 0);
513053
513057
  /** Only the most recent E turns are allowed to expand (Ctrl+O). `0` disables expanding. */
513054
513058
  const TRANSCRIPT_EXPAND_TURNS = readEnvInt("BLUN_TUI_EXPAND_TURNS", 3);
513055
513059
  /** Only trim once the window exceeds maxTurns by this much (avoids churn). */
513056
513060
  const TRANSCRIPT_HYSTERESIS = readEnvInt("BLUN_TUI_HYSTERESIS", 5);
513057
513061
  /** Keep this many recent steps untouched inside a turn; older steps are merged into a summary. `0` disables merging. */
513058
- const TRANSCRIPT_KEEP_RECENT_STEPS = readEnvInt("BLUN_TUI_KEEP_RECENT_STEPS", 30);
513062
+ const TRANSCRIPT_KEEP_RECENT_STEPS = readEnvInt("BLUN_TUI_KEEP_RECENT_STEPS", 0);
513059
513063
  /**
513060
513064
  * Group consecutive entries into turns by `turnId`. Entries with the same
513061
513065
  * non-undefined `turnId` that are adjacent belong to the same turn.
@@ -513112,6 +513116,60 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
513112
513116
  }
513113
513117
  //#endregion
513114
513118
  //#region src/tui/blun-tui.ts
513119
+ const {
513120
+ RUNNING_UPDATE_HANDOFF_EXIT_CODE,
513121
+ RUNNING_UPDATE_HANDOFF_MESSAGE,
513122
+ RUNNING_UPDATE_PREPARED_MESSAGE,
513123
+ RUNTIME_READY_MESSAGE,
513124
+ isSafeRuntimeBoundary
513125
+ } = __require("./bin/running-update.cjs");
513126
+ function notifyRunningRuntimeReady(tui) {
513127
+ if (!process.connected) return;
513128
+ const sessionId = tui.getCurrentSessionId();
513129
+ if (sessionId.length === 0) return;
513130
+ try {
513131
+ process.send({
513132
+ type: RUNTIME_READY_MESSAGE,
513133
+ sessionId
513134
+ });
513135
+ } catch {}
513136
+ }
513137
+ function requestRunningUpdateAtSafeBoundary(tui) {
513138
+ if (tui.runningUpdatePreparedVersion === void 0 || tui.runningUpdateHandoffStarted || !process.connected) return false;
513139
+ if (!isSafeRuntimeBoundary({
513140
+ isShuttingDown: tui.isShuttingDown,
513141
+ streamingPhase: tui.state.appState.streamingPhase,
513142
+ isCompacting: tui.state.appState.isCompacting,
513143
+ queuedMessages: tui.state.queuedMessages.length,
513144
+ activeToolCalls: tui.streamingUI.hasActiveToolCalls() ? 1 : 0,
513145
+ shellCommands: tui.shellOutputStreams.size,
513146
+ queueCommandRunning: tui.queueCommandRunning
513147
+ })) return false;
513148
+ const sessionId = tui.getCurrentSessionId();
513149
+ if (sessionId.length === 0) return false;
513150
+ tui.runningUpdateHandoffStarted = true;
513151
+ try {
513152
+ process.send({
513153
+ type: RUNNING_UPDATE_HANDOFF_MESSAGE,
513154
+ sessionId,
513155
+ version: tui.runningUpdatePreparedVersion
513156
+ });
513157
+ } catch {
513158
+ tui.runningUpdateHandoffStarted = false;
513159
+ return false;
513160
+ }
513161
+ void tui.stop(RUNNING_UPDATE_HANDOFF_EXIT_CODE);
513162
+ return true;
513163
+ }
513164
+ function installRunningUpdateListener(tui) {
513165
+ const handler = (message) => {
513166
+ if (message?.type !== RUNNING_UPDATE_PREPARED_MESSAGE || typeof message.version !== "string") return;
513167
+ tui.runningUpdatePreparedVersion = message.version;
513168
+ requestRunningUpdateAtSafeBoundary(tui);
513169
+ };
513170
+ process.on("message", handler);
513171
+ return () => process.off("message", handler);
513172
+ }
513115
513173
  function loadingTipKind(mode) {
513116
513174
  if (mode === "waiting" || mode === "tool") return "blun";
513117
513175
  if (mode === "composing") return "composing";
@@ -513196,6 +513254,9 @@ var BlunTUI = class {
513196
513254
  startupLoginRequired = false;
513197
513255
  startupWorkspaceSelectionPending = false;
513198
513256
  startupGoalPromptedSessionId;
513257
+ runningUpdatePreparedVersion;
513258
+ runningUpdateHandoffStarted = false;
513259
+ runningUpdateMessageDispose;
513199
513260
  startupPhaseMs = {};
513200
513261
  lastActivityMode;
513201
513262
  currentLoadingTip = void 0;
@@ -513309,6 +513370,7 @@ var BlunTUI = class {
513309
513370
  this.editorKeyboard.install();
513310
513371
  this.scrollbackController = new ScrollbackController(this.state);
513311
513372
  this.scrollbackController.install();
513373
+ this.runningUpdateMessageDispose = installRunningUpdateListener(this);
513312
513374
  this.buildLayout();
513313
513375
  }
513314
513376
  getSlashCommands() {
@@ -513739,6 +513801,8 @@ var BlunTUI = class {
513739
513801
  this.managedQuotaWarningController.dispose();
513740
513802
  this.state.loopIndicator.dispose();
513741
513803
  this.state.footer.dispose();
513804
+ this.runningUpdateMessageDispose?.();
513805
+ this.runningUpdateMessageDispose = void 0;
513742
513806
  for (const dispose of this.reverseRpcDisposers) dispose();
513743
513807
  this.reverseRpcDisposers.length = 0;
513744
513808
  this.disposeTerminalTracking();
@@ -513962,6 +514026,7 @@ var BlunTUI = class {
513962
514026
  if (this.shellOutputStreams.size === 0) {
513963
514027
  this.setAppState({ streamingPhase: "idle" });
513964
514028
  this.drainOneQueuedMessage();
514029
+ requestRunningUpdateAtSafeBoundary(this);
513965
514030
  }
513966
514031
  }
513967
514032
  queueDrainTimer;
@@ -516409,22 +516474,27 @@ async function runShell(opts, version, updateStartupNotice) {
516409
516474
  tui.onExit = async (exitCode = 0) => {
516410
516475
  const sessionId = tui.getCurrentSessionId();
516411
516476
  const hasContent = tui.hasSessionContent();
516477
+ const runningUpdateHandoff = exitCode === RUNNING_UPDATE_HANDOFF_EXIT_CODE;
516412
516478
  setCrashPhase("shutdown");
516413
516479
  trackLifecycle("exit", { duration_ms: Date.now() - startedAt });
516414
516480
  await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS });
516415
516481
  const gutter = " ".repeat(1);
516416
- process.stdout.write(`${gutter}Bye!\n`);
516482
+ if (!runningUpdateHandoff) process.stdout.write(`${gutter}Bye!\n`);
516417
516483
  const hints = [];
516418
- if (sessionId !== "" && hasContent) hints.push(`${gutter}To resume this session: blun -r ${sessionId}`);
516419
- if (tui.exitOpenUrl !== void 0) hints.push(`${gutter}open ${toTerminalHyperlink$1(tui.exitOpenUrl, tui.exitOpenUrl)}`);
516484
+ if (!runningUpdateHandoff && sessionId !== "" && hasContent) hints.push(`${gutter}To resume this session: blun -r ${sessionId}`);
516485
+ if (!runningUpdateHandoff && tui.exitOpenUrl !== void 0) hints.push(`${gutter}open ${toTerminalHyperlink$1(tui.exitOpenUrl, tui.exitOpenUrl)}`);
516420
516486
  if (hints.length > 0) process.stderr.write(`\n${hints.join("\n")}\n`);
516421
516487
  removeCrashHandlers();
516422
516488
  restoreStty();
516489
+ if (process.connected) try {
516490
+ process.disconnect();
516491
+ } catch {}
516423
516492
  process.exit(exitCode);
516424
516493
  };
516425
516494
  try {
516426
516495
  const initStartedAt = Date.now();
516427
516496
  await tui.start();
516497
+ notifyRunningRuntimeReady(tui);
516428
516498
  const initMs = Date.now() - initStartedAt;
516429
516499
  const startupSessionId = tui.getCurrentSessionId();
516430
516500
  const mcpMs = await tui.getStartupMcpMs();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.60",
3
+ "version": "9.1.62",
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": {
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: access
3
- description: Manage Telegram channel access — approve pairings, edit allowlists, set DM/group policy.
3
+ description: Manage Telegram channel access — create connection codes, approve pairings, edit allowlists, set DM/group policy.
4
4
  ---
5
5
  Manage access control for the BLUN Telegram channel. The state file is
6
6
  `~/.blun/channels/telegram/access.json` (create the directory and a default
7
- `{"dmPolicy":"pairing","allowFrom":[],"groups":{},"pending":{}}` if missing).
7
+ `{"dmPolicy":"pairing","allowFrom":[],"groups":{},"pending":{},"connect":{}}` if missing).
8
8
  Honor `BLUN_TELEGRAM_STATE_DIR` if that environment variable is set.
9
9
 
10
10
  ARGUMENTS: $ARGUMENTS
@@ -13,6 +13,14 @@ Supported operations (parse from the arguments above; if empty, show current
13
13
  state as a short summary — dmPolicy, allowFrom entries, groups, pending codes
14
14
  with expiry):
15
15
 
16
+ - `connect` — generate a cryptographically random 6-character lowercase hex
17
+ code in this console. Store it as `connect[code]` with `createdAt` now and
18
+ `expiresAt` ten minutes from now, save atomically, then print exactly these
19
+ two useful lines (never ask for or print a Telegram chat id):
20
+ `Telegram-Code: <code>`
21
+ `Jetzt im gewuenschten privaten Chat oder in der Gruppe senden: /connect <code>`
22
+ The bridge consumes the code once and records the Telegram ids internally.
23
+
16
24
  - `pair <code>` — look up `pending[<code>]`. If present and not expired
17
25
  (`expiresAt` in ms since epoch): move its `senderId` into `allowFrom`
18
26
  (deduplicated), delete the pending entry, save the file, then write an empty