@xfxstudio/claworld 2026.6.10 → 2026.6.29-testing.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.
@@ -10,6 +10,10 @@ import {
10
10
  inspectClaworldChannelAccount,
11
11
  listClaworldAccountIds,
12
12
  } from './config-schema.js';
13
+ import {
14
+ fetchJson,
15
+ normalizeRelayHttpBaseUrl,
16
+ } from '../runtime/http-boundary.js';
13
17
 
14
18
  function collectUnsupportedSetupFlags(input = {}) {
15
19
  const unsupported = [];
@@ -102,7 +106,7 @@ function hasClaworldBinding(config = {}, { agentId, accountId } = {}) {
102
106
  });
103
107
  }
104
108
 
105
- function isRelayBootstrapReady(account = {}) {
109
+ function hasRuntimeCredential(account = {}) {
106
110
  return Boolean(
107
111
  account?.configured
108
112
  && normalizeText(account?.appToken, null),
@@ -129,7 +133,7 @@ export function inspectManagedClaworldInstall({
129
133
  const accountStatus = managedAccountPresent
130
134
  ? inspectClaworldChannelAccount(cfg, managedOptions.accountId)
131
135
  : inspectClaworldChannelAccount({}, managedOptions.accountId);
132
- const activationReady = isRelayBootstrapReady(accountStatus);
136
+ const runtimeCredentialReady = hasRuntimeCredential(accountStatus);
133
137
  const setupReady = Boolean(
134
138
  managedAccountPresent
135
139
  && managedBindingPresent
@@ -139,13 +143,13 @@ export function inspectManagedClaworldInstall({
139
143
  let selectionHint = 'remote relay world channel';
140
144
  let quickstartScore = 5;
141
145
 
142
- if (setupReady && activationReady) {
146
+ if (setupReady && runtimeCredentialReady) {
143
147
  statusLabel = 'configured';
144
148
  selectionHint = 'configured · ready';
145
149
  quickstartScore = 2;
146
150
  } else if (setupReady) {
147
- statusLabel = 'configured (activation pending)';
148
- selectionHint = 'configured · activation pending';
151
+ statusLabel = 'configured (email verification pending)';
152
+ selectionHint = 'configured · email verification pending';
149
153
  quickstartScore = 3;
150
154
  } else if (managedAccountPresent && !managedBindingPresent) {
151
155
  statusLabel = 'configured (binding pending)';
@@ -166,7 +170,7 @@ export function inspectManagedClaworldInstall({
166
170
  managedAgentPresent,
167
171
  managedBindingPresent,
168
172
  accountStatus,
169
- activationReady,
173
+ runtimeCredentialReady,
170
174
  setupReady,
171
175
  statusLabel,
172
176
  selectionHint,
@@ -184,7 +188,7 @@ export function buildClaworldOnboardingStatus({
184
188
  statusLines: [`Claworld: ${inspection.statusLabel}`],
185
189
  selectionHint: inspection.selectionHint,
186
190
  quickstartScore: inspection.quickstartScore,
187
- activationReady: inspection.activationReady,
191
+ runtimeCredentialReady: inspection.runtimeCredentialReady,
188
192
  };
189
193
  }
190
194
 
@@ -231,25 +235,172 @@ function resolveManagedOptionsFromContext({ cfg = {}, accountId = null, input =
231
235
  });
232
236
  }
233
237
 
238
+ function resolveSetupFetchImpl(runtime = {}) {
239
+ const candidate = runtime?.fetchImpl
240
+ || runtime?.fetch
241
+ || (typeof globalThis.fetch === 'function' ? globalThis.fetch : null);
242
+ if (typeof candidate !== 'function') {
243
+ throw new Error('Claworld setup requires fetch support to complete email verification.');
244
+ }
245
+ return (url, init) => candidate(url, init);
246
+ }
247
+
248
+ function validateSetupEmail(value) {
249
+ const normalized = normalizeText(value, null);
250
+ if (!normalized) return 'Email is required.';
251
+ if (!normalized.includes('@')) return 'Enter a valid email address.';
252
+ return null;
253
+ }
254
+
255
+ function validateSetupVerificationCode(value) {
256
+ return normalizeText(value, null) ? null : 'Verification code is required.';
257
+ }
258
+
259
+ function formatSetupApiError(result, fallbackMessage) {
260
+ const body = ensureObject(result?.body);
261
+ const detail = normalizeText(
262
+ body.publicMessage,
263
+ normalizeText(body.error, normalizeText(body.message, null)),
264
+ );
265
+ return detail ? `${fallbackMessage}: ${detail}` : fallbackMessage;
266
+ }
267
+
268
+ async function startSetupEmailVerification({
269
+ runtimeConfig,
270
+ email,
271
+ displayName = null,
272
+ fetchImpl,
273
+ } = {}) {
274
+ const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
275
+ const normalizedEmail = normalizeText(email, null);
276
+ const normalizedDisplayName = normalizeText(displayName, null);
277
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/identity/email/start`, {
278
+ method: 'POST',
279
+ headers: {
280
+ 'content-type': 'application/json',
281
+ ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
282
+ },
283
+ body: JSON.stringify({
284
+ email: normalizedEmail,
285
+ ...(normalizedDisplayName ? { displayName: normalizedDisplayName } : {}),
286
+ }),
287
+ });
288
+ if (!result.ok) {
289
+ throw new Error(formatSetupApiError(result, 'Failed to start Claworld email verification'));
290
+ }
291
+ return ensureObject(result.body);
292
+ }
293
+
294
+ async function completeSetupEmailVerification({
295
+ runtimeConfig,
296
+ email,
297
+ code,
298
+ fetchImpl,
299
+ } = {}) {
300
+ const baseUrl = normalizeRelayHttpBaseUrl(runtimeConfig.serverUrl);
301
+ const result = await fetchJson(fetchImpl, `${baseUrl}/v1/identity/email/verify`, {
302
+ method: 'POST',
303
+ headers: {
304
+ 'content-type': 'application/json',
305
+ ...(runtimeConfig.apiKey ? { 'x-api-key': runtimeConfig.apiKey } : {}),
306
+ },
307
+ body: JSON.stringify({
308
+ email: normalizeText(email, null),
309
+ code: normalizeText(code, null),
310
+ }),
311
+ });
312
+ if (!result.ok) {
313
+ throw new Error(formatSetupApiError(result, 'Failed to complete Claworld email verification'));
314
+ }
315
+ return ensureObject(result.body);
316
+ }
317
+
318
+ async function resolveSetupCredential({
319
+ managedOptions,
320
+ prompter,
321
+ runtime = {},
322
+ input = {},
323
+ } = {}) {
324
+ if (managedOptions.appToken) return null;
325
+
326
+ const fetchImpl = resolveSetupFetchImpl(runtime);
327
+ const email = normalizeText(await prompter.text({
328
+ message: 'Claworld account email',
329
+ placeholder: 'you@example.com',
330
+ validate: validateSetupEmail,
331
+ }), null);
332
+
333
+ await startSetupEmailVerification({
334
+ runtimeConfig: managedOptions,
335
+ email,
336
+ displayName: normalizeText(input?.name, null),
337
+ fetchImpl,
338
+ });
339
+
340
+ const code = normalizeText(await prompter.text({
341
+ message: `Verification code sent to ${email}`,
342
+ placeholder: '123456',
343
+ validate: validateSetupVerificationCode,
344
+ }), null);
345
+
346
+ const verification = await completeSetupEmailVerification({
347
+ runtimeConfig: managedOptions,
348
+ email,
349
+ code,
350
+ fetchImpl,
351
+ });
352
+ const appToken = normalizeText(verification.appToken, null);
353
+ const relayAgentId = normalizeText(verification.agentId, null);
354
+ if (!appToken || !relayAgentId) {
355
+ throw new Error('Claworld email verification did not return appToken and agentId.');
356
+ }
357
+ return {
358
+ email,
359
+ appToken,
360
+ relayAgentId,
361
+ created: verification.created === true,
362
+ recovered: verification.recovered === true,
363
+ };
364
+ }
365
+
234
366
  async function applyManagedOnboardingConfig({
235
367
  cfg = {},
368
+ runtime = {},
236
369
  prompter,
237
370
  accountId = null,
238
371
  phase = 'setup',
239
372
  input = {},
240
373
  } = {}) {
241
- const managedOptions = resolveManagedOptionsFromContext({ cfg, accountId, input });
374
+ const initialManagedOptions = resolveManagedOptionsFromContext({ cfg, accountId, input });
375
+ const setupCredential = await resolveSetupCredential({
376
+ managedOptions: initialManagedOptions,
377
+ prompter,
378
+ runtime,
379
+ input,
380
+ });
381
+ const managedOptions = setupCredential
382
+ ? {
383
+ ...initialManagedOptions,
384
+ appToken: setupCredential.appToken,
385
+ relayAgentId: setupCredential.relayAgentId,
386
+ }
387
+ : initialManagedOptions;
242
388
  const next = applyClaworldManagedRuntimeConfig(cfg, managedOptions);
243
389
 
244
390
  const noteLines = [
245
391
  `Bound local agent/account: ${managedOptions.agentId}`,
246
392
  `Remote backend: ${managedOptions.serverUrl}`,
247
- managedOptions.appToken
248
- ? 'Activation state: ready via configured appToken'
249
- : 'Activation state: pending until claworld_manage_account(action=activate_account) runs',
393
+ setupCredential
394
+ ? `Email verification: completed for ${setupCredential.email}; runtime credential saved to OpenClaw config`
395
+ : managedOptions.appToken
396
+ ? 'Runtime credential: configured appToken is present'
397
+ : 'Email verification: pending until claworld_manage_account(action=start_email_verification|complete_email_verification) runs',
398
+ managedOptions.relayAgentId
399
+ ? `Remote agent identity: ${managedOptions.relayAgentId}`
400
+ : 'Remote agent identity: pending',
250
401
  'Workspace memory: runtime prompt bootstrap maintains .claworld/ in the active host workspace',
251
402
  'This flow refreshes plugin-side config and binds claworld onto the selected local agent.',
252
- 'Setup lifecycle: OpenClaw host-native setup.',
403
+ 'Setup lifecycle: OpenClaw host-native setup; channel reload is handled by config reload.',
253
404
  ];
254
405
  await prompter.note(
255
406
  noteLines.join('\n'),
@@ -294,17 +445,19 @@ export const claworldOnboardingAdapter = {
294
445
  }),
295
446
  };
296
447
  },
297
- configure: async ({ cfg, prompter, accountOverrides }) =>
448
+ configure: async ({ cfg, runtime, prompter, accountOverrides }) =>
298
449
  applyManagedOnboardingConfig({
299
450
  cfg,
451
+ runtime,
300
452
  prompter,
301
453
  accountId: accountOverrides?.claworld,
302
454
  phase: 'setup',
303
455
  input: {},
304
456
  }),
305
- configureWhenConfigured: async ({ cfg, prompter, accountOverrides }) =>
457
+ configureWhenConfigured: async ({ cfg, runtime, prompter, accountOverrides }) =>
306
458
  applyManagedOnboardingConfig({
307
459
  cfg,
460
+ runtime,
308
461
  prompter,
309
462
  accountId: accountOverrides?.claworld,
310
463
  phase: 'refresh',
@@ -548,11 +548,12 @@ function projectToolShareCard(payload = null) {
548
548
  const imageUrl = normalizeText(payload?.imageUrl, null);
549
549
  const downloadUrl = normalizeText(payload?.downloadUrl, imageUrl);
550
550
  const templateId = normalizeText(payload?.templateId, null);
551
+ const variant = normalizeText(payload?.variant, null);
551
552
  const imageFormat = normalizeText(payload?.imageFormat, null);
552
553
  const mimeType = normalizeText(payload?.mimeType, null);
553
554
  const expiresAt = normalizeText(payload?.expiresAt, null);
554
555
  const description = normalizeText(payload?.description, null);
555
- if (!imageUrl && !downloadUrl && !templateId && !imageFormat && !mimeType && !expiresAt && !description) {
556
+ if (!imageUrl && !downloadUrl && !templateId && !variant && !imageFormat && !mimeType && !expiresAt && !description) {
556
557
  return {
557
558
  status: normalizeText(payload?.status, 'unavailable'),
558
559
  reason: normalizeText(payload?.reason, null),
@@ -564,6 +565,7 @@ function projectToolShareCard(payload = null) {
564
565
  imageUrl,
565
566
  downloadUrl,
566
567
  templateId,
568
+ variant,
567
569
  imageFormat,
568
570
  mimeType,
569
571
  expiresAt,
@@ -686,20 +688,29 @@ export function projectToolAccountViewResponse({
686
688
  } = {}) {
687
689
  const publicIdentityState = projectToolAccountIdentityFields(identityPayload);
688
690
  const accountProfile = projectToolAccountProfileState(identityPayload);
689
- const identityReady = identityPayload?.ready === true;
691
+ const publicIdentityReady = identityPayload?.ready === true;
690
692
  const accountProfileReady = accountProfile.ready === true;
691
- const activationReady = pairingPayload?.status === 'paired';
693
+ const emailVerified = pairingPayload?.emailVerified === true;
694
+ const runtimePaired = pairingPayload?.status === 'paired';
692
695
  const bindingReady = typeof pairingPayload?.bindingReady === 'boolean'
693
696
  ? pairingPayload.bindingReady
694
- : activationReady;
697
+ : runtimePaired;
695
698
  const bindingStatus = normalizeText(
696
699
  pairingPayload?.bindingStatus,
697
- activationReady
700
+ runtimePaired
698
701
  ? (bindingReady ? 'bound' : 'identity_unresolved')
699
- : 'unactivated',
702
+ : 'unbound',
700
703
  );
701
- const ready = activationReady && identityReady && accountProfileReady;
702
- const blockedAction = !identityReady
704
+ const ready = emailVerified && publicIdentityReady && accountProfileReady;
705
+ const blockedAction = !emailVerified
706
+ ? {
707
+ requiredAction: 'start_email_verification',
708
+ nextAction: 'start_email_verification',
709
+ nextTool: 'claworld_manage_account',
710
+ missingFields: [{ fieldId: 'email', label: 'Identity Email', description: 'Email-based identity verification is required before using other account features.' }],
711
+ reason: 'email_verification_required',
712
+ }
713
+ : !publicIdentityReady
703
714
  ? {
704
715
  requiredAction: publicIdentityState.requiredAction,
705
716
  nextAction: publicIdentityState.nextAction,
@@ -731,22 +742,28 @@ export function projectToolAccountViewResponse({
731
742
  action: 'view',
732
743
  status: ready ? 'ready' : 'pending',
733
744
  ready,
734
- readiness: pairingPayload?.status === 'paired'
735
- ? (identityReady
736
- ? (accountProfileReady ? 'paired_and_ready' : 'paired_but_account_profile_incomplete')
737
- : 'paired_but_identity_pending')
738
- : 'installed_unactivated',
745
+ readiness: !emailVerified
746
+ ? 'email_verification_required'
747
+ : !publicIdentityReady
748
+ ? 'public_identity_incomplete'
749
+ : accountProfileReady
750
+ ? 'ready'
751
+ : 'account_profile_incomplete',
739
752
  accountId: normalizeText(pairingPayload?.runtimeConfig?.accountId, normalizeText(accountId, null)),
740
- reason: normalizeText(pairingPayload?.reason, blockedAction.reason),
753
+ reason: blockedAction.reason,
754
+ bindingReason: normalizeText(pairingPayload?.reason, null),
741
755
  bindingSource: normalizeText(pairingPayload?.bindingSource, null),
742
- activation: {
743
- status: activationReady ? 'ready' : 'pending',
756
+ emailVerification: {
757
+ status: emailVerified ? 'verified' : 'pending',
758
+ email: normalizeText(pairingPayload?.email, null),
759
+ verifiedAt: normalizeText(pairingPayload?.verifiedAt, null),
744
760
  },
745
761
  diagnostics: {
746
762
  toolReachable: true,
763
+ emailVerified,
747
764
  bindingReady,
748
765
  bindingStatus,
749
- publicIdentityReady: identityReady,
766
+ publicIdentityReady,
750
767
  accountProfileReady,
751
768
  relayPresenceResolved: relayResolved,
752
769
  relayOnline,
@@ -781,7 +798,7 @@ export function projectToolAccountMutationResponse({
781
798
  accountId = null,
782
799
  identityPayload = null,
783
800
  shareCard = undefined,
784
- runtimeActivation = null,
801
+ runtimeIdentity = null,
785
802
  } = {}) {
786
803
  const publicIdentityState = projectToolAccountIdentityFields(identityPayload);
787
804
  const accountProfile = projectToolAccountProfileState(identityPayload);
@@ -790,10 +807,21 @@ export function projectToolAccountMutationResponse({
790
807
  : (identityPayload && Object.prototype.hasOwnProperty.call(identityPayload, 'shareCard')
791
808
  ? projectToolShareCard(identityPayload.shareCard)
792
809
  : undefined);
793
- const identityReady = identityPayload?.ready === true;
810
+ const publicIdentityReady = identityPayload?.ready === true;
794
811
  const accountProfileReady = accountProfile.ready === true;
795
- const ready = identityReady && accountProfileReady;
796
- const blockedAction = !identityReady
812
+ const emailVerificationPayload = normalizeObject(identityPayload?.emailVerification, null);
813
+ const emailVerified = identityPayload?.emailVerified === true
814
+ || normalizeText(emailVerificationPayload?.status, null) === 'verified';
815
+ const ready = emailVerified && publicIdentityReady && accountProfileReady;
816
+ const blockedAction = !emailVerified
817
+ ? {
818
+ requiredAction: 'start_email_verification',
819
+ nextAction: 'start_email_verification',
820
+ nextTool: 'claworld_manage_account',
821
+ missingFields: [{ fieldId: 'email', label: 'Identity Email', description: 'Email-based identity verification is required before using other account features.' }],
822
+ reason: 'email_verification_required',
823
+ }
824
+ : !publicIdentityReady
797
825
  ? {
798
826
  requiredAction: publicIdentityState.requiredAction,
799
827
  nextAction: publicIdentityState.nextAction,
@@ -820,10 +848,24 @@ export function projectToolAccountMutationResponse({
820
848
  action,
821
849
  status: ready ? 'ready' : 'pending',
822
850
  ready,
823
- readiness: identityReady
824
- ? (accountProfileReady ? 'ready' : 'account_profile_incomplete')
825
- : 'public_identity_incomplete',
851
+ readiness: !emailVerified
852
+ ? 'email_verification_required'
853
+ : !publicIdentityReady
854
+ ? 'public_identity_incomplete'
855
+ : accountProfileReady
856
+ ? 'ready'
857
+ : 'account_profile_incomplete',
826
858
  accountId: normalizeText(accountId, null),
859
+ emailVerification: {
860
+ status: emailVerified ? 'verified' : 'pending',
861
+ email: normalizeText(emailVerificationPayload?.email, null),
862
+ verifiedAt: normalizeText(emailVerificationPayload?.verifiedAt, null),
863
+ },
864
+ diagnostics: {
865
+ emailVerified,
866
+ publicIdentityReady,
867
+ accountProfileReady,
868
+ },
827
869
  profile: accountProfile.profile,
828
870
  ...publicIdentityState,
829
871
  accountProfile,
@@ -835,7 +877,7 @@ export function projectToolAccountMutationResponse({
835
877
  pluginVersionStatus: projectToolPluginVersionStatus(identityPayload?.pluginVersionStatus),
836
878
  chatRequestApprovalPolicy: projectToolChatRequestApprovalPolicy(identityPayload?.chatRequestApprovalPolicy),
837
879
  ...(resolvedShareCard !== undefined ? { shareCard: resolvedShareCard } : {}),
838
- ...(runtimeActivation ? { runtimeActivation } : {}),
880
+ ...(runtimeIdentity ? { runtimeIdentity } : {}),
839
881
  ...(action === 'update_identity'
840
882
  ? {
841
883
  updated: resolvedShareCard && resolvedShareCard.status === 'ready'
@@ -181,7 +181,6 @@ const TERMINAL_CONVERSATION_ACTIONS = PUBLIC_TOOL_ACTION_CATALOG.claworld_manage
181
181
 
182
182
  const ACCOUNT_IMPLEMENTATION_ACTIONS = Object.freeze({
183
183
  view_account: 'view',
184
- activate_account: 'update_identity',
185
184
  });
186
185
 
187
186
  const WORLD_IMPLEMENTATION_ACTIONS = Object.freeze({
@@ -429,6 +428,11 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
429
428
  minLength: 1,
430
429
  examples: ['claworld'],
431
430
  });
431
+ const shareCardVariantProperty = stringParam({
432
+ description: 'Optional share-card version. Choose from the user\'s usual communication language or sharing context: zh for Chinese, en for languages outside Chinese.',
433
+ enumValues: ['en', 'zh'],
434
+ examples: ['en', 'zh'],
435
+ });
432
436
  const worldIdProperty = stringParam({
433
437
  description: 'Canonical world id.',
434
438
  minLength: 1,
@@ -444,12 +448,13 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
444
448
  {
445
449
  name: accountTool,
446
450
  label: 'Claworld Manage Account',
447
- description: 'Terminal account surface for readiness, public identity, global profile, share-card generation, and account-level chat policy.',
451
+ description: 'Terminal account surface for readiness, public identity, global profile, share-card generation, account-level chat policy, and email-based identity verification.',
448
452
  metadata: buildToolMetadata({
449
453
  category: 'account',
450
454
  usageNotes: [
451
- 'Use this owner-facing account surface for activation, profile, policy, and subscription decisions.',
455
+ 'Use this human-facing account surface for identity verification, profile, policy, and subscription decisions.',
452
456
  'Use action=view_account for readiness; update_display_name, update_agent_profile, or set_chat_policy for common account mutations.',
457
+ 'Use start_email_verification with email + optional displayName to start email-based identity verification, then complete_email_verification with email + code to finish.',
453
458
  'Use subscribe_person or unsubscribe_person when a search/profile result exposes a person subscription target.',
454
459
  ],
455
460
  }),
@@ -461,10 +466,10 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
461
466
  action: stringParam({
462
467
  description: 'Account action.',
463
468
  enumValues: TERMINAL_ACCOUNT_ACTIONS,
464
- examples: ['view_account', 'update_display_name', 'set_chat_policy'],
469
+ examples: ['view_account', 'start_email_verification', 'update_display_name', 'set_chat_policy'],
465
470
  }),
466
471
  displayName: stringParam({
467
- description: 'Public-facing display name for activate_account or update_display_name.',
472
+ description: 'Public-facing display name for update_display_name or start_email_verification.',
468
473
  minLength: 1,
469
474
  examples: ['Moza', '小发发'],
470
475
  }),
@@ -473,7 +478,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
473
478
  examples: ['喜欢慢节奏介绍,也愿意先让 agent 做初步认识。🙂'],
474
479
  }),
475
480
  humanProfile: stringParam({
476
- description: 'Owner-facing human profile text for update_human_profile.',
481
+ description: 'Human-facing profile text for update_human_profile.',
477
482
  examples: ['周末在上海,喜欢网球和安静咖啡馆。'],
478
483
  }),
479
484
  agentProfile: stringParam({
@@ -507,11 +512,22 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
507
512
  generateShareCard: booleanParam({
508
513
  description: 'When true, include a temporary public identity card when supported.',
509
514
  }),
515
+ shareCardVariant: shareCardVariantProperty,
510
516
  expiresInSeconds: integerParam({
511
517
  description: 'Optional temporary share-card TTL in seconds.',
512
518
  minimum: 1,
513
519
  examples: [7200],
514
520
  }),
521
+ email: stringParam({
522
+ description: 'Email address for start_email_verification or complete_email_verification.',
523
+ minLength: 1,
524
+ examples: ['agent@example.com'],
525
+ }),
526
+ code: stringParam({
527
+ description: 'Verification code from email for complete_email_verification.',
528
+ minLength: 1,
529
+ examples: ['123456'],
530
+ }),
515
531
  },
516
532
  }),
517
533
  async execute(toolCallId, params = {}) {
@@ -542,6 +558,34 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
542
558
  return buildTerminalActionResult({ tool: accountTool, action, payload });
543
559
  }
544
560
 
561
+ if (action === 'start_email_verification') {
562
+ const email = normalizeText(params.email, null);
563
+ if (!email) requireManageWorldField('email', 'email is required for action=start_email_verification');
564
+ const context = await resolveToolContext(api, plugin, params, { bindRuntime: false });
565
+ const payload = await plugin.runtime.productShell.identity.startEmailVerification({
566
+ ...context,
567
+ runtime: api?.runtime || null,
568
+ email,
569
+ displayName: params.displayName || null,
570
+ });
571
+ return buildTerminalActionResult({ tool: accountTool, action, payload });
572
+ }
573
+
574
+ if (action === 'complete_email_verification') {
575
+ const email = normalizeText(params.email, null);
576
+ const code = normalizeText(params.code, null);
577
+ if (!email) requireManageWorldField('email', 'email is required for action=complete_email_verification');
578
+ if (!code) requireManageWorldField('code', 'code is required for action=complete_email_verification');
579
+ const context = await resolveToolContext(api, plugin, params, { bindRuntime: false });
580
+ const payload = await plugin.runtime.productShell.identity.completeEmailVerification({
581
+ ...context,
582
+ runtime: api?.runtime || null,
583
+ email,
584
+ code,
585
+ });
586
+ return buildTerminalActionResult({ tool: accountTool, action, payload });
587
+ }
588
+
545
589
  const implementationAction = ACCOUNT_IMPLEMENTATION_ACTIONS[action] || null;
546
590
  if (implementationAction) {
547
591
  const implementationParams = {
@@ -563,7 +607,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
563
607
  requireManageWorldField('action', `action=${action} requires the account management runtime adapter`);
564
608
  }
565
609
  const context = await resolveToolContext(api, plugin, params, {
566
- requiredPublicIdentityCapability: action === 'activate_account' ? null : 'manage account',
610
+ requiredPublicIdentityCapability: action === 'view_account' ? null : 'manage account',
567
611
  });
568
612
  const generateShareCard = typeof params.generateShareCard === 'boolean'
569
613
  ? params.generateShareCard
@@ -580,6 +624,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
580
624
  chatRequestApprovalPolicy: params.chatRequestApprovalPolicy || null,
581
625
  proactivitySettings: params.proactivitySettings,
582
626
  generateShareCard,
627
+ shareCardVariant: params.shareCardVariant ?? null,
583
628
  expiresInSeconds: params.expiresInSeconds ?? null,
584
629
  });
585
630
  return buildTerminalActionResult({ tool: accountTool, action, payload });
@@ -740,14 +785,6 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
740
785
  minLength: 1,
741
786
  examples: ['Runtime Peer'],
742
787
  }),
743
- generateShareCard: booleanParam({
744
- description: 'When true, include the current account share-card when available.',
745
- }),
746
- expiresInSeconds: integerParam({
747
- description: 'Optional temporary share-card TTL in seconds.',
748
- minimum: 1,
749
- examples: [7200],
750
- }),
751
788
  },
752
789
  }),
753
790
  async execute(toolCallId, params = {}) {
@@ -1112,6 +1149,11 @@ function buildRegisteredTools(api, plugin) {
1112
1149
  minLength: 1,
1113
1150
  examples: ['claworld'],
1114
1151
  });
1152
+ const shareCardVariantProperty = stringParam({
1153
+ description: 'Optional share-card version. Choose from the user\'s usual communication language or sharing context: zh for Chinese, en for languages outside Chinese.',
1154
+ enumValues: ['en', 'zh'],
1155
+ examples: ['en', 'zh'],
1156
+ });
1115
1157
  const chatRequestApprovalPolicyProperty = objectParam({
1116
1158
  description: 'Backend-managed inbound chat-request policy for this account.',
1117
1159
  required: ['mode'],
@@ -1963,6 +2005,7 @@ function buildRegisteredTools(api, plugin) {
1963
2005
  generateShareCard: booleanParam({
1964
2006
  description: 'When true, return a temporary public identity card URL. Defaults to false for view and true for update_identity.',
1965
2007
  }),
2008
+ shareCardVariant: shareCardVariantProperty,
1966
2009
  expiresInSeconds: integerParam({
1967
2010
  description: 'Optional temporary share-card TTL in seconds.',
1968
2011
  minimum: 1,
@@ -1979,6 +2022,7 @@ function buildRegisteredTools(api, plugin) {
1979
2022
  action: 'update_identity',
1980
2023
  displayName: '小发发',
1981
2024
  generateShareCard: true,
2025
+ shareCardVariant: 'zh',
1982
2026
  },
1983
2027
  {
1984
2028
  accountId: 'claworld',
@@ -2010,13 +2054,14 @@ function buildRegisteredTools(api, plugin) {
2010
2054
  ...context,
2011
2055
  displayName,
2012
2056
  generateShareCard,
2057
+ shareCardVariant: params.shareCardVariant ?? null,
2013
2058
  expiresInSeconds: params.expiresInSeconds ?? null,
2014
2059
  });
2015
2060
  return buildToolResult(projectToolAccountMutationResponse({
2016
2061
  action,
2017
2062
  accountId: context.accountId,
2018
2063
  identityPayload: payload,
2019
- runtimeActivation: payload?.runtimeActivation || null,
2064
+ runtimeIdentity: payload?.runtimeIdentity || null,
2020
2065
  }));
2021
2066
  }
2022
2067
 
@@ -2067,6 +2112,7 @@ function buildRegisteredTools(api, plugin) {
2067
2112
  runtimeConfig,
2068
2113
  agentId: context.agentId || runtimeConfig.relay?.agentId || null,
2069
2114
  generateShareCard,
2115
+ shareCardVariant: params.shareCardVariant ?? null,
2070
2116
  expiresInSeconds: params.expiresInSeconds ?? null,
2071
2117
  });
2072
2118
  const pairedAgentId = identityPayload?.agentId || runtimeConfig.relay?.agentId || null;
@@ -2100,11 +2146,19 @@ function buildRegisteredTools(api, plugin) {
2100
2146
  || runtimeConfig.relay?.appToken
2101
2147
  || runtimeConfig.relay?.credentialToken,
2102
2148
  );
2103
- const activationReady = hasConfiguredAppToken;
2104
- const bindingReady = activationReady && Boolean(pairedAgentId);
2105
- const bindingStatus = activationReady
2149
+ const identityStatus = pairedAgentId && typeof plugin.runtime?.productShell?.identity?.getIdentityStatus === 'function'
2150
+ ? await plugin.runtime.productShell.identity.getIdentityStatus({
2151
+ cfg,
2152
+ accountId,
2153
+ runtimeConfig: pairedRuntimeConfig,
2154
+ agentId: pairedAgentId,
2155
+ })
2156
+ : null;
2157
+ const emailVerified = identityStatus?.emailVerified === true;
2158
+ const bindingReady = hasConfiguredAppToken && Boolean(pairedAgentId);
2159
+ const bindingStatus = hasConfiguredAppToken
2106
2160
  ? (bindingReady ? 'bound' : 'identity_unresolved')
2107
- : 'unactivated';
2161
+ : 'unbound';
2108
2162
  let relayAgent = relayAgentFallback;
2109
2163
  if (hasConfiguredAppToken && pairedAgentId && typeof plugin.helpers?.pairing?.resolveAgentIdentity === 'function') {
2110
2164
  const resolvedRelayAgent = await plugin.helpers.pairing.resolveAgentIdentity({
@@ -2123,13 +2177,16 @@ function buildRegisteredTools(api, plugin) {
2123
2177
  }
2124
2178
  }
2125
2179
  const pairingPayload = {
2126
- status: activationReady ? 'paired' : 'unpaired',
2180
+ status: hasConfiguredAppToken ? 'paired' : 'unpaired',
2127
2181
  bindingReady,
2128
2182
  bindingStatus,
2129
- reason: activationReady
2183
+ emailVerified,
2184
+ email: identityStatus?.email || null,
2185
+ verifiedAt: identityStatus?.verifiedAt || null,
2186
+ reason: hasConfiguredAppToken
2130
2187
  ? (pairedAgentId ? null : 'missing_agent_id')
2131
2188
  : 'missing_app_token',
2132
- bindingSource: activationReady
2189
+ bindingSource: hasConfiguredAppToken
2133
2190
  ? 'configured_app_token'
2134
2191
  : (runtimeConfig.registration?.enabled === true ? 'registration_pending' : 'unbound'),
2135
2192
  runtimeConfig: pairedRuntimeConfig,
@@ -32,7 +32,8 @@ export const PUBLIC_TOOL_ACTION_CATALOG = Object.freeze({
32
32
  ]),
33
33
  claworld_manage_account: Object.freeze([
34
34
  'view_account',
35
- 'activate_account',
35
+ 'start_email_verification',
36
+ 'complete_email_verification',
36
37
  'update_display_name',
37
38
  'update_human_profile',
38
39
  'update_agent_profile',