@xfxstudio/claworld 0.2.10-beta.0 → 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.
@@ -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.0",
11
+ "version": "0.2.10-beta.1",
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.0",
3
+ "version": "0.2.10-beta.1",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -296,10 +296,9 @@ function printInstallSummary(result) {
296
296
  console.log('Claworld installer complete');
297
297
  console.log('===========================');
298
298
  console.log(`Managed account: ${result.transformed?.config?.channels?.claworld?.defaultAccount || 'claworld'}`);
299
- console.log(`Canonical agentId: ${result.activation?.agentId || '(unknown)'}`);
300
299
  console.log(`OpenClaw version: ${result.host?.version || '(unknown)'}`);
301
300
  console.log(`Plugin action: ${result.plugin?.action || 'unknown'}`);
302
- console.log(`Activation mode: ${result.activationMode}`);
301
+ console.log(`Activation: ${result.activationStatus === 'ready' ? 'ready' : 'pending via claworld_update_public_identity'}`);
303
302
  console.log(`Runtime refresh: ${result.runtimeRefresh?.action || 'unknown'}`);
304
303
  console.log(`Config path: ${result.configPath}`);
305
304
  console.log(`Backup path: ${result.backupPath || '(unchanged or new file)'}`);
@@ -315,10 +314,10 @@ function printUpdateSummary(result, doctorResult) {
315
314
  console.log('Claworld update complete');
316
315
  console.log('========================');
317
316
  console.log(`Managed account: ${result.managedOptions?.accountId || 'claworld'}`);
318
- console.log(`Canonical agentId: ${result.activation?.agentId || '(unknown)'}`);
319
317
  console.log(`OpenClaw version: ${result.host?.version || '(unknown)'}`);
320
318
  console.log(`Plugin action: ${result.plugin?.action || 'unknown'}`);
321
319
  console.log(`Tracked install: ${trackedSummary}`);
320
+ console.log(`Activation: ${result.activationStatus === 'ready' ? 'ready' : 'pending via claworld_update_public_identity'}`);
322
321
  console.log(`Runtime refresh: ${result.runtimeRefresh?.action || 'unknown'}`);
323
322
  console.log(`Doctor status: ${doctorResult?.status || 'unknown'}`);
324
323
  console.log(`Config path: ${result.configPath}`);
@@ -143,13 +143,7 @@ export function isManagedToolAllowlistReady(config = {}, options = {}) {
143
143
  export function isRelayBootstrapReady(account = {}) {
144
144
  return Boolean(
145
145
  account?.configured
146
- && (
147
- normalizeText(account?.appToken, null)
148
- || (
149
- account?.registration?.enabled === true
150
- && normalizeText(account?.registration?.displayName, null)
151
- )
152
- ),
146
+ && normalizeText(account?.appToken, null),
153
147
  );
154
148
  }
155
149
 
@@ -756,8 +750,8 @@ export function inspectManagedClaworldInstall({
756
750
  statusLabel = managedReady ? 'configured' : 'configured (managed refresh recommended)';
757
751
  selectionHint = managedReady ? 'configured · managed runtime' : 'configured · managed refresh';
758
752
  } else if (hasAnyConfig) {
759
- statusLabel = 'needs refresh';
760
- selectionHint = 'needs refresh · remote relay';
753
+ statusLabel = 'configured (activation pending)';
754
+ selectionHint = 'configured · activation pending';
761
755
  }
762
756
 
763
757
  return {
@@ -1288,6 +1282,7 @@ export async function verifyClaworldInstall({
1288
1282
  attempts = DEFAULT_VERIFICATION_ATTEMPTS,
1289
1283
  delayMs = DEFAULT_VERIFICATION_DELAY_MS,
1290
1284
  requireGatewayRunning = resolveRequireGatewayRunning(env),
1285
+ requireChannelToken = true,
1291
1286
  } = {}) {
1292
1287
  let lastResult = null;
1293
1288
 
@@ -1341,7 +1336,10 @@ export async function verifyClaworldInstall({
1341
1336
  channelAccount
1342
1337
  && channelAccount.configured === true
1343
1338
  && channelAccount.enabled !== false
1344
- && channelAccount.tokenStatus === 'available'
1339
+ && (
1340
+ requireChannelToken !== true
1341
+ || channelAccount.tokenStatus === 'available'
1342
+ )
1345
1343
  );
1346
1344
  const pluginReady = Boolean(plugin.installed);
1347
1345
  const bindingReady = Boolean(bindingLine);
@@ -1358,6 +1356,7 @@ export async function verifyClaworldInstall({
1358
1356
  channelReady,
1359
1357
  bindingReady,
1360
1358
  requireGatewayRunning,
1359
+ requireChannelToken,
1361
1360
  };
1362
1361
 
1363
1362
  if (lastResult.ok) {
@@ -1419,30 +1418,13 @@ async function reconcileManagedClaworldRuntime({
1419
1418
  normalizeText(existingInstall.accountStatus?.serverUrl, DEFAULT_CLAWORLD_SERVER_URL),
1420
1419
  );
1421
1420
 
1422
- const manifest = await fetchInstallManifest({
1423
- serverUrl: effectiveServerUrl,
1424
- apiKey,
1425
- fetchImpl,
1426
- });
1427
- const manifestMinHostVersion = normalizeText(
1428
- manifest?.installer?.minHostVersion,
1429
- normalizeText(manifest?.plugin?.minHostVersion, CLAWORLD_OPENCLAW_MIN_HOST_VERSION),
1430
- );
1431
- if (host && compareVersionParts(host.version, manifestMinHostVersion) < 0) {
1432
- throw createInstallerError(
1433
- 'openclaw_version_too_old',
1434
- `OpenClaw ${host.version} is below the required minimum ${manifestMinHostVersion}.`,
1435
- { hostVersion: host.version, minHostVersion: manifestMinHostVersion },
1436
- );
1437
- }
1438
-
1439
1421
  const installAccountId = normalizeText(
1440
1422
  accountId,
1441
- normalizeText(manifest?.setup?.defaultAccountId, DEFAULT_CLAWORLD_ACCOUNT_ID),
1423
+ DEFAULT_CLAWORLD_ACCOUNT_ID,
1442
1424
  );
1443
1425
  const installAgentId = normalizeText(
1444
1426
  agentId,
1445
- normalizeText(manifest?.setup?.defaultLocalAgentId, installAccountId),
1427
+ installAccountId,
1446
1428
  );
1447
1429
 
1448
1430
  const preflight = inspectManagedClaworldInstall({
@@ -1465,51 +1447,15 @@ async function reconcileManagedClaworldRuntime({
1465
1447
  normalizeText(preflight.managedOptions.displayName, null),
1466
1448
  );
1467
1449
 
1468
- let activation = null;
1469
- let activationMode = 'new_activation';
1470
- const existingAppToken = normalizeText(preflight.reusableAppToken, null);
1471
- if (existingAppToken) {
1472
- try {
1473
- activation = await activateInstall({
1474
- serverUrl: effectiveServerUrl,
1475
- apiKey,
1476
- appToken: existingAppToken,
1477
- displayName: desiredDisplayName,
1478
- fetchImpl,
1479
- });
1480
- activationMode = 'reused_existing_token';
1481
- } catch (error) {
1482
- const status = error?.context?.response?.status;
1483
- if (status !== 401 && status !== 403) {
1484
- throw error;
1485
- }
1486
- }
1487
- }
1488
- if (!activation) {
1489
- activation = await activateInstall({
1490
- serverUrl: effectiveServerUrl,
1491
- apiKey,
1492
- displayName: desiredDisplayName,
1493
- fetchImpl,
1494
- });
1495
- activationMode = 'created_new_activation';
1496
- }
1497
-
1498
1450
  const managedOptions = resolveClaworldManagedRuntimeOptions({
1499
1451
  cfg: currentConfig,
1500
1452
  installerState: currentInstallerState,
1501
1453
  accountId: installAccountId,
1502
- input: {
1503
- name: desiredDisplayName,
1504
- appToken: activation.appToken,
1505
- toolProfile,
1506
- },
1507
1454
  overrides: {
1508
1455
  agentId: installAgentId,
1509
1456
  workspace,
1510
1457
  serverUrl: effectiveServerUrl,
1511
1458
  apiKey,
1512
- appToken: activation.appToken,
1513
1459
  displayName: desiredDisplayName,
1514
1460
  toolProfile,
1515
1461
  approvalMode,
@@ -1562,11 +1508,12 @@ async function reconcileManagedClaworldRuntime({
1562
1508
  env,
1563
1509
  dryRun,
1564
1510
  delayMs: Math.min(timeoutMs, DEFAULT_VERIFICATION_DELAY_MS),
1511
+ requireChannelToken: Boolean(managedOptions.appToken),
1565
1512
  });
1566
1513
  if (!verification.ok) {
1567
1514
  throw createInstallerError(
1568
1515
  'claworld_install_verification_failed',
1569
- 'Claworld install verification did not confirm the managed account binding.',
1516
+ 'Claworld install verification did not confirm the managed channel runtime shape.',
1570
1517
  { verification },
1571
1518
  );
1572
1519
  }
@@ -1585,10 +1532,8 @@ async function reconcileManagedClaworldRuntime({
1585
1532
  backupPath,
1586
1533
  existingInstall,
1587
1534
  effectiveServerUrl,
1588
- manifest,
1589
1535
  preflight,
1590
- activationMode,
1591
- activation,
1536
+ activationStatus: managedOptions.appToken ? 'ready' : 'pending',
1592
1537
  managedOptions,
1593
1538
  transformed,
1594
1539
  configChanged,
@@ -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) {
@@ -52,6 +52,7 @@ import {
52
52
  normalizeRuntimeBoundaryError,
53
53
  serializeRuntimeBoundaryError,
54
54
  } from '../../lib/runtime-errors.js';
55
+ import { PUBLIC_IDENTITY_STATUS } from '../../lib/public-identity.js';
55
56
  import { v4 as uuidv4 } from 'uuid';
56
57
 
57
58
  function normalizeRelayHttpBaseUrl(serverUrl) {
@@ -909,6 +910,48 @@ async function fetchPublicIdentity({
909
910
  agentId = null,
910
911
  fetchImpl,
911
912
  }) {
913
+ if (!resolveRuntimeAppToken(runtimeConfig)) {
914
+ const recommendedDisplayName = normalizeClaworldText(
915
+ runtimeConfig?.name,
916
+ normalizeClaworldText(runtimeConfig?.registration?.displayName, null),
917
+ );
918
+ return {
919
+ status: 'pending',
920
+ agentId: normalizeClaworldText(agentId, null),
921
+ ready: false,
922
+ publicIdentity: {
923
+ status: PUBLIC_IDENTITY_STATUS.PENDING,
924
+ displayName: null,
925
+ code: null,
926
+ displayIdentity: null,
927
+ confirmedAt: null,
928
+ updatedAt: null,
929
+ },
930
+ recommendedDisplayName,
931
+ nextAction: 'set_public_identity',
932
+ requiredAction: 'set_public_identity',
933
+ nextTool: 'claworld_update_public_identity',
934
+ missingFields: [
935
+ {
936
+ fieldId: 'displayName',
937
+ label: 'Public Name',
938
+ description: 'A public display name used in Claworld identity surfaces.',
939
+ },
940
+ {
941
+ fieldId: 'code',
942
+ label: 'Public Code',
943
+ description: 'A system-generated unique suffix used in the public identity.',
944
+ },
945
+ ],
946
+ feedbackSummary: {
947
+ totalLikesReceived: 0,
948
+ totalDislikesReceived: 0,
949
+ totalLikesGiven: 0,
950
+ totalDislikesGiven: 0,
951
+ },
952
+ };
953
+ }
954
+
912
955
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
913
956
  const path = buildRelayJsonPath('/v1/profile/public-identity', {
914
957
  agentId,
@@ -950,31 +993,85 @@ async function updatePublicIdentity({
950
993
  context: { field: 'displayName' },
951
994
  });
952
995
  }
996
+ let resolvedRuntimeConfig = applyRuntimeIdentity(runtimeConfig);
997
+ let resolvedAgentId = normalizeClaworldText(agentId, normalizeClaworldText(resolvedRuntimeConfig?.relay?.agentId, null));
998
+
999
+ if (!resolveRuntimeAppToken(resolvedRuntimeConfig)) {
1000
+ const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
1001
+ const activationResult = await fetchJson(fetchImpl, `${baseUrl}/v1/onboarding/activate`, {
1002
+ method: 'POST',
1003
+ headers: {
1004
+ 'content-type': 'application/json',
1005
+ ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
1006
+ ...buildRuntimeAuthHeaders(resolvedRuntimeConfig),
1007
+ },
1008
+ body: JSON.stringify({
1009
+ displayName: normalizedDisplayName,
1010
+ }),
1011
+ });
1012
+ if (!activationResult.ok) {
1013
+ createRelayRouteError({
1014
+ result: activationResult,
1015
+ runtimeConfig: resolvedRuntimeConfig,
1016
+ code: 'claworld_activation_failed',
1017
+ publicMessage: 'failed to activate Claworld account',
1018
+ });
1019
+ }
1020
+ const activatedToken = normalizeClaworldText(activationResult.body?.appToken, null);
1021
+ const activatedAgentId = normalizeClaworldText(activationResult.body?.agentId, null);
1022
+ if (!activatedToken || !activatedAgentId) {
1023
+ throw createRuntimeBoundaryError({
1024
+ code: 'claworld_activation_failed',
1025
+ category: 'runtime',
1026
+ status: 502,
1027
+ message: 'claworld activation did not return appToken and agentId',
1028
+ publicMessage: 'failed to activate Claworld account',
1029
+ recoverable: true,
1030
+ });
1031
+ }
1032
+ resolvedRuntimeConfig = applyRuntimeIdentity(resolvedRuntimeConfig, {
1033
+ appToken: activatedToken,
1034
+ relay: {
1035
+ ...(resolvedRuntimeConfig?.relay && typeof resolvedRuntimeConfig.relay === 'object' ? resolvedRuntimeConfig.relay : {}),
1036
+ agentId: activatedAgentId,
1037
+ },
1038
+ });
1039
+ resolvedAgentId = activatedAgentId;
1040
+ }
953
1041
  const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
954
1042
  const result = await fetchJson(fetchImpl, `${baseUrl}/v1/profile/public-identity`, {
955
1043
  method: 'PUT',
956
1044
  headers: {
957
1045
  'content-type': 'application/json',
958
- ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
959
- ...buildRuntimeAuthHeaders(runtimeConfig),
1046
+ ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
1047
+ ...buildRuntimeAuthHeaders(resolvedRuntimeConfig),
960
1048
  },
961
1049
  body: JSON.stringify({
962
- ...(normalizeClaworldText(agentId, null) ? { agentId: normalizeClaworldText(agentId, null) } : {}),
1050
+ ...(resolvedAgentId ? { agentId: resolvedAgentId } : {}),
963
1051
  displayName: normalizedDisplayName,
964
1052
  }),
965
1053
  });
966
1054
  if (!result.ok) {
967
1055
  createRelayRouteError({
968
1056
  result,
969
- runtimeConfig,
1057
+ runtimeConfig: resolvedRuntimeConfig,
970
1058
  code: 'public_identity_update_failed',
971
1059
  publicMessage: 'failed to update public identity',
972
1060
  context: {
973
- agentId: normalizeClaworldText(agentId, null),
1061
+ agentId: resolvedAgentId,
974
1062
  },
975
1063
  });
976
1064
  }
977
- return result.body || {};
1065
+ return {
1066
+ ...(result.body || {}),
1067
+ runtimeActivation: !resolveRuntimeAppToken(runtimeConfig)
1068
+ ? {
1069
+ status: 'activated',
1070
+ agentId: resolvedAgentId,
1071
+ }
1072
+ : null,
1073
+ runtimeConfig: resolvedRuntimeConfig,
1074
+ };
978
1075
  }
979
1076
 
980
1077
  async function registerRelayBinding({ runtimeConfig, fetchImpl, logger }) {
@@ -1890,6 +1987,18 @@ export function createClaworldChannelPlugin({
1890
1987
  });
1891
1988
  }
1892
1989
 
1990
+ function rememberAccountBinding({ runtimeConfig, accountId = null, bindingSource = 'binding_cache', relayAgent = null }) {
1991
+ const normalizedRuntimeConfig = applyRuntimeIdentity(runtimeConfig);
1992
+ const accountKey = resolveAccountBindingKey(normalizedRuntimeConfig, accountId || null);
1993
+ accountBindingStates.set(accountKey, {
1994
+ binding: {
1995
+ runtimeConfig: normalizedRuntimeConfig,
1996
+ bindingSource,
1997
+ ...(relayAgent ? { relayAgent } : {}),
1998
+ },
1999
+ });
2000
+ }
2001
+
1893
2002
  async function ensureAccountRelayBinding({ runtimeConfig, accountId = null }) {
1894
2003
  const normalizedRuntimeConfig = applyRuntimeIdentity(runtimeConfig);
1895
2004
  const accountKey = resolveAccountBindingKey(normalizedRuntimeConfig, accountId || null);
@@ -1937,7 +2046,7 @@ export function createClaworldChannelPlugin({
1937
2046
  return promise;
1938
2047
  }
1939
2048
 
1940
- async function persistRuntimeAppToken({ runtime, accountId, appToken }) {
2049
+ async function persistRuntimeAppToken({ runtime, accountId, appToken, relayAgentId = null }) {
1941
2050
  if (!runtime?.config?.loadConfig || !runtime?.config?.writeConfigFile) {
1942
2051
  return { skipped: true, reason: 'missing_runtime_config_io' };
1943
2052
  }
@@ -1960,43 +2069,67 @@ export function createClaworldChannelPlugin({
1960
2069
  ? accounts[accountId]
1961
2070
  : {};
1962
2071
 
1963
- if (normalizeClaworldText(account.appToken, null) === appToken) {
2072
+ const normalizedRelayAgentId = normalizeClaworldText(relayAgentId, normalizeClaworldText(account?.relay?.agentId, null));
2073
+ const currentAppToken = normalizeClaworldText(account.appToken, null);
2074
+ const currentRelayAgentId = normalizeClaworldText(account?.relay?.agentId, null);
2075
+ if (currentAppToken === appToken && currentRelayAgentId === normalizedRelayAgentId) {
1964
2076
  return { skipped: true, reason: 'already_persisted' };
1965
2077
  }
1966
2078
 
1967
2079
  accounts[accountId] = {
1968
2080
  ...account,
1969
2081
  appToken,
2082
+ relay: {
2083
+ ...(account?.relay && typeof account.relay === 'object' && !Array.isArray(account.relay) ? account.relay : {}),
2084
+ ...(normalizedRelayAgentId ? { agentId: normalizedRelayAgentId } : {}),
2085
+ },
1970
2086
  };
2087
+ delete accounts[accountId].registration;
1971
2088
  claworldRoot.accounts = accounts;
1972
2089
  nextCfg.channels.claworld = claworldRoot;
1973
2090
  await runtime.config.writeConfigFile(nextCfg);
1974
2091
  return { skipped: false, ok: true };
1975
2092
  }
1976
2093
 
1977
- async function resolveBoundRuntimeContext(context = {}) {
2094
+ function resolveConfiguredRuntimeContext(context = {}) {
1978
2095
  const cfg = context.cfg || {};
1979
2096
  const accountId = context.accountId || null;
1980
2097
  const runtimeContext = accountRuntimeContexts.get(accountId || 'default') || null;
1981
- let runtimeConfig = runtimeContext?.runtimeConfig || context.runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
2098
+ const runtimeConfig = runtimeContext?.runtimeConfig || context.runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
2099
+ return {
2100
+ ...context,
2101
+ cfg: runtimeContext?.cfg || cfg,
2102
+ accountId: runtimeConfig.accountId || accountId || null,
2103
+ runtimeConfig,
2104
+ agentId: context.agentId || runtimeConfig.relay?.agentId || null,
2105
+ bindingSource: runtimeContext?.deferredFailure ? 'runtime_context_deferred' : 'runtime_context',
2106
+ };
2107
+ }
2108
+
2109
+ async function resolveBoundRuntimeContext(context = {}) {
2110
+ const configuredContext = resolveConfiguredRuntimeContext(context);
2111
+ const cfg = configuredContext.cfg || {};
2112
+ const accountId = configuredContext.accountId || null;
2113
+ let runtimeConfig = configuredContext.runtimeConfig;
2114
+ const runtimeContext = accountRuntimeContexts.get(accountId || 'default') || null;
1982
2115
  if (runtimeContext?.runtimeConfig && !runtimeContext?.deferredFailure) {
1983
2116
  return {
1984
- ...context,
2117
+ ...configuredContext,
1985
2118
  cfg: runtimeContext.cfg || cfg,
1986
2119
  accountId: runtimeConfig.accountId || accountId || null,
1987
2120
  runtimeConfig,
1988
- agentId: context.agentId || runtimeConfig.relay?.agentId || null,
2121
+ agentId: configuredContext.agentId || runtimeConfig.relay?.agentId || null,
1989
2122
  bindingSource: 'runtime_context',
1990
2123
  };
1991
2124
  }
1992
2125
  const binding = await ensureAccountRelayBinding({ runtimeConfig, accountId });
1993
2126
  runtimeConfig = binding.runtimeConfig;
1994
2127
  return {
1995
- ...context,
2128
+ ...configuredContext,
1996
2129
  cfg,
1997
2130
  accountId: runtimeConfig.accountId || accountId || null,
1998
2131
  runtimeConfig,
1999
- agentId: context.agentId || runtimeConfig.relay?.agentId || null,
2132
+ agentId: configuredContext.agentId || runtimeConfig.relay?.agentId || null,
2000
2133
  bindingSource: binding.bindingSource,
2001
2134
  };
2002
2135
  }
@@ -2117,6 +2250,7 @@ export function createClaworldChannelPlugin({
2117
2250
  runtime: pluginRuntime,
2118
2251
  accountId: runtimeConfig.accountId,
2119
2252
  appToken: resolveRuntimeAppToken(runtimeConfig),
2253
+ relayAgentId: runtimeConfig.relay?.agentId || null,
2120
2254
  });
2121
2255
  if (!persisted.skipped) {
2122
2256
  logger.info?.(`[claworld:${runtimeAccountId}] persisted runtime appToken`, {
@@ -2304,6 +2438,77 @@ export function createClaworldChannelPlugin({
2304
2438
  };
2305
2439
  }
2306
2440
 
2441
+ async function getRuntimePublicIdentity(context = {}) {
2442
+ const resolvedContext = resolveConfiguredRuntimeContext(context);
2443
+ return fetchPublicIdentity({
2444
+ runtimeConfig: resolvedContext.runtimeConfig,
2445
+ agentId: resolvedContext.agentId || null,
2446
+ fetchImpl,
2447
+ });
2448
+ }
2449
+
2450
+ async function updateRuntimePublicIdentity(context = {}) {
2451
+ const resolvedContext = resolveConfiguredRuntimeContext(context);
2452
+ const updateResult = await updatePublicIdentity({
2453
+ runtimeConfig: resolvedContext.runtimeConfig,
2454
+ agentId: resolvedContext.agentId || null,
2455
+ displayName: context.displayName || null,
2456
+ fetchImpl,
2457
+ });
2458
+
2459
+ const runtimeActivation = updateResult?.runtimeActivation && typeof updateResult.runtimeActivation === 'object'
2460
+ ? updateResult.runtimeActivation
2461
+ : null;
2462
+ const nextRuntimeConfig = updateResult?.runtimeConfig && typeof updateResult.runtimeConfig === 'object'
2463
+ ? updateResult.runtimeConfig
2464
+ : resolvedContext.runtimeConfig;
2465
+ const nextAgentId = normalizeClaworldText(
2466
+ runtimeActivation?.agentId,
2467
+ normalizeClaworldText(resolvedContext.agentId, normalizeClaworldText(nextRuntimeConfig?.relay?.agentId, null)),
2468
+ );
2469
+
2470
+ if (runtimeActivation && resolveRuntimeAppToken(nextRuntimeConfig)) {
2471
+ const runtimeResolution = resolvePluginRuntimeCandidate(context.runtime || null);
2472
+ try {
2473
+ await persistRuntimeAppToken({
2474
+ runtime: runtimeResolution.runtime,
2475
+ accountId: resolvedContext.accountId || nextRuntimeConfig.accountId || null,
2476
+ appToken: resolveRuntimeAppToken(nextRuntimeConfig),
2477
+ relayAgentId: nextAgentId,
2478
+ });
2479
+ } catch (error) {
2480
+ logger.warn?.('[claworld:profile] failed to persist activated runtime binding', {
2481
+ accountId: resolvedContext.accountId || nextRuntimeConfig.accountId || null,
2482
+ error: error?.message || String(error),
2483
+ });
2484
+ }
2485
+
2486
+ rememberAccountBinding({
2487
+ runtimeConfig: nextRuntimeConfig,
2488
+ accountId: resolvedContext.accountId || nextRuntimeConfig.accountId || null,
2489
+ bindingSource: 'activated_app_token',
2490
+ });
2491
+
2492
+ const accountKey = resolveAccountBindingKey(nextRuntimeConfig, resolvedContext.accountId || null);
2493
+ const currentRuntimeContext = accountRuntimeContexts.get(accountKey) || null;
2494
+ if (currentRuntimeContext) {
2495
+ accountRuntimeContexts.set(accountKey, {
2496
+ ...currentRuntimeContext,
2497
+ runtimeConfig: nextRuntimeConfig,
2498
+ deferredFailure: null,
2499
+ deferredErrorMessage: null,
2500
+ });
2501
+ }
2502
+ }
2503
+
2504
+ const payload = updateResult && typeof updateResult === 'object' && !Array.isArray(updateResult)
2505
+ ? { ...updateResult }
2506
+ : {};
2507
+ delete payload.runtimeActivation;
2508
+ delete payload.runtimeConfig;
2509
+ return payload;
2510
+ }
2511
+
2307
2512
  return {
2308
2513
  id: 'claworld',
2309
2514
  meta: {
@@ -2556,23 +2761,8 @@ export function createClaworldChannelPlugin({
2556
2761
  },
2557
2762
  },
2558
2763
  profile: {
2559
- getPublicIdentity: async (context = {}) => {
2560
- const resolvedContext = await resolveBoundRuntimeContext(context);
2561
- return fetchPublicIdentity({
2562
- runtimeConfig: resolvedContext.runtimeConfig,
2563
- agentId: resolvedContext.agentId || null,
2564
- fetchImpl,
2565
- });
2566
- },
2567
- updatePublicIdentity: async (context = {}) => {
2568
- const resolvedContext = await resolveBoundRuntimeContext(context);
2569
- return updatePublicIdentity({
2570
- runtimeConfig: resolvedContext.runtimeConfig,
2571
- agentId: resolvedContext.agentId || null,
2572
- displayName: context.displayName || null,
2573
- fetchImpl,
2574
- });
2575
- },
2764
+ getPublicIdentity: getRuntimePublicIdentity,
2765
+ updatePublicIdentity: updateRuntimePublicIdentity,
2576
2766
  },
2577
2767
  postSetup: {
2578
2768
  fetchWorldDirectory: async (context = {}) => {
@@ -2725,23 +2915,8 @@ export function createClaworldChannelPlugin({
2725
2915
  demo,
2726
2916
  productShell: {
2727
2917
  profile: {
2728
- getPublicIdentity: async (context = {}) => {
2729
- const resolvedContext = await resolveBoundRuntimeContext(context);
2730
- return fetchPublicIdentity({
2731
- runtimeConfig: resolvedContext.runtimeConfig || null,
2732
- agentId: resolvedContext.agentId || null,
2733
- fetchImpl,
2734
- });
2735
- },
2736
- updatePublicIdentity: async (context = {}) => {
2737
- const resolvedContext = await resolveBoundRuntimeContext(context);
2738
- return updatePublicIdentity({
2739
- runtimeConfig: resolvedContext.runtimeConfig || null,
2740
- agentId: resolvedContext.agentId || null,
2741
- displayName: context.displayName || null,
2742
- fetchImpl,
2743
- });
2744
- },
2918
+ getPublicIdentity: getRuntimePublicIdentity,
2919
+ updatePublicIdentity: updateRuntimePublicIdentity,
2745
2920
  },
2746
2921
  fetchWorldDirectory: async (context = {}) => {
2747
2922
  const resolvedContext = await resolveBoundRuntimeContext(context);
@@ -355,7 +355,7 @@ function buildManagedAccountEntry(options = {}) {
355
355
 
356
356
  if (options.appToken) {
357
357
  base.appToken = options.appToken;
358
- } else {
358
+ } else if (normalizeText(options.registrationDisplayName, null)) {
359
359
  base.registration = {
360
360
  enabled: true,
361
361
  displayName: normalizeText(options.registrationDisplayName, options.displayName),
@@ -404,10 +404,18 @@ function buildMergedAccountEntry(existingAccount = {}, options = {}) {
404
404
  delete merged.toolProfile;
405
405
 
406
406
  if (options.appToken) {
407
- return {
407
+ const withToken = {
408
408
  ...merged,
409
409
  appToken: options.appToken,
410
410
  };
411
+ delete withToken.registration;
412
+ return withToken;
413
+ }
414
+
415
+ if (!normalizeText(options.registrationDisplayName, null)) {
416
+ const withoutRegistration = { ...merged };
417
+ delete withoutRegistration.registration;
418
+ return withoutRegistration;
411
419
  }
412
420
 
413
421
  return {
@@ -512,7 +520,9 @@ export function buildWorkspaceAgentsContent({
512
520
  } = {}) {
513
521
  const identityLine = appToken
514
522
  ? '- relay binding is resolved from the configured appToken at runtime'
515
- : '- relay binding is created during runtime bootstrap and persists as backend-issued credentials';
523
+ : registrationDisplayName
524
+ ? '- relay binding is created during runtime bootstrap and persists as backend-issued credentials'
525
+ : '- activation is pending until the user completes Claworld public identity setup';
516
526
 
517
527
  return `# Claworld Channel Agent
518
528
 
@@ -522,7 +532,7 @@ Routing contract:
522
532
 
523
533
  - local OpenClaw agent id: \`${agentId}\`
524
534
  - claworld account id: \`${accountId}\`
525
- ${registrationDisplayName ? `- bootstrap display name: \`${registrationDisplayName}\`` : '- credential mode: appToken/manual binding'}
535
+ ${registrationDisplayName ? `- bootstrap display name: \`${registrationDisplayName}\`` : (appToken ? '- credential mode: appToken/manual binding' : '- credential mode: activation pending')}
526
536
  ${identityLine}
527
537
  ${defaultTargetAgentId ? `- default outbound target agentId: \`${defaultTargetAgentId}\`` : '- outbound sends require explicit target agentId inputs'}
528
538
 
@@ -553,7 +563,9 @@ export function buildWorkspaceMemoryContent({
553
563
  } = {}) {
554
564
  const identityLine = appToken
555
565
  ? '- relay binding: resolved from appToken at runtime'
556
- : '- relay binding: assigned during runtime bootstrap';
566
+ : registrationDisplayName
567
+ ? '- relay binding: assigned during runtime bootstrap'
568
+ : '- relay binding: pending until public identity setup completes';
557
569
 
558
570
  return `# Claworld Memory
559
571
 
@@ -661,10 +673,7 @@ export function resolveClaworldManagedRuntimeOptions({
661
673
  ? null
662
674
  : normalizeRegistrationDisplayName(
663
675
  explicitRegistrationDisplayName,
664
- normalizeRegistrationDisplayName(
665
- existingRegistrationDisplayName,
666
- displayName,
667
- ),
676
+ existingRegistrationDisplayName,
668
677
  );
669
678
  const approvalMode = normalizeChatRequestApprovalMode(
670
679
  normalizeText(overrides.approvalMode, null),
@@ -716,10 +725,6 @@ export function applyClaworldManagedRuntimeConfig(inputConfig = {}, options = {}
716
725
  const sessionDmScope = normalizeText(options.sessionDmScope, DEFAULT_CLAWORLD_DM_SCOPE);
717
726
  const manageAgentEntry = options.manageAgentEntry === true;
718
727
 
719
- if (!options.appToken && !normalizeText(options.registrationDisplayName, null)) {
720
- throw new Error('claworld registration displayName is required when appToken is absent');
721
- }
722
-
723
728
  const removedManagedToolNames = new Set([
724
729
  ...CLAWORLD_PUBLIC_TOOL_NAMES,
725
730
  ...CLAWORLD_COMPATIBILITY_TOOL_NAMES,
@@ -84,8 +84,11 @@ function validateClaworldSetupInput({ cfg = {}, accountId = null, input = {} } =
84
84
  const registrationDisplayName = normalizeText(
85
85
  input.name,
86
86
  normalizeText(
87
- inspected?.registration?.displayName,
88
- normalizeText(inspected?.localAgent?.displayName, null),
87
+ inspected?.name,
88
+ normalizeText(
89
+ inspected?.registration?.displayName,
90
+ normalizeText(inspected?.localAgent?.displayName, null),
91
+ ),
89
92
  ),
90
93
  );
91
94
  if (!appToken && !registrationDisplayName) {
@@ -105,8 +108,11 @@ function currentManagedIdentityInput({ cfg = {}, accountId = null } = {}) {
105
108
  }
106
109
 
107
110
  const currentDisplayName = normalizeText(
108
- inspected?.registration?.displayName,
109
- normalizeText(inspected?.localAgent?.displayName, null),
111
+ inspected?.name,
112
+ normalizeText(
113
+ inspected?.registration?.displayName,
114
+ normalizeText(inspected?.localAgent?.displayName, null),
115
+ ),
110
116
  );
111
117
  return currentDisplayName
112
118
  ? { name: currentDisplayName }
@@ -163,11 +169,17 @@ function applyManagedAccountName({ cfg = {}, accountId, name } = {}) {
163
169
  }
164
170
 
165
171
  function resolveManagedOptionsFromContext({ cfg = {}, accountId = null, input = {}, overrides = {} } = {}) {
172
+ const normalizedInput = ensureObject(input);
173
+ const resolvedInput = { ...normalizedInput };
174
+ delete resolvedInput.name;
166
175
  return resolveClaworldManagedRuntimeOptions({
167
176
  cfg,
168
177
  accountId: normalizeText(accountId, DEFAULT_CLAWORLD_ACCOUNT_ID),
169
- input,
170
- overrides,
178
+ input: resolvedInput,
179
+ overrides: {
180
+ ...overrides,
181
+ ...(normalizeText(normalizedInput.name, null) ? { displayName: normalizedInput.name } : {}),
182
+ },
171
183
  });
172
184
  }
173
185
 
@@ -187,9 +199,9 @@ async function applyManagedOnboardingConfig({
187
199
  const noteLines = [
188
200
  `Bound local agent/account: ${managedOptions.agentId}`,
189
201
  `Remote backend: ${managedOptions.serverUrl}`,
190
- managedOptions.registrationDisplayName
191
- ? `Bootstrap mode: registration (${managedOptions.registrationDisplayName})`
192
- : 'Bootstrap mode: appToken/manual binding',
202
+ managedOptions.appToken
203
+ ? 'Activation state: ready via configured appToken'
204
+ : 'Activation state: pending until claworld_update_public_identity runs',
193
205
  managedOptions.manageWorkspace
194
206
  ? 'This flow refreshes plugin-side config and the dedicated claworld workspace contract. It does not start a backend service.'
195
207
  : 'This flow refreshes plugin-side config and binds claworld onto the existing local agent. It does not start a backend service.',
@@ -178,12 +178,12 @@ function withToolErrorBoundary(toolName, execute) {
178
178
  };
179
179
  }
180
180
 
181
- async function resolveToolContext(api, plugin, params = {}) {
181
+ async function resolveToolContext(api, plugin, params = {}, { bindRuntime = true } = {}) {
182
182
  const cfg = await loadCurrentConfig(api);
183
183
  const accountId = normalizeText(params.accountId, plugin.config.defaultAccountId(cfg) || null);
184
184
  const runtimeConfig = plugin.config.resolveRuntimeConfig(cfg, accountId);
185
185
 
186
- if (typeof plugin.helpers?.resolveToolRuntimeContext === 'function') {
186
+ if (bindRuntime && typeof plugin.helpers?.resolveToolRuntimeContext === 'function') {
187
187
  return await plugin.helpers.resolveToolRuntimeContext({
188
188
  cfg,
189
189
  accountId,
@@ -1128,19 +1128,30 @@ function buildRegisteredTools(api, plugin) {
1128
1128
  runtimeConfig,
1129
1129
  });
1130
1130
  const pairedAgentId = payload.runtimeConfig?.relay?.agentId || payload.relayAgent?.agentId || null;
1131
- const publicIdentity = pairedAgentId
1132
- ? await plugin.runtime.productShell.profile.getPublicIdentity({
1133
- cfg,
1134
- accountId,
1135
- runtimeConfig,
1136
- agentId: pairedAgentId,
1137
- })
1138
- : null;
1131
+ const publicIdentity = await plugin.runtime.productShell.profile.getPublicIdentity({
1132
+ cfg,
1133
+ accountId,
1134
+ runtimeConfig: payload.runtimeConfig || runtimeConfig,
1135
+ agentId: pairedAgentId,
1136
+ });
1137
+ const ready = payload.status === 'paired' && publicIdentity?.ready === true;
1138
+ const readiness = payload.status === 'paired'
1139
+ ? (publicIdentity?.ready === true ? 'paired_and_ready' : 'paired_but_identity_pending')
1140
+ : 'installed_unactivated';
1139
1141
  return buildToolResult({
1140
- status: payload.status,
1142
+ status: ready ? 'ready' : 'pending',
1143
+ ready,
1144
+ readiness,
1141
1145
  reason: payload.reason || null,
1146
+ requiredAction: publicIdentity?.requiredAction || (ready ? null : 'set_public_identity'),
1147
+ nextAction: publicIdentity?.nextAction || (ready ? 'continue_claworld_flow' : 'set_public_identity'),
1148
+ nextTool: publicIdentity?.nextTool || (ready ? null : 'claworld_update_public_identity'),
1149
+ missingFields: Array.isArray(publicIdentity?.missingFields) ? publicIdentity.missingFields : [],
1142
1150
  accountId: payload.runtimeConfig?.accountId || accountId,
1143
1151
  bindingSource: payload.bindingSource || null,
1152
+ activation: {
1153
+ status: payload.status === 'paired' ? 'ready' : 'pending',
1154
+ },
1144
1155
  relay: {
1145
1156
  agentId: payload.runtimeConfig?.relay?.agentId || payload.relayAgent?.agentId || null,
1146
1157
  displayName: payload.relayAgent?.displayName || null,
@@ -1148,6 +1159,7 @@ function buildRegisteredTools(api, plugin) {
1148
1159
  contactable: payload.relayAgent?.contactable ?? null,
1149
1160
  online: payload.relayAgent?.online ?? null,
1150
1161
  resolved: payload.relayAgent?.resolved ?? null,
1162
+ bindingStatus: payload.status === 'paired' ? 'bound' : 'unactivated',
1151
1163
  },
1152
1164
  publicIdentity: publicIdentity
1153
1165
  ? {
@@ -1192,7 +1204,7 @@ function buildRegisteredTools(api, plugin) {
1192
1204
  },
1193
1205
  }),
1194
1206
  async execute(_toolCallId, params = {}) {
1195
- const context = await resolveToolContext(api, plugin, params);
1207
+ const context = await resolveToolContext(api, plugin, params, { bindRuntime: false });
1196
1208
  const payload = await plugin.runtime.productShell.profile.getPublicIdentity(context);
1197
1209
  return buildToolResult(payload);
1198
1210
  },
@@ -1221,7 +1233,7 @@ function buildRegisteredTools(api, plugin) {
1221
1233
  },
1222
1234
  }),
1223
1235
  async execute(_toolCallId, params = {}) {
1224
- const context = await resolveToolContext(api, plugin, params);
1236
+ const context = await resolveToolContext(api, plugin, params, { bindRuntime: false });
1225
1237
  const payload = await plugin.runtime.productShell.profile.updatePublicIdentity({
1226
1238
  ...context,
1227
1239
  displayName: params.displayName,
@@ -164,9 +164,9 @@ export function createOnboardingService({ worldService, store = null } = {}) {
164
164
  'installer validates OpenClaw availability and minimum host version',
165
165
  'installer verifies or installs the claworld OpenClaw plugin package',
166
166
  'installer writes or refreshes the managed claworld channel config and binds it to the local main agent by default',
167
- 'installer calls POST /v1/onboarding/activate to obtain agentId and appToken when reuse is not possible',
168
- 'installer persists the returned appToken into the managed claworld account config',
169
- 'installer reloads or starts the runtime and verifies the relay binding',
167
+ 'installer reloads or starts the runtime and verifies the managed channel shape without requiring backend activation',
168
+ 'first-run readiness goes through claworld_pair_agent and then claworld_update_public_identity',
169
+ 'claworld_update_public_identity performs activation when needed and completes the public displayName#code identity',
170
170
  `ongoing lifecycle uses ${CLAWORLD_UPDATE_COMMAND} for tracked package updates plus managed repair, then ${CLAWORLD_DOCTOR_COMMAND} for health confirmation`,
171
171
  ],
172
172
  recommendedWorlds,
@@ -182,8 +182,9 @@ export function createOnboardingService({ worldService, store = null } = {}) {
182
182
  selectedWorld: selectedWorld ? { worldId: selectedWorld.worldId, displayName: selectedWorld.displayName } : null,
183
183
  actions: [
184
184
  'install the claworld plugin and write the managed config shape',
185
- 'activate the install through POST /v1/onboarding/activate',
186
- 'persist the returned appToken and verify the runtime binding',
185
+ 'run claworld_pair_agent to confirm readiness after install',
186
+ 'if public identity is pending, call claworld_update_public_identity',
187
+ 'claworld_update_public_identity activates the backend binding when needed and persists the returned appToken',
187
188
  'collect required world profile fields',
188
189
  'validate world membership eligibility',
189
190
  'start the first A2A loop or deliver world content',