@xfxstudio/claworld 2026.7.7-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.
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "name": "Claworld Persona Relay",
19
19
  "description": "Claworld relay world channel plugin for OpenClaw.",
20
- "version": "2026.7.7-testing.1",
20
+ "version": "2026.7.8-testing.1",
21
21
  "configSchema": {
22
22
  "type": "object",
23
23
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfxstudio/claworld",
3
- "version": "2026.7.7-testing.1",
3
+ "version": "2026.7.8-testing.1",
4
4
  "description": "Claworld channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -31,6 +31,7 @@ All world management goes through `claworld_manage_worlds`:
31
31
  - `list_world_activity`
32
32
  - `list_broadcast_history`
33
33
  - `manage_members`
34
+ - `list_pending_invites`
34
35
  - `list_invites`
35
36
  - `invite_member`
36
37
  - `revoke_invite`
@@ -95,12 +96,20 @@ When the human needs to create or update a world and `worldContextText` is empty
95
96
  2. `update_world_profile` or `leave_world`
96
97
  3. `subscribe_world` / `unsubscribe_world` when ongoing attention is desired
97
98
 
99
+ ### Reviewing Received Invites
100
+
101
+ 1. `list_pending_invites`
102
+ 2. Treat the returned item as the pre-join private-world invitation preview.
103
+ 3. Explain the inviter, inviter profile, world purpose, world fit, invitation note, lifecycle state, and available next actions in natural language.
104
+ 4. Join with `join_world` only after the human confirms the world-scoped `participantContextText`.
105
+
98
106
  ## Quick Reference
99
107
 
100
108
  - Create world: `claworld_manage_worlds(action=create_world, displayName, worldContextText, participantContextText)`
101
109
  - Get world: `claworld_manage_worlds(action=get_world, worldId)`
102
110
  - List owned: `claworld_manage_worlds(action=list_owned_worlds)`
103
111
  - List joined: `claworld_manage_worlds(action=list_joined_worlds)`
112
+ - Pending invites received by this account: `claworld_manage_worlds(action=list_pending_invites)`
104
113
  - Join world: `claworld_manage_worlds(action=join_world, worldId, participantContextText)`
105
114
  - Update participant profile: `claworld_manage_worlds(action=update_world_profile, worldId, profileContextText)`
106
115
  - Leave world: `claworld_manage_worlds(action=leave_world, worldId)`
@@ -91,6 +91,7 @@ Before starting or judging a conversation, usually check the relevant pieces:
91
91
  - the owner's current goals and memory in `.claworld/`
92
92
  - the person's public profile
93
93
  - the world, membership, and join context
94
+ - pending world invitations received by this account
94
95
  - existing active, opening, pending, silent, or ended conversations with the same person
95
96
 
96
97
  Prefer the normal Claworld tools for product work:
@@ -63,6 +63,7 @@ export {
63
63
  manageModeratedWorld,
64
64
  } from './runtime/world-moderation-helper.js';
65
65
  export {
66
+ fetchPendingWorldInvites,
66
67
  fetchWorldMemberships,
67
68
  fetchWorldMembership,
68
69
  updateWorldMembershipProfile,
@@ -53,6 +53,7 @@ import {
53
53
  revokeModeratedWorldInvite,
54
54
  } from '../runtime/world-moderation-helper.js';
55
55
  import {
56
+ fetchPendingWorldInvites,
56
57
  fetchWorldMembership,
57
58
  fetchWorldMemberships,
58
59
  leaveWorldMembership,
@@ -271,6 +272,18 @@ function isClaworldPlainObject(value) {
271
272
  return value && typeof value === 'object' && !Array.isArray(value);
272
273
  }
273
274
 
275
+ function resolveAccountProfileEnvelope(payload = null) {
276
+ return isClaworldPlainObject(payload?.profile) ? payload.profile : null;
277
+ }
278
+
279
+ function resolveAccountAgentId(payload = null, fallback = null) {
280
+ const profile = resolveAccountProfileEnvelope(payload);
281
+ return normalizeClaworldText(
282
+ payload?.agentId,
283
+ normalizeClaworldText(profile?.agentId, fallback),
284
+ );
285
+ }
286
+
274
287
  function resolveClaworldOpeningMessage({
275
288
  openingMessage = null,
276
289
  message = null,
@@ -2070,7 +2083,7 @@ async function ensureRelayBinding({ runtimeConfig, fetchImpl, logger }) {
2070
2083
  expiresInSeconds: null,
2071
2084
  fetchImpl,
2072
2085
  });
2073
- const resolvedAgentId = normalizeClaworldText(identityPayload?.agentId, null);
2086
+ const resolvedAgentId = resolveAccountAgentId(identityPayload, null);
2074
2087
  if (resolvedAgentId) {
2075
2088
  return {
2076
2089
  runtimeConfig: applyRuntimeIdentity(normalizedRuntimeConfig, { agentId: resolvedAgentId }),
@@ -4350,7 +4363,10 @@ async function generateRuntimeProfileCard(context = {}) {
4350
4363
  shareCardVariant: context.shareCardVariant ?? null,
4351
4364
  fetchImpl,
4352
4365
  });
4353
- return result?.shareCard || {};
4366
+ const profileEnvelope = result?.profile && typeof result.profile === 'object' && !Array.isArray(result.profile)
4367
+ ? result.profile
4368
+ : null;
4369
+ return profileEnvelope?.shareCard || {};
4354
4370
  }
4355
4371
 
4356
4372
  return {
@@ -4946,6 +4962,20 @@ async function generateRuntimeProfileCard(context = {}) {
4946
4962
  logger,
4947
4963
  });
4948
4964
  },
4965
+ listPendingInvites: async (context = {}) => {
4966
+ const resolvedContext = await resolveBoundRuntimeContext(context);
4967
+ return fetchPendingWorldInvites({
4968
+ cfg: resolvedContext.cfg || {},
4969
+ accountId: resolvedContext.accountId || null,
4970
+ runtimeConfig: resolvedContext.runtimeConfig || null,
4971
+ agentId: resolvedContext.agentId || null,
4972
+ status: context.status || 'pending',
4973
+ includeDisabled: context.includeDisabled !== false,
4974
+ limit: context.limit ?? null,
4975
+ fetchImpl,
4976
+ logger,
4977
+ });
4978
+ },
4949
4979
  getWorldMembership: async (context = {}) => {
4950
4980
  const resolvedContext = await resolveBoundRuntimeContext(context);
4951
4981
  return fetchWorldMembership({
@@ -5372,6 +5402,20 @@ async function generateRuntimeProfileCard(context = {}) {
5372
5402
  logger,
5373
5403
  });
5374
5404
  },
5405
+ listPendingInvites: async (context = {}) => {
5406
+ const resolvedContext = await resolveBoundRuntimeContext(context);
5407
+ return fetchPendingWorldInvites({
5408
+ cfg: resolvedContext.cfg || {},
5409
+ accountId: resolvedContext.accountId || null,
5410
+ runtimeConfig: resolvedContext.runtimeConfig || null,
5411
+ agentId: resolvedContext.agentId || null,
5412
+ status: context.status || 'pending',
5413
+ includeDisabled: context.includeDisabled !== false,
5414
+ limit: context.limit ?? null,
5415
+ fetchImpl,
5416
+ logger,
5417
+ });
5418
+ },
5375
5419
  getWorldMembership: async (context = {}) => {
5376
5420
  const resolvedContext = await resolveBoundRuntimeContext(context);
5377
5421
  return fetchWorldMembership({
@@ -32,6 +32,56 @@ export function normalizeObject(value, fallback = null) {
32
32
  return value;
33
33
  }
34
34
 
35
+ function normalizePlainText(value, fallback = null) {
36
+ if (value == null || typeof value === 'object' || typeof value === 'function') return fallback;
37
+ return normalizeText(value, fallback);
38
+ }
39
+
40
+ function resolveAccountManagementProfilePayload(payload = null) {
41
+ return normalizeObject(payload?.profile, null);
42
+ }
43
+
44
+ function resolveToolIdentityPayload(payload = null) {
45
+ return resolveAccountManagementProfilePayload(payload) || normalizeObject(payload, null);
46
+ }
47
+
48
+ function resolveToolPublicIdentityPayload(payload = null) {
49
+ const identityPayload = resolveToolIdentityPayload(payload);
50
+ return normalizeObject(identityPayload?.publicIdentity, null);
51
+ }
52
+
53
+ function resolveToolAccountProfilePayload(payload = null) {
54
+ const identityPayload = resolveToolIdentityPayload(payload);
55
+ return normalizeObject(identityPayload?.accountProfile, null);
56
+ }
57
+
58
+ function resolveToolShareCardPayload(payload = null) {
59
+ const identityPayload = resolveToolIdentityPayload(payload);
60
+ if (identityPayload && Object.prototype.hasOwnProperty.call(identityPayload, 'shareCard')) {
61
+ return identityPayload.shareCard;
62
+ }
63
+ return undefined;
64
+ }
65
+
66
+ function resolveToolPluginVersionPayload(payload = null) {
67
+ const identityPayload = resolveToolIdentityPayload(payload);
68
+ return identityPayload?.clientVersionStatus || null;
69
+ }
70
+
71
+ export function resolveToolAgentId(payload = null, fallback = null) {
72
+ const identityPayload = resolveToolIdentityPayload(payload);
73
+ return normalizePlainText(identityPayload?.agentId, fallback);
74
+ }
75
+
76
+ export function resolveToolDisplayName(payload = null, fallback = null) {
77
+ const identityPayload = resolveToolIdentityPayload(payload);
78
+ const publicIdentity = resolveToolPublicIdentityPayload(payload);
79
+ return normalizePlainText(
80
+ publicIdentity?.displayName,
81
+ normalizePlainText(identityPayload?.recommendedDisplayName, fallback),
82
+ );
83
+ }
84
+
35
85
  function resolveRuntimeAppToken(runtimeConfig = {}) {
36
86
  return normalizeText(
37
87
  runtimeConfig?.appToken,
@@ -94,7 +144,7 @@ async function buildPendingPublicIdentityError({
94
144
  accountId: normalizeText(accountId, null),
95
145
  agentId: normalizeText(
96
146
  agentId,
97
- normalizeText(identityPayload?.agentId, null),
147
+ resolveToolAgentId(identityPayload, null),
98
148
  ),
99
149
  httpStatus: 409,
100
150
  backendCode: 'public_identity_incomplete',
@@ -512,31 +562,36 @@ export function inferAccountAction(params = {}) {
512
562
  }
513
563
 
514
564
  function projectToolPublicIdentity(payload = null) {
515
- if (!payload || typeof payload !== 'object') return null;
565
+ const identityPayload = resolveToolIdentityPayload(payload);
566
+ if (!identityPayload) return null;
567
+ const publicIdentity = resolveToolPublicIdentityPayload(payload);
516
568
  return {
517
- status: payload.status || null,
518
- ready: payload.ready ?? null,
519
- publicIdentity: payload.publicIdentity && typeof payload.publicIdentity === 'object'
569
+ status: normalizePlainText(identityPayload.status, null),
570
+ ready: identityPayload.ready ?? null,
571
+ publicIdentity: publicIdentity
520
572
  ? {
521
- status: payload.publicIdentity.status || null,
522
- displayIdentity: payload.publicIdentity.displayIdentity || null,
523
- displayName: payload.publicIdentity.displayName || null,
524
- code: payload.publicIdentity.code || null,
525
- confirmedAt: payload.publicIdentity.confirmedAt || null,
526
- updatedAt: payload.publicIdentity.updatedAt || null,
573
+ status: normalizePlainText(publicIdentity.status, null),
574
+ displayIdentity: normalizePlainText(publicIdentity.displayIdentity, null),
575
+ displayName: normalizePlainText(publicIdentity.displayName, null),
576
+ code: normalizePlainText(publicIdentity.code, null),
577
+ confirmedAt: normalizePlainText(publicIdentity.confirmedAt, null),
578
+ updatedAt: normalizePlainText(publicIdentity.updatedAt, null),
527
579
  }
528
580
  : null,
529
- recommendedDisplayName: payload.recommendedDisplayName || null,
530
- requiredAction: payload.requiredAction || null,
531
- nextAction: payload.nextAction || null,
532
- nextTool: payload.nextTool || null,
533
- missingFields: Array.isArray(payload.missingFields) ? payload.missingFields : [],
534
- feedbackSummary: payload.feedbackSummary && typeof payload.feedbackSummary === 'object'
581
+ recommendedDisplayName: normalizePlainText(
582
+ identityPayload.recommendedDisplayName,
583
+ null,
584
+ ),
585
+ requiredAction: normalizePlainText(identityPayload.requiredAction, null),
586
+ nextAction: normalizePlainText(identityPayload.nextAction, null),
587
+ nextTool: normalizePlainText(identityPayload.nextTool, null),
588
+ missingFields: Array.isArray(identityPayload.missingFields) ? identityPayload.missingFields : [],
589
+ feedbackSummary: identityPayload.feedbackSummary && typeof identityPayload.feedbackSummary === 'object'
535
590
  ? {
536
- totalLikesReceived: Number(payload.feedbackSummary.totalLikesReceived || 0),
537
- totalDislikesReceived: Number(payload.feedbackSummary.totalDislikesReceived || 0),
538
- totalLikesGiven: Number(payload.feedbackSummary.totalLikesGiven || 0),
539
- totalDislikesGiven: Number(payload.feedbackSummary.totalDislikesGiven || 0),
591
+ totalLikesReceived: Number(identityPayload.feedbackSummary.totalLikesReceived || 0),
592
+ totalDislikesReceived: Number(identityPayload.feedbackSummary.totalDislikesReceived || 0),
593
+ totalLikesGiven: Number(identityPayload.feedbackSummary.totalLikesGiven || 0),
594
+ totalDislikesGiven: Number(identityPayload.feedbackSummary.totalDislikesGiven || 0),
540
595
  }
541
596
  : null,
542
597
  };
@@ -596,23 +651,28 @@ function projectToolAccountIdentityFields(identityPayload = null) {
596
651
  }
597
652
 
598
653
  function projectToolAccountProfile(identityPayload = null) {
599
- return normalizeText(identityPayload?.profile, null);
654
+ const identityPayloadSource = resolveToolIdentityPayload(identityPayload);
655
+ const profilePayload = resolveToolAccountProfilePayload(identityPayload);
656
+ return normalizePlainText(
657
+ profilePayload?.profile,
658
+ normalizePlainText(identityPayloadSource?.profile, null),
659
+ );
600
660
  }
601
661
 
602
662
  function projectToolAccountProfileState(identityPayload = null) {
603
- const profilePayload = normalizeObject(identityPayload?.accountProfile, null);
604
- const profile = normalizeText(profilePayload?.profile, projectToolAccountProfile(identityPayload));
663
+ const profilePayload = resolveToolAccountProfilePayload(identityPayload);
664
+ const profile = normalizePlainText(profilePayload?.profile, projectToolAccountProfile(identityPayload));
605
665
  const ready = profilePayload
606
666
  ? profilePayload.ready === true
607
667
  : Boolean(profile);
608
668
  return {
609
- status: normalizeText(profilePayload?.status, ready ? 'ready' : 'pending'),
669
+ status: normalizePlainText(profilePayload?.status, ready ? 'ready' : 'pending'),
610
670
  ready,
611
671
  profile,
612
- reason: normalizeText(profilePayload?.reason, ready ? null : 'account_profile_missing'),
613
- requiredAction: normalizeText(profilePayload?.requiredAction, ready ? null : 'update_agent_profile'),
614
- nextAction: normalizeText(profilePayload?.nextAction, ready ? null : 'update_agent_profile'),
615
- nextTool: normalizeText(profilePayload?.nextTool, ready ? null : 'claworld_manage_account'),
672
+ reason: normalizePlainText(profilePayload?.reason, ready ? null : 'account_profile_missing'),
673
+ requiredAction: normalizePlainText(profilePayload?.requiredAction, ready ? null : 'update_agent_profile'),
674
+ nextAction: normalizePlainText(profilePayload?.nextAction, ready ? null : 'update_agent_profile'),
675
+ nextTool: normalizePlainText(profilePayload?.nextTool, ready ? null : 'claworld_manage_account'),
616
676
  missingFields: Array.isArray(profilePayload?.missingFields)
617
677
  ? profilePayload.missingFields
618
678
  : (ready
@@ -630,9 +690,9 @@ function projectToolAccountProfileState(identityPayload = null) {
630
690
  function resolveToolPublicIdentityReady(identityPayload = null, publicIdentityState = {}) {
631
691
  const diagnostics = normalizeObject(identityPayload?.diagnostics, null);
632
692
  if (typeof diagnostics?.publicIdentityReady === 'boolean') return diagnostics.publicIdentityReady;
633
- const identityStatus = normalizeText(publicIdentityState?.publicIdentity?.status, null);
693
+ const identityStatus = normalizePlainText(publicIdentityState?.publicIdentity?.status, null);
634
694
  if (identityStatus) return identityStatus === 'ready';
635
- return identityPayload?.ready === true;
695
+ return resolveToolIdentityPayload(identityPayload)?.ready === true || identityPayload?.ready === true;
636
696
  }
637
697
 
638
698
  function projectToolPluginVersionStatus(payload = null) {
@@ -746,8 +806,9 @@ export function projectToolAccountViewResponse({
746
806
  const relayResolved = typeof accountRelay?.resolved === 'boolean'
747
807
  ? accountRelay.resolved
748
808
  : (typeof relayOnline === 'boolean');
749
- const resolvedShareCard = identityPayload && Object.prototype.hasOwnProperty.call(identityPayload, 'shareCard')
750
- ? projectToolShareCard(identityPayload.shareCard)
809
+ const shareCardPayload = resolveToolShareCardPayload(identityPayload);
810
+ const resolvedShareCard = shareCardPayload !== undefined
811
+ ? projectToolShareCard(shareCardPayload)
751
812
  : undefined;
752
813
  return {
753
814
  action: 'view',
@@ -799,7 +860,7 @@ export function projectToolAccountViewResponse({
799
860
  nextAction: blockedAction.nextAction,
800
861
  nextTool: blockedAction.nextTool,
801
862
  missingFields: blockedAction.missingFields,
802
- pluginVersionStatus: projectToolPluginVersionStatus(identityPayload?.pluginVersionStatus),
863
+ pluginVersionStatus: projectToolPluginVersionStatus(resolveToolPluginVersionPayload(identityPayload)),
803
864
  ...(resolvedShareCard !== undefined ? { shareCard: resolvedShareCard } : {}),
804
865
  };
805
866
  }
@@ -813,10 +874,11 @@ export function projectToolAccountMutationResponse({
813
874
  } = {}) {
814
875
  const publicIdentityState = projectToolAccountIdentityFields(identityPayload);
815
876
  const accountProfile = projectToolAccountProfileState(identityPayload);
877
+ const identityShareCardPayload = resolveToolShareCardPayload(identityPayload);
816
878
  const resolvedShareCard = shareCard !== undefined
817
879
  ? shareCard
818
- : (identityPayload && Object.prototype.hasOwnProperty.call(identityPayload, 'shareCard')
819
- ? projectToolShareCard(identityPayload.shareCard)
880
+ : (identityShareCardPayload !== undefined
881
+ ? projectToolShareCard(identityShareCardPayload)
820
882
  : undefined);
821
883
  const publicIdentityReady = resolveToolPublicIdentityReady(identityPayload, publicIdentityState);
822
884
  const accountProfileReady = accountProfile.ready === true;
@@ -885,7 +947,7 @@ export function projectToolAccountMutationResponse({
885
947
  nextTool: blockedAction.nextTool,
886
948
  missingFields: blockedAction.missingFields,
887
949
  reason: blockedAction.reason,
888
- pluginVersionStatus: projectToolPluginVersionStatus(identityPayload?.pluginVersionStatus),
950
+ pluginVersionStatus: projectToolPluginVersionStatus(resolveToolPluginVersionPayload(identityPayload)),
889
951
  ...(resolvedShareCard !== undefined ? { shareCard: resolvedShareCard } : {}),
890
952
  ...(runtimeIdentity ? { runtimeIdentity } : {}),
891
953
  ...(action === 'update_identity'
@@ -50,6 +50,8 @@ import {
50
50
  projectToolManageWorldActionResponse,
51
51
  requireManageWorldField,
52
52
  resolveToolContext,
53
+ resolveToolAgentId,
54
+ resolveToolDisplayName,
53
55
  stringParam,
54
56
  withToolErrorBoundary,
55
57
  } from './register-tooling.js';
@@ -686,6 +688,13 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
686
688
  shareCardVariant: params.shareCardVariant ?? null,
687
689
  expiresInSeconds: params.expiresInSeconds ?? null,
688
690
  });
691
+ if (action === 'update_display_name') {
692
+ return buildToolResult(projectToolAccountMutationResponse({
693
+ action,
694
+ accountId: context.accountId,
695
+ identityPayload: payload,
696
+ }));
697
+ }
689
698
  return buildTerminalActionResult({ tool: accountTool, action, payload });
690
699
  },
691
700
  },
@@ -888,6 +897,7 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
888
897
  category: 'world_management',
889
898
  usageNotes: [
890
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.',
891
901
  'action=create_world creates an owner-managed world.',
892
902
  'Owner governance and member self-service actions use terminal action names such as update_world, publish_broadcast, and update_world_profile.',
893
903
  'Subscription, activity, and member-list actions are backed by the product-shell terminal routes.',
@@ -1007,6 +1017,18 @@ function createTerminalToolAdapters(api, plugin, internalTools) {
1007
1017
  });
1008
1018
  return buildTerminalActionResult({ tool: manageWorldsTool, action, payload });
1009
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
+ }
1010
1032
  if (action === 'list_invites') {
1011
1033
  const worldId = normalizeText(params.worldId, null);
1012
1034
  if (!worldId) requireManageWorldField('worldId');
@@ -2032,7 +2054,7 @@ function buildRegisteredTools(api, plugin) {
2032
2054
  shareCardVariant: params.shareCardVariant ?? null,
2033
2055
  expiresInSeconds: params.expiresInSeconds ?? null,
2034
2056
  });
2035
- const pairedAgentId = identityPayload?.agentId || runtimeConfig.relay?.agentId || null;
2057
+ const pairedAgentId = resolveToolAgentId(identityPayload, runtimeConfig.relay?.agentId || null);
2036
2058
  const pairedRuntimeConfig = pairedAgentId
2037
2059
  ? {
2038
2060
  ...runtimeConfig,
@@ -2045,11 +2067,11 @@ function buildRegisteredTools(api, plugin) {
2045
2067
  const relayAgentFallback = pairedAgentId
2046
2068
  ? {
2047
2069
  agentId: pairedAgentId,
2048
- displayName: normalizeText(
2049
- identityPayload?.publicIdentity?.displayName,
2070
+ displayName: resolveToolDisplayName(
2071
+ identityPayload,
2050
2072
  normalizeText(
2051
- identityPayload?.recommendedDisplayName,
2052
- normalizeText(runtimeConfig?.name, normalizeText(runtimeConfig?.registration?.displayName, null)),
2073
+ runtimeConfig?.name,
2074
+ normalizeText(runtimeConfig?.registration?.displayName, null),
2053
2075
  ),
2054
2076
  ),
2055
2077
  visibilityMode: null,
@@ -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,
@@ -59,6 +59,7 @@ export const PUBLIC_TOOL_ACTION_CATALOG = Object.freeze({
59
59
  'list_world_activity',
60
60
  'list_broadcast_history',
61
61
  'manage_members',
62
+ 'list_pending_invites',
62
63
  'list_invites',
63
64
  'invite_member',
64
65
  'revoke_invite',