@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
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
promptSafeSynthesisInput,
|
|
7
7
|
stableJson,
|
|
8
8
|
} = require('../core/session-checkpoint-producer');
|
|
9
|
+
const { assistantShapingPromptLines } = require('../core/memory-type-policy');
|
|
9
10
|
const { compactCurrentMemorySnapshot } = require('./codex-current-memory');
|
|
10
11
|
|
|
11
12
|
function positiveInt(value, fallback, max = 100000) {
|
|
@@ -122,8 +123,9 @@ function buildActiveSessionCheckpointPrompt(checkpointInput = {}, opts = {}) {
|
|
|
122
123
|
'Use only the <active_checkpoint_input> block. Do not use hidden tool output, injected context, or debug material.',
|
|
123
124
|
'This checkpoint is process material for later handoff. It is not active current memory and must not be treated as final truth.',
|
|
124
125
|
'Return compact JSON with this shape:',
|
|
125
|
-
'{"summaryText":"...","structuredSummary":{"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]},"coverage":{"coordinateSystem":"codex_sanitized_view_v1","coveredUntilMessageIndex":0,"coveredUntilChar":0}}',
|
|
126
|
+
'{"summaryText":"...","structuredSummary":{"assistant_shaping":[],"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]},"coverage":{"coordinateSystem":"codex_sanitized_view_v1","coveredUntilMessageIndex":0,"coveredUntilChar":0}}',
|
|
126
127
|
`Keep facts/decisions/open_loops concrete and scoped. Use at most ${maxFacts} facts.`,
|
|
128
|
+
...assistantShapingPromptLines(),
|
|
127
129
|
'Preserve the coverage object so a later handoff can skip the already-covered transcript prefix.',
|
|
128
130
|
'',
|
|
129
131
|
'<active_checkpoint_input>',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { formatRuntimeMemoryLine } = require('../core/memory-type-policy');
|
|
4
|
+
|
|
3
5
|
function compactCurrentMemoryRow(row = {}) {
|
|
4
6
|
const payload = row.payload && typeof row.payload === 'object' ? row.payload : {};
|
|
5
7
|
const confidence = payload.confidence || payload.currentMemoryConfidence || null;
|
|
@@ -10,6 +12,9 @@ function compactCurrentMemoryRow(row = {}) {
|
|
|
10
12
|
summary: String(row.summary || row.title || '').replace(/\s+/g, ' ').trim(),
|
|
11
13
|
authority: row.authority || null,
|
|
12
14
|
confidence,
|
|
15
|
+
policyKey: payload.policyKey || payload.policy_key || row.policyKey || row.policy_key || null,
|
|
16
|
+
shapingKind: payload.shapingKind || payload.shaping_kind || payload.kind || payload.category || row.shapingKind || row.shaping_kind || null,
|
|
17
|
+
servingImpact: payload.servingImpact || payload.serving_impact || payload.impact || row.servingImpact || row.serving_impact || null,
|
|
13
18
|
};
|
|
14
19
|
}
|
|
15
20
|
|
|
@@ -35,12 +40,11 @@ function formatCurrentMemoryPromptBlock(currentMemory = null, opts = {}) {
|
|
|
35
40
|
`truncated="${Boolean(meta.truncated || rows.length > compactRows.length)}"`,
|
|
36
41
|
`degraded="${Boolean(meta.degraded || currentMemory?.error)}"`,
|
|
37
42
|
];
|
|
38
|
-
const lines = compactRows.map(row => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
43
|
+
const lines = compactRows.map(row => formatRuntimeMemoryLine(row, {
|
|
44
|
+
includeScope: true,
|
|
45
|
+
includeAuthority: true,
|
|
46
|
+
includeConfidence: true,
|
|
47
|
+
}));
|
|
44
48
|
if (currentMemory && currentMemory.error && lines.length === 0) {
|
|
45
49
|
lines.push(`- degraded: ${String(currentMemory.error).replace(/\s+/g, ' ').trim()}`);
|
|
46
50
|
}
|
package/consumers/codex.js
CHANGED
|
@@ -22,6 +22,7 @@ const crypto = require('crypto');
|
|
|
22
22
|
const DEFAULT_CODEX_HOME = path.join(os.homedir(), '.codex');
|
|
23
23
|
const { normalizeMessages } = require('./shared/normalize');
|
|
24
24
|
const { applyEnrichSafetyGate } = require('../core/memory-safety-gate');
|
|
25
|
+
const { assistantShapingPromptLines } = require('../core/memory-type-policy');
|
|
25
26
|
const {
|
|
26
27
|
buildActiveSessionCheckpointInput,
|
|
27
28
|
buildActiveSessionCheckpointPrompt,
|
|
@@ -40,7 +41,7 @@ const DEFAULT_MAX_AFTERBURNS = 1;
|
|
|
40
41
|
const DEFAULT_MIN_IMPORT_USER_MESSAGES = 3;
|
|
41
42
|
const MAX_RETRY_COUNT = 3;
|
|
42
43
|
const SAFE_SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/;
|
|
43
|
-
const DEFAULT_RECOVERY_MAX_BYTES = 1024 * 1024;
|
|
44
|
+
const DEFAULT_RECOVERY_MAX_BYTES = 20 * 1024 * 1024;
|
|
44
45
|
const DEFAULT_RECOVERY_MAX_MESSAGES = 80;
|
|
45
46
|
const DEFAULT_RECOVERY_MAX_CHARS = 24000;
|
|
46
47
|
const DEFAULT_RECOVERY_MAX_PROMPT_TOKENS = 9000;
|
|
@@ -1335,8 +1336,9 @@ function buildFinalizationPrompt(view = {}, opts = {}) {
|
|
|
1335
1336
|
'You are finalizing an Aquifer memory session for Codex.',
|
|
1336
1337
|
'Use only the sanitized transcript below. Do not infer from hidden tool output or injected context.',
|
|
1337
1338
|
'Return compact JSON with this shape:',
|
|
1338
|
-
'{"summaryText":"...","structuredSummary":{"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]}}',
|
|
1339
|
+
'{"summaryText":"...","structuredSummary":{"assistant_shaping":[],"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]}}',
|
|
1339
1340
|
`Keep facts/decisions/open_loops concrete and scoped. Use at most ${maxFacts} facts.`,
|
|
1341
|
+
...assistantShapingPromptLines(),
|
|
1340
1342
|
'',
|
|
1341
1343
|
`sessionId: ${view.sessionId}`,
|
|
1342
1344
|
`transcriptHash: ${view.transcriptHash}`,
|
|
@@ -1352,7 +1354,8 @@ function buildFinalizationPrompt(view = {}, opts = {}) {
|
|
|
1352
1354
|
0,
|
|
1353
1355
|
'Use current_memory as the already-committed current state. Reconcile the transcript against it: keep valid state, supersede stale state, and mark uncertain items explicitly.',
|
|
1354
1356
|
);
|
|
1355
|
-
lines.
|
|
1357
|
+
const transcriptIndex = lines.indexOf('<sanitized_transcript>');
|
|
1358
|
+
lines.splice(transcriptIndex >= 0 ? transcriptIndex : lines.length, 0, formatCurrentMemoryPromptBlock(opts.currentMemory, opts), '');
|
|
1356
1359
|
}
|
|
1357
1360
|
return lines.join('\n');
|
|
1358
1361
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// Aquifer default persona — parameterized daily_entries writer.
|
|
4
|
-
// Schema matches
|
|
4
|
+
// Schema matches the generic daily_entries shape (id / event_at / source / tag / text /
|
|
5
5
|
// agent_id / session_id / metadata / dedupe_key) — hosts clone that DDL into
|
|
6
6
|
// their own schema and set persona.dailyTable = '<schema>.daily_entries'.
|
|
7
7
|
//
|
|
8
8
|
// Host must create the table before use:
|
|
9
|
-
// CREATE TABLE
|
|
9
|
+
// CREATE TABLE app_memory.daily_entries (...);
|
|
10
10
|
|
|
11
11
|
const crypto = require('crypto');
|
|
12
12
|
const { parseHandoffSection } = require('../shared/summary-parser');
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
// Entry point:
|
|
7
7
|
// const persona = require('@shadowforge0/aquifer-memory/consumers/default')
|
|
8
8
|
// .createPersona({
|
|
9
|
-
// agentName: '
|
|
10
|
-
// observedOwner: '
|
|
11
|
-
// schema: '
|
|
12
|
-
// scope: '
|
|
13
|
-
// dailyTable: '
|
|
9
|
+
// agentName: 'Assistant',
|
|
10
|
+
// observedOwner: 'operator',
|
|
11
|
+
// schema: 'app_memory',
|
|
12
|
+
// scope: 'app_memory',
|
|
13
|
+
// dailyTable: 'app_memory.daily_entries', // or null to skip daily writes
|
|
14
14
|
// language: 'zh-TW', // or 'en'
|
|
15
|
-
// briefingIntro: '
|
|
15
|
+
// briefingIntro: '以下是現況...', // optional context-inject preamble
|
|
16
16
|
// });
|
|
17
17
|
//
|
|
18
18
|
// Returns a persona module with the standard persona adapter shape — host
|
|
@@ -21,13 +21,15 @@
|
|
|
21
21
|
// .createPersona({ ... });
|
|
22
22
|
//
|
|
23
23
|
// This is intentionally a minimal persona: summary + optional daily_entries,
|
|
24
|
-
// no workspace-files, no consolidation, no
|
|
24
|
+
// no workspace-files, no consolidation, no host-specific scaffolding.
|
|
25
25
|
// Host can extend by composing with Aquifer primitives from consumers/shared.
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
const { createAquifer } = require('../../index');
|
|
29
29
|
const { runIngest } = require('../shared/ingest');
|
|
30
30
|
const { parseEntitySection } = require('../shared/entity-parser');
|
|
31
|
+
const { registerOpenClawProductStatusTools } = require('../shared/openclaw-product-tools');
|
|
32
|
+
const { buildCompatibilityRecallRequest, runCompatibilityRecall, memoryServingMode } = require('../shared/compat-recall');
|
|
31
33
|
|
|
32
34
|
const summaryModule = require('./prompts/summary');
|
|
33
35
|
const dailyEntriesModule = require('./daily-entries');
|
|
@@ -129,10 +131,10 @@ function createPersona(personaOpts = {}) {
|
|
|
129
131
|
// v1.2.0: all four are env-driven by default. Host may override any of
|
|
130
132
|
// them via opts. Aquifer core throws with clear guidance if the required
|
|
131
133
|
// env vars are missing, so we do not pre-validate here.
|
|
132
|
-
const aquifer = getAquifer(opts);
|
|
133
|
-
const pool = opts.pool || aquifer.getPool();
|
|
134
|
-
const llmFn = opts.llmFn || aquifer.getLlmFn();
|
|
135
|
-
const embedFn = opts.embedFn || aquifer.getEmbedFn();
|
|
134
|
+
const aquifer = opts.aquifer || getAquifer(opts);
|
|
135
|
+
const pool = opts.pool || (aquifer.getPool ? aquifer.getPool() : null);
|
|
136
|
+
const llmFn = opts.llmFn || (aquifer.getLlmFn ? aquifer.getLlmFn() : null);
|
|
137
|
+
const embedFn = opts.embedFn || (aquifer.getEmbedFn ? aquifer.getEmbedFn() : null);
|
|
136
138
|
return {
|
|
137
139
|
pool,
|
|
138
140
|
embedFn,
|
|
@@ -205,7 +207,16 @@ function createPersona(personaOpts = {}) {
|
|
|
205
207
|
try {
|
|
206
208
|
const agentId = ctx?.agentId || defaultAgentId;
|
|
207
209
|
if ((ctx?.sessionKey || '').includes('subagent')) return;
|
|
208
|
-
const
|
|
210
|
+
const bootstrapOpts = { limit: 5, maxChars: 2000, format: 'text' };
|
|
211
|
+
if (memoryServingMode(aquifer) === 'curated') {
|
|
212
|
+
const activeScopeKey = opts.activeScopeKey || opts.active_scope_key || ctx?.activeScopeKey || ctx?.active_scope_key;
|
|
213
|
+
const activeScopePath = opts.activeScopePath || opts.active_scope_path || ctx?.activeScopePath || ctx?.active_scope_path;
|
|
214
|
+
if (activeScopeKey) bootstrapOpts.activeScopeKey = activeScopeKey;
|
|
215
|
+
if (activeScopePath) bootstrapOpts.activeScopePath = activeScopePath;
|
|
216
|
+
} else {
|
|
217
|
+
bootstrapOpts.agentId = agentId;
|
|
218
|
+
}
|
|
219
|
+
const recalled = await aquifer.bootstrap(bootstrapOpts);
|
|
209
220
|
const context = persona.briefingIntro + (recalled ? `\n\n${recalled}` : '');
|
|
210
221
|
if (context.length > 0) return { prependSystemContext: context };
|
|
211
222
|
} catch (err) {
|
|
@@ -232,33 +243,23 @@ function createPersona(personaOpts = {}) {
|
|
|
232
243
|
source: { type: 'string' },
|
|
233
244
|
date_from: { type: 'string' },
|
|
234
245
|
date_to: { type: 'string' },
|
|
246
|
+
host: { type: 'string' },
|
|
247
|
+
session_id: { type: 'string' },
|
|
235
248
|
entities: { type: 'array', items: { type: 'string' }, description: 'Named entities (person/project/tool/file)' },
|
|
236
249
|
entity_mode: { type: 'string', enum: ['any', 'all'], description: '"any" boosts; "all" hard-filters to sessions containing every entity' },
|
|
237
250
|
mode: { type: 'string', enum: ['fts', 'hybrid', 'vector'], description: 'Recall strategy, default hybrid' },
|
|
251
|
+
active_scope_key: { type: 'string' },
|
|
252
|
+
active_scope_path: { type: 'array', items: { type: 'string' } },
|
|
238
253
|
},
|
|
239
254
|
},
|
|
240
255
|
async execute(_toolCallId, params) {
|
|
241
256
|
try {
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
agentId: params?.agent_id || ctx?.agentId || undefined,
|
|
245
|
-
source: params?.source || undefined,
|
|
246
|
-
dateFrom: params?.date_from || undefined,
|
|
247
|
-
dateTo: params?.date_to || undefined,
|
|
248
|
-
limit,
|
|
249
|
-
};
|
|
250
|
-
if (Array.isArray(params?.entities) && params.entities.length > 0) {
|
|
251
|
-
recallOpts.entities = params.entities;
|
|
252
|
-
recallOpts.entityMode = params?.entity_mode || 'any';
|
|
253
|
-
}
|
|
254
|
-
if (params?.mode === 'fts' || params?.mode === 'hybrid' || params?.mode === 'vector') {
|
|
255
|
-
recallOpts.mode = params.mode;
|
|
256
|
-
}
|
|
257
|
-
const results = await aquifer.recall(String(params?.query || ''), recallOpts);
|
|
257
|
+
const request = buildCompatibilityRecallRequest(aquifer, params || {}, ctx || {});
|
|
258
|
+
const results = await runCompatibilityRecall(aquifer, String(params?.query || ''), request);
|
|
258
259
|
const lines = results.map((r, i) =>
|
|
259
260
|
`${i+1}. ${r.structuredSummary?.title || r.summaryText?.slice(0, 80) || '(untitled)'}`
|
|
260
261
|
);
|
|
261
|
-
return { content: [{ type: 'text', text: lines.join('\n') || 'No matching sessions.' }] };
|
|
262
|
+
return { content: [{ type: 'text', text: [request.laneHeader, '', lines.join('\n') || 'No matching sessions.'].join('\n') }] };
|
|
262
263
|
} catch (err) {
|
|
263
264
|
return { content: [{ type: 'text', text: `session_recall error: ${err.message}` }], isError: true };
|
|
264
265
|
}
|
|
@@ -269,11 +270,19 @@ function createPersona(personaOpts = {}) {
|
|
|
269
270
|
return { aquifer };
|
|
270
271
|
}
|
|
271
272
|
|
|
273
|
+
function registerProductStatusTools(api, opts = {}) {
|
|
274
|
+
const aquifer = opts.aquifer || resolveCommon(opts).aquifer;
|
|
275
|
+
registerOpenClawProductStatusTools(api, aquifer);
|
|
276
|
+
api.logger.info('[default-persona] registerProductStatusTools: memory_stats + memory_pending registered');
|
|
277
|
+
return { aquifer, productStatusToolsRegistered: true };
|
|
278
|
+
}
|
|
279
|
+
|
|
272
280
|
function mountOnOpenClaw(api, opts = {}) {
|
|
273
281
|
const r = registerAfterburn(api, opts);
|
|
282
|
+
registerProductStatusTools(api, { ...opts, aquifer: r.aquifer });
|
|
274
283
|
registerContextInject(api, opts);
|
|
275
284
|
registerRecallTool(api, opts);
|
|
276
|
-
return r;
|
|
285
|
+
return { ...r, productStatusToolsRegistered: true };
|
|
277
286
|
}
|
|
278
287
|
|
|
279
288
|
return {
|
|
@@ -282,6 +291,7 @@ function createPersona(personaOpts = {}) {
|
|
|
282
291
|
registerAfterburn,
|
|
283
292
|
registerContextInject,
|
|
284
293
|
registerRecallTool,
|
|
294
|
+
registerProductStatusTools,
|
|
285
295
|
buildPostProcess,
|
|
286
296
|
buildSummaryFn,
|
|
287
297
|
buildEntityParseFn,
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// Parameterized via personaOpts:
|
|
6
6
|
// agentName — human name/role the prompt addresses (default 'Assistant')
|
|
7
7
|
// observedOwner — if set, the prompt asks for a short observation about
|
|
8
|
-
// that person
|
|
8
|
+
// that person.
|
|
9
9
|
// null → the section is omitted entirely.
|
|
10
10
|
// language — 'en' | 'zh-TW' (default 'en')
|
|
11
11
|
//
|
|
12
|
-
// Output format mirrors
|
|
12
|
+
// Output format mirrors the generic RECAP fields so downstream daily-entries
|
|
13
13
|
// parsing works uniformly across personas.
|
|
14
14
|
|
|
15
15
|
function buildSummaryPrompt({ conversationText, agentId, now, dailyContext, persona = {} }) {
|
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,42 +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
|
-
lines.push(`Summaries: ${stats.summaries}`);
|
|
393
|
-
lines.push(`Turn embeddings: ${stats.turnEmbeddings}`);
|
|
394
|
-
lines.push(`Entities: ${stats.entities}`);
|
|
395
|
-
if (stats.memoryRecords) {
|
|
396
|
-
lines.push(`Memory records: ${stats.memoryRecords.total} total (${stats.memoryRecords.active} active, ${stats.memoryRecords.visibleInRecall} recall-visible, ${stats.memoryRecords.visibleInBootstrap} bootstrap-visible)`);
|
|
397
|
-
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)}`);
|
|
398
|
-
}
|
|
399
|
-
if (stats.sessionFinalizations?.available) {
|
|
400
|
-
const statusText = Object.entries(stats.sessionFinalizations.statuses || {})
|
|
401
|
-
.map(([status, count]) => `${status}: ${count}`)
|
|
402
|
-
.join(', ') || 'none';
|
|
403
|
-
lines.push(`Session finalizations: ${stats.sessionFinalizations.total} total (${statusText})`);
|
|
404
|
-
if (stats.sessionFinalizations.latestFinalizedAt) {
|
|
405
|
-
lines.push(`Latest finalization: ${new Date(stats.sessionFinalizations.latestFinalizedAt).toISOString().slice(0, 10)}`);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (stats.earliest) lines.push(`Date range: ${new Date(stats.earliest).toISOString().slice(0, 10)} → ${new Date(stats.latest).toISOString().slice(0, 10)}`);
|
|
409
|
-
if ((stats.serving?.mode || 'legacy') !== 'curated') {
|
|
410
|
-
lines.push('Warning: legacy serving returns session/evidence material; configure curated serving with an active scope for current-memory answers.');
|
|
411
|
-
}
|
|
412
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
412
|
+
const text = formatMemoryStatsInterface(stats, { diagnostics: params.diagnostics === true });
|
|
413
|
+
return { content: [{ type: 'text', text }] };
|
|
413
414
|
} catch (err) {
|
|
414
415
|
return {
|
|
415
416
|
content: [{ type: 'text', text: `memory_stats error: ${err.message}` }],
|
|
@@ -421,22 +422,31 @@ async function main() {
|
|
|
421
422
|
|
|
422
423
|
server.tool(
|
|
423
424
|
'memory_pending',
|
|
424
|
-
'
|
|
425
|
+
manifestToolDescription('memory_pending', 'Return saved-content preparation status.'),
|
|
425
426
|
{
|
|
426
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.'),
|
|
427
432
|
},
|
|
428
433
|
async (params) => {
|
|
429
434
|
try {
|
|
430
435
|
const aquifer = getAquifer();
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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 }] };
|
|
438
446
|
}
|
|
439
|
-
|
|
447
|
+
const rows = await aquifer.getPendingSessions(queryOpts);
|
|
448
|
+
const text = formatPendingRowsInterface(rows, { diagnostics: params.diagnostics === true });
|
|
449
|
+
return { content: [{ type: 'text', text }] };
|
|
440
450
|
} catch (err) {
|
|
441
451
|
return {
|
|
442
452
|
content: [{ type: 'text', text: `memory_pending error: ${err.message}` }],
|
|
@@ -496,16 +506,16 @@ async function main() {
|
|
|
496
506
|
if (!envelope.ready) {
|
|
497
507
|
const err = envelope.error || { code: 'AQ_MIGRATION_NOT_READY', message: 'aquifer.init() did not reach ready state' };
|
|
498
508
|
process.stderr.write(
|
|
499
|
-
`[aquifer-mcp] startup aborted: migrationMode=${envelope.migrationMode} ` +
|
|
500
|
-
`memoryMode=${envelope.memoryMode} pending=${envelope.pendingMigrations
|
|
509
|
+
`[aquifer-mcp] startup aborted: migrationMode=${envelope.migrationMode || 'unknown'} ` +
|
|
510
|
+
`memoryMode=${envelope.memoryMode || 'unknown'} pending=${countEnvelopeList(envelope.pendingMigrations)} ` +
|
|
501
511
|
`error=${err.code || 'unknown'}: ${err.message}\n`
|
|
502
512
|
);
|
|
503
513
|
await aquifer.close().catch(() => {});
|
|
504
514
|
process.exit(1);
|
|
505
515
|
}
|
|
506
516
|
process.stderr.write(
|
|
507
|
-
`[aquifer-mcp] init ok: mode=${envelope.migrationMode} applied=${envelope.appliedMigrations
|
|
508
|
-
`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`
|
|
509
519
|
);
|
|
510
520
|
|
|
511
521
|
const transport = new StdioServerTransport();
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
// $OPENCLAW_HOME/extensions/aquifer-memory/ ← symlink to this directory
|
|
7
7
|
//
|
|
8
8
|
// Behavior:
|
|
9
|
-
// -
|
|
10
|
-
//
|
|
9
|
+
// - Reads $OPENCLAW_HOME/.env and then mcp.servers.aquifer.env from
|
|
10
|
+
// $OPENCLAW_HOME/openclaw.json so ingest uses the same DB/schema/env as
|
|
11
|
+
// the Aquifer MCP recall tools, without leaving those values in the
|
|
12
|
+
// shared OpenClaw process environment.
|
|
11
13
|
// - Delegates to consumers/openclaw-plugin.js. If AQUIFER_PERSONA is set
|
|
12
14
|
// (pluginConfig.persona or env), the plugin loads the persona module
|
|
13
15
|
// and hands off mountOnOpenClaw(api); otherwise the default generic
|
|
14
|
-
// path runs (before_reset capture +
|
|
16
|
+
// path runs (before_reset capture + product status, recall, and feedback tools).
|
|
15
17
|
//
|
|
16
18
|
// Host-specific customization goes in a persona module, not here.
|
|
17
19
|
|
|
@@ -21,17 +23,73 @@ const os = require('os');
|
|
|
21
23
|
|
|
22
24
|
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw');
|
|
23
25
|
|
|
24
|
-
function
|
|
26
|
+
function mergeEnvObject(target, values, opts = {}) {
|
|
27
|
+
if (!values || typeof values !== 'object') return;
|
|
28
|
+
for (const [key, value] of Object.entries(values)) {
|
|
29
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) continue;
|
|
30
|
+
if (value === undefined || value === null) continue;
|
|
31
|
+
if (!opts.override && (process.env[key] || target[key])) continue;
|
|
32
|
+
target[key] = String(value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readEnvFile(envPath) {
|
|
37
|
+
const values = {};
|
|
25
38
|
try {
|
|
26
39
|
const text = fs.readFileSync(envPath, 'utf8');
|
|
27
40
|
for (const line of text.split('\n')) {
|
|
28
41
|
const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
29
|
-
if (m
|
|
42
|
+
if (m) values[m[1]] = m[2].trim();
|
|
30
43
|
}
|
|
31
44
|
} catch { /* .env missing — ok */ }
|
|
45
|
+
return values;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readOpenClawAquiferMcpEnv(configPath) {
|
|
49
|
+
try {
|
|
50
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
51
|
+
return cfg.mcp?.servers?.aquifer?.env || {};
|
|
52
|
+
} catch { /* openclaw.json missing or malformed — plugin config/env may still work */ }
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildAquiferEnv() {
|
|
57
|
+
const env = {};
|
|
58
|
+
mergeEnvObject(env, readEnvFile(path.join(OPENCLAW_HOME, '.env')));
|
|
59
|
+
mergeEnvObject(env, readOpenClawAquiferMcpEnv(path.join(OPENCLAW_HOME, 'openclaw.json')), { override: true });
|
|
60
|
+
return env;
|
|
32
61
|
}
|
|
33
62
|
|
|
34
|
-
|
|
63
|
+
function withEnvOverlay(env, fn) {
|
|
64
|
+
const previous = new Map();
|
|
65
|
+
for (const [key, value] of Object.entries(env || {})) {
|
|
66
|
+
previous.set(key, Object.prototype.hasOwnProperty.call(process.env, key) ? process.env[key] : undefined);
|
|
67
|
+
process.env[key] = value;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return fn();
|
|
71
|
+
} finally {
|
|
72
|
+
for (const [key, value] of previous.entries()) {
|
|
73
|
+
if (value === undefined) delete process.env[key];
|
|
74
|
+
else process.env[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
35
78
|
|
|
36
79
|
// Re-export the plugin as-is. OpenClaw expects { id, name, register }.
|
|
37
|
-
|
|
80
|
+
const plugin = require('../openclaw-plugin');
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
...plugin,
|
|
84
|
+
register(api) {
|
|
85
|
+
const aquiferEnv = buildAquiferEnv();
|
|
86
|
+
const pluginConfig = { ...(api.pluginConfig || {}) };
|
|
87
|
+
if (!pluginConfig.persona && aquiferEnv.AQUIFER_PERSONA) {
|
|
88
|
+
pluginConfig.persona = aquiferEnv.AQUIFER_PERSONA;
|
|
89
|
+
}
|
|
90
|
+
return withEnvOverlay(aquiferEnv, () => plugin.register({
|
|
91
|
+
...api,
|
|
92
|
+
pluginConfig,
|
|
93
|
+
}));
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "aquifer-memory",
|
|
3
3
|
"name": "Aquifer Memory",
|
|
4
|
-
"version": "1.
|
|
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.
|
|
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.",
|