@caupulican/pi-agent-core 0.93.17 → 0.93.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/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +50 -17
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +5 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +4 -0
- package/dist/agent.js.map +1 -1
- package/dist/compaction/compaction.d.ts +22 -8
- package/dist/compaction/compaction.d.ts.map +1 -1
- package/dist/compaction/compaction.js +96 -27
- package/dist/compaction/compaction.js.map +1 -1
- package/dist/provider-request-estimator.d.ts.map +1 -1
- package/dist/provider-request-estimator.js +27 -17
- package/dist/provider-request-estimator.js.map +1 -1
- package/dist/provider-request-planner.d.ts +7 -1
- package/dist/provider-request-planner.d.ts.map +1 -1
- package/dist/provider-request-planner.js +20 -1
- package/dist/provider-request-planner.js.map +1 -1
- package/dist/session/lifecycle-ledger.d.ts +173 -0
- package/dist/session/lifecycle-ledger.d.ts.map +1 -0
- package/dist/session/lifecycle-ledger.js +648 -0
- package/dist/session/lifecycle-ledger.js.map +1 -0
- package/dist/session/session-manager.d.ts +61 -5
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +487 -66
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/session-tree.d.ts +10 -0
- package/dist/session/session-tree.d.ts.map +1 -0
- package/dist/session/session-tree.js +33 -0
- package/dist/session/session-tree.js.map +1 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,27 +8,16 @@ import { StreamingLineDecoder } from "@caupulican/pi-ai/streaming-lines";
|
|
|
8
8
|
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
|
|
9
9
|
import { normalizePath, resolvePath } from "../utils/paths.js";
|
|
10
10
|
import { uuidv7 } from "../uuid.js";
|
|
11
|
+
import { encodeSessionEntry, indexSessionLifecycle, inspectSessionLifecycle, isSessionLifecycleEntry, planSessionLifecycleRepair, sessionLifecycleToolIdentityKey, validateLoadedLifecycleEntries, validateSessionLifecycleEntry, } from "./lifecycle-ledger.js";
|
|
11
12
|
import { compactToolResultDetailsForRetention } from "./message-retention.js";
|
|
12
|
-
|
|
13
|
+
import { invalidSessionParentCycle, visitSessionAncestry } from "./session-tree.js";
|
|
14
|
+
export * from "./lifecycle-ledger.js";
|
|
15
|
+
export const CURRENT_SESSION_VERSION = 4;
|
|
13
16
|
/** Maximum borrowed entries one non-copying SessionManager visit may inspect. */
|
|
14
17
|
export const MAX_SESSION_ENTRY_VISIT_COUNT = 1_024;
|
|
18
|
+
/** Maximum normal message/custom-message records in one atomic append batch. */
|
|
19
|
+
export const MAX_SESSION_MESSAGE_BATCH_ENTRIES = 256;
|
|
15
20
|
const MAX_SESSION_LINEAGE_DEPTH = 64;
|
|
16
|
-
function invalidSessionParentCycle(entryId) {
|
|
17
|
-
return new Error(`Invalid session entry graph: parent cycle detected at entry "${entryId}".`);
|
|
18
|
-
}
|
|
19
|
-
/** Visit leaf-to-root ancestry through one bounded, cycle-rejecting implementation path. */
|
|
20
|
-
function visitSessionAncestry(start, byId, visitor) {
|
|
21
|
-
let current = start;
|
|
22
|
-
// One external start plus every indexed node is the longest possible acyclic walk.
|
|
23
|
-
let remainingEntries = byId.size + 1;
|
|
24
|
-
while (current) {
|
|
25
|
-
if (remainingEntries-- === 0)
|
|
26
|
-
throw invalidSessionParentCycle(current.id);
|
|
27
|
-
if (visitor(current) === false)
|
|
28
|
-
return;
|
|
29
|
-
current = current.parentId ? byId.get(current.parentId) : undefined;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
21
|
const synthesizedSessionContextMessages = new WeakSet();
|
|
33
22
|
function retainSynthesizedSessionContextMessage(message) {
|
|
34
23
|
synthesizedSessionContextMessages.add(message);
|
|
@@ -62,6 +51,9 @@ function generateId(byId) {
|
|
|
62
51
|
// Fallback to full UUID if somehow we have collisions
|
|
63
52
|
return randomUUID();
|
|
64
53
|
}
|
|
54
|
+
function assistantToolIdentityKey(assistantMessageEntryId, callId) {
|
|
55
|
+
return JSON.stringify([assistantMessageEntryId, callId]);
|
|
56
|
+
}
|
|
65
57
|
/** Migrate v1 → v2: add id/parentId tree structure. Mutates in place. */
|
|
66
58
|
function migrateV1ToV2(entries) {
|
|
67
59
|
const ids = new Set();
|
|
@@ -104,6 +96,13 @@ function migrateV2ToV3(entries) {
|
|
|
104
96
|
}
|
|
105
97
|
}
|
|
106
98
|
}
|
|
99
|
+
/** Migrate v3 → v4: reserve the version for the typed lifecycle ledger. */
|
|
100
|
+
function migrateV3ToV4(entries) {
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
if (entry.type === "session")
|
|
103
|
+
entry.version = 4;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
107
106
|
/**
|
|
108
107
|
* Run all necessary migrations to bring entries to current version.
|
|
109
108
|
* Mutates entries in place. Returns true if any migration was applied.
|
|
@@ -117,6 +116,8 @@ function migrateToCurrentVersion(entries) {
|
|
|
117
116
|
migrateV1ToV2(entries);
|
|
118
117
|
if (version < 3)
|
|
119
118
|
migrateV2ToV3(entries);
|
|
119
|
+
if (version < 4)
|
|
120
|
+
migrateV3ToV4(entries);
|
|
120
121
|
return true;
|
|
121
122
|
}
|
|
122
123
|
/** Exported for testing */
|
|
@@ -201,11 +202,9 @@ export function buildSessionContext(entries, leafId, byId) {
|
|
|
201
202
|
compaction = entry;
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
|
-
// Build messages and collect corresponding entries
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
// 2. Emit kept messages (from firstKeptEntryId up to compaction)
|
|
208
|
-
// 3. Emit messages after compaction
|
|
205
|
+
// Build messages and collect corresponding entries. Standard compaction emits the summary followed
|
|
206
|
+
// by a contiguous recent tail. Session replacement instead retains one sparse original-user anchor
|
|
207
|
+
// before the summary, matching providers whose compactor atomically replaces the live transcript.
|
|
209
208
|
const messages = [];
|
|
210
209
|
const appendMessage = (entry) => {
|
|
211
210
|
if (entry.type === "message") {
|
|
@@ -219,19 +218,28 @@ export function buildSessionContext(entries, leafId, byId) {
|
|
|
219
218
|
}
|
|
220
219
|
};
|
|
221
220
|
if (compaction) {
|
|
222
|
-
// Emit summary first
|
|
223
|
-
messages.push(retainSynthesizedSessionContextMessage(createCompactionSummaryMessage(compaction.summary, compaction.tokensBefore, compaction.timestamp)));
|
|
224
221
|
// Find compaction index in path
|
|
225
222
|
const compactionIdx = path.findIndex((e) => e.type === "compaction" && e.id === compaction.id);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
223
|
+
const originalUserEntry = compaction.retention?.mode === "original-user"
|
|
224
|
+
? path
|
|
225
|
+
.slice(0, compactionIdx)
|
|
226
|
+
.find((entry) => entry.id === compaction.retention?.userEntryId &&
|
|
227
|
+
entry.type === "message" &&
|
|
228
|
+
entry.message.role === "user")
|
|
229
|
+
: undefined;
|
|
230
|
+
if (originalUserEntry)
|
|
231
|
+
appendMessage(originalUserEntry);
|
|
232
|
+
messages.push(retainSynthesizedSessionContextMessage(createCompactionSummaryMessage(compaction.summary, compaction.tokensBefore, compaction.timestamp)));
|
|
233
|
+
if (!compaction.retention) {
|
|
234
|
+
// Emit the standard contiguous tail before compaction, starting from firstKeptEntryId.
|
|
235
|
+
let foundFirstKept = false;
|
|
236
|
+
for (let i = 0; i < compactionIdx; i++) {
|
|
237
|
+
const entry = path[i];
|
|
238
|
+
if (entry.id === compaction.firstKeptEntryId) {
|
|
239
|
+
foundFirstKept = true;
|
|
240
|
+
}
|
|
241
|
+
if (foundFirstKept)
|
|
242
|
+
appendMessage(entry);
|
|
235
243
|
}
|
|
236
244
|
}
|
|
237
245
|
// Emit messages after compaction
|
|
@@ -360,7 +368,9 @@ function loadEntriesFromFileInternal(filePath, options, onEntry) {
|
|
|
360
368
|
}
|
|
361
369
|
/** Exported for testing */
|
|
362
370
|
export function loadEntriesFromFile(filePath, options) {
|
|
363
|
-
|
|
371
|
+
const entries = loadEntriesFromFileInternal(filePath, options).entries;
|
|
372
|
+
validateLoadedLifecycleEntries(entries);
|
|
373
|
+
return entries;
|
|
364
374
|
}
|
|
365
375
|
const ENTRY_ID_PREFIX_BYTES = 64 * 1024;
|
|
366
376
|
const COMPACTED_PAYLOAD_RELEASE_MIN_CHARS = 16 * 1024;
|
|
@@ -847,6 +857,7 @@ export class SessionManager {
|
|
|
847
857
|
this.inheritedSessionIds = collectParentSessionIds(header?.parentSession, dirname(this.sessionFile));
|
|
848
858
|
this._ensureEntryFileLocations(this.coldPayloadEntryIds);
|
|
849
859
|
const migrated = migrateToCurrentVersion(this.fileEntries);
|
|
860
|
+
validateLoadedLifecycleEntries(this.fileEntries);
|
|
850
861
|
// Validate and index the complete parent graph before a migration rewrite can
|
|
851
862
|
// mutate the source file. Malformed cycles must fail synchronously on cold open.
|
|
852
863
|
this._buildIndex();
|
|
@@ -888,6 +899,7 @@ export class SessionManager {
|
|
|
888
899
|
this.labelsById.clear();
|
|
889
900
|
this.labelTimestampsById.clear();
|
|
890
901
|
this.leafId = null;
|
|
902
|
+
this.lifecycleActiveCache = undefined;
|
|
891
903
|
this.flushed = false;
|
|
892
904
|
this.persistenceStateUncertain = false;
|
|
893
905
|
this._invalidateSessionContextCache();
|
|
@@ -960,6 +972,7 @@ export class SessionManager {
|
|
|
960
972
|
this.labelsById = labelsById;
|
|
961
973
|
this.labelTimestampsById = labelTimestampsById;
|
|
962
974
|
this.leafId = leafId;
|
|
975
|
+
this.lifecycleActiveCache = undefined;
|
|
963
976
|
for (const id of this.coldPayloadEntryIds) {
|
|
964
977
|
if (!byId.has(id))
|
|
965
978
|
this.coldPayloadEntryIds.delete(id);
|
|
@@ -981,7 +994,7 @@ export class SessionManager {
|
|
|
981
994
|
const fd = openSync(tempFile, "wx");
|
|
982
995
|
try {
|
|
983
996
|
for (const entry of this.fileEntries) {
|
|
984
|
-
writeFileSync(fd, `${JSON.stringify(entry)}\n`);
|
|
997
|
+
writeFileSync(fd, `${entry.type === "session" ? JSON.stringify(entry) : encodeSessionEntry(entry)}\n`);
|
|
985
998
|
}
|
|
986
999
|
}
|
|
987
1000
|
finally {
|
|
@@ -1120,24 +1133,42 @@ export class SessionManager {
|
|
|
1120
1133
|
const leaf = this.leafId ? this.byId.get(this.leafId) : undefined;
|
|
1121
1134
|
visitSessionAncestry(leaf, this.byId, (current) => {
|
|
1122
1135
|
if (current.type === "compaction") {
|
|
1123
|
-
this._releaseCompactedMessagePayloads(current.firstKeptEntryId, current.parentId);
|
|
1136
|
+
this._releaseCompactedMessagePayloads(current.firstKeptEntryId, current.parentId, current.retention, current.id);
|
|
1124
1137
|
return false;
|
|
1125
1138
|
}
|
|
1126
1139
|
});
|
|
1127
1140
|
}
|
|
1128
|
-
_releaseCompactedMessagePayloads(firstKeptEntryId, compactionParentId) {
|
|
1141
|
+
_releaseCompactedMessagePayloads(firstKeptEntryId, compactionParentId, retention, compactionEntryId) {
|
|
1129
1142
|
if (!this.persist || !this.flushed || !this.sessionFile)
|
|
1130
1143
|
return;
|
|
1131
1144
|
const keptEntryIds = new Set();
|
|
1132
1145
|
let foundFirstKept = false;
|
|
1133
1146
|
const compactionParent = compactionParentId ? this.byId.get(compactionParentId) : undefined;
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1147
|
+
if (retention?.mode === "original-user") {
|
|
1148
|
+
visitSessionAncestry(compactionParent, this.byId, (current) => {
|
|
1149
|
+
if (current.id === retention.userEntryId && current.type === "message" && current.message.role === "user") {
|
|
1150
|
+
keptEntryIds.add(current.id);
|
|
1151
|
+
foundFirstKept = true;
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
const leaf = this.leafId ? this.byId.get(this.leafId) : undefined;
|
|
1156
|
+
visitSessionAncestry(leaf, this.byId, (current) => {
|
|
1157
|
+
if (current.id === compactionEntryId)
|
|
1158
|
+
return false;
|
|
1159
|
+
if (current.type === "message")
|
|
1160
|
+
keptEntryIds.add(current.id);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
visitSessionAncestry(compactionParent, this.byId, (current) => {
|
|
1165
|
+
keptEntryIds.add(current.id);
|
|
1166
|
+
if (current.id === firstKeptEntryId) {
|
|
1167
|
+
foundFirstKept = true;
|
|
1168
|
+
return false;
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1141
1172
|
if (!foundFirstKept)
|
|
1142
1173
|
return;
|
|
1143
1174
|
const retainedIds = new Set();
|
|
@@ -1160,56 +1191,159 @@ export class SessionManager {
|
|
|
1160
1191
|
this._releaseMessageProperty(release.entry, property);
|
|
1161
1192
|
}
|
|
1162
1193
|
}
|
|
1163
|
-
|
|
1194
|
+
_persistEntries(entries, encodedEntries) {
|
|
1164
1195
|
if (!this.persist || !this.sessionFile)
|
|
1165
1196
|
return;
|
|
1197
|
+
if (entries.length !== encodedEntries.length) {
|
|
1198
|
+
throw new Error("Session persistence encoding count does not match the entry batch.");
|
|
1199
|
+
}
|
|
1166
1200
|
if (this.persistenceStateUncertain) {
|
|
1167
1201
|
throw new Error("Session persistence state is uncertain after a failed write; reopen the session file or start a new session before appending.");
|
|
1168
1202
|
}
|
|
1169
1203
|
try {
|
|
1170
|
-
const hasAssistant = (entry.type === "message" && entry.message.role === "assistant")
|
|
1171
|
-
|
|
1172
|
-
if (!hasAssistant) {
|
|
1173
|
-
if (this.flushed) {
|
|
1204
|
+
const hasAssistant = this.fileEntries.some((candidate) => candidate.type === "message" && candidate.message.role === "assistant") || entries.some((entry) => entry.type === "message" && entry.message.role === "assistant");
|
|
1205
|
+
const forceFlush = entries.some((entry) => isSessionLifecycleEntry(entry));
|
|
1206
|
+
if (!hasAssistant && !forceFlush) {
|
|
1207
|
+
if (this.flushed && encodedEntries.length > 0) {
|
|
1174
1208
|
this._ensureSessionFileParent(this.sessionFile);
|
|
1175
|
-
appendFileSync(this.sessionFile, `${
|
|
1209
|
+
appendFileSync(this.sessionFile, `${encodedEntries.join("\n")}\n`);
|
|
1176
1210
|
}
|
|
1177
1211
|
return;
|
|
1178
1212
|
}
|
|
1213
|
+
const encodedPayload = `${encodedEntries.join("\n")}\n`;
|
|
1179
1214
|
if (!this.flushed) {
|
|
1180
1215
|
this._ensureSessionFileParent(this.sessionFile);
|
|
1181
1216
|
const fd = openSync(this.sessionFile, "wx");
|
|
1182
1217
|
try {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
writeFileSync(fd, `${JSON.stringify(entry)}\n`);
|
|
1218
|
+
const prefix = this.fileEntries.map((candidate) => candidate.type === "session" ? JSON.stringify(candidate) : encodeSessionEntry(candidate));
|
|
1219
|
+
writeFileSync(fd, `${prefix.concat(encodedEntries).join("\n")}\n`);
|
|
1187
1220
|
}
|
|
1188
1221
|
finally {
|
|
1189
1222
|
closeSync(fd);
|
|
1190
1223
|
}
|
|
1191
1224
|
this.flushed = true;
|
|
1192
1225
|
}
|
|
1193
|
-
else {
|
|
1226
|
+
else if (encodedEntries.length > 0) {
|
|
1194
1227
|
this._ensureSessionFileParent(this.sessionFile);
|
|
1195
|
-
appendFileSync(this.sessionFile,
|
|
1228
|
+
appendFileSync(this.sessionFile, encodedPayload);
|
|
1196
1229
|
}
|
|
1197
1230
|
}
|
|
1198
1231
|
catch (error) {
|
|
1199
1232
|
// append/write/close failures may leave a partial JSONL suffix. Do not publish the
|
|
1200
|
-
//
|
|
1233
|
+
// entries in memory, and fence every later append until an explicit reload owns the
|
|
1201
1234
|
// surviving canonical prefix.
|
|
1202
1235
|
this.persistenceStateUncertain = true;
|
|
1203
1236
|
throw error;
|
|
1204
1237
|
}
|
|
1205
1238
|
}
|
|
1239
|
+
_persist(entry) {
|
|
1240
|
+
const encodedEntry = encodeSessionEntry(entry);
|
|
1241
|
+
this._persistEntries([entry], [encodedEntry]);
|
|
1242
|
+
}
|
|
1243
|
+
_appendEntries(entries, preencodedEntries) {
|
|
1244
|
+
const encodedEntries = preencodedEntries ??
|
|
1245
|
+
(this.persist && this.sessionFile
|
|
1246
|
+
? entries.map((entry) => {
|
|
1247
|
+
if (isSessionLifecycleEntry(entry))
|
|
1248
|
+
validateSessionLifecycleEntry(entry);
|
|
1249
|
+
return encodeSessionEntry(entry);
|
|
1250
|
+
})
|
|
1251
|
+
: []);
|
|
1252
|
+
if (preencodedEntries && preencodedEntries.length !== entries.length) {
|
|
1253
|
+
throw new Error("Session append encoding count does not match the entry batch.");
|
|
1254
|
+
}
|
|
1255
|
+
if (encodedEntries.length > 0) {
|
|
1256
|
+
this._persistEntries(entries, encodedEntries);
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
for (const entry of entries) {
|
|
1260
|
+
if (isSessionLifecycleEntry(entry))
|
|
1261
|
+
encodeSessionEntry(entry);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
for (const entry of entries) {
|
|
1265
|
+
this.fileEntries.push(entry);
|
|
1266
|
+
this.entries.push(entry);
|
|
1267
|
+
this.byId.set(entry.id, entry);
|
|
1268
|
+
this.leafId = entry.id;
|
|
1269
|
+
this._advanceLifecycleActiveCache(entry);
|
|
1270
|
+
this._advanceSessionContextCache(entry);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1206
1273
|
_appendEntry(entry) {
|
|
1207
|
-
this.
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
this.
|
|
1211
|
-
|
|
1212
|
-
|
|
1274
|
+
this._appendEntries([entry]);
|
|
1275
|
+
}
|
|
1276
|
+
_getLifecycleActiveCache() {
|
|
1277
|
+
if (this.lifecycleActiveCache?.leafId === this.leafId)
|
|
1278
|
+
return this.lifecycleActiveCache;
|
|
1279
|
+
const branch = [];
|
|
1280
|
+
const leaf = this.leafId === null ? undefined : this.byId.get(this.leafId);
|
|
1281
|
+
visitSessionAncestry(leaf, this.byId, (entry) => {
|
|
1282
|
+
branch.push(entry);
|
|
1283
|
+
});
|
|
1284
|
+
branch.reverse();
|
|
1285
|
+
const cache = {
|
|
1286
|
+
leafId: this.leafId,
|
|
1287
|
+
positions: new Map(),
|
|
1288
|
+
entryTypes: new Map(),
|
|
1289
|
+
requestIds: new Set(),
|
|
1290
|
+
startsByIdentity: new Set(),
|
|
1291
|
+
terminalsByIdentity: new Set(),
|
|
1292
|
+
compactionStarts: new Map(),
|
|
1293
|
+
compactionEnds: new Set(),
|
|
1294
|
+
assistantToolsByIdentity: new Map(),
|
|
1295
|
+
};
|
|
1296
|
+
for (let position = 0; position < branch.length; position += 1) {
|
|
1297
|
+
this._applyLifecycleCacheEntry(cache, branch[position], position);
|
|
1298
|
+
}
|
|
1299
|
+
this.lifecycleActiveCache = cache;
|
|
1300
|
+
return cache;
|
|
1301
|
+
}
|
|
1302
|
+
/** Apply one canonical branch entry to both rebuilt and incrementally advanced lifecycle caches. */
|
|
1303
|
+
_applyLifecycleCacheEntry(cache, entry, position) {
|
|
1304
|
+
cache.positions.set(entry.id, position);
|
|
1305
|
+
cache.entryTypes.set(entry.id, entry.type);
|
|
1306
|
+
if (entry.type === "request_snapshot") {
|
|
1307
|
+
cache.currentRequestId = entry.requestId;
|
|
1308
|
+
cache.requestIds.add(entry.requestId);
|
|
1309
|
+
}
|
|
1310
|
+
else if (entry.type === "foreground_tool_start") {
|
|
1311
|
+
cache.startsByIdentity.add(sessionLifecycleToolIdentityKey(entry.requestId, entry.assistantMessageEntryId, entry.callId));
|
|
1312
|
+
}
|
|
1313
|
+
else if (entry.type === "foreground_tool_terminal") {
|
|
1314
|
+
cache.terminalsByIdentity.add(sessionLifecycleToolIdentityKey(entry.requestId, entry.assistantMessageEntryId, entry.callId));
|
|
1315
|
+
}
|
|
1316
|
+
else if (entry.type === "compaction_start" && !cache.compactionStarts.has(entry.compactionId)) {
|
|
1317
|
+
cache.compactionStarts.set(entry.compactionId, entry);
|
|
1318
|
+
}
|
|
1319
|
+
else if (entry.type === "compaction_end") {
|
|
1320
|
+
cache.compactionEnds.add(entry.compactionId);
|
|
1321
|
+
}
|
|
1322
|
+
else if (entry.type === "message" && entry.message.role === "assistant") {
|
|
1323
|
+
for (const block of entry.message.content) {
|
|
1324
|
+
if (block.type !== "toolCall")
|
|
1325
|
+
continue;
|
|
1326
|
+
const key = assistantToolIdentityKey(entry.id, block.id);
|
|
1327
|
+
if (cache.assistantToolsByIdentity.has(key)) {
|
|
1328
|
+
cache.assistantToolsByIdentity.set(key, "duplicate");
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
cache.assistantToolsByIdentity.set(key, {
|
|
1332
|
+
requestId: cache.currentRequestId,
|
|
1333
|
+
toolName: block.name,
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
cache.leafId = entry.id;
|
|
1339
|
+
}
|
|
1340
|
+
_advanceLifecycleActiveCache(entry) {
|
|
1341
|
+
const cache = this.lifecycleActiveCache;
|
|
1342
|
+
if (!cache || cache.leafId !== entry.parentId) {
|
|
1343
|
+
this.lifecycleActiveCache = undefined;
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
this._applyLifecycleCacheEntry(cache, entry, cache.positions.size);
|
|
1213
1347
|
}
|
|
1214
1348
|
_invalidateSessionContextCache() {
|
|
1215
1349
|
this.sessionContextCache = undefined;
|
|
@@ -1262,6 +1396,63 @@ export class SessionManager {
|
|
|
1262
1396
|
this._appendEntry(entry);
|
|
1263
1397
|
return entry.id;
|
|
1264
1398
|
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Append a bounded mixed message/custom-message batch as one durable publication.
|
|
1401
|
+
* Every item is encoded before the session tree or persistence state is mutated.
|
|
1402
|
+
*/
|
|
1403
|
+
appendMessageBatch(batch) {
|
|
1404
|
+
if (batch.length === 0)
|
|
1405
|
+
return [];
|
|
1406
|
+
if (batch.length > MAX_SESSION_MESSAGE_BATCH_ENTRIES) {
|
|
1407
|
+
throw new RangeError(`Session message batch cannot contain more than ${MAX_SESSION_MESSAGE_BATCH_ENTRIES} entries.`);
|
|
1408
|
+
}
|
|
1409
|
+
const entries = [];
|
|
1410
|
+
const ids = new Set(this.byId.keys());
|
|
1411
|
+
let parentId = this.leafId;
|
|
1412
|
+
for (const item of batch) {
|
|
1413
|
+
if (!item || typeof item !== "object") {
|
|
1414
|
+
throw new TypeError("Session message batch items must be objects.");
|
|
1415
|
+
}
|
|
1416
|
+
const id = generateId(ids);
|
|
1417
|
+
let entry;
|
|
1418
|
+
if (item.kind === "message") {
|
|
1419
|
+
if (!item.message || typeof item.message !== "object") {
|
|
1420
|
+
throw new TypeError("Session message batch message items require a message object.");
|
|
1421
|
+
}
|
|
1422
|
+
entry = {
|
|
1423
|
+
type: "message",
|
|
1424
|
+
id,
|
|
1425
|
+
parentId,
|
|
1426
|
+
timestamp: new Date().toISOString(),
|
|
1427
|
+
message: item.message,
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
else if (item.kind === "custom") {
|
|
1431
|
+
if (!item.message || typeof item.message !== "object") {
|
|
1432
|
+
throw new TypeError("Session message batch custom items require a message object.");
|
|
1433
|
+
}
|
|
1434
|
+
entry = {
|
|
1435
|
+
type: "custom_message",
|
|
1436
|
+
customType: item.message.customType,
|
|
1437
|
+
content: item.message.content,
|
|
1438
|
+
display: item.message.display,
|
|
1439
|
+
details: item.message.details,
|
|
1440
|
+
id,
|
|
1441
|
+
parentId,
|
|
1442
|
+
timestamp: new Date().toISOString(),
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
throw new TypeError("Session message batch items must use kind message or custom.");
|
|
1447
|
+
}
|
|
1448
|
+
entries.push(entry);
|
|
1449
|
+
ids.add(id);
|
|
1450
|
+
parentId = id;
|
|
1451
|
+
}
|
|
1452
|
+
const encodedEntries = entries.map((entry) => encodeSessionEntry(entry));
|
|
1453
|
+
this._appendEntries(entries, encodedEntries);
|
|
1454
|
+
return entries.map((entry) => entry.id);
|
|
1455
|
+
}
|
|
1265
1456
|
/** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */
|
|
1266
1457
|
appendThinkingLevelChange(thinkingLevel) {
|
|
1267
1458
|
const entry = {
|
|
@@ -1288,7 +1479,7 @@ export class SessionManager {
|
|
|
1288
1479
|
return entry.id;
|
|
1289
1480
|
}
|
|
1290
1481
|
/** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */
|
|
1291
|
-
appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook, usage) {
|
|
1482
|
+
appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook, usage, retention) {
|
|
1292
1483
|
const compactionParentId = this.leafId;
|
|
1293
1484
|
const entry = {
|
|
1294
1485
|
type: "compaction",
|
|
@@ -1298,15 +1489,229 @@ export class SessionManager {
|
|
|
1298
1489
|
summary,
|
|
1299
1490
|
firstKeptEntryId,
|
|
1300
1491
|
tokensBefore,
|
|
1492
|
+
retention,
|
|
1301
1493
|
details,
|
|
1302
1494
|
usage,
|
|
1303
1495
|
fromHook,
|
|
1304
1496
|
};
|
|
1305
1497
|
this._appendEntry(entry);
|
|
1306
|
-
this._releaseCompactedMessagePayloads(firstKeptEntryId, compactionParentId);
|
|
1498
|
+
this._releaseCompactedMessagePayloads(firstKeptEntryId, compactionParentId, retention, entry.id);
|
|
1307
1499
|
return entry.id;
|
|
1308
1500
|
}
|
|
1309
|
-
|
|
1501
|
+
_findAssistantToolCall(assistantMessageEntryId, callId) {
|
|
1502
|
+
const cache = this._getLifecycleActiveCache();
|
|
1503
|
+
if (!cache.positions.has(assistantMessageEntryId)) {
|
|
1504
|
+
throw new Error(`Assistant message is outside the active branch: ${assistantMessageEntryId}.`);
|
|
1505
|
+
}
|
|
1506
|
+
const assistant = this.byId.get(assistantMessageEntryId);
|
|
1507
|
+
if (!assistant || assistant.type !== "message") {
|
|
1508
|
+
throw new Error(`Foreground tool lifecycle identity requires an assistant message: ${assistantMessageEntryId}.`);
|
|
1509
|
+
}
|
|
1510
|
+
const reference = cache.assistantToolsByIdentity.get(assistantToolIdentityKey(assistantMessageEntryId, callId));
|
|
1511
|
+
if (reference === "duplicate") {
|
|
1512
|
+
throw new Error(`Foreground tool lifecycle identity must resolve to exactly one assistant tool call: ${assistantMessageEntryId}/${callId}.`);
|
|
1513
|
+
}
|
|
1514
|
+
if (!reference) {
|
|
1515
|
+
throw new Error(`Foreground tool lifecycle identity must resolve to exactly one assistant tool call: ${assistantMessageEntryId}/${callId}.`);
|
|
1516
|
+
}
|
|
1517
|
+
return reference;
|
|
1518
|
+
}
|
|
1519
|
+
_validateRequestSnapshot(entry) {
|
|
1520
|
+
const cache = this._getLifecycleActiveCache();
|
|
1521
|
+
if (cache.requestIds.has(entry.requestId)) {
|
|
1522
|
+
throw new Error(`Duplicate request snapshot identity: ${entry.requestId}.`);
|
|
1523
|
+
}
|
|
1524
|
+
for (const messageEntryId of entry.messageEntryIds) {
|
|
1525
|
+
const messageEntry = this.byId.get(messageEntryId);
|
|
1526
|
+
if (!messageEntry || messageEntry.type !== "message") {
|
|
1527
|
+
throw new Error(`Request snapshot references a non-message entry: ${messageEntryId}.`);
|
|
1528
|
+
}
|
|
1529
|
+
if (!cache.positions.has(messageEntryId)) {
|
|
1530
|
+
throw new Error(`Request snapshot message is outside the active branch: ${messageEntryId}.`);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
_validateForegroundToolStartBatch(entries) {
|
|
1535
|
+
const cache = this._getLifecycleActiveCache();
|
|
1536
|
+
const seen = new Set();
|
|
1537
|
+
for (const entry of entries) {
|
|
1538
|
+
validateSessionLifecycleEntry(entry);
|
|
1539
|
+
const call = this._findAssistantToolCall(entry.assistantMessageEntryId, entry.callId);
|
|
1540
|
+
if (call.requestId !== undefined && call.requestId !== entry.requestId) {
|
|
1541
|
+
throw new Error(`Foreground tool start request does not match its assistant request: ${entry.id}.`);
|
|
1542
|
+
}
|
|
1543
|
+
const key = sessionLifecycleToolIdentityKey(entry.requestId, entry.assistantMessageEntryId, entry.callId);
|
|
1544
|
+
if (seen.has(key) || cache.startsByIdentity.has(key)) {
|
|
1545
|
+
throw new Error(`Duplicate foreground tool start identity: ${entry.requestId}/${entry.assistantMessageEntryId}/${entry.callId}.`);
|
|
1546
|
+
}
|
|
1547
|
+
seen.add(key);
|
|
1548
|
+
if (call.toolName !== entry.toolName) {
|
|
1549
|
+
throw new Error(`Foreground tool start name does not match its assistant call: ${entry.id}.`);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
_validateForegroundToolTerminal(entry) {
|
|
1554
|
+
validateSessionLifecycleEntry(entry);
|
|
1555
|
+
const call = this._findAssistantToolCall(entry.assistantMessageEntryId, entry.callId);
|
|
1556
|
+
if (call.requestId !== undefined && call.requestId !== entry.requestId) {
|
|
1557
|
+
throw new Error(`Foreground tool terminal request does not match its assistant request: ${entry.id}.`);
|
|
1558
|
+
}
|
|
1559
|
+
if (call.toolName !== entry.toolName) {
|
|
1560
|
+
throw new Error(`Foreground tool terminal name does not match its assistant call: ${entry.id}.`);
|
|
1561
|
+
}
|
|
1562
|
+
const cache = this._getLifecycleActiveCache();
|
|
1563
|
+
const identityKey = sessionLifecycleToolIdentityKey(entry.requestId, entry.assistantMessageEntryId, entry.callId);
|
|
1564
|
+
if (cache.terminalsByIdentity.has(identityKey)) {
|
|
1565
|
+
throw new Error(`Duplicate foreground tool terminal identity: ${entry.id}.`);
|
|
1566
|
+
}
|
|
1567
|
+
if (!cache.startsByIdentity.has(identityKey)) {
|
|
1568
|
+
throw new Error(`Foreground tool terminal has no matching durable start: ${entry.id}.`);
|
|
1569
|
+
}
|
|
1570
|
+
const resultEntry = this.byId.get(entry.resultMessageEntryId);
|
|
1571
|
+
if (!resultEntry ||
|
|
1572
|
+
!cache.positions.has(entry.resultMessageEntryId) ||
|
|
1573
|
+
resultEntry.type !== "message" ||
|
|
1574
|
+
resultEntry.message.role !== "toolResult") {
|
|
1575
|
+
throw new Error(`Foreground tool terminal references a non-result entry: ${entry.resultMessageEntryId}.`);
|
|
1576
|
+
}
|
|
1577
|
+
const resultErrorKind = resultEntry.message.isError
|
|
1578
|
+
? (resultEntry.message.errorKind ?? "tool_failure")
|
|
1579
|
+
: undefined;
|
|
1580
|
+
if (resultEntry.message.toolCallId !== entry.callId ||
|
|
1581
|
+
resultEntry.message.toolName !== entry.toolName ||
|
|
1582
|
+
entry.outcome !== (resultEntry.message.isError ? "error" : "success") ||
|
|
1583
|
+
(resultEntry.message.errorKind !== undefined && !resultEntry.message.isError) ||
|
|
1584
|
+
entry.errorKind !== resultErrorKind) {
|
|
1585
|
+
throw new Error(`Foreground tool terminal metadata contradicts its canonical result: ${entry.id}.`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
_validateCompactionStart(entry) {
|
|
1589
|
+
validateSessionLifecycleEntry(entry);
|
|
1590
|
+
const cache = this._getLifecycleActiveCache();
|
|
1591
|
+
if (cache.compactionStarts.has(entry.compactionId)) {
|
|
1592
|
+
throw new Error(`Duplicate compaction start identity: ${entry.compactionId}.`);
|
|
1593
|
+
}
|
|
1594
|
+
const firstKeptPosition = cache.positions.get(entry.firstKeptEntryId);
|
|
1595
|
+
const leafPosition = this.leafId === null ? undefined : cache.positions.get(this.leafId);
|
|
1596
|
+
if (firstKeptPosition === undefined || leafPosition === undefined || firstKeptPosition > leafPosition) {
|
|
1597
|
+
throw new Error(`Compaction start firstKeptEntryId is not an earlier active-branch entry: ${entry.firstKeptEntryId}.`);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
_validateCompactionEnd(entry) {
|
|
1601
|
+
validateSessionLifecycleEntry(entry);
|
|
1602
|
+
const cache = this._getLifecycleActiveCache();
|
|
1603
|
+
const start = cache.compactionStarts.get(entry.compactionId);
|
|
1604
|
+
if (!start)
|
|
1605
|
+
throw new Error(`Compaction end has no matching start: ${entry.compactionId}.`);
|
|
1606
|
+
if (cache.compactionEnds.has(entry.compactionId)) {
|
|
1607
|
+
throw new Error(`Duplicate compaction end identity: ${entry.compactionId}.`);
|
|
1608
|
+
}
|
|
1609
|
+
if (entry.outcome !== "success")
|
|
1610
|
+
return;
|
|
1611
|
+
const compactionPosition = entry.compactionEntryId ? cache.positions.get(entry.compactionEntryId) : undefined;
|
|
1612
|
+
const compactionEntry = entry.compactionEntryId ? this.byId.get(entry.compactionEntryId) : undefined;
|
|
1613
|
+
const finalFirstKeptPosition = compactionEntry?.type === "compaction" ? cache.positions.get(compactionEntry.firstKeptEntryId) : undefined;
|
|
1614
|
+
if (!entry.compactionEntryId ||
|
|
1615
|
+
cache.entryTypes.get(entry.compactionEntryId) !== "compaction" ||
|
|
1616
|
+
compactionPosition === undefined ||
|
|
1617
|
+
compactionPosition <= (cache.positions.get(start.id) ?? -1) ||
|
|
1618
|
+
compactionEntry?.type !== "compaction" ||
|
|
1619
|
+
finalFirstKeptPosition === undefined ||
|
|
1620
|
+
finalFirstKeptPosition >= compactionPosition) {
|
|
1621
|
+
throw new Error(`Compaction end references an invalid summary entry: ${entry.compactionEntryId ?? "missing"}.`);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
/** Append a bounded provider request snapshot before its transport starts. */
|
|
1625
|
+
appendRequestSnapshot(snapshot) {
|
|
1626
|
+
const entry = {
|
|
1627
|
+
type: "request_snapshot",
|
|
1628
|
+
id: generateId(this.byId),
|
|
1629
|
+
parentId: this.leafId,
|
|
1630
|
+
timestamp: new Date().toISOString(),
|
|
1631
|
+
...snapshot,
|
|
1632
|
+
messageEntryIds: [...snapshot.messageEntryIds],
|
|
1633
|
+
};
|
|
1634
|
+
validateSessionLifecycleEntry(entry);
|
|
1635
|
+
this._validateRequestSnapshot(entry);
|
|
1636
|
+
this._appendEntry(entry);
|
|
1637
|
+
return entry.id;
|
|
1638
|
+
}
|
|
1639
|
+
/** Append a bounded batch of foreground tool execution start markers in one durable publication. */
|
|
1640
|
+
appendForegroundToolStarts(starts) {
|
|
1641
|
+
if (starts.length === 0)
|
|
1642
|
+
return [];
|
|
1643
|
+
const entries = [];
|
|
1644
|
+
let parentId = this.leafId;
|
|
1645
|
+
const ids = new Set(this.byId.keys());
|
|
1646
|
+
for (const start of starts) {
|
|
1647
|
+
const entry = {
|
|
1648
|
+
type: "foreground_tool_start",
|
|
1649
|
+
id: generateId(ids),
|
|
1650
|
+
parentId,
|
|
1651
|
+
timestamp: new Date().toISOString(),
|
|
1652
|
+
...start,
|
|
1653
|
+
};
|
|
1654
|
+
entries.push(entry);
|
|
1655
|
+
ids.add(entry.id);
|
|
1656
|
+
parentId = entry.id;
|
|
1657
|
+
}
|
|
1658
|
+
this._validateForegroundToolStartBatch(entries);
|
|
1659
|
+
this._appendEntries(entries);
|
|
1660
|
+
return entries.map((entry) => entry.id);
|
|
1661
|
+
}
|
|
1662
|
+
/** Append one bounded foreground tool execution start marker. */
|
|
1663
|
+
appendForegroundToolStart(requestId, assistantMessageEntryId, callId, toolName) {
|
|
1664
|
+
return this.appendForegroundToolStarts([{ requestId, assistantMessageEntryId, callId, toolName }])[0];
|
|
1665
|
+
}
|
|
1666
|
+
/** Append a bounded foreground tool terminal marker. */
|
|
1667
|
+
appendForegroundToolTerminal(requestId, assistantMessageEntryId, callId, toolName, outcome, metadata) {
|
|
1668
|
+
const entry = {
|
|
1669
|
+
type: "foreground_tool_terminal",
|
|
1670
|
+
id: generateId(this.byId),
|
|
1671
|
+
parentId: this.leafId,
|
|
1672
|
+
timestamp: new Date().toISOString(),
|
|
1673
|
+
requestId,
|
|
1674
|
+
assistantMessageEntryId,
|
|
1675
|
+
callId,
|
|
1676
|
+
toolName,
|
|
1677
|
+
outcome,
|
|
1678
|
+
...metadata,
|
|
1679
|
+
};
|
|
1680
|
+
this._validateForegroundToolTerminal(entry);
|
|
1681
|
+
this._appendEntry(entry);
|
|
1682
|
+
return entry.id;
|
|
1683
|
+
}
|
|
1684
|
+
/** Append a bounded compaction transaction start marker. */
|
|
1685
|
+
appendCompactionStart(compactionId, firstKeptEntryId, tokensBefore) {
|
|
1686
|
+
const entry = {
|
|
1687
|
+
type: "compaction_start",
|
|
1688
|
+
id: generateId(this.byId),
|
|
1689
|
+
parentId: this.leafId,
|
|
1690
|
+
timestamp: new Date().toISOString(),
|
|
1691
|
+
compactionId,
|
|
1692
|
+
firstKeptEntryId,
|
|
1693
|
+
tokensBefore,
|
|
1694
|
+
};
|
|
1695
|
+
this._validateCompactionStart(entry);
|
|
1696
|
+
this._appendEntry(entry);
|
|
1697
|
+
return entry.id;
|
|
1698
|
+
}
|
|
1699
|
+
/** Append a bounded compaction transaction end marker. */
|
|
1700
|
+
appendCompactionEnd(compactionId, outcome, metadata = {}) {
|
|
1701
|
+
const entry = {
|
|
1702
|
+
type: "compaction_end",
|
|
1703
|
+
id: generateId(this.byId),
|
|
1704
|
+
parentId: this.leafId,
|
|
1705
|
+
timestamp: new Date().toISOString(),
|
|
1706
|
+
compactionId,
|
|
1707
|
+
outcome,
|
|
1708
|
+
...metadata,
|
|
1709
|
+
};
|
|
1710
|
+
this._validateCompactionEnd(entry);
|
|
1711
|
+
this._appendEntry(entry);
|
|
1712
|
+
return entry.id;
|
|
1713
|
+
}
|
|
1714
|
+
/** Append a custom entry (for extensions) as child of current leaf, then advance leaf. */
|
|
1310
1715
|
appendCustomEntry(customType, data) {
|
|
1311
1716
|
const entry = {
|
|
1312
1717
|
type: "custom",
|
|
@@ -1344,6 +1749,18 @@ export class SessionManager {
|
|
|
1344
1749
|
}
|
|
1345
1750
|
return undefined;
|
|
1346
1751
|
}
|
|
1752
|
+
/** Return lifecycle records on one branch without exposing lifecycle data to model context. */
|
|
1753
|
+
getSessionLifecycleIndex(fromId) {
|
|
1754
|
+
return indexSessionLifecycle(this.entries, fromId === undefined ? this.leafId : fromId);
|
|
1755
|
+
}
|
|
1756
|
+
/** Inspect lifecycle balance on one branch without mutating or appending repair records. */
|
|
1757
|
+
inspectSessionLifecycle(fromId) {
|
|
1758
|
+
return inspectSessionLifecycle(this.entries, fromId === undefined ? this.leafId : fromId);
|
|
1759
|
+
}
|
|
1760
|
+
/** Plan deterministic lifecycle repair for one branch without mutating the session. */
|
|
1761
|
+
planSessionLifecycleRepair(fromId) {
|
|
1762
|
+
return planSessionLifecycleRepair(this.entries, fromId === undefined ? this.leafId : fromId);
|
|
1763
|
+
}
|
|
1347
1764
|
/**
|
|
1348
1765
|
* Append a custom message entry (for extensions) that participates in LLM context.
|
|
1349
1766
|
* @param customType Extension identifier for filtering on reload
|
|
@@ -1716,6 +2133,7 @@ export class SessionManager {
|
|
|
1716
2133
|
}
|
|
1717
2134
|
this._invalidateSessionContextCache();
|
|
1718
2135
|
this.leafId = branchFromId;
|
|
2136
|
+
this.lifecycleActiveCache = undefined;
|
|
1719
2137
|
}
|
|
1720
2138
|
/**
|
|
1721
2139
|
* Reset the leaf pointer to null (before any entries).
|
|
@@ -1725,6 +2143,7 @@ export class SessionManager {
|
|
|
1725
2143
|
resetLeaf() {
|
|
1726
2144
|
this._invalidateSessionContextCache();
|
|
1727
2145
|
this.leafId = null;
|
|
2146
|
+
this.lifecycleActiveCache = undefined;
|
|
1728
2147
|
}
|
|
1729
2148
|
/**
|
|
1730
2149
|
* Start a new branch with a summary of the abandoned path.
|
|
@@ -1737,6 +2156,7 @@ export class SessionManager {
|
|
|
1737
2156
|
}
|
|
1738
2157
|
this._invalidateSessionContextCache();
|
|
1739
2158
|
this.leafId = branchFromId;
|
|
2159
|
+
this.lifecycleActiveCache = undefined;
|
|
1740
2160
|
const entry = {
|
|
1741
2161
|
type: "branch_summary",
|
|
1742
2162
|
id: generateId(this.byId),
|
|
@@ -1843,6 +2263,7 @@ export class SessionManager {
|
|
|
1843
2263
|
this.labelsById = branched.labelsById;
|
|
1844
2264
|
this.labelTimestampsById = branched.labelTimestampsById;
|
|
1845
2265
|
this.leafId = branched.leafId;
|
|
2266
|
+
this.lifecycleActiveCache = undefined;
|
|
1846
2267
|
this.persistenceStateUncertain = branched.persistenceStateUncertain;
|
|
1847
2268
|
this.inheritedSessionIds = new Set(branched.inheritedSessionIds);
|
|
1848
2269
|
this.coldPayloadEntryIds.clear();
|