@shadowforge0/aquifer-memory 1.7.0 → 1.8.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/.env.example +8 -0
- package/README.md +66 -0
- package/aquifer.config.example.json +19 -0
- package/consumers/cli.js +192 -12
- package/consumers/codex-active-checkpoint.js +186 -0
- package/consumers/codex-current-memory.js +106 -0
- package/consumers/codex-handoff.js +442 -3
- package/consumers/codex.js +164 -107
- package/consumers/mcp.js +144 -6
- package/consumers/shared/config.js +60 -1
- package/consumers/shared/factory.js +10 -3
- package/core/aquifer.js +351 -840
- package/core/backends/capabilities.js +89 -0
- package/core/backends/local.js +430 -0
- package/core/legacy-bootstrap.js +140 -0
- package/core/mcp-manifest.js +66 -2
- package/core/memory-promotion.js +157 -26
- package/core/memory-recall.js +341 -22
- package/core/memory-records.js +128 -8
- package/core/memory-serving.js +132 -0
- package/core/postgres-migrations.js +533 -0
- package/core/public-session-filter.js +40 -0
- package/core/recall-runtime.js +115 -0
- package/core/scope-attribution.js +279 -0
- package/core/session-checkpoint-producer.js +412 -0
- package/core/session-checkpoints.js +432 -0
- package/core/session-finalization.js +82 -1
- package/core/storage-checkpoints.js +546 -0
- package/core/storage.js +121 -8
- package/docs/setup.md +22 -0
- package/package.json +8 -4
- package/schema/014-v1-checkpoint-runs.sql +349 -0
- package/schema/015-v1-evidence-items.sql +92 -0
- package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
- package/schema/017-v1-memory-record-embeddings.sql +25 -0
- package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
- package/scripts/codex-checkpoint-commands.js +464 -0
- package/scripts/codex-checkpoint-runtime.js +520 -0
- package/scripts/codex-recovery.js +105 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function compactCurrentMemoryRow(row = {}) {
|
|
4
|
+
const payload = row.payload && typeof row.payload === 'object' ? row.payload : {};
|
|
5
|
+
const confidence = payload.confidence || payload.currentMemoryConfidence || null;
|
|
6
|
+
return {
|
|
7
|
+
memoryType: row.memoryType || row.memory_type || 'memory',
|
|
8
|
+
canonicalKey: row.canonicalKey || row.canonical_key || null,
|
|
9
|
+
scopeKey: row.scopeKey || row.scope_key || null,
|
|
10
|
+
summary: String(row.summary || row.title || '').replace(/\s+/g, ' ').trim(),
|
|
11
|
+
authority: row.authority || null,
|
|
12
|
+
confidence,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function currentMemoryRows(currentMemory = null) {
|
|
17
|
+
return Array.isArray(currentMemory?.memories)
|
|
18
|
+
? currentMemory.memories
|
|
19
|
+
: (Array.isArray(currentMemory?.items) ? currentMemory.items : []);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function currentMemoryLimit(opts = {}) {
|
|
23
|
+
return Math.max(0, Math.min(20, opts.maxCurrentMemoryItems || opts.currentMemoryLimit || 12));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatCurrentMemoryPromptBlock(currentMemory = null, opts = {}) {
|
|
27
|
+
const maxItems = currentMemoryLimit(opts);
|
|
28
|
+
const meta = currentMemory && currentMemory.meta ? currentMemory.meta : {};
|
|
29
|
+
const rows = currentMemoryRows(currentMemory);
|
|
30
|
+
const compactRows = rows.map(compactCurrentMemoryRow).filter(row => row.summary).slice(0, maxItems);
|
|
31
|
+
const attrs = [
|
|
32
|
+
`source="${meta.source || 'memory_records'}"`,
|
|
33
|
+
`serving_contract="${meta.servingContract || meta.serving_contract || 'current_memory_v1'}"`,
|
|
34
|
+
`count="${compactRows.length}"`,
|
|
35
|
+
`truncated="${Boolean(meta.truncated || rows.length > compactRows.length)}"`,
|
|
36
|
+
`degraded="${Boolean(meta.degraded || currentMemory?.error)}"`,
|
|
37
|
+
];
|
|
38
|
+
const lines = compactRows.map(row => {
|
|
39
|
+
const scope = row.scopeKey ? ` scope=${row.scopeKey}` : '';
|
|
40
|
+
const authority = row.authority ? ` authority=${row.authority}` : '';
|
|
41
|
+
const confidence = row.confidence ? ` confidence=${row.confidence}` : '';
|
|
42
|
+
return `- ${row.memoryType}${scope}${authority}${confidence}: ${row.summary}`;
|
|
43
|
+
});
|
|
44
|
+
if (currentMemory && currentMemory.error && lines.length === 0) {
|
|
45
|
+
lines.push(`- degraded: ${String(currentMemory.error).replace(/\s+/g, ' ').trim()}`);
|
|
46
|
+
}
|
|
47
|
+
if (lines.length === 0) lines.push('- none');
|
|
48
|
+
return [
|
|
49
|
+
`<current_memory ${attrs.join(' ')}>`,
|
|
50
|
+
...lines,
|
|
51
|
+
'</current_memory>',
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function compactCurrentMemorySnapshot(currentMemory = null, opts = {}) {
|
|
56
|
+
const maxItems = currentMemoryLimit(opts);
|
|
57
|
+
const meta = currentMemory && currentMemory.meta ? currentMemory.meta : {};
|
|
58
|
+
const rows = currentMemoryRows(currentMemory);
|
|
59
|
+
return {
|
|
60
|
+
memories: rows.map(compactCurrentMemoryRow).filter(row => row.summary).slice(0, maxItems),
|
|
61
|
+
meta: {
|
|
62
|
+
source: meta.source || 'memory_records',
|
|
63
|
+
servingContract: meta.servingContract || meta.serving_contract || 'current_memory_v1',
|
|
64
|
+
count: Math.min(rows.length, maxItems),
|
|
65
|
+
truncated: Boolean(meta.truncated || rows.length > maxItems),
|
|
66
|
+
degraded: Boolean(meta.degraded || currentMemory?.error),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function resolveCurrentMemoryForFinalization(aquifer, opts = {}) {
|
|
72
|
+
if (opts.includeCurrentMemory === false) return null;
|
|
73
|
+
if (opts.currentMemory !== undefined) return opts.currentMemory;
|
|
74
|
+
const currentFn = aquifer?.memory?.current || aquifer?.memory?.listCurrentMemory;
|
|
75
|
+
if (typeof currentFn !== 'function') return null;
|
|
76
|
+
const limit = Math.max(1, Math.min(20, opts.currentMemoryLimit || opts.maxCurrentMemoryItems || 12));
|
|
77
|
+
try {
|
|
78
|
+
return await currentFn.call(aquifer.memory, {
|
|
79
|
+
tenantId: opts.tenantId,
|
|
80
|
+
activeScopeKey: opts.activeScopeKey || opts.scopeKey,
|
|
81
|
+
activeScopePath: opts.activeScopePath,
|
|
82
|
+
scopeId: opts.scopeId,
|
|
83
|
+
asOf: opts.asOf,
|
|
84
|
+
limit,
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return {
|
|
88
|
+
memories: [],
|
|
89
|
+
meta: {
|
|
90
|
+
source: 'memory_records',
|
|
91
|
+
servingContract: 'current_memory_v1',
|
|
92
|
+
count: 0,
|
|
93
|
+
truncated: false,
|
|
94
|
+
degraded: true,
|
|
95
|
+
},
|
|
96
|
+
error: err.message,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
compactCurrentMemoryRow,
|
|
103
|
+
formatCurrentMemoryPromptBlock,
|
|
104
|
+
compactCurrentMemorySnapshot,
|
|
105
|
+
resolveCurrentMemoryForFinalization,
|
|
106
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
3
5
|
const {
|
|
4
6
|
buildFinalizationPrompt,
|
|
5
7
|
finalizeTranscriptView,
|
|
@@ -148,23 +150,435 @@ function formatHandoffContextBlock(metadata = {}) {
|
|
|
148
150
|
return lines.join('\n');
|
|
149
151
|
}
|
|
150
152
|
|
|
153
|
+
function normalizeCheckpointItem(item) {
|
|
154
|
+
if (typeof item === 'string') return normalizeText(item);
|
|
155
|
+
return normalizeText(item && (
|
|
156
|
+
item.item
|
|
157
|
+
|| item.decision
|
|
158
|
+
|| item.state
|
|
159
|
+
|| item.constraint
|
|
160
|
+
|| item.conclusion
|
|
161
|
+
|| item.summary
|
|
162
|
+
|| item.text
|
|
163
|
+
));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function optionalNonNegativeInteger(...values) {
|
|
167
|
+
for (const value of values) {
|
|
168
|
+
if (value === undefined || value === null || value === '') continue;
|
|
169
|
+
const n = Number(value);
|
|
170
|
+
if (Number.isInteger(n) && n >= 0) return n;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function compactCheckpointRow(row = {}) {
|
|
176
|
+
const structured = row.structuredSummary || row.structured_summary || row.payload?.structuredSummary || {};
|
|
177
|
+
const payload = row.payload && typeof row.payload === 'object' ? row.payload : {};
|
|
178
|
+
const coverage = row.coverage || row.coverageMetadata || row.coverage_metadata || row.metadata?.coverage || payload.coverage || {};
|
|
179
|
+
const transcriptCoverage = coverage.transcript && typeof coverage.transcript === 'object'
|
|
180
|
+
? coverage.transcript
|
|
181
|
+
: {};
|
|
182
|
+
const summary = normalizeText(row.summaryText || row.summary_text || row.summary || row.payload?.summaryText);
|
|
183
|
+
const scopeKey = normalizeText(row.scopeKey || row.scope_key || row.targetScopeKey || row.target_scope_key);
|
|
184
|
+
const topicKey = normalizeText(row.topicKey || row.topic_key || row.topic || row.payload?.topicKey);
|
|
185
|
+
const status = normalizeText(row.status || row.lifecycle || 'accepted_process_material');
|
|
186
|
+
const trigger = normalizeText(row.trigger || row.triggerKind || row.trigger_kind || row.payload?.triggerKind);
|
|
187
|
+
const bucket = {
|
|
188
|
+
scopeKey,
|
|
189
|
+
topicKey,
|
|
190
|
+
status,
|
|
191
|
+
trigger,
|
|
192
|
+
summary,
|
|
193
|
+
decisions: [],
|
|
194
|
+
openLoops: [],
|
|
195
|
+
states: [],
|
|
196
|
+
constraints: [],
|
|
197
|
+
conclusions: [],
|
|
198
|
+
coverage: {
|
|
199
|
+
coveredUntilMessageIndex: optionalNonNegativeInteger(
|
|
200
|
+
coverage.coveredUntilMessageIndex,
|
|
201
|
+
coverage.covered_until_message_index,
|
|
202
|
+
coverage.messageIndex,
|
|
203
|
+
coverage.message_index,
|
|
204
|
+
row.coveredUntilMessageIndex,
|
|
205
|
+
row.covered_until_message_index
|
|
206
|
+
),
|
|
207
|
+
coveredUntilChar: optionalNonNegativeInteger(
|
|
208
|
+
coverage.coveredUntilChar,
|
|
209
|
+
coverage.coveredUntilCharIndex,
|
|
210
|
+
coverage.covered_until_char,
|
|
211
|
+
coverage.covered_until_char_index,
|
|
212
|
+
row.coveredUntilChar,
|
|
213
|
+
row.covered_until_char
|
|
214
|
+
),
|
|
215
|
+
coveredUntilLine: optionalNonNegativeInteger(
|
|
216
|
+
coverage.coveredUntilLine,
|
|
217
|
+
coverage.coveredUntilLineIndex,
|
|
218
|
+
coverage.covered_until_line,
|
|
219
|
+
coverage.covered_until_line_index,
|
|
220
|
+
coverage.line,
|
|
221
|
+
coverage.lineIndex,
|
|
222
|
+
coverage.line_index,
|
|
223
|
+
transcriptCoverage.coveredUntilLine,
|
|
224
|
+
transcriptCoverage.covered_until_line,
|
|
225
|
+
transcriptCoverage.line,
|
|
226
|
+
transcriptCoverage.lineIndex,
|
|
227
|
+
transcriptCoverage.line_index,
|
|
228
|
+
row.coveredUntilLine,
|
|
229
|
+
row.covered_until_line
|
|
230
|
+
),
|
|
231
|
+
coveredUntilLineChar: optionalNonNegativeInteger(
|
|
232
|
+
coverage.coveredUntilLineChar,
|
|
233
|
+
coverage.coveredUntilLineCharIndex,
|
|
234
|
+
coverage.covered_until_line_char,
|
|
235
|
+
coverage.covered_until_line_char_index,
|
|
236
|
+
coverage.char,
|
|
237
|
+
coverage.charIndex,
|
|
238
|
+
coverage.char_index,
|
|
239
|
+
transcriptCoverage.coveredUntilLineChar,
|
|
240
|
+
transcriptCoverage.covered_until_line_char,
|
|
241
|
+
transcriptCoverage.char,
|
|
242
|
+
transcriptCoverage.charIndex,
|
|
243
|
+
transcriptCoverage.char_index,
|
|
244
|
+
row.coveredUntilLineChar,
|
|
245
|
+
row.covered_until_line_char
|
|
246
|
+
),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
for (const item of normalizeList(structured.decisions || row.decisions)) {
|
|
250
|
+
const text = normalizeCheckpointItem(item);
|
|
251
|
+
if (text) addUniqueByText(bucket.decisions, { item: text }, text);
|
|
252
|
+
}
|
|
253
|
+
for (const item of normalizeList(structured.open_loops || structured.openLoops || row.openLoops || row.open_loops)) {
|
|
254
|
+
const text = normalizeCheckpointItem(item);
|
|
255
|
+
if (text) addUniqueByText(bucket.openLoops, { item: text }, text);
|
|
256
|
+
}
|
|
257
|
+
for (const item of normalizeList(structured.states || row.states)) {
|
|
258
|
+
const text = normalizeCheckpointItem(item);
|
|
259
|
+
if (text) addUniqueByText(bucket.states, { item: text }, text);
|
|
260
|
+
}
|
|
261
|
+
for (const item of normalizeList(structured.constraints || row.constraints)) {
|
|
262
|
+
const text = normalizeCheckpointItem(item);
|
|
263
|
+
if (text) addUniqueByText(bucket.constraints, { item: text }, text);
|
|
264
|
+
}
|
|
265
|
+
for (const item of normalizeList(structured.conclusions || row.conclusions)) {
|
|
266
|
+
const text = normalizeCheckpointItem(item);
|
|
267
|
+
if (text) addUniqueByText(bucket.conclusions, { item: text }, text);
|
|
268
|
+
}
|
|
269
|
+
return bucket;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function messageText(message = {}) {
|
|
273
|
+
if (typeof message.content === 'string') return message.content;
|
|
274
|
+
if (Array.isArray(message.content)) {
|
|
275
|
+
return message.content
|
|
276
|
+
.map(part => typeof part === 'string' ? part : (part && (part.text || part.content) ? String(part.text || part.content) : ''))
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
.join('\n');
|
|
279
|
+
}
|
|
280
|
+
if (typeof message.text === 'string') return message.text;
|
|
281
|
+
return '';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function renderMessages(messages = []) {
|
|
285
|
+
return messages.map((message) => {
|
|
286
|
+
const role = normalizeText(message.role) || 'message';
|
|
287
|
+
return `[${role}]\n${messageText(message)}`;
|
|
288
|
+
}).join('\n\n');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function approxPromptTokens(text) {
|
|
292
|
+
return Math.ceil(String(text || '').length / 3);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function hashText(value) {
|
|
296
|
+
return crypto.createHash('sha256').update(String(value || '')).digest('hex');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function offsetFromLineChar(text, lineIndex, charIndex) {
|
|
300
|
+
if (typeof text !== 'string') return null;
|
|
301
|
+
if (!Number.isInteger(lineIndex) || lineIndex < 0) return null;
|
|
302
|
+
if (!Number.isInteger(charIndex) || charIndex < 0) return null;
|
|
303
|
+
let offset = 0;
|
|
304
|
+
let currentLine = 0;
|
|
305
|
+
while (currentLine < lineIndex) {
|
|
306
|
+
const lineBreak = text.indexOf('\n', offset);
|
|
307
|
+
if (lineBreak === -1) return null;
|
|
308
|
+
offset = lineBreak + 1;
|
|
309
|
+
currentLine += 1;
|
|
310
|
+
}
|
|
311
|
+
const lineBreak = text.indexOf('\n', offset);
|
|
312
|
+
const lineEnd = lineBreak === -1 ? text.length : lineBreak;
|
|
313
|
+
if (charIndex > (lineEnd - offset)) return null;
|
|
314
|
+
return offset + charIndex;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function compactCheckpointSnapshot(checkpoints = [], opts = {}) {
|
|
318
|
+
const maxCheckpoints = Math.max(0, Math.min(12, opts.maxCheckpoints || opts.checkpointLimit || 6));
|
|
319
|
+
const rows = Array.isArray(checkpoints?.checkpoints)
|
|
320
|
+
? checkpoints.checkpoints
|
|
321
|
+
: (Array.isArray(checkpoints?.items) ? checkpoints.items : checkpoints);
|
|
322
|
+
const compactRows = (Array.isArray(rows) ? rows : [])
|
|
323
|
+
.map(compactCheckpointRow)
|
|
324
|
+
.filter(row => row.summary || row.decisions.length || row.openLoops.length || row.states.length || row.constraints.length || row.conclusions.length)
|
|
325
|
+
.slice(0, maxCheckpoints);
|
|
326
|
+
return {
|
|
327
|
+
checkpoints: compactRows,
|
|
328
|
+
meta: {
|
|
329
|
+
source: checkpoints?.meta?.source || 'checkpoint_runs',
|
|
330
|
+
role: 'handoff_process_material',
|
|
331
|
+
count: compactRows.length,
|
|
332
|
+
truncated: Boolean(checkpoints?.meta?.truncated || (Array.isArray(rows) && rows.length > compactRows.length)),
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function buildUncoveredTailView(view = {}, checkpoints = null) {
|
|
338
|
+
const snapshot = compactCheckpointSnapshot(checkpoints || []);
|
|
339
|
+
let coveredUntilMessageIndex = null;
|
|
340
|
+
let coveredUntilChar = null;
|
|
341
|
+
let coveredUntilLine = null;
|
|
342
|
+
let coveredUntilLineChar = null;
|
|
343
|
+
for (const row of snapshot.checkpoints) {
|
|
344
|
+
const messageIndex = row.coverage.coveredUntilMessageIndex;
|
|
345
|
+
if (Number.isInteger(messageIndex) && messageIndex >= 0) {
|
|
346
|
+
coveredUntilMessageIndex = Math.max(coveredUntilMessageIndex ?? -1, messageIndex);
|
|
347
|
+
}
|
|
348
|
+
const charIndex = row.coverage.coveredUntilChar;
|
|
349
|
+
if (Number.isInteger(charIndex) && charIndex >= 0) {
|
|
350
|
+
coveredUntilChar = Math.max(coveredUntilChar ?? -1, charIndex);
|
|
351
|
+
}
|
|
352
|
+
const lineIndex = row.coverage.coveredUntilLine;
|
|
353
|
+
const lineChar = row.coverage.coveredUntilLineChar;
|
|
354
|
+
if (Number.isInteger(lineIndex) && lineIndex >= 0 && Number.isInteger(lineChar) && lineChar >= 0) {
|
|
355
|
+
const shouldReplace = coveredUntilLine === null
|
|
356
|
+
|| lineIndex > coveredUntilLine
|
|
357
|
+
|| (lineIndex === coveredUntilLine && lineChar > coveredUntilLineChar);
|
|
358
|
+
if (shouldReplace) {
|
|
359
|
+
coveredUntilLine = lineIndex;
|
|
360
|
+
coveredUntilLineChar = lineChar;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
let bestTail = null;
|
|
365
|
+
if (Number.isInteger(coveredUntilMessageIndex) && Array.isArray(view.messages)) {
|
|
366
|
+
if (coveredUntilMessageIndex < view.messages.length) {
|
|
367
|
+
const tailMessages = view.messages.slice(coveredUntilMessageIndex + 1);
|
|
368
|
+
const text = renderMessages(tailMessages);
|
|
369
|
+
bestTail = {
|
|
370
|
+
...view,
|
|
371
|
+
messages: tailMessages,
|
|
372
|
+
text,
|
|
373
|
+
charCount: text.length,
|
|
374
|
+
approxPromptTokens: approxPromptTokens(text),
|
|
375
|
+
checkpointTail: {
|
|
376
|
+
sourceMessageCount: view.messages.length,
|
|
377
|
+
coveredUntilMessageIndex,
|
|
378
|
+
tailMessageCount: tailMessages.length,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (Number.isInteger(coveredUntilChar) && typeof view.text === 'string') {
|
|
384
|
+
if (coveredUntilChar <= view.text.length) {
|
|
385
|
+
const text = view.text.slice(coveredUntilChar);
|
|
386
|
+
const candidate = {
|
|
387
|
+
...view,
|
|
388
|
+
text,
|
|
389
|
+
charCount: text.length,
|
|
390
|
+
approxPromptTokens: approxPromptTokens(text),
|
|
391
|
+
checkpointTail: {
|
|
392
|
+
sourceCharCount: view.text.length,
|
|
393
|
+
coveredUntilChar,
|
|
394
|
+
tailCharCount: text.length,
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
if (!bestTail || candidate.text.length < bestTail.text.length) bestTail = candidate;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (Number.isInteger(coveredUntilLine) && Number.isInteger(coveredUntilLineChar) && typeof view.text === 'string') {
|
|
401
|
+
const start = offsetFromLineChar(view.text, coveredUntilLine, coveredUntilLineChar);
|
|
402
|
+
if (start !== null) {
|
|
403
|
+
const text = view.text.slice(start);
|
|
404
|
+
const candidate = {
|
|
405
|
+
...view,
|
|
406
|
+
text,
|
|
407
|
+
charCount: text.length,
|
|
408
|
+
approxPromptTokens: approxPromptTokens(text),
|
|
409
|
+
checkpointTail: {
|
|
410
|
+
sourceCharCount: view.text.length,
|
|
411
|
+
coveredUntilLine,
|
|
412
|
+
coveredUntilLineChar,
|
|
413
|
+
tailCharCount: text.length,
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
if (!bestTail || candidate.text.length < bestTail.text.length) bestTail = candidate;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return bestTail || view;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function formatCheckpointContextBlock(checkpoints = null, opts = {}) {
|
|
423
|
+
const snapshot = compactCheckpointSnapshot(checkpoints || [], opts);
|
|
424
|
+
if (snapshot.checkpoints.length === 0) return '';
|
|
425
|
+
const lines = [
|
|
426
|
+
`<checkpoint_context source="${snapshot.meta.source}" role="${snapshot.meta.role}" count="${snapshot.meta.count}" truncated="${snapshot.meta.truncated}">`,
|
|
427
|
+
'Checkpoint context is producer process material, not current truth. Use it only to reduce transcript replay and reconcile against current_memory and the uncovered transcript tail.',
|
|
428
|
+
];
|
|
429
|
+
for (const row of snapshot.checkpoints) {
|
|
430
|
+
const attrs = [
|
|
431
|
+
row.scopeKey ? `scope=${row.scopeKey}` : null,
|
|
432
|
+
row.topicKey ? `topic=${row.topicKey}` : null,
|
|
433
|
+
row.status ? `status=${row.status}` : null,
|
|
434
|
+
row.trigger ? `trigger=${row.trigger}` : null,
|
|
435
|
+
].filter(Boolean).join(' ');
|
|
436
|
+
lines.push(`checkpoint${attrs ? ` ${attrs}` : ''}: ${row.summary || 'process material'}`);
|
|
437
|
+
for (const decision of row.decisions) lines.push(` decision: ${decision.item}`);
|
|
438
|
+
for (const state of row.states) lines.push(` state: ${state.item}`);
|
|
439
|
+
for (const constraint of row.constraints) lines.push(` constraint: ${constraint.item}`);
|
|
440
|
+
for (const conclusion of row.conclusions) lines.push(` conclusion: ${conclusion.item}`);
|
|
441
|
+
for (const loop of row.openLoops) lines.push(` open_loop: ${loop.item}`);
|
|
442
|
+
}
|
|
443
|
+
lines.push('</checkpoint_context>');
|
|
444
|
+
return lines.join('\n');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function compactPreviousBootstrapContext(input = null, opts = {}) {
|
|
448
|
+
const source = input !== undefined && input !== null
|
|
449
|
+
? input
|
|
450
|
+
: (opts.previousBootstrap !== undefined ? opts.previousBootstrap : null);
|
|
451
|
+
if (!source) return null;
|
|
452
|
+
const rawText = typeof source === 'string'
|
|
453
|
+
? source
|
|
454
|
+
: normalizeText(source.text || source.context || source.sessionStartText || source.session_start_text || '');
|
|
455
|
+
const memories = Array.isArray(source.memories) ? source.memories : [];
|
|
456
|
+
const renderedMemories = memories
|
|
457
|
+
.map(item => normalizeText(item && (item.summary || item.title || item.text || item.state || item.decision || item.item)))
|
|
458
|
+
.filter(Boolean)
|
|
459
|
+
.slice(0, 12);
|
|
460
|
+
const text = rawText || renderedMemories.map(item => `- ${item}`).join('\n');
|
|
461
|
+
if (!text) return null;
|
|
462
|
+
const maxChars = Math.max(240, Math.min(6000, opts.previousBootstrapMaxChars || 3000));
|
|
463
|
+
const clipped = text.length > maxChars ? text.slice(0, maxChars) : text;
|
|
464
|
+
const meta = source && typeof source === 'object' && source.meta && typeof source.meta === 'object'
|
|
465
|
+
? source.meta
|
|
466
|
+
: {};
|
|
467
|
+
return {
|
|
468
|
+
text: clipped,
|
|
469
|
+
meta: {
|
|
470
|
+
source: 'previous_bootstrap',
|
|
471
|
+
originalSource: meta.source || source.source || null,
|
|
472
|
+
activeScopePath: Array.isArray(meta.activeScopePath) ? meta.activeScopePath : undefined,
|
|
473
|
+
truncated: text.length > clipped.length,
|
|
474
|
+
hash: hashText(text),
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function formatPreviousBootstrapContextBlock(previousBootstrap = null, opts = {}) {
|
|
480
|
+
const compact = compactPreviousBootstrapContext(previousBootstrap, opts);
|
|
481
|
+
if (!compact) return '';
|
|
482
|
+
const lines = [
|
|
483
|
+
`<previous_bootstrap_context source="${compact.meta.source}" truncated="${compact.meta.truncated}">`,
|
|
484
|
+
'Previous bootstrap context is producer process material, not current truth. Use it to reconcile what should carry forward, close, supersede, or be dropped.',
|
|
485
|
+
'Treat previous_bootstrap_context as producer process material and reconcile it against current_memory and the transcript before creating candidates.',
|
|
486
|
+
compact.text,
|
|
487
|
+
'</previous_bootstrap_context>',
|
|
488
|
+
];
|
|
489
|
+
return lines.join('\n');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function resolveCheckpointsForHandoff(aquifer, payload = {}, opts = {}) {
|
|
493
|
+
if (opts.includeCheckpoints === false) return null;
|
|
494
|
+
const provided = opts.checkpoints !== undefined ? opts.checkpoints : payload.checkpoints;
|
|
495
|
+
if (provided !== undefined) return compactCheckpointSnapshot(provided, opts);
|
|
496
|
+
const listFn = aquifer?.checkpoints?.listForHandoff || aquifer?.checkpoints?.listAcceptedForHandoff;
|
|
497
|
+
if (typeof listFn !== 'function') return null;
|
|
498
|
+
try {
|
|
499
|
+
const rows = await listFn.call(aquifer.checkpoints, {
|
|
500
|
+
tenantId: opts.tenantId,
|
|
501
|
+
sessionId: payload.sessionId || opts.sessionId,
|
|
502
|
+
activeScopeKey: opts.activeScopeKey || opts.scopeKey,
|
|
503
|
+
activeScopePath: opts.activeScopePath,
|
|
504
|
+
limit: opts.checkpointLimit || opts.maxCheckpoints || 6,
|
|
505
|
+
});
|
|
506
|
+
return compactCheckpointSnapshot(rows, opts);
|
|
507
|
+
} catch (err) {
|
|
508
|
+
return {
|
|
509
|
+
checkpoints: [],
|
|
510
|
+
meta: {
|
|
511
|
+
source: 'checkpoint_runs',
|
|
512
|
+
role: 'handoff_process_material',
|
|
513
|
+
count: 0,
|
|
514
|
+
truncated: false,
|
|
515
|
+
degraded: true,
|
|
516
|
+
error: err.message,
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function resolvePreviousBootstrapForHandoff(aquifer, payload = {}, opts = {}) {
|
|
523
|
+
if (opts.includePreviousBootstrap === false) return null;
|
|
524
|
+
if (opts.previousBootstrap !== undefined) return compactPreviousBootstrapContext(opts.previousBootstrap, opts);
|
|
525
|
+
if (payload.previousBootstrap !== undefined) return compactPreviousBootstrapContext(payload.previousBootstrap, opts);
|
|
526
|
+
|
|
527
|
+
const bootstrapFn = typeof aquifer?.memory?.bootstrap === 'function'
|
|
528
|
+
? aquifer.memory.bootstrap
|
|
529
|
+
: (typeof aquifer?.bootstrap === 'function' ? aquifer.bootstrap : null);
|
|
530
|
+
if (typeof bootstrapFn !== 'function') return null;
|
|
531
|
+
|
|
532
|
+
const bootstrapOwner = typeof aquifer?.memory?.bootstrap === 'function' ? aquifer.memory : aquifer;
|
|
533
|
+
const bootstrapOpts = {
|
|
534
|
+
tenantId: opts.tenantId,
|
|
535
|
+
scopeId: opts.scopeId,
|
|
536
|
+
activeScopeKey: opts.activeScopeKey || opts.scopeKey,
|
|
537
|
+
activeScopePath: opts.activeScopePath,
|
|
538
|
+
asOf: opts.previousBootstrapAsOf || opts.asOf,
|
|
539
|
+
limit: opts.previousBootstrapLimit || opts.bootstrapLimit || 20,
|
|
540
|
+
maxChars: opts.previousBootstrapMaxChars || 3000,
|
|
541
|
+
format: 'both',
|
|
542
|
+
};
|
|
543
|
+
if (bootstrapOwner === aquifer) bootstrapOpts.servingMode = 'curated';
|
|
544
|
+
try {
|
|
545
|
+
const result = await bootstrapFn.call(bootstrapOwner, bootstrapOpts);
|
|
546
|
+
return compactPreviousBootstrapContext(result, opts);
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
151
552
|
function buildHandoffSynthesisPrompt(payload = {}, view = {}, opts = {}) {
|
|
152
553
|
if (!view || view.status !== 'ok') {
|
|
153
554
|
throw new Error(`Codex handoff synthesis requires an ok normalized transcript view; got ${view && view.status ? view.status : 'missing'}`);
|
|
154
555
|
}
|
|
155
556
|
const metadata = buildHandoffMetadata(payload);
|
|
156
|
-
const
|
|
557
|
+
const checkpoints = opts.checkpoints || payload.checkpoints;
|
|
558
|
+
const previousBootstrap = opts.previousBootstrap !== undefined ? opts.previousBootstrap : payload.previousBootstrap;
|
|
559
|
+
const promptView = buildUncoveredTailView(view, checkpoints);
|
|
560
|
+
const basePrompt = buildFinalizationPrompt(promptView, opts);
|
|
561
|
+
const checkpointBlock = formatCheckpointContextBlock(checkpoints, opts);
|
|
562
|
+
const previousBootstrapBlock = formatPreviousBootstrapContextBlock(previousBootstrap, opts);
|
|
157
563
|
const handoffBlock = [
|
|
564
|
+
checkpointBlock,
|
|
565
|
+
checkpointBlock ? '' : null,
|
|
566
|
+
previousBootstrapBlock,
|
|
567
|
+
previousBootstrapBlock ? '' : null,
|
|
158
568
|
formatHandoffContextBlock(metadata),
|
|
159
569
|
'',
|
|
160
570
|
'<handoff_synthesis_rules>',
|
|
161
571
|
'Treat handoff_context as producer process material, not current truth by itself.',
|
|
572
|
+
'Treat checkpoint_context as producer process material, not current truth by itself.',
|
|
573
|
+
'Treat previous_bootstrap_context as producer process material, not current truth by itself.',
|
|
162
574
|
'Use the sanitized transcript and current_memory to decide what should become current memory candidates.',
|
|
575
|
+
'When checkpoint_context is present, use it to avoid replaying already-covered session ranges, but reconcile it against the transcript tail instead of promoting it directly.',
|
|
576
|
+
'When previous_bootstrap_context is present, use it only to reconcile carry-forward intent; do not copy it directly into current memory candidates.',
|
|
163
577
|
'Do not copy old current_memory unchanged unless this session confirms it should carry forward.',
|
|
164
578
|
'Represent resolved, superseded, revoked, or uncertain items explicitly in structuredSummary payload fields when applicable.',
|
|
165
579
|
'Do not include raw transcript, tool output, debug ids, DB ids, hashes, secrets, or injected context in memory candidates.',
|
|
166
580
|
'</handoff_synthesis_rules>',
|
|
167
|
-
].join('\n');
|
|
581
|
+
].filter(line => line !== null && line !== undefined).join('\n');
|
|
168
582
|
return basePrompt.replace('<sanitized_transcript>', `${handoffBlock}\n\n<sanitized_transcript>`);
|
|
169
583
|
}
|
|
170
584
|
|
|
@@ -174,12 +588,16 @@ async function prepareHandoffSynthesis(aquifer, payload = {}, opts = {}) {
|
|
|
174
588
|
throw new Error(`Codex handoff synthesis requires an ok normalized transcript view; got ${view && view.status ? view.status : 'missing'}`);
|
|
175
589
|
}
|
|
176
590
|
const currentMemory = await resolveCurrentMemoryForFinalization(aquifer, opts);
|
|
591
|
+
const checkpoints = await resolveCheckpointsForHandoff(aquifer, payload, opts);
|
|
592
|
+
const previousBootstrap = await resolvePreviousBootstrapForHandoff(aquifer, payload, opts);
|
|
177
593
|
return {
|
|
178
594
|
status: 'needs_agent_summary',
|
|
179
595
|
outputSchemaVersion: 'handoff_current_memory_synthesis_v1',
|
|
180
596
|
view,
|
|
181
597
|
currentMemory,
|
|
182
|
-
|
|
598
|
+
checkpoints,
|
|
599
|
+
previousBootstrap,
|
|
600
|
+
prompt: buildHandoffSynthesisPrompt(payload, view, { ...opts, currentMemory, checkpoints, previousBootstrap }),
|
|
183
601
|
};
|
|
184
602
|
}
|
|
185
603
|
|
|
@@ -201,7 +619,20 @@ async function finalizeHandoff(aquifer, payload = {}, opts = {}) {
|
|
|
201
619
|
};
|
|
202
620
|
}
|
|
203
621
|
const currentMemory = await resolveCurrentMemoryForFinalization(aquifer, opts);
|
|
622
|
+
const checkpoints = await resolveCheckpointsForHandoff(aquifer, payload, opts);
|
|
623
|
+
const previousBootstrap = await resolvePreviousBootstrapForHandoff(aquifer, payload, opts);
|
|
204
624
|
if (currentMemory) metadata.currentMemory = compactCurrentMemorySnapshot(currentMemory, opts);
|
|
625
|
+
if (checkpoints) metadata.checkpoints = checkpoints;
|
|
626
|
+
const candidateEnvelope = usedSynthesis
|
|
627
|
+
? {
|
|
628
|
+
version: 'handoff_current_memory_synthesis_v1',
|
|
629
|
+
inputContext: {
|
|
630
|
+
previousBootstrap: previousBootstrap ? previousBootstrap.meta : null,
|
|
631
|
+
checkpoints: checkpoints ? checkpoints.meta : null,
|
|
632
|
+
currentMemory: metadata.currentMemory ? metadata.currentMemory.meta : null,
|
|
633
|
+
},
|
|
634
|
+
}
|
|
635
|
+
: opts.candidateEnvelope || null;
|
|
205
636
|
const result = await finalizeTranscriptView(aquifer, view, summary, {
|
|
206
637
|
...opts,
|
|
207
638
|
mode: 'handoff',
|
|
@@ -209,6 +640,8 @@ async function finalizeHandoff(aquifer, payload = {}, opts = {}) {
|
|
|
209
640
|
metadata,
|
|
210
641
|
authority: opts.authority || (usedSynthesis ? 'verified_summary' : 'manual'),
|
|
211
642
|
candidates,
|
|
643
|
+
candidateEnvelope,
|
|
644
|
+
coverage: opts.coverage || payload.coverage || null,
|
|
212
645
|
candidatePayload: usedSynthesis
|
|
213
646
|
? {
|
|
214
647
|
kind: 'handoff_synthesis',
|
|
@@ -252,6 +685,12 @@ async function finalizeHandoff(aquifer, payload = {}, opts = {}) {
|
|
|
252
685
|
module.exports = {
|
|
253
686
|
buildHandoffMetadata,
|
|
254
687
|
buildHandoffSynthesisPrompt,
|
|
688
|
+
compactCheckpointSnapshot,
|
|
689
|
+
buildUncoveredTailView,
|
|
690
|
+
formatCheckpointContextBlock,
|
|
691
|
+
compactPreviousBootstrapContext,
|
|
692
|
+
formatPreviousBootstrapContextBlock,
|
|
693
|
+
resolvePreviousBootstrapForHandoff,
|
|
255
694
|
prepareHandoffSynthesis,
|
|
256
695
|
resolveHandoffSummary,
|
|
257
696
|
finalizeHandoff,
|