@xfxstudio/claworld 2026.7.2-testing.1 → 2026.7.8-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.
@@ -25,10 +25,6 @@ import {
25
25
  } from '../runtime/working-memory.js';
26
26
  import { resolveOpenClawWorkspaceRoot } from '../runtime/workspace-resolver.js';
27
27
  import { setClaworldRuntime } from './runtime.js';
28
- import {
29
- CHAT_REQUEST_APPROVAL_POLICY_MODES,
30
- CHAT_REQUEST_APPROVAL_POLICY_ORIGIN_TYPES,
31
- } from '../../product-shell/contracts/chat-request-approval-policy.js';
32
28
  import { PUBLIC_TOOL_ACTION_CATALOG } from '../../product-shell/contracts/search-item.js';
33
29
  import {
34
30
  ACCOUNT_ACTIONS,
@@ -54,6 +50,8 @@ import {
54
50
  projectToolManageWorldActionResponse,
55
51
  requireManageWorldField,
56
52
  resolveToolContext,
53
+ resolveToolAgentId,
54
+ resolveToolDisplayName,
57
55
  stringParam,
58
56
  withToolErrorBoundary,
59
57
  } from './register-tooling.js';
@@ -236,13 +234,50 @@ function normalizeTerminalAccountAction(params = {}) {
236
234
  Object.prototype.hasOwnProperty.call(params, 'agentProfile')
237
235
  || Object.prototype.hasOwnProperty.call(params, 'profile')
238
236
  ) return 'update_agent_profile';
239
- if (Object.prototype.hasOwnProperty.call(params, 'discoverable')) return 'set_discoverability';
240
- if (Object.prototype.hasOwnProperty.call(params, 'contactable')) return 'set_contactability';
241
- if (normalizeObject(params.chatRequestApprovalPolicy, null)) return 'set_chat_policy';
237
+ if (Object.prototype.hasOwnProperty.call(params, 'visibilityMode')) return 'set_visibility_mode';
238
+ if (Object.prototype.hasOwnProperty.call(params, 'contactPolicy')) return 'set_contact_policy';
239
+ if (Object.prototype.hasOwnProperty.call(params, 'chatRequestPolicy')) {
240
+ requireManageWorldField('chatRequestPolicy', 'chatRequestPolicy is not supported by claworld_manage_account; use contactPolicy');
241
+ }
242
242
  if (Object.prototype.hasOwnProperty.call(params, 'proactivitySettings')) return 'set_proactivity';
243
243
  return 'view_account';
244
244
  }
245
245
 
246
+ function hasProvidedTerminalAccountPolicyField(params = {}, fieldId) {
247
+ if (!Object.prototype.hasOwnProperty.call(params, fieldId)) return false;
248
+ const value = params[fieldId];
249
+ if (value == null) return false;
250
+ if (typeof value === 'string') return normalizeText(value, null) != null;
251
+ return true;
252
+ }
253
+
254
+ function validateTerminalAccountPolicyPayload(action, params = {}) {
255
+ if (action === 'set_visibility_mode') {
256
+ if (!normalizeText(params.visibilityMode, null)) {
257
+ requireManageWorldField('visibilityMode', 'visibilityMode is required for action=set_visibility_mode');
258
+ }
259
+ if (hasProvidedTerminalAccountPolicyField(params, 'contactPolicy')) {
260
+ requireManageWorldField('contactPolicy', 'contactPolicy is not supported for action=set_visibility_mode');
261
+ }
262
+ if (hasProvidedTerminalAccountPolicyField(params, 'chatRequestPolicy')) {
263
+ requireManageWorldField('chatRequestPolicy', 'chatRequestPolicy is not supported by claworld_manage_account; use contactPolicy');
264
+ }
265
+ return;
266
+ }
267
+ if (action === 'set_contact_policy') {
268
+ if (!normalizeText(params.contactPolicy, null)) {
269
+ requireManageWorldField('contactPolicy', 'contactPolicy is required for action=set_contact_policy');
270
+ }
271
+ if (hasProvidedTerminalAccountPolicyField(params, 'visibilityMode')) {
272
+ requireManageWorldField('visibilityMode', 'visibilityMode is not supported for action=set_contact_policy');
273
+ }
274
+ if (hasProvidedTerminalAccountPolicyField(params, 'chatRequestPolicy')) {
275
+ requireManageWorldField('chatRequestPolicy', 'chatRequestPolicy is not supported by claworld_manage_account; use contactPolicy');
276
+ }
277
+ return;
278
+ }
279
+ }
280
+
246
281
  function normalizeTerminalWorldAction(params = {}) {
247
282
  if (hasExplicitAction(params)) {
248
283
  const explicitAction = normalizeText(params.action, null);
@@ -470,12 +505,12 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
470
505
  {
471
506
  name: accountTool,
472
507
  label: 'Claworld Manage Account',
473
- description: 'Terminal account surface for readiness, public identity, global profile, share-card generation, account-level chat policy, and email-based identity verification.',
508
+ description: 'Terminal account surface for readiness, public identity, global profile, share-card generation, visibility, inbound contact policy, and email-based identity verification.',
474
509
  metadata: buildToolMetadata({
475
510
  category: 'account',
476
511
  usageNotes: [
477
512
  'Use this human-facing account surface for identity verification, profile, policy, and subscription decisions.',
478
- 'Use action=view_account for readiness; update_display_name, update_agent_profile, or set_chat_policy for common account mutations.',
513
+ 'Use action=view_account for readiness; update_display_name, update_agent_profile, or set_contact_policy for common account mutations.',
479
514
  'Use start_email_verification with email + optional displayName to start email-based identity verification, then complete_email_verification with email + code to finish.',
480
515
  'Use subscribe_person or unsubscribe_person when a search/profile result exposes a person subscription target.',
481
516
  ],
@@ -488,7 +523,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
488
523
  action: stringParam({
489
524
  description: 'Account action.',
490
525
  enumValues: TERMINAL_ACCOUNT_ACTIONS,
491
- examples: ['view_account', 'start_email_verification', 'update_display_name', 'set_chat_policy'],
526
+ examples: ['view_account', 'start_email_verification', 'update_display_name', 'set_contact_policy'],
492
527
  }),
493
528
  displayName: stringParam({
494
529
  description: 'Public-facing display name for update_display_name or start_email_verification.',
@@ -507,11 +542,15 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
507
542
  description: 'Agent-facing profile/personality text for update_agent_profile.',
508
543
  examples: ['偏主动但会先确认边界,擅长总结和约局。'],
509
544
  }),
510
- discoverable: booleanParam({ description: 'Whether this account can appear in global people search.' }),
511
- contactable: booleanParam({ description: 'Whether this account can receive direct public-profile chat requests.' }),
512
- chatRequestApprovalPolicy: objectParam({
513
- description: 'Backend-managed inbound chat-request policy for this account.',
514
- additionalProperties: true,
545
+ visibilityMode: stringParam({
546
+ description: 'Account visibility mode: public is searchable, unlisted is explicit-identity reachable, private is not publicly reachable.',
547
+ enumValues: ['public', 'unlisted', 'private'],
548
+ examples: ['public', 'unlisted', 'private'],
549
+ }),
550
+ contactPolicy: stringParam({
551
+ description: 'Inbound contact policy: open accepts eligible requests, approval_required keeps the request path open but requires review, closed blocks new inbound contact.',
552
+ enumValues: ['open', 'approval_required', 'closed'],
553
+ examples: ['open', 'approval_required', 'closed'],
515
554
  }),
516
555
  proactivitySettings: objectParam({
517
556
  description: 'Account-level proactive-management settings.',
@@ -554,6 +593,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
554
593
  }),
555
594
  async execute(toolCallId, params = {}) {
556
595
  const action = normalizeTerminalAccountAction(params);
596
+ validateTerminalAccountPolicyPayload(action, params);
557
597
  const subscriptionTargetId = normalizeText(params.targetAgentId, normalizeText(params.targetId, null));
558
598
  if (action === 'subscribe_person') {
559
599
  if (!subscriptionTargetId) requireManageWorldField('targetAgentId', 'targetAgentId is required for action=subscribe_person');
@@ -641,14 +681,20 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
641
681
  profile: params.profile,
642
682
  humanProfile: params.humanProfile,
643
683
  agentProfile: params.agentProfile,
644
- discoverable: params.discoverable,
645
- contactable: params.contactable,
646
- chatRequestApprovalPolicy: params.chatRequestApprovalPolicy || null,
684
+ visibilityMode: params.visibilityMode,
685
+ contactPolicy: params.contactPolicy,
647
686
  proactivitySettings: params.proactivitySettings,
648
687
  generateShareCard,
649
688
  shareCardVariant: params.shareCardVariant ?? null,
650
689
  expiresInSeconds: params.expiresInSeconds ?? null,
651
690
  });
691
+ if (action === 'update_display_name') {
692
+ return buildToolResult(projectToolAccountMutationResponse({
693
+ action,
694
+ accountId: context.accountId,
695
+ identityPayload: payload,
696
+ }));
697
+ }
652
698
  return buildTerminalActionResult({ tool: accountTool, action, payload });
653
699
  },
654
700
  },
@@ -661,7 +707,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
661
707
  usageNotes: [
662
708
  'scope=worlds searches or browses visible worlds.',
663
709
  'scope=world_members searches members in an authorized world.',
664
- 'scope=people searches globally discoverable/contactable public identities.',
710
+ 'scope=people searches globally public identities; unlisted people require explicit identity/profile lookup.',
665
711
  'scope=mixed combines world, optional world-member, and global people search results in one SearchItemEnvelope list.',
666
712
  ],
667
713
  }),
@@ -851,6 +897,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
851
897
  category: 'world_management',
852
898
  usageNotes: [
853
899
  'action=join_world joins a visible world with world-scoped profile text.',
900
+ 'action=list_pending_invites lists pending world invitations received by the current account.',
854
901
  'action=create_world creates an owner-managed world.',
855
902
  'Owner governance and member self-service actions use terminal action names such as update_world, publish_broadcast, and update_world_profile.',
856
903
  'Subscription, activity, and member-list actions are backed by the product-shell terminal routes.',
@@ -970,6 +1017,18 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
970
1017
  });
971
1018
  return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
972
1019
  }
1020
+ if (action === 'list_pending_invites') {
1021
+ const context = await resolveToolContext(api, plugin, params, {
1022
+ requiredPublicIdentityCapability: 'list pending world invites',
1023
+ });
1024
+ const payload = await plugin.runtime.productShell.membership.listPendingInvites({
1025
+ ...context,
1026
+ status: params.status || 'pending',
1027
+ includeDisabled: params.includeDisabled !== false,
1028
+ limit: params.limit ?? null,
1029
+ });
1030
+ return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
1031
+ }
973
1032
  if (action === 'list_invites') {
974
1033
  const worldId = normalizeText(params.worldId, null);
975
1034
  if (!worldId) requireManageWorldField('worldId');
@@ -1200,43 +1259,6 @@ function buildRegisteredTools(api, plugin) {
1200
1259
  enumValues: ['en', 'zh'],
1201
1260
  examples: ['en', 'zh'],
1202
1261
  });
1203
- const chatRequestApprovalPolicyProperty = objectParam({
1204
- description: 'Backend-managed inbound chat-request policy for this account.',
1205
- required: ['mode'],
1206
- properties: {
1207
- mode: stringParam({
1208
- description: 'Policy mode controlling which new inbound chat requests auto-accept.',
1209
- enumValues: CHAT_REQUEST_APPROVAL_POLICY_MODES,
1210
- examples: ['open', 'manual_review'],
1211
- }),
1212
- blocks: objectParam({
1213
- description: 'Optional deny rules applied before the allow mode is evaluated.',
1214
- properties: {
1215
- originTypes: arrayParam({
1216
- description: 'Canonical request origin types that should always be rejected.',
1217
- items: stringParam({
1218
- enumValues: CHAT_REQUEST_APPROVAL_POLICY_ORIGIN_TYPES,
1219
- }),
1220
- examples: [['world_broadcast']],
1221
- }),
1222
- worldIds: arrayParam({
1223
- description: 'World ids that should always be rejected.',
1224
- items: stringParam({}),
1225
- examples: [['dating-demo-world']],
1226
- }),
1227
- },
1228
- }),
1229
- },
1230
- examples: [
1231
- {
1232
- mode: 'trusted_or_world',
1233
- blocks: {
1234
- originTypes: ['world_broadcast'],
1235
- worldIds: [],
1236
- },
1237
- },
1238
- ],
1239
- });
1240
1262
  const worldIdProperty = stringParam({
1241
1263
  description: 'Canonical world id returned by claworld_search(scope=worlds) or claworld_manage_worlds(action=get_world).',
1242
1264
  minLength: 1,
@@ -1925,20 +1947,6 @@ function buildRegisteredTools(api, plugin) {
1925
1947
  },
1926
1948
  outcome: 'Stores the current account profile text. Pass an empty string to clear it.',
1927
1949
  },
1928
- {
1929
- title: 'Update the inbound chat policy',
1930
- input: {
1931
- accountId: 'claworld',
1932
- action: 'update_chat_policy',
1933
- chatRequestApprovalPolicy: {
1934
- mode: 'trusted_or_world',
1935
- blocks: {
1936
- originTypes: ['world_broadcast'],
1937
- },
1938
- },
1939
- },
1940
- outcome: 'Stores the account-level chat-request policy in the backend and returns the updated policy snapshot.',
1941
- },
1942
1950
  ],
1943
1951
  }),
1944
1952
  parameters: objectParam({
@@ -1947,9 +1955,9 @@ function buildRegisteredTools(api, plugin) {
1947
1955
  properties: {
1948
1956
  accountId: accountIdProperty,
1949
1957
  action: stringParam({
1950
- description: 'Account action. Defaults to view; inferred from displayName, profile, or chatRequestApprovalPolicy when omitted.',
1958
+ description: 'Account action. Defaults to view; inferred from displayName or profile when omitted.',
1951
1959
  enumValues: ACCOUNT_ACTIONS,
1952
- examples: ['view', 'update_identity', 'update_profile', 'update_chat_policy'],
1960
+ examples: ['view', 'update_identity', 'update_profile'],
1953
1961
  }),
1954
1962
  displayName: stringParam({
1955
1963
  description: 'Public-facing display name. Required for action=update_identity. # is reserved and must not appear here.',
@@ -1960,7 +1968,6 @@ function buildRegisteredTools(api, plugin) {
1960
1968
  description: 'Global plain-text profile for this account. Maximum 500 characters. Use an empty string to clear it. HTML is not supported.',
1961
1969
  examples: ['喜欢慢节奏介绍和小范围世界,也愿意先让 agent 帮我做初步认识。🙂'],
1962
1970
  }),
1963
- chatRequestApprovalPolicy: chatRequestApprovalPolicyProperty,
1964
1971
  generateShareCard: booleanParam({
1965
1972
  description: 'When true, return a temporary public identity card URL. Defaults to false for view and true for update_identity.',
1966
1973
  }),
@@ -1988,13 +1995,6 @@ function buildRegisteredTools(api, plugin) {
1988
1995
  action: 'update_profile',
1989
1996
  profile: '喜欢慢节奏介绍和小范围世界,也愿意先让 agent 帮我做初步认识。🙂',
1990
1997
  },
1991
- {
1992
- accountId: 'claworld',
1993
- action: 'update_chat_policy',
1994
- chatRequestApprovalPolicy: {
1995
- mode: 'manual_review',
1996
- },
1997
- },
1998
1998
  ],
1999
1999
  }),
2000
2000
  async execute(_toolCallId, params = {}) {
@@ -2040,26 +2040,6 @@ function buildRegisteredTools(api, plugin) {
2040
2040
  }));
2041
2041
  }
2042
2042
 
2043
- if (action === 'update_chat_policy') {
2044
- const context = await resolveToolContext(api, plugin, params);
2045
- const chatRequestApprovalPolicy = normalizeObject(params.chatRequestApprovalPolicy, null);
2046
- if (!chatRequestApprovalPolicy) {
2047
- requireManageWorldField(
2048
- 'chatRequestApprovalPolicy',
2049
- 'chatRequestApprovalPolicy is required for action=update_chat_policy',
2050
- );
2051
- }
2052
- const payload = await plugin.runtime.productShell.profile.updateChatRequestApprovalPolicy({
2053
- ...context,
2054
- chatRequestApprovalPolicy,
2055
- });
2056
- return buildToolResult(projectToolAccountMutationResponse({
2057
- action,
2058
- accountId: context.accountId,
2059
- identityPayload: payload,
2060
- }));
2061
- }
2062
-
2063
2043
  const context = await resolveToolContext(api, plugin, params);
2064
2044
  const cfg = context.cfg || await loadCurrentConfig(api);
2065
2045
  const accountId = context.accountId;
@@ -2074,7 +2054,7 @@ function buildRegisteredTools(api, plugin) {
2074
2054
  shareCardVariant: params.shareCardVariant ?? null,
2075
2055
  expiresInSeconds: params.expiresInSeconds ?? null,
2076
2056
  });
2077
- const pairedAgentId = identityPayload?.agentId || runtimeConfig.relay?.agentId || null;
2057
+ const pairedAgentId = resolveToolAgentId(identityPayload, runtimeConfig.relay?.agentId || null);
2078
2058
  const pairedRuntimeConfig = pairedAgentId
2079
2059
  ? {
2080
2060
  ...runtimeConfig,
@@ -2087,15 +2067,15 @@ function buildRegisteredTools(api, plugin) {
2087
2067
  const relayAgentFallback = pairedAgentId
2088
2068
  ? {
2089
2069
  agentId: pairedAgentId,
2090
- displayName: normalizeText(
2091
- identityPayload?.publicIdentity?.displayName,
2070
+ displayName: resolveToolDisplayName(
2071
+ identityPayload,
2092
2072
  normalizeText(
2093
- identityPayload?.recommendedDisplayName,
2094
- normalizeText(runtimeConfig?.name, normalizeText(runtimeConfig?.registration?.displayName, null)),
2073
+ runtimeConfig?.name,
2074
+ normalizeText(runtimeConfig?.registration?.displayName, null),
2095
2075
  ),
2096
2076
  ),
2097
- discoverable: null,
2098
- contactable: null,
2077
+ visibilityMode: null,
2078
+ contactPolicy: null,
2099
2079
  online: null,
2100
2080
  resolved: null,
2101
2081
  }
@@ -2113,7 +2093,13 @@ function buildRegisteredTools(api, plugin) {
2113
2093
  agentId: pairedAgentId,
2114
2094
  })
2115
2095
  : null;
2116
- const emailVerified = identityStatus?.emailVerified === true;
2096
+ const accountPayload = normalizeObject(identityStatus?.accountView, null)
2097
+ || (normalizeObject(identityStatus?.relay, null) ? identityStatus : null);
2098
+ const accountViewAccount = normalizeObject(accountPayload?.account, null);
2099
+ const accountViewDiagnostics = normalizeObject(accountPayload?.diagnostics, null);
2100
+ const emailVerified = identityStatus?.emailVerified === true
2101
+ || accountViewAccount?.emailVerified === true
2102
+ || accountViewDiagnostics?.emailVerified === true;
2117
2103
  const bindingReady = hasConfiguredAppToken && Boolean(pairedAgentId);
2118
2104
  const bindingStatus = hasConfiguredAppToken
2119
2105
  ? (bindingReady ? 'bound' : 'identity_unresolved')
@@ -2140,8 +2126,8 @@ function buildRegisteredTools(api, plugin) {
2140
2126
  bindingReady,
2141
2127
  bindingStatus,
2142
2128
  emailVerified,
2143
- email: identityStatus?.email || null,
2144
- verifiedAt: identityStatus?.verifiedAt || null,
2129
+ email: identityStatus?.email || accountViewAccount?.email || null,
2130
+ verifiedAt: identityStatus?.verifiedAt || accountViewAccount?.verifiedAt || null,
2145
2131
  reason: hasConfiguredAppToken
2146
2132
  ? (pairedAgentId ? null : 'missing_agent_id')
2147
2133
  : 'missing_app_token',
@@ -2155,6 +2141,7 @@ function buildRegisteredTools(api, plugin) {
2155
2141
  accountId,
2156
2142
  pairingPayload,
2157
2143
  identityPayload,
2144
+ accountPayload,
2158
2145
  }));
2159
2146
  },
2160
2147
  },
@@ -2,7 +2,10 @@ import { EventEmitter } from 'events';
2
2
  import WebSocket from 'ws';
3
3
  import { resolveClaworldRuntimeConfig } from './config-schema.js';
4
4
  import { buildRuntimeAuthHeaders } from './account-identity.js';
5
- import { buildClaworldRelayClientVersion } from '../plugin-version.js';
5
+ import {
6
+ CLAWORLD_OPENCLAW_PLUGIN_CLIENT,
7
+ CLAWORLD_PLUGIN_CURRENT_VERSION,
8
+ } from '../plugin-version.js';
6
9
  import { createRelayEventProtocol } from '../protocol/relay-event-protocol.js';
7
10
  import { createInboundSessionRouter } from '../runtime/inbound-session-router.js';
8
11
  import { createOutboundSessionBridge } from '../runtime/outbound-session-bridge.js';
@@ -372,6 +375,7 @@ export class ClaworldRelayClient extends EventEmitter {
372
375
  type: 'auth',
373
376
  agentId,
374
377
  credential,
378
+ client: CLAWORLD_OPENCLAW_PLUGIN_CLIENT,
375
379
  clientVersion,
376
380
  bridgeProtocol: this.protocol.version,
377
381
  });
@@ -552,7 +556,7 @@ export class ClaworldRelayClient extends EventEmitter {
552
556
  config,
553
557
  agentId,
554
558
  credential = null,
555
- clientVersion = buildClaworldRelayClientVersion(),
559
+ clientVersion = CLAWORLD_PLUGIN_CURRENT_VERSION,
556
560
  sessionTarget,
557
561
  fallbackTarget,
558
562
  } = {}) {
@@ -1,7 +1,10 @@
1
1
  import repoPackageJson from '../../package.json' with { type: 'json' };
2
2
 
3
3
  export const CLAWORLD_PLUGIN_PACKAGE_NAME = '@xfxstudio/claworld';
4
- export const CLAWORLD_PLUGIN_VERSION_HEADER = 'x-claworld-plugin-version';
4
+ export const CLAWORLD_CLIENT_HEADER = 'x-claworld-client';
5
+ export const CLAWORLD_CLIENT_VERSION_HEADER = 'x-claworld-client-version';
6
+ export const CLAWORLD_CLIENT_CHANNEL_HEADER = 'x-claworld-client-channel';
7
+ export const CLAWORLD_OPENCLAW_PLUGIN_CLIENT = 'openclaw-plugin';
5
8
 
6
9
  function normalizeText(value, fallback = null) {
7
10
  if (value == null) return fallback;
@@ -9,15 +12,6 @@ function normalizeText(value, fallback = null) {
9
12
  return normalized || fallback;
10
13
  }
11
14
 
12
- function normalizeHeaderValue(value) {
13
- if (Array.isArray(value)) {
14
- return normalizeHeaderValue(value[0]);
15
- }
16
- const normalized = normalizeText(value, null);
17
- if (!normalized) return null;
18
- return normalized.split(',')[0]?.trim() || null;
19
- }
20
-
21
15
  export function normalizeClaworldPluginVersion(value, fallback = null) {
22
16
  const normalized = normalizeText(value, null);
23
17
  if (!normalized) return fallback;
@@ -39,16 +33,9 @@ function resolveCurrentPluginVersion() {
39
33
 
40
34
  export const CLAWORLD_PLUGIN_CURRENT_VERSION = resolveCurrentPluginVersion();
41
35
 
42
- export function readClaworldPluginVersionFromHeaders(headers = {}) {
43
- const rawVersion = normalizeHeaderValue(headers?.[CLAWORLD_PLUGIN_VERSION_HEADER]);
44
- return {
45
- rawVersion,
46
- reportedVersion: normalizeClaworldPluginVersion(rawVersion, rawVersion),
47
- normalizedVersion: normalizeClaworldPluginVersion(rawVersion, null),
48
- source: rawVersion ? CLAWORLD_PLUGIN_VERSION_HEADER : null,
49
- };
50
- }
51
-
52
- export function buildClaworldRelayClientVersion(version = CLAWORLD_PLUGIN_CURRENT_VERSION) {
53
- return `claworld-plugin/${normalizeClaworldPluginVersion(version, CLAWORLD_PLUGIN_CURRENT_VERSION)}`;
36
+ export function inferClaworldClientChannel(version = CLAWORLD_PLUGIN_CURRENT_VERSION, fallback = null) {
37
+ const normalized = normalizeClaworldPluginVersion(version, null);
38
+ if (!normalized) return fallback;
39
+ if (/-testing(?:\.|$)/.test(normalized)) return 'testing';
40
+ return 'stable';
54
41
  }
@@ -583,8 +583,8 @@ function projectToolAgentSummary(agent = {}) {
583
583
  displayName: normalizeText(agent.displayName, null),
584
584
  identity: normalizeText(agent.publicIdentity?.displayIdentity, null),
585
585
  online: agent.online === true,
586
- discoverable: typeof agent.discoverable === 'boolean' ? agent.discoverable : null,
587
- contactable: typeof agent.contactable === 'boolean' ? agent.contactable : null,
586
+ visibilityMode: normalizeText(agent.visibilityMode, null),
587
+ contactPolicy: normalizeText(agent.contactPolicy, null),
588
588
  };
589
589
  }
590
590
 
@@ -15,6 +15,23 @@ function normalizeOptionalBoolean(value, fallback = null) {
15
15
  return fallback;
16
16
  }
17
17
 
18
+ function normalizeOptionalInteger(value, fallback = null) {
19
+ const parsed = Number(value);
20
+ if (!Number.isFinite(parsed)) return fallback;
21
+ return Math.trunc(parsed);
22
+ }
23
+
24
+ function normalizePositiveInteger(value, fallback = null) {
25
+ const parsed = Number(value);
26
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
27
+ return Math.max(1, Math.trunc(parsed));
28
+ }
29
+
30
+ function normalizeStringList(values = []) {
31
+ if (!Array.isArray(values)) return [];
32
+ return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
33
+ }
34
+
18
35
  function normalizeWorldRole(worldRole, fallback = null) {
19
36
  const normalized = normalizeText(worldRole, fallback);
20
37
  return ['owner', 'member'].includes(normalized) ? normalized : fallback;
@@ -38,6 +55,97 @@ function normalizeManagedWorldMembership(payload = {}) {
38
55
  };
39
56
  }
40
57
 
58
+ function normalizePendingInviteAction(action = null) {
59
+ if (!action || typeof action !== 'object' || Array.isArray(action)) return null;
60
+ return {
61
+ action: normalizeText(action.action, null),
62
+ tool: normalizeText(action.tool, null),
63
+ worldId: normalizeText(action.worldId, null),
64
+ requiredFields: normalizeStringList(action.requiredFields),
65
+ };
66
+ }
67
+
68
+ function normalizeParticipantContextField(field = null) {
69
+ if (!field || typeof field !== 'object' || Array.isArray(field)) return null;
70
+ return {
71
+ fieldId: normalizeText(field.fieldId, null),
72
+ label: normalizeText(field.label, null),
73
+ description: normalizeText(field.description, null),
74
+ };
75
+ }
76
+
77
+ function normalizeJoinPlan(plan = null) {
78
+ if (!plan || typeof plan !== 'object' || Array.isArray(plan)) return null;
79
+ return {
80
+ worldId: normalizeText(plan.worldId, null),
81
+ participantContextField: normalizeParticipantContextField(plan.participantContextField),
82
+ nextAction: normalizeText(plan.nextAction, null),
83
+ };
84
+ }
85
+
86
+ function normalizeInviterProfile(inviter = null) {
87
+ if (!inviter || typeof inviter !== 'object' || Array.isArray(inviter)) return null;
88
+ return {
89
+ agentId: normalizeText(inviter.agentId, null),
90
+ displayName: normalizeText(inviter.displayName, null),
91
+ publicIdentity: normalizeText(inviter.publicIdentity, null),
92
+ profile: normalizeText(inviter.profile, null),
93
+ };
94
+ }
95
+
96
+ function normalizeInviteLifecycle(lifecycle = null) {
97
+ if (!lifecycle || typeof lifecycle !== 'object' || Array.isArray(lifecycle)) return null;
98
+ return {
99
+ status: normalizeText(lifecycle.status, null),
100
+ expiresAt: normalizeText(lifecycle.expiresAt, null),
101
+ expirationPolicy: normalizeText(lifecycle.expirationPolicy, null),
102
+ acceptedAt: normalizeText(lifecycle.acceptedAt, null),
103
+ inviteRevokedAt: normalizeText(lifecycle.inviteRevokedAt, null),
104
+ };
105
+ }
106
+
107
+ function normalizePendingWorldInvite(payload = {}) {
108
+ return {
109
+ invitationId: normalizeText(payload.invitationId, null),
110
+ membershipId: normalizeText(payload.membershipId, null),
111
+ agentId: normalizeText(payload.agentId, null),
112
+ worldId: normalizeText(payload.worldId, null),
113
+ displayName: normalizeText(payload.displayName, null),
114
+ worldContextText: normalizeText(payload.worldContextText, null),
115
+ participantContextField: normalizeParticipantContextField(payload.participantContextField),
116
+ joinPlan: normalizeJoinPlan(payload.joinPlan),
117
+ membershipStatus: normalizeText(payload.membershipStatus, null),
118
+ status: normalizeText(payload.status, null),
119
+ invitedByAgentId: normalizeText(payload.invitedByAgentId, null),
120
+ invitedByDisplayName: normalizeText(payload.invitedByDisplayName, null),
121
+ invitedByPublicIdentity: normalizeText(payload.invitedByPublicIdentity, null),
122
+ inviter: normalizeInviterProfile(payload.inviter),
123
+ invitedAt: normalizeText(payload.invitedAt, null),
124
+ inviteMessage: normalizeText(payload.inviteMessage, null),
125
+ expiresAt: normalizeText(payload.expiresAt, null),
126
+ expirationPolicy: normalizeText(payload.expirationPolicy, null),
127
+ lifecycle: normalizeInviteLifecycle(payload.lifecycle),
128
+ membershipUpdatedAt: normalizeText(payload.membershipUpdatedAt, null),
129
+ worldUpdatedAt: normalizeText(payload.worldUpdatedAt, null),
130
+ nextAction: normalizeText(payload.nextAction, null),
131
+ nextActions: Array.isArray(payload.nextActions)
132
+ ? payload.nextActions.map((action) => normalizePendingInviteAction(action)).filter(Boolean)
133
+ : [],
134
+ };
135
+ }
136
+
137
+ function normalizePendingWorldInviteList(payload = {}) {
138
+ return {
139
+ agentId: normalizeText(payload.agentId, null),
140
+ status: normalizeText(payload.status, null),
141
+ items: Array.isArray(payload.items)
142
+ ? payload.items.map((item) => normalizePendingWorldInvite(item))
143
+ : [],
144
+ totalItems: normalizeOptionalInteger(payload.totalItems, 0),
145
+ nextAction: normalizeText(payload.nextAction, null),
146
+ };
147
+ }
148
+
41
149
  function normalizeMembershipList(payload = {}) {
42
150
  return {
43
151
  items: Array.isArray(payload.items)
@@ -116,6 +224,55 @@ export async function fetchWorldMemberships({
116
224
  return normalizeMembershipList(result.body);
117
225
  }
118
226
 
227
+ export async function fetchPendingWorldInvites({
228
+ cfg = {},
229
+ accountId = null,
230
+ runtimeConfig = null,
231
+ agentId = null,
232
+ status = 'pending',
233
+ includeDisabled = true,
234
+ limit = null,
235
+ fetchImpl,
236
+ logger = console,
237
+ } = {}) {
238
+ if (typeof fetchImpl !== 'function') {
239
+ throw new Error('fetch is unavailable for claworld world membership helper');
240
+ }
241
+
242
+ const resolvedAgentId = normalizeText(agentId, null);
243
+ if (!resolvedAgentId) {
244
+ throw new Error('claworld world membership helper requires agentId');
245
+ }
246
+
247
+ const resolvedRuntimeConfig = runtimeConfig || resolveClaworldRuntimeConfig(cfg, accountId);
248
+ const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
249
+ const requestUrl = new URL(`${baseUrl}/v1/world-invitations`);
250
+ requestUrl.searchParams.set('agentId', resolvedAgentId);
251
+ requestUrl.searchParams.set('status', normalizeText(status, 'pending'));
252
+ requestUrl.searchParams.set('includeDisabled', includeDisabled ? 'true' : 'false');
253
+ const normalizedLimit = normalizePositiveInteger(limit, null);
254
+ if (normalizedLimit) requestUrl.searchParams.set('limit', String(normalizedLimit));
255
+ const result = await fetchJson(fetchImpl, requestUrl.toString(), {
256
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
257
+ accept: 'application/json',
258
+ ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
259
+ }),
260
+ });
261
+
262
+ if (!result.ok) {
263
+ logger.error?.('[claworld:membership] pending world invites fetch failed', {
264
+ status: result.status,
265
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
266
+ body: result.body,
267
+ });
268
+ throw createWorldMembershipHttpError('list_pending_invites', result, {
269
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
270
+ });
271
+ }
272
+
273
+ return normalizePendingWorldInviteList(result.body);
274
+ }
275
+
119
276
  export async function fetchWorldMembership({
120
277
  cfg = {},
121
278
  accountId = null,
@@ -5,6 +5,7 @@ const SUPPORTED_MODES = Object.freeze([
5
5
  'trusted_only',
6
6
  'trusted_or_world',
7
7
  'open',
8
+ 'reject_all',
8
9
  ]);
9
10
  const SUPPORTED_ORIGIN_TYPES = Object.freeze([
10
11
  'chat_request',
@@ -59,6 +60,11 @@ export function normalizeChatRequestApprovalMode(value, fallback = DEFAULT_MODE)
59
60
  case 'auto_accept':
60
61
  case 'all':
61
62
  return 'open';
63
+ case 'reject':
64
+ case 'reject_all':
65
+ case 'closed':
66
+ case 'do_not_disturb':
67
+ return 'reject_all';
62
68
  default:
63
69
  return SUPPORTED_MODES.includes(fallback) ? fallback : DEFAULT_MODE;
64
70
  }