rol-websocket-channel 1.7.1 → 1.7.3

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.
@@ -497,7 +497,6 @@ export class MessageHandler {
497
497
  return await logs(data, context);
498
498
  });
499
499
  }
500
- // OpenClaw core updates are managed outside this plugin.
501
500
  async pluginSelfUpdate(data) {
502
501
  return wrapAdminCall(async () => {
503
502
  const context = getContext();
@@ -20,7 +20,6 @@ const methods = new Map([
20
20
  ['system.stop', stop],
21
21
  ['system.doctorFix', doctorFix],
22
22
  ['system.logs', logs],
23
- // OpenClaw core updates are managed outside this plugin.
24
23
  ['system.pluginSelfUpdate', pluginSelfUpdate],
25
24
  ['system.currentVersion', currentVersion],
26
25
  // Agents
@@ -8,7 +8,6 @@ const execAsync = promisify(exec);
8
8
  const execFileAsync = promisify(execFile);
9
9
  const UPDATE_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;
10
10
  const UPDATE_COMMAND_MAX_BUFFER = 10 * 1024 * 1024;
11
- const OPENCLAW_UPDATE_TARGET_VERSION = '2026.5.6';
12
11
  const CHANNEL_FALLBACK_VERSION = '1.5.9';
13
12
  export const ping = async () => {
14
13
  return {
@@ -74,17 +73,21 @@ export const doctorFix = async (_params, context) => {
74
73
  };
75
74
  }
76
75
  };
77
- export const openclawUpdate = async () => {
78
- // OpenClaw core updates are intentionally not exposed by this plugin.
79
- return {
80
- ok: false,
81
- action: 'openclawUpdate',
82
- disabled: true,
83
- restartRecommended: false
84
- };
85
- };
86
76
  export const pluginSelfUpdate = async (_params, context) => {
87
77
  const result = await runOpenClawCommand(['plugins', 'update', 'rol-websocket-channel'], context, 'pluginSelfUpdate');
78
+ const output = `${result.stdout}\n${result.stderr}`;
79
+ if (isPathSourceUpdateSkip(output)) {
80
+ return {
81
+ ok: false,
82
+ action: 'pluginSelfUpdate',
83
+ plugin: 'rol-websocket-channel',
84
+ skipped: true,
85
+ reason: 'path-source-plugin',
86
+ message: 'rol-websocket-channel is installed as a local path/global plugin, so OpenClaw plugins update skipped it.',
87
+ restartRecommended: false,
88
+ ...result
89
+ };
90
+ }
88
91
  return {
89
92
  ok: true,
90
93
  action: 'pluginSelfUpdate',
@@ -94,19 +97,16 @@ export const pluginSelfUpdate = async (_params, context) => {
94
97
  };
95
98
  };
96
99
  export const currentVersion = async (_params, context) => {
97
- const [openclawCurrentResult, channelPackage, registryInfo] = await Promise.all([
98
- runOpenClawCommand(['--version'], context, 'currentVersion.openclaw'),
100
+ const [channelPackage, registryInfo] = await Promise.all([
99
101
  readJsonFile(path.join(context.projectRoot, 'package.json')),
100
102
  queryNpmRegistry('rol-websocket-channel', context)
101
103
  ]);
102
- const openclawCurrentVersion = parseOpenClawVersion(openclawCurrentResult.stdout);
103
104
  const channelCurrentVersion = normalizeVersion(channelPackage.version);
104
105
  const channelLatestVersion = registryInfo.version;
105
106
  const targets = [
106
- buildExternalVersionTarget('openclaw', 'openclaw', openclawCurrentVersion),
107
107
  buildCurrentVersionTarget('channel', normalizePackageName(channelPackage.name) || 'rol-websocket-channel', channelCurrentVersion, channelLatestVersion)
108
108
  ];
109
- const upgradeTargets = targets.filter((t) => t.name !== 'openclaw' && t.status === 'outdated');
109
+ const upgradeTargets = targets.filter((t) => t.status === 'outdated');
110
110
  return {
111
111
  ok: true,
112
112
  action: 'currentVersion',
@@ -263,38 +263,6 @@ async function runOpenClawCommand(args, context, action) {
263
263
  });
264
264
  }
265
265
  }
266
- async function runSystemCommand(command, args, cwd, context, action) {
267
- const options = buildExecOptions(cwd, context.openclawRoot);
268
- console.log(`[system] exec start: action=${action}, command=${command}, args=${JSON.stringify(args)}, cwd=${options.cwd}`);
269
- try {
270
- const { stdout, stderr } = await execFileAsync(command, args, {
271
- ...options,
272
- timeout: UPDATE_COMMAND_TIMEOUT_MS,
273
- maxBuffer: UPDATE_COMMAND_MAX_BUFFER
274
- });
275
- console.log(`[system] exec success: action=${action}, command=${command}, args=${JSON.stringify(args)}, stdoutLength=${stdout.length}, stderrLength=${stderr.length}`);
276
- return {
277
- command,
278
- args,
279
- cwd: options.cwd,
280
- stdout,
281
- stderr
282
- };
283
- }
284
- catch (err) {
285
- const stdout = typeof err?.stdout === 'string' ? err.stdout : '';
286
- const stderr = typeof err?.stderr === 'string' ? err.stderr : '';
287
- console.error(`[system] exec failed: action=${action}, command=${command}, args=${JSON.stringify(args)}, cwd=${options.cwd}, stdout=${JSON.stringify(stdout)}, stderr=${JSON.stringify(stderr)}`);
288
- throw new JsonRpcException(JSON_RPC_ERRORS.internalError, `OpenClaw system command failed: ${err instanceof Error ? err.message : String(err)}`, {
289
- action,
290
- command,
291
- args,
292
- cwd: options.cwd,
293
- stdout,
294
- stderr
295
- });
296
- }
297
- }
298
266
  function buildExecOptions(cwd, openclawRoot) {
299
267
  const env = { ...process.env };
300
268
  const openclawHome = resolveOpenClawHomeForCli(openclawRoot);
@@ -338,19 +306,15 @@ async function queryNpmRegistry(packageName, context) {
338
306
  });
339
307
  const data = JSON.parse(stdout);
340
308
  const version = typeof data.version === 'string' ? data.version : null;
341
- const requiredOpenclawVersion = data?.openclaw?.build?.openclawVersion ??
342
- data?.openclaw?.compat?.pluginApi ??
343
- null;
344
309
  if (!version) {
345
310
  console.error(`[system] queryNpmRegistry: no version field in npm view output`);
346
- return { version: CHANNEL_FALLBACK_VERSION, requiredOpenclawVersion: OPENCLAW_UPDATE_TARGET_VERSION };
311
+ return { version: CHANNEL_FALLBACK_VERSION };
347
312
  }
348
- return { version, requiredOpenclawVersion };
313
+ return { version };
349
314
  }
350
315
  catch (err) {
351
316
  console.error(`[system] queryNpmRegistry failed: ${err instanceof Error ? err.message : String(err)}`);
352
- // 查询失败时回退到硬编码的已知版本,不阻塞整个接口
353
- return { version: CHANNEL_FALLBACK_VERSION, requiredOpenclawVersion: OPENCLAW_UPDATE_TARGET_VERSION };
317
+ return { version: CHANNEL_FALLBACK_VERSION };
354
318
  }
355
319
  }
356
320
  function buildCurrentVersionTarget(name, packageName, currentVersion, latestVersion) {
@@ -363,34 +327,6 @@ function buildCurrentVersionTarget(name, packageName, currentVersion, latestVers
363
327
  status
364
328
  };
365
329
  }
366
- function buildExternalVersionTarget(name, packageName, currentVersion) {
367
- return {
368
- name,
369
- packageName,
370
- currentVersion,
371
- latestVersion: 'managed-externally',
372
- status: 'external'
373
- };
374
- }
375
- function buildCommandStep(name, result) {
376
- return {
377
- name,
378
- command: result.command,
379
- args: result.args,
380
- cwd: result.cwd,
381
- stdout: result.stdout,
382
- stderr: result.stderr
383
- };
384
- }
385
- function parseOpenClawVersion(stdout) {
386
- const match = stdout.match(/OpenClaw\s+([^\s(]+)/i);
387
- if (!match) {
388
- throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'Unable to parse OpenClaw version', {
389
- stdout
390
- });
391
- }
392
- return normalizeVersion(match[1]);
393
- }
394
330
  function normalizePackageName(value) {
395
331
  if (typeof value !== 'string' || !value.trim()) {
396
332
  return null;
@@ -403,3 +339,6 @@ function normalizeVersion(value) {
403
339
  }
404
340
  return value.trim().replace(/^v/i, '');
405
341
  }
342
+ function isPathSourceUpdateSkip(output) {
343
+ return /Skipping\s+"?rol-websocket-channel"?\s+\(source:\s*path\)/i.test(output);
344
+ }
@@ -606,8 +606,6 @@ export class MessageHandler {
606
606
  });
607
607
  }
608
608
 
609
- // OpenClaw core updates are managed outside this plugin.
610
-
611
609
  async pluginSelfUpdate(data: any): Promise<any> {
612
610
  return wrapAdminCall(async () => {
613
611
  const context = getContext();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rol-websocket-channel",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "Unified OpenClaw plugin: MQTT Channel + Admin Bridge for remote management",
5
5
  "license": "MIT",
6
6
  "author": "nixgnehc",
@@ -55,7 +55,6 @@ const methods = new Map<string, MethodHandler>([
55
55
  ['system.stop', stop],
56
56
  ['system.doctorFix', doctorFix],
57
57
  ['system.logs', logs],
58
- // OpenClaw core updates are managed outside this plugin.
59
58
  ['system.pluginSelfUpdate', pluginSelfUpdate],
60
59
  ['system.currentVersion', currentVersion],
61
60
 
@@ -10,7 +10,6 @@ const execAsync = promisify(exec);
10
10
  const execFileAsync = promisify(execFile);
11
11
  const UPDATE_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;
12
12
  const UPDATE_COMMAND_MAX_BUFFER = 10 * 1024 * 1024;
13
- const OPENCLAW_UPDATE_TARGET_VERSION = '2026.5.6';
14
13
  const CHANNEL_FALLBACK_VERSION = '1.5.9';
15
14
 
16
15
  export const ping: MethodHandler = async (): Promise<JsonValue> => {
@@ -81,22 +80,26 @@ export const doctorFix: MethodHandler = async (_params, context: MethodContext):
81
80
  }
82
81
  };
83
82
 
84
- export const openclawUpdate: MethodHandler = async (): Promise<JsonValue> => {
85
- // OpenClaw core updates are intentionally not exposed by this plugin.
86
- return {
87
- ok: false,
88
- action: 'openclawUpdate',
89
- disabled: true,
90
- restartRecommended: false
91
- };
92
- };
93
-
94
83
  export const pluginSelfUpdate: MethodHandler = async (_params, context: MethodContext): Promise<JsonValue> => {
95
84
  const result = await runOpenClawCommand(
96
85
  ['plugins', 'update', 'rol-websocket-channel'],
97
86
  context,
98
87
  'pluginSelfUpdate'
99
88
  );
89
+ const output = `${result.stdout}\n${result.stderr}`;
90
+
91
+ if (isPathSourceUpdateSkip(output)) {
92
+ return {
93
+ ok: false,
94
+ action: 'pluginSelfUpdate',
95
+ plugin: 'rol-websocket-channel',
96
+ skipped: true,
97
+ reason: 'path-source-plugin',
98
+ message: 'rol-websocket-channel is installed as a local path/global plugin, so OpenClaw plugins update skipped it.',
99
+ restartRecommended: false,
100
+ ...result
101
+ };
102
+ }
100
103
 
101
104
  return {
102
105
  ok: true,
@@ -108,17 +111,14 @@ export const pluginSelfUpdate: MethodHandler = async (_params, context: MethodCo
108
111
  };
109
112
 
110
113
  export const currentVersion: MethodHandler = async (_params, context: MethodContext): Promise<JsonValue> => {
111
- const [openclawCurrentResult, channelPackage, registryInfo] = await Promise.all([
112
- runOpenClawCommand(['--version'], context, 'currentVersion.openclaw'),
114
+ const [channelPackage, registryInfo] = await Promise.all([
113
115
  readJsonFile<{ name?: string; version?: string }>(path.join(context.projectRoot, 'package.json')),
114
116
  queryNpmRegistry('rol-websocket-channel', context)
115
117
  ]);
116
118
 
117
- const openclawCurrentVersion = parseOpenClawVersion(openclawCurrentResult.stdout);
118
119
  const channelCurrentVersion = normalizeVersion(channelPackage.version);
119
120
  const channelLatestVersion = registryInfo.version;
120
121
  const targets = [
121
- buildExternalVersionTarget('openclaw', 'openclaw', openclawCurrentVersion),
122
122
  buildCurrentVersionTarget(
123
123
  'channel',
124
124
  normalizePackageName(channelPackage.name) || 'rol-websocket-channel',
@@ -127,7 +127,7 @@ export const currentVersion: MethodHandler = async (_params, context: MethodCont
127
127
  )
128
128
  ];
129
129
 
130
- const upgradeTargets = targets.filter((t) => t.name !== 'openclaw' && t.status === 'outdated');
130
+ const upgradeTargets = targets.filter((t) => t.status === 'outdated');
131
131
 
132
132
  return {
133
133
  ok: true,
@@ -324,59 +324,6 @@ async function runOpenClawCommand(
324
324
  }
325
325
  }
326
326
 
327
- async function runSystemCommand(
328
- command: string,
329
- args: string[],
330
- cwd: string,
331
- context: MethodContext,
332
- action: string
333
- ): Promise<{ command: string; args: string[]; cwd: string; stdout: string; stderr: string }> {
334
- const options = buildExecOptions(cwd, context.openclawRoot);
335
-
336
- console.log(
337
- `[system] exec start: action=${action}, command=${command}, args=${JSON.stringify(args)}, cwd=${options.cwd}`
338
- );
339
-
340
- try {
341
- const { stdout, stderr } = await execFileAsync(command, args, {
342
- ...options,
343
- timeout: UPDATE_COMMAND_TIMEOUT_MS,
344
- maxBuffer: UPDATE_COMMAND_MAX_BUFFER
345
- });
346
-
347
- console.log(
348
- `[system] exec success: action=${action}, command=${command}, args=${JSON.stringify(args)}, stdoutLength=${stdout.length}, stderrLength=${stderr.length}`
349
- );
350
-
351
- return {
352
- command,
353
- args,
354
- cwd: options.cwd,
355
- stdout,
356
- stderr
357
- };
358
- } catch (err: any) {
359
- const stdout = typeof err?.stdout === 'string' ? err.stdout : '';
360
- const stderr = typeof err?.stderr === 'string' ? err.stderr : '';
361
- console.error(
362
- `[system] exec failed: action=${action}, command=${command}, args=${JSON.stringify(args)}, cwd=${options.cwd}, stdout=${JSON.stringify(stdout)}, stderr=${JSON.stringify(stderr)}`
363
- );
364
-
365
- throw new JsonRpcException(
366
- JSON_RPC_ERRORS.internalError,
367
- `OpenClaw system command failed: ${err instanceof Error ? err.message : String(err)}`,
368
- {
369
- action,
370
- command,
371
- args,
372
- cwd: options.cwd,
373
- stdout,
374
- stderr
375
- }
376
- );
377
- }
378
- }
379
-
380
327
  function buildExecOptions(
381
328
  cwd: string,
382
329
  openclawRoot?: string
@@ -421,7 +368,6 @@ function resolveOpenClawHomeForCli(openclawRoot?: string): string | undefined {
421
368
 
422
369
  interface NpmRegistryInfo {
423
370
  version: string;
424
- requiredOpenclawVersion: string | null;
425
371
  }
426
372
 
427
373
  async function queryNpmRegistry(
@@ -439,23 +385,18 @@ async function queryNpmRegistry(
439
385
 
440
386
  const data = JSON.parse(stdout);
441
387
  const version = typeof data.version === 'string' ? data.version : null;
442
- const requiredOpenclawVersion =
443
- data?.openclaw?.build?.openclawVersion ??
444
- data?.openclaw?.compat?.pluginApi ??
445
- null;
446
388
 
447
389
  if (!version) {
448
390
  console.error(`[system] queryNpmRegistry: no version field in npm view output`);
449
- return { version: CHANNEL_FALLBACK_VERSION, requiredOpenclawVersion: OPENCLAW_UPDATE_TARGET_VERSION };
391
+ return { version: CHANNEL_FALLBACK_VERSION };
450
392
  }
451
393
 
452
- return { version, requiredOpenclawVersion };
394
+ return { version };
453
395
  } catch (err) {
454
396
  console.error(
455
397
  `[system] queryNpmRegistry failed: ${err instanceof Error ? err.message : String(err)}`
456
398
  );
457
- // 查询失败时回退到硬编码的已知版本,不阻塞整个接口
458
- return { version: CHANNEL_FALLBACK_VERSION, requiredOpenclawVersion: OPENCLAW_UPDATE_TARGET_VERSION };
399
+ return { version: CHANNEL_FALLBACK_VERSION };
459
400
  }
460
401
  }
461
402
 
@@ -475,45 +416,6 @@ function buildCurrentVersionTarget(
475
416
  };
476
417
  }
477
418
 
478
- function buildExternalVersionTarget(
479
- name: string,
480
- packageName: string,
481
- currentVersion: string
482
- ): Record<string, JsonValue> {
483
- return {
484
- name,
485
- packageName,
486
- currentVersion,
487
- latestVersion: 'managed-externally',
488
- status: 'external'
489
- };
490
- }
491
-
492
- function buildCommandStep(
493
- name: string,
494
- result: { command: string; args: string[]; cwd: string; stdout: string; stderr: string }
495
- ): Record<string, JsonValue> {
496
- return {
497
- name,
498
- command: result.command,
499
- args: result.args,
500
- cwd: result.cwd,
501
- stdout: result.stdout,
502
- stderr: result.stderr
503
- };
504
- }
505
-
506
- function parseOpenClawVersion(stdout: string): string {
507
- const match = stdout.match(/OpenClaw\s+([^\s(]+)/i);
508
- if (!match) {
509
- throw new JsonRpcException(JSON_RPC_ERRORS.internalError, 'Unable to parse OpenClaw version', {
510
- stdout
511
- });
512
- }
513
-
514
- return normalizeVersion(match[1]);
515
- }
516
-
517
419
  function normalizePackageName(value: string | undefined): string | null {
518
420
  if (typeof value !== 'string' || !value.trim()) {
519
421
  return null;
@@ -529,3 +431,7 @@ function normalizeVersion(value: string | undefined): string {
529
431
 
530
432
  return value.trim().replace(/^v/i, '');
531
433
  }
434
+
435
+ function isPathSourceUpdateSkip(output: string): boolean {
436
+ return /Skipping\s+"?rol-websocket-channel"?\s+\(source:\s*path\)/i.test(output);
437
+ }
@@ -1,166 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import fs from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { afterEach, describe, test } from 'node:test';
6
- import { currentVersion, openclawUpdate } from './system.js';
7
- const tempDirs = [];
8
- const ORIGINAL_OPENCLAW_BIN = process.env.OPENCLAW_BIN;
9
- const ORIGINAL_NPM_BIN = process.env.NPM_BIN;
10
- afterEach(async () => {
11
- if (ORIGINAL_OPENCLAW_BIN === undefined) {
12
- delete process.env.OPENCLAW_BIN;
13
- }
14
- else {
15
- process.env.OPENCLAW_BIN = ORIGINAL_OPENCLAW_BIN;
16
- }
17
- if (ORIGINAL_NPM_BIN === undefined) {
18
- delete process.env.NPM_BIN;
19
- }
20
- else {
21
- process.env.NPM_BIN = ORIGINAL_NPM_BIN;
22
- }
23
- while (tempDirs.length > 0) {
24
- const dir = tempDirs.pop();
25
- if (dir) {
26
- await fs.rm(dir, { recursive: true, force: true });
27
- }
28
- }
29
- });
30
- describe('currentVersion', () => {
31
- test('returns current OpenClaw and channel versions without checking remote latest versions', async () => {
32
- const context = await createMethodContext();
33
- process.env.OPENCLAW_BIN = await createFakeOpenClawBin(context.projectRoot);
34
- const result = await currentVersion({}, context);
35
- const calls = await readCalls(context.projectRoot);
36
- assert.equal(result.ok, true);
37
- assert.equal(result.action, 'currentVersion');
38
- assert.equal(result.checkOnly, true);
39
- assert.equal('guaranteeLatest' in result, false);
40
- assert.equal(result.restartRecommended, false);
41
- assert.deepEqual(result.targets, [
42
- {
43
- name: 'openclaw',
44
- packageName: 'openclaw',
45
- currentVersion: '2026.5.7',
46
- status: 'current'
47
- },
48
- {
49
- name: 'channel',
50
- packageName: 'rol-websocket-channel',
51
- currentVersion: '1.4.8',
52
- status: 'current'
53
- }
54
- ]);
55
- assert.equal(calls.some((call) => call.includes('update')), false);
56
- assert.deepEqual(calls, [
57
- ['openclaw', '--version']
58
- ]);
59
- });
60
- });
61
- describe('openclawUpdate', () => {
62
- test('installs the fixed OpenClaw core version and verifies it', async () => {
63
- const context = await createMethodContext();
64
- process.env.OPENCLAW_BIN = await createFakeOpenClawBin(context.projectRoot);
65
- process.env.NPM_BIN = await createFakeNpmBin(context.projectRoot);
66
- const result = await openclawUpdate({}, context);
67
- const calls = await readCalls(context.projectRoot);
68
- assert.equal(result.ok, true);
69
- assert.equal(result.action, 'openclawUpdate');
70
- assert.equal(result.targetPackage, '@openclaw/core');
71
- assert.equal(result.targetVersion, '2026.5.6');
72
- assert.equal(result.restartRecommended, true);
73
- assert.deepEqual(result.steps.map((step) => [step.name, step.args]), [
74
- ['installCore', ['install', '-g', '@openclaw/core@2026.5.6']],
75
- ['version', ['--version']],
76
- ['doctorDeep', ['doctor', '--deep']]
77
- ]);
78
- assert.deepEqual(calls, [
79
- ['npm', 'install', '-g', '@openclaw/core@2026.5.6'],
80
- ['openclaw', '--version'],
81
- ['openclaw', 'doctor', '--deep']
82
- ]);
83
- });
84
- });
85
- async function createMethodContext() {
86
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'rol-system-'));
87
- tempDirs.push(dir);
88
- await fs.writeFile(path.join(dir, 'package.json'), `${JSON.stringify({
89
- name: 'rol-websocket-channel',
90
- version: '1.4.8'
91
- }, null, 2)}\n`, 'utf8');
92
- return {
93
- projectRoot: dir,
94
- openclawRoot: dir
95
- };
96
- }
97
- async function createFakeOpenClawBin(root) {
98
- if (process.platform === 'win32') {
99
- const scriptPath = path.join(root, 'fake-openclaw.cmd');
100
- await fs.writeFile(scriptPath, [
101
- '@echo off',
102
- `node "%~dp0fake-openclaw.js" %*`
103
- ].join('\r\n'), 'utf8');
104
- await createFakeOpenClawJs(root);
105
- return scriptPath;
106
- }
107
- const scriptPath = path.join(root, 'fake-openclaw');
108
- await fs.writeFile(scriptPath, [
109
- '#!/bin/sh',
110
- 'node "$(dirname "$0")/fake-openclaw.js" "$@"'
111
- ].join('\n'), 'utf8');
112
- await fs.chmod(scriptPath, 0o755);
113
- await createFakeOpenClawJs(root);
114
- return scriptPath;
115
- }
116
- async function createFakeOpenClawJs(root) {
117
- await fs.writeFile(path.join(root, 'fake-openclaw.js'), [
118
- "import fs from 'node:fs';",
119
- "import path from 'node:path';",
120
- 'const file = path.join(process.cwd(), "calls.ndjson");',
121
- 'fs.appendFileSync(file, `${JSON.stringify(["openclaw", ...process.argv.slice(2)])}\\n`);',
122
- 'console.log("OpenClaw 2026.5.7 (eeef486)");'
123
- ].join('\n'), 'utf8');
124
- }
125
- async function createFakeNpmBin(root) {
126
- if (process.platform === 'win32') {
127
- const scriptPath = path.join(root, 'fake-npm.cmd');
128
- await fs.writeFile(scriptPath, [
129
- '@echo off',
130
- `node "%~dp0fake-npm.js" %*`
131
- ].join('\r\n'), 'utf8');
132
- await createFakeNpmJs(root);
133
- return scriptPath;
134
- }
135
- const scriptPath = path.join(root, 'fake-npm');
136
- await fs.writeFile(scriptPath, [
137
- '#!/bin/sh',
138
- 'node "$(dirname "$0")/fake-npm.js" "$@"'
139
- ].join('\n'), 'utf8');
140
- await fs.chmod(scriptPath, 0o755);
141
- await createFakeNpmJs(root);
142
- return scriptPath;
143
- }
144
- async function createFakeNpmJs(root) {
145
- await fs.writeFile(path.join(root, 'fake-npm.js'), [
146
- "import fs from 'node:fs';",
147
- "import path from 'node:path';",
148
- 'const args = process.argv.slice(2);',
149
- 'const file = path.join(process.cwd(), "calls.ndjson");',
150
- 'fs.appendFileSync(file, `${JSON.stringify(["npm", ...args])}\\n`);',
151
- 'if (args.join(" ") === "view openclaw version --json") {',
152
- ' console.log(JSON.stringify("2026.5.7"));',
153
- '} else if (args.join(" ") === "view rol-websocket-channel version --json") {',
154
- ' console.log(JSON.stringify("1.4.8"));',
155
- '} else if (args.join(" ") === "install -g @openclaw/core@2026.5.6") {',
156
- ' console.log("installed @openclaw/core@2026.5.6");',
157
- '} else {',
158
- ' console.error(`Unexpected npm args: ${args.join(" ")}`);',
159
- ' process.exit(1);',
160
- '}'
161
- ].join('\n'), 'utf8');
162
- }
163
- async function readCalls(root) {
164
- const content = await fs.readFile(path.join(root, 'calls.ndjson'), 'utf8');
165
- return content.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
166
- }
@@ -1,233 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import fs from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { afterEach, describe, test } from 'node:test';
6
-
7
- import { currentVersion, openclawUpdate } from './system.js';
8
- import type { MethodContext } from '../types.js';
9
-
10
- const tempDirs: string[] = [];
11
- const ORIGINAL_OPENCLAW_BIN = process.env.OPENCLAW_BIN;
12
- const ORIGINAL_NPM_BIN = process.env.NPM_BIN;
13
-
14
- afterEach(async () => {
15
- if (ORIGINAL_OPENCLAW_BIN === undefined) {
16
- delete process.env.OPENCLAW_BIN;
17
- } else {
18
- process.env.OPENCLAW_BIN = ORIGINAL_OPENCLAW_BIN;
19
- }
20
-
21
- if (ORIGINAL_NPM_BIN === undefined) {
22
- delete process.env.NPM_BIN;
23
- } else {
24
- process.env.NPM_BIN = ORIGINAL_NPM_BIN;
25
- }
26
-
27
- while (tempDirs.length > 0) {
28
- const dir = tempDirs.pop();
29
- if (dir) {
30
- await fs.rm(dir, { recursive: true, force: true });
31
- }
32
- }
33
- });
34
-
35
- describe('currentVersion', () => {
36
- test('returns current OpenClaw and channel versions without checking remote latest versions', async () => {
37
- const context = await createMethodContext();
38
- process.env.OPENCLAW_BIN = await createFakeOpenClawBin(context.projectRoot);
39
-
40
- const result = await currentVersion({}, context) as {
41
- ok: boolean;
42
- action: string;
43
- checkOnly: boolean;
44
- guaranteeLatest?: boolean;
45
- targets: Array<{
46
- name: string;
47
- packageName: string;
48
- currentVersion: string;
49
- status: string;
50
- }>;
51
- restartRecommended: boolean;
52
- };
53
- const calls = await readCalls(context.projectRoot);
54
-
55
- assert.equal(result.ok, true);
56
- assert.equal(result.action, 'currentVersion');
57
- assert.equal(result.checkOnly, true);
58
- assert.equal('guaranteeLatest' in result, false);
59
- assert.equal(result.restartRecommended, false);
60
- assert.deepEqual(result.targets, [
61
- {
62
- name: 'openclaw',
63
- packageName: 'openclaw',
64
- currentVersion: '2026.5.7',
65
- status: 'current'
66
- },
67
- {
68
- name: 'channel',
69
- packageName: 'rol-websocket-channel',
70
- currentVersion: '1.4.8',
71
- status: 'current'
72
- }
73
- ]);
74
- assert.equal(
75
- calls.some((call: string[]) => call.includes('update')),
76
- false
77
- );
78
- assert.deepEqual(calls, [
79
- ['openclaw', '--version']
80
- ]);
81
- });
82
- });
83
-
84
- describe('openclawUpdate', () => {
85
- test('installs the fixed OpenClaw core version and verifies it', async () => {
86
- const context = await createMethodContext();
87
- process.env.OPENCLAW_BIN = await createFakeOpenClawBin(context.projectRoot);
88
- process.env.NPM_BIN = await createFakeNpmBin(context.projectRoot);
89
-
90
- const result = await openclawUpdate({}, context) as {
91
- ok: boolean;
92
- action: string;
93
- targetPackage: string;
94
- targetVersion: string;
95
- restartRecommended: boolean;
96
- steps: Array<{
97
- name: string;
98
- args: string[];
99
- }>;
100
- };
101
- const calls = await readCalls(context.projectRoot);
102
-
103
- assert.equal(result.ok, true);
104
- assert.equal(result.action, 'openclawUpdate');
105
- assert.equal(result.targetPackage, '@openclaw/core');
106
- assert.equal(result.targetVersion, '2026.5.6');
107
- assert.equal(result.restartRecommended, true);
108
- assert.deepEqual(result.steps.map((step) => [step.name, step.args]), [
109
- ['installCore', ['install', '-g', '@openclaw/core@2026.5.6']],
110
- ['version', ['--version']],
111
- ['doctorDeep', ['doctor', '--deep']]
112
- ]);
113
- assert.deepEqual(calls, [
114
- ['npm', 'install', '-g', '@openclaw/core@2026.5.6'],
115
- ['openclaw', '--version'],
116
- ['openclaw', 'doctor', '--deep']
117
- ]);
118
- });
119
- });
120
-
121
- async function createMethodContext(): Promise<MethodContext> {
122
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'rol-system-'));
123
- tempDirs.push(dir);
124
- await fs.writeFile(path.join(dir, 'package.json'), `${JSON.stringify({
125
- name: 'rol-websocket-channel',
126
- version: '1.4.8'
127
- }, null, 2)}\n`, 'utf8');
128
- return {
129
- projectRoot: dir,
130
- openclawRoot: dir
131
- };
132
- }
133
-
134
- async function createFakeOpenClawBin(root: string): Promise<string> {
135
- if (process.platform === 'win32') {
136
- const scriptPath = path.join(root, 'fake-openclaw.cmd');
137
- await fs.writeFile(
138
- scriptPath,
139
- [
140
- '@echo off',
141
- `node "%~dp0fake-openclaw.js" %*`
142
- ].join('\r\n'),
143
- 'utf8'
144
- );
145
- await createFakeOpenClawJs(root);
146
- return scriptPath;
147
- }
148
-
149
- const scriptPath = path.join(root, 'fake-openclaw');
150
- await fs.writeFile(
151
- scriptPath,
152
- [
153
- '#!/bin/sh',
154
- 'node "$(dirname "$0")/fake-openclaw.js" "$@"'
155
- ].join('\n'),
156
- 'utf8'
157
- );
158
- await fs.chmod(scriptPath, 0o755);
159
- await createFakeOpenClawJs(root);
160
- return scriptPath;
161
- }
162
-
163
- async function createFakeOpenClawJs(root: string): Promise<void> {
164
- await fs.writeFile(
165
- path.join(root, 'fake-openclaw.js'),
166
- [
167
- "import fs from 'node:fs';",
168
- "import path from 'node:path';",
169
- 'const file = path.join(process.cwd(), "calls.ndjson");',
170
- 'fs.appendFileSync(file, `${JSON.stringify(["openclaw", ...process.argv.slice(2)])}\\n`);',
171
- 'console.log("OpenClaw 2026.5.7 (eeef486)");'
172
- ].join('\n'),
173
- 'utf8'
174
- );
175
- }
176
-
177
- async function createFakeNpmBin(root: string): Promise<string> {
178
- if (process.platform === 'win32') {
179
- const scriptPath = path.join(root, 'fake-npm.cmd');
180
- await fs.writeFile(
181
- scriptPath,
182
- [
183
- '@echo off',
184
- `node "%~dp0fake-npm.js" %*`
185
- ].join('\r\n'),
186
- 'utf8'
187
- );
188
- await createFakeNpmJs(root);
189
- return scriptPath;
190
- }
191
-
192
- const scriptPath = path.join(root, 'fake-npm');
193
- await fs.writeFile(
194
- scriptPath,
195
- [
196
- '#!/bin/sh',
197
- 'node "$(dirname "$0")/fake-npm.js" "$@"'
198
- ].join('\n'),
199
- 'utf8'
200
- );
201
- await fs.chmod(scriptPath, 0o755);
202
- await createFakeNpmJs(root);
203
- return scriptPath;
204
- }
205
-
206
- async function createFakeNpmJs(root: string): Promise<void> {
207
- await fs.writeFile(
208
- path.join(root, 'fake-npm.js'),
209
- [
210
- "import fs from 'node:fs';",
211
- "import path from 'node:path';",
212
- 'const args = process.argv.slice(2);',
213
- 'const file = path.join(process.cwd(), "calls.ndjson");',
214
- 'fs.appendFileSync(file, `${JSON.stringify(["npm", ...args])}\\n`);',
215
- 'if (args.join(" ") === "view openclaw version --json") {',
216
- ' console.log(JSON.stringify("2026.5.7"));',
217
- '} else if (args.join(" ") === "view rol-websocket-channel version --json") {',
218
- ' console.log(JSON.stringify("1.4.8"));',
219
- '} else if (args.join(" ") === "install -g @openclaw/core@2026.5.6") {',
220
- ' console.log("installed @openclaw/core@2026.5.6");',
221
- '} else {',
222
- ' console.error(`Unexpected npm args: ${args.join(" ")}`);',
223
- ' process.exit(1);',
224
- '}'
225
- ].join('\n'),
226
- 'utf8'
227
- );
228
- }
229
-
230
- async function readCalls(root: string): Promise<string[][]> {
231
- const content = await fs.readFile(path.join(root, 'calls.ndjson'), 'utf8');
232
- return content.trim().split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line) as string[]);
233
- }
@@ -1,77 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
-
4
- import { handleCustomMessageType } from '../index.js';
5
- import { messageHandler } from '../message-handler.js';
6
- import {
7
- _setMqttConnectFn,
8
- closeGlobalConnection,
9
- createGlobalMqttConnection
10
- } from '../src/mqtt/connection-manager.js';
11
-
12
- test('openclawUpdate publishes an immediate running response before the command finishes', async () => {
13
- const published: Array<{ topic: string; message: string }> = [];
14
- const originalOpenclawUpdate = (messageHandler as any).openclawUpdate;
15
- let finish!: () => void;
16
- const commandFinished = new Promise<void>((resolve) => {
17
- finish = resolve;
18
- });
19
-
20
- const fakeClient = {
21
- connected: true,
22
- on() {},
23
- subscribe() {},
24
- end() {},
25
- publish(topic: string, message: string) {
26
- published.push({ topic, message });
27
- }
28
- };
29
-
30
- _setMqttConnectFn(() => fakeClient as any);
31
- await createGlobalMqttConnection('mqtt://test', 'announcement/user/agent/#', {});
32
-
33
- try {
34
- (messageHandler as any).openclawUpdate = async () => {
35
- await commandFinished;
36
- return { ok: true, result: { ok: true, action: 'openclawUpdate' } };
37
- };
38
-
39
- const task = handleCustomMessageType(
40
- 'openclawUpdate',
41
- { source_type: 'device' },
42
- 'trace-update-001',
43
- 'default',
44
- 'announcement/user/agent/#'
45
- );
46
-
47
- await new Promise((resolve) => setImmediate(resolve));
48
-
49
- assert.equal(published.length, 1);
50
- const first = JSON.parse(published[0]!.message);
51
- assert.equal(published[0]!.topic, 'announcement/user/agent/device');
52
- assert.equal(first.trace_id, 'trace-update-001');
53
- assert.equal(first.success, true);
54
- assert.equal(first.data.status, 'running');
55
-
56
- await task;
57
- finish();
58
- await waitFor(() => published.length === 2);
59
-
60
- assert.equal(published.length, 2);
61
- const final = JSON.parse(published[1]!.message);
62
- assert.equal(final.trace_id, 'trace-update-001');
63
- assert.equal(final.success, true);
64
- assert.deepEqual(final.data, { ok: true, action: 'openclawUpdate' });
65
- } finally {
66
- (messageHandler as any).openclawUpdate = originalOpenclawUpdate;
67
- closeGlobalConnection();
68
- _setMqttConnectFn(null);
69
- }
70
- });
71
-
72
- async function waitFor(predicate: () => boolean): Promise<void> {
73
- for (let i = 0; i < 20; i += 1) {
74
- if (predicate()) return;
75
- await new Promise((resolve) => setTimeout(resolve, 10));
76
- }
77
- }