blun-king-cli 9.1.458 → 9.1.459

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,210 @@
1
+ 'use strict';
2
+
3
+ const AGENT_RESUME_SNAPSHOT_SCHEMA_VERSION = 1;
4
+
5
+ function copyJsonObject(value, fallback) {
6
+ if (value === undefined) return fallback;
7
+ return JSON.parse(JSON.stringify(value));
8
+ }
9
+
10
+ function captureMessageTimes(agent) {
11
+ const replayTimes = new Map();
12
+ for (const record of agent.replayBuilder.records) {
13
+ if (record?.type === 'message' && Number.isFinite(record.time)) {
14
+ replayTimes.set(record.message, record.time);
15
+ }
16
+ }
17
+ return agent.context.history.map((message) => {
18
+ const time = agent.records.messageTimes?.get(message) ?? replayTimes.get(message);
19
+ return Number.isFinite(time) ? time : null;
20
+ });
21
+ }
22
+
23
+ function createAgentResumeSnapshot(agent) {
24
+ const userTools = [...agent.tools.userTools.values()].map((tool) => ({
25
+ name: tool.name,
26
+ description: tool.description,
27
+ parameters: tool.parameters,
28
+ }));
29
+ return {
30
+ schemaVersion: AGENT_RESUME_SNAPSHOT_SCHEMA_VERSION,
31
+ context: {
32
+ history: agent.context.history,
33
+ tokenCount: agent.context.tokenCount,
34
+ tokenCountCoveredMessageCount: agent.context.tokenCountCoveredMessageCount,
35
+ lastAssistantAt: agent.context._lastAssistantAt,
36
+ messageTimes: captureMessageTimes(agent),
37
+ },
38
+ config: {
39
+ cwd: agent.config._cwd,
40
+ modelAlias: agent.config._modelAlias,
41
+ profileName: agent.config._profileName,
42
+ thinkingEffort: agent.config._thinkingEffort,
43
+ actionStyle: agent.config._actionStyle,
44
+ systemPrompt: agent.config._systemPrompt,
45
+ },
46
+ permission: {
47
+ hasModeOverride: agent.permission.modeOverride !== undefined,
48
+ modeOverride: agent.permission.modeOverride,
49
+ sessionApprovalRulePatterns: [...agent.permission.localSessionApprovalRulePatterns],
50
+ },
51
+ plan: {
52
+ active: agent.planMode._isActive,
53
+ id: agent.planMode._planId,
54
+ filePath: agent.planMode._planFilePath,
55
+ },
56
+ swarm: { active: agent.swarmMode.active },
57
+ usage: { byModel: agent.usage.byModel },
58
+ tools: {
59
+ userTools,
60
+ enabledTools: [...agent.tools.enabledTools],
61
+ mcpAccessPatterns: [...agent.tools.mcpAccessPatterns],
62
+ store: agent.tools.store,
63
+ allowedTools: agent.tools.allowedTools,
64
+ excludedTools: [...agent.tools.excludedTools],
65
+ },
66
+ goal: agent.goal.state ?? null,
67
+ turn: {
68
+ turnId: agent.turn.turnId,
69
+ telegramDeliveryIdentities: agent.turn.telegramDeliveryLedger.snapshot(),
70
+ },
71
+ microCompaction: { cutoff: agent.microCompaction.cutoff },
72
+ };
73
+ }
74
+
75
+ function isValidSnapshot(snapshot) {
76
+ return snapshot !== null
77
+ && typeof snapshot === 'object'
78
+ && snapshot.schemaVersion === AGENT_RESUME_SNAPSHOT_SCHEMA_VERSION
79
+ && snapshot.context !== null
80
+ && typeof snapshot.context === 'object'
81
+ && Array.isArray(snapshot.context.history)
82
+ && snapshot.config !== null
83
+ && typeof snapshot.config === 'object'
84
+ && typeof snapshot.config.cwd === 'string'
85
+ && snapshot.permission !== null
86
+ && typeof snapshot.permission === 'object'
87
+ && snapshot.tools !== null
88
+ && typeof snapshot.tools === 'object'
89
+ && Array.isArray(snapshot.tools.userTools)
90
+ && snapshot.turn !== null
91
+ && typeof snapshot.turn === 'object';
92
+ }
93
+
94
+ function restoreAgentResumeSnapshot(agent, snapshot) {
95
+ if (!isValidSnapshot(snapshot)) return false;
96
+
97
+ const previousRestoring = agent.records._restoring;
98
+ agent.records._restoring = { time: Date.now() };
99
+ try {
100
+ agent.config._cwd = snapshot.config.cwd;
101
+ agent.config._modelAlias = snapshot.config.modelAlias;
102
+ agent.config._profileName = snapshot.config.profileName;
103
+ agent.config._thinkingEffort = snapshot.config.thinkingEffort ?? 'off';
104
+ agent.config._actionStyle = snapshot.config.actionStyle ?? 'default';
105
+ agent.config._systemPrompt = snapshot.config.systemPrompt ?? '';
106
+ agent.kaos.chdir(snapshot.config.cwd);
107
+
108
+ agent.context._history = snapshot.context.history;
109
+ agent.context._tokenCount = Number.isFinite(snapshot.context.tokenCount)
110
+ ? snapshot.context.tokenCount
111
+ : 0;
112
+ agent.context.tokenCountCoveredMessageCount = Number.isInteger(
113
+ snapshot.context.tokenCountCoveredMessageCount,
114
+ ) ? Math.max(
115
+ 0,
116
+ Math.min(snapshot.context.history.length, snapshot.context.tokenCountCoveredMessageCount),
117
+ ) : 0;
118
+ agent.context._lastAssistantAt = Number.isFinite(snapshot.context.lastAssistantAt)
119
+ ? snapshot.context.lastAssistantAt
120
+ : null;
121
+ const messageTimes = Array.isArray(snapshot.context.messageTimes)
122
+ ? snapshot.context.messageTimes
123
+ : [];
124
+ if (agent.records.messageTimes?.set) {
125
+ snapshot.context.history.forEach((message, index) => {
126
+ const time = messageTimes[index];
127
+ if (Number.isFinite(time)) agent.records.messageTimes.set(message, time);
128
+ });
129
+ }
130
+ agent.context.openSteps.clear();
131
+ agent.context.pendingToolResultIds.clear();
132
+ agent.context.deferredMessages = [];
133
+ agent.context.markPendingTokenEstimateDirty();
134
+ agent.injection.onContextClear();
135
+
136
+ agent.permission.modeOverride = snapshot.permission.hasModeOverride === true
137
+ ? snapshot.permission.modeOverride
138
+ : undefined;
139
+ agent.permission.localSessionApprovalRulePatterns = new Set(
140
+ Array.isArray(snapshot.permission.sessionApprovalRulePatterns)
141
+ ? snapshot.permission.sessionApprovalRulePatterns
142
+ : [],
143
+ );
144
+
145
+ agent.planMode._isActive = snapshot.plan?.active === true;
146
+ agent.planMode._planId = agent.planMode._isActive ? snapshot.plan?.id ?? null : null;
147
+ agent.planMode._planFilePath = agent.planMode._isActive
148
+ ? snapshot.plan?.filePath ?? null
149
+ : null;
150
+ agent.swarmMode.active = snapshot.swarm?.active ?? null;
151
+ agent.usage.byModel = copyJsonObject(snapshot.usage?.byModel, {});
152
+ agent.usage.currentTurn = undefined;
153
+
154
+ agent.tools.userTools.clear();
155
+ for (const tool of snapshot.tools.userTools) {
156
+ if (!tool || typeof tool.name !== 'string' || !tool.name) continue;
157
+ agent.tools.registerUserTool({
158
+ name: tool.name,
159
+ description: typeof tool.description === 'string' ? tool.description : '',
160
+ parameters: tool.parameters ?? { type: 'object' },
161
+ });
162
+ }
163
+ agent.tools.enabledTools = new Set(
164
+ Array.isArray(snapshot.tools.enabledTools) ? snapshot.tools.enabledTools : [],
165
+ );
166
+ agent.tools.mcpAccessPatterns = Array.isArray(snapshot.tools.mcpAccessPatterns)
167
+ ? [...snapshot.tools.mcpAccessPatterns]
168
+ : [];
169
+ agent.tools.store = copyJsonObject(snapshot.tools.store, {});
170
+ agent.tools.allowedTools = Array.isArray(snapshot.tools.allowedTools)
171
+ ? [...snapshot.tools.allowedTools]
172
+ : undefined;
173
+ agent.tools.excludedTools = Array.isArray(snapshot.tools.excludedTools)
174
+ ? [...snapshot.tools.excludedTools]
175
+ : [];
176
+ if (agent.config.hasProvider) agent.tools.initializeBuiltinTools();
177
+
178
+ agent.goal.state = snapshot.goal === null
179
+ ? undefined
180
+ : copyJsonObject(snapshot.goal, undefined);
181
+ agent.turn.turnId = Number.isInteger(snapshot.turn.turnId) ? snapshot.turn.turnId : -1;
182
+ agent.turn.activeTurn = 'resuming';
183
+ agent.turn.steerBuffer.length = 0;
184
+ agent.turn.telegramDeliveryLedger.restore(snapshot.turn.telegramDeliveryIdentities);
185
+ agent.microCompaction.cutoff = Number.isInteger(snapshot.microCompaction?.cutoff)
186
+ ? Math.max(0, Math.min(snapshot.context.history.length, snapshot.microCompaction.cutoff))
187
+ : 0;
188
+
189
+ agent.toolResultBatchOffload.reset(agent.context._history);
190
+ agent.userMessageOffload.reset(agent.context._history);
191
+ agent.assistantMessageOffload.reset(agent.context._history);
192
+ agent.replayBuilder.records = agent.context._history.map((message, index) => {
193
+ const time = messageTimes[index];
194
+ return Number.isFinite(time)
195
+ ? { type: 'message', message, time }
196
+ : { type: 'message', message };
197
+ });
198
+ agent.replayBuilder.frozen = false;
199
+ agent.replayBuilder.segmentStart = 0;
200
+ return true;
201
+ } finally {
202
+ agent.records._restoring = previousRestoring;
203
+ }
204
+ }
205
+
206
+ module.exports = {
207
+ AGENT_RESUME_SNAPSHOT_SCHEMA_VERSION,
208
+ createAgentResumeSnapshot,
209
+ restoreAgentResumeSnapshot,
210
+ };
@@ -67,6 +67,22 @@ class TelegramDeliveryLedger {
67
67
  }
68
68
  return identity;
69
69
  }
70
+
71
+ snapshot() {
72
+ return [...this.identities];
73
+ }
74
+
75
+ restore(identities) {
76
+ this.identities.clear();
77
+ if (!Array.isArray(identities)) return;
78
+ for (const identity of identities) {
79
+ if (typeof identity !== 'string' || !identity || this.identities.has(identity)) continue;
80
+ this.identities.add(identity);
81
+ while (this.identities.size > this.limit) {
82
+ this.identities.delete(this.identities.values().next().value);
83
+ }
84
+ }
85
+ }
70
86
  }
71
87
 
72
88
  function repeatedUserMessageKey(message) {
@@ -0,0 +1,254 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const RESUME_CHECKPOINT_SCHEMA_VERSION = 1;
8
+ const MAX_RESUME_SNAPSHOT_BYTES = 4 * 1024 * 1024;
9
+ const MAX_RESUME_CHECKPOINT_BYTES = MAX_RESUME_SNAPSHOT_BYTES + 256 * 1024;
10
+ const WIRE_SAMPLE_BYTES = 4096;
11
+
12
+ function resumeCheckpointPath(wirePath) {
13
+ return path.join(path.dirname(wirePath), 'resume-checkpoint.json');
14
+ }
15
+
16
+ function sha256(value) {
17
+ return crypto.createHash('sha256').update(value).digest('hex');
18
+ }
19
+
20
+ function invalid(reason) {
21
+ return { ok: false, reason };
22
+ }
23
+
24
+ function isNonNegativeInteger(value) {
25
+ return Number.isSafeInteger(value) && value >= 0;
26
+ }
27
+
28
+ function sampleRanges(offset) {
29
+ if (offset === 0) return [];
30
+ const length = Math.min(WIRE_SAMPLE_BYTES, offset);
31
+ const starts = [
32
+ 0,
33
+ Math.max(0, Math.floor((offset - length) / 2)),
34
+ Math.max(0, offset - length),
35
+ ];
36
+ return [...new Set(starts)].map((start) => ({
37
+ start,
38
+ length: Math.min(length, offset - start),
39
+ }));
40
+ }
41
+
42
+ async function readExact(handle, start, length) {
43
+ const buffer = Buffer.alloc(length);
44
+ let filled = 0;
45
+ while (filled < length) {
46
+ const { bytesRead } = await handle.read(
47
+ buffer,
48
+ filled,
49
+ length - filled,
50
+ start + filled,
51
+ );
52
+ if (bytesRead === 0) break;
53
+ filled += bytesRead;
54
+ }
55
+ return filled === length ? buffer : buffer.subarray(0, filled);
56
+ }
57
+
58
+ async function captureWireSamples(handle, offset) {
59
+ const samples = [];
60
+ for (const range of sampleRanges(offset)) {
61
+ const bytes = await readExact(handle, range.start, range.length);
62
+ if (bytes.length !== range.length) {
63
+ throw new Error('wire_changed_during_checkpoint');
64
+ }
65
+ samples.push({ ...range, sha256: sha256(bytes) });
66
+ }
67
+ return samples;
68
+ }
69
+
70
+ async function writeAtomic(filePath, bytes) {
71
+ const directory = path.dirname(filePath);
72
+ const temporaryPath = `${filePath}.tmp-${process.pid}-${crypto.randomBytes(8).toString('hex')}`;
73
+ let handle;
74
+ try {
75
+ handle = await fs.promises.open(temporaryPath, 'wx', 0o600);
76
+ await handle.writeFile(bytes);
77
+ await handle.sync();
78
+ await handle.close();
79
+ handle = undefined;
80
+ await fs.promises.rename(temporaryPath, filePath);
81
+ } finally {
82
+ if (handle) await handle.close().catch(() => {});
83
+ await fs.promises.rm(temporaryPath, { force: true }).catch(() => {});
84
+ }
85
+ }
86
+
87
+ async function writeResumeCheckpoint({ wirePath, wireOffset, recordCount, snapshot }) {
88
+ if (!isNonNegativeInteger(recordCount)) {
89
+ return { written: false, reason: 'record_count_invalid' };
90
+ }
91
+ if (wireOffset !== undefined && !isNonNegativeInteger(wireOffset)) {
92
+ return { written: false, reason: 'wire_offset_invalid' };
93
+ }
94
+
95
+ let snapshotJson;
96
+ try {
97
+ snapshotJson = JSON.stringify(snapshot);
98
+ } catch {
99
+ return { written: false, reason: 'snapshot_not_serializable' };
100
+ }
101
+ if (snapshotJson === undefined) {
102
+ return { written: false, reason: 'snapshot_not_serializable' };
103
+ }
104
+ const snapshotBytes = Buffer.byteLength(snapshotJson, 'utf8');
105
+ if (snapshotBytes > MAX_RESUME_SNAPSHOT_BYTES) {
106
+ return { written: false, reason: 'snapshot_too_large' };
107
+ }
108
+
109
+ const handle = await fs.promises.open(wirePath, 'r');
110
+ let stat;
111
+ let offset;
112
+ let samples;
113
+ try {
114
+ stat = await handle.stat();
115
+ offset = wireOffset ?? stat.size;
116
+ if (offset > stat.size) {
117
+ return { written: false, reason: 'wire_truncated' };
118
+ }
119
+ if (offset > 0) {
120
+ const boundary = await readExact(handle, offset - 1, 1);
121
+ if (boundary.length !== 1 || boundary[0] !== 0x0a) {
122
+ return { written: false, reason: 'wire_not_line_terminated' };
123
+ }
124
+ }
125
+ samples = await captureWireSamples(handle, offset);
126
+ } finally {
127
+ await handle.close();
128
+ }
129
+
130
+ const payload = {
131
+ schemaVersion: RESUME_CHECKPOINT_SCHEMA_VERSION,
132
+ createdAt: new Date().toISOString(),
133
+ wire: {
134
+ offset,
135
+ recordCount,
136
+ sizeAtCheckpoint: offset,
137
+ identity: {
138
+ ino: Number.isSafeInteger(stat.ino) ? stat.ino : null,
139
+ birthtimeMs: Number.isFinite(stat.birthtimeMs) ? stat.birthtimeMs : null,
140
+ samples,
141
+ },
142
+ },
143
+ snapshot,
144
+ snapshotSha256: sha256(snapshotJson),
145
+ };
146
+ const checkpointBytes = Buffer.from(`${JSON.stringify(payload)}\n`, 'utf8');
147
+ if (checkpointBytes.length > MAX_RESUME_CHECKPOINT_BYTES) {
148
+ return { written: false, reason: 'checkpoint_too_large' };
149
+ }
150
+
151
+ await writeAtomic(resumeCheckpointPath(wirePath), checkpointBytes);
152
+ return {
153
+ written: true,
154
+ offset,
155
+ recordCount,
156
+ checkpointPath: resumeCheckpointPath(wirePath),
157
+ };
158
+ }
159
+
160
+ async function loadResumeCheckpoint({ wirePath }) {
161
+ const checkpointPath = resumeCheckpointPath(wirePath);
162
+ let checkpointStat;
163
+ try {
164
+ checkpointStat = await fs.promises.stat(checkpointPath);
165
+ } catch (error) {
166
+ return invalid(error && error.code === 'ENOENT' ? 'checkpoint_missing' : 'checkpoint_unreadable');
167
+ }
168
+ if (checkpointStat.size > MAX_RESUME_CHECKPOINT_BYTES) {
169
+ return invalid('checkpoint_too_large');
170
+ }
171
+
172
+ let checkpoint;
173
+ try {
174
+ checkpoint = JSON.parse(await fs.promises.readFile(checkpointPath, 'utf8'));
175
+ } catch (error) {
176
+ return invalid(error instanceof SyntaxError ? 'checkpoint_invalid_json' : 'checkpoint_unreadable');
177
+ }
178
+ if (!checkpoint || checkpoint.schemaVersion !== RESUME_CHECKPOINT_SCHEMA_VERSION) {
179
+ return invalid('checkpoint_schema_unsupported');
180
+ }
181
+ const wire = checkpoint.wire;
182
+ if (
183
+ !wire
184
+ || !isNonNegativeInteger(wire.offset)
185
+ || !isNonNegativeInteger(wire.recordCount)
186
+ || !wire.identity
187
+ || !Array.isArray(wire.identity.samples)
188
+ || typeof checkpoint.snapshotSha256 !== 'string'
189
+ ) {
190
+ return invalid('checkpoint_invalid_shape');
191
+ }
192
+
193
+ let snapshotJson;
194
+ try {
195
+ snapshotJson = JSON.stringify(checkpoint.snapshot);
196
+ } catch {
197
+ return invalid('checkpoint_invalid_snapshot');
198
+ }
199
+ if (snapshotJson === undefined || Buffer.byteLength(snapshotJson, 'utf8') > MAX_RESUME_SNAPSHOT_BYTES) {
200
+ return invalid('checkpoint_invalid_snapshot');
201
+ }
202
+ if (sha256(snapshotJson) !== checkpoint.snapshotSha256) {
203
+ return invalid('checkpoint_snapshot_mismatch');
204
+ }
205
+
206
+ let handle;
207
+ try {
208
+ handle = await fs.promises.open(wirePath, 'r');
209
+ const stat = await handle.stat();
210
+ if (stat.size < wire.offset) return invalid('wire_truncated');
211
+ if (wire.offset > 0) {
212
+ const boundary = await readExact(handle, wire.offset - 1, 1);
213
+ if (boundary.length !== 1 || boundary[0] !== 0x0a) {
214
+ return invalid('wire_offset_not_line_boundary');
215
+ }
216
+ }
217
+ for (const sample of wire.identity.samples) {
218
+ if (
219
+ !sample
220
+ || !isNonNegativeInteger(sample.start)
221
+ || !isNonNegativeInteger(sample.length)
222
+ || sample.start + sample.length > wire.offset
223
+ || typeof sample.sha256 !== 'string'
224
+ ) {
225
+ return invalid('checkpoint_invalid_shape');
226
+ }
227
+ const bytes = await readExact(handle, sample.start, sample.length);
228
+ if (bytes.length !== sample.length || sha256(bytes) !== sample.sha256) {
229
+ return invalid('wire_identity_mismatch');
230
+ }
231
+ }
232
+ } catch (error) {
233
+ return invalid(error && error.code === 'ENOENT' ? 'wire_missing' : 'wire_unreadable');
234
+ } finally {
235
+ if (handle) await handle.close().catch(() => {});
236
+ }
237
+
238
+ return {
239
+ ok: true,
240
+ offset: wire.offset,
241
+ recordCount: wire.recordCount,
242
+ snapshot: checkpoint.snapshot,
243
+ checkpointPath,
244
+ };
245
+ }
246
+
247
+ module.exports = {
248
+ MAX_RESUME_CHECKPOINT_BYTES,
249
+ MAX_RESUME_SNAPSHOT_BYTES,
250
+ RESUME_CHECKPOINT_SCHEMA_VERSION,
251
+ loadResumeCheckpoint,
252
+ resumeCheckpointPath,
253
+ writeResumeCheckpoint,
254
+ };
package/blun.mjs CHANGED
@@ -10,6 +10,8 @@ import cognitiveGoalAutostartPolicy from "./bin/cognitive-goal-autostart-policy.
10
10
  import cognitiveGoalTimeTriggerController from "./bin/cognitive-goal-time-trigger-controller.cjs";
11
11
  import runtimeExitLedger from "./bin/runtime-exit-ledger.cjs";
12
12
  import compactionModelPolicy from "./bin/compaction-model-policy.cjs";
13
+ import agentResumeSnapshot from "./bin/agent-resume-snapshot.cjs";
14
+ import sessionResumeCheckpoint from "./bin/session-resume-checkpoint.cjs";
13
15
  import crypto$1, { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
14
16
  import * as fs$16 from "node:fs";
15
17
  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";
@@ -44,6 +46,8 @@ const { goalAutostartDecision, goalContinuationDecision } = cognitiveGoalAutosta
44
46
  const { GoalTimeTriggerController } = cognitiveGoalTimeTriggerController;
45
47
  const { recordRuntimeExit } = runtimeExitLedger;
46
48
  const { resolveCompactionModelAlias } = compactionModelPolicy;
49
+ const { createAgentResumeSnapshot, restoreAgentResumeSnapshot } = agentResumeSnapshot;
50
+ const { loadResumeCheckpoint, writeResumeCheckpoint } = sessionResumeCheckpoint;
47
51
  import { EventEmitter as EventEmitter$1 } from "node:events";
48
52
  import { StringDecoder } from "node:string_decoder";
49
53
  import co from "node:assert";
@@ -234802,6 +234806,7 @@ var init_persistence = __esmMin((() => {
234802
234806
  filePath;
234803
234807
  options;
234804
234808
  pendingRecords = [];
234809
+ persistedRecordCount = 0;
234805
234810
  shouldClear = false;
234806
234811
  directorySynced = false;
234807
234812
  flushPromise;
@@ -234811,10 +234816,15 @@ var init_persistence = __esmMin((() => {
234811
234816
  this.options = options;
234812
234817
  }
234813
234818
  async *read() {
234819
+ yield* this.readFrom(0, 0);
234820
+ }
234821
+ async *readFrom(offset = 0, lineNumber = 0) {
234814
234822
  await this.flush();
234815
234823
  let line = "";
234816
- let lineNumber = 0;
234817
- const stream = createReadStream(this.filePath, { encoding: "utf8" });
234824
+ const stream = createReadStream(this.filePath, {
234825
+ encoding: "utf8",
234826
+ start: offset
234827
+ });
234818
234828
  try {
234819
234829
  for await (const chunk of stream) {
234820
234830
  line += chunk;
@@ -234893,6 +234903,8 @@ var init_persistence = __esmMin((() => {
234893
234903
  try {
234894
234904
  if (content.length > 0) await fh.writeFile(content, "utf8");
234895
234905
  await fh.sync();
234906
+ if (shouldClear) this.persistedRecordCount = batch.length;
234907
+ else this.persistedRecordCount += batch.length;
234896
234908
  } finally {
234897
234909
  await fh.close();
234898
234910
  }
@@ -235224,6 +235236,7 @@ var init_records = __esmMin((() => {
235224
235236
  persistence;
235225
235237
  _restoring = null;
235226
235238
  metadataInitialized = false;
235239
+ messageTimes = new WeakMap();
235227
235240
  constructor(agent, persistence) {
235228
235241
  this.agent = agent;
235229
235242
  this.persistence = persistence;
@@ -235237,6 +235250,7 @@ var init_records = __esmMin((() => {
235237
235250
  ...record,
235238
235251
  time: Date.now()
235239
235252
  };
235253
+ if (stamped.type === "context.append_message") this.messageTimes.set(stamped.message, stamped.time);
235240
235254
  if (this.persistence !== void 0 && !this.metadataInitialized && stamped.type !== "metadata") {
235241
235255
  this.persistence.append({
235242
235256
  type: "metadata",
@@ -235251,6 +235265,7 @@ var init_records = __esmMin((() => {
235251
235265
  restore(record) {
235252
235266
  this._restoring = { time: record.time ?? Date.now() };
235253
235267
  try {
235268
+ if (record.type === "context.append_message" && Number.isFinite(record.time)) this.messageTimes.set(record.message, record.time);
235254
235269
  restoreAgentRecord(this.agent, record);
235255
235270
  return this.agent.replayBuilder.finishRestoringRecord(record.type);
235256
235271
  } finally {
@@ -235260,13 +235275,16 @@ var init_records = __esmMin((() => {
235260
235275
  async replay(options = {}) {
235261
235276
  if (!this.persistence) throw new Error("No persistence provided for AgentRecords");
235262
235277
  const rewriteMigratedRecords = options.rewriteMigratedRecords ?? true;
235278
+ if (rewriteMigratedRecords && await this.tryReplayResumeCheckpoint()) return {};
235263
235279
  let migrations = [];
235264
235280
  let hasMetadata = false;
235265
235281
  let shouldRewrite = false;
235266
235282
  let warning;
235267
235283
  const replayedRecords = rewriteMigratedRecords ? [] : void 0;
235268
235284
  let completed = true;
235285
+ let replayedRecordCount = 0;
235269
235286
  for await (const record of this.persistence.read()) {
235287
+ replayedRecordCount++;
235270
235288
  if (!hasMetadata) {
235271
235289
  if (record.type !== "metadata") throw new Error("AgentRecords replay expected metadata as the first record");
235272
235290
  hasMetadata = true;
@@ -235295,9 +235313,45 @@ var init_records = __esmMin((() => {
235295
235313
  this.persistence.rewrite(replayedRecords);
235296
235314
  await this.persistence.flush();
235297
235315
  }
235316
+ if (completed && this.persistence instanceof FileSystemAgentRecordPersistence) this.persistence.persistedRecordCount = replayedRecordCount;
235298
235317
  if (completed && this.agent.blobStore !== void 0) for (const msg of this.agent.context.history) await this.agent.blobStore.rehydrateParts(msg.content);
235299
235318
  return { warning };
235300
235319
  }
235320
+ async tryReplayResumeCheckpoint() {
235321
+ if (!(this.persistence instanceof FileSystemAgentRecordPersistence)) return false;
235322
+ if (this.agent.replayBuilder.options.range !== void 0) return false;
235323
+ const checkpoint = await loadResumeCheckpoint({ wirePath: this.persistence.filePath });
235324
+ if (!checkpoint.ok) return false;
235325
+ if (!restoreAgentResumeSnapshot(this.agent, checkpoint.snapshot)) return false;
235326
+ this.metadataInitialized = true;
235327
+ let tailRecordCount = 0;
235328
+ for await (const record of this.persistence.readFrom(checkpoint.offset, checkpoint.recordCount)) {
235329
+ this.restore(record);
235330
+ tailRecordCount++;
235331
+ }
235332
+ this.persistence.persistedRecordCount = checkpoint.recordCount + tailRecordCount;
235333
+ if (this.agent.blobStore !== void 0) for (const msg of this.agent.context.history) await this.agent.blobStore.rehydrateParts(msg.content);
235334
+ return true;
235335
+ }
235336
+ async writeResumeCheckpoint() {
235337
+ if (!(this.persistence instanceof FileSystemAgentRecordPersistence)) return;
235338
+ if (this.agent.replayBuilder.options.range !== void 0) return;
235339
+ try {
235340
+ await this.flush();
235341
+ const wireOffset = statSync(this.persistence.filePath).size;
235342
+ const recordCount = this.persistence.persistedRecordCount;
235343
+ const snapshot = createAgentResumeSnapshot(this.agent);
235344
+ const result = await writeResumeCheckpoint({
235345
+ wirePath: this.persistence.filePath,
235346
+ wireOffset,
235347
+ recordCount,
235348
+ snapshot
235349
+ });
235350
+ if (!result.written) this.agent.log.warn("resume checkpoint was not written", { reason: result.reason });
235351
+ } catch (error) {
235352
+ this.agent.log.warn("resume checkpoint write failed", { error });
235353
+ }
235354
+ }
235301
235355
  async flush() {
235302
235356
  await this.persistence?.flush();
235303
235357
  }
@@ -262495,6 +262549,7 @@ var init_turn = __esmMin((() => {
262495
262549
  this.cognitiveLocalClaimsByTurn.delete(turnId);
262496
262550
  this.stepFailureByTurn.delete(turnId);
262497
262551
  await this.agent.records.flush();
262552
+ await this.agent.records.writeResumeCheckpoint();
262498
262553
  return {
262499
262554
  event: ended,
262500
262555
  stopReason: completedStopReason,
@@ -265772,6 +265827,8 @@ var init_agent = __esmMin((() => {
265772
265827
  await this.cron?.loadFromDisk();
265773
265828
  this.context.finishResume();
265774
265829
  this.turn.finishResume();
265830
+ await this.records.flush();
265831
+ await this.records.writeResumeCheckpoint();
265775
265832
  } finally {
265776
265833
  this.replayBuilder.postRestoring = false;
265777
265834
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.458",
3
+ "version": "9.1.459",
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": {