rol-websocket-channel 1.6.3 → 1.6.7

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/dist/index.js CHANGED
@@ -530,6 +530,12 @@ export async function handleCustomMessageType(msgType, innerData, traceId, accou
530
530
  response.data = methodResult.result;
531
531
  if (!methodResult.ok) {
532
532
  response.error = methodResult.error?.message || "Unknown error";
533
+ if (methodResult.error?.code !== undefined) {
534
+ response.error_code = methodResult.error.code;
535
+ }
536
+ if (methodResult.error?.data !== undefined) {
537
+ response.error_data = methodResult.error.data;
538
+ }
533
539
  if (isSkillInstallFlow) {
534
540
  console.error(`[rol-websocket-channel] custom message failed: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}, error=${response.error}, detail=${JSON.stringify(methodResult.error?.data ?? {})}`);
535
541
  }
@@ -0,0 +1,3 @@
1
+ export function resolveOpenClawBin() {
2
+ return process.env.OPENCLAW_BIN || 'openclaw';
3
+ }
@@ -2,6 +2,7 @@ import { exec, execFile } from 'node:child_process';
2
2
  import path from 'node:path';
3
3
  import { promisify } from 'node:util';
4
4
  import { pathExists, readJsonFile, writeJsonFile } from '../lib/fs.js';
5
+ import { resolveOpenClawBin } from '../lib/openclaw-bin.js';
5
6
  import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
6
7
  const execAsync = promisify(exec);
7
8
  const execFileAsync = promisify(execFile);
@@ -23,18 +24,12 @@ const RUNTIME_ENTRYPOINTS = [
23
24
  'index.cjs'
24
25
  ];
25
26
  // ---------------------------------------------------------------------------
26
- // Resolve openclaw binary path (supports OPENCLAW_BIN env override)
27
- // ---------------------------------------------------------------------------
28
- function resolveOpenClawBin() {
29
- return process.env.OPENCLAW_BIN || 'openclaw';
30
- }
31
- // ---------------------------------------------------------------------------
32
27
  // Public API: installMem9 (idempotent, phase-based)
33
28
  // ---------------------------------------------------------------------------
34
29
  export async function installMem9(context) {
35
30
  const config = await ensureOpenClawConfigExists(context.openclawRoot);
36
31
  const currentState = readMem9State(config);
37
- const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot);
32
+ const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot, config);
38
33
  // Phase A: Plugin not installed → install only, then restart
39
34
  if (!currentState.installed && !currentEntrypoint) {
40
35
  await ensureOpenClawCli();
@@ -52,7 +47,7 @@ export async function installMem9(context) {
52
47
  }
53
48
  // Phase B: Installed but no key → create key, write config, restart
54
49
  if (!currentState.configured || !currentState.apiKey) {
55
- const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
50
+ const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
56
51
  const apiKey = await createMem9Key();
57
52
  const updated = await writeMem9Config(context.openclawRoot, apiKey);
58
53
  const restart = await restartGateway(context.projectRoot);
@@ -73,7 +68,7 @@ export async function installMem9(context) {
73
68
  };
74
69
  }
75
70
  // Phase C: Already configured → ensure slot/hooks/allow are correct
76
- const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
71
+ const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
77
72
  const updated = await ensureMem9SlotConfig(context.openclawRoot, currentState.apiKey);
78
73
  const restart = await restartGateway(context.projectRoot);
79
74
  return {
@@ -102,7 +97,7 @@ export async function reconnectMem9(key, context) {
102
97
  }
103
98
  const config = await ensureOpenClawConfigExists(context.openclawRoot);
104
99
  const previousState = readMem9State(config);
105
- const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
100
+ const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
106
101
  const updated = await writeMem9Config(context.openclawRoot, apiKey);
107
102
  const restart = await restartGateway(context.projectRoot);
108
103
  return {
@@ -153,7 +148,7 @@ async function ensureOpenClawCli() {
153
148
  await execFileAsync(bin, ['--version']);
154
149
  }
155
150
  catch (error) {
156
- throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `openclaw command is not available (tried: ${bin}). Set OPENCLAW_BIN env to override.`, { code: 'MEM9_OPENCLAW_NOT_FOUND', bin, detail: error instanceof Error ? error.message : String(error) });
151
+ throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `openclaw command is not available (tried: ${bin}). Configure the OpenClaw binary path for the Gateway service.`, { code: 'MEM9_OPENCLAW_NOT_FOUND', bin, detail: error instanceof Error ? error.message : String(error) });
157
152
  }
158
153
  }
159
154
  async function ensureNodeRuntime() {
@@ -178,8 +173,8 @@ async function installMem9Plugin(cwd) {
178
173
  });
179
174
  }
180
175
  }
181
- export async function findMem9RuntimeEntrypoint(openclawRoot) {
182
- for (const packageRoot of MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))) {
176
+ export async function findMem9RuntimeEntrypoint(openclawRoot, config) {
177
+ for (const packageRoot of resolveMem9RuntimePackageRoots(openclawRoot, config)) {
183
178
  for (const entrypoint of RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))) {
184
179
  if (await pathExists(entrypoint)) {
185
180
  return entrypoint;
@@ -188,17 +183,34 @@ export async function findMem9RuntimeEntrypoint(openclawRoot) {
188
183
  }
189
184
  return null;
190
185
  }
191
- async function ensureMem9RuntimeEntrypoint(openclawRoot) {
192
- const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot);
186
+ async function ensureMem9RuntimeEntrypoint(openclawRoot, config) {
187
+ const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot, config);
193
188
  if (entrypoint) {
194
189
  return entrypoint;
195
190
  }
191
+ const installRecord = readMem9InstallRecord(config);
192
+ const checkedPackageRoots = resolveMem9RuntimePackageRoots(openclawRoot, config);
196
193
  throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'mem9 plugin is installed but missing compiled runtime output', {
197
194
  code: 'MEM9_RUNTIME_OUTPUT_MISSING',
198
195
  expected: RUNTIME_ENTRYPOINTS.map((item) => `./${item.replace(/\\/g, '/')}`),
199
- packageRoots: MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))
196
+ checkedPackageRoots,
197
+ checkedEntrypoints: checkedPackageRoots.flatMap((packageRoot) => RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))),
198
+ installRecord
200
199
  });
201
200
  }
201
+ function resolveMem9RuntimePackageRoots(openclawRoot, config) {
202
+ const roots = [];
203
+ const installPath = pickString(readMem9InstallRecord(config)?.installPath);
204
+ if (installPath) {
205
+ roots.push(path.isAbsolute(installPath) ? installPath : path.resolve(openclawRoot, installPath));
206
+ }
207
+ for (const fallbackRoot of MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))) {
208
+ if (!roots.includes(fallbackRoot)) {
209
+ roots.push(fallbackRoot);
210
+ }
211
+ }
212
+ return roots;
213
+ }
202
214
  async function createMem9Key() {
203
215
  let response;
204
216
  try {
@@ -368,6 +380,10 @@ function pickString(value) {
368
380
  function isRecord(value) {
369
381
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
370
382
  }
383
+ function readMem9InstallRecord(config) {
384
+ const record = config?.plugins?.installs?.[MEM9_PLUGIN_ID];
385
+ return isRecord(record) ? record : null;
386
+ }
371
387
  function readMem9State(config) {
372
388
  const installed = Boolean((config.plugins?.installs && typeof config.plugins.installs === 'object' && MEM9_PLUGIN_ID in config.plugins.installs)
373
389
  || (config.plugins?.entries && typeof config.plugins.entries === 'object' && MEM9_PLUGIN_ID in config.plugins.entries));
package/index.ts CHANGED
@@ -681,6 +681,12 @@ export async function handleCustomMessageType(
681
681
  response.data = methodResult.result;
682
682
  if (!methodResult.ok) {
683
683
  response.error = methodResult.error?.message || "Unknown error";
684
+ if (methodResult.error?.code !== undefined) {
685
+ response.error_code = methodResult.error.code;
686
+ }
687
+ if (methodResult.error?.data !== undefined) {
688
+ response.error_data = methodResult.error.data;
689
+ }
684
690
  if (isSkillInstallFlow) {
685
691
  console.error(
686
692
  `[rol-websocket-channel] custom message failed: type=${msgType}, traceId=${traceId}, slug=${innerData?.slug ?? ""}, error=${response.error}, detail=${JSON.stringify(methodResult.error?.data ?? {})}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rol-websocket-channel",
3
- "version": "1.6.3",
3
+ "version": "1.6.7",
4
4
  "description": "Unified OpenClaw plugin: MQTT Channel + Admin Bridge for remote management",
5
5
  "license": "MIT",
6
6
  "author": "nixgnehc",
@@ -0,0 +1,3 @@
1
+ export function resolveOpenClawBin(): string {
2
+ return process.env.OPENCLAW_BIN || 'openclaw';
3
+ }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { promisify } from 'node:util';
4
4
 
5
5
  import { pathExists, readJsonFile, writeJsonFile } from '../lib/fs.js';
6
+ import { resolveOpenClawBin } from '../lib/openclaw-bin.js';
6
7
  import { JsonRpcException, JSON_RPC_ERRORS } from '../jsonrpc.js';
7
8
  import type { JsonValue, MethodContext } from '../types.js';
8
9
 
@@ -38,14 +39,6 @@ interface OpenClawConfig {
38
39
  [key: string]: any;
39
40
  }
40
41
 
41
- // ---------------------------------------------------------------------------
42
- // Resolve openclaw binary path (supports OPENCLAW_BIN env override)
43
- // ---------------------------------------------------------------------------
44
-
45
- function resolveOpenClawBin(): string {
46
- return process.env.OPENCLAW_BIN || 'openclaw';
47
- }
48
-
49
42
  // ---------------------------------------------------------------------------
50
43
  // Public API: installMem9 (idempotent, phase-based)
51
44
  // ---------------------------------------------------------------------------
@@ -53,7 +46,7 @@ function resolveOpenClawBin(): string {
53
46
  export async function installMem9(context: MethodContext): Promise<JsonValue> {
54
47
  const config = await ensureOpenClawConfigExists(context.openclawRoot);
55
48
  const currentState = readMem9State(config);
56
- const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot);
49
+ const currentEntrypoint = await findMem9RuntimeEntrypoint(context.openclawRoot, config);
57
50
 
58
51
  // Phase A: Plugin not installed → install only, then restart
59
52
  if (!currentState.installed && !currentEntrypoint) {
@@ -74,7 +67,7 @@ export async function installMem9(context: MethodContext): Promise<JsonValue> {
74
67
 
75
68
  // Phase B: Installed but no key → create key, write config, restart
76
69
  if (!currentState.configured || !currentState.apiKey) {
77
- const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
70
+ const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
78
71
  const apiKey = await createMem9Key();
79
72
  const updated = await writeMem9Config(context.openclawRoot, apiKey);
80
73
  const restart = await restartGateway(context.projectRoot);
@@ -97,7 +90,7 @@ export async function installMem9(context: MethodContext): Promise<JsonValue> {
97
90
  }
98
91
 
99
92
  // Phase C: Already configured → ensure slot/hooks/allow are correct
100
- const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
93
+ const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
101
94
  const updated = await ensureMem9SlotConfig(context.openclawRoot, currentState.apiKey!);
102
95
  const restart = await restartGateway(context.projectRoot);
103
96
 
@@ -130,7 +123,7 @@ export async function reconnectMem9(key: string, context: MethodContext): Promis
130
123
 
131
124
  const config = await ensureOpenClawConfigExists(context.openclawRoot);
132
125
  const previousState = readMem9State(config);
133
- const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot);
126
+ const runtimeEntrypoint = await ensureMem9RuntimeEntrypoint(context.openclawRoot, config);
134
127
  const updated = await writeMem9Config(context.openclawRoot, apiKey);
135
128
  const restart = await restartGateway(context.projectRoot);
136
129
 
@@ -189,7 +182,7 @@ async function ensureOpenClawCli(): Promise<void> {
189
182
  } catch (error) {
190
183
  throw new JsonRpcException(
191
184
  JSON_RPC_ERRORS.internalError,
192
- `openclaw command is not available (tried: ${bin}). Set OPENCLAW_BIN env to override.`,
185
+ `openclaw command is not available (tried: ${bin}). Configure the OpenClaw binary path for the Gateway service.`,
193
186
  { code: 'MEM9_OPENCLAW_NOT_FOUND', bin, detail: error instanceof Error ? error.message : String(error) }
194
187
  );
195
188
  }
@@ -225,8 +218,8 @@ async function installMem9Plugin(cwd: string): Promise<void> {
225
218
  }
226
219
  }
227
220
 
228
- export async function findMem9RuntimeEntrypoint(openclawRoot: string): Promise<string | null> {
229
- for (const packageRoot of MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))) {
221
+ export async function findMem9RuntimeEntrypoint(openclawRoot: string, config?: OpenClawConfig): Promise<string | null> {
222
+ for (const packageRoot of resolveMem9RuntimePackageRoots(openclawRoot, config)) {
230
223
  for (const entrypoint of RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))) {
231
224
  if (await pathExists(entrypoint)) {
232
225
  return entrypoint;
@@ -236,22 +229,40 @@ export async function findMem9RuntimeEntrypoint(openclawRoot: string): Promise<s
236
229
  return null;
237
230
  }
238
231
 
239
- async function ensureMem9RuntimeEntrypoint(openclawRoot: string): Promise<string> {
240
- const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot);
232
+ async function ensureMem9RuntimeEntrypoint(openclawRoot: string, config?: OpenClawConfig): Promise<string> {
233
+ const entrypoint = await findMem9RuntimeEntrypoint(openclawRoot, config);
241
234
  if (entrypoint) {
242
235
  return entrypoint;
243
236
  }
237
+ const installRecord = readMem9InstallRecord(config);
238
+ const checkedPackageRoots = resolveMem9RuntimePackageRoots(openclawRoot, config);
244
239
  throw new JsonRpcException(
245
240
  JSON_RPC_ERRORS.internalError,
246
241
  'mem9 plugin is installed but missing compiled runtime output',
247
242
  {
248
243
  code: 'MEM9_RUNTIME_OUTPUT_MISSING',
249
244
  expected: RUNTIME_ENTRYPOINTS.map((item) => `./${item.replace(/\\/g, '/')}`),
250
- packageRoots: MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))
245
+ checkedPackageRoots,
246
+ checkedEntrypoints: checkedPackageRoots.flatMap((packageRoot) => RUNTIME_ENTRYPOINTS.map((item) => path.join(packageRoot, item))),
247
+ installRecord
251
248
  }
252
249
  );
253
250
  }
254
251
 
252
+ function resolveMem9RuntimePackageRoots(openclawRoot: string, config?: OpenClawConfig): string[] {
253
+ const roots: string[] = [];
254
+ const installPath = pickString(readMem9InstallRecord(config)?.installPath);
255
+ if (installPath) {
256
+ roots.push(path.isAbsolute(installPath) ? installPath : path.resolve(openclawRoot, installPath));
257
+ }
258
+ for (const fallbackRoot of MEM9_PACKAGE_ROOTS.map((item) => path.join(openclawRoot, item))) {
259
+ if (!roots.includes(fallbackRoot)) {
260
+ roots.push(fallbackRoot);
261
+ }
262
+ }
263
+ return roots;
264
+ }
265
+
255
266
  async function createMem9Key(): Promise<string> {
256
267
  let response: Response;
257
268
  try {
@@ -447,6 +458,11 @@ function isRecord(value: unknown): value is Record<string, any> {
447
458
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
448
459
  }
449
460
 
461
+ function readMem9InstallRecord(config?: OpenClawConfig): Record<string, any> | null {
462
+ const record = config?.plugins?.installs?.[MEM9_PLUGIN_ID];
463
+ return isRecord(record) ? record : null;
464
+ }
465
+
450
466
  function readMem9State(config: OpenClawConfig): {
451
467
  installed: boolean;
452
468
  configured: boolean;