-
Notifications
You must be signed in to change notification settings - Fork 659
Expand file tree
/
Copy pathcompaction-recovery.js
More file actions
96 lines (90 loc) · 3.59 KB
/
Copy pathcompaction-recovery.js
File metadata and controls
96 lines (90 loc) · 3.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
const SUMMARY_FIELD_PATHS = [
// Current Pi session_compact event shape:
// { compactionEntry: CompactionEntry, fromExtension, reason, willRetry }
// where CompactionEntry.summary holds the summary text. Listed first so the
// documented shape wins over generic legacy fallbacks.
["compactionEntry", "summary"],
["summary"],
["compactedSummary"],
["compacted_summary"],
["compactSummary"],
["compact_summary"],
["content"],
["text"],
["message"],
["compacted", "summary"],
["compacted", "content"],
["compaction", "summary"],
["compaction", "content"],
["output", "summary"],
["output", "content"],
["payload", "summary"],
["payload", "content"],
["data", "summary"],
["data", "content"],
];
function getPath(root, path) {
let current = root;
for (const key of path) {
if (!current || typeof current !== "object" || !(key in current)) return undefined;
current = current[key];
}
return current;
}
function normalizeSummary(value) {
if (typeof value !== "string") return undefined;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
/**
* Best-effort extraction for Pi compaction event shapes. Unsupported shapes
* intentionally return undefined instead of throwing.
*/
export function extractCompactedSummary(event) {
if (!event || typeof event !== "object") return undefined;
for (const path of SUMMARY_FIELD_PATHS) {
const summary = normalizeSummary(getPath(event, path));
if (summary) return summary;
}
return undefined;
}
/**
* Manual fallback used when the compaction summary could NOT be archived to
* Engram during the session_compact handler (no summary extracted, no session,
* or the Engram write failed). Asks the agent to save it on the next turn.
*/
export function recoveryInstruction(project) {
return (
`CRITICAL INSTRUCTION FOR COMPACTED SUMMARY:\n` +
`The agent has access to Engram persistent memory via MCP tools when gentle-engram and the Engram MCP tools are installed and active.\n` +
`FIRST ACTION REQUIRED: Call mem_session_summary with the content of this compacted summary. ` +
`Use project: '${project}'. This preserves what was accomplished before compaction. Do this BEFORE any other work.\n` +
`If mem_session_summary is unavailable, manually save this compacted summary once Engram tools are available.`
);
}
/**
* Used when the compaction summary was already archived to Engram by
* gentle-engram. Still surfaces retrieved project context for continuity, but
* omits the manual `FIRST ACTION REQUIRED: mem_session_summary` instruction so
* the agent does not duplicate the save it just completed.
*/
function persistedAcknowledgement(project) {
return (
`Compaction recovery summary was already saved to Engram (${project}) by gentle-engram. ` +
`No manual mem_session_summary call is needed for this compaction. ` +
`Call mem_context if you need additional recent project memory.`
);
}
/**
* Build the recovery notice injected before the next agent turn.
*
* @param {string} project Engram project name.
* @param {string|undefined} context Retrieved Engram project context, if any.
* @param {boolean} [persisted=false] True when the compaction summary was
* already archived to Engram; suppresses the manual save instruction.
*/
export function buildRecoveryNotice(project, context, persisted = false) {
const instruction = persisted ? persistedAcknowledgement(project) : recoveryInstruction(project);
const trimmedContext = typeof context === "string" ? context.trim() : "";
return trimmedContext ? `${trimmedContext}\n\n${instruction}` : instruction;
}