@shadowforge0/aquifer-memory 1.8.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -0
- package/README.md +49 -22
- package/README_CN.md +24 -22
- package/README_TW.md +20 -22
- package/aquifer.config.example.json +2 -1
- package/consumers/cli.js +535 -2
- package/consumers/codex.js +1 -1
- package/consumers/mcp.js +3 -0
- package/consumers/openclaw-ext/index.js +64 -6
- 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 +39 -1
- package/consumers/shared/config.js +2 -0
- package/core/aquifer.js +180 -33
- package/core/backends/local.js +109 -0
- package/core/doctor.js +924 -0
- package/core/finalization-inspector.js +164 -0
- package/core/memory-explain.js +624 -0
- package/core/memory-recall.js +49 -23
- package/core/memory-records.js +16 -5
- package/core/memory-review.js +891 -0
- package/core/memory-serving.js +61 -4
- package/core/operator-observability.js +249 -0
- package/core/postgres-migrations.js +13 -0
- package/core/session-finalization.js +76 -1
- package/core/storage.js +124 -8
- package/docs/getting-started.md +34 -1
- package/docs/setup.md +102 -22
- package/package.json +5 -4
- package/schema/019-v1-memory-review-resolutions.sql +53 -0
- package/scripts/codex-checkpoint-commands.js +28 -0
- package/scripts/codex-checkpoint-runtime.js +109 -0
- package/scripts/codex-recovery.js +16 -4
|
@@ -6,8 +6,10 @@
|
|
|
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
|
|
@@ -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.0",
|
|
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.0",
|
|
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.",
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { version: packageVersion } = require('../package.json');
|
|
7
|
+
|
|
8
|
+
const OPENCLAW_PLUGIN_ID = 'aquifer-memory';
|
|
9
|
+
|
|
10
|
+
function nowStamp() {
|
|
11
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function uniqueBackupPath(filePath) {
|
|
15
|
+
const base = `${filePath}.bak-${nowStamp()}`;
|
|
16
|
+
if (!fs.existsSync(base)) return base;
|
|
17
|
+
for (let i = 1; i < 1000; i += 1) {
|
|
18
|
+
const candidate = `${base}-${i}`;
|
|
19
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Could not allocate unique backup path for ${filePath}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function valueFlag(flags = {}, name, fallback = null) {
|
|
25
|
+
const value = flags[name];
|
|
26
|
+
if (value === undefined || value === null || value === true || value === '') return fallback;
|
|
27
|
+
return String(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveOpenClawHome(flags = {}, env = process.env) {
|
|
31
|
+
return path.resolve(valueFlag(flags, 'openclaw-home', env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw')));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readJson(filePath) {
|
|
35
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function writeJsonWithBackup(filePath, value, opts = {}) {
|
|
39
|
+
if (opts.dryRun) return null;
|
|
40
|
+
const backupPath = uniqueBackupPath(filePath);
|
|
41
|
+
if (fs.existsSync(filePath)) fs.copyFileSync(filePath, backupPath);
|
|
42
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
43
|
+
return backupPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function samePath(a, b) {
|
|
47
|
+
return path.resolve(a) === path.resolve(b);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ensureOpenClawHome(openclawHome) {
|
|
51
|
+
if (!fs.existsSync(openclawHome) || !fs.statSync(openclawHome).isDirectory()) {
|
|
52
|
+
throw new Error(`OPENCLAW_HOME not found: ${openclawHome}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ensureExtensionLink({ openclawHome, packageRoot, dryRun = false, force = false }) {
|
|
57
|
+
const source = path.join(packageRoot, 'consumers', 'openclaw-ext');
|
|
58
|
+
const dest = path.join(openclawHome, 'extensions', 'aquifer-memory');
|
|
59
|
+
if (!fs.existsSync(source)) throw new Error(`OpenClaw extension source not found: ${source}`);
|
|
60
|
+
|
|
61
|
+
const result = {
|
|
62
|
+
source,
|
|
63
|
+
dest,
|
|
64
|
+
action: 'link',
|
|
65
|
+
backupPath: null,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(dest)) {
|
|
69
|
+
const stat = fs.lstatSync(dest);
|
|
70
|
+
if (stat.isSymbolicLink()) {
|
|
71
|
+
const target = fs.readlinkSync(dest);
|
|
72
|
+
const resolvedTarget = path.resolve(path.dirname(dest), target);
|
|
73
|
+
if (samePath(resolvedTarget, source)) {
|
|
74
|
+
return { ...result, action: 'already-linked' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!force) {
|
|
79
|
+
throw new Error(`${dest} already exists; rerun with --force to move it aside and relink`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
result.backupPath = `${dest}.bak-${nowStamp()}`;
|
|
83
|
+
result.action = 'replace-link';
|
|
84
|
+
if (!dryRun) fs.renameSync(dest, result.backupPath);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!dryRun) {
|
|
88
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
89
|
+
fs.symlinkSync(source, dest, 'dir');
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeMcpServer(existing = {}, packageRoot) {
|
|
95
|
+
return {
|
|
96
|
+
...existing,
|
|
97
|
+
command: 'node',
|
|
98
|
+
args: [path.join(packageRoot, 'consumers', 'mcp.js')],
|
|
99
|
+
env: {
|
|
100
|
+
...(existing.env && typeof existing.env === 'object' ? existing.env : {}),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function ensureMcpConfig({ openclawHome, packageRoot, dryRun = false }) {
|
|
106
|
+
const configPath = path.join(openclawHome, 'openclaw.json');
|
|
107
|
+
const mcpPath = path.join(packageRoot, 'consumers', 'mcp.js');
|
|
108
|
+
const result = {
|
|
109
|
+
configPath,
|
|
110
|
+
mcpPath,
|
|
111
|
+
action: 'skipped',
|
|
112
|
+
backupPath: null,
|
|
113
|
+
previousArgs: null,
|
|
114
|
+
nextArgs: [mcpPath],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (!fs.existsSync(configPath)) {
|
|
118
|
+
return { ...result, reason: 'openclaw.json not found' };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const config = readJson(configPath);
|
|
122
|
+
const existing = config.mcp?.servers?.aquifer || {};
|
|
123
|
+
result.previousArgs = Array.isArray(existing.args) ? existing.args : null;
|
|
124
|
+
|
|
125
|
+
const nextServer = normalizeMcpServer(existing, packageRoot);
|
|
126
|
+
const previousText = JSON.stringify(existing);
|
|
127
|
+
const nextText = JSON.stringify(nextServer);
|
|
128
|
+
if (previousText === nextText) {
|
|
129
|
+
return { ...result, action: 'already-configured' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
config.mcp = config.mcp && typeof config.mcp === 'object' ? config.mcp : {};
|
|
133
|
+
config.mcp.servers = config.mcp.servers && typeof config.mcp.servers === 'object'
|
|
134
|
+
? config.mcp.servers
|
|
135
|
+
: {};
|
|
136
|
+
config.mcp.servers.aquifer = nextServer;
|
|
137
|
+
result.action = 'write-config';
|
|
138
|
+
result.backupPath = writeJsonWithBackup(configPath, config, { dryRun });
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizePluginEntry(existing = {}) {
|
|
143
|
+
const entry = existing && typeof existing === 'object' ? existing : {};
|
|
144
|
+
return {
|
|
145
|
+
...entry,
|
|
146
|
+
enabled: true,
|
|
147
|
+
config: entry.config && typeof entry.config === 'object' ? entry.config : {},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function ensurePluginConfig({ openclawHome, extensionPath, dryRun = false }) {
|
|
152
|
+
const configPath = path.join(openclawHome, 'openclaw.json');
|
|
153
|
+
const result = {
|
|
154
|
+
configPath,
|
|
155
|
+
pluginId: OPENCLAW_PLUGIN_ID,
|
|
156
|
+
extensionPath,
|
|
157
|
+
action: 'skipped',
|
|
158
|
+
backupPath: null,
|
|
159
|
+
previousLoadPaths: null,
|
|
160
|
+
nextLoadPaths: null,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(configPath)) {
|
|
164
|
+
return { ...result, reason: 'openclaw.json not found' };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const config = readJson(configPath);
|
|
168
|
+
const plugins = config.plugins && typeof config.plugins === 'object' ? config.plugins : {};
|
|
169
|
+
const load = plugins.load && typeof plugins.load === 'object' ? plugins.load : {};
|
|
170
|
+
const previousLoadPaths = Array.isArray(load.paths) ? load.paths : [];
|
|
171
|
+
const nextLoadPaths = previousLoadPaths.includes(extensionPath)
|
|
172
|
+
? previousLoadPaths
|
|
173
|
+
: [...previousLoadPaths, extensionPath];
|
|
174
|
+
const entries = plugins.entries && typeof plugins.entries === 'object' ? plugins.entries : {};
|
|
175
|
+
const previousEntry = entries[OPENCLAW_PLUGIN_ID];
|
|
176
|
+
const nextEntry = normalizePluginEntry(previousEntry);
|
|
177
|
+
const previousAllow = Array.isArray(plugins.allow) ? plugins.allow : null;
|
|
178
|
+
const nextAllow = previousAllow && !previousAllow.includes(OPENCLAW_PLUGIN_ID)
|
|
179
|
+
? [...previousAllow, OPENCLAW_PLUGIN_ID]
|
|
180
|
+
: previousAllow;
|
|
181
|
+
|
|
182
|
+
result.previousLoadPaths = previousLoadPaths;
|
|
183
|
+
result.nextLoadPaths = nextLoadPaths;
|
|
184
|
+
|
|
185
|
+
const unchanged = JSON.stringify(previousLoadPaths) === JSON.stringify(nextLoadPaths)
|
|
186
|
+
&& JSON.stringify(previousEntry) === JSON.stringify(nextEntry)
|
|
187
|
+
&& JSON.stringify(previousAllow) === JSON.stringify(nextAllow);
|
|
188
|
+
if (unchanged) {
|
|
189
|
+
return { ...result, action: 'already-configured' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
config.plugins = {
|
|
193
|
+
...plugins,
|
|
194
|
+
entries: {
|
|
195
|
+
...entries,
|
|
196
|
+
[OPENCLAW_PLUGIN_ID]: nextEntry,
|
|
197
|
+
},
|
|
198
|
+
load: {
|
|
199
|
+
...load,
|
|
200
|
+
paths: nextLoadPaths,
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
if (nextAllow) config.plugins.allow = nextAllow;
|
|
204
|
+
|
|
205
|
+
result.action = 'write-config';
|
|
206
|
+
result.backupPath = writeJsonWithBackup(configPath, config, { dryRun });
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function inspectInstalledPackage(openclawHome) {
|
|
211
|
+
const packagePath = path.join(openclawHome, 'node_modules', '@shadowforge0', 'aquifer-memory', 'package.json');
|
|
212
|
+
if (!fs.existsSync(packagePath)) {
|
|
213
|
+
return { packagePath, installed: false, version: null };
|
|
214
|
+
}
|
|
215
|
+
const pkg = readJson(packagePath);
|
|
216
|
+
return { packagePath, installed: true, version: pkg.version || null };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildInstallReport(args = {}, opts = {}) {
|
|
220
|
+
const flags = args.flags || {};
|
|
221
|
+
const env = opts.env || process.env;
|
|
222
|
+
const openclawHome = resolveOpenClawHome(flags, env);
|
|
223
|
+
const packageRoot = opts.packageRoot || path.resolve(__dirname, '..');
|
|
224
|
+
const dryRun = flags['dry-run'] === true;
|
|
225
|
+
const force = flags.force === true;
|
|
226
|
+
const linkCurrentPackage = flags['link-current-package'] === true;
|
|
227
|
+
const skipExtension = flags['skip-extension'] === true;
|
|
228
|
+
const skipMcp = flags['skip-mcp'] === true;
|
|
229
|
+
|
|
230
|
+
ensureOpenClawHome(openclawHome);
|
|
231
|
+
|
|
232
|
+
const installedPackage = inspectInstalledPackage(openclawHome);
|
|
233
|
+
const expectedPackageRoot = path.join(openclawHome, 'node_modules', '@shadowforge0', 'aquifer-memory');
|
|
234
|
+
const targetPackageRoot = linkCurrentPackage || !installedPackage.installed
|
|
235
|
+
? packageRoot
|
|
236
|
+
: expectedPackageRoot;
|
|
237
|
+
const report = {
|
|
238
|
+
ok: true,
|
|
239
|
+
dryRun,
|
|
240
|
+
openclawHome,
|
|
241
|
+
packageRoot,
|
|
242
|
+
targetPackageRoot,
|
|
243
|
+
linkCurrentPackage,
|
|
244
|
+
packageVersion,
|
|
245
|
+
expectedPackageRoot,
|
|
246
|
+
installedPackage,
|
|
247
|
+
extension: skipExtension ? { action: 'skipped', reason: '--skip-extension' } : null,
|
|
248
|
+
plugin: skipExtension ? { action: 'skipped', reason: '--skip-extension' } : null,
|
|
249
|
+
mcp: skipMcp ? { action: 'skipped', reason: '--skip-mcp' } : null,
|
|
250
|
+
warnings: [],
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
if (installedPackage.installed && installedPackage.version && installedPackage.version !== packageVersion) {
|
|
254
|
+
report.warnings.push(
|
|
255
|
+
`OpenClaw node_modules has @shadowforge0/aquifer-memory ${installedPackage.version}; installer package is ${packageVersion}.`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (!samePath(targetPackageRoot, expectedPackageRoot)) {
|
|
259
|
+
report.warnings.push(
|
|
260
|
+
`MCP will point outside OpenClaw node_modules: ${targetPackageRoot}. For a durable package install, run npm install --prefix "${openclawHome}" @shadowforge0/aquifer-memory@${packageVersion}.`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (!samePath(packageRoot, expectedPackageRoot) && samePath(targetPackageRoot, expectedPackageRoot)) {
|
|
264
|
+
report.warnings.push(
|
|
265
|
+
`Installer is running from ${packageRoot}, but durable OpenClaw wiring targets ${expectedPackageRoot}. Use --link-current-package only for source-checkout development.`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!skipExtension) {
|
|
270
|
+
report.extension = ensureExtensionLink({ openclawHome, packageRoot: targetPackageRoot, dryRun, force });
|
|
271
|
+
report.plugin = ensurePluginConfig({
|
|
272
|
+
openclawHome,
|
|
273
|
+
extensionPath: report.extension.dest,
|
|
274
|
+
dryRun,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!skipMcp) {
|
|
279
|
+
report.mcp = ensureMcpConfig({ openclawHome, packageRoot: targetPackageRoot, dryRun });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return report;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function formatInstallReport(report) {
|
|
286
|
+
const lines = [];
|
|
287
|
+
lines.push(`Aquifer package: ${report.packageVersion} at ${report.packageRoot}`);
|
|
288
|
+
if (report.installedPackage.installed) {
|
|
289
|
+
lines.push(`OpenClaw package: ${report.installedPackage.version} at ${report.installedPackage.packagePath}`);
|
|
290
|
+
} else {
|
|
291
|
+
lines.push(`OpenClaw package: not installed at ${report.installedPackage.packagePath}`);
|
|
292
|
+
}
|
|
293
|
+
lines.push(`Extension: ${report.extension.action}${report.extension.dest ? ` (${report.extension.dest})` : ''}`);
|
|
294
|
+
if (report.extension.backupPath) lines.push(`Extension backup: ${report.extension.backupPath}`);
|
|
295
|
+
lines.push(`Plugin config: ${report.plugin.action}${report.plugin.configPath ? ` (${report.plugin.configPath})` : ''}`);
|
|
296
|
+
if (report.plugin.backupPath) lines.push(`Plugin config backup: ${report.plugin.backupPath}`);
|
|
297
|
+
if (report.plugin.reason) lines.push(`Plugin config note: ${report.plugin.reason}`);
|
|
298
|
+
lines.push(`MCP config: ${report.mcp.action}${report.mcp.configPath ? ` (${report.mcp.configPath})` : ''}`);
|
|
299
|
+
if (report.mcp.backupPath) lines.push(`MCP config backup: ${report.mcp.backupPath}`);
|
|
300
|
+
if (report.mcp.reason) lines.push(`MCP config note: ${report.mcp.reason}`);
|
|
301
|
+
for (const warning of report.warnings) lines.push(`Warning: ${warning}`);
|
|
302
|
+
if (report.dryRun) lines.push('Dry run: no files were changed.');
|
|
303
|
+
return lines.join('\n');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function cmdInstallOpenClaw(args = {}, opts = {}) {
|
|
307
|
+
const report = buildInstallReport(args, opts);
|
|
308
|
+
if (args.flags?.json) {
|
|
309
|
+
console.log(JSON.stringify(report, null, 2));
|
|
310
|
+
} else {
|
|
311
|
+
console.log(formatInstallReport(report));
|
|
312
|
+
}
|
|
313
|
+
return report;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = {
|
|
317
|
+
buildInstallReport,
|
|
318
|
+
cmdInstallOpenClaw,
|
|
319
|
+
ensureExtensionLink,
|
|
320
|
+
ensurePluginConfig,
|
|
321
|
+
ensureMcpConfig,
|
|
322
|
+
formatInstallReport,
|
|
323
|
+
inspectInstalledPackage,
|
|
324
|
+
normalizeMcpServer,
|
|
325
|
+
resolveOpenClawHome,
|
|
326
|
+
};
|
|
@@ -33,6 +33,43 @@ function coerceRawEntries(messages) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function normalizeTimestamp(value) {
|
|
37
|
+
if (value === null || value === undefined || value === '') return null;
|
|
38
|
+
|
|
39
|
+
if (value instanceof Date) {
|
|
40
|
+
const time = value.getTime();
|
|
41
|
+
return Number.isFinite(time) ? value.toISOString() : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof value === 'number') {
|
|
45
|
+
if (!Number.isFinite(value)) return null;
|
|
46
|
+
const millis = Math.abs(value) < 1e12 ? value * 1000 : value;
|
|
47
|
+
const date = new Date(millis);
|
|
48
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof value === 'string') {
|
|
52
|
+
const trimmed = value.trim();
|
|
53
|
+
if (!trimmed) return null;
|
|
54
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
55
|
+
return normalizeTimestamp(Number(trimmed));
|
|
56
|
+
}
|
|
57
|
+
const parsed = Date.parse(trimmed);
|
|
58
|
+
if (!Number.isFinite(parsed)) return null;
|
|
59
|
+
return new Date(parsed).toISOString();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function selectTimestamp(...values) {
|
|
66
|
+
for (const value of values) {
|
|
67
|
+
const ts = normalizeTimestamp(value);
|
|
68
|
+
if (ts) return ts;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
36
73
|
function normalizeEntries(rawEntries) {
|
|
37
74
|
const normalized = [];
|
|
38
75
|
let userCount = 0, assistantCount = 0;
|
|
@@ -55,7 +92,7 @@ function normalizeEntries(rawEntries) {
|
|
|
55
92
|
.join('\n');
|
|
56
93
|
}
|
|
57
94
|
|
|
58
|
-
const ts = entry.timestamp
|
|
95
|
+
const ts = selectTimestamp(entry.timestamp, msg.timestamp);
|
|
59
96
|
if (ts && !startedAt) startedAt = ts;
|
|
60
97
|
if (ts) lastMessageAt = ts;
|
|
61
98
|
|
|
@@ -117,6 +154,7 @@ module.exports = buildPlugin();
|
|
|
117
154
|
// contract; OpenClaw reads { id, name, register } only.
|
|
118
155
|
module.exports.normalizeEntries = normalizeEntries;
|
|
119
156
|
module.exports.coerceRawEntries = coerceRawEntries;
|
|
157
|
+
module.exports.normalizeTimestamp = normalizeTimestamp;
|
|
120
158
|
|
|
121
159
|
function register(api) {
|
|
122
160
|
const pluginConfig = api.pluginConfig || {};
|
|
@@ -55,6 +55,7 @@ const DEFAULTS = {
|
|
|
55
55
|
servingMode: 'legacy', // 'legacy' | 'curated'
|
|
56
56
|
activeScopeKey: null,
|
|
57
57
|
activeScopePath: null,
|
|
58
|
+
allowedScopeKeys: null,
|
|
58
59
|
},
|
|
59
60
|
codex: {
|
|
60
61
|
checkpoint: {
|
|
@@ -120,6 +121,7 @@ const ENV_MAP = [
|
|
|
120
121
|
['AQUIFER_MEMORY_SERVING_MODE', 'memory.servingMode'],
|
|
121
122
|
['AQUIFER_MEMORY_ACTIVE_SCOPE_KEY', 'memory.activeScopeKey'],
|
|
122
123
|
['AQUIFER_MEMORY_ACTIVE_SCOPE_PATH', 'memory.activeScopePath'],
|
|
124
|
+
['AQUIFER_MEMORY_ALLOWED_SCOPE_KEYS', 'memory.allowedScopeKeys'],
|
|
123
125
|
['AQUIFER_CODEX_CHECKPOINT_CHECK_INTERVAL_MS', 'codex.checkpoint.checkIntervalMs', Number],
|
|
124
126
|
['AQUIFER_CODEX_CHECKPOINT_CHECK_INTERVAL_MINUTES', 'codex.checkpoint.checkIntervalMinutes', Number],
|
|
125
127
|
['AQUIFER_CODEX_CHECKPOINT_EVERY_MESSAGES', 'codex.checkpoint.everyMessages', Number],
|