@memorilabs/openclaw-memori 0.0.8 → 0.0.9
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 +104 -29
- package/dist/cli/commands.d.ts +2 -0
- package/dist/cli/commands.js +143 -0
- package/dist/cli/config-file.d.ts +8 -0
- package/dist/cli/config-file.js +64 -0
- package/dist/handlers/augmentation.js +12 -5
- package/dist/index.js +10 -3
- package/dist/sanitizer.d.ts +1 -0
- package/dist/sanitizer.js +10 -2
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/memori-feedback.d.ts +25 -0
- package/dist/tools/memori-feedback.js +40 -0
- package/dist/tools/memori-recall-summary.d.ts +39 -0
- package/dist/tools/memori-recall-summary.js +58 -0
- package/dist/tools/memori-recall.d.ts +61 -0
- package/dist/tools/memori-recall.js +99 -0
- package/dist/tools/types.d.ts +8 -0
- package/dist/tools/types.js +1 -0
- package/dist/types.d.ts +4 -8
- package/dist/utils/context.d.ts +4 -2
- package/dist/utils/context.js +4 -2
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/memori-client.d.ts +11 -0
- package/dist/utils/memori-client.js +20 -2
- package/dist/utils/skills-loader.d.ts +6 -0
- package/dist/utils/skills-loader.js +14 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/openclaw.plugin.json +15 -2
- package/package.json +3 -2
- package/skills/memori/skills.md +65 -0
- package/dist/handlers/recall.d.ts +0 -5
- package/dist/handlers/recall.js +0 -34
package/README.md
CHANGED
|
@@ -67,16 +67,15 @@ Memori enforces user-scoped memory boundaries for secure multi-user deployments.
|
|
|
67
67
|
|
|
68
68
|
The Memori plugin replaces OpenClaw's flat-file memory workflow with managed, structured memory that is scoped by `entity_id`, `process_id`, and `session_id` and enriched automatically through OpenClaw's existing hooks.
|
|
69
69
|
|
|
70
|
-
| Capability
|
|
71
|
-
|
|
|
72
|
-
| **Structured memory storage**
|
|
73
|
-
| **Advanced Augmentation**
|
|
74
|
-
| **Intelligent Recall**
|
|
75
|
-
| **Production-ready observability** | Memori Cloud gives you dashboard visibility into memory creation, recalls, cache hit rate, sessions, quota usage, top subjects, per-memory retrieval metrics, and knowledge-graph relationships, so you can inspect what was stored and how recall is behaving in production.
|
|
70
|
+
| Capability | What changes |
|
|
71
|
+
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| **Structured memory storage** | Instead of raw markdown blobs, Memori stores conversations, facts, preferences, and knowledge-graph triples as structured records tied to an entity, process, and session. Facts are extracted as subject-predicate-object relationships, deduplicated over time, and connected into a graph so related memories stay queryable instead of being buried in text files. |
|
|
73
|
+
| **Advanced Augmentation** | After each conversation, Memori processes the user and assistant exchange asynchronously in the background, identifies facts, preferences, skills, and attributes, generates embeddings for semantic search, and updates the knowledge graph without blocking the agent's response path. |
|
|
74
|
+
| **Intelligent Recall** | Before the agent responds, Memori searches the current entity's stored facts and knowledge graph, ranks memories by semantic relevance and importance, and injects the most useful context into the prompt so durable knowledge survives context-window compression. |
|
|
75
|
+
| **Production-ready observability** | Memori Cloud gives you dashboard visibility into memory creation, recalls, cache hit rate, sessions, quota usage, top subjects, per-memory retrieval metrics, and knowledge-graph relationships, so you can inspect what was stored and how recall is behaving in production. |
|
|
76
76
|
|
|
77
77
|
The plugin still remains drop-in: OpenClaw handles the agent loop, while Memori adds recall, augmentation, sanitization, and observability around it.
|
|
78
78
|
|
|
79
|
-
|
|
80
79
|
## Quickstart
|
|
81
80
|
|
|
82
81
|
Get persistent memory running in your OpenClaw gateway in three steps.
|
|
@@ -86,36 +85,47 @@ Get persistent memory running in your OpenClaw gateway in three steps.
|
|
|
86
85
|
- [OpenClaw](https://openclaw.ai) `v2026.3.2` or later
|
|
87
86
|
- A Memori API key from [app.memorilabs.ai](https://app.memorilabs.ai)
|
|
88
87
|
- An Entity ID to attribute memories to, such as a user ID, tenant ID, or agent name
|
|
88
|
+
- A Project ID to scope memories to a specific project or workspace
|
|
89
89
|
|
|
90
90
|
### 1. Install and Enable
|
|
91
91
|
|
|
92
|
-
Run the following commands in your terminal to install and enable the plugin:
|
|
93
|
-
|
|
94
92
|
```bash
|
|
95
|
-
#
|
|
93
|
+
# Install the plugin from npm
|
|
96
94
|
openclaw plugins install @memorilabs/openclaw-memori
|
|
97
95
|
|
|
98
|
-
#
|
|
96
|
+
# Enable it in your workspace
|
|
99
97
|
openclaw plugins enable openclaw-memori
|
|
100
|
-
|
|
101
|
-
# 3. Restart the OpenClaw gateway
|
|
102
|
-
openclaw gateway restart
|
|
103
98
|
```
|
|
104
99
|
|
|
105
100
|
### 2. Configure
|
|
106
101
|
|
|
107
|
-
The plugin
|
|
102
|
+
The plugin requires an API key, an Entity ID, and a Project ID. Use the built-in `memori` CLI to configure it in one step.
|
|
108
103
|
|
|
109
|
-
|
|
104
|
+
#### Option A: Memori CLI (Recommended)
|
|
110
105
|
|
|
111
106
|
```bash
|
|
112
|
-
openclaw
|
|
113
|
-
|
|
107
|
+
openclaw memori init \
|
|
108
|
+
--api-key "YOUR_MEMORI_API_KEY" \
|
|
109
|
+
--entity-id "your-entity-id" \
|
|
110
|
+
--project-id "your-project-id"
|
|
114
111
|
```
|
|
115
112
|
|
|
116
|
-
|
|
113
|
+
Then restart the gateway:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
openclaw gateway restart
|
|
117
|
+
```
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
#### Option B: OpenClaw config set
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
openclaw config set plugins.entries.openclaw-memori.config.apiKey "YOUR_MEMORI_API_KEY"
|
|
123
|
+
openclaw config set plugins.entries.openclaw-memori.config.entityId "your-entity-id"
|
|
124
|
+
openclaw config set plugins.entries.openclaw-memori.config.projectId "your-project-id"
|
|
125
|
+
openclaw gateway restart
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Option C: Direct JSON (`~/.openclaw/openclaw.json`)
|
|
119
129
|
|
|
120
130
|
```json
|
|
121
131
|
{
|
|
@@ -125,7 +135,8 @@ Add the following to your `~/.openclaw/openclaw.json` file:
|
|
|
125
135
|
"enabled": true,
|
|
126
136
|
"config": {
|
|
127
137
|
"apiKey": "your-memori-api-key",
|
|
128
|
-
"entityId": "your-
|
|
138
|
+
"entityId": "your-entity-id",
|
|
139
|
+
"projectId": "your-project-id"
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
142
|
}
|
|
@@ -135,25 +146,42 @@ Add the following to your `~/.openclaw/openclaw.json` file:
|
|
|
135
146
|
|
|
136
147
|
### Configuration Options
|
|
137
148
|
|
|
138
|
-
| Option
|
|
139
|
-
|
|
|
140
|
-
| `apiKey`
|
|
141
|
-
| `entityId`
|
|
149
|
+
| Option | Type | Required | Description |
|
|
150
|
+
| ----------- | -------- | -------- | --------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| `apiKey` | `string` | **Yes** | Your Memori API key from [app.memorilabs.ai](https://app.memorilabs.ai). |
|
|
152
|
+
| `entityId` | `string` | **Yes** | The unique identifier for the entity (e.g., user, agent, or tenant) to attribute these memories to. |
|
|
153
|
+
| `projectId` | `string` | **Yes** | Scopes all memories to a specific project or workspace. |
|
|
142
154
|
|
|
143
155
|
### 3. Verify
|
|
144
156
|
|
|
145
|
-
|
|
157
|
+
Check that the plugin is configured and can reach the API:
|
|
146
158
|
|
|
147
159
|
```bash
|
|
148
|
-
openclaw
|
|
149
|
-
openclaw gateway logs --filter "[Memori]"
|
|
160
|
+
openclaw memori status --check
|
|
150
161
|
```
|
|
151
162
|
|
|
152
163
|
You should see:
|
|
153
164
|
|
|
165
|
+
```text
|
|
166
|
+
Memori Plugin Status
|
|
167
|
+
────────────────────────────────────
|
|
168
|
+
API Key: ****...A3xQ
|
|
169
|
+
Entity ID: your-entity-id
|
|
170
|
+
Project ID: your-project-id
|
|
171
|
+
|
|
172
|
+
Checking API connectivity... OK
|
|
173
|
+
Status: Ready
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
You can also inspect gateway logs to confirm the plugin loaded:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
openclaw gateway logs --filter "[Memori]"
|
|
180
|
+
```
|
|
181
|
+
|
|
154
182
|
```text
|
|
155
183
|
[Memori] === INITIALIZING PLUGIN ===
|
|
156
|
-
[Memori] Tracking Entity ID: your-
|
|
184
|
+
[Memori] Tracking Entity ID: your-entity-id
|
|
157
185
|
```
|
|
158
186
|
|
|
159
187
|
To test the full memory loop:
|
|
@@ -167,6 +195,53 @@ To test the full memory loop:
|
|
|
167
195
|
4. Confirm recall ran:
|
|
168
196
|
`Successfully injected memory context.`
|
|
169
197
|
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## CLI Reference
|
|
201
|
+
|
|
202
|
+
The plugin registers a `memori` command group in the OpenClaw CLI.
|
|
203
|
+
|
|
204
|
+
### `openclaw memori init`
|
|
205
|
+
|
|
206
|
+
Configure the plugin with your credentials. All three flags are required.
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
openclaw memori init \
|
|
210
|
+
--api-key <key> \
|
|
211
|
+
--entity-id <id> \
|
|
212
|
+
--project-id <id>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### `openclaw memori status`
|
|
216
|
+
|
|
217
|
+
Show the current configuration and whether the plugin is ready to run. Add `--check` to test live API connectivity.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
openclaw memori status
|
|
221
|
+
openclaw memori status --check
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### `openclaw memori config`
|
|
225
|
+
|
|
226
|
+
Fine-grained configuration management.
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Show all current values
|
|
230
|
+
openclaw memori config show
|
|
231
|
+
|
|
232
|
+
# Get a single value (API key is masked)
|
|
233
|
+
openclaw memori config get api-key
|
|
234
|
+
openclaw memori config get entity-id
|
|
235
|
+
openclaw memori config get project-id
|
|
236
|
+
|
|
237
|
+
# Set a single value
|
|
238
|
+
openclaw memori config set api-key "NEW_KEY"
|
|
239
|
+
openclaw memori config set entity-id "new-entity"
|
|
240
|
+
openclaw memori config set project-id "new-project"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Valid keys: `api-key`, `entity-id`, `project-id`.
|
|
244
|
+
|
|
170
245
|
## How It Works
|
|
171
246
|
|
|
172
247
|
This plugin integrates with OpenClaw's event lifecycle to provide persistent memory without interfering with the agent's core logic:
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createRecallClient } from '../utils/memori-client.js';
|
|
2
|
+
import { CONFIG_KEY_MAP, readPluginConfig, writePluginConfig } from './config-file.js';
|
|
3
|
+
function maskKey(key) {
|
|
4
|
+
if (key.length <= 8)
|
|
5
|
+
return '****';
|
|
6
|
+
return `****...${key.slice(-4)}`;
|
|
7
|
+
}
|
|
8
|
+
function isReady(cfg) {
|
|
9
|
+
return Boolean(cfg.apiKey && cfg.entityId && cfg.projectId);
|
|
10
|
+
}
|
|
11
|
+
export function registerCliCommands(api) {
|
|
12
|
+
api.registerCli(({ program }) => {
|
|
13
|
+
const memori = program
|
|
14
|
+
.command('memori')
|
|
15
|
+
.description('Memori memory plugin commands')
|
|
16
|
+
.configureHelp({ sortSubcommands: true });
|
|
17
|
+
// ── init ────────────────────────────────────────────────────────────────
|
|
18
|
+
memori
|
|
19
|
+
.command('init')
|
|
20
|
+
.description('Configure the Memori plugin with your API credentials')
|
|
21
|
+
.option('--api-key <key>', 'Memori API key (from app.memorilabs.ai)')
|
|
22
|
+
.option('--entity-id <id>', 'Entity ID to scope all memories to')
|
|
23
|
+
.option('--project-id <id>', 'Project ID to scope all memories to')
|
|
24
|
+
.action((opts) => {
|
|
25
|
+
const missing = [];
|
|
26
|
+
if (!opts.apiKey)
|
|
27
|
+
missing.push('--api-key');
|
|
28
|
+
if (!opts.entityId)
|
|
29
|
+
missing.push('--entity-id');
|
|
30
|
+
if (!opts.projectId)
|
|
31
|
+
missing.push('--project-id');
|
|
32
|
+
if (missing.length > 0) {
|
|
33
|
+
console.error(`Error: missing required option(s): ${missing.join(', ')}`);
|
|
34
|
+
console.error('\nUsage:\n openclaw memori init --api-key <key> --entity-id <id> --project-id <id>');
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
writePluginConfig({
|
|
39
|
+
apiKey: opts.apiKey,
|
|
40
|
+
entityId: opts.entityId,
|
|
41
|
+
projectId: opts.projectId,
|
|
42
|
+
});
|
|
43
|
+
console.log('\nMemori configured successfully.\n');
|
|
44
|
+
console.log(` API Key: ${maskKey(opts.apiKey || '')}`);
|
|
45
|
+
console.log(` Entity ID: ${opts.entityId}`);
|
|
46
|
+
console.log(` Project ID: ${opts.projectId}`);
|
|
47
|
+
console.log('\nRestart the gateway to apply changes:');
|
|
48
|
+
console.log(' openclaw gateway restart\n');
|
|
49
|
+
});
|
|
50
|
+
// ── status ──────────────────────────────────────────────────────────────
|
|
51
|
+
memori
|
|
52
|
+
.command('status')
|
|
53
|
+
.description('Show current configuration and verify API connectivity')
|
|
54
|
+
.option('--check', 'Test live API connectivity')
|
|
55
|
+
.action(async (opts) => {
|
|
56
|
+
const cfg = readPluginConfig();
|
|
57
|
+
const ready = isReady(cfg);
|
|
58
|
+
console.log('\nMemori Plugin Status');
|
|
59
|
+
console.log('─'.repeat(36));
|
|
60
|
+
console.log(` API Key: ${cfg.apiKey ? maskKey(cfg.apiKey) : '(not set)'}`);
|
|
61
|
+
console.log(` Entity ID: ${cfg.entityId ?? '(not set)'}`);
|
|
62
|
+
console.log(` Project ID: ${cfg.projectId ?? '(not set)'}`);
|
|
63
|
+
console.log();
|
|
64
|
+
if (!ready) {
|
|
65
|
+
console.log('Status: Not configured.');
|
|
66
|
+
console.log('Run: openclaw memori init --api-key <key> --entity-id <id> --project-id <id>\n');
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (opts.check) {
|
|
71
|
+
process.stdout.write('Checking API connectivity... ');
|
|
72
|
+
try {
|
|
73
|
+
const client = createRecallClient(cfg.apiKey || '', cfg.entityId || '');
|
|
74
|
+
await client.agentRecall({ projectId: cfg.projectId });
|
|
75
|
+
console.log('OK');
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
console.log('FAILED');
|
|
79
|
+
console.error(` ${String(e)}\n`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
console.log('Status: Ready\n');
|
|
85
|
+
});
|
|
86
|
+
// ── config ──────────────────────────────────────────────────────────────
|
|
87
|
+
const configCmd = memori
|
|
88
|
+
.command('config')
|
|
89
|
+
.description('Manage plugin configuration')
|
|
90
|
+
.configureHelp({ sortSubcommands: true });
|
|
91
|
+
const KEYS = Object.keys(CONFIG_KEY_MAP).join(', ');
|
|
92
|
+
configCmd
|
|
93
|
+
.command('show')
|
|
94
|
+
.description('Display current configuration')
|
|
95
|
+
.action(() => {
|
|
96
|
+
const cfg = readPluginConfig();
|
|
97
|
+
console.log('\nMemori Plugin Configuration');
|
|
98
|
+
console.log('─'.repeat(36));
|
|
99
|
+
console.log(` api-key: ${cfg.apiKey ? maskKey(cfg.apiKey) : '(not set)'}`);
|
|
100
|
+
console.log(` entity-id: ${cfg.entityId ?? '(not set)'}`);
|
|
101
|
+
console.log(` project-id: ${cfg.projectId ?? '(not set)'}`);
|
|
102
|
+
console.log();
|
|
103
|
+
});
|
|
104
|
+
configCmd
|
|
105
|
+
.command('get')
|
|
106
|
+
.argument('<key>', `Config key — one of: ${KEYS}`)
|
|
107
|
+
.description('Get a specific config value')
|
|
108
|
+
.action((key) => {
|
|
109
|
+
if (!(key in CONFIG_KEY_MAP)) {
|
|
110
|
+
console.error(`Unknown key "${key}". Valid keys: ${KEYS}`);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const field = CONFIG_KEY_MAP[key];
|
|
115
|
+
const value = readPluginConfig()[field];
|
|
116
|
+
if (value === undefined) {
|
|
117
|
+
console.log('(not set)');
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(field === 'apiKey' ? maskKey(value) : value);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
configCmd
|
|
124
|
+
.command('set')
|
|
125
|
+
.argument('<key>', `Config key — one of: ${KEYS}`)
|
|
126
|
+
.argument('<value>', 'Value to set')
|
|
127
|
+
.description('Set a specific config value')
|
|
128
|
+
.action((key, value) => {
|
|
129
|
+
if (!(key in CONFIG_KEY_MAP)) {
|
|
130
|
+
console.error(`Unknown key "${key}". Valid keys: ${KEYS}`);
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const field = CONFIG_KEY_MAP[key];
|
|
135
|
+
writePluginConfig({ [field]: value });
|
|
136
|
+
console.log(`Set ${key}.`);
|
|
137
|
+
});
|
|
138
|
+
}, {
|
|
139
|
+
descriptors: [
|
|
140
|
+
{ name: 'memori', description: 'Memori memory plugin commands', hasSubcommands: true },
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface MemoriCLIConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
entityId?: string;
|
|
4
|
+
projectId?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function readPluginConfig(): MemoriCLIConfig;
|
|
7
|
+
export declare function writePluginConfig(updates: Partial<MemoriCLIConfig>): void;
|
|
8
|
+
export declare const CONFIG_KEY_MAP: Record<string, keyof MemoriCLIConfig>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
const PLUGIN_ID = 'openclaw-memori';
|
|
5
|
+
const CONFIG_PATH = join(homedir(), '.openclaw', 'openclaw.json');
|
|
6
|
+
function readFullConfig() {
|
|
7
|
+
if (!existsSync(CONFIG_PATH))
|
|
8
|
+
return {};
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
throw new Error(`Failed to parse OpenClaw config at ${CONFIG_PATH}: ${String(e)}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function writeFullConfig(config) {
|
|
17
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
18
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
function ensurePluginStructure(config) {
|
|
21
|
+
if (!config.plugins || typeof config.plugins !== 'object')
|
|
22
|
+
config.plugins = {};
|
|
23
|
+
const plugins = config.plugins;
|
|
24
|
+
if (!plugins.entries || typeof plugins.entries !== 'object')
|
|
25
|
+
plugins.entries = {};
|
|
26
|
+
const entries = plugins.entries;
|
|
27
|
+
if (!entries[PLUGIN_ID] || typeof entries[PLUGIN_ID] !== 'object')
|
|
28
|
+
entries[PLUGIN_ID] = {};
|
|
29
|
+
const entry = entries[PLUGIN_ID];
|
|
30
|
+
if (!entry.config || typeof entry.config !== 'object')
|
|
31
|
+
entry.config = {};
|
|
32
|
+
}
|
|
33
|
+
function getPluginConfigBlock(full) {
|
|
34
|
+
const plugins = full.plugins;
|
|
35
|
+
const entries = plugins?.entries;
|
|
36
|
+
const entry = entries?.[PLUGIN_ID];
|
|
37
|
+
return entry?.config;
|
|
38
|
+
}
|
|
39
|
+
export function readPluginConfig() {
|
|
40
|
+
const cfg = getPluginConfigBlock(readFullConfig());
|
|
41
|
+
if (!cfg)
|
|
42
|
+
return {};
|
|
43
|
+
return {
|
|
44
|
+
apiKey: cfg.apiKey,
|
|
45
|
+
entityId: cfg.entityId,
|
|
46
|
+
projectId: cfg.projectId,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function writePluginConfig(updates) {
|
|
50
|
+
const full = readFullConfig();
|
|
51
|
+
ensurePluginStructure(full);
|
|
52
|
+
const entry = full.plugins.entries[PLUGIN_ID];
|
|
53
|
+
const cfg = entry.config;
|
|
54
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
55
|
+
if (value)
|
|
56
|
+
cfg[key] = value;
|
|
57
|
+
}
|
|
58
|
+
writeFullConfig(full);
|
|
59
|
+
}
|
|
60
|
+
export const CONFIG_KEY_MAP = {
|
|
61
|
+
'api-key': 'apiKey',
|
|
62
|
+
'entity-id': 'entityId',
|
|
63
|
+
'project-id': 'projectId',
|
|
64
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { extractContext, initializeMemoriClient } from '../utils/index.js';
|
|
2
|
-
import { cleanText, isSystemMessage } from '../sanitizer.js';
|
|
2
|
+
import { cleanText, extractContentType, isSystemMessage } from '../sanitizer.js';
|
|
3
3
|
import { AUGMENTATION_CONFIG, MESSAGE_CONSTANTS, ROLE } from '../constants.js';
|
|
4
4
|
import { SDK_VERSION } from '../version.js';
|
|
5
5
|
/**
|
|
@@ -73,7 +73,9 @@ function extractToolCalls(msg, toolResults) {
|
|
|
73
73
|
*/
|
|
74
74
|
function parseUserMessage(msg) {
|
|
75
75
|
const cleanedContent = cleanText(msg.content);
|
|
76
|
-
return cleanedContent
|
|
76
|
+
return cleanedContent
|
|
77
|
+
? { role: msg.role, content: cleanedContent, type: extractContentType(msg.content) }
|
|
78
|
+
: null;
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
79
81
|
* Parses the most recent conversation turn from messages.
|
|
@@ -102,10 +104,15 @@ function parseTurnFromMessages(messages) {
|
|
|
102
104
|
assistantMessage = {
|
|
103
105
|
role: msg.role,
|
|
104
106
|
content: cleanedContent.replace(/^\[\[.*?\]\]\s*/, ''),
|
|
107
|
+
type: extractContentType(msg.content),
|
|
105
108
|
};
|
|
106
109
|
}
|
|
107
110
|
else if (extractedTools.length > 0) {
|
|
108
|
-
assistantMessage = {
|
|
111
|
+
assistantMessage = {
|
|
112
|
+
role: msg.role,
|
|
113
|
+
content: MESSAGE_CONSTANTS.SILENT_REPLY,
|
|
114
|
+
type: extractContentType(msg.content),
|
|
115
|
+
};
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
}
|
|
@@ -164,8 +171,8 @@ export async function handleAugmentation(event, ctx, config, logger) {
|
|
|
164
171
|
logger.info('Assistant used tool-based messaging. Using synthetic response.');
|
|
165
172
|
turn.assistantMessage.content = MESSAGE_CONSTANTS.SYNTHETIC_RESPONSE;
|
|
166
173
|
}
|
|
167
|
-
const payload = buildAugmentationPayload(turn.userMessage
|
|
168
|
-
const context = extractContext(event, ctx, config.entityId);
|
|
174
|
+
const payload = buildAugmentationPayload(turn.userMessage, turn.assistantMessage, turn.tools, event);
|
|
175
|
+
const context = extractContext(event, ctx, config.entityId, config.projectId);
|
|
169
176
|
const memoriClient = initializeMemoriClient(config.apiKey, context);
|
|
170
177
|
await memoriClient.augmentation(payload);
|
|
171
178
|
logger.info('Augmentation successful!');
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
|
-
import { handleRecall } from './handlers/recall.js';
|
|
2
1
|
import { handleAugmentation } from './handlers/augmentation.js';
|
|
3
2
|
import { PLUGIN_CONFIG } from './constants.js';
|
|
4
|
-
import { MemoriLogger } from './utils/index.js';
|
|
3
|
+
import { MemoriLogger, loadSkillsContent } from './utils/index.js';
|
|
4
|
+
import { registerAllTools } from './tools/index.js';
|
|
5
|
+
import { registerCliCommands } from './cli/commands.js';
|
|
5
6
|
const memoriPlugin = {
|
|
6
7
|
id: PLUGIN_CONFIG.ID,
|
|
7
8
|
name: PLUGIN_CONFIG.NAME,
|
|
8
9
|
description: 'Hosted memory backend',
|
|
9
10
|
register(api) {
|
|
11
|
+
registerCliCommands(api);
|
|
10
12
|
const rawConfig = api.pluginConfig;
|
|
11
13
|
const config = {
|
|
12
14
|
apiKey: rawConfig?.apiKey,
|
|
13
15
|
entityId: rawConfig?.entityId,
|
|
16
|
+
projectId: rawConfig?.projectId,
|
|
14
17
|
};
|
|
15
18
|
if (!config.apiKey || !config.entityId) {
|
|
16
19
|
api.logger.warn(`${PLUGIN_CONFIG.LOG_PREFIX} Missing apiKey or entityId in config. Plugin disabled.`);
|
|
17
20
|
return;
|
|
18
21
|
}
|
|
19
22
|
const logger = new MemoriLogger(api);
|
|
23
|
+
const skillsContent = loadSkillsContent(api.resolvePath.bind(api));
|
|
20
24
|
logger.info(`\n=== ${PLUGIN_CONFIG.LOG_PREFIX} INITIALIZING PLUGIN ===`);
|
|
21
25
|
logger.info(`${PLUGIN_CONFIG.LOG_PREFIX} Tracking Entity ID: ${config.entityId}`);
|
|
22
|
-
|
|
26
|
+
if (skillsContent) {
|
|
27
|
+
api.on('before_prompt_build', () => ({ appendSystemContext: skillsContent }));
|
|
28
|
+
}
|
|
23
29
|
api.on('agent_end', (event, ctx) => handleAugmentation(event, ctx, config, logger));
|
|
30
|
+
registerAllTools({ api, config, logger });
|
|
24
31
|
},
|
|
25
32
|
};
|
|
26
33
|
export default memoriPlugin;
|
package/dist/sanitizer.d.ts
CHANGED
package/dist/sanitizer.js
CHANGED
|
@@ -19,7 +19,7 @@ export function isSystemMessage(text) {
|
|
|
19
19
|
*
|
|
20
20
|
* OpenClaw wraps metadata in markdown code fences (triple tick).
|
|
21
21
|
* The actual message is always after the LAST closing fence.
|
|
22
|
-
* The message might
|
|
22
|
+
* The message might also contain a timestamp prefix like: [Day YYYY-MM-DD HH:MM TZ], that will need to be removed
|
|
23
23
|
*/
|
|
24
24
|
function extractRawUserMessage(content) {
|
|
25
25
|
let message = content;
|
|
@@ -49,11 +49,19 @@ function extractMessageText(content) {
|
|
|
49
49
|
}
|
|
50
50
|
return '';
|
|
51
51
|
}
|
|
52
|
+
export function extractContentType(rawContent) {
|
|
53
|
+
if (typeof rawContent === 'string' || !rawContent)
|
|
54
|
+
return 'text';
|
|
55
|
+
if (isMessageBlockArray(rawContent)) {
|
|
56
|
+
const primary = rawContent.find((block) => (block.type === 'text' || typeof block.text === 'string') && block.text);
|
|
57
|
+
return primary?.type ?? 'text';
|
|
58
|
+
}
|
|
59
|
+
return 'text';
|
|
60
|
+
}
|
|
52
61
|
export function cleanText(rawContent) {
|
|
53
62
|
let text = extractMessageText(rawContent);
|
|
54
63
|
if (!text)
|
|
55
64
|
return '';
|
|
56
65
|
text = extractRawUserMessage(text);
|
|
57
|
-
text = text.replace(/<memori_context>[\s\S]*?<\/memori_context>\s*/g, '');
|
|
58
66
|
return text.trim();
|
|
59
67
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createMemoriRecallTool } from './memori-recall.js';
|
|
2
|
+
import { createMemoriRecallSummaryTool } from './memori-recall-summary.js';
|
|
3
|
+
import { createMemoriFeedbackTool } from './memori-feedback.js';
|
|
4
|
+
export function registerAllTools(deps) {
|
|
5
|
+
deps.api.registerTool(createMemoriRecallTool(deps));
|
|
6
|
+
deps.api.registerTool(createMemoriRecallSummaryTool(deps));
|
|
7
|
+
deps.api.registerTool(createMemoriFeedbackTool(deps));
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ToolDeps } from './types.js';
|
|
2
|
+
export declare function createMemoriFeedbackTool(deps: ToolDeps): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
content: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
required: string[];
|
|
15
|
+
};
|
|
16
|
+
execute(_toolCallId: string, params: {
|
|
17
|
+
content: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
details: null;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRecallClient } from '../utils/memori-client.js';
|
|
2
|
+
export function createMemoriFeedbackTool(deps) {
|
|
3
|
+
const { config, logger } = deps;
|
|
4
|
+
return {
|
|
5
|
+
name: 'memori_feedback',
|
|
6
|
+
label: 'Memori Feedback',
|
|
7
|
+
description: 'CRITICAL: You MUST use this tool immediately whenever the user asks you to send feedback, report a bug, suggest a feature, or complain about Memori. Send feedback directly to the Memori team (positive or negative).',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
content: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'REQUIRED: The feedback message to send.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
required: ['content'],
|
|
17
|
+
},
|
|
18
|
+
async execute(_toolCallId, params) {
|
|
19
|
+
try {
|
|
20
|
+
logger.info(`memori_feedback sending: ${params.content}`);
|
|
21
|
+
const client = createRecallClient(config.apiKey, config.entityId);
|
|
22
|
+
await client.agentFeedback(params.content);
|
|
23
|
+
const result = { success: true, message: 'Feedback sent successfully.' };
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
26
|
+
details: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
logger.warn(`memori_feedback failed: ${String(e)}`);
|
|
31
|
+
const errorResult = { error: 'Feedback failed to send.' };
|
|
32
|
+
logger.info(`memori_feedback error result: ${JSON.stringify(errorResult)}`);
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
35
|
+
details: null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ToolDeps } from './types.js';
|
|
2
|
+
export declare function createMemoriRecallSummaryTool(deps: ToolDeps): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
dateStart: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
dateEnd: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
projectId: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
sessionId: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
execute(_toolCallId: string, params: {
|
|
28
|
+
dateStart?: string;
|
|
29
|
+
dateEnd?: string;
|
|
30
|
+
projectId?: string;
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
content: {
|
|
34
|
+
type: "text";
|
|
35
|
+
text: string;
|
|
36
|
+
}[];
|
|
37
|
+
details: null;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRecallClient } from '../utils/memori-client.js';
|
|
2
|
+
export function createMemoriRecallSummaryTool(deps) {
|
|
3
|
+
const { config, logger } = deps;
|
|
4
|
+
return {
|
|
5
|
+
name: 'memori_recall_summary',
|
|
6
|
+
label: 'Recall Memory Summary',
|
|
7
|
+
description: 'CRITICAL: You MUST use this tool BEFORE answering any requests for a summary, status update, daily brief, or high-level overview of a project or past sessions. Fetch summarized views of stored memories from Memori within a specific date range.',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
dateStart: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'ISO 8601 (MUST be UTC) date string to filter summaries created on or after this time',
|
|
14
|
+
},
|
|
15
|
+
dateEnd: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'ISO 8601 (MUST be UTC) date string to filter summaries created on or before this time',
|
|
18
|
+
},
|
|
19
|
+
projectId: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'CRITICAL: Leave this EMPTY to use the configured default project. ONLY provide a value if the user explicitly asks to search a different project by name.',
|
|
22
|
+
},
|
|
23
|
+
sessionId: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Filter to a specific session. Cannot be used without projectId.',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
async execute(_toolCallId, params) {
|
|
30
|
+
try {
|
|
31
|
+
const finalParams = { projectId: config.projectId, ...params };
|
|
32
|
+
if (finalParams.sessionId && !finalParams.projectId) {
|
|
33
|
+
const errorResult = { error: 'sessionId cannot be provided without projectId' };
|
|
34
|
+
logger.warn(`memori_recall_summary rejected: ${JSON.stringify(errorResult)}`);
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
37
|
+
details: null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
logger.info(`memori_recall_summary params: ${JSON.stringify(finalParams)}`);
|
|
41
|
+
const client = createRecallClient(config.apiKey, config.entityId);
|
|
42
|
+
const result = await client.agentRecallSummary(finalParams);
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
45
|
+
details: null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
logger.warn(`memori_recall_summary failed: ${String(e)}`);
|
|
50
|
+
const errorResult = { error: 'Recall summary failed' };
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
53
|
+
details: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ToolDeps } from './types.js';
|
|
2
|
+
export declare function createMemoriRecallTool(deps: ToolDeps): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
query: {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
limit: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
dateStart: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
dateEnd: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
projectId: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
sessionId: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
signal: {
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
enum: string[];
|
|
37
|
+
};
|
|
38
|
+
source: {
|
|
39
|
+
type: string;
|
|
40
|
+
description: string;
|
|
41
|
+
enum: string[];
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
required: string[];
|
|
45
|
+
};
|
|
46
|
+
execute(_toolCallId: string, params: {
|
|
47
|
+
query: string;
|
|
48
|
+
dateStart?: string;
|
|
49
|
+
dateEnd?: string;
|
|
50
|
+
projectId?: string;
|
|
51
|
+
sessionId?: string;
|
|
52
|
+
signal?: string;
|
|
53
|
+
source?: string;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
content: {
|
|
56
|
+
type: "text";
|
|
57
|
+
text: string;
|
|
58
|
+
}[];
|
|
59
|
+
details: null;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createRecallClient } from '../utils/memori-client.js';
|
|
2
|
+
export function createMemoriRecallTool(deps) {
|
|
3
|
+
const { config, logger } = deps;
|
|
4
|
+
return {
|
|
5
|
+
name: 'memori_recall',
|
|
6
|
+
label: 'Recall Memory',
|
|
7
|
+
description: 'CRITICAL: You MUST use this tool to search for past context BEFORE claiming you do not know the user, their preferences, or past events. Explicitly fetch relevant memories from Memori using filters...',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
query: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'REQUIRED: The natural language search query to find specific facts (e.g., "What database did we decide to use?", "Ryan\'s dogs"). DO NOT use wildcards like "*" or regex. This is a semantic search, so use real words.',
|
|
14
|
+
},
|
|
15
|
+
limit: {
|
|
16
|
+
type: 'number',
|
|
17
|
+
description: 'Maximum number of memories to return (default: 10)',
|
|
18
|
+
},
|
|
19
|
+
dateStart: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'ISO 8601 (MUST be UTC) date string to filter memories created on or after this time',
|
|
22
|
+
},
|
|
23
|
+
dateEnd: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'ISO 8601 (MUST be UTC) date string to filter memories created on or before this time',
|
|
26
|
+
},
|
|
27
|
+
projectId: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'CRITICAL: Leave this EMPTY to use the configured default project. ONLY provide a value if the user explicitly asks to search a different project by name.',
|
|
30
|
+
},
|
|
31
|
+
sessionId: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Filter to a specific session. Cannot be used without projectId.',
|
|
34
|
+
},
|
|
35
|
+
signal: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Filter to a specific fact signal. MUST be one of the allowed enum values.',
|
|
38
|
+
enum: [
|
|
39
|
+
'commit',
|
|
40
|
+
'discovery',
|
|
41
|
+
'failure',
|
|
42
|
+
'inference',
|
|
43
|
+
'pattern',
|
|
44
|
+
'result',
|
|
45
|
+
'update',
|
|
46
|
+
'verification',
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
source: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'Filter to a specific source origin. MUST be one of the allowed enum values.',
|
|
52
|
+
enum: [
|
|
53
|
+
'constraint',
|
|
54
|
+
'decision',
|
|
55
|
+
'execution',
|
|
56
|
+
'fact',
|
|
57
|
+
'insight',
|
|
58
|
+
'instruction',
|
|
59
|
+
'status',
|
|
60
|
+
'strategy',
|
|
61
|
+
'task',
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
// Force the LLM to ALWAYS provide a search query
|
|
66
|
+
required: ['query'],
|
|
67
|
+
},
|
|
68
|
+
async execute(_toolCallId, params) {
|
|
69
|
+
try {
|
|
70
|
+
// If params.projectId is undefined, it falls back to config.projectId.
|
|
71
|
+
// If the LLM intentionally provides one, it overwrites the config.
|
|
72
|
+
const finalParams = { projectId: config.projectId, ...params };
|
|
73
|
+
if (finalParams.sessionId && !finalParams.projectId) {
|
|
74
|
+
const errorResult = { error: 'sessionId cannot be provided without projectId' };
|
|
75
|
+
logger.warn(`memori_recall rejected: ${JSON.stringify(errorResult)}`);
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
78
|
+
details: null,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
logger.info(`memori_recall params: ${JSON.stringify(finalParams)}`);
|
|
82
|
+
const client = createRecallClient(config.apiKey, config.entityId);
|
|
83
|
+
const result = await client.agentRecall(finalParams);
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
86
|
+
details: null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
logger.warn(`memori_recall failed: ${String(e)}`);
|
|
91
|
+
const errorResult = { error: 'Recall failed' };
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: JSON.stringify(errorResult) }],
|
|
94
|
+
details: null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
2
|
+
import type { MemoriPluginConfig } from '../types.js';
|
|
3
|
+
import type { MemoriLogger } from '../utils/logger.js';
|
|
4
|
+
export interface ToolDeps {
|
|
5
|
+
api: OpenClawPluginApi;
|
|
6
|
+
config: MemoriPluginConfig;
|
|
7
|
+
logger: MemoriLogger;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { IntegrationMessage } from '@memorilabs/memori/integrations';
|
|
1
2
|
export interface MemoriPluginConfig {
|
|
2
3
|
apiKey: string;
|
|
3
4
|
entityId: string;
|
|
5
|
+
projectId: string;
|
|
4
6
|
}
|
|
5
7
|
export interface OpenClawMessageBlock {
|
|
6
8
|
type?: string;
|
|
@@ -44,13 +46,7 @@ export interface ExtractedToolCall {
|
|
|
44
46
|
result: unknown;
|
|
45
47
|
}
|
|
46
48
|
export interface ParsedTurn {
|
|
47
|
-
userMessage:
|
|
48
|
-
|
|
49
|
-
content: string;
|
|
50
|
-
} | null;
|
|
51
|
-
assistantMessage: {
|
|
52
|
-
role: string;
|
|
53
|
-
content: string;
|
|
54
|
-
} | null;
|
|
49
|
+
userMessage: IntegrationMessage | null;
|
|
50
|
+
assistantMessage: IntegrationMessage | null;
|
|
55
51
|
tools: ExtractedToolCall[];
|
|
56
52
|
}
|
package/dist/utils/context.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface ExtractedContext {
|
|
|
6
6
|
entityId: string;
|
|
7
7
|
sessionId: string;
|
|
8
8
|
provider: string;
|
|
9
|
+
projectId: string;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* Extracts and normalizes context information from OpenClaw event and context objects.
|
|
@@ -14,7 +15,8 @@ export interface ExtractedContext {
|
|
|
14
15
|
* @param event - OpenClaw event object
|
|
15
16
|
* @param ctx - OpenClaw context object
|
|
16
17
|
* @param configuredEntityId - Hardcoded entity ID from plugin config
|
|
17
|
-
* @
|
|
18
|
+
* @param configuredProjectId - Project ID from plugin config
|
|
19
|
+
* @returns Normalized context with entityId, sessionId, provider, and projectId
|
|
18
20
|
* @throws Error If entityId, sessionId, or provider cannot be determined
|
|
19
21
|
*/
|
|
20
|
-
export declare function extractContext(event: OpenClawEvent, ctx: OpenClawContext, configuredEntityId: string): ExtractedContext;
|
|
22
|
+
export declare function extractContext(event: OpenClawEvent, ctx: OpenClawContext, configuredEntityId: string, configuredProjectId: string): ExtractedContext;
|
package/dist/utils/context.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* @param event - OpenClaw event object
|
|
6
6
|
* @param ctx - OpenClaw context object
|
|
7
7
|
* @param configuredEntityId - Hardcoded entity ID from plugin config
|
|
8
|
-
* @
|
|
8
|
+
* @param configuredProjectId - Project ID from plugin config
|
|
9
|
+
* @returns Normalized context with entityId, sessionId, provider, and projectId
|
|
9
10
|
* @throws Error If entityId, sessionId, or provider cannot be determined
|
|
10
11
|
*/
|
|
11
|
-
export function extractContext(event, ctx, configuredEntityId) {
|
|
12
|
+
export function extractContext(event, ctx, configuredEntityId, configuredProjectId) {
|
|
12
13
|
const sessionId = ctx.sessionKey || event.sessionId;
|
|
13
14
|
const provider = ctx.messageProvider || event.messageProvider;
|
|
14
15
|
if (!sessionId) {
|
|
@@ -21,5 +22,6 @@ export function extractContext(event, ctx, configuredEntityId) {
|
|
|
21
22
|
entityId: configuredEntityId,
|
|
22
23
|
sessionId,
|
|
23
24
|
provider,
|
|
25
|
+
projectId: configuredProjectId,
|
|
24
26
|
};
|
|
25
27
|
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { extractContext, type ExtractedContext } from './context.js';
|
|
2
2
|
export { MemoriLogger } from './logger.js';
|
|
3
|
-
export { initializeMemoriClient } from './memori-client.js';
|
|
3
|
+
export { initializeMemoriClient, createRecallClient } from './memori-client.js';
|
|
4
|
+
export { loadSkillsContent } from './skills-loader.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { extractContext } from './context.js';
|
|
2
2
|
export { MemoriLogger } from './logger.js';
|
|
3
|
-
export { initializeMemoriClient } from './memori-client.js';
|
|
3
|
+
export { initializeMemoriClient, createRecallClient } from './memori-client.js';
|
|
4
|
+
export { loadSkillsContent } from './skills-loader.js';
|
|
@@ -8,3 +8,14 @@ import { ExtractedContext } from './context.js';
|
|
|
8
8
|
* @returns Configured OpenClawIntegration instance
|
|
9
9
|
*/
|
|
10
10
|
export declare function initializeMemoriClient(apiKey: string, context: ExtractedContext): OpenClawIntegration;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a minimal Memori client scoped only to an entity, with no session or project
|
|
13
|
+
* context pre-set. Intended for use in tool execute handlers where OpenClaw does not
|
|
14
|
+
* reliably provide session context — callers supply projectId/sessionId as explicit
|
|
15
|
+
* parameters instead.
|
|
16
|
+
*
|
|
17
|
+
* @param apiKey - Memori API key
|
|
18
|
+
* @param entityId - Entity ID for attribution
|
|
19
|
+
* @returns Configured OpenClawIntegration instance
|
|
20
|
+
*/
|
|
21
|
+
export declare function createRecallClient(apiKey: string, entityId: string): OpenClawIntegration;
|
|
@@ -11,7 +11,25 @@ export function initializeMemoriClient(apiKey, context) {
|
|
|
11
11
|
const memori = new Memori();
|
|
12
12
|
memori.config.apiKey = apiKey;
|
|
13
13
|
const openclaw = memori.integrate(OpenClawIntegration);
|
|
14
|
-
openclaw
|
|
15
|
-
|
|
14
|
+
openclaw
|
|
15
|
+
.scope(context.sessionId, context.projectId)
|
|
16
|
+
.attribution(context.entityId, context.provider);
|
|
17
|
+
return openclaw;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a minimal Memori client scoped only to an entity, with no session or project
|
|
21
|
+
* context pre-set. Intended for use in tool execute handlers where OpenClaw does not
|
|
22
|
+
* reliably provide session context — callers supply projectId/sessionId as explicit
|
|
23
|
+
* parameters instead.
|
|
24
|
+
*
|
|
25
|
+
* @param apiKey - Memori API key
|
|
26
|
+
* @param entityId - Entity ID for attribution
|
|
27
|
+
* @returns Configured OpenClawIntegration instance
|
|
28
|
+
*/
|
|
29
|
+
export function createRecallClient(apiKey, entityId) {
|
|
30
|
+
const memori = new Memori();
|
|
31
|
+
memori.config.apiKey = apiKey;
|
|
32
|
+
const openclaw = memori.integrate(OpenClawIntegration);
|
|
33
|
+
openclaw.attribution(entityId);
|
|
16
34
|
return openclaw;
|
|
17
35
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the Memori skills document at plugin registration time.
|
|
3
|
+
* Returns an empty string if the file cannot be read so the plugin
|
|
4
|
+
* degrades gracefully rather than failing to register.
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadSkillsContent(resolvePath: (input: string) => string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Loads the Memori skills document at plugin registration time.
|
|
4
|
+
* Returns an empty string if the file cannot be read so the plugin
|
|
5
|
+
* degrades gracefully rather than failing to register.
|
|
6
|
+
*/
|
|
7
|
+
export function loadSkillsContent(resolvePath) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(resolvePath('skills/memori/skills.md'), 'utf-8');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.0.
|
|
1
|
+
export declare const SDK_VERSION = "0.0.9";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = '0.0.
|
|
1
|
+
export const SDK_VERSION = '0.0.9';
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-memori",
|
|
3
3
|
"name": "Memori System",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.8",
|
|
5
5
|
"description": "Hosted memory backend",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
|
+
"contracts": {
|
|
9
|
+
"tools": ["memori_recall", "memori_recall_summary", "memori_feedback"]
|
|
10
|
+
},
|
|
8
11
|
"uiHints": {
|
|
9
12
|
"apiKey": {
|
|
10
13
|
"label": "Memori API Key",
|
|
@@ -16,6 +19,11 @@
|
|
|
16
19
|
"label": "Entity ID",
|
|
17
20
|
"placeholder": "e.g., your-app-user-id",
|
|
18
21
|
"help": "Required. The unique identifier to attribute these memories to."
|
|
22
|
+
},
|
|
23
|
+
"projectId": {
|
|
24
|
+
"label": "Project ID",
|
|
25
|
+
"placeholder": "e.g., my-project",
|
|
26
|
+
"help": "Required. Scopes all memories to this project."
|
|
19
27
|
}
|
|
20
28
|
},
|
|
21
29
|
"configSchema": {
|
|
@@ -29,7 +37,12 @@
|
|
|
29
37
|
"entityId": {
|
|
30
38
|
"type": "string",
|
|
31
39
|
"title": "Entity ID",
|
|
32
|
-
"description": "Required.
|
|
40
|
+
"description": "Required. The unique identifier to attribute these memories to."
|
|
41
|
+
},
|
|
42
|
+
"projectId": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"title": "Project ID",
|
|
45
|
+
"description": "Required. Scopes all memories to this project."
|
|
33
46
|
}
|
|
34
47
|
},
|
|
35
48
|
"required": []
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memorilabs/openclaw-memori",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Official MemoriLabs.ai long-term memory plugin for OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
|
+
"skills",
|
|
10
11
|
"openclaw.plugin.json"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
@@ -66,6 +67,6 @@
|
|
|
66
67
|
"@hono/node-server": "^1.19.10"
|
|
67
68
|
},
|
|
68
69
|
"dependencies": {
|
|
69
|
-
"@memorilabs/memori": "
|
|
70
|
+
"@memorilabs/memori": "0.1.11-beta"
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
## Memori — Your Persistent Memory Layer
|
|
2
|
+
|
|
3
|
+
You have access to Memori, a structured long-term memory backend.
|
|
4
|
+
|
|
5
|
+
**Automatic augmentation** (`agent_end`): After you respond, the conversation turn is automatically sent to Memori to extract and store facts, preferences, decisions, and relationships for future sessions. You do not need to do this manually.
|
|
6
|
+
|
|
7
|
+
**Manual Recall (IMPORTANT)**: You do NOT automatically receive context from past sessions.
|
|
8
|
+
**RULE:** You must NEVER say "I don't know" about the user, their preferences, or past events without FIRST running a `memori_recall` search to check if you remember it. If a user asks a question about themselves, you MUST search Memori before responding.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
### Memory Retrieval Tools
|
|
13
|
+
|
|
14
|
+
Use these to search your memory explicitly:
|
|
15
|
+
|
|
16
|
+
**`memori_recall`** — Fetch granular memory facts using a search query and optional filters. Use this when you need specific details (e.g., "what database did we choose?").
|
|
17
|
+
|
|
18
|
+
| Parameter | Type | Description |
|
|
19
|
+
| ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `query` | string | **Required.** A natural language semantic search query (e.g. "dogs"). **DO NOT use wildcards like `*`.** |
|
|
21
|
+
| `dateStart` | string | ISO 8601 (MUST be UTC) — memories on or after this time |
|
|
22
|
+
| `dateEnd` | string | ISO 8601 (MUST be UTC) — memories on or before this time |
|
|
23
|
+
| `projectId` | string | CRITICAL: Leave EMPTY to use the current project. ONLY provide a value if the user explicitly names a different project. |
|
|
24
|
+
| `sessionId` | string | Scope to a specific session — **requires `projectId`** |
|
|
25
|
+
| `signal` | string | Filter by signal type. Allowed values: `commit`, `discovery`, `failure`, `inference`, `pattern`, `result`, `update`, `verification`. |
|
|
26
|
+
| `source` | string | Filter by source origin. Allowed values: `constraint`, `decision`, `execution`, `fact`, `insight`, `instruction`, `status`, `strategy`, `task`. |
|
|
27
|
+
|
|
28
|
+
**`memori_recall_summary`** — Fetch summarized views of stored memories. **RULE:** You must NEVER guess or make up a status update, daily brief, or project overview. If the user asks "what did we do last time" or "give me a summary", you MUST use this tool before answering.
|
|
29
|
+
|
|
30
|
+
| Parameter | Type | Description |
|
|
31
|
+
| ----------- | ------ | ------------------------------------------------------------------------------------------------------------------------ |
|
|
32
|
+
| `dateStart` | string | ISO 8601 (MUST be UTC) — summaries on or after this time |
|
|
33
|
+
| `dateEnd` | string | ISO 8601 (MUST be UTC) — summaries on or before this time |
|
|
34
|
+
| `projectId` | string | CRITICAL: Leave EMPTY to use the current project. ONLY provide a value if the user explicitly names a different project. |
|
|
35
|
+
| `sessionId` | string | Scope to a specific session — **requires `projectId`** |
|
|
36
|
+
|
|
37
|
+
> `sessionId` cannot be used without `projectId`. The backend will reject it.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### Feedback Tool
|
|
42
|
+
|
|
43
|
+
**RULE:** You must ALWAYS use this tool immediately if the user asks you to send feedback, report a bug, suggest a feature, or complains about the system. Do NOT just say "I will let the developers know"—you must actually execute the tool to send the message.
|
|
44
|
+
|
|
45
|
+
**`memori_feedback`** — Send feedback, suggestions, or issues directly to the Memori team.
|
|
46
|
+
|
|
47
|
+
| Parameter | Type | Description |
|
|
48
|
+
| --------- | ------ | ------------------------------------------------------------------------ |
|
|
49
|
+
| `content` | string | **Required.** The feedback text to send (positive, negative, bugs, etc.) |
|
|
50
|
+
|
|
51
|
+
### Memory Scoping
|
|
52
|
+
|
|
53
|
+
All memories are scoped to the current `entityId` and `projectId`. The current project is applied by default — you only need to pass `projectId` when explicitly overriding it for a cross-project lookup.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### Coexistence With File Memory
|
|
58
|
+
|
|
59
|
+
Memori works alongside local file memory (e.g., `MEMORY.md`), it does not replace it:
|
|
60
|
+
|
|
61
|
+
| Layer | Scope | Lifetime |
|
|
62
|
+
| ------------------------- | ------------------------------------- | --------------------------- |
|
|
63
|
+
| Session context | Current conversation | Dies with session |
|
|
64
|
+
| File memory (`MEMORY.md`) | Curated strategic facts | Persistent on disk |
|
|
65
|
+
| Memori | Auto-extracted facts, knowledge graph | Cloud — survives compaction |
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { OpenClawEvent, OpenClawContext, MemoriPluginConfig } from '../types.js';
|
|
2
|
-
import { MemoriLogger } from '../utils/index.js';
|
|
3
|
-
export declare function handleRecall(event: OpenClawEvent, ctx: OpenClawContext, config: MemoriPluginConfig, logger: MemoriLogger): Promise<{
|
|
4
|
-
prependContext: string;
|
|
5
|
-
} | undefined>;
|
package/dist/handlers/recall.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { cleanText, isSystemMessage } from '../sanitizer.js';
|
|
2
|
-
import { RECALL_CONFIG } from '../constants.js';
|
|
3
|
-
import { extractContext, initializeMemoriClient } from '../utils/index.js';
|
|
4
|
-
export async function handleRecall(event, ctx, config, logger) {
|
|
5
|
-
logger.section('RECALL HOOK START');
|
|
6
|
-
try {
|
|
7
|
-
const context = extractContext(event, ctx, config.entityId);
|
|
8
|
-
const promptText = cleanText(event.prompt);
|
|
9
|
-
if (!promptText ||
|
|
10
|
-
promptText.length < RECALL_CONFIG.MIN_PROMPT_LENGTH ||
|
|
11
|
-
isSystemMessage(promptText)) {
|
|
12
|
-
logger.info('Prompt too short or is a system message. Aborting recall.');
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
15
|
-
const memoriClient = initializeMemoriClient(config.apiKey, context);
|
|
16
|
-
logger.info('Executing SDK Recall...');
|
|
17
|
-
const recallText = await memoriClient.recall(promptText);
|
|
18
|
-
const hookReturn = recallText ? { prependContext: recallText } : undefined;
|
|
19
|
-
if (hookReturn) {
|
|
20
|
-
logger.info('Successfully injected memory context.');
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
logger.info('No relevant memories found.');
|
|
24
|
-
}
|
|
25
|
-
return hookReturn;
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
logger.error(`Recall failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
logger.endSection('RECALL HOOK END');
|
|
33
|
-
}
|
|
34
|
-
}
|