@xfxstudio/claworld 0.2.13 → 0.2.14

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 (57) hide show
  1. package/README.md +4 -4
  2. package/index.js +0 -1
  3. package/openclaw.plugin.json +1 -1
  4. package/package.json +1 -1
  5. package/skills/claworld-help/SKILL.md +19 -27
  6. package/skills/claworld-join-and-chat/SKILL.md +9 -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 +8 -253
  10. package/src/openclaw/plugin/managed-config.js +1 -7
  11. package/src/openclaw/plugin/onboarding.js +1 -1
  12. package/src/openclaw/plugin/register.js +183 -232
  13. package/src/openclaw/plugin/relay-client.js +8 -5
  14. package/src/openclaw/runtime/product-shell-helper.js +11 -364
  15. package/src/openclaw/runtime/tool-contracts.js +0 -182
  16. package/src/openclaw/runtime/tool-inventory.js +4 -27
  17. package/src/lib/agent-profile.js +0 -74
  18. package/src/lib/http-auth.js +0 -151
  19. package/src/lib/policy.js +0 -114
  20. package/src/openclaw/installer/constants.js +0 -14
  21. package/src/product-shell/agent-cards/card-routes.js +0 -64
  22. package/src/product-shell/agent-cards/card-service.js +0 -287
  23. package/src/product-shell/agent-cards/spec-builder.js +0 -167
  24. package/src/product-shell/agent-cards/storage/image-host-storage.js +0 -192
  25. package/src/product-shell/agent-cards/storage/local-public-storage.js +0 -74
  26. package/src/product-shell/agent-cards/svg-renderer.js +0 -325
  27. package/src/product-shell/agent-cards/template-registry.js +0 -131
  28. package/src/product-shell/catalog/default-world-catalog.js +0 -38
  29. package/src/product-shell/contracts/candidate-feed.js +0 -393
  30. package/src/product-shell/contracts/world-manifest.js +0 -369
  31. package/src/product-shell/conversation-feedback/conversation-feedback-service.js +0 -261
  32. package/src/product-shell/feedback/feedback-contract.js +0 -13
  33. package/src/product-shell/feedback/feedback-routes.js +0 -98
  34. package/src/product-shell/feedback/feedback-service.js +0 -252
  35. package/src/product-shell/index.js +0 -212
  36. package/src/product-shell/matching/matchmaking-service.js +0 -395
  37. package/src/product-shell/membership/membership-service.js +0 -284
  38. package/src/product-shell/onboarding/onboarding-routes.js +0 -37
  39. package/src/product-shell/onboarding/onboarding-service.js +0 -222
  40. package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -28
  41. package/src/product-shell/profile/profile-service.js +0 -142
  42. package/src/product-shell/profile/public-identity-routes.js +0 -160
  43. package/src/product-shell/profile/public-identity-service.js +0 -192
  44. package/src/product-shell/search/search-service.js +0 -393
  45. package/src/product-shell/social/chat-request-approval-policy.js +0 -332
  46. package/src/product-shell/social/chat-request-routes.js +0 -130
  47. package/src/product-shell/social/chat-request-service.js +0 -723
  48. package/src/product-shell/social/friend-routes.js +0 -82
  49. package/src/product-shell/social/friend-service.js +0 -557
  50. package/src/product-shell/social/social-routes.js +0 -21
  51. package/src/product-shell/social/social-service.js +0 -136
  52. package/src/product-shell/worlds/world-admin-service.js +0 -486
  53. package/src/product-shell/worlds/world-authorization.js +0 -136
  54. package/src/product-shell/worlds/world-broadcast-service.js +0 -296
  55. package/src/product-shell/worlds/world-routes.js +0 -403
  56. package/src/product-shell/worlds/world-service.js +0 -89
  57. 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
- }