pi-blackhole 0.4.2 → 0.4.3
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 +15 -0
- package/dist/index.js +11660 -0
- package/dist/index.js.map +1 -0
- package/example-config.json +1 -1
- package/index.ts +37 -63
- package/package.json +21 -9
- package/src/commands/cleanup.ts +279 -240
- package/src/commands/memory.ts +236 -184
- package/src/commands/pi-vcc.ts +202 -152
- package/src/commands/vcc-recall.ts +126 -95
- package/src/core/brief.ts +167 -33
- package/src/core/build-sections.ts +8 -2
- package/src/core/config-env.ts +117 -0
- package/src/core/content.ts +31 -7
- package/src/core/drill-down.ts +41 -11
- package/src/core/filter-noise.ts +9 -3
- package/src/core/format-recall.ts +15 -6
- package/src/core/format.ts +14 -4
- package/src/core/lineage.ts +9 -3
- package/src/core/load-messages.ts +24 -5
- package/src/core/normalize.ts +38 -14
- package/src/core/recall-scope.ts +11 -3
- package/src/core/render-entries.ts +22 -6
- package/src/core/sanitize.ts +5 -1
- package/src/core/search-entries.ts +111 -19
- package/src/core/settings.ts +1 -3
- package/src/core/summarize.ts +42 -21
- package/src/core/unified-config.ts +549 -411
- package/src/extract/commits.ts +4 -2
- package/src/extract/files.ts +10 -5
- package/src/extract/goals.ts +7 -2
- package/src/hooks/before-compact.ts +210 -88
- package/src/om/agents/dropper/agent.ts +380 -265
- package/src/om/agents/dropper/coverage.ts +102 -82
- package/src/om/agents/observer/agent.ts +242 -206
- package/src/om/agents/reflector/agent.ts +212 -153
- package/src/om/cleanup.ts +239 -218
- package/src/om/clipboard.ts +59 -51
- package/src/om/compaction-trigger.ts +448 -333
- package/src/om/config.ts +13 -6
- package/src/om/configure-overlay.ts +518 -355
- package/src/om/consolidation.ts +1460 -953
- package/src/om/cooldown.ts +75 -65
- package/src/om/debug-log.ts +86 -68
- package/src/om/ids.ts +1 -1
- package/src/om/ledger/fold.ts +89 -78
- package/src/om/ledger/progress.ts +181 -153
- package/src/om/ledger/projection.ts +248 -185
- package/src/om/ledger/recall.ts +247 -196
- package/src/om/ledger/render-summary.ts +79 -50
- package/src/om/ledger/types.ts +146 -117
- package/src/om/model-budget.ts +23 -13
- package/src/om/pending.ts +243 -179
- package/src/om/provider-stream.ts +52 -7
- package/src/om/retryable-error.ts +12 -16
- package/src/om/reverse-recall.ts +97 -91
- package/src/om/runtime.ts +474 -375
- package/src/om/serialize.ts +190 -166
- package/src/om/status-overlay.ts +246 -195
- package/src/om/tokens.ts +28 -21
- package/src/pi-base/blackhole-settings.ts +437 -0
- package/src/pi-base/config-manager.ts +440 -0
- package/src/pi-base/config.ts +469 -0
- package/src/pi-base/env.ts +43 -0
- package/src/pi-base/paths.ts +47 -0
- package/src/pi-base/settings/body.ts +1648 -0
- package/src/pi-base/settings/fields/action.ts +43 -0
- package/src/pi-base/settings/fields/boolean.ts +47 -0
- package/src/pi-base/settings/fields/custom.ts +72 -0
- package/src/pi-base/settings/fields/enum.ts +310 -0
- package/src/pi-base/settings/fields/index.ts +46 -0
- package/src/pi-base/settings/fields/model.ts +452 -0
- package/src/pi-base/settings/fields/string.ts +527 -0
- package/src/pi-base/settings/fields/text.ts +115 -0
- package/src/pi-base/settings/frame.ts +197 -0
- package/src/pi-base/settings/index.ts +77 -0
- package/src/pi-base/settings/inline-edit.ts +313 -0
- package/src/pi-base/settings/modal.ts +152 -0
- package/src/pi-base/settings/types.ts +500 -0
- package/src/pi-base/settings/validate-field.ts +113 -0
- package/src/pi-base/shell.ts +117 -0
- package/src/pi-base/types.ts +6 -0
- package/src/pi-base/ui.ts +32 -0
- package/src/tools/recall.ts +347 -225
- package/src/types.ts +20 -3
- package/tsup.config.ts +23 -0
- package/vitest.config.ts +15 -15
package/src/om/ledger/recall.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* Unmodified.
|
|
6
6
|
*/
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
isObservationsDroppedEntry,
|
|
9
|
+
isObservationsRecordedEntry,
|
|
10
|
+
isReflectionsRecordedEntry,
|
|
11
|
+
type Entry,
|
|
12
|
+
type Observation,
|
|
13
|
+
type Reflection,
|
|
14
14
|
} from "./types.js";
|
|
15
15
|
|
|
16
16
|
const SOURCE_TYPES = new Set(["message", "custom_message", "branch_summary"]);
|
|
@@ -18,226 +18,277 @@ const SOURCE_TYPES = new Set(["message", "custom_message", "branch_summary"]);
|
|
|
18
18
|
export type { Entry, Observation, Reflection };
|
|
19
19
|
|
|
20
20
|
type ObservationLedgerLocation = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
entryId: string;
|
|
22
|
+
entryIndex: number;
|
|
23
|
+
recordIndex: number;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
type ReflectionLedgerLocation = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
entryId: string;
|
|
28
|
+
entryIndex: number;
|
|
29
|
+
recordIndex: number;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export type RecalledObservation = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
observation: Observation;
|
|
34
|
+
observationEntryId: string;
|
|
35
|
+
observationRecordIndex: number;
|
|
36
|
+
status: "active" | "dropped";
|
|
37
|
+
sourceEntryIds: string[];
|
|
38
|
+
sourceEntries: Entry[];
|
|
39
|
+
missingSourceEntryIds: string[];
|
|
40
|
+
nonSourceEntryIds: string[];
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
export type RecalledReflection = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
reflection: Reflection;
|
|
45
|
+
reflectionEntryId: string;
|
|
46
|
+
reflectionRecordIndex: number;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
export type RecallResult =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
type IndexedObservation = ObservationLedgerLocation & {
|
|
50
|
+
| {
|
|
51
|
+
status: "not_found";
|
|
52
|
+
memoryId: string;
|
|
53
|
+
kind: undefined;
|
|
54
|
+
reflections: [];
|
|
55
|
+
observations: [];
|
|
56
|
+
sourceEntries: [];
|
|
57
|
+
missingSourceEntryIds: [];
|
|
58
|
+
nonSourceEntryIds: [];
|
|
59
|
+
missingSupportingObservationIds: [];
|
|
60
|
+
collision: false;
|
|
61
|
+
partial: false;
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
status: "found";
|
|
65
|
+
memoryId: string;
|
|
66
|
+
kind: "observation" | "reflection" | "mixed";
|
|
67
|
+
reflections: RecalledReflection[];
|
|
68
|
+
observations: RecalledObservation[];
|
|
69
|
+
sourceEntries: Entry[];
|
|
70
|
+
missingSourceEntryIds: string[];
|
|
71
|
+
nonSourceEntryIds: string[];
|
|
72
|
+
missingSupportingObservationIds: string[];
|
|
73
|
+
collision: boolean;
|
|
74
|
+
partial: boolean;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type IndexedObservation = ObservationLedgerLocation & {
|
|
78
|
+
observation: Observation;
|
|
79
|
+
};
|
|
78
80
|
type IndexedReflection = ReflectionLedgerLocation & { reflection: Reflection };
|
|
79
81
|
|
|
80
82
|
function isSourceEntry(entry: Entry): boolean {
|
|
81
|
-
|
|
83
|
+
return SOURCE_TYPES.has(entry.type);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
function uniqueById(entries: Entry[]): Entry[] {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
const seen = new Set<string>();
|
|
88
|
+
const result: Entry[] = [];
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (seen.has(entry.id)) continue;
|
|
91
|
+
seen.add(entry.id);
|
|
92
|
+
result.push(entry);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
function uniqueStrings(values: string[]): string[] {
|
|
96
|
-
|
|
98
|
+
return Array.from(new Set(values));
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
export function indexLedger(entries: Entry[]): {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
observations: IndexedObservation[];
|
|
103
|
+
reflections: IndexedReflection[];
|
|
104
|
+
droppedIds: Set<string>;
|
|
103
105
|
} {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
106
|
+
const observations: IndexedObservation[] = [];
|
|
107
|
+
const reflections: IndexedReflection[] = [];
|
|
108
|
+
const droppedIds = new Set<string>();
|
|
109
|
+
|
|
110
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
|
111
|
+
const entry = entries[entryIndex];
|
|
112
|
+
if (isObservationsRecordedEntry(entry)) {
|
|
113
|
+
entry.data.observations.forEach((observation, recordIndex) => {
|
|
114
|
+
observations.push({
|
|
115
|
+
observation,
|
|
116
|
+
entryId: entry.id,
|
|
117
|
+
entryIndex,
|
|
118
|
+
recordIndex,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (isReflectionsRecordedEntry(entry)) {
|
|
124
|
+
entry.data.reflections.forEach((reflection, recordIndex) => {
|
|
125
|
+
reflections.push({
|
|
126
|
+
reflection,
|
|
127
|
+
entryId: entry.id,
|
|
128
|
+
entryIndex,
|
|
129
|
+
recordIndex,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (isObservationsDroppedEntry(entry)) {
|
|
135
|
+
entry.data.observationIds.forEach((id) => droppedIds.add(id));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { observations, reflections, droppedIds };
|
|
128
140
|
}
|
|
129
141
|
|
|
130
|
-
function resolveObservationSources(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
function resolveObservationSources(
|
|
143
|
+
entries: Entry[],
|
|
144
|
+
observation: Observation,
|
|
145
|
+
location: ObservationLedgerLocation,
|
|
146
|
+
): RecalledObservation {
|
|
147
|
+
const sourceEntryIds = uniqueStrings(observation.sourceEntryIds);
|
|
148
|
+
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
149
|
+
const sourceEntries: Entry[] = [];
|
|
150
|
+
const missingSourceEntryIds: string[] = [];
|
|
151
|
+
const nonSourceEntryIds: string[] = [];
|
|
152
|
+
|
|
153
|
+
for (const sourceEntryId of sourceEntryIds) {
|
|
154
|
+
const sourceEntry = byId.get(sourceEntryId);
|
|
155
|
+
if (!sourceEntry) {
|
|
156
|
+
missingSourceEntryIds.push(sourceEntryId);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!isSourceEntry(sourceEntry)) {
|
|
160
|
+
nonSourceEntryIds.push(sourceEntryId);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
sourceEntries.push(sourceEntry);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
observation,
|
|
168
|
+
observationEntryId: location.entryId,
|
|
169
|
+
observationRecordIndex: location.recordIndex,
|
|
170
|
+
status: "active",
|
|
171
|
+
sourceEntryIds,
|
|
172
|
+
sourceEntries,
|
|
173
|
+
missingSourceEntryIds,
|
|
174
|
+
nonSourceEntryIds,
|
|
175
|
+
};
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
function notFound(memoryId: string): RecallResult {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
return {
|
|
180
|
+
status: "not_found",
|
|
181
|
+
memoryId,
|
|
182
|
+
kind: undefined,
|
|
183
|
+
reflections: [],
|
|
184
|
+
observations: [],
|
|
185
|
+
sourceEntries: [],
|
|
186
|
+
missingSourceEntryIds: [],
|
|
187
|
+
nonSourceEntryIds: [],
|
|
188
|
+
missingSupportingObservationIds: [],
|
|
189
|
+
collision: false,
|
|
190
|
+
partial: false,
|
|
191
|
+
};
|
|
176
192
|
}
|
|
177
193
|
|
|
178
|
-
export function recallMemorySources(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
194
|
+
export function recallMemorySources(
|
|
195
|
+
entries: Entry[],
|
|
196
|
+
memoryId: string,
|
|
197
|
+
): RecallResult {
|
|
198
|
+
const {
|
|
199
|
+
observations: indexedObservations,
|
|
200
|
+
reflections: indexedReflections,
|
|
201
|
+
droppedIds,
|
|
202
|
+
} = indexLedger(entries);
|
|
203
|
+
const directObservationMatches = indexedObservations.filter(
|
|
204
|
+
({ observation }) => observation.id === memoryId,
|
|
205
|
+
);
|
|
206
|
+
const reflectionMatches = indexedReflections.filter(
|
|
207
|
+
({ reflection }) => reflection.id === memoryId,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (directObservationMatches.length === 0 && reflectionMatches.length === 0)
|
|
211
|
+
return notFound(memoryId);
|
|
212
|
+
|
|
213
|
+
const observationsById = new Map<string, IndexedObservation>();
|
|
214
|
+
for (const indexed of indexedObservations) {
|
|
215
|
+
if (!observationsById.has(indexed.observation.id))
|
|
216
|
+
observationsById.set(indexed.observation.id, indexed);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const recalledByKey = new Map<string, RecalledObservation>();
|
|
220
|
+
const rawMissingSupportingObservationIds: string[] = [];
|
|
221
|
+
|
|
222
|
+
function addObservation(indexed: IndexedObservation): void {
|
|
223
|
+
const key = `${indexed.entryId}:${indexed.recordIndex}`;
|
|
224
|
+
if (recalledByKey.has(key)) return;
|
|
225
|
+
const recalled = resolveObservationSources(
|
|
226
|
+
entries,
|
|
227
|
+
indexed.observation,
|
|
228
|
+
indexed,
|
|
229
|
+
);
|
|
230
|
+
recalled.status = droppedIds.has(indexed.observation.id)
|
|
231
|
+
? "dropped"
|
|
232
|
+
: "active";
|
|
233
|
+
recalledByKey.set(key, recalled);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const match of directObservationMatches) addObservation(match);
|
|
237
|
+
|
|
238
|
+
for (const { reflection } of reflectionMatches) {
|
|
239
|
+
for (const observationId of uniqueStrings(
|
|
240
|
+
reflection.supportingObservationIds,
|
|
241
|
+
)) {
|
|
242
|
+
const indexed = observationsById.get(observationId);
|
|
243
|
+
if (!indexed) {
|
|
244
|
+
rawMissingSupportingObservationIds.push(observationId);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
addObservation(indexed);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const recalledObservations = Array.from(recalledByKey.values());
|
|
252
|
+
const recalledReflections: RecalledReflection[] = reflectionMatches.map(
|
|
253
|
+
({ reflection, entryId, recordIndex }) => ({
|
|
254
|
+
reflection,
|
|
255
|
+
reflectionEntryId: entryId,
|
|
256
|
+
reflectionRecordIndex: recordIndex,
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
const sourceEntries = uniqueById(
|
|
260
|
+
recalledObservations.flatMap((match) => match.sourceEntries),
|
|
261
|
+
);
|
|
262
|
+
const missingSourceEntryIds = uniqueStrings(
|
|
263
|
+
recalledObservations.flatMap((match) => match.missingSourceEntryIds),
|
|
264
|
+
);
|
|
265
|
+
const nonSourceEntryIds = uniqueStrings(
|
|
266
|
+
recalledObservations.flatMap((match) => match.nonSourceEntryIds),
|
|
267
|
+
);
|
|
268
|
+
const uniqueMissingSupportingObservationIds = uniqueStrings(
|
|
269
|
+
rawMissingSupportingObservationIds,
|
|
270
|
+
);
|
|
271
|
+
const matchCount = directObservationMatches.length + reflectionMatches.length;
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
status: "found",
|
|
275
|
+
memoryId,
|
|
276
|
+
kind:
|
|
277
|
+
directObservationMatches.length > 0 && reflectionMatches.length > 0
|
|
278
|
+
? "mixed"
|
|
279
|
+
: reflectionMatches.length > 0
|
|
280
|
+
? "reflection"
|
|
281
|
+
: "observation",
|
|
282
|
+
reflections: recalledReflections,
|
|
283
|
+
observations: recalledObservations,
|
|
284
|
+
sourceEntries,
|
|
285
|
+
missingSourceEntryIds,
|
|
286
|
+
nonSourceEntryIds,
|
|
287
|
+
missingSupportingObservationIds: uniqueMissingSupportingObservationIds,
|
|
288
|
+
collision: matchCount > 1,
|
|
289
|
+
partial:
|
|
290
|
+
missingSourceEntryIds.length > 0 ||
|
|
291
|
+
nonSourceEntryIds.length > 0 ||
|
|
292
|
+
uniqueMissingSupportingObservationIds.length > 0,
|
|
293
|
+
};
|
|
243
294
|
}
|
|
@@ -18,18 +18,26 @@ export const OM_FOOTER_FULL = `----\n${OM_INSTRUCTIONS_FULL}\n----`;
|
|
|
18
18
|
export const OM_FOOTER_BASIC = `----\n${OM_INSTRUCTIONS_BASIC}\n----`;
|
|
19
19
|
|
|
20
20
|
export function observationToSummaryLine(observation: Observation): string {
|
|
21
|
-
|
|
21
|
+
return `[${observation.id}] ${observation.timestamp} [${observation.relevance}] ${observation.content}`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Score an observation for cap/trim selection.
|
|
25
25
|
* Relevance tier dominates: medium (5+) always outranks low (max 2).
|
|
26
26
|
* Recency is based on position in the flat-mapped array (0 = oldest, N-1 = newest),
|
|
27
27
|
* avoiding wall-clock dependency that punishes sessions spanning days or weeks. */
|
|
28
|
-
export function scoreObservation(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
export function scoreObservation(
|
|
29
|
+
obs: Observation,
|
|
30
|
+
index: number,
|
|
31
|
+
total: number,
|
|
32
|
+
): number {
|
|
33
|
+
const base =
|
|
34
|
+
obs.relevance === "high" || obs.relevance === "critical"
|
|
35
|
+
? 10
|
|
36
|
+
: obs.relevance === "medium"
|
|
37
|
+
? 5
|
|
38
|
+
: 1;
|
|
39
|
+
const recency = total > 1 ? index / (total - 1) : 1;
|
|
40
|
+
return base + recency;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
/** Select observations up to a token budget, keeping all high-relevance items
|
|
@@ -39,58 +47,79 @@ export function scoreObservation(obs: Observation, index: number, total: number)
|
|
|
39
47
|
* Reflections are never trimmed — they are inherently rare and always stay.
|
|
40
48
|
* Observations stay in the branch either way; this only caps what is rendered
|
|
41
49
|
* in the compaction summary output. */
|
|
42
|
-
export function selectPriorObservations(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
export function selectPriorObservations(
|
|
51
|
+
observations: Observation[],
|
|
52
|
+
maxTokens: number,
|
|
53
|
+
): Observation[] {
|
|
54
|
+
// Track original indices so we can restore chronological order after scoring
|
|
55
|
+
const indexed = observations.map((obs, i) => ({ obs, originalIndex: i }));
|
|
56
|
+
const high = indexed.filter(
|
|
57
|
+
(item) =>
|
|
58
|
+
item.obs.relevance === "high" || item.obs.relevance === "critical",
|
|
59
|
+
);
|
|
60
|
+
const rest = indexed.filter(
|
|
61
|
+
(item) =>
|
|
62
|
+
item.obs.relevance !== "high" && item.obs.relevance !== "critical",
|
|
63
|
+
);
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
// High always kept — consume budget first
|
|
66
|
+
let budget = maxTokens;
|
|
67
|
+
const selected = new Set<{ obs: Observation; originalIndex: number }>();
|
|
68
|
+
for (const item of high) {
|
|
69
|
+
const lineTokens = Math.ceil(observationToSummaryLine(item.obs).length / 4);
|
|
70
|
+
selected.add(item);
|
|
71
|
+
budget -= lineTokens;
|
|
72
|
+
}
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
// Score medium + low and select best within remaining budget
|
|
75
|
+
if (rest.length > 0 && budget > 0) {
|
|
76
|
+
const scored = rest.map((item, i) => ({
|
|
77
|
+
item,
|
|
78
|
+
score: scoreObservation(item.obs, i, rest.length),
|
|
79
|
+
}));
|
|
80
|
+
scored.sort((a, b) => b.score - a.score); // highest score first
|
|
81
|
+
for (const { item } of scored) {
|
|
82
|
+
const lineTokens = Math.ceil(
|
|
83
|
+
observationToSummaryLine(item.obs).length / 4,
|
|
84
|
+
);
|
|
85
|
+
if (budget - lineTokens < 0) break;
|
|
86
|
+
selected.add(item);
|
|
87
|
+
budget -= lineTokens;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
68
90
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
// Restore original chronological order before returning
|
|
92
|
+
return Array.from(selected)
|
|
93
|
+
.sort((a, b) => a.originalIndex - b.originalIndex)
|
|
94
|
+
.map((item) => item.obs);
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
export function reflectionToSummaryLine(reflection: Reflection): string {
|
|
76
|
-
|
|
98
|
+
return `[${reflection.id}] ${reflection.content}`;
|
|
77
99
|
}
|
|
78
100
|
|
|
79
|
-
export function renderSummary(
|
|
80
|
-
|
|
101
|
+
export function renderSummary(
|
|
102
|
+
reflections: Reflection[],
|
|
103
|
+
observations: Observation[],
|
|
104
|
+
): string {
|
|
105
|
+
const hasContent = reflections.length > 0 || observations.length > 0;
|
|
81
106
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
const parts: string[] = [];
|
|
108
|
+
if (reflections.length > 0) {
|
|
109
|
+
parts.push(
|
|
110
|
+
`## Reflections\n${reflections.map(reflectionToSummaryLine).join("\n")}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (observations.length > 0) {
|
|
114
|
+
parts.push(
|
|
115
|
+
`## Observations\n${observations.map(observationToSummaryLine).join("\n")}`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
89
118
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
119
|
+
const footer = hasContent ? OM_FOOTER_FULL : OM_FOOTER_BASIC;
|
|
120
|
+
if (parts.length > 0) {
|
|
121
|
+
parts.push(footer);
|
|
122
|
+
return parts.join("\n\n");
|
|
123
|
+
}
|
|
124
|
+
return footer;
|
|
96
125
|
}
|