rol-websocket-channel 1.4.2 → 1.4.8
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/{MQTT-API /346/226/260/345/242/236/346/226/207/344/273/266/345/212/237/350/203/275.md" → MQTT-API 5-6.md } +89 -1
- package/dist/index.js +617 -617
- package/dist/message-handler.js +515 -503
- package/dist/src/admin/cli.js +43 -43
- package/dist/src/admin/jsonrpc.js +60 -60
- package/dist/src/admin/lib/fs.js +30 -30
- package/dist/src/admin/lib/paths.js +80 -80
- package/dist/src/admin/methods/admin.js +60 -60
- package/dist/src/admin/methods/agents-extended.js +251 -251
- package/dist/src/admin/methods/artifacts.js +736 -642
- package/dist/src/admin/methods/artifacts.test.js +210 -191
- package/dist/src/admin/methods/cron.js +250 -250
- package/dist/src/admin/methods/index.js +104 -102
- package/dist/src/admin/methods/mem9.js +309 -270
- package/dist/src/admin/methods/mem9.test.js +34 -0
- package/dist/src/admin/methods/memory.js +363 -363
- package/dist/src/admin/methods/models-extended.js +190 -190
- package/dist/src/admin/methods/models.js +195 -195
- package/dist/src/admin/methods/pairing.js +268 -268
- package/dist/src/admin/methods/sessions-extended.js +215 -215
- package/dist/src/admin/methods/sessions.js +75 -75
- package/dist/src/admin/methods/skills-extended.js +157 -157
- package/dist/src/admin/methods/skills-toggle.js +183 -183
- package/dist/src/admin/methods/skills.js +528 -528
- package/dist/src/admin/methods/system.js +271 -180
- package/dist/src/admin/methods/usage.js +1170 -1170
- package/dist/src/admin/types.js +1 -1
- package/dist/src/mqtt/connection-manager.js +209 -209
- package/dist/src/mqtt/index.js +5 -5
- package/dist/src/mqtt/mqtt-client.js +110 -110
- package/dist/src/mqtt/mqtt.test.js +418 -418
- package/dist/src/mqtt/types.js +2 -2
- package/dist/src/shared/context.js +24 -24
- package/dist/src/shared/wrapper.js +23 -23
- package/message-handler.ts +15 -1
- package/openclaw.plugin.json +73 -0
- package/package.json +1 -1
- package/src/admin/methods/artifacts.test.ts +35 -0
- package/src/admin/methods/artifacts.ts +140 -2
- package/src/admin/methods/index.ts +3 -1
- package/src/admin/methods/mem9.test.ts +39 -0
- package/src/admin/methods/mem9.ts +48 -1
- package/src/admin/methods/system.ts +129 -1
|
@@ -1,270 +1,309 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
4
|
-
import { pathExists, readJsonFile, writeJsonFile } from '../lib/fs.js';
|
|
5
|
-
import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
6
|
-
const execFileAsync = promisify(execFile);
|
|
7
|
-
const MEM9_PLUGIN_SPEC = '@mem9/mem9';
|
|
8
|
-
const MEM9_PLUGIN_ID = 'mem9';
|
|
9
|
-
const MEM9_API_URL = 'https://api.mem9.ai';
|
|
10
|
-
const MEM9_CREATE_URL = `${MEM9_API_URL}/v1alpha1/mem9s`;
|
|
11
|
-
const GATEWAY_SERVICE = 'openclaw-gateway.service';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { pathExists, readJsonFile, writeJsonFile } from '../lib/fs.js';
|
|
5
|
+
import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const MEM9_PLUGIN_SPEC = '@mem9/mem9';
|
|
8
|
+
const MEM9_PLUGIN_ID = 'mem9';
|
|
9
|
+
const MEM9_API_URL = 'https://api.mem9.ai';
|
|
10
|
+
const MEM9_CREATE_URL = `${MEM9_API_URL}/v1alpha1/mem9s`;
|
|
11
|
+
const GATEWAY_SERVICE = 'openclaw-gateway.service';
|
|
12
|
+
const MEM9_PACKAGE_ROOTS = [
|
|
13
|
+
path.join('npm', 'node_modules', '@mem9', 'mem9'),
|
|
14
|
+
path.join('npm', 'node_modules', 'mem9')
|
|
15
|
+
];
|
|
16
|
+
const RUNTIME_ENTRYPOINTS = [
|
|
17
|
+
path.join('dist', 'index.js'),
|
|
18
|
+
path.join('dist', 'index.mjs'),
|
|
19
|
+
path.join('dist', 'index.cjs'),
|
|
20
|
+
'index.js',
|
|
21
|
+
'index.mjs',
|
|
22
|
+
'index.cjs'
|
|
23
|
+
];
|
|
24
|
+
export async function installMem9(context) {
|
|
25
|
+
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
26
|
+
await ensureOpenClawCli();
|
|
27
|
+
await ensureNodeRuntime();
|
|
28
|
+
const currentState = readMem9State(config);
|
|
29
|
+
const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot);
|
|
30
|
+
const installResult = currentState.installed && currentEntrypoint
|
|
31
|
+
? { attempted: false, installed: true }
|
|
32
|
+
: await installMem9Plugin(context.projectRoot);
|
|
33
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
34
|
+
if (currentState.configured && currentState.apiKey) {
|
|
35
|
+
const updated = await ensureMem9SlotConfig(context.openclawRoot, currentState.apiKey);
|
|
36
|
+
const restart = await restartGateway(context.projectRoot);
|
|
37
|
+
return {
|
|
38
|
+
ok: true,
|
|
39
|
+
installed: true,
|
|
40
|
+
alreadyInstalled: installResult.installed,
|
|
41
|
+
alreadyConfigured: true,
|
|
42
|
+
createdNewKey: false,
|
|
43
|
+
reusedExistingKey: true,
|
|
44
|
+
plugin: MEM9_PLUGIN_ID,
|
|
45
|
+
runtimeEntrypoint,
|
|
46
|
+
apiUrl: MEM9_API_URL,
|
|
47
|
+
apiKey: currentState.apiKey,
|
|
48
|
+
updated,
|
|
49
|
+
restart
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const apiKey = await createMem9Key();
|
|
53
|
+
const updated = await writeMem9Config(context.openclawRoot, apiKey);
|
|
54
|
+
const restart = await restartGateway(context.projectRoot);
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
installed: true,
|
|
58
|
+
alreadyInstalled: installResult.installed,
|
|
59
|
+
alreadyConfigured: false,
|
|
60
|
+
createdNewKey: true,
|
|
61
|
+
reusedExistingKey: false,
|
|
62
|
+
plugin: MEM9_PLUGIN_ID,
|
|
63
|
+
runtimeEntrypoint,
|
|
64
|
+
apiUrl: MEM9_API_URL,
|
|
65
|
+
apiKey,
|
|
66
|
+
updated,
|
|
67
|
+
restart
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export async function reconnectMem9(key, context) {
|
|
71
|
+
const apiKey = key.trim();
|
|
72
|
+
if (!apiKey) {
|
|
73
|
+
throwMem9Error('MEM9_KEY_REQUIRED', 'mem9 key is required');
|
|
74
|
+
}
|
|
75
|
+
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
76
|
+
const previousState = readMem9State(config);
|
|
77
|
+
const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
|
|
78
|
+
const updated = await writeMem9Config(context.openclawRoot, apiKey);
|
|
79
|
+
const restart = await restartGateway(context.projectRoot);
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
reconnected: true,
|
|
83
|
+
replacedExistingKey: Boolean(previousState.apiKey && previousState.apiKey !== apiKey),
|
|
84
|
+
plugin: MEM9_PLUGIN_ID,
|
|
85
|
+
runtimeEntrypoint,
|
|
86
|
+
apiUrl: MEM9_API_URL,
|
|
87
|
+
apiKey,
|
|
88
|
+
updated: ['plugins.entries.mem9.config.apiKey', ...updated.filter((item) => item !== 'plugins.entries.mem9' && item !== 'plugins.slots.memory')],
|
|
89
|
+
restart
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export async function getMem9Config(context) {
|
|
93
|
+
const config = await ensureOpenClawConfigExists(context.openclawRoot);
|
|
94
|
+
const state = readMem9State(config);
|
|
95
|
+
const entry = isRecord(config.plugins?.entries?.[MEM9_PLUGIN_ID]) ? config.plugins?.entries?.[MEM9_PLUGIN_ID] : {};
|
|
96
|
+
const pluginConfig = isRecord(entry.config) ? entry.config : {};
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
installed: state.installed,
|
|
100
|
+
configured: state.configured,
|
|
101
|
+
plugin: MEM9_PLUGIN_ID,
|
|
102
|
+
enabled: entry.enabled === true,
|
|
103
|
+
apiUrl: pickString(pluginConfig.apiUrl) ?? MEM9_API_URL,
|
|
104
|
+
apiKey: state.apiKey,
|
|
105
|
+
slot: config.plugins?.slots?.memory ?? null
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function ensureOpenClawConfigExists(openclawRoot) {
|
|
109
|
+
const configPath = path.join(openclawRoot, 'openclaw.json');
|
|
110
|
+
if (!(await pathExists(configPath))) {
|
|
111
|
+
throwMem9Error('MEM9_CONFIG_NOT_FOUND', `openclaw.json not found: ${configPath}`);
|
|
112
|
+
}
|
|
113
|
+
return await readJsonFile(configPath);
|
|
114
|
+
}
|
|
115
|
+
async function ensureOpenClawCli() {
|
|
116
|
+
try {
|
|
117
|
+
await execFileAsync('openclaw', ['--version']);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'openclaw command is not available', { code: 'MEM9_OPENCLAW_NOT_FOUND', detail: error instanceof Error ? error.message : String(error) });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function ensureNodeRuntime() {
|
|
124
|
+
try {
|
|
125
|
+
await execFileAsync('node', ['--version']);
|
|
126
|
+
await execFileAsync('npm', ['--version']);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'node or npm command is not available', { code: 'MEM9_NODE_NOT_FOUND', detail: error instanceof Error ? error.message : String(error) });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function runOpenClawCommand(args, cwd, code) {
|
|
133
|
+
try {
|
|
134
|
+
await execFileAsync('openclaw', args, { cwd });
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `openclaw ${args.join(' ')} failed`, {
|
|
138
|
+
code,
|
|
139
|
+
stdout: typeof error?.stdout === 'string' ? error.stdout : '',
|
|
140
|
+
stderr: typeof error?.stderr === 'string' ? error.stderr : ''
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function installMem9Plugin(cwd) {
|
|
145
|
+
await runOpenClawCommand(['plugins', 'install', MEM9_PLUGIN_SPEC], cwd, 'MEM9_PLUGIN_INSTALL_FAILED');
|
|
146
|
+
return {
|
|
147
|
+
attempted: true,
|
|
148
|
+
installed: true
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export async function findMem9RuntimeEntrypoint(openclawRoot) {
|
|
152
|
+
for (const packageRoot of MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))) {
|
|
153
|
+
for (const entrypoint of RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))) {
|
|
154
|
+
if (await pathExists(entrypoint)) {
|
|
155
|
+
return entrypoint;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
async function ensureMem9RuntimeEntrypoint(openclawRoot) {
|
|
162
|
+
const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot);
|
|
163
|
+
if (entrypoint) {
|
|
164
|
+
return entrypoint;
|
|
165
|
+
}
|
|
166
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'mem9 plugin is installed but missing compiled runtime output required by OpenClaw 2026.5.6', {
|
|
167
|
+
code: 'MEM9_RUNTIME_OUTPUT_MISSING',
|
|
168
|
+
expected: RUNTIME_ENTRYPOINTS.map((item) => `./${item.replace(/\\/g, '/')}`),
|
|
169
|
+
packageRoots: MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async function createMem9Key() {
|
|
173
|
+
const response = await fetch(MEM9_CREATE_URL, {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers: {
|
|
176
|
+
accept: 'application/json',
|
|
177
|
+
'content-type': 'application/json'
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({})
|
|
180
|
+
});
|
|
181
|
+
const rawText = await response.text();
|
|
182
|
+
const payload = tryParseJson(rawText);
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `mem9 key create failed: ${response.status}`, {
|
|
185
|
+
code: 'MEM9_KEY_CREATE_FAILED',
|
|
186
|
+
status: response.status,
|
|
187
|
+
payload
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const root = isRecord(payload) && isRecord(payload.data) ? payload.data : isRecord(payload) ? payload : null;
|
|
191
|
+
const key = pickString(root?.id) ?? pickString(root?.value) ?? pickString(root?.apiKey);
|
|
192
|
+
if (!key) {
|
|
193
|
+
throwMem9Error('MEM9_KEY_CREATE_FAILED', 'mem9 create response missing id');
|
|
194
|
+
}
|
|
195
|
+
return key;
|
|
196
|
+
}
|
|
197
|
+
async function writeMem9Config(openclawRoot, apiKey) {
|
|
198
|
+
const configPath = path.join(openclawRoot, 'openclaw.json');
|
|
199
|
+
const config = await readJsonFile(configPath);
|
|
200
|
+
if (!config.plugins)
|
|
201
|
+
config.plugins = {};
|
|
202
|
+
if (!config.plugins.entries || typeof config.plugins.entries !== 'object') {
|
|
203
|
+
config.plugins.entries = {};
|
|
204
|
+
}
|
|
205
|
+
if (!config.plugins.slots || typeof config.plugins.slots !== 'object') {
|
|
206
|
+
config.plugins.slots = {};
|
|
207
|
+
}
|
|
208
|
+
const existingEntry = isRecord(config.plugins.entries[MEM9_PLUGIN_ID]) ? config.plugins.entries[MEM9_PLUGIN_ID] : {};
|
|
209
|
+
const existingPluginConfig = isRecord(existingEntry.config) ? existingEntry.config : {};
|
|
210
|
+
const hadExistingKey = typeof existingPluginConfig.apiKey === 'string' && existingPluginConfig.apiKey.trim().length > 0;
|
|
211
|
+
config.plugins.entries[MEM9_PLUGIN_ID] = {
|
|
212
|
+
...existingEntry,
|
|
213
|
+
enabled: true,
|
|
214
|
+
config: {
|
|
215
|
+
...existingPluginConfig,
|
|
216
|
+
apiUrl: MEM9_API_URL,
|
|
217
|
+
apiKey
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
config.plugins.slots.memory = MEM9_PLUGIN_ID;
|
|
221
|
+
await writeJsonFile(configPath, config);
|
|
222
|
+
return [
|
|
223
|
+
hadExistingKey ? 'plugins.entries.mem9.config.apiKey (replaced)' : 'plugins.entries.mem9',
|
|
224
|
+
'plugins.slots.memory'
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
async function ensureMem9SlotConfig(openclawRoot, apiKey) {
|
|
228
|
+
const configPath = path.join(openclawRoot, 'openclaw.json');
|
|
229
|
+
const config = await readJsonFile(configPath);
|
|
230
|
+
if (!config.plugins)
|
|
231
|
+
config.plugins = {};
|
|
232
|
+
if (!config.plugins.entries || typeof config.plugins.entries !== 'object') {
|
|
233
|
+
config.plugins.entries = {};
|
|
234
|
+
}
|
|
235
|
+
if (!config.plugins.slots || typeof config.plugins.slots !== 'object') {
|
|
236
|
+
config.plugins.slots = {};
|
|
237
|
+
}
|
|
238
|
+
const existingEntry = isRecord(config.plugins.entries[MEM9_PLUGIN_ID]) ? config.plugins.entries[MEM9_PLUGIN_ID] : {};
|
|
239
|
+
const existingPluginConfig = isRecord(existingEntry.config) ? existingEntry.config : {};
|
|
240
|
+
config.plugins.entries[MEM9_PLUGIN_ID] = {
|
|
241
|
+
...existingEntry,
|
|
242
|
+
enabled: true,
|
|
243
|
+
config: {
|
|
244
|
+
...existingPluginConfig,
|
|
245
|
+
apiUrl: MEM9_API_URL,
|
|
246
|
+
apiKey
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
config.plugins.slots.memory = MEM9_PLUGIN_ID;
|
|
250
|
+
await writeJsonFile(configPath, config);
|
|
251
|
+
return [
|
|
252
|
+
'plugins.entries.mem9',
|
|
253
|
+
'plugins.slots.memory'
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
async function restartGateway(cwd) {
|
|
257
|
+
try {
|
|
258
|
+
await execFileAsync('systemctl', ['--user', 'restart', GATEWAY_SERVICE], { cwd });
|
|
259
|
+
return {
|
|
260
|
+
attempted: true,
|
|
261
|
+
success: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
return {
|
|
266
|
+
attempted: true,
|
|
267
|
+
success: false,
|
|
268
|
+
message: error instanceof Error ? error.message : String(error),
|
|
269
|
+
stderr: typeof error?.stderr === 'string' ? error.stderr : ''
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function tryParseJson(raw) {
|
|
274
|
+
const trimmed = raw.trim();
|
|
275
|
+
if (!trimmed) {
|
|
276
|
+
return {};
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
return JSON.parse(trimmed);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return raw;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function pickString(value) {
|
|
286
|
+
if (typeof value !== 'string') {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const trimmed = value.trim();
|
|
290
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
291
|
+
}
|
|
292
|
+
function isRecord(value) {
|
|
293
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
294
|
+
}
|
|
295
|
+
function readMem9State(config) {
|
|
296
|
+
const installed = Boolean((config.plugins?.installs && typeof config.plugins.installs === 'object' && MEM9_PLUGIN_ID in config.plugins.installs)
|
|
297
|
+
|| (config.plugins?.entries && typeof config.plugins.entries === 'object' && MEM9_PLUGIN_ID in config.plugins.entries));
|
|
298
|
+
const entry = isRecord(config.plugins?.entries?.[MEM9_PLUGIN_ID]) ? config.plugins?.entries?.[MEM9_PLUGIN_ID] : {};
|
|
299
|
+
const pluginConfig = isRecord(entry.config) ? entry.config : {};
|
|
300
|
+
const apiKey = pickString(pluginConfig.apiKey);
|
|
301
|
+
return {
|
|
302
|
+
installed,
|
|
303
|
+
configured: Boolean(apiKey),
|
|
304
|
+
apiKey
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function throwMem9Error(code, message) {
|
|
308
|
+
throw new JsonRpcException(JSON_RPC_ERRORS.invalidParams, message, { code });
|
|
309
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { findMem9RuntimeEntrypoint } from './mem9.js';
|
|
7
|
+
describe('mem9 runtime compatibility', () => {
|
|
8
|
+
test('accepts compiled runtime output under the installed package', async () => {
|
|
9
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mem9-runtime-'));
|
|
10
|
+
try {
|
|
11
|
+
const packageRoot = path.join(root, '.openclaw', 'npm', 'node_modules', '@mem9', 'mem9');
|
|
12
|
+
await fs.mkdir(path.join(packageRoot, 'dist'), { recursive: true });
|
|
13
|
+
await fs.writeFile(path.join(packageRoot, 'dist', 'index.js'), 'export default {};\n', 'utf8');
|
|
14
|
+
const entrypoint = await findMem9RuntimeEntrypoint(path.join(root, '.openclaw'));
|
|
15
|
+
assert.equal(entrypoint, path.join(packageRoot, 'dist', 'index.js'));
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
test('rejects TypeScript-only mem9 packages before writing memory slot', async () => {
|
|
22
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mem9-runtime-'));
|
|
23
|
+
try {
|
|
24
|
+
const packageRoot = path.join(root, '.openclaw', 'npm', 'node_modules', '@mem9', 'mem9');
|
|
25
|
+
await fs.mkdir(packageRoot, { recursive: true });
|
|
26
|
+
await fs.writeFile(path.join(packageRoot, 'index.ts'), 'export default {};\n', 'utf8');
|
|
27
|
+
const entrypoint = await findMem9RuntimeEntrypoint(path.join(root, '.openclaw'));
|
|
28
|
+
assert.equal(entrypoint, null);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|