pi-observational-memory 2.4.2 → 3.0.0
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/README.md +316 -79
- package/package.json +10 -9
- package/src/agents/dropper/agent.ts +152 -0
- package/src/agents/dropper/pool.ts +67 -0
- package/src/agents/dropper/prompts.ts +43 -0
- package/src/{observer.ts → agents/observer/agent.ts} +30 -16
- package/src/agents/observer/prompts.ts +119 -0
- package/src/agents/reflector/agent.ts +134 -0
- package/src/agents/reflector/prompts.ts +77 -0
- package/src/clipboard.ts +63 -0
- package/src/commands/status.ts +79 -78
- package/src/commands/view.ts +58 -66
- package/src/config.ts +95 -16
- package/src/debug-log.ts +49 -0
- package/src/hooks/compaction-hook.ts +28 -184
- package/src/hooks/compaction-trigger.ts +35 -21
- package/src/hooks/consolidation-trigger.ts +331 -0
- package/src/index.ts +3 -3
- package/src/model-budget.ts +9 -0
- package/src/runtime.ts +46 -19
- package/src/serialize.ts +1 -1
- package/src/session-ledger/fold.ts +100 -0
- package/src/session-ledger/index.ts +6 -0
- package/src/session-ledger/progress.ts +129 -0
- package/src/session-ledger/projection.ts +220 -0
- package/src/session-ledger/recall.ts +237 -0
- package/src/session-ledger/render-summary.ts +31 -0
- package/src/session-ledger/types.ts +200 -0
- package/src/tokens.ts +1 -1
- package/src/tools/recall-observation.ts +84 -214
- package/src/branch.ts +0 -577
- package/src/compaction.ts +0 -617
- package/src/hooks/observer-trigger.ts +0 -96
- package/src/prompts.ts +0 -301
- package/src/relevance.ts +0 -15
- package/src/types.ts +0 -155
package/src/branch.ts
DELETED
|
@@ -1,577 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
OBSERVATION_CUSTOM_TYPE,
|
|
3
|
-
isObservationEntryData,
|
|
4
|
-
isReflectionRecord,
|
|
5
|
-
isSupportedMemoryDetails,
|
|
6
|
-
type MemoryReflection,
|
|
7
|
-
type ObservationEntryData,
|
|
8
|
-
type ObservationRecord,
|
|
9
|
-
type ReflectionRecord,
|
|
10
|
-
type SupportedMemoryDetails,
|
|
11
|
-
} from "./types.js";
|
|
12
|
-
import { estimateEntryTokens } from "./tokens.js";
|
|
13
|
-
|
|
14
|
-
export type Entry = {
|
|
15
|
-
type: string;
|
|
16
|
-
id: string;
|
|
17
|
-
timestamp?: string;
|
|
18
|
-
message?: unknown;
|
|
19
|
-
content?: unknown;
|
|
20
|
-
customType?: string;
|
|
21
|
-
summary?: unknown;
|
|
22
|
-
fromId?: string;
|
|
23
|
-
data?: unknown;
|
|
24
|
-
details?: unknown;
|
|
25
|
-
firstKeptEntryId?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const RAW_TYPES = new Set(["message", "custom_message", "branch_summary"]);
|
|
29
|
-
|
|
30
|
-
export function isSourceEntry(entry: Entry): boolean {
|
|
31
|
-
return RAW_TYPES.has(entry.type);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function isObservationEntry(entry: Entry): boolean {
|
|
35
|
-
return entry.type === "custom" && entry.customType === OBSERVATION_CUSTOM_TYPE;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type RecallObservationMatch =
|
|
39
|
-
| {
|
|
40
|
-
status: "ok";
|
|
41
|
-
observation: ObservationRecord;
|
|
42
|
-
observationEntryId: string;
|
|
43
|
-
sourceEntryIds: string[];
|
|
44
|
-
sourceEntries: Entry[];
|
|
45
|
-
}
|
|
46
|
-
| {
|
|
47
|
-
status: "no_source";
|
|
48
|
-
observation: ObservationRecord;
|
|
49
|
-
observationEntryId: string;
|
|
50
|
-
}
|
|
51
|
-
| {
|
|
52
|
-
status: "source_unavailable";
|
|
53
|
-
observation: ObservationRecord;
|
|
54
|
-
observationEntryId: string;
|
|
55
|
-
sourceEntryIds: string[];
|
|
56
|
-
missingSourceEntryIds: string[];
|
|
57
|
-
nonSourceEntryIds: string[];
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export type RecallObservationSourcesResult =
|
|
61
|
-
| {
|
|
62
|
-
status: "not_found";
|
|
63
|
-
observationId: string;
|
|
64
|
-
matches: [];
|
|
65
|
-
collision: false;
|
|
66
|
-
}
|
|
67
|
-
| {
|
|
68
|
-
status: "found";
|
|
69
|
-
observationId: string;
|
|
70
|
-
matches: RecallObservationMatch[];
|
|
71
|
-
collision: boolean;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
export type RecallMemoryObservation =
|
|
75
|
-
| {
|
|
76
|
-
status: "ok";
|
|
77
|
-
observation: ObservationRecord;
|
|
78
|
-
observationEntryId: string;
|
|
79
|
-
observationRecordIndex: number;
|
|
80
|
-
sourceEntryIds: string[];
|
|
81
|
-
sourceEntries: Entry[];
|
|
82
|
-
}
|
|
83
|
-
| {
|
|
84
|
-
status: "no_source";
|
|
85
|
-
observation: ObservationRecord;
|
|
86
|
-
observationEntryId: string;
|
|
87
|
-
observationRecordIndex: number;
|
|
88
|
-
}
|
|
89
|
-
| {
|
|
90
|
-
status: "source_unavailable";
|
|
91
|
-
observation: ObservationRecord;
|
|
92
|
-
observationEntryId: string;
|
|
93
|
-
observationRecordIndex: number;
|
|
94
|
-
sourceEntryIds: string[];
|
|
95
|
-
sourceEntries: Entry[];
|
|
96
|
-
missingSourceEntryIds: string[];
|
|
97
|
-
nonSourceEntryIds: string[];
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export type RecallMemoryReflectionMatch = {
|
|
101
|
-
reflection: ReflectionRecord;
|
|
102
|
-
reflectionIndex: number;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export type RecallUnavailableSupportingObservation = {
|
|
106
|
-
reflection: ReflectionRecord;
|
|
107
|
-
reflectionIndex: number;
|
|
108
|
-
observationId: string;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export type RecallUnavailableReflectionProvenance = {
|
|
112
|
-
reflection: ReflectionRecord;
|
|
113
|
-
reflectionIndex: number;
|
|
114
|
-
reason: "legacy";
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export type RecallMemorySourcesResult =
|
|
118
|
-
| {
|
|
119
|
-
status: "not_found";
|
|
120
|
-
memoryId: string;
|
|
121
|
-
reflectionMatches: [];
|
|
122
|
-
directObservationMatches: [];
|
|
123
|
-
observations: [];
|
|
124
|
-
sourceEntries: [];
|
|
125
|
-
unavailableSupportingObservations: [];
|
|
126
|
-
unavailableReflectionProvenance: [];
|
|
127
|
-
missingSourceEntryIds: [];
|
|
128
|
-
nonSourceEntryIds: [];
|
|
129
|
-
collision: false;
|
|
130
|
-
partial: false;
|
|
131
|
-
}
|
|
132
|
-
| {
|
|
133
|
-
status: "found";
|
|
134
|
-
memoryId: string;
|
|
135
|
-
reflectionMatches: RecallMemoryReflectionMatch[];
|
|
136
|
-
directObservationMatches: RecallMemoryObservation[];
|
|
137
|
-
observations: RecallMemoryObservation[];
|
|
138
|
-
sourceEntries: Entry[];
|
|
139
|
-
unavailableSupportingObservations: RecallUnavailableSupportingObservation[];
|
|
140
|
-
unavailableReflectionProvenance: RecallUnavailableReflectionProvenance[];
|
|
141
|
-
missingSourceEntryIds: string[];
|
|
142
|
-
nonSourceEntryIds: string[];
|
|
143
|
-
collision: boolean;
|
|
144
|
-
partial: boolean;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
type IndexedObservation = {
|
|
148
|
-
observation: ObservationRecord;
|
|
149
|
-
observationEntryId: string;
|
|
150
|
-
observationRecordIndex: number;
|
|
151
|
-
branchIndex: number;
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export function findLastCompactionIndex(entries: Entry[]): number {
|
|
155
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
156
|
-
if (entries[i].type === "compaction") return i;
|
|
157
|
-
}
|
|
158
|
-
return -1;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function lastObservationCoverEndIdx(entries: Entry[]): number {
|
|
162
|
-
const idToIdx = new Map<string, number>();
|
|
163
|
-
for (let i = 0; i < entries.length; i++) idToIdx.set(entries[i].id, i);
|
|
164
|
-
let maxIdx = -1;
|
|
165
|
-
for (let i = 0; i < entries.length; i++) {
|
|
166
|
-
const entry = entries[i];
|
|
167
|
-
if (!isObservationEntry(entry)) continue;
|
|
168
|
-
if (!isObservationEntryData(entry.data)) continue;
|
|
169
|
-
const coverIdx = idToIdx.get(entry.data.coversUpToId);
|
|
170
|
-
if (coverIdx !== undefined && coverIdx > maxIdx) maxIdx = coverIdx;
|
|
171
|
-
}
|
|
172
|
-
return maxIdx;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function rawTokensFromIndex(entries: Entry[], startIndex: number): number {
|
|
176
|
-
let total = 0;
|
|
177
|
-
for (let i = Math.max(0, startIndex); i < entries.length; i++) {
|
|
178
|
-
if (RAW_TYPES.has(entries[i].type)) total += estimateEntryTokens(entries[i]);
|
|
179
|
-
}
|
|
180
|
-
return total;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function rawTokensSinceLastBound(entries: Entry[]): number {
|
|
184
|
-
return rawTokensFromIndex(entries, lastObservationCoverEndIdx(entries) + 1);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function rawTokensSinceLastCompaction(entries: Entry[]): number {
|
|
188
|
-
const compactionIdx = findLastCompactionIndex(entries);
|
|
189
|
-
if (compactionIdx === -1) return rawTokensFromIndex(entries, 0);
|
|
190
|
-
return rawTokensFromIndex(entries, liveTailStartIndex(entries));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function liveTailStartIndex(entries: Entry[]): number {
|
|
194
|
-
const compactionIdx = findLastCompactionIndex(entries);
|
|
195
|
-
if (compactionIdx === -1) return 0;
|
|
196
|
-
const firstKept = entries[compactionIdx].firstKeptEntryId;
|
|
197
|
-
if (!firstKept) throw new Error("compaction entry missing firstKeptEntryId");
|
|
198
|
-
const firstKeptIdx = entries.findIndex((e) => e.id === firstKept);
|
|
199
|
-
if (firstKeptIdx === -1) throw new Error(`firstKeptEntryId "${firstKept}" not found in entries`);
|
|
200
|
-
return firstKeptIdx;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function firstRawIdAfter(entries: Entry[], afterIndex: number): string | undefined {
|
|
204
|
-
for (let i = Math.max(0, afterIndex + 1); i < entries.length; i++) {
|
|
205
|
-
if (RAW_TYPES.has(entries[i].type)) return entries[i].id;
|
|
206
|
-
}
|
|
207
|
-
return undefined;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function gapRawEntries(entries: Entry[], newFirstKeptEntryId: string): Entry[] {
|
|
211
|
-
const lastBoundIdx = lastObservationCoverEndIdx(entries);
|
|
212
|
-
const newKeptIdx = entries.findIndex((e) => e.id === newFirstKeptEntryId);
|
|
213
|
-
if (newKeptIdx === -1) return [];
|
|
214
|
-
const result: Entry[] = [];
|
|
215
|
-
for (let i = lastBoundIdx + 1; i < newKeptIdx; i++) {
|
|
216
|
-
if (RAW_TYPES.has(entries[i].type)) result.push(entries[i]);
|
|
217
|
-
}
|
|
218
|
-
return result;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function rawTailEntriesBetween(entries: Entry[], fromId: string, untilId: string): Entry[] {
|
|
222
|
-
const fromIdx = entries.findIndex((e) => e.id === fromId);
|
|
223
|
-
const untilIdx = entries.findIndex((e) => e.id === untilId);
|
|
224
|
-
if (fromIdx === -1 || untilIdx === -1 || untilIdx < fromIdx) return [];
|
|
225
|
-
|
|
226
|
-
const result: Entry[] = [];
|
|
227
|
-
for (let i = fromIdx; i <= untilIdx; i++) {
|
|
228
|
-
if (isSourceEntry(entries[i])) result.push(entries[i]);
|
|
229
|
-
}
|
|
230
|
-
return result;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function uniqueIds(ids: string[]): string[] {
|
|
234
|
-
return Array.from(new Set(ids));
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function collectIndexedObservations(entries: Entry[]): IndexedObservation[] {
|
|
238
|
-
const observations: IndexedObservation[] = [];
|
|
239
|
-
for (let branchIndex = 0; branchIndex < entries.length; branchIndex++) {
|
|
240
|
-
const entry = entries[branchIndex];
|
|
241
|
-
if (!isObservationEntry(entry)) continue;
|
|
242
|
-
if (!isObservationEntryData(entry.data)) continue;
|
|
243
|
-
entry.data.records.forEach((observation, observationRecordIndex) => {
|
|
244
|
-
observations.push({ observation, observationEntryId: entry.id, observationRecordIndex, branchIndex });
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
return observations;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function observationKey(observation: Pick<IndexedObservation, "observationEntryId" | "observationRecordIndex">): string {
|
|
251
|
-
return `${observation.observationEntryId}:${observation.observationRecordIndex}`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function resolveSourceEntries(entries: Entry[], sourceEntryIds: string[]): {
|
|
255
|
-
status: "ok" | "source_unavailable";
|
|
256
|
-
sourceEntryIds: string[];
|
|
257
|
-
sourceEntries: Entry[];
|
|
258
|
-
missingSourceEntryIds: string[];
|
|
259
|
-
nonSourceEntryIds: string[];
|
|
260
|
-
} {
|
|
261
|
-
const requested = uniqueIds(sourceEntryIds);
|
|
262
|
-
const requestedSet = new Set(requested);
|
|
263
|
-
const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
264
|
-
const missingSourceEntryIds = requested.filter((id) => !entriesById.has(id));
|
|
265
|
-
const nonSourceEntryIds = requested.filter((id) => {
|
|
266
|
-
const entry = entriesById.get(id);
|
|
267
|
-
return entry !== undefined && !isSourceEntry(entry);
|
|
268
|
-
});
|
|
269
|
-
if (missingSourceEntryIds.length > 0 || nonSourceEntryIds.length > 0) {
|
|
270
|
-
return {
|
|
271
|
-
status: "source_unavailable",
|
|
272
|
-
sourceEntryIds: requested,
|
|
273
|
-
sourceEntries: [],
|
|
274
|
-
missingSourceEntryIds,
|
|
275
|
-
nonSourceEntryIds,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const sourceEntries = entries.filter((entry) => requestedSet.has(entry.id));
|
|
280
|
-
return {
|
|
281
|
-
status: "ok",
|
|
282
|
-
sourceEntryIds: sourceEntries.map((entry) => entry.id),
|
|
283
|
-
sourceEntries,
|
|
284
|
-
missingSourceEntryIds: [],
|
|
285
|
-
nonSourceEntryIds: [],
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function resolveSourceEntriesPartial(entries: Entry[], sourceEntryIds: string[]): {
|
|
290
|
-
status: "ok" | "source_unavailable";
|
|
291
|
-
sourceEntryIds: string[];
|
|
292
|
-
sourceEntries: Entry[];
|
|
293
|
-
missingSourceEntryIds: string[];
|
|
294
|
-
nonSourceEntryIds: string[];
|
|
295
|
-
} {
|
|
296
|
-
const requested = uniqueIds(sourceEntryIds);
|
|
297
|
-
const requestedSet = new Set(requested);
|
|
298
|
-
const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
299
|
-
const missingSourceEntryIds = requested.filter((id) => !entriesById.has(id));
|
|
300
|
-
const nonSourceEntryIds = requested.filter((id) => {
|
|
301
|
-
const entry = entriesById.get(id);
|
|
302
|
-
return entry !== undefined && !isSourceEntry(entry);
|
|
303
|
-
});
|
|
304
|
-
const sourceEntries = entries.filter((entry) => requestedSet.has(entry.id) && isSourceEntry(entry));
|
|
305
|
-
return {
|
|
306
|
-
status: missingSourceEntryIds.length > 0 || nonSourceEntryIds.length > 0 ? "source_unavailable" : "ok",
|
|
307
|
-
sourceEntryIds: requested,
|
|
308
|
-
sourceEntries,
|
|
309
|
-
missingSourceEntryIds,
|
|
310
|
-
nonSourceEntryIds,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function memoryObservationFromIndexed(entries: Entry[], indexed: IndexedObservation): RecallMemoryObservation {
|
|
315
|
-
const { observation, observationEntryId, observationRecordIndex } = indexed;
|
|
316
|
-
if (!observation.sourceEntryIds || observation.sourceEntryIds.length === 0) {
|
|
317
|
-
return { status: "no_source", observation, observationEntryId, observationRecordIndex };
|
|
318
|
-
}
|
|
319
|
-
const resolved = resolveSourceEntriesPartial(entries, observation.sourceEntryIds);
|
|
320
|
-
if (resolved.status === "source_unavailable") {
|
|
321
|
-
return {
|
|
322
|
-
status: "source_unavailable",
|
|
323
|
-
observation,
|
|
324
|
-
observationEntryId,
|
|
325
|
-
observationRecordIndex,
|
|
326
|
-
sourceEntryIds: resolved.sourceEntryIds,
|
|
327
|
-
sourceEntries: resolved.sourceEntries,
|
|
328
|
-
missingSourceEntryIds: resolved.missingSourceEntryIds,
|
|
329
|
-
nonSourceEntryIds: resolved.nonSourceEntryIds,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
return {
|
|
333
|
-
status: "ok",
|
|
334
|
-
observation,
|
|
335
|
-
observationEntryId,
|
|
336
|
-
observationRecordIndex,
|
|
337
|
-
sourceEntryIds: resolved.sourceEntryIds,
|
|
338
|
-
sourceEntries: resolved.sourceEntries,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function uniqueSourceEntriesInBranchOrder(entries: Entry[], observations: RecallMemoryObservation[]): Entry[] {
|
|
343
|
-
const requested = new Set<string>();
|
|
344
|
-
for (const observation of observations) {
|
|
345
|
-
if (observation.status === "ok" || observation.status === "source_unavailable") {
|
|
346
|
-
for (const entry of observation.sourceEntries) requested.add(entry.id);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return entries.filter((entry) => requested.has(entry.id) && isSourceEntry(entry));
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function uniqueUnavailableSourceIds(
|
|
353
|
-
observations: RecallMemoryObservation[],
|
|
354
|
-
field: "missingSourceEntryIds" | "nonSourceEntryIds",
|
|
355
|
-
): string[] {
|
|
356
|
-
const ids: string[] = [];
|
|
357
|
-
for (const observation of observations) {
|
|
358
|
-
if (observation.status !== "source_unavailable") continue;
|
|
359
|
-
ids.push(...observation[field]);
|
|
360
|
-
}
|
|
361
|
-
return uniqueIds(ids);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export function recallObservationSources(entries: Entry[], observationId: string): RecallObservationSourcesResult {
|
|
365
|
-
const matches: RecallObservationMatch[] = [];
|
|
366
|
-
for (const entry of entries) {
|
|
367
|
-
if (!isObservationEntry(entry)) continue;
|
|
368
|
-
if (!isObservationEntryData(entry.data)) continue;
|
|
369
|
-
for (const observation of entry.data.records) {
|
|
370
|
-
if (observation.id !== observationId) continue;
|
|
371
|
-
if (!observation.sourceEntryIds || observation.sourceEntryIds.length === 0) {
|
|
372
|
-
matches.push({ status: "no_source", observation, observationEntryId: entry.id });
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const resolved = resolveSourceEntries(entries, observation.sourceEntryIds);
|
|
377
|
-
if (resolved.status === "source_unavailable") {
|
|
378
|
-
matches.push({
|
|
379
|
-
status: "source_unavailable",
|
|
380
|
-
observation,
|
|
381
|
-
observationEntryId: entry.id,
|
|
382
|
-
sourceEntryIds: resolved.sourceEntryIds,
|
|
383
|
-
missingSourceEntryIds: resolved.missingSourceEntryIds,
|
|
384
|
-
nonSourceEntryIds: resolved.nonSourceEntryIds,
|
|
385
|
-
});
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
matches.push({
|
|
390
|
-
status: "ok",
|
|
391
|
-
observation,
|
|
392
|
-
observationEntryId: entry.id,
|
|
393
|
-
sourceEntryIds: resolved.sourceEntryIds,
|
|
394
|
-
sourceEntries: resolved.sourceEntries,
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (matches.length === 0) return { status: "not_found", observationId, matches: [], collision: false };
|
|
400
|
-
return { status: "found", observationId, matches, collision: matches.length > 1 };
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function getPriorMemoryDetails(entries: Entry[]): SupportedMemoryDetails | undefined {
|
|
404
|
-
const idx = findLastCompactionIndex(entries);
|
|
405
|
-
if (idx === -1) return undefined;
|
|
406
|
-
const details = entries[idx].details;
|
|
407
|
-
return isSupportedMemoryDetails(details) ? details : undefined;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
export function recallMemorySources(entries: Entry[], memoryId: string): RecallMemorySourcesResult {
|
|
411
|
-
const indexedObservations = collectIndexedObservations(entries);
|
|
412
|
-
const observationsById = new Map<string, IndexedObservation[]>();
|
|
413
|
-
for (const observation of indexedObservations) {
|
|
414
|
-
const matches = observationsById.get(observation.observation.id) ?? [];
|
|
415
|
-
matches.push(observation);
|
|
416
|
-
observationsById.set(observation.observation.id, matches);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const priorDetails = getPriorMemoryDetails(entries);
|
|
420
|
-
const reflectionMatches: RecallMemoryReflectionMatch[] = [];
|
|
421
|
-
if (priorDetails) {
|
|
422
|
-
priorDetails.reflections.forEach((reflection, reflectionIndex) => {
|
|
423
|
-
if (!isReflectionRecord(reflection) || reflection.id !== memoryId) return;
|
|
424
|
-
reflectionMatches.push({ reflection, reflectionIndex });
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const directIndexedObservations = observationsById.get(memoryId) ?? [];
|
|
429
|
-
const observationsByKey = new Map<string, IndexedObservation>();
|
|
430
|
-
for (const observation of directIndexedObservations) {
|
|
431
|
-
observationsByKey.set(observationKey(observation), observation);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const unavailableSupportingObservations: RecallUnavailableSupportingObservation[] = [];
|
|
435
|
-
const unavailableReflectionProvenance: RecallUnavailableReflectionProvenance[] = [];
|
|
436
|
-
for (const reflectionMatch of reflectionMatches) {
|
|
437
|
-
if (reflectionMatch.reflection.legacy === true) {
|
|
438
|
-
unavailableReflectionProvenance.push({ ...reflectionMatch, reason: "legacy" });
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
for (const observationId of reflectionMatch.reflection.supportingObservationIds) {
|
|
442
|
-
const supportingObservations = observationsById.get(observationId);
|
|
443
|
-
if (!supportingObservations || supportingObservations.length === 0) {
|
|
444
|
-
unavailableSupportingObservations.push({ ...reflectionMatch, observationId });
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
for (const observation of supportingObservations) {
|
|
448
|
-
observationsByKey.set(observationKey(observation), observation);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const indexedObservationBag = Array.from(observationsByKey.values()).sort((a, b) => {
|
|
454
|
-
if (a.branchIndex !== b.branchIndex) return a.branchIndex - b.branchIndex;
|
|
455
|
-
return a.observationRecordIndex - b.observationRecordIndex;
|
|
456
|
-
});
|
|
457
|
-
const observations = indexedObservationBag.map((observation) => memoryObservationFromIndexed(entries, observation));
|
|
458
|
-
const directObservationKeys = new Set(directIndexedObservations.map(observationKey));
|
|
459
|
-
const directObservationMatches = observations.filter((observation) => directObservationKeys.has(observationKey(observation)));
|
|
460
|
-
const sourceEntries = uniqueSourceEntriesInBranchOrder(entries, observations);
|
|
461
|
-
const missingSourceEntryIds = uniqueUnavailableSourceIds(observations, "missingSourceEntryIds");
|
|
462
|
-
const nonSourceEntryIds = uniqueUnavailableSourceIds(observations, "nonSourceEntryIds");
|
|
463
|
-
|
|
464
|
-
if (reflectionMatches.length === 0 && directObservationMatches.length === 0) {
|
|
465
|
-
return {
|
|
466
|
-
status: "not_found",
|
|
467
|
-
memoryId,
|
|
468
|
-
reflectionMatches: [],
|
|
469
|
-
directObservationMatches: [],
|
|
470
|
-
observations: [],
|
|
471
|
-
sourceEntries: [],
|
|
472
|
-
unavailableSupportingObservations: [],
|
|
473
|
-
unavailableReflectionProvenance: [],
|
|
474
|
-
missingSourceEntryIds: [],
|
|
475
|
-
nonSourceEntryIds: [],
|
|
476
|
-
collision: false,
|
|
477
|
-
partial: false,
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const hasNoSourceObservationInMemoryRecall =
|
|
482
|
-
reflectionMatches.length > 0 && observations.some((observation) => observation.status === "no_source");
|
|
483
|
-
|
|
484
|
-
return {
|
|
485
|
-
status: "found",
|
|
486
|
-
memoryId,
|
|
487
|
-
reflectionMatches,
|
|
488
|
-
directObservationMatches,
|
|
489
|
-
observations,
|
|
490
|
-
sourceEntries,
|
|
491
|
-
unavailableSupportingObservations,
|
|
492
|
-
unavailableReflectionProvenance,
|
|
493
|
-
missingSourceEntryIds,
|
|
494
|
-
nonSourceEntryIds,
|
|
495
|
-
collision: reflectionMatches.length + directObservationMatches.length > 1,
|
|
496
|
-
partial:
|
|
497
|
-
unavailableReflectionProvenance.length > 0 ||
|
|
498
|
-
hasNoSourceObservationInMemoryRecall ||
|
|
499
|
-
unavailableSupportingObservations.length > 0 ||
|
|
500
|
-
missingSourceEntryIds.length > 0 ||
|
|
501
|
-
nonSourceEntryIds.length > 0,
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
export function collectObservationsByCoverage(
|
|
506
|
-
entries: Entry[],
|
|
507
|
-
priorFirstKeptEntryId: string | undefined,
|
|
508
|
-
newFirstKeptEntryId: string,
|
|
509
|
-
): ObservationEntryData[] {
|
|
510
|
-
const idToIdx = new Map<string, number>();
|
|
511
|
-
for (let i = 0; i < entries.length; i++) idToIdx.set(entries[i].id, i);
|
|
512
|
-
|
|
513
|
-
const newFKIIdx = idToIdx.get(newFirstKeptEntryId);
|
|
514
|
-
if (newFKIIdx === undefined) return [];
|
|
515
|
-
|
|
516
|
-
let priorFKIIdx: number;
|
|
517
|
-
if (priorFirstKeptEntryId === undefined) {
|
|
518
|
-
priorFKIIdx = -1;
|
|
519
|
-
} else {
|
|
520
|
-
const idx = idToIdx.get(priorFirstKeptEntryId);
|
|
521
|
-
if (idx === undefined) throw new Error(`priorFirstKeptEntryId "${priorFirstKeptEntryId}" not found in entries`);
|
|
522
|
-
priorFKIIdx = idx;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const result: ObservationEntryData[] = [];
|
|
526
|
-
for (const entry of entries) {
|
|
527
|
-
if (!isObservationEntry(entry)) continue;
|
|
528
|
-
if (!isObservationEntryData(entry.data)) continue;
|
|
529
|
-
const fromIdx = idToIdx.get(entry.data.coversFromId);
|
|
530
|
-
if (fromIdx === undefined) continue;
|
|
531
|
-
if (fromIdx >= priorFKIIdx && fromIdx < newFKIIdx) result.push(entry.data);
|
|
532
|
-
}
|
|
533
|
-
return result;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function collectObservationsPendingNextCompaction(entries: Entry[]): ObservationEntryData[] {
|
|
537
|
-
const idToIdx = new Map<string, number>();
|
|
538
|
-
for (let i = 0; i < entries.length; i++) idToIdx.set(entries[i].id, i);
|
|
539
|
-
|
|
540
|
-
const priorCompactionIdx = findLastCompactionIndex(entries);
|
|
541
|
-
let thresholdIdx: number;
|
|
542
|
-
if (priorCompactionIdx === -1) {
|
|
543
|
-
thresholdIdx = -1;
|
|
544
|
-
} else {
|
|
545
|
-
const priorFirstKept = entries[priorCompactionIdx].firstKeptEntryId;
|
|
546
|
-
if (!priorFirstKept) throw new Error("prior compaction entry missing firstKeptEntryId");
|
|
547
|
-
const idx = idToIdx.get(priorFirstKept);
|
|
548
|
-
if (idx === undefined) throw new Error(`prior firstKeptEntryId "${priorFirstKept}" not found in entries`);
|
|
549
|
-
thresholdIdx = idx;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const result: ObservationEntryData[] = [];
|
|
553
|
-
for (const entry of entries) {
|
|
554
|
-
if (!isObservationEntry(entry)) continue;
|
|
555
|
-
if (!isObservationEntryData(entry.data)) continue;
|
|
556
|
-
const fromIdx = idToIdx.get(entry.data.coversFromId);
|
|
557
|
-
if (fromIdx === undefined) continue;
|
|
558
|
-
if (fromIdx >= thresholdIdx) result.push(entry.data);
|
|
559
|
-
}
|
|
560
|
-
return result;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
export interface MemoryState {
|
|
564
|
-
reflections: MemoryReflection[];
|
|
565
|
-
committedObs: ObservationRecord[];
|
|
566
|
-
pendingObs: ObservationRecord[];
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
export function getMemoryState(entries: Entry[]): MemoryState {
|
|
570
|
-
const priorDetails = getPriorMemoryDetails(entries);
|
|
571
|
-
const pendingData = collectObservationsPendingNextCompaction(entries);
|
|
572
|
-
return {
|
|
573
|
-
reflections: priorDetails?.reflections ?? [],
|
|
574
|
-
committedObs: priorDetails?.observations ?? [],
|
|
575
|
-
pendingObs: pendingData.flatMap((d) => d.records),
|
|
576
|
-
};
|
|
577
|
-
}
|