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.
- package/bin/session-replay-window-policy.cjs +22 -0
- package/bin/session-scrollback-archive.cjs +23 -0
- package/blun.mjs +49 -11
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
506513
|
-
|
|
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
|
-
|
|
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
|
|
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) {
|