blun-king-cli 9.1.461 → 9.1.463

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.
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ function replayWindowStartIndex(records, activeTurnCount, startsTurn) {
4
+ if (
5
+ !Array.isArray(records)
6
+ || !Number.isSafeInteger(activeTurnCount)
7
+ || activeTurnCount <= 0
8
+ || typeof startsTurn !== 'function'
9
+ ) {
10
+ return 0;
11
+ }
12
+
13
+ let remainingTurns = activeTurnCount;
14
+ for (let index = records.length - 1; index >= 0; index -= 1) {
15
+ if (!startsTurn(records[index])) continue;
16
+ remainingTurns -= 1;
17
+ if (remainingTurns === 0) return index;
18
+ }
19
+ return 0;
20
+ }
21
+
22
+ module.exports = { replayWindowStartIndex };
@@ -41,6 +41,7 @@ class SessionScrollbackArchive {
41
41
  this.loadedChunks = new Map();
42
42
  this.lineCount = 0;
43
43
  this.loadedExisting = false;
44
+ this.activeTurnCount = undefined;
44
45
 
45
46
  if (reset) {
46
47
  this.reset();
@@ -108,6 +109,21 @@ class SessionScrollbackArchive {
108
109
  return output;
109
110
  }
110
111
 
112
+ setActiveTurnCount(count) {
113
+ if (!isNonNegativeInteger(count)) return false;
114
+ if (this.activeTurnCount === count) return true;
115
+ const previous = this.activeTurnCount;
116
+ this.activeTurnCount = count;
117
+ try {
118
+ fs.mkdirSync(this.directory, { recursive: true });
119
+ this.writeIndex(this.chunks, this.lineCount);
120
+ return true;
121
+ } catch {
122
+ this.activeTurnCount = previous;
123
+ return false;
124
+ }
125
+ }
126
+
111
127
  reset() {
112
128
  try {
113
129
  fs.rmSync(this.directory, { recursive: true, force: true });
@@ -116,6 +132,7 @@ class SessionScrollbackArchive {
116
132
  this.loadedChunks.clear();
117
133
  this.lineCount = 0;
118
134
  this.loadedExisting = false;
135
+ this.activeTurnCount = undefined;
119
136
  }
120
137
 
121
138
  clear() {
@@ -165,6 +182,9 @@ class SessionScrollbackArchive {
165
182
 
166
183
  this.chunks = chunks;
167
184
  this.lineCount = parsed.totalLines;
185
+ this.activeTurnCount = isNonNegativeInteger(parsed.activeTurnCount)
186
+ ? parsed.activeTurnCount
187
+ : undefined;
168
188
  this.loadedExisting = true;
169
189
  } catch {}
170
190
  }
@@ -195,6 +215,9 @@ class SessionScrollbackArchive {
195
215
  schemaVersion: ARCHIVE_SCHEMA_VERSION,
196
216
  totalLines,
197
217
  chunks,
218
+ ...(isNonNegativeInteger(this.activeTurnCount)
219
+ ? { activeTurnCount: this.activeTurnCount }
220
+ : {}),
198
221
  }));
199
222
  }
200
223
  }
package/blun.mjs CHANGED
@@ -13,6 +13,7 @@ import compactionModelPolicy from "./bin/compaction-model-policy.cjs";
13
13
  import agentResumeSnapshot from "./bin/agent-resume-snapshot.cjs";
14
14
  import sessionResumeCheckpoint from "./bin/session-resume-checkpoint.cjs";
15
15
  import sessionScrollbackArchive from "./bin/session-scrollback-archive.cjs";
16
+ import sessionReplayWindowPolicy from "./bin/session-replay-window-policy.cjs";
16
17
  import crypto$1, { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
17
18
  import * as fs$16 from "node:fs";
18
19
  import Kt, { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, promises, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
@@ -50,6 +51,7 @@ const { resolveCompactionModelAlias } = compactionModelPolicy;
50
51
  const { createAgentResumeSnapshot, restoreAgentResumeSnapshot } = agentResumeSnapshot;
51
52
  const { loadResumeCheckpoint, writeResumeCheckpoint } = sessionResumeCheckpoint;
52
53
  const { SessionScrollbackArchive, sessionScrollbackArchiveDirectory } = sessionScrollbackArchive;
54
+ const { replayWindowStartIndex } = sessionReplayWindowPolicy;
53
55
  import { EventEmitter as EventEmitter$1 } from "node:events";
54
56
  import { StringDecoder } from "node:string_decoder";
55
57
  import co from "node:assert";
@@ -342546,7 +342548,7 @@ function parseTuiConfig(tomlText) {
342546
342548
  }
342547
342549
  async function saveTuiConfig(config, filePath = getTuiConfigPath()) {
342548
342550
  assertConfiguredAppearanceContrast(config);
342549
- await withTuiConfigLock(filePath, () => writeTuiConfigAtomic(config, filePath));
342551
+ return withTuiConfigLock(filePath, () => writeTuiConfigIfChanged(config, filePath));
342550
342552
  }
342551
342553
  async function updateTuiAppearance(appearance, filePath = getTuiConfigPath()) {
342552
342554
  const parsedAppearance = normalizeAppearanceConfig(appearance === void 0 ? void 0 : AppearanceConfigSchema.parse(appearance));
@@ -342567,7 +342569,7 @@ async function updateTuiAppearance(appearance, filePath = getTuiConfigPath()) {
342567
342569
  ...parsedAppearance === void 0 ? { appearance: void 0 } : { appearance: parsedAppearance }
342568
342570
  });
342569
342571
  assertConfiguredAppearanceContrast(next);
342570
- await writeTuiConfigAtomic(next, filePath);
342572
+ await writeTuiConfigIfChanged(next, filePath);
342571
342573
  return next;
342572
342574
  });
342573
342575
  }
@@ -342638,12 +342640,12 @@ async function withTuiConfigLock(filePath, action) {
342638
342640
  await release();
342639
342641
  }
342640
342642
  }
342641
- async function writeTuiConfigAtomic(config, filePath) {
342643
+ async function writeTuiConfigAtomic(config, filePath, rendered = renderTuiConfig(config)) {
342642
342644
  const temporary = join(dirname(filePath), `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
342643
342645
  const handle = await open(temporary, "wx", 384);
342644
342646
  try {
342645
342647
  try {
342646
- await handle.writeFile(renderTuiConfig(config), "utf-8");
342648
+ await handle.writeFile(rendered, "utf-8");
342647
342649
  await handle.sync();
342648
342650
  } finally {
342649
342651
  await handle.close();
@@ -342654,6 +342656,12 @@ async function writeTuiConfigAtomic(config, filePath) {
342654
342656
  throw error;
342655
342657
  }
342656
342658
  }
342659
+ async function writeTuiConfigIfChanged(config, filePath) {
342660
+ const rendered = renderTuiConfig(config);
342661
+ if (await configFileMatches(filePath, rendered)) return false;
342662
+ await writeTuiConfigAtomic(config, filePath, rendered);
342663
+ return true;
342664
+ }
342657
342665
  function isNotFound$3(error) {
342658
342666
  return typeof error === "object" && error !== null && error.code === "ENOENT";
342659
342667
  }
@@ -506110,9 +506118,15 @@ var ScrollbackBuffer = class {
506110
506118
  get totalLines() {
506111
506119
  return this.archive.totalLines + this.snapshotLines.length;
506112
506120
  }
506121
+ get activeTurnCount() {
506122
+ return this.archive instanceof SessionScrollbackArchive ? this.archive.activeTurnCount : void 0;
506123
+ }
506113
506124
  archiveLines(lines) {
506114
506125
  return this.archive.append(lines);
506115
506126
  }
506127
+ setActiveTurnCount(count) {
506128
+ return this.archive instanceof SessionScrollbackArchive ? this.archive.setActiveTurnCount(count) : false;
506129
+ }
506116
506130
  usePersistentArchive(directory, options) {
506117
506131
  this.releaseArchive();
506118
506132
  const archive = new SessionScrollbackArchive({
@@ -506509,12 +506523,17 @@ var ScrollbackController = class {
506509
506523
  reset: !resumeCheckpointRestored,
506510
506524
  reuse: resumeCheckpointRestored
506511
506525
  });
506512
- this.suppressReplayArchiveWrites = resumeCheckpointRestored && loadedExisting;
506513
- return true;
506526
+ const activeTurnCount = resumeCheckpointRestored && loadedExisting && buffer.totalLines > 0 ? buffer.activeTurnCount : void 0;
506527
+ this.suppressReplayArchiveWrites = resumeCheckpointRestored && loadedExisting && activeTurnCount === void 0;
506528
+ return activeTurnCount;
506514
506529
  }
506515
506530
  finishSessionReplay() {
506531
+ this.persistActiveTurnCount(groupTurns(this.state.transcriptEntries).length);
506516
506532
  this.suppressReplayArchiveWrites = false;
506517
506533
  }
506534
+ persistActiveTurnCount(count) {
506535
+ this.buffer?.setActiveTurnCount(count);
506536
+ }
506518
506537
  archiveComponents(components) {
506519
506538
  const buffer = this.buffer;
506520
506539
  if (buffer === void 0 || components.length === 0) return components.length === 0;
@@ -509538,6 +509557,21 @@ function createReplayRenderContext() {
509538
509557
  suppressNextPlanModeOffNotice: false
509539
509558
  };
509540
509559
  }
509560
+ function replayRecordStartsVisibleTurn(record) {
509561
+ if (record?.type !== "message" || record.message?.role !== "user") return false;
509562
+ const message = record.message;
509563
+ if (backgroundOrigin(message) !== void 0) return false;
509564
+ if (message.origin?.kind === "hook_result" || message.origin?.kind === "injection") return false;
509565
+ if (message.origin?.kind === "shell_command") return message.origin.phase === "input";
509566
+ if (message.origin?.kind === "cron_job" || message.origin?.kind === "cron_missed") return false;
509567
+ if (isGoalForkClearedSystemReminder(message)) return false;
509568
+ if (goalOutcomeReminderFromSystemMessage(message) !== null) return false;
509569
+ const skill = skillActivationFromOrigin(message.origin);
509570
+ if (skill !== void 0) return message.origin?.kind === "skill_activation" && message.origin.trigger === "user-slash";
509571
+ const pluginCommand = pluginCommandFromOrigin(message.origin);
509572
+ if (pluginCommand !== void 0) return message.origin?.kind === "plugin_command" && message.origin.trigger === "user-slash";
509573
+ return true;
509574
+ }
509541
509575
  function replayEntry(context, kind, content, renderMode, extras = {}) {
509542
509576
  return {
509543
509577
  id: nextTranscriptId(),
@@ -509804,9 +509838,9 @@ var SessionReplayRenderer = class {
509804
509838
  this.host.showError(uiText("replay.error.unavailable"));
509805
509839
  return false;
509806
509840
  }
509807
- this.host.scrollbackController.prepareSessionReplay(main);
509841
+ const activeTurnCount = this.host.scrollbackController.prepareSessionReplay(main);
509808
509842
  this.hydrateSnapshot(main);
509809
- await this.renderRecords(main);
509843
+ await this.renderRecords(main, activeTurnCount);
509810
509844
  this.applyTerminalBackgroundAgentStatuses(main);
509811
509845
  this.host.mergeAllTurnSteps();
509812
509846
  this.host.requestTranscriptRender();
@@ -509874,9 +509908,11 @@ var SessionReplayRenderer = class {
509874
509908
  state.footer.setBackgroundCounts(countActiveBackgroundTasks(sessionEventHandler.backgroundTasks));
509875
509909
  state.ui.requestRender();
509876
509910
  }
509877
- async renderRecords(agent) {
509911
+ async renderRecords(agent, activeTurnCount) {
509878
509912
  const context = createReplayRenderContext();
509879
- for (const [recordIndex, record] of agent.replay.entries()) {
509913
+ const startIndex = replayWindowStartIndex(agent.replay, activeTurnCount, replayRecordStartsVisibleTurn);
509914
+ for (let recordIndex = startIndex; recordIndex < agent.replay.length; recordIndex++) {
509915
+ const record = agent.replay[recordIndex];
509880
509916
  this.renderRecord(context, record);
509881
509917
  if (shouldYieldSessionReplay(recordIndex + 1, agent.replay.length)) await yieldSessionReplayControl();
509882
509918
  }
@@ -519128,9 +519164,11 @@ var BlunTUI = class {
519128
519164
  markTranscriptComponent(component, entry);
519129
519165
  this.state.transcriptContainer.addChild(component);
519130
519166
  }
519131
- const trimmed = component !== null && this.isTurnBoundaryComponent(component) ? this.trimTranscriptWindow() : false;
519167
+ const turnBoundary = component !== null && this.isTurnBoundaryComponent(component);
519168
+ const trimmed = turnBoundary ? this.trimTranscriptWindow() : false;
519132
519169
  const deferReplayWork = this.state.appState.isReplaying;
519133
519170
  const merged = deferReplayWork ? false : this.mergeCurrentTurnSteps();
519171
+ if (turnBoundary && !deferReplayWork) this.scrollbackController.persistActiveTurnCount(groupTurns(this.state.transcriptEntries).length);
519134
519172
  if ((component || trimmed || merged) && !deferReplayWork) this.state.ui.requestRender();
519135
519173
  }
519136
519174
  appendApprovalTranscriptEntry(request, response) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.461",
3
+ "version": "9.1.463",
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": {