@shadowforge0/aquifer-memory 1.8.1 → 1.9.1
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 +1 -0
- package/README.md +82 -26
- package/README_CN.md +33 -23
- package/README_TW.md +25 -24
- package/aquifer.config.example.json +2 -1
- package/consumers/cli.js +587 -33
- package/consumers/codex-active-checkpoint.js +3 -1
- package/consumers/codex-current-memory.js +10 -6
- package/consumers/codex.js +6 -3
- package/consumers/default/daily-entries.js +2 -2
- package/consumers/default/index.js +40 -30
- package/consumers/default/prompts/summary.js +2 -2
- package/consumers/mcp.js +56 -46
- package/consumers/openclaw-ext/index.js +65 -7
- package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
- package/consumers/openclaw-ext/package.json +1 -1
- package/consumers/openclaw-install.js +326 -0
- package/consumers/openclaw-plugin.js +105 -24
- package/consumers/shared/compat-recall.js +101 -0
- package/consumers/shared/config.js +2 -0
- package/consumers/shared/openclaw-product-tools.js +130 -0
- package/consumers/shared/recall-format.js +2 -2
- package/core/aquifer.js +553 -41
- package/core/backends/local.js +169 -1
- package/core/doctor.js +924 -0
- package/core/finalization-inspector.js +164 -0
- package/core/finalization-review.js +88 -42
- package/core/interface.js +629 -0
- package/core/mcp-manifest.js +11 -3
- package/core/memory-bootstrap.js +25 -27
- package/core/memory-consolidation.js +564 -42
- package/core/memory-explain.js +593 -0
- package/core/memory-promotion.js +392 -55
- package/core/memory-recall.js +75 -71
- package/core/memory-records.js +107 -108
- package/core/memory-review.js +891 -0
- package/core/memory-serving.js +61 -4
- package/core/memory-type-policy.js +298 -0
- package/core/operator-observability.js +249 -0
- package/core/postgres-migrations.js +22 -0
- package/core/session-checkpoint-producer.js +3 -1
- package/core/session-checkpoints.js +1 -1
- package/core/session-finalization.js +78 -3
- package/core/storage.js +124 -8
- package/docs/getting-started.md +50 -4
- package/docs/setup.md +163 -24
- package/package.json +5 -4
- package/schema/004-completion.sql +4 -4
- package/schema/010-v1-finalization-review.sql +72 -0
- package/schema/019-v1-memory-review-resolutions.sql +53 -0
- package/schema/020-v1-assistant-shaping-memory.sql +30 -0
- package/scripts/backfill-canonical-key.js +1 -1
- package/scripts/codex-checkpoint-commands.js +28 -0
- package/scripts/codex-checkpoint-runtime.js +109 -0
- package/scripts/codex-recovery.js +16 -4
- package/scripts/diagnose-fts-zh.js +1 -1
- package/scripts/extract-insights-from-recent-sessions.js +4 -4
package/core/aquifer.js
CHANGED
|
@@ -14,11 +14,243 @@ const { createMemoryServingRuntime } = require('./memory-serving');
|
|
|
14
14
|
const { createLegacyBootstrap } = require('./legacy-bootstrap');
|
|
15
15
|
const { buildRerankDocument, resolveEmbedFn, shouldAutoRerank } = require('./recall-runtime');
|
|
16
16
|
const { filterPublicPlaceholderSessionRows } = require('./public-session-filter');
|
|
17
|
+
const { buildBacklogProductStatus, buildReadinessSurface } = require('./interface');
|
|
17
18
|
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// createAquifer
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
|
|
23
|
+
const ACTIONABLE_PENDING_STATUSES = new Set(['pending', 'failed']);
|
|
24
|
+
const TERMINAL_FINALIZATION_STATUSES = ['finalized', 'skipped', 'declined', 'deferred'];
|
|
25
|
+
|
|
26
|
+
function normalizePendingWorkLimit(value, fallback = 100, max = 500) {
|
|
27
|
+
const parsed = parseInt(value, 10);
|
|
28
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
29
|
+
return Math.min(parsed, max);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizePendingWorkStatuses(value) {
|
|
33
|
+
const input = Array.isArray(value)
|
|
34
|
+
? value
|
|
35
|
+
: String(value || '').split(',');
|
|
36
|
+
const statuses = input.map(item => String(item || '').trim()).filter(Boolean);
|
|
37
|
+
const normalized = statuses.length > 0 ? statuses : Array.from(ACTIONABLE_PENDING_STATUSES);
|
|
38
|
+
for (const status of normalized) {
|
|
39
|
+
if (!ACTIONABLE_PENDING_STATUSES.has(status)) {
|
|
40
|
+
throw new Error(`pending work status must be one of: ${Array.from(ACTIONABLE_PENDING_STATUSES).join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return [...new Set(normalized)];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizePendingWorkAction(value) {
|
|
47
|
+
const action = String(value || 'inspect').trim();
|
|
48
|
+
if (!['inspect', 'backfill', 'skip'].includes(action)) {
|
|
49
|
+
throw new Error('pending work action must be inspect, backfill, or skip');
|
|
50
|
+
}
|
|
51
|
+
return action;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pluralize(count, singular, plural = `${singular}s`) {
|
|
55
|
+
return count === 1 ? singular : plural;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizePendingWorkRow(row = {}) {
|
|
59
|
+
return {
|
|
60
|
+
sessionId: row.session_id,
|
|
61
|
+
sessionKey: row.session_key || null,
|
|
62
|
+
source: row.source || 'api',
|
|
63
|
+
agentId: row.agent_id,
|
|
64
|
+
status: row.processing_status,
|
|
65
|
+
processingError: row.processing_error || null,
|
|
66
|
+
startedAt: row.started_at || null,
|
|
67
|
+
lastMessageAt: row.last_message_at || null,
|
|
68
|
+
msgCount: row.msg_count || 0,
|
|
69
|
+
userCount: row.user_count || 0,
|
|
70
|
+
model: row.model || null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizePendingWorkGroup(row = {}) {
|
|
75
|
+
return {
|
|
76
|
+
source: row.source || 'api',
|
|
77
|
+
agentId: row.agent_id,
|
|
78
|
+
status: row.processing_status,
|
|
79
|
+
count: row.count || 0,
|
|
80
|
+
earliest: row.earliest || null,
|
|
81
|
+
latest: row.latest || null,
|
|
82
|
+
msgCount: row.msg_count || 0,
|
|
83
|
+
userCount: row.user_count || 0,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildPendingWorkGuidance(groups = []) {
|
|
88
|
+
const statuses = new Set(groups.map(group => group.status));
|
|
89
|
+
const guidance = [];
|
|
90
|
+
if (statuses.has('pending')) {
|
|
91
|
+
guidance.push({
|
|
92
|
+
status: 'pending',
|
|
93
|
+
recommendedAction: 'backfill',
|
|
94
|
+
alternateAction: 'skip',
|
|
95
|
+
reason: 'Pending sessions can be enriched; skip is reserved for deterministic policy suppression.',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (statuses.has('failed')) {
|
|
99
|
+
guidance.push({
|
|
100
|
+
status: 'failed',
|
|
101
|
+
recommendedAction: 'backfill',
|
|
102
|
+
alternateAction: 'inspect_error',
|
|
103
|
+
reason: 'Failed sessions are retryable work; session skip only mutates rows still in pending status.',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return guidance;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildPendingWorkPlan(samples = [], action = 'inspect', groups = []) {
|
|
110
|
+
const totalByStatus = {};
|
|
111
|
+
for (const group of groups || []) {
|
|
112
|
+
totalByStatus[group.status] = (totalByStatus[group.status] || 0) + (group.count || 0);
|
|
113
|
+
}
|
|
114
|
+
const plan = {
|
|
115
|
+
action,
|
|
116
|
+
dryRunOnly: true,
|
|
117
|
+
allowed: 0,
|
|
118
|
+
blocked: 0,
|
|
119
|
+
changes: [],
|
|
120
|
+
notes: [],
|
|
121
|
+
};
|
|
122
|
+
if (action === 'inspect') {
|
|
123
|
+
plan.notes.push('No lifecycle changes planned.');
|
|
124
|
+
return plan;
|
|
125
|
+
}
|
|
126
|
+
if (action === 'backfill') {
|
|
127
|
+
plan.allowed = Object.values(totalByStatus).reduce((sum, count) => sum + count, 0);
|
|
128
|
+
} else if (action === 'skip') {
|
|
129
|
+
plan.allowed = totalByStatus.pending || 0;
|
|
130
|
+
plan.blocked = totalByStatus.failed || 0;
|
|
131
|
+
}
|
|
132
|
+
for (const row of samples) {
|
|
133
|
+
if (action === 'backfill') {
|
|
134
|
+
plan.changes.push({
|
|
135
|
+
sessionId: row.sessionId,
|
|
136
|
+
source: row.source,
|
|
137
|
+
agentId: row.agentId,
|
|
138
|
+
status: row.status,
|
|
139
|
+
operation: 'enrich',
|
|
140
|
+
allowed: true,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (action === 'skip' && row.status === 'pending') {
|
|
145
|
+
plan.changes.push({
|
|
146
|
+
sessionId: row.sessionId,
|
|
147
|
+
source: row.source,
|
|
148
|
+
agentId: row.agentId,
|
|
149
|
+
status: row.status,
|
|
150
|
+
operation: 'mark_session_skipped',
|
|
151
|
+
allowed: true,
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
plan.changes.push({
|
|
156
|
+
sessionId: row.sessionId,
|
|
157
|
+
source: row.source,
|
|
158
|
+
agentId: row.agentId,
|
|
159
|
+
status: row.status,
|
|
160
|
+
operation: action,
|
|
161
|
+
allowed: false,
|
|
162
|
+
reason: action === 'skip'
|
|
163
|
+
? 'skip only applies to pending sessions; failed sessions should be retried or suppressed through an explicit terminal policy'
|
|
164
|
+
: 'unsupported action',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (action === 'skip' && plan.blocked > 0) {
|
|
168
|
+
plan.notes.push(`${plan.blocked} failed row(s) are intentionally blocked from skip. Filter to --status pending before applying skip.`);
|
|
169
|
+
}
|
|
170
|
+
return plan;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildPendingWorkDecision(groups = []) {
|
|
174
|
+
const statusCounts = {};
|
|
175
|
+
for (const group of groups || []) {
|
|
176
|
+
statusCounts[group.status] = (statusCounts[group.status] || 0) + (group.count || 0);
|
|
177
|
+
}
|
|
178
|
+
const publicStatus = buildBacklogProductStatus({ statusCounts });
|
|
179
|
+
return {
|
|
180
|
+
status: publicStatus.code === 'clear'
|
|
181
|
+
? 'clear'
|
|
182
|
+
: publicStatus.code === 'recoverable'
|
|
183
|
+
? 'recoverable'
|
|
184
|
+
: 'attention',
|
|
185
|
+
summary: publicStatus.headline,
|
|
186
|
+
nextStep: publicStatus.action,
|
|
187
|
+
statusCounts,
|
|
188
|
+
publicStatus,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildReadiness(stats = {}) {
|
|
193
|
+
const servingMode = stats.serving?.mode || 'legacy';
|
|
194
|
+
const activeScopePath = Array.isArray(stats.serving?.activeScopePath)
|
|
195
|
+
? stats.serving.activeScopePath
|
|
196
|
+
: [];
|
|
197
|
+
const memoryRecords = stats.memoryRecords || {};
|
|
198
|
+
const pendingTotal = stats.pendingSessions?.total || 0;
|
|
199
|
+
const hasHistoricalMemory = (stats.summaries || 0) > 0 || (stats.turnEmbeddings || 0) > 0;
|
|
200
|
+
const currentEnabled = servingMode === 'curated';
|
|
201
|
+
const activeCurrentRows = memoryRecords.available ? (memoryRecords.active || 0) : 0;
|
|
202
|
+
const currentReady = currentEnabled && activeCurrentRows > 0;
|
|
203
|
+
const currentEmpty = currentEnabled && activeCurrentRows === 0;
|
|
204
|
+
const activeScopeReady = currentEnabled && activeScopePath.length > 0 && !(activeScopePath.length === 1 && activeScopePath[0] === 'global');
|
|
205
|
+
|
|
206
|
+
const checks = [
|
|
207
|
+
{
|
|
208
|
+
key: 'backend',
|
|
209
|
+
status: 'ready',
|
|
210
|
+
label: 'Service',
|
|
211
|
+
message: 'Aquifer is online.',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
key: 'historical_memory',
|
|
215
|
+
status: hasHistoricalMemory ? 'ready' : 'empty',
|
|
216
|
+
label: 'Saved content',
|
|
217
|
+
message: hasHistoricalMemory
|
|
218
|
+
? 'Saved content is available.'
|
|
219
|
+
: 'No saved content is available yet.',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
key: 'current_memory',
|
|
223
|
+
status: currentReady ? 'ready' : currentEmpty ? 'empty' : 'not_enabled',
|
|
224
|
+
label: 'Memory',
|
|
225
|
+
message: currentReady
|
|
226
|
+
? `Memory has ${activeCurrentRows} active row(s).`
|
|
227
|
+
: currentEmpty
|
|
228
|
+
? 'Memory is enabled, but no active rows are available yet.'
|
|
229
|
+
: 'Memory is still getting ready.',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
key: 'active_scope',
|
|
233
|
+
status: activeScopeReady ? 'ready' : currentEnabled ? 'attention' : 'not_enabled',
|
|
234
|
+
label: 'App link',
|
|
235
|
+
message: activeScopeReady
|
|
236
|
+
? `Memory is linked through ${activeScopePath.join(' > ')}.`
|
|
237
|
+
: currentEnabled
|
|
238
|
+
? 'Memory is not linked to this app yet.'
|
|
239
|
+
: 'Memory link is not active yet.',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
key: 'backlog',
|
|
243
|
+
status: pendingTotal > 0 ? 'attention' : 'ready',
|
|
244
|
+
label: 'Saved content',
|
|
245
|
+
message: pendingTotal > 0
|
|
246
|
+
? `${pendingTotal} saved-content ${pluralize(pendingTotal, 'item')} still need attention.`
|
|
247
|
+
: 'Saved content is ready.',
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
return buildReadinessSurface(stats, { checks });
|
|
252
|
+
}
|
|
253
|
+
|
|
22
254
|
function createAquifer(config = {}) {
|
|
23
255
|
const backendKind = normalizeBackendKind(config.backend?.kind || config.storage?.backend || 'postgres');
|
|
24
256
|
if (backendKind !== 'postgres') {
|
|
@@ -657,36 +889,34 @@ function createAquifer(config = {}) {
|
|
|
657
889
|
// --- read path ---
|
|
658
890
|
|
|
659
891
|
async memoryRecall(query, opts = {}) {
|
|
892
|
+
assertMemoryRecallQuery(query);
|
|
660
893
|
memoryServing.assertCuratedRecallOpts(opts);
|
|
661
|
-
await ensureMigrated();
|
|
662
|
-
if (typeof query !== 'string' || query.trim().length === 0) {
|
|
663
|
-
throw new Error('memory.recall(query): query must be a non-empty string');
|
|
664
|
-
}
|
|
665
894
|
const validModes = new Set(['fts', 'hybrid', 'vector']);
|
|
666
895
|
const mode = opts.mode || 'hybrid';
|
|
667
896
|
if (!validModes.has(mode)) {
|
|
668
897
|
throw new Error(`Invalid curated recall mode: "${mode}". Must be one of: fts, hybrid, vector`);
|
|
669
898
|
}
|
|
899
|
+
if (mode === 'vector' && !embedFn) {
|
|
900
|
+
throw new Error('curated memory_recall mode=vector requires config.embed.fn or EMBED_PROVIDER env');
|
|
901
|
+
}
|
|
902
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
903
|
+
await ensureMigrated();
|
|
670
904
|
let queryVec = null;
|
|
671
|
-
if (mode === 'hybrid' || mode === 'vector') {
|
|
672
|
-
if (!embedFn) {
|
|
673
|
-
if (mode === 'vector') {
|
|
674
|
-
throw new Error('curated memory_recall mode=vector requires config.embed.fn or EMBED_PROVIDER env');
|
|
675
|
-
}
|
|
676
|
-
} else {
|
|
905
|
+
if ((mode === 'hybrid' || mode === 'vector') && embedFn) {
|
|
677
906
|
const embedded = await embedFn([query]);
|
|
678
907
|
queryVec = Array.isArray(embedded) && Array.isArray(embedded[0]) ? embedded[0] : null;
|
|
679
908
|
if (!queryVec && mode === 'vector') throw new Error('embedFn returned empty vector for curated memory_recall');
|
|
680
|
-
}
|
|
681
909
|
}
|
|
682
|
-
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
683
910
|
const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
|
|
684
911
|
const runLexical = mode === 'fts' || mode === 'hybrid';
|
|
685
912
|
const runVector = (mode === 'vector' || mode === 'hybrid') && queryVec;
|
|
686
913
|
const [lexicalRows, embeddingRows] = await Promise.all([
|
|
687
914
|
runLexical ? aquifer.memory.recall(query, {
|
|
688
915
|
...scopedOpts,
|
|
689
|
-
|
|
916
|
+
// memory_records.search_tsv is generated with the stable simple config
|
|
917
|
+
// in schema/007, so curated memory_recall must query with the same
|
|
918
|
+
// config even when legacy session search selected a zh tokenizer.
|
|
919
|
+
ftsConfig: 'simple',
|
|
690
920
|
}) : Promise.resolve([]),
|
|
691
921
|
runVector ? aquifer.memory.recallViaMemoryEmbeddings(queryVec, scopedOpts) : Promise.resolve([]),
|
|
692
922
|
]);
|
|
@@ -1168,22 +1398,24 @@ function createAquifer(config = {}) {
|
|
|
1168
1398
|
async skip(sessionId, opts = {}) {
|
|
1169
1399
|
const agentId = opts.agentId || 'agent';
|
|
1170
1400
|
const reason = opts.reason || null;
|
|
1401
|
+
const source = opts.source || null;
|
|
1171
1402
|
// Atomic CAS: only skip if still pending (avoids race with concurrent enrich)
|
|
1172
1403
|
const result = await pool.query(
|
|
1173
1404
|
`UPDATE ${qi(schema)}.sessions
|
|
1174
1405
|
SET processing_status = 'skipped', processing_error = $1
|
|
1175
1406
|
WHERE session_id = $2 AND agent_id = $3 AND tenant_id = $4
|
|
1407
|
+
AND ($5::text IS NULL OR source = $5)
|
|
1176
1408
|
AND processing_status = 'pending'
|
|
1177
1409
|
RETURNING id`,
|
|
1178
|
-
[reason, sessionId, agentId, tenantId]
|
|
1410
|
+
[reason, sessionId, agentId, tenantId, source]
|
|
1179
1411
|
);
|
|
1180
1412
|
if (result.rows.length === 0) {
|
|
1181
1413
|
// Check if session exists at all
|
|
1182
|
-
const existing = await storage.getSession(pool, sessionId, agentId, {}, { schema, tenantId });
|
|
1414
|
+
const existing = await storage.getSession(pool, sessionId, agentId, { source: source || undefined }, { schema, tenantId });
|
|
1183
1415
|
if (!existing) throw new Error(`Session not found: ${sessionId} (agentId=${agentId})`);
|
|
1184
1416
|
return null; // exists but not pending — no-op
|
|
1185
1417
|
}
|
|
1186
|
-
return { id: result.rows[0].id, sessionId, agentId, status: 'skipped' };
|
|
1418
|
+
return { id: result.rows[0].id, sessionId, agentId, source, status: 'skipped' };
|
|
1187
1419
|
},
|
|
1188
1420
|
|
|
1189
1421
|
// --- public config accessor ---
|
|
@@ -1195,6 +1427,7 @@ function createAquifer(config = {}) {
|
|
|
1195
1427
|
memoryServingMode: memoryServing.servingMode,
|
|
1196
1428
|
memoryActiveScopeKey: memoryServing.defaultActiveScopeKey,
|
|
1197
1429
|
memoryActiveScopePath: memoryServing.defaultActiveScopePath,
|
|
1430
|
+
memoryAllowedScopeKeys: memoryServing.defaultAllowedScopeKeys,
|
|
1198
1431
|
backendKind,
|
|
1199
1432
|
backendProfile: backendInfo.profile,
|
|
1200
1433
|
capabilities: backendInfo.capabilities,
|
|
@@ -1299,6 +1532,11 @@ function createAquifer(config = {}) {
|
|
|
1299
1532
|
latestFinalizedAt: null,
|
|
1300
1533
|
latestUpdatedAt: null,
|
|
1301
1534
|
};
|
|
1535
|
+
let pendingSessions = {
|
|
1536
|
+
available: false,
|
|
1537
|
+
total: null,
|
|
1538
|
+
statuses: {},
|
|
1539
|
+
};
|
|
1302
1540
|
try {
|
|
1303
1541
|
const finalizationResult = await pool.query(
|
|
1304
1542
|
`SELECT
|
|
@@ -1327,15 +1565,47 @@ function createAquifer(config = {}) {
|
|
|
1327
1565
|
.sort()
|
|
1328
1566
|
.pop() || null,
|
|
1329
1567
|
};
|
|
1330
|
-
} catch {
|
|
1568
|
+
} catch (err) {
|
|
1569
|
+
if (err?.code !== '42P01') throw err;
|
|
1570
|
+
/* session_finalizations table may not exist on older installs */
|
|
1571
|
+
}
|
|
1331
1572
|
|
|
1332
|
-
|
|
1573
|
+
try {
|
|
1574
|
+
const actionablePending = await pool.query(
|
|
1575
|
+
`SELECT s.processing_status, COUNT(*)::int AS count
|
|
1576
|
+
FROM ${qi(schema)}.sessions s
|
|
1577
|
+
WHERE s.tenant_id = $1
|
|
1578
|
+
AND s.processing_status IN ('pending', 'failed')
|
|
1579
|
+
AND NOT EXISTS (
|
|
1580
|
+
SELECT 1
|
|
1581
|
+
FROM ${qi(schema)}.session_finalizations f
|
|
1582
|
+
WHERE f.tenant_id = s.tenant_id
|
|
1583
|
+
AND f.session_id = s.session_id
|
|
1584
|
+
AND f.agent_id = s.agent_id
|
|
1585
|
+
AND f.source = s.source
|
|
1586
|
+
AND f.status IN ('finalized', 'skipped', 'declined', 'deferred')
|
|
1587
|
+
)
|
|
1588
|
+
GROUP BY s.processing_status`,
|
|
1589
|
+
[tenantId]
|
|
1590
|
+
);
|
|
1591
|
+
pendingSessions = {
|
|
1592
|
+
available: true,
|
|
1593
|
+
total: actionablePending.rows.reduce((sum, row) => sum + row.count, 0),
|
|
1594
|
+
statuses: Object.fromEntries(actionablePending.rows.map(row => [row.processing_status, row.count])),
|
|
1595
|
+
};
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
if (err?.code !== '42P01') throw err;
|
|
1598
|
+
/* session_finalizations table may not exist on older installs */
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
const stats = {
|
|
1333
1602
|
backendKind,
|
|
1334
1603
|
backendProfile: backendInfo.profile,
|
|
1335
1604
|
serving: {
|
|
1336
1605
|
mode: memoryServing.servingMode,
|
|
1337
1606
|
activeScopeKey: memoryServing.defaultActiveScopeKey,
|
|
1338
1607
|
activeScopePath: memoryServing.defaultActiveScopePath,
|
|
1608
|
+
allowedScopeKeys: memoryServing.defaultAllowedScopeKeys,
|
|
1339
1609
|
},
|
|
1340
1610
|
sessions: Object.fromEntries(sessions.rows.map(r => [r.processing_status, r.count])),
|
|
1341
1611
|
sessionTotal: sessions.rows.reduce((s, r) => s + r.count, 0),
|
|
@@ -1344,22 +1614,175 @@ function createAquifer(config = {}) {
|
|
|
1344
1614
|
entities: entityCount,
|
|
1345
1615
|
memoryRecords,
|
|
1346
1616
|
sessionFinalizations,
|
|
1617
|
+
pendingSessions,
|
|
1347
1618
|
earliest: timeRange.rows[0]?.earliest || null,
|
|
1348
1619
|
latest: timeRange.rows[0]?.latest || null,
|
|
1349
1620
|
};
|
|
1621
|
+
return {
|
|
1622
|
+
...stats,
|
|
1623
|
+
readiness: buildReadiness(stats),
|
|
1624
|
+
};
|
|
1625
|
+
},
|
|
1626
|
+
|
|
1627
|
+
async getPendingWork(opts = {}) {
|
|
1628
|
+
const limit = normalizePendingWorkLimit(opts.limit, 100, 500);
|
|
1629
|
+
const statuses = normalizePendingWorkStatuses(opts.statuses || opts.status);
|
|
1630
|
+
const action = normalizePendingWorkAction(opts.action || opts.plan);
|
|
1631
|
+
const filters = {
|
|
1632
|
+
source: opts.source || null,
|
|
1633
|
+
agentId: opts.agentId || null,
|
|
1634
|
+
statuses,
|
|
1635
|
+
limit,
|
|
1636
|
+
};
|
|
1637
|
+
|
|
1638
|
+
const params = [tenantId, statuses];
|
|
1639
|
+
const where = [
|
|
1640
|
+
's.tenant_id = $1',
|
|
1641
|
+
's.processing_status = ANY($2::text[])',
|
|
1642
|
+
];
|
|
1643
|
+
if (filters.source) {
|
|
1644
|
+
params.push(filters.source);
|
|
1645
|
+
where.push(`s.source = $${params.length}`);
|
|
1646
|
+
}
|
|
1647
|
+
if (filters.agentId) {
|
|
1648
|
+
params.push(filters.agentId);
|
|
1649
|
+
where.push(`s.agent_id = $${params.length}`);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
const terminalExclusion = `NOT EXISTS (
|
|
1653
|
+
SELECT 1
|
|
1654
|
+
FROM ${qi(schema)}.session_finalizations f
|
|
1655
|
+
WHERE f.tenant_id = s.tenant_id
|
|
1656
|
+
AND f.session_id = s.session_id
|
|
1657
|
+
AND f.agent_id = s.agent_id
|
|
1658
|
+
AND f.source = s.source
|
|
1659
|
+
AND f.status = ANY($${params.length + 1}::text[])
|
|
1660
|
+
)`;
|
|
1661
|
+
const runQueries = async (excludeTerminal = true) => {
|
|
1662
|
+
const queryParams = excludeTerminal ? [...params, TERMINAL_FINALIZATION_STATUSES] : params;
|
|
1663
|
+
const whereSql = [...where, ...(excludeTerminal ? [terminalExclusion] : [])].join(' AND ');
|
|
1664
|
+
const limitParam = queryParams.length + 1;
|
|
1665
|
+
const [groupsResult, samplesResult] = await Promise.all([
|
|
1666
|
+
pool.query(
|
|
1667
|
+
`SELECT
|
|
1668
|
+
COALESCE(s.source, 'api') AS source,
|
|
1669
|
+
s.agent_id,
|
|
1670
|
+
s.processing_status,
|
|
1671
|
+
COUNT(*)::int AS count,
|
|
1672
|
+
MIN(s.started_at) AS earliest,
|
|
1673
|
+
MAX(s.started_at) AS latest,
|
|
1674
|
+
COALESCE(SUM(s.msg_count), 0)::int AS msg_count,
|
|
1675
|
+
COALESCE(SUM(s.user_count), 0)::int AS user_count
|
|
1676
|
+
FROM ${qi(schema)}.sessions s
|
|
1677
|
+
WHERE ${whereSql}
|
|
1678
|
+
GROUP BY COALESCE(s.source, 'api'), s.agent_id, s.processing_status
|
|
1679
|
+
ORDER BY COUNT(*) DESC, latest DESC`,
|
|
1680
|
+
queryParams,
|
|
1681
|
+
),
|
|
1682
|
+
pool.query(
|
|
1683
|
+
`SELECT
|
|
1684
|
+
s.session_id,
|
|
1685
|
+
s.session_key,
|
|
1686
|
+
COALESCE(s.source, 'api') AS source,
|
|
1687
|
+
s.agent_id,
|
|
1688
|
+
s.processing_status,
|
|
1689
|
+
s.processing_error,
|
|
1690
|
+
s.started_at,
|
|
1691
|
+
s.last_message_at,
|
|
1692
|
+
s.msg_count,
|
|
1693
|
+
s.user_count,
|
|
1694
|
+
s.model
|
|
1695
|
+
FROM ${qi(schema)}.sessions s
|
|
1696
|
+
WHERE ${whereSql}
|
|
1697
|
+
ORDER BY s.started_at DESC
|
|
1698
|
+
LIMIT $${limitParam}`,
|
|
1699
|
+
[...queryParams, limit],
|
|
1700
|
+
),
|
|
1701
|
+
]);
|
|
1702
|
+
return { groupsResult, samplesResult, terminalFiltering: excludeTerminal };
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
let result;
|
|
1706
|
+
try {
|
|
1707
|
+
result = await runQueries(true);
|
|
1708
|
+
} catch (err) {
|
|
1709
|
+
if (err?.code !== '42P01') throw err;
|
|
1710
|
+
result = await runQueries(false);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
const groups = result.groupsResult.rows.map(normalizePendingWorkGroup);
|
|
1714
|
+
const samples = result.samplesResult.rows.map(normalizePendingWorkRow);
|
|
1715
|
+
const total = groups.reduce((sum, group) => sum + group.count, 0);
|
|
1716
|
+
const decision = buildPendingWorkDecision(groups);
|
|
1717
|
+
return {
|
|
1718
|
+
available: true,
|
|
1719
|
+
generatedAt: new Date().toISOString(),
|
|
1720
|
+
terminalFiltering: result.terminalFiltering,
|
|
1721
|
+
filters,
|
|
1722
|
+
total,
|
|
1723
|
+
status: decision.status,
|
|
1724
|
+
statusCounts: decision.statusCounts,
|
|
1725
|
+
summary: decision.summary,
|
|
1726
|
+
nextStep: decision.nextStep,
|
|
1727
|
+
publicStatus: decision.publicStatus,
|
|
1728
|
+
groups,
|
|
1729
|
+
samples,
|
|
1730
|
+
guidance: buildPendingWorkGuidance(groups),
|
|
1731
|
+
plan: buildPendingWorkPlan(samples, action, groups),
|
|
1732
|
+
};
|
|
1350
1733
|
},
|
|
1351
1734
|
|
|
1352
1735
|
async getPendingSessions(opts = {}) {
|
|
1353
|
-
const limit = opts.limit
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1736
|
+
const limit = normalizePendingWorkLimit(opts.limit, 100, 500);
|
|
1737
|
+
const statuses = normalizePendingWorkStatuses(opts.statuses || opts.status);
|
|
1738
|
+
const params = [tenantId, statuses];
|
|
1739
|
+
const where = [
|
|
1740
|
+
's.tenant_id = $1',
|
|
1741
|
+
's.processing_status = ANY($2::text[])',
|
|
1742
|
+
];
|
|
1743
|
+
if (opts.source) {
|
|
1744
|
+
params.push(opts.source);
|
|
1745
|
+
where.push(`s.source = $${params.length}`);
|
|
1746
|
+
}
|
|
1747
|
+
if (opts.agentId) {
|
|
1748
|
+
params.push(opts.agentId);
|
|
1749
|
+
where.push(`s.agent_id = $${params.length}`);
|
|
1750
|
+
}
|
|
1751
|
+
let result;
|
|
1752
|
+
try {
|
|
1753
|
+
const terminalParam = params.length + 1;
|
|
1754
|
+
const limitParam = params.length + 2;
|
|
1755
|
+
result = await pool.query(
|
|
1756
|
+
`SELECT s.session_id, s.session_key, s.source, s.agent_id, s.processing_status,
|
|
1757
|
+
s.processing_error, s.started_at, s.last_message_at, s.msg_count, s.user_count, s.model
|
|
1758
|
+
FROM ${qi(schema)}.sessions s
|
|
1759
|
+
WHERE ${where.join(' AND ')}
|
|
1760
|
+
AND NOT EXISTS (
|
|
1761
|
+
SELECT 1
|
|
1762
|
+
FROM ${qi(schema)}.session_finalizations f
|
|
1763
|
+
WHERE f.tenant_id = s.tenant_id
|
|
1764
|
+
AND f.session_id = s.session_id
|
|
1765
|
+
AND f.agent_id = s.agent_id
|
|
1766
|
+
AND f.source = s.source
|
|
1767
|
+
AND f.status = ANY($${terminalParam}::text[])
|
|
1768
|
+
)
|
|
1769
|
+
ORDER BY s.started_at DESC
|
|
1770
|
+
LIMIT $${limitParam}`,
|
|
1771
|
+
[...params, TERMINAL_FINALIZATION_STATUSES, limit]
|
|
1772
|
+
);
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
if (err?.code !== '42P01') throw err;
|
|
1775
|
+
const limitParam = params.length + 1;
|
|
1776
|
+
result = await pool.query(
|
|
1777
|
+
`SELECT session_id, session_key, source, agent_id, processing_status,
|
|
1778
|
+
processing_error, started_at, last_message_at, msg_count, user_count, model
|
|
1779
|
+
FROM ${qi(schema)}.sessions s
|
|
1780
|
+
WHERE ${where.join(' AND ')}
|
|
1781
|
+
ORDER BY started_at DESC
|
|
1782
|
+
LIMIT $${limitParam}`,
|
|
1783
|
+
[...params, limit]
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1363
1786
|
return result.rows;
|
|
1364
1787
|
},
|
|
1365
1788
|
|
|
@@ -1386,9 +1809,10 @@ function createAquifer(config = {}) {
|
|
|
1386
1809
|
},
|
|
1387
1810
|
|
|
1388
1811
|
async memoryBootstrap(opts = {}) {
|
|
1389
|
-
await ensureMigrated();
|
|
1390
1812
|
memoryServing.assertCuratedBootstrapOpts(opts);
|
|
1391
|
-
|
|
1813
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1814
|
+
await ensureMigrated();
|
|
1815
|
+
return memoryBootstrap.bootstrap(scopedOpts);
|
|
1392
1816
|
},
|
|
1393
1817
|
|
|
1394
1818
|
async historicalBootstrap(opts = {}) {
|
|
@@ -1426,6 +1850,10 @@ function createAquifer(config = {}) {
|
|
|
1426
1850
|
const { createMemoryConsolidation } = require('./memory-consolidation');
|
|
1427
1851
|
const { createSessionFinalization } = require('./session-finalization');
|
|
1428
1852
|
const { createSessionCheckpoints } = require('./session-checkpoints');
|
|
1853
|
+
const { createOperatorObservability } = require('./operator-observability');
|
|
1854
|
+
const { createDoctor } = require('./doctor');
|
|
1855
|
+
const { createMemoryExplain } = require('./memory-explain');
|
|
1856
|
+
const { createMemoryReview } = require('./memory-review');
|
|
1429
1857
|
const qSchema = qi(schema);
|
|
1430
1858
|
aquifer.narratives = createNarratives({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
1431
1859
|
aquifer.timeline = createTimeline({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
@@ -1437,8 +1865,7 @@ function createAquifer(config = {}) {
|
|
|
1437
1865
|
aquifer.consolidation = createConsolidation({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
1438
1866
|
aquifer.bundles = createBundles({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
1439
1867
|
// entityState materialises in schema/005-entity-state-history.sql, gated on
|
|
1440
|
-
// entitiesEnabled
|
|
1441
|
-
// scripts/drop-entity-state-history.sql.
|
|
1868
|
+
// entitiesEnabled because it FK-references entities.
|
|
1442
1869
|
aquifer.entityState = createEntityState({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
1443
1870
|
// insights materialises in schema/006-insights.sql. No FK from elsewhere
|
|
1444
1871
|
// into this table; DROP CASCADE is clean. See scripts/drop-insights.sql.
|
|
@@ -1476,6 +1903,33 @@ function createAquifer(config = {}) {
|
|
|
1476
1903
|
schema,
|
|
1477
1904
|
defaultTenantId: tenantId,
|
|
1478
1905
|
});
|
|
1906
|
+
const operatorObservability = createOperatorObservability({
|
|
1907
|
+
pool,
|
|
1908
|
+
schema: qSchema,
|
|
1909
|
+
defaultTenantId: tenantId,
|
|
1910
|
+
});
|
|
1911
|
+
const doctor = createDoctor({
|
|
1912
|
+
pool,
|
|
1913
|
+
schema,
|
|
1914
|
+
recordsSchema: qSchema,
|
|
1915
|
+
defaultTenantId: tenantId,
|
|
1916
|
+
dbConfigured: Boolean(dbInput),
|
|
1917
|
+
backendKind,
|
|
1918
|
+
backendProfile: backendInfo.profile,
|
|
1919
|
+
memoryServing,
|
|
1920
|
+
listPendingMigrations: () => migrationRuntime.listPendingMigrations(),
|
|
1921
|
+
operatorObservability,
|
|
1922
|
+
});
|
|
1923
|
+
const memoryExplain = createMemoryExplain({
|
|
1924
|
+
pool,
|
|
1925
|
+
schema: qSchema,
|
|
1926
|
+
defaultTenantId: tenantId,
|
|
1927
|
+
});
|
|
1928
|
+
const memoryReview = createMemoryReview({
|
|
1929
|
+
pool,
|
|
1930
|
+
schema: qSchema,
|
|
1931
|
+
defaultTenantId: tenantId,
|
|
1932
|
+
});
|
|
1479
1933
|
|
|
1480
1934
|
function currentMemoryScopeKeys(opts = {}) {
|
|
1481
1935
|
if (Array.isArray(opts.activeScopePath) && opts.activeScopePath.length > 0) {
|
|
@@ -1485,6 +1939,12 @@ function createAquifer(config = {}) {
|
|
|
1485
1939
|
return null;
|
|
1486
1940
|
}
|
|
1487
1941
|
|
|
1942
|
+
function assertMemoryRecallQuery(query) {
|
|
1943
|
+
if (typeof query !== 'string' || query.trim().length === 0) {
|
|
1944
|
+
throw new Error('memory.recall(query): query must be a non-empty string');
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1488
1948
|
// v1 curated-memory sidecar. Top-level recall/bootstrap can opt into this
|
|
1489
1949
|
// plane through memory.servingMode while legacy/evidence mode remains
|
|
1490
1950
|
// available for compatibility and debugging.
|
|
@@ -1520,21 +1980,25 @@ function createAquifer(config = {}) {
|
|
|
1520
1980
|
return memoryPromotion.promote(candidates, opts);
|
|
1521
1981
|
},
|
|
1522
1982
|
bootstrap: async (opts = {}) => {
|
|
1983
|
+
memoryServing.assertCuratedBootstrapOpts(opts);
|
|
1984
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1523
1985
|
await ensureMigrated();
|
|
1524
|
-
return memoryBootstrap.bootstrap(
|
|
1986
|
+
return memoryBootstrap.bootstrap(scopedOpts);
|
|
1525
1987
|
},
|
|
1526
1988
|
current: async (opts = {}) => {
|
|
1989
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1527
1990
|
await ensureMigrated();
|
|
1528
|
-
return memoryRecords.currentProjection(
|
|
1991
|
+
return memoryRecords.currentProjection(scopedOpts);
|
|
1529
1992
|
},
|
|
1530
1993
|
listCurrentMemory: async (opts = {}) => {
|
|
1994
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1531
1995
|
await ensureMigrated();
|
|
1532
|
-
return memoryRecords.currentProjection(
|
|
1996
|
+
return memoryRecords.currentProjection(scopedOpts);
|
|
1533
1997
|
},
|
|
1534
1998
|
backfillEmbeddings: async (opts = {}) => {
|
|
1535
|
-
await ensureMigrated();
|
|
1536
1999
|
requireEmbed('memory.backfillEmbeddings');
|
|
1537
2000
|
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
2001
|
+
await ensureMigrated();
|
|
1538
2002
|
const listInput = {
|
|
1539
2003
|
tenantId: scopedOpts.tenantId || tenantId,
|
|
1540
2004
|
asOf: scopedOpts.asOf,
|
|
@@ -1611,20 +2075,39 @@ function createAquifer(config = {}) {
|
|
|
1611
2075
|
};
|
|
1612
2076
|
},
|
|
1613
2077
|
recall: async (query, opts = {}) => {
|
|
2078
|
+
assertMemoryRecallQuery(query);
|
|
2079
|
+
memoryServing.assertCuratedRecallOpts(opts);
|
|
2080
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1614
2081
|
await ensureMigrated();
|
|
1615
|
-
return memoryRecall.recall(query,
|
|
2082
|
+
return memoryRecall.recall(query, scopedOpts);
|
|
1616
2083
|
},
|
|
1617
2084
|
recallViaEvidenceItems: async (query, opts = {}) => {
|
|
2085
|
+
assertMemoryRecallQuery(query);
|
|
2086
|
+
memoryServing.assertCuratedRecallOpts(opts);
|
|
2087
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1618
2088
|
await ensureMigrated();
|
|
1619
|
-
return memoryRecall.recallViaEvidenceItems(query,
|
|
2089
|
+
return memoryRecall.recallViaEvidenceItems(query, scopedOpts);
|
|
1620
2090
|
},
|
|
1621
2091
|
recallViaMemoryEmbeddings: async (queryVec, opts = {}) => {
|
|
2092
|
+
memoryServing.assertCuratedRecallOpts(opts);
|
|
2093
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1622
2094
|
await ensureMigrated();
|
|
1623
|
-
return memoryRecall.recallViaMemoryEmbeddings(queryVec,
|
|
2095
|
+
return memoryRecall.recallViaMemoryEmbeddings(queryVec, scopedOpts);
|
|
1624
2096
|
},
|
|
1625
2097
|
recallViaLinkedSummaryEmbeddings: async (queryVec, opts = {}) => {
|
|
2098
|
+
memoryServing.assertCuratedRecallOpts(opts);
|
|
2099
|
+
const scopedOpts = memoryServing.withDefaultScope(opts);
|
|
1626
2100
|
await ensureMigrated();
|
|
1627
|
-
return memoryRecall.recallViaLinkedSummaryEmbeddings(queryVec,
|
|
2101
|
+
return memoryRecall.recallViaLinkedSummaryEmbeddings(queryVec, scopedOpts);
|
|
2102
|
+
},
|
|
2103
|
+
explainBootstrap: async (opts = {}) => {
|
|
2104
|
+
return memoryExplain.explainBootstrap(memoryServing.withDefaultScope(opts));
|
|
2105
|
+
},
|
|
2106
|
+
explainCurrent: async (query, opts = {}) => {
|
|
2107
|
+
return memoryExplain.explainCurrent(query, memoryServing.withDefaultScope(opts));
|
|
2108
|
+
},
|
|
2109
|
+
explainMemory: async (query, opts = {}) => {
|
|
2110
|
+
return memoryExplain.explainMemory(query, memoryServing.withDefaultScope(opts));
|
|
1628
2111
|
},
|
|
1629
2112
|
rankHybridMemoryRows: (lexicalRows, embeddingRows, opts = {}) => {
|
|
1630
2113
|
return memoryRecall.rankHybridMemoryRows(lexicalRows, embeddingRows, opts);
|
|
@@ -1665,9 +2148,11 @@ function createAquifer(config = {}) {
|
|
|
1665
2148
|
return sessionFinalization.get(input);
|
|
1666
2149
|
},
|
|
1667
2150
|
list: async (input = {}) => {
|
|
1668
|
-
await ensureMigrated();
|
|
1669
2151
|
return sessionFinalization.list(input);
|
|
1670
2152
|
},
|
|
2153
|
+
inspect: async (input = {}) => {
|
|
2154
|
+
return sessionFinalization.inspect(input);
|
|
2155
|
+
},
|
|
1671
2156
|
updateStatus: async (input = {}) => {
|
|
1672
2157
|
await ensureMigrated();
|
|
1673
2158
|
return sessionFinalization.updateStatus(input);
|
|
@@ -1722,6 +2207,33 @@ function createAquifer(config = {}) {
|
|
|
1722
2207
|
},
|
|
1723
2208
|
};
|
|
1724
2209
|
|
|
2210
|
+
aquifer.operator = {
|
|
2211
|
+
status: async (input = {}) => operatorObservability.status(input),
|
|
2212
|
+
inspect: async (input = {}) => operatorObservability.inspect(input),
|
|
2213
|
+
};
|
|
2214
|
+
|
|
2215
|
+
aquifer.doctor = {
|
|
2216
|
+
run: async (input = {}) => doctor.run(input),
|
|
2217
|
+
};
|
|
2218
|
+
|
|
2219
|
+
aquifer.review = {
|
|
2220
|
+
queue: async (input = {}) => {
|
|
2221
|
+
const scopedInput = memoryServing.withDefaultScope(input);
|
|
2222
|
+
await ensureMigrated();
|
|
2223
|
+
return memoryReview.queue(scopedInput);
|
|
2224
|
+
},
|
|
2225
|
+
inspect: async (input = {}) => {
|
|
2226
|
+
const scopedInput = memoryServing.withDefaultScope(input);
|
|
2227
|
+
await ensureMigrated();
|
|
2228
|
+
return memoryReview.inspect(scopedInput);
|
|
2229
|
+
},
|
|
2230
|
+
resolve: async (input = {}) => {
|
|
2231
|
+
const scopedInput = memoryServing.withDefaultScope(input);
|
|
2232
|
+
await ensureMigrated();
|
|
2233
|
+
return memoryReview.resolve(scopedInput);
|
|
2234
|
+
},
|
|
2235
|
+
};
|
|
2236
|
+
|
|
1725
2237
|
aquifer.finalizeSession = aquifer.finalization.finalizeSession;
|
|
1726
2238
|
|
|
1727
2239
|
return aquifer;
|