@shadowforge0/aquifer-memory 1.6.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 +72 -0
- package/README_CN.md +17 -0
- package/README_TW.md +4 -0
- package/aquifer.config.example.json +19 -0
- package/consumers/cli.js +259 -12
- package/consumers/codex-active-checkpoint.js +186 -0
- package/consumers/codex-current-memory.js +106 -0
- package/consumers/codex-handoff.js +551 -6
- package/consumers/codex.js +209 -25
- package/consumers/mcp.js +144 -6
- package/consumers/shared/config.js +60 -1
- package/consumers/shared/factory.js +10 -3
- package/core/aquifer.js +357 -838
- 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-bootstrap.js +20 -8
- package/core/memory-consolidation.js +365 -11
- package/core/memory-promotion.js +157 -26
- package/core/memory-recall.js +341 -22
- package/core/memory-records.js +347 -11
- 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 +98 -2
- package/core/storage-checkpoints.js +546 -0
- package/core/storage.js +121 -8
- package/docs/getting-started.md +6 -0
- package/docs/setup.md +66 -3
- 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 +246 -1
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
buildFinalizationPrompt,
|
|
7
|
+
finalizeTranscriptView,
|
|
8
|
+
resolveCurrentMemoryForFinalization,
|
|
9
|
+
compactCurrentMemorySnapshot,
|
|
10
|
+
} = require('./codex');
|
|
4
11
|
const { buildFinalizationReview } = require('../core/finalization-review');
|
|
5
12
|
|
|
6
13
|
function normalizeText(value) {
|
|
@@ -95,25 +102,554 @@ function buildHandoffMetadata(payload = {}) {
|
|
|
95
102
|
};
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
function resolveHandoffSummary(payload = {}, opts = {}) {
|
|
106
|
+
const synthesisSummary = opts.synthesisSummary
|
|
107
|
+
|| opts.handoffSynthesisSummary
|
|
108
|
+
|| payload.synthesisSummary
|
|
109
|
+
|| payload.handoffSynthesisSummary
|
|
110
|
+
|| null;
|
|
111
|
+
if (synthesisSummary) {
|
|
112
|
+
return {
|
|
113
|
+
summary: synthesisSummary,
|
|
114
|
+
candidates: Array.isArray(synthesisSummary.candidates) ? synthesisSummary.candidates : undefined,
|
|
115
|
+
usedSynthesis: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
summary: opts.summary || payload.summary || {
|
|
120
|
+
summaryText: opts.summaryText || payload.summaryText,
|
|
121
|
+
structuredSummary: opts.structuredSummary || payload.structuredSummary,
|
|
122
|
+
},
|
|
123
|
+
candidates: Array.isArray(opts.candidates) ? opts.candidates : undefined,
|
|
124
|
+
usedSynthesis: false,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function formatHandoffContextBlock(metadata = {}) {
|
|
129
|
+
const handoff = metadata.handoff || {};
|
|
130
|
+
const lines = [
|
|
131
|
+
`<handoff_context source="${metadata.source || 'codex_handoff'}">`,
|
|
132
|
+
`title: ${handoff.title || 'untitled'}`,
|
|
133
|
+
`overview: ${handoff.overview || 'none'}`,
|
|
134
|
+
`status: ${handoff.status || 'unknown'}`,
|
|
135
|
+
`lastStep: ${handoff.lastStep || 'none'}`,
|
|
136
|
+
`next: ${handoff.next || 'none'}`,
|
|
137
|
+
];
|
|
138
|
+
for (const decision of handoff.decisions || []) {
|
|
139
|
+
lines.push(`decision: ${decision.decision}${decision.reason ? ` | ${decision.reason}` : ''}`);
|
|
140
|
+
}
|
|
141
|
+
for (const loop of handoff.openLoops || []) {
|
|
142
|
+
lines.push(`open_loop: ${loop.item}${loop.owner ? ` | owner=${loop.owner}` : ''}`);
|
|
143
|
+
}
|
|
144
|
+
for (const topic of handoff.topics || []) {
|
|
145
|
+
const name = normalizeText(topic && (topic.name || topic.topic || topic.title));
|
|
146
|
+
const summary = normalizeText(topic && (topic.summary || topic.text));
|
|
147
|
+
if (name || summary) lines.push(`topic: ${name || 'topic'}${summary ? ` | ${summary}` : ''}`);
|
|
148
|
+
}
|
|
149
|
+
lines.push('</handoff_context>');
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
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
|
+
|
|
552
|
+
function buildHandoffSynthesisPrompt(payload = {}, view = {}, opts = {}) {
|
|
553
|
+
if (!view || view.status !== 'ok') {
|
|
554
|
+
throw new Error(`Codex handoff synthesis requires an ok normalized transcript view; got ${view && view.status ? view.status : 'missing'}`);
|
|
555
|
+
}
|
|
556
|
+
const metadata = buildHandoffMetadata(payload);
|
|
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);
|
|
563
|
+
const handoffBlock = [
|
|
564
|
+
checkpointBlock,
|
|
565
|
+
checkpointBlock ? '' : null,
|
|
566
|
+
previousBootstrapBlock,
|
|
567
|
+
previousBootstrapBlock ? '' : null,
|
|
568
|
+
formatHandoffContextBlock(metadata),
|
|
569
|
+
'',
|
|
570
|
+
'<handoff_synthesis_rules>',
|
|
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.',
|
|
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.',
|
|
577
|
+
'Do not copy old current_memory unchanged unless this session confirms it should carry forward.',
|
|
578
|
+
'Represent resolved, superseded, revoked, or uncertain items explicitly in structuredSummary payload fields when applicable.',
|
|
579
|
+
'Do not include raw transcript, tool output, debug ids, DB ids, hashes, secrets, or injected context in memory candidates.',
|
|
580
|
+
'</handoff_synthesis_rules>',
|
|
581
|
+
].filter(line => line !== null && line !== undefined).join('\n');
|
|
582
|
+
return basePrompt.replace('<sanitized_transcript>', `${handoffBlock}\n\n<sanitized_transcript>`);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async function prepareHandoffSynthesis(aquifer, payload = {}, opts = {}) {
|
|
586
|
+
const view = opts.view || payload.view;
|
|
587
|
+
if (!view || view.status !== 'ok') {
|
|
588
|
+
throw new Error(`Codex handoff synthesis requires an ok normalized transcript view; got ${view && view.status ? view.status : 'missing'}`);
|
|
589
|
+
}
|
|
590
|
+
const currentMemory = await resolveCurrentMemoryForFinalization(aquifer, opts);
|
|
591
|
+
const checkpoints = await resolveCheckpointsForHandoff(aquifer, payload, opts);
|
|
592
|
+
const previousBootstrap = await resolvePreviousBootstrapForHandoff(aquifer, payload, opts);
|
|
593
|
+
return {
|
|
594
|
+
status: 'needs_agent_summary',
|
|
595
|
+
outputSchemaVersion: 'handoff_current_memory_synthesis_v1',
|
|
596
|
+
view,
|
|
597
|
+
currentMemory,
|
|
598
|
+
checkpoints,
|
|
599
|
+
previousBootstrap,
|
|
600
|
+
prompt: buildHandoffSynthesisPrompt(payload, view, { ...opts, currentMemory, checkpoints, previousBootstrap }),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
98
604
|
async function finalizeHandoff(aquifer, payload = {}, opts = {}) {
|
|
99
605
|
const view = opts.view || payload.view;
|
|
100
606
|
if (!view || view.status !== 'ok') {
|
|
101
607
|
throw new Error(`Codex handoff finalization requires an ok normalized transcript view; got ${view && view.status ? view.status : 'missing'}`);
|
|
102
608
|
}
|
|
103
|
-
const summary
|
|
104
|
-
summaryText: opts.summaryText || payload.summaryText,
|
|
105
|
-
structuredSummary: opts.structuredSummary || payload.structuredSummary,
|
|
106
|
-
};
|
|
609
|
+
const { summary, candidates, usedSynthesis } = resolveHandoffSummary(payload, opts);
|
|
107
610
|
const metadata = {
|
|
108
611
|
...buildHandoffMetadata(payload),
|
|
109
612
|
...(opts.metadata || {}),
|
|
110
613
|
};
|
|
614
|
+
if (usedSynthesis) {
|
|
615
|
+
metadata.handoffSynthesis = {
|
|
616
|
+
kind: 'handoff_current_memory_synthesis_v1',
|
|
617
|
+
source: 'operator_reviewed_summary',
|
|
618
|
+
promotionGate: 'core_finalization',
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
const currentMemory = await resolveCurrentMemoryForFinalization(aquifer, opts);
|
|
622
|
+
const checkpoints = await resolveCheckpointsForHandoff(aquifer, payload, opts);
|
|
623
|
+
const previousBootstrap = await resolvePreviousBootstrapForHandoff(aquifer, payload, opts);
|
|
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;
|
|
111
636
|
const result = await finalizeTranscriptView(aquifer, view, summary, {
|
|
112
637
|
...opts,
|
|
113
638
|
mode: 'handoff',
|
|
114
639
|
metadataSource: 'codex_handoff',
|
|
115
640
|
metadata,
|
|
116
|
-
authority: opts.authority || 'manual',
|
|
641
|
+
authority: opts.authority || (usedSynthesis ? 'verified_summary' : 'manual'),
|
|
642
|
+
candidates,
|
|
643
|
+
candidateEnvelope,
|
|
644
|
+
coverage: opts.coverage || payload.coverage || null,
|
|
645
|
+
candidatePayload: usedSynthesis
|
|
646
|
+
? {
|
|
647
|
+
kind: 'handoff_synthesis',
|
|
648
|
+
synthesisKind: 'handoff_current_memory_synthesis_v1',
|
|
649
|
+
currentMemoryRole: 'handoff_synthesis_candidate',
|
|
650
|
+
promotionGate: 'core_finalization',
|
|
651
|
+
}
|
|
652
|
+
: opts.candidatePayload || null,
|
|
117
653
|
});
|
|
118
654
|
const coreResult = result.finalization || {};
|
|
119
655
|
const finalSummary = coreResult.summary || {
|
|
@@ -148,5 +684,14 @@ async function finalizeHandoff(aquifer, payload = {}, opts = {}) {
|
|
|
148
684
|
|
|
149
685
|
module.exports = {
|
|
150
686
|
buildHandoffMetadata,
|
|
687
|
+
buildHandoffSynthesisPrompt,
|
|
688
|
+
compactCheckpointSnapshot,
|
|
689
|
+
buildUncoveredTailView,
|
|
690
|
+
formatCheckpointContextBlock,
|
|
691
|
+
compactPreviousBootstrapContext,
|
|
692
|
+
formatPreviousBootstrapContextBlock,
|
|
693
|
+
resolvePreviousBootstrapForHandoff,
|
|
694
|
+
prepareHandoffSynthesis,
|
|
695
|
+
resolveHandoffSummary,
|
|
151
696
|
finalizeHandoff,
|
|
152
697
|
};
|