jasper-recall 0.5.2 → 0.5.4

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.
@@ -10,7 +10,7 @@
10
10
  * - Auto-recall: inject relevant memories before agent processing
11
11
  */
12
12
 
13
- import { execFileSync, execSync } from 'child_process';
13
+ import { execSync } from 'child_process';
14
14
  import * as path from 'path';
15
15
  import * as os from 'os';
16
16
 
@@ -55,16 +55,12 @@ function runRecall(query: string, options: { limit?: number; json?: boolean; pub
55
55
 
56
56
  const recallPath = path.join(BIN_PATH, 'recall');
57
57
  try {
58
- return execFileSync(recallPath, args, { encoding: 'utf8', timeout: 30000 });
58
+ return execSync(`${recallPath} ${args.join(' ')}`, { encoding: 'utf8', timeout: 30000 });
59
59
  } catch (err: any) {
60
60
  throw new Error(`Recall failed: ${err.message}`);
61
61
  }
62
62
  }
63
63
 
64
- function getSimilarity(result: any): number {
65
- return typeof result?.similarity === 'number' ? result.similarity : result?.score ?? 0;
66
- }
67
-
68
64
  export default function register(api: PluginApi) {
69
65
  const cfg = api.config.plugins?.entries?.['jasper-recall']?.config ?? {};
70
66
 
@@ -85,18 +81,70 @@ export default function register(api: PluginApi) {
85
81
  // ============================================================================
86
82
 
87
83
  if (autoRecall) {
88
- api.on('before_agent_start', async (event: { prompt?: string }) => {
84
+ api.on('before_agent_start', async (event: {
85
+ prompt?: string;
86
+ senderId?: string;
87
+ source?: string;
88
+ isNewSession?: boolean;
89
+ messageCount?: number;
90
+ context?: { messages?: any[] };
91
+ }) => {
89
92
  // Skip if no prompt or too short
90
93
  if (!event.prompt || event.prompt.length < 10) {
91
94
  return;
92
95
  }
93
96
 
94
- // Skip system/internal prompts
95
- if (event.prompt.startsWith('HEARTBEAT') || event.prompt.includes('NO_REPLY')) {
97
+ const prompt = event.prompt;
98
+
99
+ // Detect fresh session (after /new or first message)
100
+ const isFreshSession = event.isNewSession ||
101
+ event.messageCount === 0 ||
102
+ event.messageCount === 1 ||
103
+ (event.context?.messages?.length ?? 0) <= 1;
104
+
105
+ // Skip heartbeats and system prompts
106
+ if (prompt.startsWith('HEARTBEAT') ||
107
+ prompt.startsWith('Read HEARTBEAT.md') ||
108
+ prompt.includes('NO_REPLY') ||
109
+ prompt.includes('HEARTBEAT_OK')) {
110
+ return;
111
+ }
112
+
113
+ // Skip agent-to-agent messages (cron jobs, workers, spawned agents)
114
+ if (event.source?.startsWith('cron:') ||
115
+ event.source?.startsWith('agent:') ||
116
+ event.source?.startsWith('spawn:') ||
117
+ event.source === 'sessions_send' ||
118
+ event.senderId?.startsWith('agent:') ||
119
+ event.senderId?.startsWith('worker-')) {
120
+ return;
121
+ }
122
+
123
+ // Skip common automated patterns
124
+ if (prompt.startsWith('Agent-to-agent') ||
125
+ prompt.startsWith('📋 PR Review') ||
126
+ prompt.startsWith('🤖 Codex Watch') ||
127
+ prompt.startsWith('ANNOUNCE_')) {
96
128
  return;
97
129
  }
98
130
 
99
131
  try {
132
+ let prependParts: string[] = [];
133
+
134
+ // If fresh session, remind agent to read identity files
135
+ if (isFreshSession) {
136
+ api.logger.info('[jasper-recall] Fresh session detected - adding identity reminder');
137
+ prependParts.push(`<session-init>
138
+ 🔄 **Fresh session detected.** Before responding, read your identity files:
139
+ \`\`\`bash
140
+ cat ~/.openclaw/workspace/IDENTITY.md
141
+ cat ~/.openclaw/workspace/SOUL.md
142
+ cat ~/.openclaw/workspace/USER.md
143
+ \`\`\`
144
+ This ensures you maintain your persona and context after session reset.
145
+ </session-init>`);
146
+ }
147
+
100
148
  const results = runRecall(event.prompt, {
101
149
  limit: 3,
102
150
  json: true,
@@ -106,25 +154,45 @@ export default function register(api: PluginApi) {
106
154
  const parsed = JSON.parse(results);
107
155
 
108
156
  // Filter by minimum score
109
- const relevant = parsed.filter((r: any) => getSimilarity(r) >= minScore);
157
+ const relevant = parsed.filter((r: any) => r.score >= minScore);
110
158
 
111
- if (relevant.length === 0) {
112
- api.logger.debug?.('[jasper-recall] No relevant memories found for auto-recall');
113
- return;
114
- }
159
+ if (relevant.length > 0) {
160
+ // Format memories for context injection
161
+ const memoryContext = relevant
162
+ .map((r: any) => `- [${r.source || 'memory'}] ${r.content.slice(0, 500)}${r.content.length > 500 ? '...' : ''}`)
163
+ .join('\n');
115
164
 
116
- // Format memories for context injection
117
- const memoryContext = relevant
118
- .map((r: any) => `- [${r.source || 'memory'}] ${r.content.slice(0, 500)}${r.content.length > 500 ? '...' : ''}`)
119
- .join('\n');
165
+ api.logger.info(`[jasper-recall] Auto-injecting ${relevant.length} memories into context`);
120
166
 
121
- api.logger.info(`[jasper-recall] Auto-injecting ${relevant.length} memories into context`);
167
+ prependParts.push(`<relevant-memories>
168
+ The following memories may be relevant to this conversation:
169
+ ${memoryContext}
170
+ </relevant-memories>`);
171
+ } else {
172
+ api.logger.debug?.('[jasper-recall] No relevant memories found for auto-recall');
173
+ }
122
174
 
123
- return {
124
- prependContext: `<relevant-memories>\nThe following memories may be relevant to this conversation:\n${memoryContext}\n</relevant-memories>`,
125
- };
175
+ if (prependParts.length > 0) {
176
+ return {
177
+ prependContext: prependParts.join('\n\n'),
178
+ };
179
+ }
126
180
  } catch (err: any) {
127
181
  api.logger.warn(`[jasper-recall] Auto-recall failed: ${err.message}`);
182
+
183
+ // Still inject identity reminder on fresh session even if recall fails
184
+ if (isFreshSession) {
185
+ return {
186
+ prependContext: `<session-init>
187
+ 🔄 **Fresh session detected.** Before responding, read your identity files:
188
+ \`\`\`bash
189
+ cat ~/.openclaw/workspace/IDENTITY.md
190
+ cat ~/.openclaw/workspace/SOUL.md
191
+ cat ~/.openclaw/workspace/USER.md
192
+ \`\`\`
193
+ </session-init>`,
194
+ };
195
+ }
128
196
  }
129
197
  });
130
198
  }
@@ -168,7 +236,7 @@ export default function register(api: PluginApi) {
168
236
  } else {
169
237
  for (const result of parsed) {
170
238
  formatted += `### ${result.source || 'Memory'}\n`;
171
- formatted += `**Similarity:** ${(getSimilarity(result) * 100).toFixed(1)}%\n\n`;
239
+ formatted += `**Score:** ${(result.score * 100).toFixed(1)}%\n\n`;
172
240
  formatted += `${result.content}\n\n---\n\n`;
173
241
  }
174
242
  }
@@ -227,6 +295,99 @@ export default function register(api: PluginApi) {
227
295
  },
228
296
  });
229
297
 
298
+ // ============================================================================
299
+ // Command: /jasper-recall setup
300
+ // ============================================================================
301
+
302
+ api.registerCommand({
303
+ name: 'jasper-recall',
304
+ description: 'Jasper Recall setup and status',
305
+ acceptsArgs: true,
306
+ requireAuth: true,
307
+ handler: async (ctx: { args?: string }) => {
308
+ const subcommand = ctx.args?.trim().toLowerCase();
309
+
310
+ if (subcommand === 'setup') {
311
+ return {
312
+ text: `🧠 **Jasper Recall Setup**
313
+
314
+ **Current status:**
315
+ - Auto-recall: ${autoRecall ? '✅ Enabled' : '❌ Disabled'}
316
+ - Public-only mode: ${publicOnly ? '✅ Yes' : '❌ No (full access)'}
317
+ - Default limit: ${defaultLimit} results
318
+ - Min score: ${minScore}
319
+
320
+ ---
321
+
322
+ <agent-action required="true">
323
+ **CREATE THESE CRON JOBS NOW:**
324
+
325
+ 1. **Index memories every 6 hours:**
326
+ - Name: "jasper-recall-index"
327
+ - Schedule: \`{ "kind": "cron", "expr": "0 */6 * * *" }\`
328
+ - Payload: \`{ "kind": "systemEvent", "text": "Run index-digests to update jasper-recall ChromaDB index" }\`
329
+ - Session target: "main"
330
+
331
+ 2. **Digest sessions daily at 4am:**
332
+ - Name: "jasper-recall-digest"
333
+ - Schedule: \`{ "kind": "cron", "expr": "0 4 * * *" }\`
334
+ - Payload: \`{ "kind": "systemEvent", "text": "Run digest-sessions to extract summaries from recent session logs into memory" }\`
335
+ - Session target: "main"
336
+
337
+ Use the \`cron\` tool with \`action: "add"\` to create both jobs.
338
+ </agent-action>`,
339
+ };
340
+ }
341
+
342
+ if (subcommand === 'status') {
343
+ try {
344
+ // Check if ChromaDB is accessible
345
+ const testResult = runRecall('test query', { limit: 1, json: true, publicOnly });
346
+ const parsed = JSON.parse(testResult);
347
+
348
+ return {
349
+ text: `🧠 **Jasper Recall Status**
350
+
351
+ **Plugin:** ✅ Active
352
+ **Auto-recall:** ${autoRecall ? '✅ Enabled' : '❌ Disabled'}
353
+ **ChromaDB:** ✅ Connected
354
+ **Index size:** ${parsed.length > 0 ? 'Has data' : 'Empty or minimal'}
355
+
356
+ **Config:**
357
+ - Public-only: ${publicOnly}
358
+ - Default limit: ${defaultLimit}
359
+ - Min score: ${minScore}`,
360
+ };
361
+ } catch (err: any) {
362
+ return {
363
+ text: `🧠 **Jasper Recall Status**
364
+
365
+ **Plugin:** ✅ Active
366
+ **Auto-recall:** ${autoRecall ? '✅ Enabled' : '❌ Disabled'}
367
+ **ChromaDB:** ❌ Error - ${err.message}
368
+
369
+ Run \`npx jasper-recall setup\` to install dependencies.`,
370
+ };
371
+ }
372
+ }
373
+
374
+ // Default: show help
375
+ return {
376
+ text: `🧠 **Jasper Recall**
377
+
378
+ **Commands:**
379
+ - \`/jasper-recall setup\` — Setup instructions & cron jobs
380
+ - \`/jasper-recall status\` — Check plugin status
381
+ - \`/recall <query>\` — Search memory
382
+ - \`/index\` — Re-index memory files
383
+
384
+ **CLI:**
385
+ - \`npx jasper-recall setup\` — Install Python dependencies
386
+ - \`npx jasper-recall doctor\` — Health check`,
387
+ };
388
+ },
389
+ });
390
+
230
391
  // ============================================================================
231
392
  // RPC Methods
232
393
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jasper-recall",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Local RAG system for AI agent memory using ChromaDB and sentence-transformers",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "openclaw": {
10
10
  "extensions": [
11
- "./extensions/openclaw-plugin/index.ts"
11
+ "./extensions/jasper-recall/index.ts"
12
12
  ]
13
13
  },
14
14
  "scripts": {
@@ -42,6 +42,7 @@
42
42
  "scripts/",
43
43
  "src/",
44
44
  "extensions/",
45
+ "openclaw.plugin.json",
45
46
  "SKILL.md",
46
47
  "README.md"
47
48
  ]
@@ -1,204 +0,0 @@
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`
@@ -1,417 +0,0 @@
1
- /**
2
- * Jasper Recall OpenClaw Plugin
3
- *
4
- * Semantic search over indexed memory using ChromaDB.
5
- * "Remember everything. Recall what matters."
6
- *
7
- * Features:
8
- * - `recall` tool for manual searches
9
- * - `/recall` command for quick lookups
10
- * - Auto-recall: inject relevant memories before agent processing
11
- */
12
-
13
- import { execSync } from 'child_process';
14
- import * as path from 'path';
15
- import * as os from 'os';
16
-
17
- interface PluginConfig {
18
- enabled?: boolean;
19
- autoRecall?: boolean;
20
- defaultLimit?: number;
21
- publicOnly?: boolean;
22
- minScore?: number;
23
- logLevel?: 'debug' | 'info' | 'warn' | 'error';
24
- }
25
-
26
- interface PluginApi {
27
- config: {
28
- plugins?: {
29
- entries?: {
30
- 'jasper-recall'?: {
31
- config?: PluginConfig;
32
- };
33
- };
34
- };
35
- };
36
- logger: {
37
- info: (msg: string) => void;
38
- warn: (msg: string) => void;
39
- error: (msg: string) => void;
40
- debug: (msg: string) => void;
41
- };
42
- registerTool: (tool: any) => void;
43
- registerCommand: (cmd: any) => void;
44
- registerGatewayMethod: (name: string, handler: any) => void;
45
- on: (event: string, handler: (event: any) => Promise<any>) => void;
46
- }
47
-
48
- const BIN_PATH = path.join(os.homedir(), '.local', 'bin');
49
-
50
- function runRecall(query: string, options: { limit?: number; json?: boolean; publicOnly?: boolean } = {}): string {
51
- const args = [JSON.stringify(query)];
52
- if (options.limit) args.push('-n', String(options.limit));
53
- if (options.json) args.push('--json');
54
- if (options.publicOnly) args.push('--public-only');
55
-
56
- const recallPath = path.join(BIN_PATH, 'recall');
57
- try {
58
- return execSync(`${recallPath} ${args.join(' ')}`, { encoding: 'utf8', timeout: 30000 });
59
- } catch (err: any) {
60
- throw new Error(`Recall failed: ${err.message}`);
61
- }
62
- }
63
-
64
- export default function register(api: PluginApi) {
65
- const cfg = api.config.plugins?.entries?.['jasper-recall']?.config ?? {};
66
-
67
- if (cfg.enabled === false) {
68
- api.logger.info('[jasper-recall] Plugin disabled');
69
- return;
70
- }
71
-
72
- const defaultLimit = cfg.defaultLimit ?? 5;
73
- const publicOnly = cfg.publicOnly ?? false;
74
- const autoRecall = cfg.autoRecall ?? false;
75
- const minScore = cfg.minScore ?? 0.3;
76
-
77
- api.logger.info(`[jasper-recall] Initialized (limit=${defaultLimit}, publicOnly=${publicOnly}, autoRecall=${autoRecall})`);
78
-
79
- // ============================================================================
80
- // Auto-Recall: inject relevant memories before agent processes the message
81
- // ============================================================================
82
-
83
- if (autoRecall) {
84
- api.on('before_agent_start', async (event: {
85
- prompt?: string;
86
- senderId?: string;
87
- source?: string;
88
- isNewSession?: boolean;
89
- messageCount?: number;
90
- context?: { messages?: any[] };
91
- }) => {
92
- // Skip if no prompt or too short
93
- if (!event.prompt || event.prompt.length < 10) {
94
- return;
95
- }
96
-
97
- const prompt = event.prompt;
98
-
99
- // Detect fresh session (after /new or first message)
100
- const isFreshSession = event.isNewSession ||
101
- event.messageCount === 0 ||
102
- event.messageCount === 1 ||
103
- (event.context?.messages?.length ?? 0) <= 1;
104
-
105
- // Skip heartbeats and system prompts
106
- if (prompt.startsWith('HEARTBEAT') ||
107
- prompt.startsWith('Read HEARTBEAT.md') ||
108
- prompt.includes('NO_REPLY') ||
109
- prompt.includes('HEARTBEAT_OK')) {
110
- return;
111
- }
112
-
113
- // Skip agent-to-agent messages (cron jobs, workers, spawned agents)
114
- if (event.source?.startsWith('cron:') ||
115
- event.source?.startsWith('agent:') ||
116
- event.source?.startsWith('spawn:') ||
117
- event.source === 'sessions_send' ||
118
- event.senderId?.startsWith('agent:') ||
119
- event.senderId?.startsWith('worker-')) {
120
- return;
121
- }
122
-
123
- // Skip common automated patterns
124
- if (prompt.startsWith('Agent-to-agent') ||
125
- prompt.startsWith('📋 PR Review') ||
126
- prompt.startsWith('🤖 Codex Watch') ||
127
- prompt.startsWith('ANNOUNCE_')) {
128
- return;
129
- }
130
-
131
- try {
132
- let prependParts: string[] = [];
133
-
134
- // If fresh session, remind agent to read identity files
135
- if (isFreshSession) {
136
- api.logger.info('[jasper-recall] Fresh session detected - adding identity reminder');
137
- prependParts.push(`<session-init>
138
- 🔄 **Fresh session detected.** Before responding, read your identity files:
139
- \`\`\`bash
140
- cat ~/.openclaw/workspace/IDENTITY.md
141
- cat ~/.openclaw/workspace/SOUL.md
142
- cat ~/.openclaw/workspace/USER.md
143
- \`\`\`
144
- This ensures you maintain your persona and context after session reset.
145
- </session-init>`);
146
- }
147
-
148
- const results = runRecall(event.prompt, {
149
- limit: 3,
150
- json: true,
151
- publicOnly,
152
- });
153
-
154
- const parsed = JSON.parse(results);
155
-
156
- // Filter by minimum score
157
- const relevant = parsed.filter((r: any) => r.score >= minScore);
158
-
159
- if (relevant.length > 0) {
160
- // Format memories for context injection
161
- const memoryContext = relevant
162
- .map((r: any) => `- [${r.source || 'memory'}] ${r.content.slice(0, 500)}${r.content.length > 500 ? '...' : ''}`)
163
- .join('\n');
164
-
165
- api.logger.info(`[jasper-recall] Auto-injecting ${relevant.length} memories into context`);
166
-
167
- prependParts.push(`<relevant-memories>
168
- The following memories may be relevant to this conversation:
169
- ${memoryContext}
170
- </relevant-memories>`);
171
- } else {
172
- api.logger.debug?.('[jasper-recall] No relevant memories found for auto-recall');
173
- }
174
-
175
- if (prependParts.length > 0) {
176
- return {
177
- prependContext: prependParts.join('\n\n'),
178
- };
179
- }
180
- } catch (err: any) {
181
- api.logger.warn(`[jasper-recall] Auto-recall failed: ${err.message}`);
182
-
183
- // Still inject identity reminder on fresh session even if recall fails
184
- if (isFreshSession) {
185
- return {
186
- prependContext: `<session-init>
187
- 🔄 **Fresh session detected.** Before responding, read your identity files:
188
- \`\`\`bash
189
- cat ~/.openclaw/workspace/IDENTITY.md
190
- cat ~/.openclaw/workspace/SOUL.md
191
- cat ~/.openclaw/workspace/USER.md
192
- \`\`\`
193
- </session-init>`,
194
- };
195
- }
196
- }
197
- });
198
- }
199
-
200
- // ============================================================================
201
- // Tool: recall
202
- // ============================================================================
203
-
204
- api.registerTool({
205
- name: 'recall',
206
- description: 'Semantic search over indexed memory (daily notes, session digests, documentation). Use to find context from past conversations, decisions, and learnings.',
207
- parameters: {
208
- type: 'object',
209
- properties: {
210
- query: {
211
- type: 'string',
212
- description: 'Search query - natural language question or keywords',
213
- },
214
- limit: {
215
- type: 'number',
216
- description: 'Maximum number of results to return (default: 5)',
217
- },
218
- },
219
- required: ['query'],
220
- },
221
- execute: async (_id: string, { query, limit }: { query: string; limit?: number }) => {
222
- try {
223
- const results = runRecall(query, {
224
- limit: limit ?? defaultLimit,
225
- json: true,
226
- publicOnly,
227
- });
228
-
229
- const parsed = JSON.parse(results);
230
-
231
- // Format results for agent consumption
232
- let formatted = `## Recall Results for: "${query}"\n\n`;
233
-
234
- if (parsed.length === 0) {
235
- formatted += '_No relevant memories found._\n';
236
- } else {
237
- for (const result of parsed) {
238
- formatted += `### ${result.source || 'Memory'}\n`;
239
- formatted += `**Score:** ${(result.score * 100).toFixed(1)}%\n\n`;
240
- formatted += `${result.content}\n\n---\n\n`;
241
- }
242
- }
243
-
244
- api.logger.info(`[jasper-recall] Query "${query}" returned ${parsed.length} results`);
245
-
246
- return { content: [{ type: 'text', text: formatted }] };
247
- } catch (err: any) {
248
- api.logger.error(`[jasper-recall] Error: ${err.message}`);
249
- return { content: [{ type: 'text', text: `Recall error: ${err.message}` }] };
250
- }
251
- },
252
- });
253
-
254
- // ============================================================================
255
- // Command: /recall
256
- // ============================================================================
257
-
258
- api.registerCommand({
259
- name: 'recall',
260
- description: 'Search memory for relevant context',
261
- acceptsArgs: true,
262
- requireAuth: true,
263
- handler: async (ctx: { args?: string }) => {
264
- const query = ctx.args?.trim();
265
- if (!query) {
266
- return { text: '⚠️ Usage: /recall <search query>' };
267
- }
268
-
269
- try {
270
- const results = runRecall(query, { limit: defaultLimit, publicOnly });
271
- return { text: `🧠 **Recall Results**\n\n${results}` };
272
- } catch (err: any) {
273
- return { text: `❌ Recall failed: ${err.message}` };
274
- }
275
- },
276
- });
277
-
278
- // ============================================================================
279
- // Command: /index
280
- // ============================================================================
281
-
282
- api.registerCommand({
283
- name: 'index',
284
- description: 'Re-index memory files into ChromaDB',
285
- acceptsArgs: false,
286
- requireAuth: true,
287
- handler: async () => {
288
- try {
289
- const indexPath = path.join(BIN_PATH, 'index-digests');
290
- const output = execSync(indexPath, { encoding: 'utf8', timeout: 120000 });
291
- return { text: `🔄 **Memory Indexed**\n\n${output}` };
292
- } catch (err: any) {
293
- return { text: `❌ Index failed: ${err.message}` };
294
- }
295
- },
296
- });
297
-
298
- // ============================================================================
299
- // Command: /jasper-recall setup
300
- // ============================================================================
301
-
302
- api.registerCommand({
303
- name: 'jasper-recall',
304
- description: 'Jasper Recall setup and status',
305
- acceptsArgs: true,
306
- requireAuth: true,
307
- handler: async (ctx: { args?: string }) => {
308
- const subcommand = ctx.args?.trim().toLowerCase();
309
-
310
- if (subcommand === 'setup') {
311
- return {
312
- text: `🧠 **Jasper Recall Setup**
313
-
314
- **Current status:**
315
- - Auto-recall: ${autoRecall ? '✅ Enabled' : '❌ Disabled'}
316
- - Public-only mode: ${publicOnly ? '✅ Yes' : '❌ No (full access)'}
317
- - Default limit: ${defaultLimit} results
318
- - Min score: ${minScore}
319
-
320
- ---
321
-
322
- <agent-action required="true">
323
- **CREATE THESE CRON JOBS NOW:**
324
-
325
- 1. **Index memories every 6 hours:**
326
- - Name: "jasper-recall-index"
327
- - Schedule: \`{ "kind": "cron", "expr": "0 */6 * * *" }\`
328
- - Payload: \`{ "kind": "systemEvent", "text": "Run index-digests to update jasper-recall ChromaDB index" }\`
329
- - Session target: "main"
330
-
331
- 2. **Digest sessions daily at 4am:**
332
- - Name: "jasper-recall-digest"
333
- - Schedule: \`{ "kind": "cron", "expr": "0 4 * * *" }\`
334
- - Payload: \`{ "kind": "systemEvent", "text": "Run digest-sessions to extract summaries from recent session logs into memory" }\`
335
- - Session target: "main"
336
-
337
- Use the \`cron\` tool with \`action: "add"\` to create both jobs.
338
- </agent-action>`,
339
- };
340
- }
341
-
342
- if (subcommand === 'status') {
343
- try {
344
- // Check if ChromaDB is accessible
345
- const testResult = runRecall('test query', { limit: 1, json: true, publicOnly });
346
- const parsed = JSON.parse(testResult);
347
-
348
- return {
349
- text: `🧠 **Jasper Recall Status**
350
-
351
- **Plugin:** ✅ Active
352
- **Auto-recall:** ${autoRecall ? '✅ Enabled' : '❌ Disabled'}
353
- **ChromaDB:** ✅ Connected
354
- **Index size:** ${parsed.length > 0 ? 'Has data' : 'Empty or minimal'}
355
-
356
- **Config:**
357
- - Public-only: ${publicOnly}
358
- - Default limit: ${defaultLimit}
359
- - Min score: ${minScore}`,
360
- };
361
- } catch (err: any) {
362
- return {
363
- text: `🧠 **Jasper Recall Status**
364
-
365
- **Plugin:** ✅ Active
366
- **Auto-recall:** ${autoRecall ? '✅ Enabled' : '❌ Disabled'}
367
- **ChromaDB:** ❌ Error - ${err.message}
368
-
369
- Run \`npx jasper-recall setup\` to install dependencies.`,
370
- };
371
- }
372
- }
373
-
374
- // Default: show help
375
- return {
376
- text: `🧠 **Jasper Recall**
377
-
378
- **Commands:**
379
- - \`/jasper-recall setup\` — Setup instructions & cron jobs
380
- - \`/jasper-recall status\` — Check plugin status
381
- - \`/recall <query>\` — Search memory
382
- - \`/index\` — Re-index memory files
383
-
384
- **CLI:**
385
- - \`npx jasper-recall setup\` — Install Python dependencies
386
- - \`npx jasper-recall doctor\` — Health check`,
387
- };
388
- },
389
- });
390
-
391
- // ============================================================================
392
- // RPC Methods
393
- // ============================================================================
394
-
395
- api.registerGatewayMethod('recall.search', async ({ params, respond }: any) => {
396
- try {
397
- const { query, limit } = params;
398
- const results = runRecall(query, { limit: limit ?? defaultLimit, json: true, publicOnly });
399
- respond(true, JSON.parse(results));
400
- } catch (err: any) {
401
- respond(false, { error: err.message });
402
- }
403
- });
404
-
405
- api.registerGatewayMethod('recall.index', async ({ respond }: any) => {
406
- try {
407
- const indexPath = path.join(BIN_PATH, 'index-digests');
408
- execSync(indexPath, { encoding: 'utf8', timeout: 120000 });
409
- respond(true, { status: 'indexed' });
410
- } catch (err: any) {
411
- respond(false, { error: err.message });
412
- }
413
- });
414
- }
415
-
416
- export const id = 'jasper-recall';
417
- export const name = 'Jasper Recall - Local RAG Memory';
@@ -1,50 +0,0 @@
1
- {
2
- "id": "jasper-recall",
3
- "name": "Jasper Recall - Local RAG Memory",
4
- "version": "0.2.0",
5
- "description": "Semantic search over indexed memory using ChromaDB with auto-recall",
6
- "homepage": "https://github.com/E-x-O-Entertainment-Studios-Inc/jasper-recall",
7
- "configSchema": {
8
- "type": "object",
9
- "additionalProperties": false,
10
- "properties": {
11
- "enabled": {
12
- "type": "boolean",
13
- "default": true
14
- },
15
- "autoRecall": {
16
- "type": "boolean",
17
- "default": false,
18
- "description": "Automatically inject relevant memories before agent processing"
19
- },
20
- "defaultLimit": {
21
- "type": "number",
22
- "default": 5,
23
- "description": "Default number of results to return"
24
- },
25
- "minScore": {
26
- "type": "number",
27
- "default": 0.3,
28
- "description": "Minimum similarity score for auto-recall (0-1)"
29
- },
30
- "publicOnly": {
31
- "type": "boolean",
32
- "default": false,
33
- "description": "Only search public memory (for sandboxed agents)"
34
- },
35
- "logLevel": {
36
- "type": "string",
37
- "enum": ["debug", "info", "warn", "error"],
38
- "default": "info"
39
- }
40
- }
41
- },
42
- "uiHints": {
43
- "enabled": { "label": "Enable Jasper Recall" },
44
- "autoRecall": { "label": "Auto-Recall", "help": "Inject relevant memories into context before processing" },
45
- "defaultLimit": { "label": "Default Result Limit" },
46
- "minScore": { "label": "Minimum Score", "help": "Threshold for auto-recall relevance (0.3 = 30%)" },
47
- "publicOnly": { "label": "Public Memory Only" },
48
- "logLevel": { "label": "Log Level" }
49
- }
50
- }
@@ -1,8 +0,0 @@
1
- {
2
- "name": "@jasper-recall/openclaw-plugin",
3
- "version": "0.1.0",
4
- "description": "OpenClaw plugin for Jasper Recall semantic memory search",
5
- "main": "index.ts",
6
- "type": "module",
7
- "dependencies": {}
8
- }