@xfxstudio/claworld 0.2.10-beta.1 → 0.2.10-beta.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.
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "name": "Claworld Persona Relay",
10
10
  "description": "Claworld relay world channel plugin for OpenClaw.",
11
- "version": "0.2.10-beta.1",
11
+ "version": "0.2.10-beta.3",
12
12
  "configSchema": {
13
13
  "type": "object",
14
14
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "0.2.10-beta.1",
3
+ "version": "0.2.10-beta.3",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -7,7 +7,7 @@ import {
7
7
  CLAWORLD_DOCTOR_COMMAND,
8
8
  CLAWORLD_INSTALLER_BIN_NAME,
9
9
  CLAWORLD_INSTALLER_COMMAND,
10
- CLAWORLD_INSTALLER_PACKAGE_NAME,
10
+ CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
11
11
  CLAWORLD_UNINSTALL_COMMAND,
12
12
  CLAWORLD_UPDATE_COMMAND,
13
13
  } from './constants.js';
@@ -57,7 +57,7 @@ Install options:
57
57
  --tool-profile <profile> minimal | default | full | world
58
58
  --repo-root <path> Local repo root when using --plugin-install-mode link|copy
59
59
  --plugin-install-mode <m> npm | link | copy | skip (default: npm)
60
- --plugin-source <value> npm package or local path (default: ${CLAWORLD_INSTALLER_PACKAGE_NAME})
60
+ --plugin-source <value> npm package or local path (default: ${CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE})
61
61
  Update options:
62
62
  --config <path> OpenClaw config path (default: ${DEFAULT_OPENCLAW_CONFIG_PATH})
63
63
  --state-dir <path> Optional OPENCLAW_STATE_DIR for OpenClaw commands
@@ -118,7 +118,7 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
118
118
  displayName: null,
119
119
  repoRoot: null,
120
120
  pluginInstallMode: 'npm',
121
- pluginInstallSource: CLAWORLD_INSTALLER_PACKAGE_NAME,
121
+ pluginInstallSource: CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
122
122
  toolProfile: null,
123
123
  },
124
124
  update: {
@@ -266,7 +266,7 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
266
266
  options.command === 'install'
267
267
  && options.install.pluginInstallMode === 'link'
268
268
  && !options.install.repoRoot
269
- && options.install.pluginInstallSource === CLAWORLD_INSTALLER_PACKAGE_NAME
269
+ && options.install.pluginInstallSource === CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE
270
270
  ) {
271
271
  throw new Error('Link install mode requires --repo-root or --plugin-source <local-path>.');
272
272
  }
@@ -274,7 +274,7 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
274
274
  options.command === 'install'
275
275
  && options.install.pluginInstallMode === 'copy'
276
276
  && !options.install.repoRoot
277
- && options.install.pluginInstallSource === CLAWORLD_INSTALLER_PACKAGE_NAME
277
+ && options.install.pluginInstallSource === CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE
278
278
  ) {
279
279
  throw new Error('Copy install mode requires --repo-root or --plugin-source <local-path>.');
280
280
  }
@@ -283,7 +283,7 @@ export function parseInstallerCliArgs(argv = process.argv.slice(2), env = proces
283
283
  options.command === 'install'
284
284
  && options.install.pluginInstallMode !== 'npm'
285
285
  && options.install.repoRoot
286
- && options.install.pluginInstallSource === CLAWORLD_INSTALLER_PACKAGE_NAME
286
+ && options.install.pluginInstallSource === CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE
287
287
  ) {
288
288
  options.install.pluginInstallSource = options.install.repoRoot;
289
289
  }
@@ -1,5 +1,12 @@
1
+ import { createRequire } from 'module';
2
+
3
+ const require = createRequire(import.meta.url);
4
+ const installerPackageJson = require('../../../package.json');
5
+
1
6
  export const CLAWORLD_INSTALLER_BIN_NAME = 'claworld';
2
7
  export const CLAWORLD_INSTALLER_PACKAGE_NAME = '@xfxstudio/claworld';
8
+ export const CLAWORLD_INSTALLER_PACKAGE_VERSION = installerPackageJson.version;
9
+ export const CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE = `${CLAWORLD_INSTALLER_PACKAGE_NAME}@${CLAWORLD_INSTALLER_PACKAGE_VERSION}`;
3
10
  export const CLAWORLD_INSTALLER_COMMAND = 'npx -y @xfxstudio/claworld install';
4
11
  export const CLAWORLD_DOCTOR_COMMAND = 'npx -y @xfxstudio/claworld doctor';
5
12
  export const CLAWORLD_UPDATE_COMMAND = 'npx -y @xfxstudio/claworld update';
@@ -5,10 +5,10 @@ import path from 'path';
5
5
  import { spawnSync } from 'child_process';
6
6
  import vm from 'vm';
7
7
  import {
8
+ applyClaworldBootstrapConfig,
8
9
  DEFAULT_CLAWORLD_ACCOUNT_ID,
9
10
  DEFAULT_CLAWORLD_AGENT_ID,
10
11
  DEFAULT_CLAWORLD_SERVER_URL,
11
- applyClaworldManagedRuntimeConfig,
12
12
  ensureObject,
13
13
  expandUserPath,
14
14
  findClaworldManagedRuntimeBackup,
@@ -24,6 +24,7 @@ import {
24
24
  } from '../plugin/config-schema.js';
25
25
  import {
26
26
  CLAWORLD_INSTALLER_COMMAND,
27
+ CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
27
28
  CLAWORLD_INSTALLER_PACKAGE_NAME,
28
29
  CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
29
30
  CLAWORLD_UNINSTALL_COMMAND,
@@ -32,6 +33,7 @@ import {
32
33
  import { seedManagedWorkspaceContract } from './workspace-contract.js';
33
34
 
34
35
  export const DEFAULT_OPENCLAW_BIN = 'openclaw';
36
+ const DEFAULT_NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm';
35
37
  export const DEFAULT_OPENCLAW_CONFIG_PATH = '~/.openclaw/openclaw.json';
36
38
  export const DEFAULT_OPENCLAW_STATE_DIR = null;
37
39
  export const DEFAULT_INSTALL_TIMEOUT_MS = 15_000;
@@ -54,9 +56,28 @@ function normalizeComparablePath(value) {
54
56
  return path.resolve(String(value));
55
57
  }
56
58
 
59
+ function splitPackageNameSegments(packageName = CLAWORLD_INSTALLER_PACKAGE_NAME) {
60
+ const normalized = String(packageName || '').trim();
61
+ if (!normalized) return ['claworld'];
62
+ return normalized.split('/').filter(Boolean);
63
+ }
64
+
65
+ function resolveInstallerManagedPluginInstallRoot(configPath = DEFAULT_OPENCLAW_CONFIG_PATH) {
66
+ const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
67
+ return path.join(path.dirname(resolvedConfigPath), 'extensions', 'claworld');
68
+ }
69
+
70
+ function resolveInstallerManagedPluginSourcePath(installRoot, packageName = CLAWORLD_INSTALLER_PACKAGE_NAME) {
71
+ return path.join(installRoot, 'node_modules', ...splitPackageNameSegments(packageName));
72
+ }
73
+
74
+ async function readJsonFile(filePath) {
75
+ return JSON.parse(await fs.readFile(filePath, 'utf8'));
76
+ }
77
+
57
78
  async function resolveLocalPluginInstallTarget({
58
79
  installMode = 'npm',
59
- installSource = CLAWORLD_INSTALLER_PACKAGE_NAME,
80
+ installSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
60
81
  repoRoot = null,
61
82
  commandRunner = defaultCommandRunner,
62
83
  cwd = process.cwd(),
@@ -85,6 +106,7 @@ async function resolveLocalPluginInstallTarget({
85
106
  resolvedInstallSource === derivedRepoRoot
86
107
  || resolvedInstallSource === path.join(derivedRepoRoot, 'packages', 'openclaw-plugin')
87
108
  || resolvedInstallSource === CLAWORLD_INSTALLER_PACKAGE_NAME
109
+ || resolvedInstallSource === CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE
88
110
  ),
89
111
  );
90
112
 
@@ -432,18 +454,44 @@ function parseBindingLine(text = '', { agentId, accountId } = {}) {
432
454
  .find((line) => line === `- ${agentId} <- claworld accountId=${accountId}`) || null;
433
455
  }
434
456
 
457
+ function normalizePathSuffix(value = '') {
458
+ return String(value || '').replace(/\\/g, '/').replace(/\/+$/, '');
459
+ }
460
+
461
+ function isInstallerManagedLocalSourceRecord(installRecord = {}) {
462
+ const source = normalizeText(installRecord.source, null);
463
+ if (source !== 'path') {
464
+ return false;
465
+ }
466
+ const spec = normalizeText(installRecord.spec, null) || normalizeText(installRecord.resolvedSpec, null);
467
+ if (!spec || !spec.startsWith('@xfxstudio/claworld@')) {
468
+ return false;
469
+ }
470
+ const installPath = normalizePathSuffix(installRecord.installPath);
471
+ const sourcePath = normalizePathSuffix(installRecord.sourcePath);
472
+ return (
473
+ installPath.endsWith('/extensions/claworld')
474
+ && sourcePath.endsWith('/extensions/claworld/node_modules/@xfxstudio/claworld')
475
+ );
476
+ }
477
+
435
478
  function inspectTrackedClaworldPluginInstall(config = {}) {
436
479
  const installRecord = ensureObject(config?.plugins?.installs?.claworld);
437
480
  const tracked = Object.keys(installRecord).length > 0;
438
- const source = normalizeText(installRecord.source, null);
481
+ const recordedSource = normalizeText(installRecord.source, null);
482
+ const source = isInstallerManagedLocalSourceRecord(installRecord)
483
+ ? 'installer_npm'
484
+ : recordedSource;
439
485
  return {
440
486
  tracked,
441
487
  updateable: tracked && TRACKED_PLUGIN_UPDATEABLE_SOURCES.has(source),
442
488
  source,
489
+ recordedSource,
443
490
  spec: normalizeText(installRecord.spec, null),
444
491
  resolvedSpec: normalizeText(installRecord.resolvedSpec, null),
445
492
  resolvedVersion: normalizeText(installRecord.resolvedVersion, null),
446
493
  installPath: normalizeText(installRecord.installPath, null),
494
+ sourcePath: normalizeText(installRecord.sourcePath, null),
447
495
  record: tracked ? installRecord : null,
448
496
  };
449
497
  }
@@ -527,6 +575,67 @@ function mergePluginMetadata(config = {}, pluginConfig = {}) {
527
575
  return next;
528
576
  }
529
577
 
578
+ async function installPublishedClaworldPackageToLocalSource({
579
+ installSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
580
+ configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
581
+ commandRunner = defaultCommandRunner,
582
+ cwd = process.cwd(),
583
+ env = process.env,
584
+ dryRun = false,
585
+ refresh = false,
586
+ } = {}) {
587
+ const installRoot = resolveInstallerManagedPluginInstallRoot(configPath);
588
+ const sourcePath = resolveInstallerManagedPluginSourcePath(installRoot);
589
+ if (!dryRun && refresh) {
590
+ await fs.rm(installRoot, { recursive: true, force: true });
591
+ }
592
+ if (!dryRun) {
593
+ await fs.mkdir(installRoot, { recursive: true });
594
+ }
595
+
596
+ await executeCommand({
597
+ commandRunner,
598
+ bin: DEFAULT_NPM_BIN,
599
+ args: [
600
+ 'install',
601
+ '--ignore-scripts',
602
+ '--no-package-lock',
603
+ '--omit=dev',
604
+ '--prefix',
605
+ installRoot,
606
+ installSource,
607
+ ],
608
+ cwd,
609
+ env,
610
+ dryRun,
611
+ capture: false,
612
+ });
613
+
614
+ let resolvedVersion = null;
615
+ if (!dryRun) {
616
+ const sourcePackageJson = await readJsonFile(path.join(sourcePath, 'package.json'));
617
+ resolvedVersion = normalizeText(sourcePackageJson?.version, null);
618
+ }
619
+
620
+ return {
621
+ installRoot,
622
+ sourcePath,
623
+ resolvedVersion,
624
+ pluginConfig: {
625
+ installs: {
626
+ claworld: {
627
+ source: 'path',
628
+ spec: installSource,
629
+ resolvedSpec: installSource,
630
+ ...(resolvedVersion ? { resolvedVersion } : {}),
631
+ installPath: installRoot,
632
+ sourcePath,
633
+ },
634
+ },
635
+ },
636
+ };
637
+ }
638
+
530
639
  function defaultCommandRunner({
531
640
  bin,
532
641
  args,
@@ -863,9 +972,10 @@ export async function ensureClaworldPluginInstalled({
863
972
  env = process.env,
864
973
  dryRun = false,
865
974
  installMode = 'npm',
866
- installSource = CLAWORLD_INSTALLER_PACKAGE_NAME,
975
+ installSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
867
976
  refresh = false,
868
977
  } = {}) {
978
+ const trackedInstall = inspectTrackedClaworldPluginInstall(config);
869
979
  const bootstrap = await preparePluginBootstrapConfig({
870
980
  configPath,
871
981
  config,
@@ -883,7 +993,18 @@ export async function ensureClaworldPluginInstalled({
883
993
  env,
884
994
  dryRun,
885
995
  });
886
- if (before.installed && !refresh) {
996
+ const reusableInstallerManagedNpmInstall = (
997
+ installMode === 'npm'
998
+ && trackedInstall.tracked
999
+ && trackedInstall.source === 'installer_npm'
1000
+ && trackedInstall.recordedSource === 'path'
1001
+ && normalizeText(trackedInstall.sourcePath, null)
1002
+ );
1003
+ if (
1004
+ before.installed
1005
+ && !refresh
1006
+ && (installMode !== 'npm' || reusableInstallerManagedNpmInstall)
1007
+ ) {
887
1008
  return {
888
1009
  changed: false,
889
1010
  action: 'reused_existing_plugin_install',
@@ -901,6 +1022,37 @@ export async function ensureClaworldPluginInstalled({
901
1022
  };
902
1023
  }
903
1024
 
1025
+ if (installMode === 'npm') {
1026
+ const localInstall = await installPublishedClaworldPackageToLocalSource({
1027
+ installSource,
1028
+ configPath,
1029
+ commandRunner,
1030
+ cwd,
1031
+ env,
1032
+ dryRun,
1033
+ refresh,
1034
+ });
1035
+ return {
1036
+ changed: true,
1037
+ action: reusableInstallerManagedNpmInstall ? 'refreshed_local_plugin_source' : 'staged_local_plugin_source',
1038
+ plugin: before,
1039
+ pluginConfig: {
1040
+ ...ensureObject(bootstrap?.pluginConfig),
1041
+ ...ensureObject(localInstall.pluginConfig),
1042
+ entries: {
1043
+ ...ensureObject(bootstrap?.pluginConfig?.entries),
1044
+ ...ensureObject(localInstall.pluginConfig?.entries),
1045
+ },
1046
+ installs: {
1047
+ ...ensureObject(bootstrap?.pluginConfig?.installs),
1048
+ ...ensureObject(localInstall.pluginConfig?.installs),
1049
+ },
1050
+ },
1051
+ installSource,
1052
+ managedRepoRoot: localInstall.sourcePath,
1053
+ };
1054
+ }
1055
+
904
1056
  if (before.installed && refresh && installMode === 'copy') {
905
1057
  await executeCommand({
906
1058
  commandRunner,
@@ -990,6 +1142,26 @@ export async function updateTrackedClaworldPluginInstall({
990
1142
  );
991
1143
  }
992
1144
 
1145
+ if (trackedInstall.source === 'installer_npm') {
1146
+ const localInstall = await installPublishedClaworldPackageToLocalSource({
1147
+ installSource: trackedInstall.spec || CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
1148
+ configPath,
1149
+ commandRunner,
1150
+ cwd,
1151
+ env,
1152
+ dryRun,
1153
+ refresh: true,
1154
+ });
1155
+ return {
1156
+ changed: true,
1157
+ action: dryRun ? 'dry_run_updated_local_plugin_source' : 'updated_local_plugin_source',
1158
+ trackedInstall,
1159
+ plugin: before,
1160
+ pluginConfig: localInstall.pluginConfig,
1161
+ managedRepoRoot: localInstall.sourcePath,
1162
+ };
1163
+ }
1164
+
993
1165
  if (!trackedInstall.tracked) {
994
1166
  return {
995
1167
  changed: false,
@@ -1467,8 +1639,8 @@ async function reconcileManagedClaworldRuntime({
1467
1639
  },
1468
1640
  });
1469
1641
 
1470
- const transformed = applyClaworldManagedRuntimeConfig(currentConfig, managedOptions);
1471
- const configChanged = JSON.stringify(currentConfig) !== JSON.stringify(transformed.config);
1642
+ const transformed = applyClaworldBootstrapConfig(currentConfig, managedOptions);
1643
+ const configChanged = JSON.stringify(currentConfigState.config) !== JSON.stringify(transformed.config);
1472
1644
  const backupPath = configChanged
1473
1645
  ? await backupConfigIfPresent(configPath, currentConfigState.existed, dryRun)
1474
1646
  : null;
@@ -1560,7 +1732,7 @@ export async function runClaworldInstallerInstall({
1560
1732
  sessionDmScope = null,
1561
1733
  repoRoot = null,
1562
1734
  pluginInstallMode = 'npm',
1563
- pluginInstallSource = CLAWORLD_INSTALLER_PACKAGE_NAME,
1735
+ pluginInstallSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
1564
1736
  fetchImpl = globalThis.fetch?.bind(globalThis),
1565
1737
  commandRunner = defaultCommandRunner,
1566
1738
  cwd = process.cwd(),
@@ -1639,7 +1811,7 @@ export async function runClaworldInstallerInstall({
1639
1811
  toolProfile,
1640
1812
  approvalMode,
1641
1813
  sessionDmScope,
1642
- repoRoot: localPluginInstall.managedRepoRoot,
1814
+ repoRoot: plugin.managedRepoRoot || localPluginInstall.managedRepoRoot,
1643
1815
  fetchImpl,
1644
1816
  commandRunner,
1645
1817
  cwd,
@@ -1717,7 +1889,7 @@ export async function runClaworldInstallerUpdate({
1717
1889
  });
1718
1890
 
1719
1891
  const refreshedConfigState = await loadConfigFromDisk(resolvedConfigPath);
1720
- const currentConfig = mergePluginMetadata(refreshedConfigState.config, null);
1892
+ const currentConfig = mergePluginMetadata(refreshedConfigState.config, plugin.pluginConfig || null);
1721
1893
  const lifecycle = await reconcileManagedClaworldRuntime({
1722
1894
  openclawBin,
1723
1895
  configPath: resolvedConfigPath,
@@ -1736,6 +1908,7 @@ export async function runClaworldInstallerUpdate({
1736
1908
  toolProfile,
1737
1909
  approvalMode,
1738
1910
  sessionDmScope,
1911
+ repoRoot: plugin.managedRepoRoot || null,
1739
1912
  fetchImpl,
1740
1913
  commandRunner,
1741
1914
  cwd,
@@ -1778,6 +1951,7 @@ export async function runClaworldInstallerUninstall({
1778
1951
  const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
1779
1952
  const currentInstallerState = (await loadInstallerStateFromDisk(installerStatePath)).state;
1780
1953
  const currentConfig = currentConfigState.config;
1954
+ const trackedInstall = inspectTrackedClaworldPluginInstall(currentConfig);
1781
1955
  const host = await detectOpenclawHost({
1782
1956
  openclawBin,
1783
1957
  commandRunner,
@@ -1841,7 +2015,20 @@ export async function runClaworldInstallerUninstall({
1841
2015
 
1842
2016
  let pluginAction = 'plugin_already_absent';
1843
2017
  try {
1844
- if (pluginBefore.installed) {
2018
+ if (trackedInstall.source === 'installer_npm') {
2019
+ const localInstallPath = normalizeText(trackedInstall.installPath, null) || normalizeText(trackedInstall.sourcePath, null);
2020
+ if (!localInstallPath) {
2021
+ throw createInstallerError(
2022
+ 'claworld_installer_managed_source_missing',
2023
+ 'Installer-managed Claworld source is missing its local install path.',
2024
+ { trackedInstall },
2025
+ );
2026
+ }
2027
+ if (!dryRun) {
2028
+ await fs.rm(localInstallPath, { recursive: true, force: true });
2029
+ }
2030
+ pluginAction = 'removed_local_plugin_source';
2031
+ } else if (pluginBefore.installed) {
1845
2032
  await executeCommand({
1846
2033
  commandRunner,
1847
2034
  bin: openclawBin,
@@ -1881,7 +2068,7 @@ export async function runClaworldInstallerUninstall({
1881
2068
  env,
1882
2069
  dryRun,
1883
2070
  });
1884
- if (pluginBefore.installed && pluginAfter.installed) {
2071
+ if ((pluginBefore.installed || trackedInstall.source === 'installer_npm') && pluginAfter.installed) {
1885
2072
  throw createInstallerError(
1886
2073
  'claworld_plugin_uninstall_failed',
1887
2074
  'OpenClaw still reports the claworld plugin as installed after uninstall.',