@evermind-ai/openclaw-plugin 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -328
- package/README.zh.md +98 -328
- package/SKILL.md +333 -0
- package/bin/install.js +354 -0
- package/index.js +152 -61
- package/openclaw.plugin.json +8 -8
- package/package.json +7 -2
- package/src/assembler.js +6 -6
- package/src/config.js +2 -2
- package/src/formatter.js +1 -1
- package/src/http-client.js +2 -1
- package/src/memory-api.js +18 -22
- package/src/subagent.js +3 -3
- package/src/compaction.js +0 -85
- package/src/context-engine.js +0 -283
- package/src/lifecycle.js +0 -65
package/SKILL.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: everos
|
|
3
|
+
version: 1.1.0
|
|
4
|
+
description: |
|
|
5
|
+
Install and configure EverOS for OpenClaw natural-language memory.
|
|
6
|
+
|
|
7
|
+
Use when users say:
|
|
8
|
+
- "install everos"
|
|
9
|
+
- "setup everos"
|
|
10
|
+
- "install everos plugin"
|
|
11
|
+
- "enable everos memory"
|
|
12
|
+
- "remember my preferences in OpenClaw"
|
|
13
|
+
author: EverMind
|
|
14
|
+
keywords:
|
|
15
|
+
- everos
|
|
16
|
+
- evermemos
|
|
17
|
+
- context engine
|
|
18
|
+
- persistent memory
|
|
19
|
+
- openclaw
|
|
20
|
+
- natural language memory
|
|
21
|
+
metadata:
|
|
22
|
+
openclaw:
|
|
23
|
+
emoji: "🧠"
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# EverOS
|
|
27
|
+
|
|
28
|
+
EverOS OpenClaw Plugin gives OpenClaw persistent memory through the **ContextEngine API**.
|
|
29
|
+
|
|
30
|
+
Important distinction:
|
|
31
|
+
|
|
32
|
+
- This is a `context-engine` plugin, not a `memory` slot plugin.
|
|
33
|
+
- Users do not need to call memory tools manually.
|
|
34
|
+
- Memory is triggered by normal conversation:
|
|
35
|
+
- before reply: relevant memory is recalled and injected
|
|
36
|
+
- after reply: new conversation content is saved back automatically
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Trigger phrases
|
|
41
|
+
|
|
42
|
+
Use this skill when the user wants to:
|
|
43
|
+
|
|
44
|
+
- install EverOS
|
|
45
|
+
- set up EverOS memory for OpenClaw
|
|
46
|
+
- enable long-term memory for OpenClaw
|
|
47
|
+
- make natural-language conversations persist across sessions
|
|
48
|
+
- configure the EverOS context engine
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## When to use this skill
|
|
53
|
+
|
|
54
|
+
Use this skill when the user wants:
|
|
55
|
+
|
|
56
|
+
- persistent memory across OpenClaw sessions
|
|
57
|
+
- automatic recall and save during normal conversation
|
|
58
|
+
- a self-hosted EverOS backend
|
|
59
|
+
- memory without manually calling `memory_store` / `memory_search`
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## When NOT to use this skill
|
|
64
|
+
|
|
65
|
+
Do not use this skill for:
|
|
66
|
+
|
|
67
|
+
- cloud-only `mem9` onboarding
|
|
68
|
+
- troubleshooting unrelated OpenClaw issues
|
|
69
|
+
- temporary context that only matters in the current session
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## What the user gets
|
|
74
|
+
|
|
75
|
+
Automatic lifecycle behavior:
|
|
76
|
+
|
|
77
|
+
| Hook | Trigger | What happens |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `bootstrap()` | Session starts | Backend health check and session state init |
|
|
80
|
+
| `assemble()` | Before each turn | Searches relevant memory and injects it as context |
|
|
81
|
+
| `afterTurn()` | After each turn | Saves new messages from the turn |
|
|
82
|
+
| `compact()` | Compaction check | Participates in token-budget decisions |
|
|
83
|
+
| `dispose()` | Session ends | Clears in-memory session state |
|
|
84
|
+
|
|
85
|
+
User-facing result:
|
|
86
|
+
|
|
87
|
+
- the user chats naturally
|
|
88
|
+
- EverOS recalls relevant past context automatically
|
|
89
|
+
- EverOS stores new useful context automatically
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Definition of Done
|
|
94
|
+
|
|
95
|
+
This task is not complete until all of the following are true:
|
|
96
|
+
|
|
97
|
+
1. the EverOS backend is reachable or the user has been explicitly told it still needs to be started
|
|
98
|
+
2. the plugin is installed
|
|
99
|
+
3. `openclaw.json` is configured correctly
|
|
100
|
+
4. OpenClaw has been restarted
|
|
101
|
+
5. the user has been given a natural-language verification step
|
|
102
|
+
6. the full final handoff message has been sent
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Onboarding
|
|
107
|
+
|
|
108
|
+
### Step 0 - Set expectations
|
|
109
|
+
|
|
110
|
+
Before changing anything, tell the user:
|
|
111
|
+
|
|
112
|
+
> OpenClaw will need a restart during setup. After restart, wait about 1 minute, then send a short natural-language message here to verify memory.
|
|
113
|
+
|
|
114
|
+
Do not skip this notice if you are going to restart OpenClaw automatically.
|
|
115
|
+
|
|
116
|
+
### Step 1 - Check the EverOS backend
|
|
117
|
+
|
|
118
|
+
Default backend:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
curl -sf http://localhost:1995/health && echo "OK" || echo "NOT_RUNNING"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If the backend is healthy, continue.
|
|
125
|
+
|
|
126
|
+
If it is not healthy, tell the user plainly:
|
|
127
|
+
|
|
128
|
+
> The EverOS backend is not reachable yet. I can still finish plugin installation now, but automatic recall/save will not work until the backend is running.
|
|
129
|
+
|
|
130
|
+
If the user asks how to start the backend, give the shortest useful path:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/EverMind-AI/EverMemOS.git
|
|
134
|
+
cd EverMemOS
|
|
135
|
+
docker compose up -d
|
|
136
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
137
|
+
uv sync
|
|
138
|
+
cp env.template .env
|
|
139
|
+
# edit .env
|
|
140
|
+
uv run python src/run.py
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Step 2 - Install plugin
|
|
144
|
+
|
|
145
|
+
Preferred one-shot install:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx --yes --package @evermind-ai/openclaw-plugin everos-install
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Manual alternative:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm install -g @evermind-ai/openclaw-plugin
|
|
155
|
+
everos-install
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
What the installer does:
|
|
159
|
+
|
|
160
|
+
- adds the plugin path to `plugins.load.paths`
|
|
161
|
+
- adds `@evermind-ai/openclaw-plugin` to `plugins.allow`
|
|
162
|
+
- sets `plugins.slots.contextEngine = "@evermind-ai/openclaw-plugin"`
|
|
163
|
+
- sets `plugins.slots.memory = "none"` to avoid slot conflicts
|
|
164
|
+
- creates or updates `plugins.entries["@evermind-ai/openclaw-plugin"]`
|
|
165
|
+
|
|
166
|
+
### Step 3 - Manual config fallback
|
|
167
|
+
|
|
168
|
+
If the installer is unavailable, patch `~/.openclaw/openclaw.json` manually.
|
|
169
|
+
|
|
170
|
+
Expected config shape:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"plugins": {
|
|
175
|
+
"allow": ["@evermind-ai/openclaw-plugin"],
|
|
176
|
+
"slots": {
|
|
177
|
+
"memory": "none",
|
|
178
|
+
"contextEngine": "@evermind-ai/openclaw-plugin"
|
|
179
|
+
},
|
|
180
|
+
"entries": {
|
|
181
|
+
"@evermind-ai/openclaw-plugin": {
|
|
182
|
+
"enabled": true,
|
|
183
|
+
"config": {
|
|
184
|
+
"baseUrl": "http://localhost:1995",
|
|
185
|
+
"userId": "everos-user",
|
|
186
|
+
"groupId": "everos-group",
|
|
187
|
+
"topK": 5,
|
|
188
|
+
"memoryTypes": ["episodic_memory", "profile", "agent_skill", "agent_case"],
|
|
189
|
+
"retrieveMethod": "hybrid"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Merge-safe patch:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
jq '
|
|
201
|
+
.plugins = (.plugins // {}) |
|
|
202
|
+
.plugins.load = (.plugins.load // {}) |
|
|
203
|
+
.plugins.load.paths = ((.plugins.load.paths // []) + ["/path/to/evermemos-openclaw-plugin"] | unique) |
|
|
204
|
+
.plugins.allow = ((.plugins.allow // []) + ["@evermind-ai/openclaw-plugin"] | unique) |
|
|
205
|
+
.plugins.slots = (.plugins.slots // {}) |
|
|
206
|
+
.plugins.slots.memory = "none" |
|
|
207
|
+
.plugins.slots.contextEngine = "@evermind-ai/openclaw-plugin" |
|
|
208
|
+
.plugins.entries = (.plugins.entries // {}) |
|
|
209
|
+
.plugins.entries["@evermind-ai/openclaw-plugin"].enabled = true |
|
|
210
|
+
.plugins.entries["@evermind-ai/openclaw-plugin"].config = (
|
|
211
|
+
(.plugins.entries["@evermind-ai/openclaw-plugin"].config // {}) + {
|
|
212
|
+
"baseUrl": "http://localhost:1995",
|
|
213
|
+
"userId": "everos-user",
|
|
214
|
+
"groupId": "everos-group",
|
|
215
|
+
"topK": 5,
|
|
216
|
+
"memoryTypes": ["episodic_memory", "profile", "agent_skill", "agent_case"],
|
|
217
|
+
"retrieveMethod": "hybrid"
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
' ~/.openclaw/openclaw.json > tmp.json && mv tmp.json ~/.openclaw/openclaw.json
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Step 4 - Restart OpenClaw
|
|
224
|
+
|
|
225
|
+
Restart command:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
openclaw gateway restart
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Immediately before restart, tell the user:
|
|
232
|
+
|
|
233
|
+
> EverOS is installed. I am restarting OpenClaw now. After about 1 minute, send a short message so we can verify memory recall.
|
|
234
|
+
|
|
235
|
+
### Step 5 - Verify
|
|
236
|
+
|
|
237
|
+
Verification has two parts.
|
|
238
|
+
|
|
239
|
+
Backend:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
curl http://localhost:1995/health
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
User-facing natural-language test:
|
|
246
|
+
|
|
247
|
+
> Say: "Remember: I like espresso."
|
|
248
|
+
>
|
|
249
|
+
> Then ask: "What coffee do I like?"
|
|
250
|
+
|
|
251
|
+
This is the preferred validation because it checks the real user flow instead of just config.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Final handoff
|
|
256
|
+
|
|
257
|
+
After successful setup, send this handoff message in the user's language.
|
|
258
|
+
Do not remove sections.
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
✅ EverOS is ready.
|
|
262
|
+
|
|
263
|
+
🧭 WHAT YOU CAN DO NEXT
|
|
264
|
+
|
|
265
|
+
From now on, you can use normal natural language to make OpenClaw remember information.
|
|
266
|
+
You do not need to call memory tools manually.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
- "Remember: I like espresso."
|
|
270
|
+
- "Remember: this project uses PostgreSQL by default."
|
|
271
|
+
- "My coding style prefers small functions and explicit naming."
|
|
272
|
+
|
|
273
|
+
Later you can ask:
|
|
274
|
+
- "What coffee do I like?"
|
|
275
|
+
- "What database does this project use by default?"
|
|
276
|
+
|
|
277
|
+
⚙️ CURRENT CONNECTION
|
|
278
|
+
|
|
279
|
+
EverOS backend:
|
|
280
|
+
BASE_URL: <base-url>
|
|
281
|
+
|
|
282
|
+
OpenClaw config file:
|
|
283
|
+
~/.openclaw/openclaw.json
|
|
284
|
+
|
|
285
|
+
♻️ RECOVERY
|
|
286
|
+
|
|
287
|
+
1. Keep your EverOS backend data and configuration
|
|
288
|
+
2. Reinstall this plugin on the new machine
|
|
289
|
+
3. Write the same `baseUrl`, `userId`, and `groupId` back into `openclaw.json`
|
|
290
|
+
4. Restart OpenClaw to reconnect to the same memory space
|
|
291
|
+
|
|
292
|
+
📦 BACKUP
|
|
293
|
+
|
|
294
|
+
- Back up `~/.openclaw/openclaw.json`
|
|
295
|
+
- Back up the EverOS backend data directory or database
|
|
296
|
+
- Back up the EverMemOS `.env` and deployment configuration
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Troubleshooting
|
|
302
|
+
|
|
303
|
+
| Symptom | Fix |
|
|
304
|
+
| --- | --- |
|
|
305
|
+
| Plugin not loading | Check `plugins.allow`, `plugins.load.paths`, and `plugins.slots.contextEngine` |
|
|
306
|
+
| Backend unhealthy | Check `baseUrl` and ensure the EverOS backend is running |
|
|
307
|
+
| No recall | Verify the backend contains memories and the query is meaningful |
|
|
308
|
+
| No save | Verify `afterTurn()` is running and backend write API is reachable |
|
|
309
|
+
| Memory plugin conflict | Make sure `plugins.slots.memory = "none"` |
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## API reference
|
|
314
|
+
|
|
315
|
+
Base: `http://localhost:1995`
|
|
316
|
+
|
|
317
|
+
| Method | Path | Description |
|
|
318
|
+
| --- | --- | --- |
|
|
319
|
+
| GET | `/health` | Health check |
|
|
320
|
+
| POST | `/api/v1/memories` | Save memory |
|
|
321
|
+
| GET | `/api/v1/memories/search` | Search memory |
|
|
322
|
+
| DELETE | `/api/v1/memories` | Delete memory |
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Communication style
|
|
327
|
+
|
|
328
|
+
When talking to users:
|
|
329
|
+
|
|
330
|
+
- say this is automatic natural-language memory
|
|
331
|
+
- do not describe it as a `memory` slot plugin
|
|
332
|
+
- keep the next step concrete: restart, then try one short memory sentence
|
|
333
|
+
- prefer real conversational verification over low-level API demos
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EverOS OpenClaw Plugin Installer
|
|
5
|
+
* Configures OpenClaw to use the EverOS backend through a ContextEngine.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { exec } from "node:child_process";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import readline from "node:readline";
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
|
|
17
|
+
const HOME_DIR = process.env.HOME || process.env.USERPROFILE;
|
|
18
|
+
const CONFIG_PATH = path.join(HOME_DIR, ".openclaw", "openclaw.json");
|
|
19
|
+
const PLUGIN_ID = "@evermind-ai/openclaw-plugin";
|
|
20
|
+
const PLUGIN_DIR = path.join(__dirname, "..");
|
|
21
|
+
const STABLE_PLUGIN_DIR = path.join(HOME_DIR, ".openclaw", "plugins", "evermind-ai-openclaw-plugin");
|
|
22
|
+
const DEFAULT_CONFIG = {
|
|
23
|
+
baseUrl: "http://localhost:1995",
|
|
24
|
+
userId: "everos-user",
|
|
25
|
+
groupId: "everos-group",
|
|
26
|
+
topK: 5,
|
|
27
|
+
memoryTypes: ["episodic_memory", "profile", "agent_skill", "agent_case"],
|
|
28
|
+
retrieveMethod: "hybrid",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const colors = {
|
|
32
|
+
reset: "\x1b[0m",
|
|
33
|
+
green: "\x1b[32m",
|
|
34
|
+
yellow: "\x1b[33m",
|
|
35
|
+
blue: "\x1b[34m",
|
|
36
|
+
red: "\x1b[31m",
|
|
37
|
+
cyan: "\x1b[36m",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function log(message, color = "reset") {
|
|
41
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function success(message) {
|
|
45
|
+
log(`✓ ${message}`, "green");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function error(message) {
|
|
49
|
+
log(`✗ ${message}`, "red");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function info(message) {
|
|
53
|
+
log(`ℹ ${message}`, "cyan");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function warn(message) {
|
|
57
|
+
log(`⚠ ${message}`, "yellow");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hr() {
|
|
61
|
+
log("-".repeat(60), "blue");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const rl = readline.createInterface({
|
|
65
|
+
input: process.stdin,
|
|
66
|
+
output: process.stdout,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
async function prompt(question) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
rl.question(question, (answer) => {
|
|
72
|
+
resolve(answer.trim());
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function promptWithDefault(label, defaultValue) {
|
|
78
|
+
const answer = await prompt(`${label} (default: ${defaultValue}): `);
|
|
79
|
+
return answer || defaultValue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function closeAndExit(code) {
|
|
83
|
+
rl.close();
|
|
84
|
+
process.exit(code);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function checkBackendHealth(baseUrl) {
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(`${baseUrl.replace(/\/*$/, "")}/health`, {
|
|
90
|
+
signal: AbortSignal.timeout(5000),
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
return { ok: false, reason: `HTTP ${response.status}` };
|
|
94
|
+
}
|
|
95
|
+
const data = await response.json().catch(() => null);
|
|
96
|
+
return {
|
|
97
|
+
ok: data?.status === "healthy" || data?.status === "ok" || response.ok,
|
|
98
|
+
status: data?.status,
|
|
99
|
+
};
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return { ok: false, reason: err.message };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isDevelopmentCheckout(dir) {
|
|
106
|
+
return fs.existsSync(path.join(dir, ".git"));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ensureStablePluginPath() {
|
|
110
|
+
if (isDevelopmentCheckout(PLUGIN_DIR)) {
|
|
111
|
+
info(`Using local development checkout: ${PLUGIN_DIR}`);
|
|
112
|
+
return PLUGIN_DIR;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (path.resolve(PLUGIN_DIR) === path.resolve(STABLE_PLUGIN_DIR)) {
|
|
116
|
+
return STABLE_PLUGIN_DIR;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fs.mkdirSync(path.dirname(STABLE_PLUGIN_DIR), { recursive: true });
|
|
120
|
+
fs.cpSync(PLUGIN_DIR, STABLE_PLUGIN_DIR, {
|
|
121
|
+
recursive: true,
|
|
122
|
+
force: true,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const installerPath = path.join(STABLE_PLUGIN_DIR, "bin", "install.js");
|
|
126
|
+
if (fs.existsSync(installerPath)) {
|
|
127
|
+
fs.chmodSync(installerPath, 0o755);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
info(`Installed plugin files to ${STABLE_PLUGIN_DIR}`);
|
|
131
|
+
return STABLE_PLUGIN_DIR;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isLegacyPluginPath(entry, pluginPath) {
|
|
135
|
+
if (typeof entry !== "string") return false;
|
|
136
|
+
if (path.resolve(entry) === path.resolve(pluginPath)) return false;
|
|
137
|
+
|
|
138
|
+
const normalized = entry.replace(/\\/g, "/");
|
|
139
|
+
return normalized.includes("evermemos-openclaw-plugin") ||
|
|
140
|
+
normalized.includes("evermind-ai-openclaw-plugin") ||
|
|
141
|
+
normalized.includes("@evermind-ai/openclaw-plugin");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function loadConfig() {
|
|
145
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
146
|
+
return { exists: false, data: null };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
return { exists: true, data: JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")) };
|
|
151
|
+
} catch (err) {
|
|
152
|
+
error(`Failed to parse ${CONFIG_PATH}: ${err.message}`);
|
|
153
|
+
error("Please fix the JSON syntax before continuing.");
|
|
154
|
+
error("Installation aborted to avoid corrupting your configuration.");
|
|
155
|
+
return { exists: true, error: err.message };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function saveConfig(config) {
|
|
160
|
+
try {
|
|
161
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
162
|
+
fs.copyFileSync(CONFIG_PATH, `${CONFIG_PATH}.bak`);
|
|
163
|
+
}
|
|
164
|
+
fs.writeFileSync(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
165
|
+
return true;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
error(`Failed to save config: ${err.message}`);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ensureConfigShape(config) {
|
|
173
|
+
config.plugins = config.plugins || {};
|
|
174
|
+
config.plugins.load = config.plugins.load || {};
|
|
175
|
+
config.plugins.load.paths = Array.isArray(config.plugins.load.paths) ? config.plugins.load.paths : [];
|
|
176
|
+
config.plugins.allow = Array.isArray(config.plugins.allow) ? config.plugins.allow : [];
|
|
177
|
+
config.plugins.slots = config.plugins.slots || {};
|
|
178
|
+
config.plugins.entries = config.plugins.entries || {};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function mergePluginConfig(existingConfig, overrides = {}) {
|
|
182
|
+
return {
|
|
183
|
+
...DEFAULT_CONFIG,
|
|
184
|
+
...(existingConfig || {}),
|
|
185
|
+
...overrides,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function printSummary(pluginPath, entry) {
|
|
190
|
+
hr();
|
|
191
|
+
log("Installation Summary", "blue");
|
|
192
|
+
log(` Plugin ID: ${PLUGIN_ID}`);
|
|
193
|
+
log(` Plugin path: ${pluginPath}`);
|
|
194
|
+
log(` Config file: ${CONFIG_PATH}`);
|
|
195
|
+
log(` Backend URL: ${entry.config.baseUrl}`);
|
|
196
|
+
log(` User ID: ${entry.config.userId}`);
|
|
197
|
+
log(` Group ID: ${entry.config.groupId}`);
|
|
198
|
+
log(" Mode: context-engine (natural language auto memory)");
|
|
199
|
+
hr();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function printNextSteps(entry) {
|
|
203
|
+
log("Next Steps", "green");
|
|
204
|
+
log(" 1. Make sure your EverOS backend is running.");
|
|
205
|
+
log(` curl ${entry.config.baseUrl}/health`, "cyan");
|
|
206
|
+
log("");
|
|
207
|
+
log(" 2. Restart OpenClaw so the context engine can load.");
|
|
208
|
+
log(" openclaw gateway restart", "cyan");
|
|
209
|
+
log("");
|
|
210
|
+
log(" 3. Verify with natural language.");
|
|
211
|
+
log(' Say: "Remember: I like espresso."', "cyan");
|
|
212
|
+
log(' Then ask: "What coffee do I like?"', "cyan");
|
|
213
|
+
hr();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function restartGateway() {
|
|
217
|
+
return new Promise((resolve) => {
|
|
218
|
+
exec("openclaw gateway restart", (err) => {
|
|
219
|
+
if (err) {
|
|
220
|
+
warn(`Could not restart OpenClaw automatically: ${err.message}`);
|
|
221
|
+
info("Please restart manually: openclaw gateway restart");
|
|
222
|
+
} else {
|
|
223
|
+
success("OpenClaw gateway restarted.");
|
|
224
|
+
info('After about 1 minute, send a natural language test such as "Remember: I like espresso." to verify recall.');
|
|
225
|
+
}
|
|
226
|
+
resolve();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function install() {
|
|
232
|
+
hr();
|
|
233
|
+
log("EverOS OpenClaw Plugin Installer", "blue");
|
|
234
|
+
log("This keeps your current ContextEngine flow and enables automatic memory recall/save.");
|
|
235
|
+
hr();
|
|
236
|
+
|
|
237
|
+
const configResult = loadConfig();
|
|
238
|
+
if (configResult.error) {
|
|
239
|
+
closeAndExit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let config;
|
|
243
|
+
if (!configResult.exists) {
|
|
244
|
+
warn(`OpenClaw config not found at ${CONFIG_PATH}`);
|
|
245
|
+
const answer = await prompt("Create a new OpenClaw config now? (Y/n): ");
|
|
246
|
+
if (answer.toLowerCase() === "n") {
|
|
247
|
+
info("Installation cancelled.");
|
|
248
|
+
closeAndExit(0);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
252
|
+
config = {};
|
|
253
|
+
} else {
|
|
254
|
+
config = configResult.data;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
ensureConfigShape(config);
|
|
258
|
+
|
|
259
|
+
const pluginPath = ensureStablePluginPath();
|
|
260
|
+
config.plugins.load.paths = config.plugins.load.paths.filter((entry) => {
|
|
261
|
+
if (isLegacyPluginPath(entry, pluginPath)) {
|
|
262
|
+
warn(`Removing old plugin path: ${entry}`);
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!config.plugins.load.paths.includes(pluginPath)) {
|
|
269
|
+
config.plugins.load.paths.push(pluginPath);
|
|
270
|
+
success(`Added plugin path: ${pluginPath}`);
|
|
271
|
+
} else {
|
|
272
|
+
info(`Plugin path already configured: ${pluginPath}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!config.plugins.allow.includes(PLUGIN_ID)) {
|
|
276
|
+
config.plugins.allow.push(PLUGIN_ID);
|
|
277
|
+
success(`Added to allow list: ${PLUGIN_ID}`);
|
|
278
|
+
} else {
|
|
279
|
+
info(`Plugin already allowed: ${PLUGIN_ID}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (config.plugins.slots.contextEngine !== PLUGIN_ID) {
|
|
283
|
+
const previous = config.plugins.slots.contextEngine || "none";
|
|
284
|
+
config.plugins.slots.contextEngine = PLUGIN_ID;
|
|
285
|
+
success(`Set contextEngine slot: ${previous} -> ${PLUGIN_ID}`);
|
|
286
|
+
} else {
|
|
287
|
+
info(`contextEngine slot already points to ${PLUGIN_ID}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (config.plugins.slots.memory !== "none") {
|
|
291
|
+
const previous = config.plugins.slots.memory || "unset";
|
|
292
|
+
config.plugins.slots.memory = "none";
|
|
293
|
+
warn(`Set memory slot to none to avoid conflicts (was: ${previous})`);
|
|
294
|
+
} else {
|
|
295
|
+
info("memory slot already set to none");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const entry = config.plugins.entries[PLUGIN_ID] || {};
|
|
299
|
+
const hadExistingConfig = !!entry.config;
|
|
300
|
+
entry.enabled = true;
|
|
301
|
+
|
|
302
|
+
if (hadExistingConfig) {
|
|
303
|
+
entry.config = mergePluginConfig(entry.config);
|
|
304
|
+
info("Reusing existing plugin config and filling any missing defaults.");
|
|
305
|
+
} else {
|
|
306
|
+
hr();
|
|
307
|
+
info("Enter the minimum settings needed for the EverOS backend.");
|
|
308
|
+
const baseUrl = await promptWithDefault("EverOS backend URL", DEFAULT_CONFIG.baseUrl);
|
|
309
|
+
const health = await checkBackendHealth(baseUrl);
|
|
310
|
+
|
|
311
|
+
if (health.ok) {
|
|
312
|
+
success(`EverOS backend is reachable (${health.status || "ok"}).`);
|
|
313
|
+
} else {
|
|
314
|
+
warn(`EverOS backend is not reachable yet (${health.reason || "unknown reason"}).`);
|
|
315
|
+
warn("You can continue, but memory recall/save will not work until the backend is running.");
|
|
316
|
+
info("Typical start command: cd EverMemOS && uv run python src/run.py");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const userId = await promptWithDefault("User ID", DEFAULT_CONFIG.userId);
|
|
320
|
+
const groupId = await promptWithDefault("Group ID", DEFAULT_CONFIG.groupId);
|
|
321
|
+
entry.config = mergePluginConfig(null, { baseUrl, userId, groupId });
|
|
322
|
+
success("Plugin config created.");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
config.plugins.entries[PLUGIN_ID] = entry;
|
|
326
|
+
|
|
327
|
+
hr();
|
|
328
|
+
info("Saving OpenClaw configuration...");
|
|
329
|
+
if (!saveConfig(config)) {
|
|
330
|
+
closeAndExit(1);
|
|
331
|
+
}
|
|
332
|
+
success("Configuration saved.");
|
|
333
|
+
|
|
334
|
+
printSummary(pluginPath, entry);
|
|
335
|
+
printNextSteps(entry);
|
|
336
|
+
|
|
337
|
+
const shouldRestart = await prompt("Restart OpenClaw gateway now? (Y/n): ");
|
|
338
|
+
if (shouldRestart.toLowerCase() !== "n") {
|
|
339
|
+
info("Restarting OpenClaw gateway...");
|
|
340
|
+
info('After restart, wait about 1 minute and test with a natural language memory prompt.');
|
|
341
|
+
await restartGateway();
|
|
342
|
+
} else {
|
|
343
|
+
info("When ready, run: openclaw gateway restart");
|
|
344
|
+
info('Then test with: "Remember: I like espresso."');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
closeAndExit(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
install().catch((err) => {
|
|
351
|
+
error(`Installation failed: ${err.message}`);
|
|
352
|
+
console.error(err);
|
|
353
|
+
closeAndExit(1);
|
|
354
|
+
});
|