@xfxstudio/claworld 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +4 -4
  2. package/index.js +0 -1
  3. package/openclaw.plugin.json +1 -97
  4. package/package.json +1 -1
  5. package/skills/claworld-help/SKILL.md +47 -27
  6. package/skills/claworld-join-and-chat/SKILL.md +13 -9
  7. package/src/openclaw/index.js +0 -3
  8. package/src/openclaw/plugin/account-identity.js +0 -1
  9. package/src/openclaw/plugin/claworld-channel-plugin.js +73 -319
  10. package/src/openclaw/plugin/config-schema.js +1 -55
  11. package/src/openclaw/plugin/managed-config.js +1 -42
  12. package/src/openclaw/plugin/onboarding.js +1 -1
  13. package/src/openclaw/plugin/register.js +302 -233
  14. package/src/openclaw/plugin/relay-client.js +9 -6
  15. package/src/openclaw/runtime/product-shell-helper.js +11 -364
  16. package/src/openclaw/runtime/tool-contracts.js +0 -182
  17. package/src/openclaw/runtime/tool-inventory.js +4 -27
  18. package/src/lib/agent-profile.js +0 -74
  19. package/src/lib/http-auth.js +0 -151
  20. package/src/lib/policy.js +0 -114
  21. package/src/openclaw/installer/constants.js +0 -14
  22. package/src/product-shell/agent-cards/card-routes.js +0 -64
  23. package/src/product-shell/agent-cards/card-service.js +0 -287
  24. package/src/product-shell/agent-cards/spec-builder.js +0 -167
  25. package/src/product-shell/agent-cards/storage/image-host-storage.js +0 -192
  26. package/src/product-shell/agent-cards/storage/local-public-storage.js +0 -74
  27. package/src/product-shell/agent-cards/svg-renderer.js +0 -325
  28. package/src/product-shell/agent-cards/template-registry.js +0 -131
  29. package/src/product-shell/catalog/default-world-catalog.js +0 -38
  30. package/src/product-shell/contracts/candidate-feed.js +0 -393
  31. package/src/product-shell/contracts/world-manifest.js +0 -369
  32. package/src/product-shell/conversation-feedback/conversation-feedback-service.js +0 -261
  33. package/src/product-shell/feedback/feedback-contract.js +0 -13
  34. package/src/product-shell/feedback/feedback-routes.js +0 -98
  35. package/src/product-shell/feedback/feedback-service.js +0 -252
  36. package/src/product-shell/index.js +0 -212
  37. package/src/product-shell/matching/matchmaking-service.js +0 -395
  38. package/src/product-shell/membership/membership-service.js +0 -284
  39. package/src/product-shell/onboarding/onboarding-routes.js +0 -37
  40. package/src/product-shell/onboarding/onboarding-service.js +0 -222
  41. package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -28
  42. package/src/product-shell/profile/profile-service.js +0 -142
  43. package/src/product-shell/profile/public-identity-routes.js +0 -160
  44. package/src/product-shell/profile/public-identity-service.js +0 -192
  45. package/src/product-shell/search/search-service.js +0 -393
  46. package/src/product-shell/social/chat-request-approval-policy.js +0 -332
  47. package/src/product-shell/social/chat-request-routes.js +0 -130
  48. package/src/product-shell/social/chat-request-service.js +0 -723
  49. package/src/product-shell/social/friend-routes.js +0 -82
  50. package/src/product-shell/social/friend-service.js +0 -557
  51. package/src/product-shell/social/social-routes.js +0 -21
  52. package/src/product-shell/social/social-service.js +0 -136
  53. package/src/product-shell/worlds/world-admin-service.js +0 -486
  54. package/src/product-shell/worlds/world-authorization.js +0 -136
  55. package/src/product-shell/worlds/world-broadcast-service.js +0 -296
  56. package/src/product-shell/worlds/world-routes.js +0 -403
  57. package/src/product-shell/worlds/world-service.js +0 -89
  58. package/src/product-shell/worlds/world-text.js +0 -75
@@ -1,395 +0,0 @@
1
- import { buildCandidateFeed, normalizeCandidateFeedLimit, projectCandidateFeedModel } from '../contracts/candidate-feed.js';
2
- import { WORLD_ACTIONS } from '../worlds/world-authorization.js';
3
-
4
- const DATING_DEMO_SCORING_SIGNALS = Object.freeze([
5
- {
6
- signalId: 'intent_exact_match',
7
- label: 'Intent Exact Match',
8
- weight: 50,
9
- sourceFieldIds: ['intent'],
10
- summary: 'Add 50 points when both active memberships declare the same normalized intent.',
11
- },
12
- {
13
- signalId: 'same_location',
14
- label: 'Same Location',
15
- weight: 30,
16
- sourceFieldIds: ['location'],
17
- summary: 'Add 30 points when both active memberships declare the same normalized location.',
18
- },
19
- {
20
- signalId: 'shared_interests',
21
- label: 'Shared Interests',
22
- weight: 20,
23
- sourceFieldIds: ['interests'],
24
- summary: 'Add 10 points per shared normalized interest, capped at 20 points.',
25
- },
26
- ]);
27
-
28
- function createConfigurationError() {
29
- const error = new Error('membership_store_unavailable');
30
- error.code = 'membership_store_unavailable';
31
- error.status = 500;
32
- return error;
33
- }
34
-
35
- function createAgentNotFoundError(agentId) {
36
- const error = new Error(`agent_not_found:${agentId}`);
37
- error.code = 'agent_not_found';
38
- error.status = 404;
39
- return error;
40
- }
41
-
42
- function createInvalidCandidateFeedRequestError(fieldId, message = `${fieldId} is required`) {
43
- const error = new Error(`invalid_candidate_feed_request:${fieldId}`);
44
- error.code = 'invalid_candidate_feed_request';
45
- error.status = 400;
46
- error.responseBody = {
47
- error: error.code,
48
- message: 'candidate feed request is missing required fields',
49
- fieldErrors: [
50
- {
51
- fieldId,
52
- message,
53
- },
54
- ],
55
- };
56
- return error;
57
- }
58
-
59
- function createCandidateFeedMembershipNotActiveError(worldId, agentId) {
60
- const error = new Error(`candidate_feed_membership_not_active:${worldId}:${agentId}`);
61
- error.code = 'candidate_feed_membership_not_active';
62
- error.status = 409;
63
- error.responseBody = {
64
- error: error.code,
65
- message: 'agent must have an active world membership before requesting candidate feed',
66
- worldId,
67
- agentId,
68
- requiredMembershipStatus: 'active',
69
- };
70
- return error;
71
- }
72
-
73
- function normalizeAgentId(agentId) {
74
- const normalized = String(agentId || '').trim();
75
- return normalized || null;
76
- }
77
-
78
- function normalizeText(value) {
79
- return String(value || '').trim().toLowerCase();
80
- }
81
-
82
- function normalizeStringList(value) {
83
- if (!Array.isArray(value)) return [];
84
- return [...new Set(
85
- value
86
- .map((item) => normalizeText(item))
87
- .filter(Boolean),
88
- )].sort((left, right) => left.localeCompare(right));
89
- }
90
-
91
- function projectScoringSignals(worldId) {
92
- if (worldId !== 'dating-demo-world') return [];
93
- return DATING_DEMO_SCORING_SIGNALS.map((signal) => ({ ...signal }));
94
- }
95
-
96
- function buildStrategy(world) {
97
- return {
98
- worldId: world.worldId,
99
- mode: world.matching.mode,
100
- cadence: world.matching.cadence,
101
- strategySummary: world.matching.strategySummary,
102
- candidateSources: world.matching.candidateSources,
103
- inputFields: world.joinSchema.requiredFields.map((field) => field.fieldId),
104
- candidateFeedModel: projectCandidateFeedModel(world),
105
- scoringSignals: projectScoringSignals(world.worldId),
106
- status: world.worldId === 'dating-demo-world' ? 'candidate_scoring_ready' : 'scaffold_ready',
107
- };
108
- }
109
-
110
- function buildInterestBreakdown(requesterInterests, candidateInterests) {
111
- const sharedValues = requesterInterests.filter((interest) => candidateInterests.includes(interest));
112
- const contribution = Math.min(sharedValues.length * 10, 20);
113
-
114
- return {
115
- signalId: 'shared_interests',
116
- label: 'Shared Interests',
117
- weight: 20,
118
- sourceFieldIds: ['interests'],
119
- matched: sharedValues.length > 0,
120
- requesterValue: requesterInterests,
121
- candidateValue: candidateInterests,
122
- sharedValues,
123
- overlapCount: sharedValues.length,
124
- contribution,
125
- };
126
- }
127
-
128
- function buildDatingDemoScoreDetails(viewerMembership, candidateMembership) {
129
- const viewerIntent = normalizeText(viewerMembership.profileSnapshot?.intent);
130
- const candidateIntent = normalizeText(candidateMembership.profileSnapshot?.intent);
131
- const viewerLocation = normalizeText(viewerMembership.profileSnapshot?.location);
132
- const candidateLocation = normalizeText(candidateMembership.profileSnapshot?.location);
133
- const viewerInterests = normalizeStringList(viewerMembership.profileSnapshot?.interests);
134
- const candidateInterests = normalizeStringList(candidateMembership.profileSnapshot?.interests);
135
-
136
- const scoreBreakdown = [
137
- {
138
- signalId: 'intent_exact_match',
139
- label: 'Intent Exact Match',
140
- weight: 50,
141
- sourceFieldIds: ['intent'],
142
- matched: viewerIntent !== '' && viewerIntent === candidateIntent,
143
- requesterValue: viewerIntent,
144
- candidateValue: candidateIntent,
145
- contribution: viewerIntent !== '' && viewerIntent === candidateIntent ? 50 : 0,
146
- },
147
- {
148
- signalId: 'same_location',
149
- label: 'Same Location',
150
- weight: 30,
151
- sourceFieldIds: ['location'],
152
- matched: viewerLocation !== '' && viewerLocation === candidateLocation,
153
- requesterValue: viewerLocation,
154
- candidateValue: candidateLocation,
155
- contribution: viewerLocation !== '' && viewerLocation === candidateLocation ? 30 : 0,
156
- },
157
- buildInterestBreakdown(viewerInterests, candidateInterests),
158
- ];
159
-
160
- return {
161
- joinedAt: candidateMembership.joinedAt,
162
- score: scoreBreakdown.reduce((sum, signal) => sum + signal.contribution, 0),
163
- scoreBreakdown,
164
- scoringInputs: {
165
- requester: {
166
- intent: viewerIntent,
167
- location: viewerLocation,
168
- interests: viewerInterests,
169
- },
170
- candidate: {
171
- intent: candidateIntent,
172
- location: candidateLocation,
173
- interests: candidateInterests,
174
- },
175
- overlap: {
176
- sharedInterests: scoreBreakdown[2].sharedValues,
177
- },
178
- },
179
- };
180
- }
181
-
182
- function compareRankedCandidates(left, right) {
183
- if (right.score !== left.score) return right.score - left.score;
184
-
185
- const rightSharedInterestCount = right.scoringInputs.overlap.sharedInterests.length;
186
- const leftSharedInterestCount = left.scoringInputs.overlap.sharedInterests.length;
187
- if (rightSharedInterestCount !== leftSharedInterestCount) {
188
- return rightSharedInterestCount - leftSharedInterestCount;
189
- }
190
-
191
- if (left.joinedAt !== right.joinedAt) {
192
- return String(left.joinedAt).localeCompare(String(right.joinedAt));
193
- }
194
-
195
- return String(left.sourceMembershipId).localeCompare(String(right.sourceMembershipId));
196
- }
197
-
198
- function buildViewerContext({ world, membershipStore, normalizedAgentId, worldAuthorizationService }) {
199
- const viewerAgent = membershipStore.getAgent(normalizedAgentId);
200
- if (!viewerAgent) {
201
- throw createAgentNotFoundError(normalizedAgentId);
202
- }
203
-
204
- const authorization = worldAuthorizationService.evaluateWorldAction({
205
- worldId: world.worldId,
206
- actorAgentId: normalizedAgentId,
207
- action: WORLD_ACTIONS.VIEW_CANDIDATE_FEED,
208
- });
209
-
210
- if (!authorization.allowed || authorization.membership?.status !== 'active') {
211
- throw createCandidateFeedMembershipNotActiveError(world.worldId, normalizedAgentId);
212
- }
213
-
214
- return {
215
- viewerAgent,
216
- viewerMembership: authorization.membership,
217
- };
218
- }
219
-
220
- function buildBaseFeed({
221
- world,
222
- membershipStore,
223
- normalizedAgentId,
224
- limit,
225
- worldAuthorizationService,
226
- resolvePresence,
227
- }) {
228
- const { viewerAgent, viewerMembership } = buildViewerContext({
229
- world,
230
- membershipStore,
231
- normalizedAgentId,
232
- worldAuthorizationService,
233
- });
234
- const activeMemberships = membershipStore.listMemberships({
235
- worldId: world.worldId,
236
- status: 'active',
237
- });
238
- const normalizedLimit = normalizeCandidateFeedLimit(limit);
239
- const nowMs = typeof membershipStore.nowMs === 'function' ? membershipStore.nowMs() : Date.now();
240
- const baseFeed = buildCandidateFeed({
241
- world,
242
- viewerMembership,
243
- viewerAgent,
244
- candidateMemberships: activeMemberships,
245
- getAgent: (candidateAgentId) => membershipStore.getAgent(candidateAgentId),
246
- getPresence: (candidateAgentId) => resolvePresence(candidateAgentId),
247
- nowMs,
248
- limit: activeMemberships.length,
249
- });
250
-
251
- return {
252
- viewerMembership,
253
- activeMemberships,
254
- normalizedLimit,
255
- baseFeed,
256
- };
257
- }
258
-
259
- function buildDatingDemoFeed({
260
- world,
261
- membershipStore,
262
- normalizedAgentId,
263
- limit,
264
- worldAuthorizationService,
265
- resolvePresence,
266
- conversationFeedbackService = null,
267
- }) {
268
- const { viewerMembership, activeMemberships, normalizedLimit, baseFeed } = buildBaseFeed({
269
- world,
270
- membershipStore,
271
- normalizedAgentId,
272
- limit,
273
- worldAuthorizationService,
274
- resolvePresence,
275
- });
276
- const membershipById = new Map(activeMemberships.map((membership) => [membership.membershipId, membership]));
277
- const rankedCandidates = baseFeed.candidates
278
- .map((candidate) => {
279
- const candidateMembership = membershipById.get(candidate.sourceMembershipId);
280
- return {
281
- ...candidate,
282
- worldFeedbackSummary: conversationFeedbackService?.summarizeWorldAgent?.({
283
- worldId: world.worldId,
284
- agentId: candidate.targetAgentId,
285
- }) || {
286
- likesReceived: 0,
287
- dislikesReceived: 0,
288
- },
289
- ...buildDatingDemoScoreDetails(viewerMembership, candidateMembership),
290
- };
291
- })
292
- .sort(compareRankedCandidates);
293
-
294
- const candidates = rankedCandidates
295
- .slice(0, normalizedLimit)
296
- .map((candidate, index) => ({
297
- ...candidate,
298
- rank: index + 1,
299
- }));
300
-
301
- return {
302
- ...baseFeed,
303
- agentId: normalizedAgentId,
304
- limit: normalizedLimit,
305
- candidateSource: 'active_memberships_online',
306
- strategy: buildStrategy(world),
307
- totalCandidates: rankedCandidates.length,
308
- candidates,
309
- };
310
- }
311
-
312
- export function createMatchmakingService({
313
- worldService,
314
- worldAuthorizationService,
315
- store = null,
316
- presence = null,
317
- conversationFeedbackService = null,
318
- } = {}) {
319
- function assertStore() {
320
- if (!store) throw createConfigurationError();
321
- return store;
322
- }
323
-
324
- function resolvePresence(agentId) {
325
- if (!presence) {
326
- return {
327
- online: true,
328
- connectedAt: null,
329
- lastHeartbeatAt: null,
330
- };
331
- }
332
- return presence.getPresence(agentId);
333
- }
334
-
335
- return {
336
- describeStrategy(worldId) {
337
- const world = worldService.requireWorld(worldId);
338
- return buildStrategy(world);
339
- },
340
- listCandidateFeed({ worldId, agentId, limit } = {}) {
341
- const world = worldService.requireWorld(worldId);
342
- const membershipStore = assertStore();
343
- const normalizedAgentId = normalizeAgentId(agentId);
344
-
345
- if (!normalizedAgentId) {
346
- throw createInvalidCandidateFeedRequestError('agentId');
347
- }
348
-
349
- if (world.worldId === 'dating-demo-world') {
350
- return buildDatingDemoFeed({
351
- world,
352
- membershipStore,
353
- normalizedAgentId,
354
- limit,
355
- worldAuthorizationService,
356
- resolvePresence,
357
- conversationFeedbackService,
358
- });
359
- }
360
-
361
- const { normalizedLimit, baseFeed } = buildBaseFeed({
362
- world,
363
- membershipStore,
364
- normalizedAgentId,
365
- limit,
366
- worldAuthorizationService,
367
- resolvePresence,
368
- });
369
-
370
- return {
371
- ...baseFeed,
372
- agentId: normalizedAgentId,
373
- limit: normalizedLimit,
374
- candidateSource: 'active_memberships_online',
375
- strategy: buildStrategy(world),
376
- totalCandidates: baseFeed.candidates.length,
377
- candidates: baseFeed.candidates
378
- .slice(0, normalizedLimit)
379
- .map((candidate) => ({
380
- ...candidate,
381
- worldFeedbackSummary: conversationFeedbackService?.summarizeWorldAgent?.({
382
- worldId: world.worldId,
383
- agentId: candidate.targetAgentId,
384
- }) || {
385
- likesReceived: 0,
386
- dislikesReceived: 0,
387
- },
388
- })),
389
- };
390
- },
391
- listCandidates(options = {}) {
392
- return this.listCandidateFeed(options);
393
- },
394
- };
395
- }
@@ -1,284 +0,0 @@
1
- import { buildResolvedWorldJoinOrchestration } from '../contracts/world-orchestration.js';
2
- import { buildParticipantContextText } from '../worlds/world-text.js';
3
-
4
- function normalizeText(value, fallback = null) {
5
- if (value == null) return fallback;
6
- const normalized = String(value).trim();
7
- return normalized || fallback;
8
- }
9
-
10
- function createConfigurationError() {
11
- const error = new Error('membership_store_unavailable');
12
- error.code = 'membership_store_unavailable';
13
- error.status = 500;
14
- return error;
15
- }
16
-
17
- function createAgentNotFoundError(agentId) {
18
- const error = new Error(`agent_not_found:${agentId}`);
19
- error.code = 'agent_not_found';
20
- error.status = 404;
21
- return error;
22
- }
23
-
24
- function createInvalidJoinRequestError(fieldId, message = `${fieldId} is required`) {
25
- const error = new Error(`invalid_join_request:${fieldId}`);
26
- error.code = 'invalid_join_request';
27
- error.status = 400;
28
- error.responseBody = {
29
- error: error.code,
30
- message: 'join request is missing required fields',
31
- fieldErrors: [
32
- {
33
- fieldId,
34
- message,
35
- },
36
- ],
37
- };
38
- return error;
39
- }
40
-
41
- function normalizeAgentId(agentId) {
42
- const normalized = String(agentId || '').trim();
43
- return normalized || null;
44
- }
45
-
46
- function normalizeProfileSnapshot(profileSnapshot = null, participantContextText = null) {
47
- const base = profileSnapshot && typeof profileSnapshot === 'object' && !Array.isArray(profileSnapshot)
48
- ? { ...profileSnapshot }
49
- : {};
50
- const normalizedParticipantContextText = normalizeText(
51
- participantContextText,
52
- normalizeText(base.participantContextText, null),
53
- );
54
- if (normalizedParticipantContextText) {
55
- base.participantContextText = normalizedParticipantContextText;
56
- }
57
- return base;
58
- }
59
-
60
- function buildNextStageSummary() {
61
- return {
62
- stage: 'candidate_review',
63
- summary:
64
- 'Review the backend-authored candidate feed or use world search, then choose one target agent and create a world-scoped chat request.',
65
- };
66
- }
67
-
68
- export function createMembershipService({ worldService, store = null, publicIdentityService = null } = {}) {
69
- function assertStore() {
70
- if (!store) throw createConfigurationError();
71
- return store;
72
- }
73
-
74
- function resolveNormalizedParticipantContextText({ participantContextText = null, profileSnapshot = null, agent = null, world = null } = {}) {
75
- return buildParticipantContextText({
76
- world,
77
- agent,
78
- participantContextText,
79
- profileSnapshot,
80
- });
81
- }
82
-
83
- return {
84
- evaluateJoin({ worldId, participantContextText = null, profile = null, profileSnapshot = null } = {}) {
85
- const world = worldService.requireWorld(worldId);
86
- const normalizedParticipantContextText = resolveNormalizedParticipantContextText({
87
- world,
88
- participantContextText,
89
- profileSnapshot: profileSnapshot || profile,
90
- });
91
- const accepted = Boolean(normalizedParticipantContextText);
92
-
93
- return {
94
- worldId: world.worldId,
95
- accepted,
96
- status: accepted ? 'eligible' : 'needs_profile',
97
- participantContextText: normalizedParticipantContextText,
98
- missingFields: accepted
99
- ? []
100
- : [
101
- {
102
- fieldId: 'participantContextText',
103
- label: 'Entry Profile',
104
- description: 'A short text describing who you are in this world and what context you bring into it.',
105
- },
106
- ],
107
- nextMissingField: accepted
108
- ? null
109
- : {
110
- fieldId: 'participantContextText',
111
- label: 'Entry Profile',
112
- description: 'A short text describing who you are in this world and what context you bring into it.',
113
- },
114
- missingFieldGuidance: {
115
- mode: accepted ? 'complete' : 'single_text_field',
116
- orderedMissingFields: accepted
117
- ? []
118
- : [
119
- {
120
- fieldId: 'participantContextText',
121
- label: 'Entry Profile',
122
- description: 'A short text describing who you are in this world and what context you bring into it.',
123
- },
124
- ],
125
- orderedMissingFieldIds: accepted ? [] : ['participantContextText'],
126
- nextMissingField: accepted
127
- ? null
128
- : {
129
- fieldId: 'participantContextText',
130
- label: 'Entry Profile',
131
- description: 'A short text describing who you are in this world and what context you bring into it.',
132
- },
133
- remainingRequiredFieldCount: accepted ? 0 : 1,
134
- },
135
- nextAction: accepted ? 'call_join_world' : 'retry_join_world_after_profile_update',
136
- };
137
- },
138
-
139
- async createMembership({ worldId, agentId, participantContextText, profile = null, profileSnapshot = null } = {}) {
140
- const world = worldService.requireWorld(worldId);
141
- const membershipStore = assertStore();
142
- const normalizedAgentId = normalizeAgentId(agentId);
143
- if (!normalizedAgentId) throw createInvalidJoinRequestError('agentId');
144
-
145
- const agent = membershipStore.getAgent(normalizedAgentId);
146
- if (!agent) throw createAgentNotFoundError(normalizedAgentId);
147
-
148
- const normalizedParticipantContextText = resolveNormalizedParticipantContextText({
149
- world,
150
- agent,
151
- participantContextText,
152
- profileSnapshot: profileSnapshot || profile,
153
- });
154
- if (!normalizedParticipantContextText) {
155
- throw createInvalidJoinRequestError(
156
- 'participantContextText',
157
- 'participantContextText is required',
158
- );
159
- }
160
-
161
- const existingMembership = membershipStore.listMemberships({
162
- worldId,
163
- agentId: normalizedAgentId,
164
- })[0] || null;
165
-
166
- if (existingMembership) {
167
- return { membership: existingMembership, created: false };
168
- }
169
-
170
- const membership = await membershipStore.createMembership({
171
- worldId,
172
- agentId: normalizedAgentId,
173
- status: 'joined',
174
- profileSnapshot: normalizeProfileSnapshot(profileSnapshot || profile, normalizedParticipantContextText),
175
- participantContextText: normalizedParticipantContextText,
176
- });
177
-
178
- return { membership, created: true };
179
- },
180
-
181
- async joinWorld({
182
- worldId,
183
- agentId,
184
- participantContextText,
185
- profile = null,
186
- profileSnapshot = null,
187
- } = {}) {
188
- const world = worldService.requireWorld(worldId);
189
- const membershipStore = assertStore();
190
- const normalizedAgentId = normalizeAgentId(agentId);
191
-
192
- if (!normalizedAgentId) throw createInvalidJoinRequestError('agentId');
193
-
194
- const agent = membershipStore.getAgent(normalizedAgentId);
195
- if (!agent) throw createAgentNotFoundError(normalizedAgentId);
196
- publicIdentityService?.assertPublicIdentityReady?.({
197
- agentId: normalizedAgentId,
198
- capability: 'join world',
199
- });
200
-
201
- const normalizedParticipantContextText = resolveNormalizedParticipantContextText({
202
- world,
203
- agent,
204
- participantContextText,
205
- profileSnapshot: profileSnapshot || profile,
206
- });
207
- if (!normalizedParticipantContextText) {
208
- throw createInvalidJoinRequestError(
209
- 'participantContextText',
210
- 'participantContextText is required',
211
- );
212
- }
213
-
214
- const existingMembership = membershipStore.listMemberships({
215
- worldId: world.worldId,
216
- agentId: normalizedAgentId,
217
- })[0] || null;
218
-
219
- const membership = existingMembership
220
- ? await membershipStore.updateMembership(existingMembership.membershipId, {
221
- status: 'active',
222
- profileSnapshot: normalizeProfileSnapshot(
223
- {
224
- ...(existingMembership.profileSnapshot && typeof existingMembership.profileSnapshot === 'object'
225
- ? existingMembership.profileSnapshot
226
- : {}),
227
- ...((profileSnapshot || profile) && typeof (profileSnapshot || profile) === 'object' && !Array.isArray(profileSnapshot || profile)
228
- ? (profileSnapshot || profile)
229
- : {}),
230
- },
231
- normalizedParticipantContextText,
232
- ),
233
- participantContextText: normalizedParticipantContextText,
234
- })
235
- : await membershipStore.createMembership({
236
- worldId: world.worldId,
237
- agentId: normalizedAgentId,
238
- status: 'active',
239
- profileSnapshot: normalizeProfileSnapshot(profileSnapshot || profile, normalizedParticipantContextText),
240
- participantContextText: normalizedParticipantContextText,
241
- });
242
-
243
- const joinResult = {
244
- status: 'joined',
245
- worldId: world.worldId,
246
- membership,
247
- membershipStatus: membership.status,
248
- created: !existingMembership,
249
- participantContextText: normalizedParticipantContextText,
250
- nextAction: 'review_candidate_feed',
251
- nextStageSummary: buildNextStageSummary(),
252
- };
253
-
254
- return {
255
- ...joinResult,
256
- orchestration: buildResolvedWorldJoinOrchestration({
257
- joinResult,
258
- candidateDelivery: null,
259
- }) || null,
260
- };
261
- },
262
-
263
- getMembership({ worldId, agentId, includeDisabled = false } = {}) {
264
- const normalizedAgentId = normalizeAgentId(agentId);
265
- if (!normalizedAgentId) return null;
266
- worldService.requireWorld(worldId, { includeDisabled });
267
- return assertStore().listMemberships({ worldId, agentId: normalizedAgentId })[0] || null;
268
- },
269
-
270
- listMemberships({ worldId, agentId = null, status = null, includeDisabled = false } = {}) {
271
- worldService.requireWorld(worldId, { includeDisabled });
272
- return assertStore().listMemberships({ worldId, agentId, status });
273
- },
274
-
275
- listMembershipsAcrossWorlds({ agentId = null, status = null } = {}) {
276
- return assertStore().listMemberships({ agentId, status });
277
- },
278
-
279
- countMemberships({ worldId, agentId = null, status = null, includeDisabled = false } = {}) {
280
- worldService.requireWorld(worldId, { includeDisabled });
281
- return assertStore().countMemberships({ worldId, agentId, status });
282
- },
283
- };
284
- }