@memorilabs/openclaw-memori 0.0.8 → 0.0.10
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 +118 -91
- 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 +12 -0
- package/dist/tools/memori-feedback.d.ts +25 -0
- package/dist/tools/memori-feedback.js +40 -0
- package/dist/tools/memori-quota.d.ts +17 -0
- package/dist/tools/memori-quota.js +55 -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/memori-signup.d.ts +25 -0
- package/dist/tools/memori-signup.js +72 -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/clawhub/SKILL.md +313 -0
- package/skills/memori/SKILL.md +284 -0
- package/dist/handlers/recall.d.ts +0 -5
- package/dist/handlers/recall.js +0 -34
package/README.md
CHANGED
|
@@ -25,154 +25,181 @@
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
# Memori for OpenClaw
|
|
29
29
|
|
|
30
|
-
OpenClaw
|
|
30
|
+
Memori gives OpenClaw agents a structured, long-term memory system. It automatically captures what happens and lets agents recall it on demand — so context survives across sessions without bloating the prompt.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Instead of relying solely on natural-language memory, Memori structures persistent memory from both conversation and agent trace — the agent's actions, tool results, decisions, and outcomes — so it can recall what actually happened when it matters.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## The problem
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
OpenClaw's default memory works for simple use cases, but breaks at scale:
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
- Memory is stored as flat markdown files
|
|
41
|
+
- Context is lost due to compaction
|
|
42
|
+
- Important decisions and constraints disappear
|
|
43
|
+
- No relationships between facts
|
|
44
|
+
- Memory bleeds across users and projects
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
---
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
## What Memori changes
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
Memori replaces flat memory with structured, scoped memory built from:
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
- Agent execution (tool calls, results, decisions, outcomes)
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
Instead of replaying history, agents retrieve exactly what they need.
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
---
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
## How it works
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
Memori runs on two parallel systems:
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
### 1. Advanced augmentation
|
|
57
63
|
|
|
58
|
-
Memori
|
|
64
|
+
After each interaction, Memori converts raw session data into structured, reusable memories asynchronously.
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
- Transforms raw agent sessions into structured memory units
|
|
67
|
+
- Captures the agent's actions, reasoning, tool usage, responses, corrections, and failures
|
|
68
|
+
- Organizes into classes to enable efficient retrieval
|
|
69
|
+
- Generates embeddings for semantic retrieval
|
|
70
|
+
- Updates structured memory and the knowledge graph
|
|
61
71
|
|
|
62
|
-
|
|
72
|
+
This is how structured memory is continuously built and updated over time.
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
It runs **after the agent responds** and does not impact latency.
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
---
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
### 2. Agent-controlled recall
|
|
69
79
|
|
|
70
|
-
|
|
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. |
|
|
80
|
+
Recall is **explicit and initiated by the agent**.
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
Memori separates memory creation from memory recall:
|
|
78
83
|
|
|
84
|
+
- Creation is automatic (advanced augmentation)
|
|
85
|
+
- Recall is intentional (agent-controlled)
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
Agents decide:
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
- When to recall
|
|
90
|
+
- What scope to recall from
|
|
91
|
+
- How much history to include
|
|
92
|
+
|
|
93
|
+
Memori does not automatically inject memory into the prompt. The agent retrieves only the context it needs, keeping token usage efficient.
|
|
94
|
+
|
|
95
|
+
Available tools:
|
|
96
|
+
|
|
97
|
+
- **`memori_recall`** — query structured memory for facts, constraints, decisions, and patterns
|
|
98
|
+
- **`memori_recall_summary`** — retrieve summaries and the daily brief
|
|
99
|
+
- **`memori_feedback`** — report on memory quality to improve the system
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Quickstart
|
|
83
104
|
|
|
84
105
|
### Prerequisites
|
|
85
106
|
|
|
86
107
|
- [OpenClaw](https://openclaw.ai) `v2026.3.2` or later
|
|
87
108
|
- A Memori API key from [app.memorilabs.ai](https://app.memorilabs.ai)
|
|
88
|
-
- An Entity ID to
|
|
89
|
-
|
|
90
|
-
### 1. Install and Enable
|
|
109
|
+
- An Entity ID to scope memory to a specific user, agent, or system
|
|
110
|
+
- A Project ID to scope memory to a specific project or workspace
|
|
91
111
|
|
|
92
|
-
|
|
112
|
+
### 1. Install
|
|
93
113
|
|
|
94
114
|
```bash
|
|
95
|
-
# 1. Install the plugin from npm
|
|
96
115
|
openclaw plugins install @memorilabs/openclaw-memori
|
|
97
|
-
|
|
98
|
-
# 2. Enable it in your workspace
|
|
99
116
|
openclaw plugins enable openclaw-memori
|
|
100
|
-
|
|
101
|
-
# 3. Restart the OpenClaw gateway
|
|
102
|
-
openclaw gateway restart
|
|
103
117
|
```
|
|
104
118
|
|
|
105
119
|
### 2. Configure
|
|
106
120
|
|
|
107
|
-
|
|
121
|
+
```bash
|
|
122
|
+
openclaw memori init \
|
|
123
|
+
--api-key "YOUR_MEMORI_API_KEY" \
|
|
124
|
+
--entity-id "your-app-user-id" \
|
|
125
|
+
--project-id "my-project"
|
|
126
|
+
```
|
|
108
127
|
|
|
109
|
-
###
|
|
128
|
+
### 3. Verify
|
|
110
129
|
|
|
111
130
|
```bash
|
|
112
|
-
openclaw
|
|
113
|
-
openclaw
|
|
131
|
+
openclaw gateway restart
|
|
132
|
+
openclaw memori status --check
|
|
114
133
|
```
|
|
115
134
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
```json
|
|
121
|
-
{
|
|
122
|
-
"plugins": {
|
|
123
|
-
"entries": {
|
|
124
|
-
"openclaw-memori": {
|
|
125
|
-
"enabled": true,
|
|
126
|
-
"config": {
|
|
127
|
-
"apiKey": "your-memori-api-key",
|
|
128
|
-
"entityId": "your-app-user-id"
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
135
|
+
Expected:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
Status: Ready
|
|
134
139
|
```
|
|
135
140
|
|
|
136
|
-
###
|
|
141
|
+
### 4. Test the memory loop
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
| ---------- | -------- | -------- | --------------------------------------------------------------------------------------------------- |
|
|
140
|
-
| `apiKey` | `string` | **Yes** | Your Memori API key. |
|
|
141
|
-
| `entityId` | `string` | **Yes** | The unique identifier for the entity (e.g., user, agent, or tenant) to attribute these memories to. |
|
|
143
|
+
1. Tell the agent something durable:
|
|
142
144
|
|
|
143
|
-
|
|
145
|
+
> "I always use TypeScript and prefer functional patterns."
|
|
144
146
|
|
|
145
|
-
|
|
147
|
+
2. Start a new session and ask:
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
openclaw gateway restart
|
|
149
|
-
openclaw gateway logs --filter "[Memori]"
|
|
150
|
-
```
|
|
149
|
+
> "Write a hello world script in my preferred language."
|
|
151
150
|
|
|
152
|
-
|
|
151
|
+
3. Confirm the agent used `memori_recall` to fetch your preferences:
|
|
152
|
+
```
|
|
153
|
+
[Memori] memori_recall params: {"projectId":"my-project","query":"preferred programming language"}
|
|
154
|
+
```
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
If it works, you now have persistent memory across sessions.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Memory model
|
|
161
|
+
|
|
162
|
+
Memory is scoped to prevent noise and ensure relevance:
|
|
163
|
+
|
|
164
|
+
- `entity_id` → user, agent, or system context
|
|
165
|
+
- `project_id` → project or workspace context
|
|
166
|
+
- `session_id` → specific session (requires `project_id`)
|
|
167
|
+
- `date_start` / `date_end` → time-bounded recall (defaults to all-time if omitted)
|
|
168
|
+
- `source` → type of memory (recall only)
|
|
169
|
+
- `signal` → how the memory was derived (recall only)
|
|
170
|
+
|
|
171
|
+
All timestamps are stored in **UTC**.
|
|
158
172
|
|
|
159
|
-
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Agent behavior (read this)
|
|
176
|
+
|
|
177
|
+
Agents should:
|
|
160
178
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
- Retrieve a summary at the start of meaningful sessions
|
|
180
|
+
- Use targeted recall (not broad queries)
|
|
181
|
+
- Avoid recalling on every turn
|
|
182
|
+
- Use memory only when context is needed
|
|
183
|
+
- Send feedback when memory is missing or incorrect
|
|
184
|
+
|
|
185
|
+
See SKILL.md for full behavior guidelines.
|
|
186
|
+
|
|
187
|
+
---
|
|
169
188
|
|
|
170
|
-
##
|
|
189
|
+
## Typical workflow
|
|
171
190
|
|
|
172
|
-
|
|
191
|
+
1. Start session → retrieve summary
|
|
192
|
+
2. During task → targeted recall
|
|
193
|
+
3. Missing context → send feedback
|
|
194
|
+
4. End of session → memory is captured automatically
|
|
173
195
|
|
|
174
|
-
|
|
175
|
-
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Multi-agent ready
|
|
199
|
+
|
|
200
|
+
The plugin is fully stateless and thread-safe. You can run it across multiple agents in the same gateway without shared state or concurrency issues.
|
|
201
|
+
|
|
202
|
+
---
|
|
176
203
|
|
|
177
204
|
## Contributing
|
|
178
205
|
|
|
@@ -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,12 @@
|
|
|
1
|
+
import { createMemoriSignupTool } from './memori-signup.js';
|
|
2
|
+
import { createMemoriQuotaTool } from './memori-quota.js';
|
|
3
|
+
import { createMemoriRecallTool } from './memori-recall.js';
|
|
4
|
+
import { createMemoriRecallSummaryTool } from './memori-recall-summary.js';
|
|
5
|
+
import { createMemoriFeedbackTool } from './memori-feedback.js';
|
|
6
|
+
export function registerAllTools(deps) {
|
|
7
|
+
deps.api.registerTool(createMemoriSignupTool(deps));
|
|
8
|
+
deps.api.registerTool(createMemoriQuotaTool(deps));
|
|
9
|
+
deps.api.registerTool(createMemoriRecallTool(deps));
|
|
10
|
+
deps.api.registerTool(createMemoriRecallSummaryTool(deps));
|
|
11
|
+
deps.api.registerTool(createMemoriFeedbackTool(deps));
|
|
12
|
+
}
|
|
@@ -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
|
+
};
|