@shadowforge0/aquifer-memory 1.9.0 → 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/README.md +33 -4
- package/README_CN.md +9 -1
- package/README_TW.md +5 -2
- package/consumers/cli.js +55 -34
- package/consumers/codex-active-checkpoint.js +3 -1
- package/consumers/codex-current-memory.js +10 -6
- package/consumers/codex.js +5 -2
- 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 -49
- package/consumers/openclaw-ext/index.js +1 -1
- package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
- package/consumers/openclaw-ext/package.json +1 -1
- package/consumers/openclaw-plugin.js +66 -23
- package/consumers/shared/compat-recall.js +101 -0
- package/consumers/shared/openclaw-product-tools.js +130 -0
- package/consumers/shared/recall-format.js +2 -2
- package/core/aquifer.js +385 -20
- package/core/backends/local.js +60 -1
- 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 +20 -51
- package/core/memory-promotion.js +392 -55
- package/core/memory-recall.js +26 -48
- package/core/memory-records.js +91 -103
- package/core/memory-type-policy.js +298 -0
- package/core/postgres-migrations.js +9 -0
- package/core/session-checkpoint-producer.js +3 -1
- package/core/session-checkpoints.js +1 -1
- package/core/session-finalization.js +2 -2
- package/docs/getting-started.md +16 -3
- package/docs/setup.md +61 -2
- package/package.json +2 -2
- package/schema/004-completion.sql +4 -4
- package/schema/010-v1-finalization-review.sql +72 -0
- package/schema/020-v1-assistant-shaping-memory.sql +30 -0
- package/scripts/backfill-canonical-key.js +1 -1
- package/scripts/diagnose-fts-zh.js +1 -1
- package/scripts/extract-insights-from-recent-sessions.js +4 -4
package/consumers/mcp.js
CHANGED
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
|
|
22
22
|
const { createAquiferFromConfig } = require('./shared/factory');
|
|
23
23
|
const { version: packageVersion } = require('../package.json');
|
|
24
|
+
const {
|
|
25
|
+
formatMemoryStatsInterface,
|
|
26
|
+
formatPendingRowsInterface,
|
|
27
|
+
formatPendingWorkInterface,
|
|
28
|
+
} = require('../core/interface');
|
|
29
|
+
const { MCP_TOOL_MANIFEST } = require('../core/mcp-manifest');
|
|
24
30
|
|
|
25
31
|
let _aquifer = null;
|
|
26
32
|
|
|
@@ -29,6 +35,19 @@ function getAquifer() {
|
|
|
29
35
|
return _aquifer;
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
function manifestToolDescription(name, fallback) {
|
|
39
|
+
return MCP_TOOL_MANIFEST.find(tool => tool.name === name)?.description || fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pendingSessionOpts(params = {}) {
|
|
43
|
+
return {
|
|
44
|
+
limit: params.limit ?? 20,
|
|
45
|
+
source: params.source || undefined,
|
|
46
|
+
agentId: params.agentId || undefined,
|
|
47
|
+
status: params.status || undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
32
51
|
// ---------------------------------------------------------------------------
|
|
33
52
|
// Format recall results as readable text
|
|
34
53
|
// ---------------------------------------------------------------------------
|
|
@@ -65,6 +84,10 @@ function historicalRecallLaneHeader() {
|
|
|
65
84
|
return 'Serving lane: explicit historical/session recall';
|
|
66
85
|
}
|
|
67
86
|
|
|
87
|
+
function countEnvelopeList(value) {
|
|
88
|
+
return Array.isArray(value) ? value.length : 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
68
91
|
// ---------------------------------------------------------------------------
|
|
69
92
|
// Start MCP server
|
|
70
93
|
// ---------------------------------------------------------------------------
|
|
@@ -137,6 +160,8 @@ async function main() {
|
|
|
137
160
|
source: z.string().optional().describe('Filter by source (e.g., gateway, cc)'),
|
|
138
161
|
dateFrom: z.string().optional().describe('Start date YYYY-MM-DD'),
|
|
139
162
|
dateTo: z.string().optional().describe('End date YYYY-MM-DD'),
|
|
163
|
+
host: z.string().optional().describe('Audit boundary host filter'),
|
|
164
|
+
sessionId: z.string().optional().describe('Audit boundary session ID filter'),
|
|
140
165
|
entities: z.array(z.string()).optional().describe('Entity names to match'),
|
|
141
166
|
entityMode: z.enum(['any', 'all']).optional().describe('"any" (default, boost) or "all" (only sessions with every entity)'),
|
|
142
167
|
mode: z.enum(['fts', 'hybrid', 'vector']).optional().describe('Recall mode: "fts" (keyword only, no embed needed), "hybrid" (default, FTS + vector), "vector" (vector only)'),
|
|
@@ -151,6 +176,8 @@ async function main() {
|
|
|
151
176
|
source: params.source || undefined,
|
|
152
177
|
dateFrom: params.dateFrom || undefined,
|
|
153
178
|
dateTo: params.dateTo || undefined,
|
|
179
|
+
host: params.host || undefined,
|
|
180
|
+
sessionId: params.sessionId || undefined,
|
|
154
181
|
};
|
|
155
182
|
if (params.entities && params.entities.length > 0) {
|
|
156
183
|
recallOpts.entities = params.entities;
|
|
@@ -374,45 +401,16 @@ async function main() {
|
|
|
374
401
|
|
|
375
402
|
server.tool(
|
|
376
403
|
'memory_stats',
|
|
377
|
-
'Return
|
|
378
|
-
{
|
|
379
|
-
|
|
404
|
+
manifestToolDescription('memory_stats', 'Return Aquifer product status.'),
|
|
405
|
+
{
|
|
406
|
+
diagnostics: z.boolean().optional().describe('Include raw storage counters and serving diagnostics. Default false.'),
|
|
407
|
+
},
|
|
408
|
+
async (params = {}) => {
|
|
380
409
|
try {
|
|
381
410
|
const aquifer = getAquifer();
|
|
382
411
|
const stats = await aquifer.getStats();
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
`Serving mode: ${stats.serving?.mode || 'legacy'}`,
|
|
386
|
-
`Active scope: ${stats.serving?.activeScopePath?.join(' > ') || stats.serving?.activeScopeKey || 'none'}`,
|
|
387
|
-
`Sessions: ${stats.sessionTotal} total`,
|
|
388
|
-
];
|
|
389
|
-
for (const [status, count] of Object.entries(stats.sessions)) {
|
|
390
|
-
lines.push(` ${status}: ${count}`);
|
|
391
|
-
}
|
|
392
|
-
if (stats.pendingSessions?.available) {
|
|
393
|
-
lines.push(`Actionable pending/failed: ${stats.pendingSessions.total}`);
|
|
394
|
-
}
|
|
395
|
-
lines.push(`Summaries: ${stats.summaries}`);
|
|
396
|
-
lines.push(`Turn embeddings: ${stats.turnEmbeddings}`);
|
|
397
|
-
lines.push(`Entities: ${stats.entities}`);
|
|
398
|
-
if (stats.memoryRecords) {
|
|
399
|
-
lines.push(`Memory records: ${stats.memoryRecords.total} total (${stats.memoryRecords.active} active, ${stats.memoryRecords.visibleInRecall} recall-visible, ${stats.memoryRecords.visibleInBootstrap} bootstrap-visible)`);
|
|
400
|
-
if (stats.memoryRecords.latest) lines.push(`Memory record range: ${new Date(stats.memoryRecords.earliest).toISOString().slice(0, 10)} → ${new Date(stats.memoryRecords.latest).toISOString().slice(0, 10)}`);
|
|
401
|
-
}
|
|
402
|
-
if (stats.sessionFinalizations?.available) {
|
|
403
|
-
const statusText = Object.entries(stats.sessionFinalizations.statuses || {})
|
|
404
|
-
.map(([status, count]) => `${status}: ${count}`)
|
|
405
|
-
.join(', ') || 'none';
|
|
406
|
-
lines.push(`Session finalizations: ${stats.sessionFinalizations.total} total (${statusText})`);
|
|
407
|
-
if (stats.sessionFinalizations.latestFinalizedAt) {
|
|
408
|
-
lines.push(`Latest finalization: ${new Date(stats.sessionFinalizations.latestFinalizedAt).toISOString().slice(0, 10)}`);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (stats.earliest) lines.push(`Date range: ${new Date(stats.earliest).toISOString().slice(0, 10)} → ${new Date(stats.latest).toISOString().slice(0, 10)}`);
|
|
412
|
-
if ((stats.serving?.mode || 'legacy') !== 'curated') {
|
|
413
|
-
lines.push('Warning: legacy serving returns session/evidence material; configure curated serving with an active scope for current-memory answers.');
|
|
414
|
-
}
|
|
415
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
412
|
+
const text = formatMemoryStatsInterface(stats, { diagnostics: params.diagnostics === true });
|
|
413
|
+
return { content: [{ type: 'text', text }] };
|
|
416
414
|
} catch (err) {
|
|
417
415
|
return {
|
|
418
416
|
content: [{ type: 'text', text: `memory_stats error: ${err.message}` }],
|
|
@@ -424,22 +422,31 @@ async function main() {
|
|
|
424
422
|
|
|
425
423
|
server.tool(
|
|
426
424
|
'memory_pending',
|
|
427
|
-
'
|
|
425
|
+
manifestToolDescription('memory_pending', 'Return saved-content preparation status.'),
|
|
428
426
|
{
|
|
429
427
|
limit: z.number().int().min(1).max(200).optional().describe('Max results (default 20)'),
|
|
428
|
+
source: z.string().optional().describe('Filter by source'),
|
|
429
|
+
agentId: z.string().optional().describe('Filter by agent ID'),
|
|
430
|
+
status: z.enum(['pending', 'failed']).optional().describe('Filter by processing status'),
|
|
431
|
+
diagnostics: z.boolean().optional().describe('Include source/agent/status buckets, guidance, and samples. Default false.'),
|
|
430
432
|
},
|
|
431
433
|
async (params) => {
|
|
432
434
|
try {
|
|
433
435
|
const aquifer = getAquifer();
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
436
|
+
const queryOpts = pendingSessionOpts(params);
|
|
437
|
+
const report = typeof aquifer.getPendingWork === 'function'
|
|
438
|
+
? await aquifer.getPendingWork(queryOpts)
|
|
439
|
+
: null;
|
|
440
|
+
if (report) {
|
|
441
|
+
const text = formatPendingWorkInterface(report, {
|
|
442
|
+
diagnostics: params.diagnostics === true,
|
|
443
|
+
includePlan: false,
|
|
444
|
+
});
|
|
445
|
+
return { content: [{ type: 'text', text }] };
|
|
441
446
|
}
|
|
442
|
-
|
|
447
|
+
const rows = await aquifer.getPendingSessions(queryOpts);
|
|
448
|
+
const text = formatPendingRowsInterface(rows, { diagnostics: params.diagnostics === true });
|
|
449
|
+
return { content: [{ type: 'text', text }] };
|
|
443
450
|
} catch (err) {
|
|
444
451
|
return {
|
|
445
452
|
content: [{ type: 'text', text: `memory_pending error: ${err.message}` }],
|
|
@@ -499,16 +506,16 @@ async function main() {
|
|
|
499
506
|
if (!envelope.ready) {
|
|
500
507
|
const err = envelope.error || { code: 'AQ_MIGRATION_NOT_READY', message: 'aquifer.init() did not reach ready state' };
|
|
501
508
|
process.stderr.write(
|
|
502
|
-
`[aquifer-mcp] startup aborted: migrationMode=${envelope.migrationMode} ` +
|
|
503
|
-
`memoryMode=${envelope.memoryMode} pending=${envelope.pendingMigrations
|
|
509
|
+
`[aquifer-mcp] startup aborted: migrationMode=${envelope.migrationMode || 'unknown'} ` +
|
|
510
|
+
`memoryMode=${envelope.memoryMode || 'unknown'} pending=${countEnvelopeList(envelope.pendingMigrations)} ` +
|
|
504
511
|
`error=${err.code || 'unknown'}: ${err.message}\n`
|
|
505
512
|
);
|
|
506
513
|
await aquifer.close().catch(() => {});
|
|
507
514
|
process.exit(1);
|
|
508
515
|
}
|
|
509
516
|
process.stderr.write(
|
|
510
|
-
`[aquifer-mcp] init ok: mode=${envelope.migrationMode} applied=${envelope.appliedMigrations
|
|
511
|
-
`pending=${envelope.pendingMigrations
|
|
517
|
+
`[aquifer-mcp] init ok: mode=${envelope.migrationMode || 'unknown'} applied=${countEnvelopeList(envelope.appliedMigrations)} ` +
|
|
518
|
+
`pending=${countEnvelopeList(envelope.pendingMigrations)} durationMs=${envelope.durationMs ?? 0}\n`
|
|
512
519
|
);
|
|
513
520
|
|
|
514
521
|
const transport = new StdioServerTransport();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// - Delegates to consumers/openclaw-plugin.js. If AQUIFER_PERSONA is set
|
|
14
14
|
// (pluginConfig.persona or env), the plugin loads the persona module
|
|
15
15
|
// and hands off mountOnOpenClaw(api); otherwise the default generic
|
|
16
|
-
// path runs (before_reset capture +
|
|
16
|
+
// path runs (before_reset capture + product status, recall, and feedback tools).
|
|
17
17
|
//
|
|
18
18
|
// Host-specific customization goes in a persona module, not here.
|
|
19
19
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "aquifer-memory",
|
|
3
3
|
"name": "Aquifer Memory",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.1",
|
|
5
5
|
"description": "Session ingest + recall + feedback. Reads DATABASE_URL / EMBED_PROVIDER / AQUIFER_LLM_PROVIDER from host env; delegates to AQUIFER_PERSONA module if set.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"hooks": ["before_reset"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aquifer-openclaw-ext",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"description": "Drop-in OpenClaw extension for Aquifer Memory. Symlink into $OPENCLAW_HOME/extensions/aquifer-memory/ — no host-side boilerplate required.",
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
* Aquifer Memory — OpenClaw Host Adapter
|
|
5
5
|
*
|
|
6
6
|
* Ingest adapter: auto-captures sessions on before_reset.
|
|
7
|
-
* Tool adapter: exposes
|
|
7
|
+
* Tool adapter: exposes product status and recall/feedback tools via OpenClaw
|
|
8
|
+
* registerTool().
|
|
8
9
|
*
|
|
9
10
|
* Status: COMPATIBILITY ONLY. The official tool delivery path is mcp.servers.aquifer
|
|
10
11
|
* (see consumers/mcp.js). registerTool() exposure has OpenClaw upstream limitations
|
|
11
|
-
* that
|
|
12
|
-
* session capture; tool registration
|
|
12
|
+
* that can affect tool visibility on some hosts. This plugin is retained for
|
|
13
|
+
* before_reset session capture; tool registration follows the same product
|
|
14
|
+
* status surface as the CLI and MCP server.
|
|
13
15
|
*
|
|
14
16
|
* Install: add to openclaw.json plugins or extensions directory.
|
|
15
17
|
* Config via plugin config, environment variables, or aquifer.config.json.
|
|
@@ -18,6 +20,8 @@
|
|
|
18
20
|
const { createAquiferFromConfig } = require('./shared/factory');
|
|
19
21
|
const { runIngest } = require('./shared/ingest');
|
|
20
22
|
const { formatRecallResults: sharedFormatRecallResults } = require('./shared/recall-format');
|
|
23
|
+
const { registerOpenClawProductStatusTools } = require('./shared/openclaw-product-tools');
|
|
24
|
+
const { buildCompatibilityRecallRequest, runCompatibilityRecall } = require('./shared/compat-recall');
|
|
21
25
|
|
|
22
26
|
// ---------------------------------------------------------------------------
|
|
23
27
|
// Helpers
|
|
@@ -80,7 +84,7 @@ function normalizeEntries(rawEntries) {
|
|
|
80
84
|
if (!entry) continue;
|
|
81
85
|
const msg = entry.message || entry;
|
|
82
86
|
if (!msg || !msg.role) continue;
|
|
83
|
-
if (!['user', 'assistant'
|
|
87
|
+
if (!['user', 'assistant'].includes(msg.role)) continue;
|
|
84
88
|
|
|
85
89
|
let content = '';
|
|
86
90
|
if (typeof msg.content === 'string') {
|
|
@@ -156,17 +160,60 @@ module.exports.normalizeEntries = normalizeEntries;
|
|
|
156
160
|
module.exports.coerceRawEntries = coerceRawEntries;
|
|
157
161
|
module.exports.normalizeTimestamp = normalizeTimestamp;
|
|
158
162
|
|
|
163
|
+
function runPersonaMount(api, persona, pluginConfig) {
|
|
164
|
+
if (!api || typeof api.registerTool !== 'function') {
|
|
165
|
+
return {
|
|
166
|
+
mounted: persona.mountOnOpenClaw(api, pluginConfig) || {},
|
|
167
|
+
registeredTools: new Set(),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const registeredTools = new Set();
|
|
172
|
+
const originalRegisterTool = api.registerTool;
|
|
173
|
+
api.registerTool = function trackedRegisterTool(factory, opts) {
|
|
174
|
+
if (opts?.name) registeredTools.add(opts.name);
|
|
175
|
+
return originalRegisterTool.call(this, factory, opts);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
return {
|
|
180
|
+
mounted: persona.mountOnOpenClaw(api, pluginConfig) || {},
|
|
181
|
+
registeredTools,
|
|
182
|
+
};
|
|
183
|
+
} finally {
|
|
184
|
+
api.registerTool = originalRegisterTool;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function registerProductStatusAfterPersona(api, pluginConfig, mounted, registeredTools) {
|
|
189
|
+
if (registeredTools.has('memory_stats') && registeredTools.has('memory_pending')) return true;
|
|
190
|
+
|
|
191
|
+
let aquifer = mounted.aquifer || null;
|
|
192
|
+
if (!aquifer) {
|
|
193
|
+
try {
|
|
194
|
+
aquifer = createAquiferFromConfig(pluginConfig);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
api.logger.warn(`[aquifer-memory] product status tools unavailable after persona mount: ${err.message}`);
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
registerOpenClawProductStatusTools(api, aquifer, { skipTools: registeredTools });
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
159
205
|
function register(api) {
|
|
160
206
|
const pluginConfig = api.pluginConfig || {};
|
|
161
207
|
|
|
162
208
|
// v1.2.0: delegate to a persona layer if one is configured, otherwise
|
|
163
|
-
// run the generic default path
|
|
209
|
+
// run the generic default path.
|
|
164
210
|
const personaPath = pluginConfig.persona || process.env.AQUIFER_PERSONA;
|
|
165
211
|
if (personaPath) {
|
|
166
212
|
try {
|
|
167
213
|
const persona = require(personaPath);
|
|
168
214
|
if (persona && typeof persona.mountOnOpenClaw === 'function') {
|
|
169
|
-
|
|
215
|
+
const { mounted, registeredTools } = runPersonaMount(api, persona, pluginConfig);
|
|
216
|
+
registerProductStatusAfterPersona(api, pluginConfig, mounted, registeredTools);
|
|
170
217
|
api.logger.info(`[aquifer-memory] registered via persona: ${personaPath}`);
|
|
171
218
|
return;
|
|
172
219
|
}
|
|
@@ -232,6 +279,10 @@ function register(api) {
|
|
|
232
279
|
})();
|
|
233
280
|
});
|
|
234
281
|
|
|
282
|
+
// --- product status tools ---
|
|
283
|
+
|
|
284
|
+
registerOpenClawProductStatusTools(api, aquifer);
|
|
285
|
+
|
|
235
286
|
// --- session_recall tool ---
|
|
236
287
|
|
|
237
288
|
api.registerTool((ctx) => {
|
|
@@ -249,31 +300,23 @@ function register(api) {
|
|
|
249
300
|
source: { type: 'string', description: 'Filter by source' },
|
|
250
301
|
dateFrom: { type: 'string', description: 'Start date YYYY-MM-DD' },
|
|
251
302
|
dateTo: { type: 'string', description: 'End date YYYY-MM-DD' },
|
|
303
|
+
host: { type: 'string', description: 'Audit boundary host filter' },
|
|
304
|
+
sessionId: { type: 'string', description: 'Audit boundary session id filter' },
|
|
252
305
|
entities: { type: 'array', items: { type: 'string' }, description: 'Entity names to match' },
|
|
253
306
|
entityMode: { type: 'string', enum: ['any', 'all'], description: '"any" (default, boost) or "all" (only sessions with every entity)' },
|
|
254
307
|
mode: { type: 'string', enum: ['fts', 'hybrid', 'vector'], description: 'Recall mode: "fts" (keyword only), "hybrid" (default), "vector" (vector only)' },
|
|
255
308
|
explain: { type: 'boolean', description: 'Include per-result score breakdown (diagnostic)' },
|
|
309
|
+
activeScopeKey: { type: 'string', description: 'Active curated memory scope key' },
|
|
310
|
+
activeScopePath: { type: 'array', items: { type: 'string' }, description: 'Ordered curated scope path' },
|
|
256
311
|
},
|
|
257
312
|
required: ['query'],
|
|
258
313
|
},
|
|
259
314
|
async execute(_toolCallId, params) {
|
|
260
315
|
try {
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
source: params.source || undefined,
|
|
266
|
-
dateFrom: params.dateFrom || undefined,
|
|
267
|
-
dateTo: params.dateTo || undefined,
|
|
268
|
-
};
|
|
269
|
-
if (Array.isArray(params.entities) && params.entities.length > 0) {
|
|
270
|
-
recallOpts.entities = params.entities;
|
|
271
|
-
recallOpts.entityMode = params.entityMode || 'any';
|
|
272
|
-
}
|
|
273
|
-
if (params.mode) recallOpts.mode = params.mode;
|
|
274
|
-
|
|
275
|
-
const results = await aquifer.recall(params.query, recallOpts);
|
|
276
|
-
const text = formatRecallResults(results, { showScore: true, showExplain: !!params.explain });
|
|
316
|
+
const request = buildCompatibilityRecallRequest(aquifer, params, ctx || {});
|
|
317
|
+
const results = await runCompatibilityRecall(aquifer, params.query, request);
|
|
318
|
+
const formatted = formatRecallResults(results, { showScore: true, showExplain: !!params.explain });
|
|
319
|
+
const text = [request.laneHeader, '', formatted].join('\n');
|
|
277
320
|
return { content: [{ type: 'text', text }] };
|
|
278
321
|
} catch (err) {
|
|
279
322
|
return {
|
|
@@ -363,5 +406,5 @@ function register(api) {
|
|
|
363
406
|
};
|
|
364
407
|
}, { name: 'feedback_stats' });
|
|
365
408
|
|
|
366
|
-
api.logger.info('[aquifer-memory] registered (before_reset + session_recall + session_feedback + feedback_stats)');
|
|
409
|
+
api.logger.info('[aquifer-memory] registered (before_reset + memory_stats + memory_pending + session_recall + session_feedback + feedback_stats)');
|
|
367
410
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const VALID_RECALL_MODES = new Set(['fts', 'hybrid', 'vector']);
|
|
4
|
+
|
|
5
|
+
function getConfig(aquifer) {
|
|
6
|
+
return aquifer && typeof aquifer.getConfig === 'function' ? (aquifer.getConfig() || {}) : {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function memoryServingMode(aquifer) {
|
|
10
|
+
const config = getConfig(aquifer);
|
|
11
|
+
return config.memoryServingMode || config.serving?.mode || 'legacy';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function firstDefined(...values) {
|
|
15
|
+
return values.find(value => value !== undefined && value !== null && value !== '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function clampLimit(value, fallback = 5) {
|
|
19
|
+
return Math.max(1, Math.min(20, parseInt(value ?? fallback, 10) || fallback));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseScopePath(value) {
|
|
23
|
+
if (Array.isArray(value)) return value.map(item => String(item || '').trim()).filter(Boolean);
|
|
24
|
+
if (typeof value !== 'string') return undefined;
|
|
25
|
+
const parts = value.split(',').map(item => item.trim()).filter(Boolean);
|
|
26
|
+
return parts.length > 0 ? parts : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function hasLegacyBoundary(opts = {}) {
|
|
30
|
+
return Boolean(opts.agentId || opts.source || opts.dateFrom || opts.dateTo || opts.host || opts.sessionId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildCompatibilityRecallRequest(aquifer, params = {}, ctx = {}) {
|
|
34
|
+
const limit = clampLimit(params.limit);
|
|
35
|
+
const mode = memoryServingMode(aquifer);
|
|
36
|
+
const recallOpts = { limit };
|
|
37
|
+
const requestedMode = params.mode;
|
|
38
|
+
if (VALID_RECALL_MODES.has(requestedMode)) recallOpts.mode = requestedMode;
|
|
39
|
+
|
|
40
|
+
if (mode === 'curated') {
|
|
41
|
+
const activeScopeKey = firstDefined(params.activeScopeKey, params.active_scope_key);
|
|
42
|
+
const activeScopePath = firstDefined(params.activeScopePath, params.active_scope_path);
|
|
43
|
+
if (activeScopeKey) recallOpts.activeScopeKey = activeScopeKey;
|
|
44
|
+
const scopePath = parseScopePath(activeScopePath);
|
|
45
|
+
if (scopePath) recallOpts.activeScopePath = scopePath;
|
|
46
|
+
return {
|
|
47
|
+
mode,
|
|
48
|
+
method: 'recall',
|
|
49
|
+
recallOpts,
|
|
50
|
+
laneHeader: 'Current memory recall (curated lane).',
|
|
51
|
+
hasBoundary: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const agentId = firstDefined(params.agentId, params.agent_id, ctx.agentId);
|
|
56
|
+
if (agentId) recallOpts.agentId = agentId;
|
|
57
|
+
const source = firstDefined(params.source);
|
|
58
|
+
if (source) recallOpts.source = source;
|
|
59
|
+
const dateFrom = firstDefined(params.dateFrom, params.date_from);
|
|
60
|
+
if (dateFrom) recallOpts.dateFrom = dateFrom;
|
|
61
|
+
const dateTo = firstDefined(params.dateTo, params.date_to);
|
|
62
|
+
if (dateTo) recallOpts.dateTo = dateTo;
|
|
63
|
+
const host = firstDefined(params.host);
|
|
64
|
+
if (host) recallOpts.host = host;
|
|
65
|
+
const sessionId = firstDefined(params.sessionId, params.session_id);
|
|
66
|
+
if (sessionId) recallOpts.sessionId = sessionId;
|
|
67
|
+
|
|
68
|
+
const entities = Array.isArray(params.entities)
|
|
69
|
+
? params.entities.map(item => String(item || '').trim()).filter(Boolean)
|
|
70
|
+
: [];
|
|
71
|
+
if (entities.length > 0) {
|
|
72
|
+
recallOpts.entities = entities;
|
|
73
|
+
recallOpts.entityMode = firstDefined(params.entityMode, params.entity_mode) || 'any';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
mode,
|
|
78
|
+
method: 'evidenceRecall',
|
|
79
|
+
recallOpts,
|
|
80
|
+
laneHeader: 'Historical/session recall (legacy evidence lane; not current memory).',
|
|
81
|
+
hasBoundary: hasLegacyBoundary(recallOpts),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function runCompatibilityRecall(aquifer, query, request = {}) {
|
|
86
|
+
if (request.method === 'evidenceRecall') {
|
|
87
|
+
if (!request.hasBoundary) {
|
|
88
|
+
throw new Error('legacy session_recall requires a boundary filter (agentId, source, dateFrom/dateTo, host, or sessionId). Use MCP memory_recall for current memory.');
|
|
89
|
+
}
|
|
90
|
+
if (aquifer && typeof aquifer.evidenceRecall === 'function') {
|
|
91
|
+
return aquifer.evidenceRecall(query, request.recallOpts);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return aquifer.recall(query, request.recallOpts);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
buildCompatibilityRecallRequest,
|
|
99
|
+
runCompatibilityRecall,
|
|
100
|
+
memoryServingMode,
|
|
101
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
formatMemoryStatsInterface,
|
|
5
|
+
formatPendingRowsInterface,
|
|
6
|
+
formatPendingWorkInterface,
|
|
7
|
+
} = require('../../core/interface');
|
|
8
|
+
const { MCP_TOOL_MANIFEST } = require('../../core/mcp-manifest');
|
|
9
|
+
|
|
10
|
+
function manifestToolDescription(name, fallback) {
|
|
11
|
+
return MCP_TOOL_MANIFEST.find(tool => tool.name === name)?.description || fallback;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function clampLimit(value, fallback = 20, max = 200) {
|
|
15
|
+
const parsed = parseInt(value ?? fallback, 10);
|
|
16
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
17
|
+
return Math.max(1, Math.min(max, parsed));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pendingSessionOpts(params = {}) {
|
|
21
|
+
return {
|
|
22
|
+
limit: clampLimit(params.limit, 20, 200),
|
|
23
|
+
source: params.source || undefined,
|
|
24
|
+
agentId: params.agentId || params.agent_id || undefined,
|
|
25
|
+
status: params.status || undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeSkipTools(value) {
|
|
30
|
+
if (!value) return new Set();
|
|
31
|
+
if (value instanceof Set) return value;
|
|
32
|
+
if (Array.isArray(value)) return new Set(value);
|
|
33
|
+
return new Set();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function registerOpenClawProductStatusTools(api, aquifer, opts = {}) {
|
|
37
|
+
if (!api || typeof api.registerTool !== 'function') {
|
|
38
|
+
throw new Error('OpenClaw-compatible api.registerTool is required');
|
|
39
|
+
}
|
|
40
|
+
if (!aquifer) {
|
|
41
|
+
throw new Error('aquifer instance is required');
|
|
42
|
+
}
|
|
43
|
+
const skipTools = normalizeSkipTools(opts.skipTools);
|
|
44
|
+
|
|
45
|
+
if (!skipTools.has('memory_stats')) {
|
|
46
|
+
api.registerTool((ctx) => {
|
|
47
|
+
if ((ctx?.sessionKey || '').includes('subagent')) return null;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
name: 'memory_stats',
|
|
51
|
+
description: manifestToolDescription('memory_stats', 'Return Aquifer product status.'),
|
|
52
|
+
parameters: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
diagnostics: { type: 'boolean', description: 'Include raw storage counters and serving diagnostics. Default false.' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async execute(_toolCallId, params = {}) {
|
|
59
|
+
try {
|
|
60
|
+
const stats = await aquifer.getStats();
|
|
61
|
+
const text = formatMemoryStatsInterface(stats, { diagnostics: params.diagnostics === true });
|
|
62
|
+
return { content: [{ type: 'text', text }] };
|
|
63
|
+
} catch (err) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: 'text', text: `memory_stats error: ${err.message}` }],
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}, { name: 'memory_stats' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!skipTools.has('memory_pending')) {
|
|
75
|
+
api.registerTool((ctx) => {
|
|
76
|
+
if ((ctx?.sessionKey || '').includes('subagent')) return null;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
name: 'memory_pending',
|
|
80
|
+
description: manifestToolDescription('memory_pending', 'Return saved-content preparation status.'),
|
|
81
|
+
parameters: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
limit: { type: 'number', description: 'Max results (default 20)' },
|
|
85
|
+
source: { type: 'string', description: 'Filter by source' },
|
|
86
|
+
agentId: { type: 'string', description: 'Filter by agent ID' },
|
|
87
|
+
agent_id: { type: 'string', description: 'Filter by agent ID' },
|
|
88
|
+
status: { type: 'string', enum: ['pending', 'failed'], description: 'Filter by processing status' },
|
|
89
|
+
diagnostics: { type: 'boolean', description: 'Include source/agent/status buckets, guidance, and samples. Default false.' },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
async execute(_toolCallId, params = {}) {
|
|
93
|
+
try {
|
|
94
|
+
const opts = pendingSessionOpts(params);
|
|
95
|
+
const report = typeof aquifer.getPendingWork === 'function'
|
|
96
|
+
? await aquifer.getPendingWork(opts)
|
|
97
|
+
: null;
|
|
98
|
+
const text = report
|
|
99
|
+
? formatPendingWorkInterface(report, {
|
|
100
|
+
diagnostics: params.diagnostics === true,
|
|
101
|
+
includePlan: false,
|
|
102
|
+
})
|
|
103
|
+
: formatPendingRowsInterface(
|
|
104
|
+
typeof aquifer.getPendingSessions === 'function'
|
|
105
|
+
? await aquifer.getPendingSessions(opts)
|
|
106
|
+
: [],
|
|
107
|
+
{ diagnostics: params.diagnostics === true }
|
|
108
|
+
);
|
|
109
|
+
return { content: [{ type: 'text', text }] };
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: 'text', text: `memory_pending error: ${err.message}` }],
|
|
113
|
+
isError: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}, { name: 'memory_pending' });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { aquifer, productStatusToolsRegistered: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
clampLimit,
|
|
126
|
+
manifestToolDescription,
|
|
127
|
+
normalizeSkipTools,
|
|
128
|
+
pendingSessionOpts,
|
|
129
|
+
registerOpenClawProductStatusTools,
|
|
130
|
+
};
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// Shared recall formatter — turns aquifer.recall() rows into human-readable
|
|
5
|
-
// text. The default is English and markdown-ish;
|
|
6
|
-
//
|
|
5
|
+
// text. The default is English and markdown-ish; persona adapters can override
|
|
6
|
+
// individual renderers.
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
|
|
9
9
|
function truncate(s, n) {
|