pi-observational-memory 2.4.3 → 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 +312 -103
- package/package.json +9 -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} +11 -13
- 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 +78 -55
- package/src/debug-log.ts +1 -5
- package/src/hooks/compaction-hook.ts +24 -369
- package/src/hooks/compaction-trigger.ts +12 -21
- package/src/hooks/consolidation-trigger.ts +331 -0
- package/src/index.ts +3 -3
- package/src/model-budget.ts +1 -1
- 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 -1030
- package/src/hooks/observer-trigger.ts +0 -129
- package/src/progress.ts +0 -155
- package/src/prompts.ts +0 -302
- package/src/relevance.ts +0 -15
- package/src/types.ts +0 -155
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import type
|
|
5
|
-
import { Text } from "@
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
3
|
+
import type { Message, ToolResultMessage } from "@earendil-works/pi-ai";
|
|
4
|
+
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
6
6
|
import {
|
|
7
7
|
recallMemorySources,
|
|
8
8
|
type Entry,
|
|
9
|
-
type
|
|
10
|
-
type
|
|
11
|
-
} from "../
|
|
9
|
+
type RecallResult,
|
|
10
|
+
type RecalledObservation,
|
|
11
|
+
} from "../session-ledger/recall.js";
|
|
12
|
+
import type { Observation, Reflection } from "../session-ledger/index.js";
|
|
12
13
|
import { renderRecallSourceEntries, renderRecallSourceEntry } from "../serialize.js";
|
|
13
14
|
import { estimateEntryTokens } from "../tokens.js";
|
|
14
|
-
import type { ObservationRecord, ReflectionRecord } from "../types.js";
|
|
15
15
|
|
|
16
16
|
export const RECALL_OBSERVATION_TOOL_NAME = "recall";
|
|
17
17
|
|
|
@@ -23,11 +23,10 @@ type RecallObservationToolStatus =
|
|
|
23
23
|
| "invalid_id"
|
|
24
24
|
| "not_found"
|
|
25
25
|
| "no_source"
|
|
26
|
-
| "source_unavailable"
|
|
27
|
-
| "no_provenance";
|
|
26
|
+
| "source_unavailable";
|
|
28
27
|
|
|
29
|
-
type ObservationDetails = Pick<
|
|
30
|
-
type ReflectionDetails = Pick<
|
|
28
|
+
type ObservationDetails = Pick<Observation, "id" | "content" | "timestamp" | "relevance"> & { status?: "active" | "dropped" };
|
|
29
|
+
type ReflectionDetails = Pick<Reflection, "id" | "content" | "supportingObservationIds"> & { reflectionIndex: number };
|
|
31
30
|
|
|
32
31
|
export type RecallSourceEntryDetails = {
|
|
33
32
|
id: string;
|
|
@@ -39,7 +38,7 @@ export type RecallSourceEntryDetails = {
|
|
|
39
38
|
};
|
|
40
39
|
|
|
41
40
|
type RecallObservationMatchDetails = {
|
|
42
|
-
status:
|
|
41
|
+
status: "active" | "dropped" | "source_unavailable" | "no_source";
|
|
43
42
|
observationEntryId: string;
|
|
44
43
|
observationRecordIndex: number;
|
|
45
44
|
observation: ObservationDetails;
|
|
@@ -51,17 +50,9 @@ type RecallObservationMatchDetails = {
|
|
|
51
50
|
};
|
|
52
51
|
|
|
53
52
|
type RecallUnavailableSupportingObservationDetails = {
|
|
54
|
-
reflectionId: string;
|
|
55
|
-
reflectionIndex: number;
|
|
56
53
|
observationId: string;
|
|
57
54
|
};
|
|
58
55
|
|
|
59
|
-
type RecallUnavailableReflectionProvenanceDetails = {
|
|
60
|
-
reflectionId: string;
|
|
61
|
-
reflectionIndex: number;
|
|
62
|
-
reason: "legacy";
|
|
63
|
-
};
|
|
64
|
-
|
|
65
56
|
export type RecallObservationToolDetails = {
|
|
66
57
|
status: RecallObservationToolStatus;
|
|
67
58
|
memoryId: string;
|
|
@@ -74,7 +65,6 @@ export type RecallObservationToolDetails = {
|
|
|
74
65
|
matches: RecallObservationMatchDetails[];
|
|
75
66
|
sourceEntries: RecallSourceEntryDetails[];
|
|
76
67
|
unavailableSupportingObservations: RecallUnavailableSupportingObservationDetails[];
|
|
77
|
-
unavailableReflectionProvenance: RecallUnavailableReflectionProvenanceDetails[];
|
|
78
68
|
missingSourceEntryIds: string[];
|
|
79
69
|
nonSourceEntryIds: string[];
|
|
80
70
|
sourceCharacterCount?: number;
|
|
@@ -117,16 +107,11 @@ function sourceOriginAndQualifiers(entry: Entry): { origin: string; timestamp: s
|
|
|
117
107
|
.filter((block) => block.type === "toolCall" && typeof block.name === "string")
|
|
118
108
|
.map((block) => block.name as string),
|
|
119
109
|
);
|
|
120
|
-
return {
|
|
121
|
-
origin: "Assistant",
|
|
122
|
-
timestamp,
|
|
123
|
-
qualifiers: toolCalls.length > 0 ? [`tool calls: ${toolCalls.join(", ")}`] : [],
|
|
124
|
-
};
|
|
110
|
+
return { origin: "Assistant", timestamp, qualifiers: toolCalls.length > 0 ? [`tool calls: ${toolCalls.join(", ")}`] : [] };
|
|
125
111
|
}
|
|
126
112
|
const toolName = (msg as ToolResultMessage).toolName;
|
|
127
113
|
return { origin: `Tool result: ${typeof toolName === "string" && toolName ? toolName : "unknown"}`, timestamp, qualifiers: [] };
|
|
128
114
|
}
|
|
129
|
-
|
|
130
115
|
if (entry.type === "custom_message") {
|
|
131
116
|
return {
|
|
132
117
|
origin: "Custom message",
|
|
@@ -134,11 +119,7 @@ function sourceOriginAndQualifiers(entry: Entry): { origin: string; timestamp: s
|
|
|
134
119
|
qualifiers: typeof entry.customType === "string" && entry.customType ? [`custom: ${entry.customType}`] : [],
|
|
135
120
|
};
|
|
136
121
|
}
|
|
137
|
-
|
|
138
|
-
if (entry.type === "branch_summary") {
|
|
139
|
-
return { origin: "Branch summary", timestamp: formatDisplayTimestamp(entry.timestamp), qualifiers: [] };
|
|
140
|
-
}
|
|
141
|
-
|
|
122
|
+
if (entry.type === "branch_summary") return { origin: "Branch summary", timestamp: formatDisplayTimestamp(entry.timestamp), qualifiers: [] };
|
|
142
123
|
return { origin: entry.type || "Entry", timestamp: formatDisplayTimestamp(entry.timestamp), qualifiers: [] };
|
|
143
124
|
}
|
|
144
125
|
|
|
@@ -160,67 +141,32 @@ function sourceEntryDetails(entry: Entry, includeContent: boolean): RecallSource
|
|
|
160
141
|
};
|
|
161
142
|
}
|
|
162
143
|
|
|
163
|
-
function observationDetails(observation:
|
|
164
|
-
return {
|
|
165
|
-
id: observation.id,
|
|
166
|
-
content: observation.content,
|
|
167
|
-
timestamp: observation.timestamp,
|
|
168
|
-
relevance: observation.relevance,
|
|
169
|
-
};
|
|
144
|
+
function observationDetails(observation: Observation, status?: "active" | "dropped"): ObservationDetails {
|
|
145
|
+
return { id: observation.id, content: observation.content, timestamp: observation.timestamp, relevance: observation.relevance, ...(status ? { status } : {}) };
|
|
170
146
|
}
|
|
171
147
|
|
|
172
|
-
function reflectionDetails(reflection:
|
|
173
|
-
return {
|
|
174
|
-
id: reflection.id,
|
|
175
|
-
content: reflection.content,
|
|
176
|
-
supportingObservationIds: reflection.supportingObservationIds,
|
|
177
|
-
...(reflection.legacy === true ? { legacy: true } : {}),
|
|
178
|
-
reflectionIndex,
|
|
179
|
-
};
|
|
148
|
+
function reflectionDetails(reflection: Reflection, reflectionIndex: number): ReflectionDetails {
|
|
149
|
+
return { id: reflection.id, content: reflection.content, supportingObservationIds: reflection.supportingObservationIds, reflectionIndex };
|
|
180
150
|
}
|
|
181
151
|
|
|
182
|
-
function observationMatchDetails(match:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
status: "ok",
|
|
186
|
-
observationEntryId: match.observationEntryId,
|
|
187
|
-
observationRecordIndex: match.observationRecordIndex,
|
|
188
|
-
observation: observationDetails(match.observation),
|
|
189
|
-
sourceEntryIds: match.sourceEntryIds,
|
|
190
|
-
sourceEntries: match.sourceEntries.map((entry) => sourceEntryDetails(entry, includeSourceContent)),
|
|
191
|
-
sourceCharacterCount: renderRecallSourceEntries(match.sourceEntries).length,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
if (match.status === "source_unavailable") {
|
|
195
|
-
return {
|
|
196
|
-
status: "source_unavailable",
|
|
197
|
-
observationEntryId: match.observationEntryId,
|
|
198
|
-
observationRecordIndex: match.observationRecordIndex,
|
|
199
|
-
observation: observationDetails(match.observation),
|
|
200
|
-
sourceEntryIds: match.sourceEntryIds,
|
|
201
|
-
...(includeSourceContent
|
|
202
|
-
? {
|
|
203
|
-
sourceEntries: match.sourceEntries.map((entry) => sourceEntryDetails(entry, true)),
|
|
204
|
-
sourceCharacterCount: renderRecallSourceEntries(match.sourceEntries).length,
|
|
205
|
-
}
|
|
206
|
-
: {}),
|
|
207
|
-
missingSourceEntryIds: match.missingSourceEntryIds,
|
|
208
|
-
nonSourceEntryIds: match.nonSourceEntryIds,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
152
|
+
function observationMatchDetails(match: RecalledObservation, includeSourceContent = true): RecallObservationMatchDetails {
|
|
153
|
+
const unavailable = match.missingSourceEntryIds.length > 0 || match.nonSourceEntryIds.length > 0;
|
|
154
|
+
const status = unavailable ? "source_unavailable" : match.sourceEntries.length === 0 ? "no_source" : match.status;
|
|
211
155
|
return {
|
|
212
|
-
status
|
|
156
|
+
status,
|
|
213
157
|
observationEntryId: match.observationEntryId,
|
|
214
158
|
observationRecordIndex: match.observationRecordIndex,
|
|
215
|
-
observation: observationDetails(match.observation),
|
|
159
|
+
observation: observationDetails(match.observation, match.status),
|
|
160
|
+
sourceEntryIds: match.sourceEntryIds,
|
|
161
|
+
sourceEntries: match.sourceEntries.map((entry) => sourceEntryDetails(entry, includeSourceContent)),
|
|
162
|
+
missingSourceEntryIds: match.missingSourceEntryIds,
|
|
163
|
+
nonSourceEntryIds: match.nonSourceEntryIds,
|
|
164
|
+
sourceCharacterCount: renderRecallSourceEntries(match.sourceEntries).length,
|
|
216
165
|
};
|
|
217
166
|
}
|
|
218
167
|
|
|
219
168
|
function textResult(text: string, details: RecallObservationToolDetails) {
|
|
220
|
-
return {
|
|
221
|
-
content: [{ type: "text" as const, text }],
|
|
222
|
-
details,
|
|
223
|
-
};
|
|
169
|
+
return { content: [{ type: "text" as const, text }], details };
|
|
224
170
|
}
|
|
225
171
|
|
|
226
172
|
function emptyDetails(status: RecallObservationToolStatus, memoryId: string, message: string): RecallObservationToolDetails {
|
|
@@ -236,7 +182,6 @@ function emptyDetails(status: RecallObservationToolStatus, memoryId: string, mes
|
|
|
236
182
|
matches: [],
|
|
237
183
|
sourceEntries: [],
|
|
238
184
|
unavailableSupportingObservations: [],
|
|
239
|
-
unavailableReflectionProvenance: [],
|
|
240
185
|
missingSourceEntryIds: [],
|
|
241
186
|
nonSourceEntryIds: [],
|
|
242
187
|
message,
|
|
@@ -244,20 +189,15 @@ function emptyDetails(status: RecallObservationToolStatus, memoryId: string, mes
|
|
|
244
189
|
}
|
|
245
190
|
|
|
246
191
|
function aggregateStatus(details: Omit<RecallObservationToolDetails, "status">): RecallObservationToolStatus {
|
|
247
|
-
const observationOnly = details.reflections.length === 0 && details.unavailableSupportingObservations.length === 0
|
|
248
|
-
if (observationOnly && details.observations.some((match) => match.status === "ok")) return "ok";
|
|
249
|
-
if (observationOnly && details.observations.some((match) => match.status === "source_unavailable")) return "source_unavailable";
|
|
250
|
-
if (observationOnly && details.observations.length > 0) return "no_source";
|
|
251
|
-
if (details.unavailableReflectionProvenance.length > 0 && details.observations.length === 0 && details.sourceEntries.length === 0) return "no_provenance";
|
|
192
|
+
const observationOnly = details.reflections.length === 0 && details.unavailableSupportingObservations.length === 0;
|
|
252
193
|
if (details.partial) return "partial";
|
|
253
|
-
if (details.
|
|
254
|
-
if (details.
|
|
255
|
-
|
|
256
|
-
return "not_found";
|
|
194
|
+
if (observationOnly && details.observations.some((match) => match.status === "source_unavailable")) return "source_unavailable";
|
|
195
|
+
if (observationOnly && details.observations.length > 0 && details.sourceEntries.length === 0 && details.matches.every((match) => (match.sourceEntries ?? []).length === 0)) return "no_source";
|
|
196
|
+
return "ok";
|
|
257
197
|
}
|
|
258
198
|
|
|
259
199
|
function friendlyNoSourceMessage(memoryId: string): string {
|
|
260
|
-
return `Observation ${memoryId} has no source entries associated with it
|
|
200
|
+
return `Observation ${memoryId} has no source entries associated with it.`;
|
|
261
201
|
}
|
|
262
202
|
|
|
263
203
|
function friendlySourceUnavailableMessage(match: RecallObservationMatchDetails): string {
|
|
@@ -271,79 +211,43 @@ function reflectionLineText(reflection: ReflectionDetails): string {
|
|
|
271
211
|
}
|
|
272
212
|
|
|
273
213
|
function observationLineText(observation: ObservationDetails): string {
|
|
274
|
-
|
|
214
|
+
const status = observation.status === "dropped" ? " [dropped]" : "";
|
|
215
|
+
return `[${observation.id}]${status} ${observation.timestamp} [${observation.relevance}] ${observation.content}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function directObservationMatches(result: Extract<RecallResult, { status: "found" }>): RecalledObservation[] {
|
|
219
|
+
return result.observations.filter((match) => match.observation.id === result.memoryId);
|
|
275
220
|
}
|
|
276
221
|
|
|
277
|
-
function renderObservationOnlyTextFromResult(result: Extract<
|
|
222
|
+
function renderObservationOnlyTextFromResult(result: Extract<RecallResult, { status: "found" }>): string {
|
|
278
223
|
const sections: string[] = [];
|
|
279
|
-
if (result.collision) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const sourceText = renderRecallSourceEntries(match.sourceEntries);
|
|
285
|
-
if (sourceText.trim()) sections.push(sourceText);
|
|
286
|
-
else sections.push(`Observation ${match.observation.id} has source entries associated, but they rendered no text content.`);
|
|
224
|
+
if (result.collision) sections.push(`Memory id ${result.memoryId} matched multiple observations; returning all matching source results from the current branch.`);
|
|
225
|
+
for (const match of directObservationMatches(result)) {
|
|
226
|
+
if (match.status === "dropped") sections.push(`Observation ${match.observation.id} is dropped from active memory but remains recallable.`);
|
|
227
|
+
if (match.missingSourceEntryIds.length > 0 || match.nonSourceEntryIds.length > 0) {
|
|
228
|
+
sections.push(friendlySourceUnavailableMessage(observationMatchDetails(match, false)));
|
|
287
229
|
continue;
|
|
288
230
|
}
|
|
289
|
-
if (match.
|
|
290
|
-
sections.push(
|
|
231
|
+
if (match.sourceEntries.length === 0) {
|
|
232
|
+
sections.push(friendlyNoSourceMessage(match.observation.id));
|
|
291
233
|
continue;
|
|
292
234
|
}
|
|
293
|
-
|
|
235
|
+
const sourceText = renderRecallSourceEntries(match.sourceEntries);
|
|
236
|
+
sections.push(sourceText.trim() ? sourceText : `Observation ${match.observation.id} has source entries associated, but they rendered no text content.`);
|
|
294
237
|
}
|
|
295
238
|
return sections.join("\n\n");
|
|
296
239
|
}
|
|
297
240
|
|
|
298
241
|
function unavailableSupportingLineText(item: RecallUnavailableSupportingObservationDetails): string {
|
|
299
|
-
return `Supporting observation ${item.observationId}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function unavailableReflectionProvenanceLineText(item: RecallUnavailableReflectionProvenanceDetails): string {
|
|
303
|
-
return `Reflection ${item.reflectionId} was migrated from legacy memory created before reflection provenance was recorded, so no supporting observations or raw sources are available.`;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function unavailableObservationSourceLineText(match: RecallMemoryObservation): string {
|
|
307
|
-
return `Observation ${match.observation.id} has no source entries associated. This can happen for legacy observations created before source recall was available.`;
|
|
242
|
+
return `Supporting observation ${item.observationId} is unavailable on the current branch.`;
|
|
308
243
|
}
|
|
309
244
|
|
|
310
|
-
function renderMemoryText(result: Extract<
|
|
245
|
+
function renderMemoryText(result: Extract<RecallResult, { status: "found" }>): string {
|
|
311
246
|
const sections: string[] = [];
|
|
312
|
-
if (result.collision) {
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
if (result.
|
|
316
|
-
sections.push(`Reflections:\n${result.reflectionMatches.map((match) => reflectionLineText(reflectionDetails(match.reflection, match.reflectionIndex))).join("\n")}`);
|
|
317
|
-
}
|
|
318
|
-
if (result.observations.length > 0) {
|
|
319
|
-
sections.push(`Observations:\n${result.observations.map((match) => observationLineText(match.observation)).join("\n")}`);
|
|
320
|
-
}
|
|
321
|
-
if (result.unavailableSupportingObservations.length > 0) {
|
|
322
|
-
sections.push(
|
|
323
|
-
`Unavailable supporting observations:\n${result.unavailableSupportingObservations
|
|
324
|
-
.map((item) => unavailableSupportingLineText({
|
|
325
|
-
reflectionId: item.reflection.id,
|
|
326
|
-
reflectionIndex: item.reflectionIndex,
|
|
327
|
-
observationId: item.observationId,
|
|
328
|
-
}))
|
|
329
|
-
.join("\n")}`,
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
if (result.unavailableReflectionProvenance.length > 0) {
|
|
333
|
-
sections.push(
|
|
334
|
-
`Unavailable reflection provenance:\n${result.unavailableReflectionProvenance
|
|
335
|
-
.map((item) => unavailableReflectionProvenanceLineText({
|
|
336
|
-
reflectionId: item.reflection.id,
|
|
337
|
-
reflectionIndex: item.reflectionIndex,
|
|
338
|
-
reason: item.reason,
|
|
339
|
-
}))
|
|
340
|
-
.join("\n")}`,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
const noSourceObservations = result.observations.filter((match) => match.status === "no_source");
|
|
344
|
-
if (noSourceObservations.length > 0) {
|
|
345
|
-
sections.push(`Unavailable observation sources:\n${noSourceObservations.map(unavailableObservationSourceLineText).join("\n")}`);
|
|
346
|
-
}
|
|
247
|
+
if (result.collision) sections.push(`Memory id ${result.memoryId} matched multiple observations/reflections; returning all available evidence from the current branch.`);
|
|
248
|
+
if (result.reflections.length > 0) sections.push(`Reflections:\n${result.reflections.map((match) => reflectionLineText(reflectionDetails(match.reflection, match.reflectionRecordIndex))).join("\n")}`);
|
|
249
|
+
if (result.observations.length > 0) sections.push(`Observations:\n${result.observations.map((match) => observationLineText(observationDetails(match.observation, match.status))).join("\n")}`);
|
|
250
|
+
if (result.missingSupportingObservationIds.length > 0) sections.push(`Unavailable supporting observations:\n${result.missingSupportingObservationIds.map((id) => unavailableSupportingLineText({ observationId: id })).join("\n")}`);
|
|
347
251
|
if (result.missingSourceEntryIds.length > 0 || result.nonSourceEntryIds.length > 0) {
|
|
348
252
|
const parts: string[] = [];
|
|
349
253
|
if (result.missingSourceEntryIds.length > 0) parts.push(`missing: ${result.missingSourceEntryIds.join(", ")}`);
|
|
@@ -356,53 +260,36 @@ function renderMemoryText(result: Extract<RecallMemorySourcesResult, { status: "
|
|
|
356
260
|
return sections.join("\n\n");
|
|
357
261
|
}
|
|
358
262
|
|
|
359
|
-
function resultDetails(result: Extract<
|
|
360
|
-
const reflections = result.
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
const directObservationMatches = result.directObservationMatches.map((match) => observationMatchDetails(match, includeObservationSources(match)));
|
|
365
|
-
const sourceEntries = memoryLayerRecall ? result.sourceEntries.map((entry) => sourceEntryDetails(entry, includeSourceContent)) : [];
|
|
366
|
-
const unavailableSupportingObservations = result.unavailableSupportingObservations.map((item) => ({
|
|
367
|
-
reflectionId: item.reflection.id,
|
|
368
|
-
reflectionIndex: item.reflectionIndex,
|
|
369
|
-
observationId: item.observationId,
|
|
370
|
-
}));
|
|
371
|
-
const unavailableReflectionProvenance = result.unavailableReflectionProvenance.map((item) => ({
|
|
372
|
-
reflectionId: item.reflection.id,
|
|
373
|
-
reflectionIndex: item.reflectionIndex,
|
|
374
|
-
reason: item.reason,
|
|
375
|
-
}));
|
|
376
|
-
const partial = result.partial;
|
|
263
|
+
function resultDetails(result: Extract<RecallResult, { status: "found" }>, includeSourceContent = true): RecallObservationToolDetails {
|
|
264
|
+
const reflections = result.reflections.map((match) => reflectionDetails(match.reflection, match.reflectionRecordIndex));
|
|
265
|
+
const observations = result.observations.map((match) => observationMatchDetails(match, includeSourceContent));
|
|
266
|
+
const directMatches = directObservationMatches(result).map((match) => observationMatchDetails(match, includeSourceContent));
|
|
267
|
+
const sourceEntries = result.sourceEntries.map((entry) => sourceEntryDetails(entry, includeSourceContent));
|
|
377
268
|
const detailWithoutStatus = {
|
|
378
269
|
memoryId: result.memoryId,
|
|
379
270
|
observationId: result.memoryId,
|
|
380
271
|
collision: result.collision,
|
|
381
|
-
partial,
|
|
272
|
+
partial: result.partial,
|
|
382
273
|
reflections,
|
|
383
|
-
directObservationMatches,
|
|
274
|
+
directObservationMatches: directMatches,
|
|
384
275
|
observations,
|
|
385
|
-
matches:
|
|
276
|
+
matches: directMatches,
|
|
386
277
|
sourceEntries,
|
|
387
|
-
unavailableSupportingObservations,
|
|
388
|
-
unavailableReflectionProvenance,
|
|
278
|
+
unavailableSupportingObservations: result.missingSupportingObservationIds.map((observationId) => ({ observationId })),
|
|
389
279
|
missingSourceEntryIds: result.missingSourceEntryIds,
|
|
390
280
|
nonSourceEntryIds: result.nonSourceEntryIds,
|
|
391
281
|
sourceCharacterCount: renderRecallSourceEntries(result.sourceEntries).length,
|
|
392
282
|
};
|
|
393
|
-
return {
|
|
394
|
-
status: aggregateStatus(detailWithoutStatus),
|
|
395
|
-
...detailWithoutStatus,
|
|
396
|
-
};
|
|
283
|
+
return { status: aggregateStatus(detailWithoutStatus), ...detailWithoutStatus };
|
|
397
284
|
}
|
|
398
285
|
|
|
399
286
|
function isObservationOnly(details: RecallObservationToolDetails): boolean {
|
|
400
|
-
return details.reflections.length === 0 && details.unavailableSupportingObservations.length === 0
|
|
287
|
+
return details.reflections.length === 0 && details.unavailableSupportingObservations.length === 0;
|
|
401
288
|
}
|
|
402
289
|
|
|
403
|
-
function renderFoundResult(result: Extract<
|
|
290
|
+
function renderFoundResult(result: Extract<RecallResult, { status: "found" }>): ReturnType<typeof textResult> {
|
|
404
291
|
const details = resultDetails(result);
|
|
405
|
-
const text =
|
|
292
|
+
const text = result.kind === "observation" ? renderObservationOnlyTextFromResult(result) : renderMemoryText(result);
|
|
406
293
|
return textResult(text, details);
|
|
407
294
|
}
|
|
408
295
|
|
|
@@ -429,7 +316,6 @@ function observationCountForHeader(details: RecallObservationToolDetails): numbe
|
|
|
429
316
|
|
|
430
317
|
export function formatRecallHeaderForTui(details: RecallObservationToolDetails): string {
|
|
431
318
|
if (isFailureStatus(details.status)) return "× failure";
|
|
432
|
-
|
|
433
319
|
const parts = ["✓ success"];
|
|
434
320
|
if (details.reflections.length > 0) parts.push(plural(details.reflections.length, "reflection"));
|
|
435
321
|
const observations = observationCountForHeader(details);
|
|
@@ -438,6 +324,7 @@ export function formatRecallHeaderForTui(details: RecallObservationToolDetails):
|
|
|
438
324
|
if (sources.length > 0) parts.push(plural(sources.length, "source"));
|
|
439
325
|
const tokens = sources.reduce((sum, source) => sum + source.tokens, 0);
|
|
440
326
|
if (tokens > 0) parts.push(tokenSummary(tokens));
|
|
327
|
+
if (details.partial && details.status !== "ok") parts.push(details.status.replace(/_/g, " "));
|
|
441
328
|
return parts.join(" · ");
|
|
442
329
|
}
|
|
443
330
|
|
|
@@ -463,7 +350,8 @@ function sourceMetadataLine(source: RecallSourceEntryDetails): string {
|
|
|
463
350
|
}
|
|
464
351
|
|
|
465
352
|
function observationLine(observation: ObservationDetails): string {
|
|
466
|
-
|
|
353
|
+
const status = observation.status === "dropped" ? " dropped" : "";
|
|
354
|
+
return alignedRow("✓ observation", `${observation.timestamp} [${observation.relevance}]${status}`, observation.content);
|
|
467
355
|
}
|
|
468
356
|
|
|
469
357
|
function reflectionLine(reflection: ReflectionDetails): string {
|
|
@@ -475,16 +363,10 @@ function noteLine(kind: string, text: string): string {
|
|
|
475
363
|
}
|
|
476
364
|
|
|
477
365
|
function indentContent(content: string): string {
|
|
478
|
-
return content
|
|
479
|
-
.split("\n")
|
|
480
|
-
.map((line) => ` ${line}`)
|
|
481
|
-
.join("\n");
|
|
366
|
+
return content.split("\n").map((line) => ` ${line}`).join("\n");
|
|
482
367
|
}
|
|
483
368
|
|
|
484
|
-
function unavailableEvidenceMessage(
|
|
485
|
-
if (details.unavailableReflectionProvenance.length > 0 && details.observations.length === 0) {
|
|
486
|
-
return "migrated legacy reflection has no supporting observations";
|
|
487
|
-
}
|
|
369
|
+
function unavailableEvidenceMessage(_details: RecallObservationToolDetails): string {
|
|
488
370
|
return "no source entries are available for this memory id";
|
|
489
371
|
}
|
|
490
372
|
|
|
@@ -500,10 +382,7 @@ function pushSourceLines(lines: string[], sources: RecallSourceEntryDetails[], e
|
|
|
500
382
|
|
|
501
383
|
function memoryRows(details: RecallObservationToolDetails): string[] {
|
|
502
384
|
if (isObservationOnly(details)) return details.matches.map((match) => observationLine(match.observation));
|
|
503
|
-
return [
|
|
504
|
-
...details.reflections.map((reflection) => reflectionLine(reflection)),
|
|
505
|
-
...details.observations.map((observation) => observationLine(observation.observation)),
|
|
506
|
-
];
|
|
385
|
+
return [...details.reflections.map((reflection) => reflectionLine(reflection)), ...details.observations.map((observation) => observationLine(observation.observation))];
|
|
507
386
|
}
|
|
508
387
|
|
|
509
388
|
function noteRows(details: RecallObservationToolDetails, sources: RecallSourceEntryDetails[]): string[] {
|
|
@@ -517,22 +396,20 @@ function noteRows(details: RecallObservationToolDetails, sources: RecallSourceEn
|
|
|
517
396
|
return notes;
|
|
518
397
|
}
|
|
519
398
|
if (details.collision) notes.push(noteLine("id collision", `multiple memory items share ${details.memoryId}`));
|
|
520
|
-
if (
|
|
521
|
-
|
|
522
|
-
|
|
399
|
+
if (details.observations.some((match) => match.observation.status === "dropped")) notes.push(noteLine("dropped", "one or more observations are dropped from active memory but remain recallable"));
|
|
400
|
+
if (details.unavailableSupportingObservations.length > 0) notes.push(noteLine("missing support", details.unavailableSupportingObservations.map((item) => item.observationId).join(", ")));
|
|
401
|
+
if (details.missingSourceEntryIds.length > 0) notes.push(noteLine("missing source", details.missingSourceEntryIds.join(", ")));
|
|
402
|
+
if (details.nonSourceEntryIds.length > 0) notes.push(noteLine("non-source", details.nonSourceEntryIds.join(", ")));
|
|
403
|
+
if (sources.length === 0 && (details.reflections.length > 0 || details.observations.length > 0 || details.matches.length > 0)) notes.push(noteLine("unavailable evidence", unavailableEvidenceMessage(details)));
|
|
523
404
|
return notes;
|
|
524
405
|
}
|
|
525
406
|
|
|
526
407
|
export function formatRecallResultForTui(result: AgentToolResult<RecallObservationToolDetails>, expanded: boolean): string {
|
|
527
408
|
const details = result.details;
|
|
528
409
|
if (!details) {
|
|
529
|
-
const text = result.content
|
|
530
|
-
.filter((part): part is { type: "text"; text: string } => part.type === "text" && typeof part.text === "string")
|
|
531
|
-
.map((part) => part.text)
|
|
532
|
-
.join("\n");
|
|
410
|
+
const text = result.content.filter((part): part is { type: "text"; text: string } => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
|
|
533
411
|
return text || "recall";
|
|
534
412
|
}
|
|
535
|
-
|
|
536
413
|
const sources = sourceEntriesFromDetails(details);
|
|
537
414
|
const lines: string[] = [];
|
|
538
415
|
const rows = memoryRows(details);
|
|
@@ -542,10 +419,7 @@ export function formatRecallResultForTui(result: AgentToolResult<RecallObservati
|
|
|
542
419
|
lines.push(...notes);
|
|
543
420
|
if ((rows.length > 0 || notes.length > 0) && sources.length > 0) lines.push("");
|
|
544
421
|
pushSourceLines(lines, sources, expanded);
|
|
545
|
-
|
|
546
|
-
if (!expanded && sources.some((source) => source.content)) {
|
|
547
|
-
lines.push("", "(Ctrl+O to expand)");
|
|
548
|
-
}
|
|
422
|
+
if (!expanded && sources.some((source) => source.content)) lines.push("", "(Ctrl+O to expand)");
|
|
549
423
|
return lines.join("\n").trimEnd();
|
|
550
424
|
}
|
|
551
425
|
|
|
@@ -579,9 +453,7 @@ export const recallObservationTool = defineTool({
|
|
|
579
453
|
parameters: Type.Object({
|
|
580
454
|
id: Type.String({
|
|
581
455
|
pattern: "^[a-f0-9]{12}$",
|
|
582
|
-
description:
|
|
583
|
-
"12-character lowercase hex observation or reflection id shown in compacted memory, /om-view, or a previous recall result. " +
|
|
584
|
-
"Must be a specific id; this tool does not search by topic.",
|
|
456
|
+
description: "12-character lowercase hex observation or reflection id shown in compacted memory, /om-view, or a previous recall result. Must be a specific id; this tool does not search by topic.",
|
|
585
457
|
}),
|
|
586
458
|
}),
|
|
587
459
|
renderCall(args) {
|
|
@@ -596,14 +468,12 @@ export const recallObservationTool = defineTool({
|
|
|
596
468
|
const message = `Memory id must be 12 lowercase hex characters. Received: ${memoryId}`;
|
|
597
469
|
return textResult(message, emptyDetails("invalid_id", memoryId, message));
|
|
598
470
|
}
|
|
599
|
-
|
|
600
471
|
const branchEntries = ctx.sessionManager.getBranch() as Entry[];
|
|
601
472
|
const result = recallMemorySources(branchEntries, memoryId);
|
|
602
473
|
if (result.status === "not_found") {
|
|
603
474
|
const message = `No observation or reflection with id ${memoryId} was found on the current branch.`;
|
|
604
475
|
return textResult(message, emptyDetails("not_found", memoryId, message));
|
|
605
476
|
}
|
|
606
|
-
|
|
607
477
|
return renderFoundResult(result);
|
|
608
478
|
},
|
|
609
479
|
});
|