@xfxstudio/claworld 0.2.9 → 0.2.10-beta.1

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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/openclaw.plugin.json +7 -63
  3. package/package.json +6 -2
  4. package/skills/claworld-help/SKILL.md +5 -1
  5. package/skills/claworld-join-and-chat/SKILL.md +21 -1
  6. package/skills/claworld-manage-worlds/SKILL.md +81 -10
  7. package/src/lib/agent-profile.js +8 -3
  8. package/src/lib/chat-request.js +0 -1
  9. package/src/lib/policy.js +2 -6
  10. package/src/lib/public-identity.js +175 -0
  11. package/src/lib/relay/kickoff-text.js +1 -0
  12. package/src/openclaw/installer/cli.js +48 -4
  13. package/src/openclaw/installer/constants.js +1 -0
  14. package/src/openclaw/installer/core.js +247 -71
  15. package/src/openclaw/installer/doctor.js +31 -17
  16. package/src/openclaw/plugin/account-identity.js +1 -2
  17. package/src/openclaw/plugin/claworld-channel-plugin.js +453 -263
  18. package/src/openclaw/plugin/config-schema.js +9 -23
  19. package/src/openclaw/plugin/managed-config.js +294 -84
  20. package/src/openclaw/plugin/onboarding.js +37 -45
  21. package/src/openclaw/plugin/register.js +124 -13
  22. package/src/openclaw/plugin/relay-client.js +233 -17
  23. package/src/openclaw/runtime/backend-error-context.js +91 -0
  24. package/src/openclaw/runtime/feedback-helper.js +1 -2
  25. package/src/openclaw/runtime/product-shell-helper.js +43 -9
  26. package/src/openclaw/runtime/tool-contracts.js +26 -3
  27. package/src/openclaw/runtime/tool-inventory.js +7 -0
  28. package/src/openclaw/runtime/world-moderation-helper.js +3 -19
  29. package/src/product-shell/contracts/candidate-feed.js +7 -0
  30. package/src/product-shell/contracts/world-manifest.js +0 -1
  31. package/src/product-shell/contracts/world-orchestration.js +10 -1
  32. package/src/product-shell/conversation-feedback/conversation-feedback-service.js +261 -0
  33. package/src/product-shell/feedback/feedback-routes.js +0 -1
  34. package/src/product-shell/feedback/feedback-service.js +4 -9
  35. package/src/product-shell/index.js +40 -7
  36. package/src/product-shell/matching/matchmaking-service.js +22 -1
  37. package/src/product-shell/membership/membership-service.js +5 -1
  38. package/src/product-shell/onboarding/onboarding-service.js +16 -26
  39. package/src/product-shell/profile/public-identity-routes.js +60 -0
  40. package/src/product-shell/profile/public-identity-service.js +190 -0
  41. package/src/product-shell/search/search-service.js +9 -2
  42. package/src/product-shell/social/chat-request-service.js +22 -7
  43. package/src/product-shell/social/friend-routes.js +1 -1
  44. package/src/product-shell/social/friend-service.js +16 -19
  45. package/src/product-shell/social/social-routes.js +2 -2
  46. package/src/product-shell/social/social-service.js +31 -35
  47. package/src/product-shell/worlds/world-admin-service.js +31 -10
  48. package/src/product-shell/worlds/world-broadcast-service.js +2 -2
  49. package/src/lib/agent-address.js +0 -46
@@ -11,8 +11,11 @@ import {
11
11
  applyClaworldManagedRuntimeConfig,
12
12
  ensureObject,
13
13
  expandUserPath,
14
+ findClaworldManagedRuntimeBackup,
14
15
  normalizeText,
15
16
  resolveClaworldManagedRuntimeOptions,
17
+ setClaworldManagedRuntimeBackupState,
18
+ stripClaworldManagedRuntimeConfig,
16
19
  } from '../plugin/managed-config.js';
17
20
  import {
18
21
  defaultClaworldAccountId,
@@ -23,6 +26,7 @@ import {
23
26
  CLAWORLD_INSTALLER_COMMAND,
24
27
  CLAWORLD_INSTALLER_PACKAGE_NAME,
25
28
  CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
29
+ CLAWORLD_UNINSTALL_COMMAND,
26
30
  CLAWORLD_UPDATE_COMMAND,
27
31
  } from './constants.js';
28
32
  import { seedManagedWorkspaceContract } from './workspace-contract.js';
@@ -139,13 +143,7 @@ export function isManagedToolAllowlistReady(config = {}, options = {}) {
139
143
  export function isRelayBootstrapReady(account = {}) {
140
144
  return Boolean(
141
145
  account?.configured
142
- && (
143
- normalizeText(account?.appToken, null)
144
- || (
145
- account?.registration?.enabled === true
146
- && normalizeText(account?.registration?.agentCode, null)
147
- )
148
- ),
146
+ && normalizeText(account?.appToken, null),
149
147
  );
150
148
  }
151
149
 
@@ -658,6 +656,23 @@ export async function loadConfigFromDisk(configPath) {
658
656
  }
659
657
  }
660
658
 
659
+ export function resolveClaworldInstallerStatePath(configPath) {
660
+ const resolvedConfigPath = path.resolve(String(configPath || DEFAULT_OPENCLAW_CONFIG_PATH));
661
+ return path.join(path.dirname(resolvedConfigPath), '.claworld-installer-state.json');
662
+ }
663
+
664
+ export async function loadInstallerStateFromDisk(installerStatePath) {
665
+ try {
666
+ const raw = await fs.readFile(installerStatePath, 'utf8');
667
+ return { existed: true, state: parseConfigObject(raw, installerStatePath) };
668
+ } catch (error) {
669
+ if (error && error.code === 'ENOENT') {
670
+ return { existed: false, state: {} };
671
+ }
672
+ throw error;
673
+ }
674
+ }
675
+
661
676
  export async function backupConfigIfPresent(configPath, existed, dryRun = false) {
662
677
  if (!existed) return null;
663
678
  const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '').replace('T', '-');
@@ -675,6 +690,20 @@ export async function writeConfig(configPath, config, dryRun = false) {
675
690
  return rendered;
676
691
  }
677
692
 
693
+ export async function writeInstallerState(installerStatePath, installerState, dryRun = false) {
694
+ const renderedState = ensureObject(installerState);
695
+ if (Object.keys(renderedState).length === 0) {
696
+ if (dryRun) return '';
697
+ await fs.rm(installerStatePath, { force: true });
698
+ return '';
699
+ }
700
+ const rendered = `${JSON.stringify(renderedState, null, 2)}\n`;
701
+ if (dryRun) return rendered;
702
+ await fs.mkdir(path.dirname(installerStatePath), { recursive: true });
703
+ await fs.writeFile(installerStatePath, rendered, 'utf8');
704
+ return rendered;
705
+ }
706
+
678
707
  export function buildOpenclawCommandEnv({ configPath, stateDir = null, env = process.env } = {}) {
679
708
  return {
680
709
  ...env,
@@ -688,6 +717,7 @@ export function inspectManagedClaworldInstall({
688
717
  accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
689
718
  input = {},
690
719
  overrides = {},
720
+ installerState = {},
691
721
  } = {}) {
692
722
  const configuredAccountIds = listClaworldAccountIds(cfg);
693
723
  const hasAnyConfig = configuredAccountIds.length > 0 || cfg?.channels?.claworld != null;
@@ -696,6 +726,7 @@ export function inspectManagedClaworldInstall({
696
726
  accountId,
697
727
  input,
698
728
  overrides,
729
+ installerState,
699
730
  });
700
731
  const managedAgentPresent = Boolean(findAgentEntry(cfg, managedOptions.agentId));
701
732
  const managedBindingPresent = hasManagedBinding(cfg, managedOptions);
@@ -719,8 +750,8 @@ export function inspectManagedClaworldInstall({
719
750
  statusLabel = managedReady ? 'configured' : 'configured (managed refresh recommended)';
720
751
  selectionHint = managedReady ? 'configured · managed runtime' : 'configured · managed refresh';
721
752
  } else if (hasAnyConfig) {
722
- statusLabel = 'needs refresh';
723
- selectionHint = 'needs refresh · remote relay';
753
+ statusLabel = 'configured (activation pending)';
754
+ selectionHint = 'configured · activation pending';
724
755
  }
725
756
 
726
757
  return {
@@ -735,7 +766,10 @@ export function inspectManagedClaworldInstall({
735
766
  accountStatus,
736
767
  managedRuntimeReady,
737
768
  managedReady,
738
- reusableAppToken: normalizeText(accountStatus?.appToken, null),
769
+ reusableAppToken: normalizeText(
770
+ managedOptions.appToken,
771
+ normalizeText(accountStatus?.appToken, null),
772
+ ),
739
773
  statusLabel,
740
774
  selectionHint,
741
775
  quickstartScore: managedRuntimeReady ? 2 : 5,
@@ -879,7 +913,7 @@ export async function ensureClaworldPluginInstalled({
879
913
  });
880
914
  }
881
915
 
882
- const args = ['plugins', 'install'];
916
+ const args = ['plugins', 'install', '--dangerously-force-unsafe-install'];
883
917
  if (installMode === 'link') args.push('--link');
884
918
  args.push(installSource);
885
919
  await executeCommand({
@@ -1248,6 +1282,7 @@ export async function verifyClaworldInstall({
1248
1282
  attempts = DEFAULT_VERIFICATION_ATTEMPTS,
1249
1283
  delayMs = DEFAULT_VERIFICATION_DELAY_MS,
1250
1284
  requireGatewayRunning = resolveRequireGatewayRunning(env),
1285
+ requireChannelToken = true,
1251
1286
  } = {}) {
1252
1287
  let lastResult = null;
1253
1288
 
@@ -1301,7 +1336,10 @@ export async function verifyClaworldInstall({
1301
1336
  channelAccount
1302
1337
  && channelAccount.configured === true
1303
1338
  && channelAccount.enabled !== false
1304
- && channelAccount.tokenStatus === 'available'
1339
+ && (
1340
+ requireChannelToken !== true
1341
+ || channelAccount.tokenStatus === 'available'
1342
+ )
1305
1343
  );
1306
1344
  const pluginReady = Boolean(plugin.installed);
1307
1345
  const bindingReady = Boolean(bindingLine);
@@ -1318,6 +1356,7 @@ export async function verifyClaworldInstall({
1318
1356
  channelReady,
1319
1357
  bindingReady,
1320
1358
  requireGatewayRunning,
1359
+ requireChannelToken,
1321
1360
  };
1322
1361
 
1323
1362
  if (lastResult.ok) {
@@ -1337,8 +1376,10 @@ export async function verifyClaworldInstall({
1337
1376
  async function reconcileManagedClaworldRuntime({
1338
1377
  openclawBin = DEFAULT_OPENCLAW_BIN,
1339
1378
  configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
1379
+ installerStatePath = null,
1340
1380
  stateDir = DEFAULT_OPENCLAW_STATE_DIR,
1341
1381
  currentConfigState = { existed: false, config: {} },
1382
+ currentInstallerState = {},
1342
1383
  currentConfig = {},
1343
1384
  host = null,
1344
1385
  serverUrl = null,
@@ -1361,6 +1402,7 @@ async function reconcileManagedClaworldRuntime({
1361
1402
  const existingInstall = inspectManagedClaworldInstall({
1362
1403
  cfg: currentConfig,
1363
1404
  accountId,
1405
+ installerState: currentInstallerState,
1364
1406
  overrides: {
1365
1407
  agentId,
1366
1408
  workspace,
@@ -1376,35 +1418,19 @@ async function reconcileManagedClaworldRuntime({
1376
1418
  normalizeText(existingInstall.accountStatus?.serverUrl, DEFAULT_CLAWORLD_SERVER_URL),
1377
1419
  );
1378
1420
 
1379
- const manifest = await fetchInstallManifest({
1380
- serverUrl: effectiveServerUrl,
1381
- apiKey,
1382
- fetchImpl,
1383
- });
1384
- const manifestMinHostVersion = normalizeText(
1385
- manifest?.installer?.minHostVersion,
1386
- normalizeText(manifest?.plugin?.minHostVersion, CLAWORLD_OPENCLAW_MIN_HOST_VERSION),
1387
- );
1388
- if (host && compareVersionParts(host.version, manifestMinHostVersion) < 0) {
1389
- throw createInstallerError(
1390
- 'openclaw_version_too_old',
1391
- `OpenClaw ${host.version} is below the required minimum ${manifestMinHostVersion}.`,
1392
- { hostVersion: host.version, minHostVersion: manifestMinHostVersion },
1393
- );
1394
- }
1395
-
1396
1421
  const installAccountId = normalizeText(
1397
1422
  accountId,
1398
- normalizeText(manifest?.setup?.defaultAccountId, DEFAULT_CLAWORLD_ACCOUNT_ID),
1423
+ DEFAULT_CLAWORLD_ACCOUNT_ID,
1399
1424
  );
1400
1425
  const installAgentId = normalizeText(
1401
1426
  agentId,
1402
- normalizeText(manifest?.setup?.defaultLocalAgentId, installAccountId),
1427
+ installAccountId,
1403
1428
  );
1404
1429
 
1405
1430
  const preflight = inspectManagedClaworldInstall({
1406
1431
  cfg: currentConfig,
1407
1432
  accountId: installAccountId,
1433
+ installerState: currentInstallerState,
1408
1434
  overrides: {
1409
1435
  agentId: installAgentId,
1410
1436
  workspace,
@@ -1421,50 +1447,15 @@ async function reconcileManagedClaworldRuntime({
1421
1447
  normalizeText(preflight.managedOptions.displayName, null),
1422
1448
  );
1423
1449
 
1424
- let activation = null;
1425
- let activationMode = 'new_activation';
1426
- const existingAppToken = normalizeText(preflight.reusableAppToken, null);
1427
- if (existingAppToken) {
1428
- try {
1429
- activation = await activateInstall({
1430
- serverUrl: effectiveServerUrl,
1431
- apiKey,
1432
- appToken: existingAppToken,
1433
- displayName: desiredDisplayName,
1434
- fetchImpl,
1435
- });
1436
- activationMode = 'reused_existing_token';
1437
- } catch (error) {
1438
- const status = error?.context?.response?.status;
1439
- if (status !== 401 && status !== 403) {
1440
- throw error;
1441
- }
1442
- }
1443
- }
1444
- if (!activation) {
1445
- activation = await activateInstall({
1446
- serverUrl: effectiveServerUrl,
1447
- apiKey,
1448
- displayName: desiredDisplayName,
1449
- fetchImpl,
1450
- });
1451
- activationMode = 'created_new_activation';
1452
- }
1453
-
1454
1450
  const managedOptions = resolveClaworldManagedRuntimeOptions({
1455
1451
  cfg: currentConfig,
1452
+ installerState: currentInstallerState,
1456
1453
  accountId: installAccountId,
1457
- input: {
1458
- name: desiredDisplayName,
1459
- appToken: activation.appToken,
1460
- toolProfile,
1461
- },
1462
1454
  overrides: {
1463
1455
  agentId: installAgentId,
1464
1456
  workspace,
1465
1457
  serverUrl: effectiveServerUrl,
1466
1458
  apiKey,
1467
- appToken: activation.appToken,
1468
1459
  displayName: desiredDisplayName,
1469
1460
  toolProfile,
1470
1461
  approvalMode,
@@ -1517,29 +1508,40 @@ async function reconcileManagedClaworldRuntime({
1517
1508
  env,
1518
1509
  dryRun,
1519
1510
  delayMs: Math.min(timeoutMs, DEFAULT_VERIFICATION_DELAY_MS),
1511
+ requireChannelToken: Boolean(managedOptions.appToken),
1520
1512
  });
1521
1513
  if (!verification.ok) {
1522
1514
  throw createInstallerError(
1523
1515
  'claworld_install_verification_failed',
1524
- 'Claworld install verification did not confirm the managed account binding.',
1516
+ 'Claworld install verification did not confirm the managed channel runtime shape.',
1525
1517
  { verification },
1526
1518
  );
1527
1519
  }
1528
1520
 
1521
+ let installerStateChanged = false;
1522
+ if (installerStatePath && installAccountId) {
1523
+ const nextInstallerState = JSON.parse(JSON.stringify(ensureObject(currentInstallerState)));
1524
+ setClaworldManagedRuntimeBackupState(nextInstallerState, installAccountId, null);
1525
+ installerStateChanged = JSON.stringify(currentInstallerState) !== JSON.stringify(nextInstallerState);
1526
+ if (installerStateChanged) {
1527
+ await writeInstallerState(installerStatePath, nextInstallerState, dryRun);
1528
+ }
1529
+ }
1530
+
1529
1531
  return {
1530
1532
  backupPath,
1531
1533
  existingInstall,
1532
1534
  effectiveServerUrl,
1533
- manifest,
1534
1535
  preflight,
1535
- activationMode,
1536
- activation,
1536
+ activationStatus: managedOptions.appToken ? 'ready' : 'pending',
1537
1537
  managedOptions,
1538
1538
  transformed,
1539
1539
  configChanged,
1540
1540
  workspaceActions,
1541
1541
  runtimeRefresh,
1542
1542
  verification,
1543
+ installerStatePath,
1544
+ installerStateChanged,
1543
1545
  };
1544
1546
  }
1545
1547
 
@@ -1567,6 +1569,7 @@ export async function runClaworldInstallerInstall({
1567
1569
  timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
1568
1570
  } = {}) {
1569
1571
  const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
1572
+ const installerStatePath = resolveClaworldInstallerStatePath(resolvedConfigPath);
1570
1573
  const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
1571
1574
  const commandEnv = buildOpenclawCommandEnv({
1572
1575
  configPath: resolvedConfigPath,
@@ -1574,6 +1577,7 @@ export async function runClaworldInstallerInstall({
1574
1577
  env,
1575
1578
  });
1576
1579
  const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
1580
+ const currentInstallerState = (await loadInstallerStateFromDisk(installerStatePath)).state;
1577
1581
  const currentConfigBeforePluginInstall = currentConfigState.config;
1578
1582
  const host = await detectOpenclawHost({
1579
1583
  openclawBin,
@@ -1620,8 +1624,10 @@ export async function runClaworldInstallerInstall({
1620
1624
  const lifecycle = await reconcileManagedClaworldRuntime({
1621
1625
  openclawBin,
1622
1626
  configPath: resolvedConfigPath,
1627
+ installerStatePath,
1623
1628
  stateDir: resolvedStateDir,
1624
1629
  currentConfigState,
1630
+ currentInstallerState,
1625
1631
  currentConfig,
1626
1632
  host,
1627
1633
  serverUrl,
@@ -1646,6 +1652,7 @@ export async function runClaworldInstallerInstall({
1646
1652
  ok: true,
1647
1653
  command: CLAWORLD_INSTALLER_COMMAND,
1648
1654
  configPath: resolvedConfigPath,
1655
+ installerStatePath,
1649
1656
  stateDir: resolvedStateDir,
1650
1657
  host,
1651
1658
  plugin,
@@ -1674,6 +1681,7 @@ export async function runClaworldInstallerUpdate({
1674
1681
  timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
1675
1682
  } = {}) {
1676
1683
  const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
1684
+ const installerStatePath = resolveClaworldInstallerStatePath(resolvedConfigPath);
1677
1685
  const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
1678
1686
  const commandEnv = buildOpenclawCommandEnv({
1679
1687
  configPath: resolvedConfigPath,
@@ -1681,6 +1689,7 @@ export async function runClaworldInstallerUpdate({
1681
1689
  env,
1682
1690
  });
1683
1691
  const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
1692
+ const currentInstallerState = (await loadInstallerStateFromDisk(installerStatePath)).state;
1684
1693
  const host = await detectOpenclawHost({
1685
1694
  openclawBin,
1686
1695
  commandRunner,
@@ -1712,8 +1721,10 @@ export async function runClaworldInstallerUpdate({
1712
1721
  const lifecycle = await reconcileManagedClaworldRuntime({
1713
1722
  openclawBin,
1714
1723
  configPath: resolvedConfigPath,
1724
+ installerStatePath,
1715
1725
  stateDir: resolvedStateDir,
1716
1726
  currentConfigState: refreshedConfigState,
1727
+ currentInstallerState,
1717
1728
  currentConfig,
1718
1729
  host,
1719
1730
  serverUrl,
@@ -1737,6 +1748,7 @@ export async function runClaworldInstallerUpdate({
1737
1748
  ok: true,
1738
1749
  command: CLAWORLD_UPDATE_COMMAND,
1739
1750
  configPath: resolvedConfigPath,
1751
+ installerStatePath,
1740
1752
  stateDir: resolvedStateDir,
1741
1753
  host,
1742
1754
  plugin,
@@ -1744,6 +1756,170 @@ export async function runClaworldInstallerUpdate({
1744
1756
  };
1745
1757
  }
1746
1758
 
1759
+ export async function runClaworldInstallerUninstall({
1760
+ openclawBin = DEFAULT_OPENCLAW_BIN,
1761
+ configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
1762
+ stateDir = DEFAULT_OPENCLAW_STATE_DIR,
1763
+ accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
1764
+ agentId = DEFAULT_CLAWORLD_AGENT_ID,
1765
+ commandRunner = defaultCommandRunner,
1766
+ cwd = process.cwd(),
1767
+ env = process.env,
1768
+ dryRun = false,
1769
+ } = {}) {
1770
+ const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
1771
+ const installerStatePath = resolveClaworldInstallerStatePath(resolvedConfigPath);
1772
+ const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
1773
+ const commandEnv = buildOpenclawCommandEnv({
1774
+ configPath: resolvedConfigPath,
1775
+ stateDir: resolvedStateDir,
1776
+ env,
1777
+ });
1778
+ const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
1779
+ const currentInstallerState = (await loadInstallerStateFromDisk(installerStatePath)).state;
1780
+ const currentConfig = currentConfigState.config;
1781
+ const host = await detectOpenclawHost({
1782
+ openclawBin,
1783
+ commandRunner,
1784
+ cwd,
1785
+ env: commandEnv,
1786
+ dryRun,
1787
+ });
1788
+ if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
1789
+ throw createInstallerError(
1790
+ 'openclaw_version_too_old',
1791
+ `OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
1792
+ { hostVersion: host.version, minHostVersion: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
1793
+ );
1794
+ }
1795
+
1796
+ const pluginBefore = await inspectClaworldPluginInstall({
1797
+ openclawBin,
1798
+ configPath: resolvedConfigPath,
1799
+ stateDir: resolvedStateDir,
1800
+ commandRunner,
1801
+ cwd,
1802
+ env,
1803
+ dryRun,
1804
+ });
1805
+ const transformed = stripClaworldManagedRuntimeConfig(currentConfig, {
1806
+ accountId,
1807
+ agentId,
1808
+ preserveBackup: true,
1809
+ });
1810
+ const configChanged = JSON.stringify(currentConfig) !== JSON.stringify(transformed.config);
1811
+ const backupPath = configChanged
1812
+ ? await backupConfigIfPresent(resolvedConfigPath, currentConfigState.existed, dryRun)
1813
+ : null;
1814
+ if (configChanged) {
1815
+ await writeConfig(resolvedConfigPath, transformed.config, dryRun);
1816
+ }
1817
+ const nextInstallerState = JSON.parse(JSON.stringify(ensureObject(currentInstallerState)));
1818
+ setClaworldManagedRuntimeBackupState(nextInstallerState, accountId, transformed.backup);
1819
+ const installerStateChanged = JSON.stringify(currentInstallerState) !== JSON.stringify(nextInstallerState);
1820
+ if (installerStateChanged) {
1821
+ await writeInstallerState(installerStatePath, nextInstallerState, dryRun);
1822
+ }
1823
+
1824
+ await validateOpenclawConfig({
1825
+ openclawBin,
1826
+ configPath: resolvedConfigPath,
1827
+ stateDir: resolvedStateDir,
1828
+ commandRunner,
1829
+ cwd,
1830
+ env,
1831
+ dryRun,
1832
+ });
1833
+
1834
+ const uninstallBootstrap = pluginBefore.installed
1835
+ ? await preparePluginBootstrapConfig({
1836
+ configPath: resolvedConfigPath,
1837
+ config: currentConfig,
1838
+ dryRun,
1839
+ })
1840
+ : null;
1841
+
1842
+ let pluginAction = 'plugin_already_absent';
1843
+ try {
1844
+ if (pluginBefore.installed) {
1845
+ await executeCommand({
1846
+ commandRunner,
1847
+ bin: openclawBin,
1848
+ args: ['plugins', 'uninstall', 'claworld', '--force'],
1849
+ cwd,
1850
+ env: buildOpenclawCommandEnv({
1851
+ configPath: uninstallBootstrap?.configPath || resolvedConfigPath,
1852
+ stateDir: resolvedStateDir,
1853
+ env,
1854
+ }),
1855
+ dryRun,
1856
+ capture: false,
1857
+ });
1858
+ pluginAction = 'uninstalled_plugin';
1859
+ }
1860
+ } finally {
1861
+ if (uninstallBootstrap) {
1862
+ await uninstallBootstrap.cleanup();
1863
+ }
1864
+ }
1865
+
1866
+ const runtimeRefresh = await refreshOpenclawRuntime({
1867
+ openclawBin,
1868
+ configPath: resolvedConfigPath,
1869
+ stateDir: resolvedStateDir,
1870
+ commandRunner,
1871
+ cwd,
1872
+ env,
1873
+ dryRun,
1874
+ });
1875
+ const pluginAfter = await inspectClaworldPluginInstall({
1876
+ openclawBin,
1877
+ configPath: resolvedConfigPath,
1878
+ stateDir: resolvedStateDir,
1879
+ commandRunner,
1880
+ cwd,
1881
+ env,
1882
+ dryRun,
1883
+ });
1884
+ if (pluginBefore.installed && pluginAfter.installed) {
1885
+ throw createInstallerError(
1886
+ 'claworld_plugin_uninstall_failed',
1887
+ 'OpenClaw still reports the claworld plugin as installed after uninstall.',
1888
+ { before: pluginBefore, after: pluginAfter },
1889
+ );
1890
+ }
1891
+
1892
+ const gatewayStatus = await readGatewayStatus({
1893
+ openclawBin,
1894
+ configPath: resolvedConfigPath,
1895
+ stateDir: resolvedStateDir,
1896
+ commandRunner,
1897
+ cwd,
1898
+ env,
1899
+ dryRun,
1900
+ });
1901
+
1902
+ return {
1903
+ ok: true,
1904
+ command: CLAWORLD_UNINSTALL_COMMAND,
1905
+ configPath: resolvedConfigPath,
1906
+ installerStatePath,
1907
+ stateDir: resolvedStateDir,
1908
+ host,
1909
+ backupPath,
1910
+ transformed,
1911
+ configChanged,
1912
+ installerStateChanged,
1913
+ plugin: {
1914
+ action: pluginAction,
1915
+ before: pluginBefore,
1916
+ after: pluginAfter,
1917
+ },
1918
+ runtimeRefresh,
1919
+ gatewayStatus,
1920
+ };
1921
+ }
1922
+
1747
1923
  export {
1748
1924
  compareVersionParts,
1749
1925
  defaultCommandRunner,
@@ -249,8 +249,8 @@ export async function runClaworldDoctor({
249
249
  workspace: resolvedWorkspace,
250
250
  serverUrl: effectiveServerUrl,
251
251
  appToken: configuredAccount.appToken,
252
- registrationAgentCode: configuredAccount.registration?.agentCode || null,
253
- defaultToAddress: configuredAccount.relay?.defaultToAddress || null,
252
+ registrationDisplayName: configuredAccount.registration?.displayName || null,
253
+ defaultTargetAgentId: configuredAccount.relay?.defaultTargetAgentId || null,
254
254
  })
255
255
  : {
256
256
  ok: true,
@@ -522,11 +522,13 @@ export async function runClaworldDoctor({
522
522
  id: 'app-token',
523
523
  category: 'Credentials and runtime',
524
524
  label: 'Managed appToken',
525
- status: configuredAccount.tokenStatus === 'available' ? 'pass' : 'fail',
525
+ status: configuredAccount.tokenStatus === 'available' ? 'pass' : 'warn',
526
526
  summary: configuredAccount.tokenStatus === 'available'
527
527
  ? 'Managed account has an available appToken.'
528
- : `Managed account token status is ${configuredAccount.tokenStatus}.`,
529
- action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to refresh activation and persist a healthy appToken.`,
528
+ : `Managed account token status is ${configuredAccount.tokenStatus}; activation is still pending.`,
529
+ action: configuredAccount.tokenStatus === 'available'
530
+ ? null
531
+ : 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_update_public_identity` when prompted.',
530
532
  details: { tokenSource: configuredAccount.tokenSource, tokenStatus: configuredAccount.tokenStatus },
531
533
  }));
532
534
 
@@ -549,9 +551,9 @@ export async function runClaworldDoctor({
549
551
  id: 'backend-reachable',
550
552
  category: 'Credentials and runtime',
551
553
  label: 'Claworld backend reachability',
552
- status: 'fail',
553
- summary: `Unable to read the Claworld install contract from ${effectiveServerUrl}.`,
554
- action: 'Confirm the backend URL is correct and reachable, then rerun doctor.',
554
+ status: 'warn',
555
+ summary: `Unable to reach the Claworld backend at ${effectiveServerUrl}.`,
556
+ action: 'Restore backend reachability before pairing or live Claworld use, then rerun doctor.',
555
557
  details: { serverUrl: effectiveServerUrl, message: error?.message || String(error) },
556
558
  }));
557
559
  }
@@ -580,7 +582,7 @@ export async function runClaworldDoctor({
580
582
  label: 'Stable relay binding',
581
583
  status: 'fail',
582
584
  summary: 'Managed appToken could not be resolved into a healthy backend binding.',
583
- action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to refresh the managed credential and binding.`,
585
+ action: 'Run `claworld_pair_agent` in a live OpenClaw session to refresh the managed relay binding.',
584
586
  details: { message: error?.message || String(error) },
585
587
  }));
586
588
  }
@@ -589,17 +591,17 @@ export async function runClaworldDoctor({
589
591
  id: 'stable-agent-binding',
590
592
  category: 'Credentials and runtime',
591
593
  label: 'Stable relay binding',
592
- status: 'fail',
593
- summary: 'Doctor could not verify the managed relay binding because no reusable appToken is configured.',
594
- action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to finish activation and persist an appToken.`,
594
+ status: 'warn',
595
+ summary: 'Doctor could not verify the managed relay binding because activation is still pending.',
596
+ action: 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_update_public_identity` when prompted.',
595
597
  }));
596
598
  } else {
597
599
  checks.push(createCheck({
598
600
  id: 'stable-agent-binding',
599
601
  category: 'Credentials and runtime',
600
602
  label: 'Stable relay binding',
601
- status: 'fail',
602
- summary: 'Doctor could not verify the managed relay binding because the backend install contract was not reachable.',
603
+ status: 'warn',
604
+ summary: 'Doctor could not verify the managed relay binding because the backend was not reachable.',
603
605
  action: 'Restore backend reachability, then rerun doctor to verify the managed relay binding.',
604
606
  }));
605
607
  }
@@ -655,15 +657,27 @@ export async function runClaworldDoctor({
655
657
  && channelAccount.enabled !== false
656
658
  && channelAccount.tokenStatus === 'available'
657
659
  );
660
+ const channelPendingActivation = Boolean(
661
+ channelAccount
662
+ && channelAccount.configured === true
663
+ && channelAccount.enabled !== false
664
+ && channelAccount.tokenStatus !== 'available'
665
+ );
658
666
  checks.push(createCheck({
659
667
  id: 'channel-runtime',
660
668
  category: 'Credentials and runtime',
661
669
  label: 'Claworld channel runtime account',
662
- status: channelHealthy ? 'pass' : 'fail',
670
+ status: channelHealthy ? 'pass' : channelPendingActivation ? 'warn' : 'fail',
663
671
  summary: channelHealthy
664
672
  ? `channels status reports managed account \`${configuredAccount.accountId}\` as configured with an available token.`
665
- : `channels status does not report a healthy managed account for \`${configuredAccount.accountId}\`.`,
666
- action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` or restart the gateway to recover the managed account runtime.`,
673
+ : channelPendingActivation
674
+ ? `channels status reports managed account \`${configuredAccount.accountId}\` as configured, but activation is still pending (${channelAccount.tokenStatus}).`
675
+ : `channels status does not report a healthy managed account for \`${configuredAccount.accountId}\`.`,
676
+ action: channelHealthy
677
+ ? null
678
+ : channelPendingActivation
679
+ ? 'Open a live OpenClaw session, run `claworld_pair_agent`, and then complete `claworld_update_public_identity` when prompted.'
680
+ : `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` or restart the gateway to recover the managed account runtime.`,
667
681
  details: { channelAccount },
668
682
  }));
669
683
  } catch (error) {
@@ -17,7 +17,6 @@ export function normalizeRuntimeRegistration(candidate = {}) {
17
17
 
18
18
  return {
19
19
  enabled: true,
20
- agentCode: normalizeText(registration.agentCode, normalizeText(legacyLocalAgent.agentCode, null)),
21
20
  displayName: normalizeText(registration.displayName, normalizeText(legacyLocalAgent.displayName, null)),
22
21
  };
23
22
  }
@@ -49,7 +48,7 @@ export function applyRuntimeIdentity(runtimeConfig = {}, { agentId = null, appTo
49
48
  agentId: normalizeText(agentId, normalizeText(relay.agentId, null)),
50
49
  appToken: resolvedAppToken,
51
50
  credentialToken: resolvedAppToken,
52
- defaultToAddress: normalizeText(relay.defaultToAddress, null),
51
+ defaultTargetAgentId: normalizeText(relay.defaultTargetAgentId, null),
53
52
  },
54
53
  };
55
54
  }