jasper-recall 0.3.1 → 0.3.3
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 +30 -0
- package/SKILL.md +1 -1
- package/cli/doctor.js +191 -35
- package/cli/jasper-recall.js +79 -1
- package/extensions/jasper-recall/SKILL.md +204 -0
- package/extensions/jasper-recall/index.ts +256 -0
- package/extensions/jasper-recall/openclaw.plugin.json +50 -0
- package/extensions/jasper-recall/package.json +8 -0
- package/extensions/openclaw-plugin/SKILL.md +204 -0
- package/extensions/openclaw-plugin/index.ts +252 -0
- package/extensions/openclaw-plugin/openclaw.plugin.json +50 -0
- package/extensions/openclaw-plugin/package.json +8 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -150,6 +150,36 @@ summarize-old --min-size 1000 # Only summarize files larger than 1000 chars
|
|
|
150
150
|
- Rule-based summarization (no LLM required)
|
|
151
151
|
- Preserves headings, bullets, dates, and key markers
|
|
152
152
|
|
|
153
|
+
### doctor (v0.3.2+)
|
|
154
|
+
|
|
155
|
+
System health check and auto-repair:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx jasper-recall doctor # Check system health (default)
|
|
159
|
+
npx jasper-recall doctor --fix # Auto-repair issues
|
|
160
|
+
npx jasper-recall doctor --dry-run # Verbose mode, show exact commands
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Default mode** (no flags):
|
|
164
|
+
- Shows ✅/⚠️/❌ status for all checks
|
|
165
|
+
- For issues found, suggests what `--fix` would do
|
|
166
|
+
- Example: `❌ ChromaDB not installed → run with --fix to install`
|
|
167
|
+
|
|
168
|
+
**Fix mode** (`--fix`):
|
|
169
|
+
- Automatically repairs fixable issues:
|
|
170
|
+
- Creates Python venv if missing: `python3 -m venv ~/.openclaw/rag-env`
|
|
171
|
+
- Installs ChromaDB: `pip install chromadb`
|
|
172
|
+
- Installs sentence-transformers: `pip install sentence-transformers`
|
|
173
|
+
- Creates required directories (chroma-db, memory)
|
|
174
|
+
- Runs initial index if no collections exist
|
|
175
|
+
- Shows what it fixed: `🔧 Installed ChromaDB via pip`
|
|
176
|
+
- Non-fixable issues show manual instructions: `❌ Node.js <18 — please upgrade manually`
|
|
177
|
+
|
|
178
|
+
**Dry-run mode** (`--dry-run`):
|
|
179
|
+
- Same as default, but more verbose
|
|
180
|
+
- Shows exact commands that `--fix` would run
|
|
181
|
+
- Example: `Would run: ~/.openclaw/rag-env/bin/pip install chromadb`
|
|
182
|
+
|
|
153
183
|
### sync-shared (v0.2.0+)
|
|
154
184
|
|
|
155
185
|
Extract `[public]` tagged entries to shared memory:
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jasper-recall
|
|
3
|
-
version: 0.3.
|
|
3
|
+
version: 0.3.1
|
|
4
4
|
description: Local RAG system for agent memory using ChromaDB and sentence-transformers. v0.3.0 adds multi-agent mesh (N agents sharing memory), OpenClaw plugin with autoRecall, and agent-specific collections. Commands: recall, index-digests, digest-sessions, privacy-check, sync-shared, serve, recall-mesh.
|
|
5
5
|
---
|
|
6
6
|
|
package/cli/doctor.js
CHANGED
|
@@ -102,10 +102,20 @@ function countMemoryFiles() {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function runDoctor() {
|
|
105
|
+
function runDoctor(options = {}) {
|
|
106
|
+
const { fix = false, dryRun = false } = options;
|
|
107
|
+
const verbose = dryRun;
|
|
108
|
+
|
|
106
109
|
console.log('🏥 Jasper Recall Doctor\n');
|
|
107
110
|
|
|
111
|
+
if (fix) {
|
|
112
|
+
console.log('🔧 Fix mode enabled - will attempt to repair issues\n');
|
|
113
|
+
} else if (dryRun) {
|
|
114
|
+
console.log('👁️ Dry-run mode - showing what --fix would do\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
108
117
|
const checks = [];
|
|
118
|
+
const fixes = [];
|
|
109
119
|
|
|
110
120
|
// Node.js version check
|
|
111
121
|
const nodeResult = exec('node --version');
|
|
@@ -115,7 +125,9 @@ function runDoctor() {
|
|
|
115
125
|
label: 'Node.js',
|
|
116
126
|
status: nodeOk ? '✅' : '❌',
|
|
117
127
|
value: nodeResult.success ? `v${nodeVersion}` : 'not found',
|
|
118
|
-
ok: nodeOk
|
|
128
|
+
ok: nodeOk,
|
|
129
|
+
fixable: false,
|
|
130
|
+
fixMessage: 'Please upgrade Node.js manually: https://nodejs.org/'
|
|
119
131
|
});
|
|
120
132
|
|
|
121
133
|
// Python version check
|
|
@@ -127,7 +139,32 @@ function runDoctor() {
|
|
|
127
139
|
label: 'Python',
|
|
128
140
|
status: pythonOk ? '✅' : '❌',
|
|
129
141
|
value: pythonVersion || 'not found',
|
|
130
|
-
ok: pythonOk
|
|
142
|
+
ok: pythonOk,
|
|
143
|
+
fixable: false,
|
|
144
|
+
fixMessage: 'Please install Python 3: https://www.python.org/downloads/'
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Virtual environment check
|
|
148
|
+
const venvExists = fs.existsSync(VENV_PATH);
|
|
149
|
+
checks.push({
|
|
150
|
+
label: 'Venv',
|
|
151
|
+
status: venvExists ? '✅' : '❌',
|
|
152
|
+
value: venvExists ? VENV_PATH : 'not found',
|
|
153
|
+
ok: venvExists,
|
|
154
|
+
fixable: !venvExists && pythonOk,
|
|
155
|
+
fixMessage: !venvExists ? `create virtual environment at ${VENV_PATH}` : null,
|
|
156
|
+
fixCommand: `python3 -m venv ${VENV_PATH}`,
|
|
157
|
+
fixAction: () => {
|
|
158
|
+
console.log(` 🔧 Creating virtual environment...`);
|
|
159
|
+
const result = exec(`python3 -m venv ${VENV_PATH}`, { silent: false });
|
|
160
|
+
if (result.success) {
|
|
161
|
+
console.log(` ✅ Virtual environment created at ${VENV_PATH}`);
|
|
162
|
+
return true;
|
|
163
|
+
} else {
|
|
164
|
+
console.log(` ❌ Failed to create virtual environment`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
131
168
|
});
|
|
132
169
|
|
|
133
170
|
// ChromaDB check
|
|
@@ -140,7 +177,21 @@ function runDoctor() {
|
|
|
140
177
|
label: 'ChromaDB',
|
|
141
178
|
status: chromaOk ? '✅' : '❌',
|
|
142
179
|
value: chromaVersion ? `installed (${chromaVersion})` : 'not installed',
|
|
143
|
-
ok: chromaOk
|
|
180
|
+
ok: chromaOk,
|
|
181
|
+
fixable: !chromaOk && venvExists,
|
|
182
|
+
fixMessage: !chromaOk ? 'install chromadb via pip' : null,
|
|
183
|
+
fixCommand: `${pipPath} install chromadb`,
|
|
184
|
+
fixAction: () => {
|
|
185
|
+
console.log(` 🔧 Installing ChromaDB...`);
|
|
186
|
+
const result = exec(`${pipPath} install chromadb`, { silent: false });
|
|
187
|
+
if (result.success) {
|
|
188
|
+
console.log(` ✅ ChromaDB installed successfully`);
|
|
189
|
+
return true;
|
|
190
|
+
} else {
|
|
191
|
+
console.log(` ❌ Failed to install ChromaDB`);
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
144
195
|
});
|
|
145
196
|
|
|
146
197
|
// Sentence-transformers check
|
|
@@ -152,16 +203,21 @@ function runDoctor() {
|
|
|
152
203
|
label: 'Transformers',
|
|
153
204
|
status: transformersOk ? '✅' : '❌',
|
|
154
205
|
value: transformersVersion ? 'sentence-transformers installed' : 'not installed',
|
|
155
|
-
ok: transformersOk
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
206
|
+
ok: transformersOk,
|
|
207
|
+
fixable: !transformersOk && venvExists,
|
|
208
|
+
fixMessage: !transformersOk ? 'install sentence-transformers via pip' : null,
|
|
209
|
+
fixCommand: `${pipPath} install sentence-transformers`,
|
|
210
|
+
fixAction: () => {
|
|
211
|
+
console.log(` 🔧 Installing sentence-transformers...`);
|
|
212
|
+
const result = exec(`${pipPath} install sentence-transformers`, { silent: false });
|
|
213
|
+
if (result.success) {
|
|
214
|
+
console.log(` ✅ sentence-transformers installed successfully`);
|
|
215
|
+
return true;
|
|
216
|
+
} else {
|
|
217
|
+
console.log(` ❌ Failed to install sentence-transformers`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
165
221
|
});
|
|
166
222
|
|
|
167
223
|
// ChromaDB directory check
|
|
@@ -171,7 +227,21 @@ function runDoctor() {
|
|
|
171
227
|
label: 'Database',
|
|
172
228
|
status: chromaExists ? '✅' : '❌',
|
|
173
229
|
value: chromaExists ? `${CHROMA_PATH} (${collections} collections)` : 'not found',
|
|
174
|
-
ok: chromaExists
|
|
230
|
+
ok: chromaExists,
|
|
231
|
+
fixable: !chromaExists,
|
|
232
|
+
fixMessage: !chromaExists ? `create database directory at ${CHROMA_PATH}` : null,
|
|
233
|
+
fixCommand: `mkdir -p ${CHROMA_PATH}`,
|
|
234
|
+
fixAction: () => {
|
|
235
|
+
console.log(` 🔧 Creating ChromaDB directory...`);
|
|
236
|
+
try {
|
|
237
|
+
fs.mkdirSync(CHROMA_PATH, { recursive: true });
|
|
238
|
+
console.log(` ✅ Created directory: ${CHROMA_PATH}`);
|
|
239
|
+
return true;
|
|
240
|
+
} catch (e) {
|
|
241
|
+
console.log(` ❌ Failed to create directory: ${e.message}`);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
175
245
|
});
|
|
176
246
|
|
|
177
247
|
// Memory files check
|
|
@@ -181,17 +251,47 @@ function runDoctor() {
|
|
|
181
251
|
label: 'Memory files',
|
|
182
252
|
status: memoryExists ? '✅' : '⚠️',
|
|
183
253
|
value: memoryExists ? `${memoryCount} files in memory/` : 'directory not found',
|
|
184
|
-
ok: memoryExists
|
|
254
|
+
ok: memoryExists,
|
|
255
|
+
fixable: !memoryExists,
|
|
256
|
+
fixMessage: !memoryExists ? `create memory directory at ${MEMORY_PATH}` : null,
|
|
257
|
+
fixCommand: `mkdir -p ${MEMORY_PATH}`,
|
|
258
|
+
fixAction: () => {
|
|
259
|
+
console.log(` 🔧 Creating memory directory...`);
|
|
260
|
+
try {
|
|
261
|
+
fs.mkdirSync(MEMORY_PATH, { recursive: true });
|
|
262
|
+
console.log(` ✅ Created directory: ${MEMORY_PATH}`);
|
|
263
|
+
return true;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
console.log(` ❌ Failed to create directory: ${e.message}`);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
185
269
|
});
|
|
186
270
|
|
|
187
|
-
// Last index time
|
|
271
|
+
// Last index time / collections check
|
|
188
272
|
const lastIndexMs = getLastIndexTime();
|
|
189
|
-
const
|
|
273
|
+
const needsIndex = collections === 0 && chromaExists;
|
|
274
|
+
const lastIndexOk = !needsIndex && (lastIndexMs !== null && lastIndexMs < 7 * 24 * 60 * 60 * 1000); // < 7 days
|
|
190
275
|
checks.push({
|
|
191
276
|
label: 'Last indexed',
|
|
192
277
|
status: lastIndexMs === null ? '⚠️' : (lastIndexOk ? '✅' : '⚠️'),
|
|
193
|
-
value: lastIndexMs === null ? 'never' : formatTime(lastIndexMs),
|
|
194
|
-
ok: lastIndexMs !== null
|
|
278
|
+
value: needsIndex ? 'no collections - needs initial index' : (lastIndexMs === null ? 'never' : formatTime(lastIndexMs)),
|
|
279
|
+
ok: lastIndexMs !== null && !needsIndex,
|
|
280
|
+
fixable: needsIndex,
|
|
281
|
+
fixMessage: needsIndex ? 'run initial indexing with index-digests' : null,
|
|
282
|
+
fixCommand: 'index-digests',
|
|
283
|
+
fixAction: () => {
|
|
284
|
+
console.log(` 🔧 Running initial index...`);
|
|
285
|
+
const indexScript = path.join(__dirname, 'index-digests.js');
|
|
286
|
+
const result = exec(`node ${indexScript}`, { silent: false });
|
|
287
|
+
if (result.success) {
|
|
288
|
+
console.log(` ✅ Initial indexing complete`);
|
|
289
|
+
return true;
|
|
290
|
+
} else {
|
|
291
|
+
console.log(` ⚠️ Indexing may have completed with warnings`);
|
|
292
|
+
return true; // Don't treat warnings as failure
|
|
293
|
+
}
|
|
294
|
+
}
|
|
195
295
|
});
|
|
196
296
|
|
|
197
297
|
// Print results
|
|
@@ -199,10 +299,66 @@ function runDoctor() {
|
|
|
199
299
|
for (const check of checks) {
|
|
200
300
|
const padding = ' '.repeat(maxLabelLength - check.label.length);
|
|
201
301
|
console.log(` ${check.label}:${padding} ${check.status} ${check.value}`);
|
|
302
|
+
|
|
303
|
+
// Show fix suggestions in default/dry-run mode
|
|
304
|
+
if (!check.ok && !fix) {
|
|
305
|
+
if (check.fixable && check.fixMessage) {
|
|
306
|
+
if (verbose && check.fixCommand) {
|
|
307
|
+
console.log(` ${dryRun ? '📋' : '→'} Would run: ${check.fixCommand}`);
|
|
308
|
+
} else {
|
|
309
|
+
console.log(` → run with --fix to ${check.fixMessage}`);
|
|
310
|
+
}
|
|
311
|
+
} else if (!check.fixable && check.fixMessage) {
|
|
312
|
+
console.log(` ❌ ${check.fixMessage}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
202
315
|
}
|
|
203
316
|
|
|
204
317
|
console.log('');
|
|
205
318
|
|
|
319
|
+
// Apply fixes if requested
|
|
320
|
+
if (fix) {
|
|
321
|
+
const fixableIssues = checks.filter(c => !c.ok && c.fixable && c.fixAction);
|
|
322
|
+
|
|
323
|
+
if (fixableIssues.length === 0) {
|
|
324
|
+
const unfixableIssues = checks.filter(c => !c.ok && !c.fixable);
|
|
325
|
+
if (unfixableIssues.length > 0) {
|
|
326
|
+
console.log('⚠️ Some issues require manual intervention:\n');
|
|
327
|
+
for (const issue of unfixableIssues) {
|
|
328
|
+
console.log(` ❌ ${issue.label}: ${issue.fixMessage}`);
|
|
329
|
+
}
|
|
330
|
+
console.log('');
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
console.log('🔧 Applying fixes...\n');
|
|
334
|
+
|
|
335
|
+
for (const issue of fixableIssues) {
|
|
336
|
+
const success = issue.fixAction();
|
|
337
|
+
fixes.push({ issue: issue.label, success });
|
|
338
|
+
console.log('');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const successCount = fixes.filter(f => f.success).length;
|
|
342
|
+
const failCount = fixes.filter(f => !f.success).length;
|
|
343
|
+
|
|
344
|
+
if (failCount === 0) {
|
|
345
|
+
console.log(`✅ All ${successCount} issue${successCount > 1 ? 's' : ''} fixed!\n`);
|
|
346
|
+
} else {
|
|
347
|
+
console.log(`⚠️ Fixed ${successCount}/${fixes.length} issues (${failCount} failed)\n`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Check for remaining unfixable issues
|
|
351
|
+
const unfixableIssues = checks.filter(c => !c.ok && !c.fixable);
|
|
352
|
+
if (unfixableIssues.length > 0) {
|
|
353
|
+
console.log('⚠️ Remaining issues require manual intervention:\n');
|
|
354
|
+
for (const issue of unfixableIssues) {
|
|
355
|
+
console.log(` ❌ ${issue.label}: ${issue.fixMessage}`);
|
|
356
|
+
}
|
|
357
|
+
console.log('');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
206
362
|
// Summary
|
|
207
363
|
const allOk = checks.every(c => c.ok);
|
|
208
364
|
if (allOk) {
|
|
@@ -210,23 +366,17 @@ function runDoctor() {
|
|
|
210
366
|
return 0;
|
|
211
367
|
} else {
|
|
212
368
|
const failed = checks.filter(c => !c.ok);
|
|
213
|
-
console.log(`⚠️ ${failed.length} issue${failed.length > 1 ? 's' : ''} detected.\n`);
|
|
214
369
|
|
|
215
|
-
if (!
|
|
216
|
-
console.log(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
console.log(`→ Create memory directory: mkdir -p ${MEMORY_PATH}`);
|
|
223
|
-
}
|
|
224
|
-
if (lastIndexMs === null || !lastIndexOk) {
|
|
225
|
-
console.log('→ Index your memory: index-digests');
|
|
370
|
+
if (!fix) {
|
|
371
|
+
console.log(`⚠️ ${failed.length} issue${failed.length > 1 ? 's' : ''} detected.\n`);
|
|
372
|
+
|
|
373
|
+
const hasFixableIssues = failed.some(c => c.fixable);
|
|
374
|
+
if (hasFixableIssues) {
|
|
375
|
+
console.log('→ Run with --fix to automatically repair issues\n');
|
|
376
|
+
}
|
|
226
377
|
}
|
|
227
378
|
|
|
228
|
-
|
|
229
|
-
return 1;
|
|
379
|
+
return fixes.length > 0 && fixes.every(f => f.success) ? 0 : 1;
|
|
230
380
|
}
|
|
231
381
|
}
|
|
232
382
|
|
|
@@ -234,5 +384,11 @@ module.exports = { runDoctor };
|
|
|
234
384
|
|
|
235
385
|
// Allow direct execution
|
|
236
386
|
if (require.main === module) {
|
|
237
|
-
process.
|
|
387
|
+
const args = process.argv.slice(2);
|
|
388
|
+
const options = {
|
|
389
|
+
fix: args.includes('--fix'),
|
|
390
|
+
dryRun: args.includes('--dry-run')
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
process.exit(runDoctor(options));
|
|
238
394
|
}
|
package/cli/jasper-recall.js
CHANGED
|
@@ -26,6 +26,9 @@ const VENV_PATH = path.join(os.homedir(), '.openclaw', 'rag-env');
|
|
|
26
26
|
const CHROMA_PATH = path.join(os.homedir(), '.openclaw', 'chroma-db');
|
|
27
27
|
const BIN_PATH = path.join(os.homedir(), '.local', 'bin');
|
|
28
28
|
const SCRIPTS_DIR = path.join(__dirname, '..', 'scripts');
|
|
29
|
+
const EXTENSIONS_DIR = path.join(__dirname, '..', 'extensions');
|
|
30
|
+
const OPENCLAW_CONFIG = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
31
|
+
const OPENCLAW_SKILLS = path.join(os.homedir(), '.openclaw', 'workspace', 'skills');
|
|
29
32
|
|
|
30
33
|
function log(msg) {
|
|
31
34
|
console.log(`🦊 ${msg}`);
|
|
@@ -47,6 +50,70 @@ function run(cmd, opts = {}) {
|
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
function setupOpenClawIntegration() {
|
|
54
|
+
log('Setting up OpenClaw integration...');
|
|
55
|
+
|
|
56
|
+
// Check if OpenClaw is installed
|
|
57
|
+
const openclawDir = path.join(os.homedir(), '.openclaw');
|
|
58
|
+
if (!fs.existsSync(openclawDir)) {
|
|
59
|
+
console.log(' ⚠ OpenClaw not detected (~/.openclaw not found)');
|
|
60
|
+
console.log(' → Skipping OpenClaw integration');
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Install SKILL.md to skills directory
|
|
65
|
+
const skillSrc = path.join(EXTENSIONS_DIR, 'openclaw-plugin', 'SKILL.md');
|
|
66
|
+
const skillDest = path.join(OPENCLAW_SKILLS, 'jasper-recall', 'SKILL.md');
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(skillSrc)) {
|
|
69
|
+
fs.mkdirSync(path.dirname(skillDest), { recursive: true });
|
|
70
|
+
fs.copyFileSync(skillSrc, skillDest);
|
|
71
|
+
console.log(` ✓ Installed SKILL.md: ${skillDest}`);
|
|
72
|
+
} else {
|
|
73
|
+
console.log(' ⚠ SKILL.md not found in package (try reinstalling)');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Update openclaw.json with plugin config
|
|
77
|
+
if (fs.existsSync(OPENCLAW_CONFIG)) {
|
|
78
|
+
try {
|
|
79
|
+
const configRaw = fs.readFileSync(OPENCLAW_CONFIG, 'utf8');
|
|
80
|
+
const config = JSON.parse(configRaw);
|
|
81
|
+
|
|
82
|
+
// Initialize plugins structure if needed
|
|
83
|
+
if (!config.plugins) config.plugins = {};
|
|
84
|
+
if (!config.plugins.entries) config.plugins.entries = {};
|
|
85
|
+
|
|
86
|
+
// Check if already configured
|
|
87
|
+
if (config.plugins.entries['jasper-recall']) {
|
|
88
|
+
console.log(' ✓ Plugin already configured in openclaw.json');
|
|
89
|
+
} else {
|
|
90
|
+
// Add plugin config
|
|
91
|
+
config.plugins.entries['jasper-recall'] = {
|
|
92
|
+
enabled: true,
|
|
93
|
+
config: {
|
|
94
|
+
autoRecall: true,
|
|
95
|
+
minScore: 0.3,
|
|
96
|
+
defaultLimit: 5
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Write back with nice formatting
|
|
101
|
+
fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(config, null, 2) + '\n');
|
|
102
|
+
console.log(' ✓ Added jasper-recall plugin to openclaw.json');
|
|
103
|
+
console.log(' → Restart OpenClaw gateway to activate: openclaw gateway restart');
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.log(` ⚠ Could not update openclaw.json: ${e.message}`);
|
|
107
|
+
console.log(' → Manually add plugin config (see docs)');
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
console.log(' ⚠ openclaw.json not found');
|
|
111
|
+
console.log(' → Create config or manually add jasper-recall plugin');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
50
117
|
function setup() {
|
|
51
118
|
log('Jasper Recall — Setup');
|
|
52
119
|
console.log('=' .repeat(40));
|
|
@@ -118,6 +185,11 @@ function setup() {
|
|
|
118
185
|
console.log(` export PATH="$HOME/.local/bin:$PATH"`);
|
|
119
186
|
}
|
|
120
187
|
|
|
188
|
+
console.log('');
|
|
189
|
+
|
|
190
|
+
// OpenClaw integration
|
|
191
|
+
setupOpenClawIntegration();
|
|
192
|
+
|
|
121
193
|
console.log('');
|
|
122
194
|
console.log('=' .repeat(40));
|
|
123
195
|
log('Setup complete!');
|
|
@@ -139,6 +211,7 @@ USAGE:
|
|
|
139
211
|
COMMANDS:
|
|
140
212
|
setup Install dependencies and CLI scripts
|
|
141
213
|
doctor Run system health check
|
|
214
|
+
Flags: --fix (auto-repair issues), --dry-run (verbose output)
|
|
142
215
|
recall Search your memory (alias for the recall command)
|
|
143
216
|
index Index memory files (alias for index-digests)
|
|
144
217
|
digest Process session logs (alias for digest-sessions)
|
|
@@ -231,7 +304,12 @@ switch (command) {
|
|
|
231
304
|
case 'doctor':
|
|
232
305
|
// Run system health check
|
|
233
306
|
const { runDoctor } = require('./doctor');
|
|
234
|
-
process.
|
|
307
|
+
const args = process.argv.slice(3);
|
|
308
|
+
const options = {
|
|
309
|
+
fix: args.includes('--fix'),
|
|
310
|
+
dryRun: args.includes('--dry-run')
|
|
311
|
+
};
|
|
312
|
+
process.exit(runDoctor(options));
|
|
235
313
|
break;
|
|
236
314
|
case 'config':
|
|
237
315
|
// Configuration management
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Jasper Recall - OpenClaw Plugin
|
|
2
|
+
|
|
3
|
+
Semantic search over indexed memory using ChromaDB. Automatically injects relevant context before agent processing.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **`recall` tool** — Manual semantic search over memory
|
|
8
|
+
- **`/recall` command** — Quick lookups from chat
|
|
9
|
+
- **`/index` command** — Re-index memory files
|
|
10
|
+
- **Auto-recall** — Automatically inject relevant memories before processing
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Auto-Recall (The Magic ✨)
|
|
15
|
+
|
|
16
|
+
When `autoRecall` is enabled, jasper-recall hooks into the agent lifecycle and automatically searches your memory before every message is processed.
|
|
17
|
+
|
|
18
|
+
### How It Works
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ 1. Message arrives from user │
|
|
23
|
+
│ 2. before_agent_start hook fires │
|
|
24
|
+
│ 3. jasper-recall searches ChromaDB with message as query │
|
|
25
|
+
│ 4. Results filtered by minScore (default: 30%) │
|
|
26
|
+
│ 5. Relevant memories injected via prependContext │
|
|
27
|
+
│ 6. Agent sees memories + original message │
|
|
28
|
+
│ 7. Agent responds with full context │
|
|
29
|
+
└─────────────────────────────────────────────────────────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### What Gets Injected
|
|
33
|
+
|
|
34
|
+
```xml
|
|
35
|
+
<relevant-memories>
|
|
36
|
+
The following memories may be relevant to this conversation:
|
|
37
|
+
- [memory/2026-02-05.md] Worker orchestration decisions...
|
|
38
|
+
- [MEMORY.md] Git workflow: feature → develop → main...
|
|
39
|
+
- [memory/sops/codex-integration-sop.md] Codex Cloud sync...
|
|
40
|
+
</relevant-memories>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### What's Skipped
|
|
44
|
+
|
|
45
|
+
Auto-recall won't run for:
|
|
46
|
+
- Heartbeat polls (`HEARTBEAT...`)
|
|
47
|
+
- System prompts containing `NO_REPLY`
|
|
48
|
+
- Messages shorter than 10 characters
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
In `openclaw.json`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"plugins": {
|
|
59
|
+
"entries": {
|
|
60
|
+
"jasper-recall": {
|
|
61
|
+
"enabled": true,
|
|
62
|
+
"config": {
|
|
63
|
+
"autoRecall": true,
|
|
64
|
+
"minScore": 0.3,
|
|
65
|
+
"defaultLimit": 5,
|
|
66
|
+
"publicOnly": false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Options
|
|
75
|
+
|
|
76
|
+
| Option | Type | Default | Description |
|
|
77
|
+
|--------|------|---------|-------------|
|
|
78
|
+
| `enabled` | boolean | `true` | Enable/disable plugin |
|
|
79
|
+
| `autoRecall` | boolean | `false` | Auto-inject memories before processing |
|
|
80
|
+
| `minScore` | number | `0.3` | Minimum similarity score (0-1) for auto-recall |
|
|
81
|
+
| `defaultLimit` | number | `5` | Default number of results |
|
|
82
|
+
| `publicOnly` | boolean | `false` | Only search public memory (sandboxed agents) |
|
|
83
|
+
|
|
84
|
+
### Score Tuning
|
|
85
|
+
|
|
86
|
+
- `minScore: 0.3` — Include loosely related memories (more context, may include noise)
|
|
87
|
+
- `minScore: 0.5` — Only moderately relevant (balanced)
|
|
88
|
+
- `minScore: 0.7` — Only highly relevant (precise, may miss useful context)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Tools
|
|
93
|
+
|
|
94
|
+
### `recall`
|
|
95
|
+
|
|
96
|
+
Manual semantic search over memory.
|
|
97
|
+
|
|
98
|
+
**Parameters:**
|
|
99
|
+
- `query` (string, required): Natural language search query
|
|
100
|
+
- `limit` (number, optional): Max results (default: 5)
|
|
101
|
+
|
|
102
|
+
**Example:**
|
|
103
|
+
```
|
|
104
|
+
recall query="what did we decide about the API design" limit=3
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Returns:** Formatted markdown with matching memories, scores, and sources.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
### `/recall <query>`
|
|
114
|
+
|
|
115
|
+
Quick memory search from chat.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
/recall worker orchestration decisions
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `/index`
|
|
122
|
+
|
|
123
|
+
Re-index memory files into ChromaDB. Run after updating notes.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
/index
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## RPC Methods
|
|
132
|
+
|
|
133
|
+
For external integrations:
|
|
134
|
+
|
|
135
|
+
### `recall.search`
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{ "query": "search terms", "limit": 5 }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `recall.index`
|
|
142
|
+
|
|
143
|
+
Re-index memory files (no params).
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Requirements
|
|
148
|
+
|
|
149
|
+
- `recall` command in `~/.local/bin/`
|
|
150
|
+
- ChromaDB index at `~/.openclaw/chroma-db`
|
|
151
|
+
- Python venv at `~/.openclaw/rag-env`
|
|
152
|
+
|
|
153
|
+
## Installation
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx jasper-recall setup
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This sets up:
|
|
160
|
+
1. Python venv with ChromaDB + sentence-transformers
|
|
161
|
+
2. `recall`, `index-digests`, `digest-sessions` scripts
|
|
162
|
+
3. Initial index of memory files
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## When Auto-Recall Helps
|
|
167
|
+
|
|
168
|
+
✅ **Great for:**
|
|
169
|
+
- Questions about past decisions ("what did we decide about X?")
|
|
170
|
+
- Following up on previous work ("where were we with the worker setup?")
|
|
171
|
+
- Context about people, preferences, projects
|
|
172
|
+
- Finding SOPs and procedures
|
|
173
|
+
|
|
174
|
+
⚠️ **Less useful for:**
|
|
175
|
+
- Brand new topics with no memory
|
|
176
|
+
- Simple commands ("list files")
|
|
177
|
+
- Real-time data (weather, time)
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Sandboxed Agents
|
|
182
|
+
|
|
183
|
+
For agents processing untrusted input, use `publicOnly`:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"jasper-recall": {
|
|
188
|
+
"config": {
|
|
189
|
+
"publicOnly": true,
|
|
190
|
+
"autoRecall": true
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
This restricts searches to `memory/shared/` and public-tagged content, preventing leakage of private memories.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Links
|
|
201
|
+
|
|
202
|
+
- **GitHub**: https://github.com/E-x-O-Entertainment-Studios-Inc/jasper-recall
|
|
203
|
+
- **npm**: `npx jasper-recall setup`
|
|
204
|
+
- **ClawHub**: `clawhub install jasper-recall`
|