pi-blackhole 0.2.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/LICENSE +21 -0
- package/README.md +373 -0
- package/example-config.json +115 -0
- package/index.ts +39 -0
- package/package.json +55 -0
- package/src/commands/memory.ts +191 -0
- package/src/commands/pi-vcc.ts +94 -0
- package/src/commands/vcc-recall.ts +112 -0
- package/src/core/brief.ts +390 -0
- package/src/core/build-sections.ts +85 -0
- package/src/core/content.ts +60 -0
- package/src/core/filter-noise.ts +42 -0
- package/src/core/format-recall.ts +27 -0
- package/src/core/format.ts +76 -0
- package/src/core/lineage.ts +26 -0
- package/src/core/load-messages.ts +41 -0
- package/src/core/normalize.ts +79 -0
- package/src/core/recall-scope.ts +14 -0
- package/src/core/render-entries.ts +56 -0
- package/src/core/report.ts +237 -0
- package/src/core/sanitize.ts +5 -0
- package/src/core/search-entries.ts +227 -0
- package/src/core/settings.ts +34 -0
- package/src/core/skill-collapse.ts +35 -0
- package/src/core/summarize.ts +213 -0
- package/src/core/tool-args.ts +14 -0
- package/src/core/unified-config.ts +285 -0
- package/src/details.ts +13 -0
- package/src/extract/commits.ts +69 -0
- package/src/extract/files.ts +80 -0
- package/src/extract/goals.ts +79 -0
- package/src/extract/preferences.ts +55 -0
- package/src/hooks/before-compact.ts +345 -0
- package/src/om/agents/dropper/agent.ts +204 -0
- package/src/om/agents/dropper/prompts.ts +48 -0
- package/src/om/agents/observer/agent.ts +256 -0
- package/src/om/agents/observer/prompts.ts +119 -0
- package/src/om/agents/reflector/agent.ts +161 -0
- package/src/om/agents/reflector/prompts.ts +77 -0
- package/src/om/clipboard.ts +63 -0
- package/src/om/compaction-hook.ts +63 -0
- package/src/om/compaction-trigger.ts +92 -0
- package/src/om/config.ts +22 -0
- package/src/om/consolidation.ts +514 -0
- package/src/om/cooldown.ts +130 -0
- package/src/om/debug-log.ts +55 -0
- package/src/om/ids.ts +5 -0
- package/src/om/ledger/fold.ts +106 -0
- package/src/om/ledger/index.ts +6 -0
- package/src/om/ledger/progress.ts +225 -0
- package/src/om/ledger/projection.ts +237 -0
- package/src/om/ledger/recall.ts +243 -0
- package/src/om/ledger/render-summary.ts +44 -0
- package/src/om/ledger/types.ts +206 -0
- package/src/om/model-budget.ts +9 -0
- package/src/om/pending.ts +225 -0
- package/src/om/reverse-recall.ts +130 -0
- package/src/om/runtime.ts +241 -0
- package/src/om/serialize.ts +224 -0
- package/src/om/tokens.ts +33 -0
- package/src/sections.ts +18 -0
- package/src/tools/recall.ts +212 -0
- package/src/types.ts +19 -0
- package/vitest.config.ts +41 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory recall — resolves source evidence for observations and reflections.
|
|
3
|
+
*
|
|
4
|
+
* Upstream: https://github.com/elpapi42/pi-observational-memory (src/session-ledger/recall.ts)
|
|
5
|
+
* Unmodified.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
isObservationsDroppedEntry,
|
|
9
|
+
isObservationsRecordedEntry,
|
|
10
|
+
isReflectionsRecordedEntry,
|
|
11
|
+
type Entry,
|
|
12
|
+
type Observation,
|
|
13
|
+
type Reflection,
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
const SOURCE_TYPES = new Set(["message", "custom_message", "branch_summary"]);
|
|
17
|
+
|
|
18
|
+
export type { Entry, Observation, Reflection };
|
|
19
|
+
|
|
20
|
+
type ObservationLedgerLocation = {
|
|
21
|
+
entryId: string;
|
|
22
|
+
entryIndex: number;
|
|
23
|
+
recordIndex: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ReflectionLedgerLocation = {
|
|
27
|
+
entryId: string;
|
|
28
|
+
entryIndex: number;
|
|
29
|
+
recordIndex: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type RecalledObservation = {
|
|
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
|
+
};
|
|
42
|
+
|
|
43
|
+
export type RecalledReflection = {
|
|
44
|
+
reflection: Reflection;
|
|
45
|
+
reflectionEntryId: string;
|
|
46
|
+
reflectionRecordIndex: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type RecallResult =
|
|
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 & { observation: Observation };
|
|
78
|
+
type IndexedReflection = ReflectionLedgerLocation & { reflection: Reflection };
|
|
79
|
+
|
|
80
|
+
function isSourceEntry(entry: Entry): boolean {
|
|
81
|
+
return SOURCE_TYPES.has(entry.type);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function uniqueById(entries: Entry[]): Entry[] {
|
|
85
|
+
const seen = new Set<string>();
|
|
86
|
+
const result: Entry[] = [];
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
if (seen.has(entry.id)) continue;
|
|
89
|
+
seen.add(entry.id);
|
|
90
|
+
result.push(entry);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function uniqueStrings(values: string[]): string[] {
|
|
96
|
+
return Array.from(new Set(values));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function indexLedger(entries: Entry[]): {
|
|
100
|
+
observations: IndexedObservation[];
|
|
101
|
+
reflections: IndexedReflection[];
|
|
102
|
+
droppedIds: Set<string>;
|
|
103
|
+
} {
|
|
104
|
+
const observations: IndexedObservation[] = [];
|
|
105
|
+
const reflections: IndexedReflection[] = [];
|
|
106
|
+
const droppedIds = new Set<string>();
|
|
107
|
+
|
|
108
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
|
109
|
+
const entry = entries[entryIndex];
|
|
110
|
+
if (isObservationsRecordedEntry(entry)) {
|
|
111
|
+
entry.data.observations.forEach((observation, recordIndex) => {
|
|
112
|
+
observations.push({ observation, entryId: entry.id, entryIndex, recordIndex });
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (isReflectionsRecordedEntry(entry)) {
|
|
117
|
+
entry.data.reflections.forEach((reflection, recordIndex) => {
|
|
118
|
+
reflections.push({ reflection, entryId: entry.id, entryIndex, recordIndex });
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (isObservationsDroppedEntry(entry)) {
|
|
123
|
+
entry.data.observationIds.forEach((id) => droppedIds.add(id));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { observations, reflections, droppedIds };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function resolveObservationSources(entries: Entry[], observation: Observation, location: ObservationLedgerLocation): RecalledObservation {
|
|
131
|
+
const sourceEntryIds = uniqueStrings(observation.sourceEntryIds);
|
|
132
|
+
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
133
|
+
const sourceEntries: Entry[] = [];
|
|
134
|
+
const missingSourceEntryIds: string[] = [];
|
|
135
|
+
const nonSourceEntryIds: string[] = [];
|
|
136
|
+
|
|
137
|
+
for (const sourceEntryId of sourceEntryIds) {
|
|
138
|
+
const sourceEntry = byId.get(sourceEntryId);
|
|
139
|
+
if (!sourceEntry) {
|
|
140
|
+
missingSourceEntryIds.push(sourceEntryId);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!isSourceEntry(sourceEntry)) {
|
|
144
|
+
nonSourceEntryIds.push(sourceEntryId);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
sourceEntries.push(sourceEntry);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
observation,
|
|
152
|
+
observationEntryId: location.entryId,
|
|
153
|
+
observationRecordIndex: location.recordIndex,
|
|
154
|
+
status: "active",
|
|
155
|
+
sourceEntryIds,
|
|
156
|
+
sourceEntries,
|
|
157
|
+
missingSourceEntryIds,
|
|
158
|
+
nonSourceEntryIds,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function notFound(memoryId: string): RecallResult {
|
|
163
|
+
return {
|
|
164
|
+
status: "not_found",
|
|
165
|
+
memoryId,
|
|
166
|
+
kind: undefined,
|
|
167
|
+
reflections: [],
|
|
168
|
+
observations: [],
|
|
169
|
+
sourceEntries: [],
|
|
170
|
+
missingSourceEntryIds: [],
|
|
171
|
+
nonSourceEntryIds: [],
|
|
172
|
+
missingSupportingObservationIds: [],
|
|
173
|
+
collision: false,
|
|
174
|
+
partial: false,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function recallMemorySources(entries: Entry[], memoryId: string): RecallResult {
|
|
179
|
+
const { observations: indexedObservations, reflections: indexedReflections, droppedIds } = indexLedger(entries);
|
|
180
|
+
const directObservationMatches = indexedObservations.filter(({ observation }) => observation.id === memoryId);
|
|
181
|
+
const reflectionMatches = indexedReflections.filter(({ reflection }) => reflection.id === memoryId);
|
|
182
|
+
|
|
183
|
+
if (directObservationMatches.length === 0 && reflectionMatches.length === 0) return notFound(memoryId);
|
|
184
|
+
|
|
185
|
+
const observationsById = new Map<string, IndexedObservation>();
|
|
186
|
+
for (const indexed of indexedObservations) {
|
|
187
|
+
if (!observationsById.has(indexed.observation.id)) observationsById.set(indexed.observation.id, indexed);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const recalledByKey = new Map<string, RecalledObservation>();
|
|
191
|
+
const missingSupportingObservationIds: string[] = [];
|
|
192
|
+
|
|
193
|
+
function addObservation(indexed: IndexedObservation): void {
|
|
194
|
+
const key = `${indexed.entryId}:${indexed.recordIndex}`;
|
|
195
|
+
if (recalledByKey.has(key)) return;
|
|
196
|
+
const recalled = resolveObservationSources(entries, indexed.observation, indexed);
|
|
197
|
+
recalled.status = droppedIds.has(indexed.observation.id) ? "dropped" : "active";
|
|
198
|
+
recalledByKey.set(key, recalled);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const match of directObservationMatches) addObservation(match);
|
|
202
|
+
|
|
203
|
+
for (const { reflection } of reflectionMatches) {
|
|
204
|
+
for (const observationId of uniqueStrings(reflection.supportingObservationIds)) {
|
|
205
|
+
const indexed = observationsById.get(observationId);
|
|
206
|
+
if (!indexed) {
|
|
207
|
+
missingSupportingObservationIds.push(observationId);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
addObservation(indexed);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const recalledObservations = Array.from(recalledByKey.values());
|
|
215
|
+
const recalledReflections: RecalledReflection[] = reflectionMatches.map(({ reflection, entryId, recordIndex }) => ({
|
|
216
|
+
reflection,
|
|
217
|
+
reflectionEntryId: entryId,
|
|
218
|
+
reflectionRecordIndex: recordIndex,
|
|
219
|
+
}));
|
|
220
|
+
const sourceEntries = uniqueById(recalledObservations.flatMap((match) => match.sourceEntries));
|
|
221
|
+
const missingSourceEntryIds = uniqueStrings(recalledObservations.flatMap((match) => match.missingSourceEntryIds));
|
|
222
|
+
const nonSourceEntryIds = uniqueStrings(recalledObservations.flatMap((match) => match.nonSourceEntryIds));
|
|
223
|
+
const uniqueMissingSupportingObservationIds = uniqueStrings(missingSupportingObservationIds);
|
|
224
|
+
const matchCount = directObservationMatches.length + reflectionMatches.length;
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
status: "found",
|
|
228
|
+
memoryId,
|
|
229
|
+
kind: directObservationMatches.length > 0 && reflectionMatches.length > 0
|
|
230
|
+
? "mixed"
|
|
231
|
+
: reflectionMatches.length > 0
|
|
232
|
+
? "reflection"
|
|
233
|
+
: "observation",
|
|
234
|
+
reflections: recalledReflections,
|
|
235
|
+
observations: recalledObservations,
|
|
236
|
+
sourceEntries,
|
|
237
|
+
missingSourceEntryIds,
|
|
238
|
+
nonSourceEntryIds,
|
|
239
|
+
missingSupportingObservationIds: uniqueMissingSupportingObservationIds,
|
|
240
|
+
collision: matchCount > 1,
|
|
241
|
+
partial: missingSourceEntryIds.length > 0 || nonSourceEntryIds.length > 0 || uniqueMissingSupportingObservationIds.length > 0,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summary rendering — formats observations/reflections for compaction output.
|
|
3
|
+
*
|
|
4
|
+
* Upstream: https://github.com/elpapi42/pi-observational-memory (src/session-ledger/render-summary.ts)
|
|
5
|
+
* Unmodified.
|
|
6
|
+
*/
|
|
7
|
+
import type { Observation, Reflection } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const RECALL_NOTE =
|
|
10
|
+
"Use `recall` to search for prior work, decisions, and context from before this summary. " +
|
|
11
|
+
"Do not redo work already completed.";
|
|
12
|
+
|
|
13
|
+
const CONTEXT_USAGE_INSTRUCTIONS = `These are condensed memories from earlier in this session.
|
|
14
|
+
|
|
15
|
+
- Reflections: stable, long-lived facts about the user, project, decisions, and constraints. New reflection lines may include ids in brackets.
|
|
16
|
+
- Observations: timestamped events from the conversation history, in chronological order. Observation lines include ids in brackets.
|
|
17
|
+
|
|
18
|
+
Treat these as past records. When entries conflict, the most recent observation reflects the latest known state. Work that prior observations describe as completed should not be redone unless the user explicitly asks to revisit it.
|
|
19
|
+
|
|
20
|
+
When exact source context is needed for precision or traceability, use the \`recall\` tool with the relevant observation or reflection id. This is especially useful when a reflection materially affects a decision or is too compressed to continue confidently. Do not use \`recall\` as broad search or inject raw source unless it is needed.`;
|
|
21
|
+
|
|
22
|
+
export const OM_FOOTER = `----\n${RECALL_NOTE}\n\n${CONTEXT_USAGE_INSTRUCTIONS}\n----`;
|
|
23
|
+
|
|
24
|
+
export function observationToSummaryLine(observation: Observation): string {
|
|
25
|
+
return `[${observation.id}] ${observation.timestamp} [${observation.relevance}] ${observation.content}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function reflectionToSummaryLine(reflection: Reflection): string {
|
|
29
|
+
return `[${reflection.id}] ${reflection.content}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function renderSummary(reflections: Reflection[], observations: Observation[]): string {
|
|
33
|
+
if (reflections.length === 0 && observations.length === 0) return "";
|
|
34
|
+
|
|
35
|
+
const parts: string[] = [];
|
|
36
|
+
if (reflections.length > 0) {
|
|
37
|
+
parts.push(`## Reflections\n${reflections.map(reflectionToSummaryLine).join("\n")}`);
|
|
38
|
+
}
|
|
39
|
+
if (observations.length > 0) {
|
|
40
|
+
parts.push(`## Observations\n${observations.map(observationToSummaryLine).join("\n")}`);
|
|
41
|
+
}
|
|
42
|
+
parts.push(OM_FOOTER);
|
|
43
|
+
return parts.join("\n\n");
|
|
44
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observational memory ledger types — validated Entry, Observation, Reflection.
|
|
3
|
+
*
|
|
4
|
+
* Upstream: https://github.com/elpapi42/pi-observational-memory (src/session-ledger/types.ts)
|
|
5
|
+
* Unmodified.
|
|
6
|
+
*/
|
|
7
|
+
export const OM_OBSERVATIONS_RECORDED = "om.observations.recorded";
|
|
8
|
+
export const OM_REFLECTIONS_RECORDED = "om.reflections.recorded";
|
|
9
|
+
export const OM_OBSERVATIONS_DROPPED = "om.observations.dropped";
|
|
10
|
+
export const OM_FOLDED = "om.folded";
|
|
11
|
+
|
|
12
|
+
export const RELEVANCE_VALUES = ["low", "medium", "high", "critical"] as const;
|
|
13
|
+
export type Relevance = (typeof RELEVANCE_VALUES)[number];
|
|
14
|
+
|
|
15
|
+
export const MEMORY_ID_PATTERN = /^[a-f0-9]{12}$/;
|
|
16
|
+
|
|
17
|
+
export type Entry = {
|
|
18
|
+
type: string;
|
|
19
|
+
id: string;
|
|
20
|
+
timestamp?: string;
|
|
21
|
+
message?: unknown;
|
|
22
|
+
content?: unknown;
|
|
23
|
+
customType?: string;
|
|
24
|
+
summary?: unknown;
|
|
25
|
+
fromId?: string;
|
|
26
|
+
data?: unknown;
|
|
27
|
+
details?: unknown;
|
|
28
|
+
firstKeptEntryId?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type Observation = {
|
|
32
|
+
id: string;
|
|
33
|
+
content: string;
|
|
34
|
+
timestamp: string;
|
|
35
|
+
relevance: Relevance;
|
|
36
|
+
sourceEntryIds: string[];
|
|
37
|
+
tokenCount: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type Reflection = {
|
|
41
|
+
id: string;
|
|
42
|
+
content: string;
|
|
43
|
+
supportingObservationIds: string[];
|
|
44
|
+
tokenCount: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ObservationsRecordedEntryData = {
|
|
48
|
+
observations: Observation[];
|
|
49
|
+
coversUpToId: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type ReflectionsRecordedEntryData = {
|
|
53
|
+
reflections: Reflection[];
|
|
54
|
+
coversUpToId: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type ObservationsDroppedEntryData = {
|
|
58
|
+
observationIds: string[];
|
|
59
|
+
coversUpToId: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type MemoryDetails = {
|
|
63
|
+
type: typeof OM_FOLDED;
|
|
64
|
+
version: 1;
|
|
65
|
+
fullFold: boolean;
|
|
66
|
+
observations: Observation[];
|
|
67
|
+
reflections: Reflection[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type V3MemoryCustomType =
|
|
71
|
+
| typeof OM_OBSERVATIONS_RECORDED
|
|
72
|
+
| typeof OM_REFLECTIONS_RECORDED
|
|
73
|
+
| typeof OM_OBSERVATIONS_DROPPED;
|
|
74
|
+
|
|
75
|
+
export function isRelevance(value: unknown): value is Relevance {
|
|
76
|
+
return typeof value === "string" && (RELEVANCE_VALUES as readonly string[]).includes(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isNonEmptyString(value: unknown): value is string {
|
|
80
|
+
return typeof value === "string" && value.length > 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isNonEmptyStringArray(value: unknown): value is string[] {
|
|
84
|
+
return Array.isArray(value) && value.length > 0 && value.every(isNonEmptyString);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isMemoryId(value: unknown): value is string {
|
|
88
|
+
return typeof value === "string" && MEMORY_ID_PATTERN.test(value);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isTokenCount(value: unknown): value is number {
|
|
92
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
96
|
+
return !!value && typeof value === "object";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isObservation(value: unknown): value is Observation {
|
|
100
|
+
if (!isPlainRecord(value)) return false;
|
|
101
|
+
return (
|
|
102
|
+
isMemoryId(value.id) &&
|
|
103
|
+
isNonEmptyString(value.content) &&
|
|
104
|
+
isNonEmptyString(value.timestamp) &&
|
|
105
|
+
isRelevance(value.relevance) &&
|
|
106
|
+
isNonEmptyStringArray(value.sourceEntryIds) &&
|
|
107
|
+
isTokenCount(value.tokenCount)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isReflection(value: unknown): value is Reflection {
|
|
112
|
+
if (!isPlainRecord(value)) return false;
|
|
113
|
+
return (
|
|
114
|
+
isMemoryId(value.id) &&
|
|
115
|
+
isNonEmptyString(value.content) &&
|
|
116
|
+
!/\r|\n/.test(value.content) &&
|
|
117
|
+
isNonEmptyStringArray(value.supportingObservationIds) &&
|
|
118
|
+
isTokenCount(value.tokenCount)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function isObservationsRecordedData(value: unknown): value is ObservationsRecordedEntryData {
|
|
123
|
+
if (!isPlainRecord(value)) return false;
|
|
124
|
+
return (
|
|
125
|
+
Array.isArray(value.observations) &&
|
|
126
|
+
value.observations.length > 0 &&
|
|
127
|
+
value.observations.every(isObservation) &&
|
|
128
|
+
isNonEmptyString(value.coversUpToId)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function isReflectionsRecordedData(value: unknown): value is ReflectionsRecordedEntryData {
|
|
133
|
+
if (!isPlainRecord(value)) return false;
|
|
134
|
+
return (
|
|
135
|
+
Array.isArray(value.reflections) &&
|
|
136
|
+
value.reflections.length > 0 &&
|
|
137
|
+
value.reflections.every(isReflection) &&
|
|
138
|
+
isNonEmptyString(value.coversUpToId)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isObservationsDroppedData(value: unknown): value is ObservationsDroppedEntryData {
|
|
143
|
+
if (!isPlainRecord(value)) return false;
|
|
144
|
+
return isNonEmptyStringArray(value.observationIds) && isNonEmptyString(value.coversUpToId);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function isMemoryDetails(value: unknown): value is MemoryDetails {
|
|
148
|
+
if (!isPlainRecord(value)) return false;
|
|
149
|
+
return (
|
|
150
|
+
value.type === OM_FOLDED &&
|
|
151
|
+
value.version === 1 &&
|
|
152
|
+
typeof value.fullFold === "boolean" &&
|
|
153
|
+
Array.isArray(value.observations) &&
|
|
154
|
+
value.observations.every(isObservation) &&
|
|
155
|
+
Array.isArray(value.reflections) &&
|
|
156
|
+
value.reflections.every(isReflection)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function isObservationsRecordedEntry(entry: Entry): entry is Entry & {
|
|
161
|
+
type: "custom";
|
|
162
|
+
customType: typeof OM_OBSERVATIONS_RECORDED;
|
|
163
|
+
data: ObservationsRecordedEntryData;
|
|
164
|
+
} {
|
|
165
|
+
return entry.type === "custom" && entry.customType === OM_OBSERVATIONS_RECORDED && isObservationsRecordedData(entry.data);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function isReflectionsRecordedEntry(entry: Entry): entry is Entry & {
|
|
169
|
+
type: "custom";
|
|
170
|
+
customType: typeof OM_REFLECTIONS_RECORDED;
|
|
171
|
+
data: ReflectionsRecordedEntryData;
|
|
172
|
+
} {
|
|
173
|
+
return entry.type === "custom" && entry.customType === OM_REFLECTIONS_RECORDED && isReflectionsRecordedData(entry.data);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function isObservationsDroppedEntry(entry: Entry): entry is Entry & {
|
|
177
|
+
type: "custom";
|
|
178
|
+
customType: typeof OM_OBSERVATIONS_DROPPED;
|
|
179
|
+
data: ObservationsDroppedEntryData;
|
|
180
|
+
} {
|
|
181
|
+
return entry.type === "custom" && entry.customType === OM_OBSERVATIONS_DROPPED && isObservationsDroppedData(entry.data);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function buildObservationsRecordedData(
|
|
185
|
+
observations: Observation[],
|
|
186
|
+
coversUpToId: string,
|
|
187
|
+
): ObservationsRecordedEntryData | undefined {
|
|
188
|
+
if (observations.length === 0 || !isNonEmptyString(coversUpToId)) return undefined;
|
|
189
|
+
return { observations, coversUpToId };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function buildReflectionsRecordedData(
|
|
193
|
+
reflections: Reflection[],
|
|
194
|
+
coversUpToId: string,
|
|
195
|
+
): ReflectionsRecordedEntryData | undefined {
|
|
196
|
+
if (reflections.length === 0 || !isNonEmptyString(coversUpToId)) return undefined;
|
|
197
|
+
return { reflections, coversUpToId };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function buildObservationsDroppedData(
|
|
201
|
+
observationIds: string[],
|
|
202
|
+
coversUpToId: string,
|
|
203
|
+
): ObservationsDroppedEntryData | undefined {
|
|
204
|
+
if (observationIds.length === 0 || !isNonEmptyString(coversUpToId)) return undefined;
|
|
205
|
+
return { observationIds, coversUpToId };
|
|
206
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
3
|
+
export const AGENT_LOOP_MAX_TOKENS = 32_000;
|
|
4
|
+
|
|
5
|
+
export function boundedMaxTokens(model: Model<any>, requested: number = AGENT_LOOP_MAX_TOKENS): number {
|
|
6
|
+
return typeof model.maxTokens === "number" && model.maxTokens > 0
|
|
7
|
+
? Math.min(model.maxTokens, requested)
|
|
8
|
+
: requested;
|
|
9
|
+
}
|