@xfxstudio/claworld 0.2.9 → 0.2.10-beta.0

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 (49) hide show
  1. package/README.md +1 -1
  2. package/openclaw.plugin.json +7 -63
  3. package/package.json +6 -2
  4. package/skills/claworld-help/SKILL.md +5 -1
  5. package/skills/claworld-join-and-chat/SKILL.md +21 -1
  6. package/skills/claworld-manage-worlds/SKILL.md +81 -10
  7. package/src/lib/agent-profile.js +8 -3
  8. package/src/lib/chat-request.js +0 -1
  9. package/src/lib/policy.js +2 -6
  10. package/src/lib/public-identity.js +175 -0
  11. package/src/lib/relay/kickoff-text.js +1 -0
  12. package/src/openclaw/installer/cli.js +46 -1
  13. package/src/openclaw/installer/constants.js +1 -0
  14. package/src/openclaw/installer/core.js +234 -3
  15. package/src/openclaw/installer/doctor.js +2 -2
  16. package/src/openclaw/plugin/account-identity.js +1 -2
  17. package/src/openclaw/plugin/claworld-channel-plugin.js +270 -255
  18. package/src/openclaw/plugin/config-schema.js +9 -23
  19. package/src/openclaw/plugin/managed-config.js +284 -79
  20. package/src/openclaw/plugin/onboarding.js +22 -42
  21. package/src/openclaw/plugin/register.js +109 -10
  22. package/src/openclaw/plugin/relay-client.js +233 -17
  23. package/src/openclaw/runtime/backend-error-context.js +91 -0
  24. package/src/openclaw/runtime/feedback-helper.js +1 -2
  25. package/src/openclaw/runtime/product-shell-helper.js +43 -9
  26. package/src/openclaw/runtime/tool-contracts.js +26 -3
  27. package/src/openclaw/runtime/tool-inventory.js +7 -0
  28. package/src/openclaw/runtime/world-moderation-helper.js +3 -19
  29. package/src/product-shell/contracts/candidate-feed.js +7 -0
  30. package/src/product-shell/contracts/world-manifest.js +0 -1
  31. package/src/product-shell/contracts/world-orchestration.js +10 -1
  32. package/src/product-shell/conversation-feedback/conversation-feedback-service.js +261 -0
  33. package/src/product-shell/feedback/feedback-routes.js +0 -1
  34. package/src/product-shell/feedback/feedback-service.js +4 -9
  35. package/src/product-shell/index.js +40 -7
  36. package/src/product-shell/matching/matchmaking-service.js +22 -1
  37. package/src/product-shell/membership/membership-service.js +5 -1
  38. package/src/product-shell/onboarding/onboarding-service.js +10 -21
  39. package/src/product-shell/profile/public-identity-routes.js +60 -0
  40. package/src/product-shell/profile/public-identity-service.js +190 -0
  41. package/src/product-shell/search/search-service.js +9 -2
  42. package/src/product-shell/social/chat-request-service.js +22 -7
  43. package/src/product-shell/social/friend-routes.js +1 -1
  44. package/src/product-shell/social/friend-service.js +16 -19
  45. package/src/product-shell/social/social-routes.js +2 -2
  46. package/src/product-shell/social/social-service.js +31 -35
  47. package/src/product-shell/worlds/world-admin-service.js +31 -10
  48. package/src/product-shell/worlds/world-broadcast-service.js +2 -2
  49. package/src/lib/agent-address.js +0 -46
@@ -0,0 +1,261 @@
1
+ const FEEDBACK_TOKEN_TO_SENTIMENT = Object.freeze({
2
+ '[[like]]': 'like',
3
+ '[[dislike]]': 'dislike',
4
+ });
5
+
6
+ const SENTIMENT_VALUES = Object.freeze(['like', 'dislike']);
7
+
8
+ function normalizeText(value, fallback = null) {
9
+ if (value == null) return fallback;
10
+ const normalized = String(value).trim();
11
+ return normalized || fallback;
12
+ }
13
+
14
+ function createConfigurationError() {
15
+ const error = new Error('conversation_feedback_store_unavailable');
16
+ error.code = 'conversation_feedback_store_unavailable';
17
+ error.status = 500;
18
+ return error;
19
+ }
20
+
21
+ function createInvalidConversationFeedbackError(fieldId, message = `${fieldId} is required`) {
22
+ const error = new Error(`invalid_conversation_feedback:${fieldId}`);
23
+ error.code = 'invalid_conversation_feedback';
24
+ error.status = 400;
25
+ error.responseBody = {
26
+ error: error.code,
27
+ message: 'conversation feedback request is invalid',
28
+ fieldErrors: [
29
+ {
30
+ fieldId,
31
+ message,
32
+ },
33
+ ],
34
+ };
35
+ return error;
36
+ }
37
+
38
+ function normalizeSentiment(value, fallback = null) {
39
+ const normalized = normalizeText(value, fallback);
40
+ return SENTIMENT_VALUES.includes(normalized) ? normalized : fallback;
41
+ }
42
+
43
+ function resolveSentimentFromToken(token = null) {
44
+ const normalizedToken = normalizeText(token, null);
45
+ return normalizedToken ? FEEDBACK_TOKEN_TO_SENTIMENT[normalizedToken] || null : null;
46
+ }
47
+
48
+ export function parseConversationFeedbackTokens(text = '') {
49
+ if (typeof text !== 'string' || !text.trim()) {
50
+ return {
51
+ tokens: [],
52
+ sentiment: null,
53
+ conflict: false,
54
+ };
55
+ }
56
+
57
+ const matches = [...text.matchAll(/\[\[(like|dislike)\]\]/g)];
58
+ if (matches.length === 0) {
59
+ return {
60
+ tokens: [],
61
+ sentiment: null,
62
+ conflict: false,
63
+ };
64
+ }
65
+
66
+ const tokens = [...new Set(matches.map((match) => `[[${String(match[1] || '').trim().toLowerCase()}]]`))];
67
+ const sentiments = [...new Set(tokens.map((token) => resolveSentimentFromToken(token)).filter(Boolean))];
68
+ return {
69
+ tokens,
70
+ sentiment: sentiments.length === 1 ? sentiments[0] : null,
71
+ conflict: sentiments.length > 1,
72
+ };
73
+ }
74
+
75
+ function projectFeedbackMark(feedbackMark = {}) {
76
+ return {
77
+ feedbackMarkId: normalizeText(feedbackMark.feedbackMarkId, null),
78
+ conversationKey: normalizeText(feedbackMark.conversationKey, null),
79
+ worldId: normalizeText(feedbackMark.worldId, null),
80
+ giverAgentId: normalizeText(feedbackMark.giverAgentId, null),
81
+ receiverAgentId: normalizeText(feedbackMark.receiverAgentId, null),
82
+ sourceTurnId: normalizeText(feedbackMark.sourceTurnId, null),
83
+ sourceDeliveryId: normalizeText(feedbackMark.sourceDeliveryId, null),
84
+ token: normalizeText(feedbackMark.token, null),
85
+ sentiment: normalizeSentiment(feedbackMark.sentiment, null),
86
+ source: normalizeText(feedbackMark.source, 'reply_control_token'),
87
+ status: normalizeText(feedbackMark.status, 'active'),
88
+ createdAt: normalizeText(feedbackMark.createdAt, null),
89
+ updatedAt: normalizeText(feedbackMark.updatedAt, normalizeText(feedbackMark.createdAt, null)),
90
+ };
91
+ }
92
+
93
+ function summarizeReceived(items = []) {
94
+ return {
95
+ likesReceived: items.filter((item) => item.sentiment === 'like').length,
96
+ dislikesReceived: items.filter((item) => item.sentiment === 'dislike').length,
97
+ };
98
+ }
99
+
100
+ export function createConversationFeedbackService({ store } = {}) {
101
+ if (
102
+ !store
103
+ || typeof store.createConversationFeedbackMark !== 'function'
104
+ || typeof store.listConversationFeedbackMarks !== 'function'
105
+ || typeof store.findConversationFeedbackMarkByConversationAndActors !== 'function'
106
+ ) {
107
+ throw createConfigurationError();
108
+ }
109
+
110
+ return {
111
+ parseTokens(text = '') {
112
+ return parseConversationFeedbackTokens(text);
113
+ },
114
+
115
+ async recordFeedback({
116
+ conversationKey,
117
+ worldId = null,
118
+ giverAgentId,
119
+ receiverAgentId,
120
+ sourceTurnId = null,
121
+ sourceDeliveryId = null,
122
+ token = null,
123
+ sentiment = null,
124
+ } = {}) {
125
+ const normalizedConversationKey = normalizeText(conversationKey, null);
126
+ if (!normalizedConversationKey) {
127
+ throw createInvalidConversationFeedbackError('conversationKey');
128
+ }
129
+ const normalizedGiverAgentId = normalizeText(giverAgentId, null);
130
+ if (!normalizedGiverAgentId) {
131
+ throw createInvalidConversationFeedbackError('giverAgentId');
132
+ }
133
+ const normalizedReceiverAgentId = normalizeText(receiverAgentId, null);
134
+ if (!normalizedReceiverAgentId) {
135
+ throw createInvalidConversationFeedbackError('receiverAgentId');
136
+ }
137
+ const normalizedToken = normalizeText(token, null);
138
+ const normalizedSentiment = normalizeSentiment(sentiment, resolveSentimentFromToken(normalizedToken));
139
+ if (!normalizedSentiment) {
140
+ throw createInvalidConversationFeedbackError('sentiment', 'sentiment must be like or dislike');
141
+ }
142
+
143
+ const existing = store.findConversationFeedbackMarkByConversationAndActors({
144
+ conversationKey: normalizedConversationKey,
145
+ giverAgentId: normalizedGiverAgentId,
146
+ receiverAgentId: normalizedReceiverAgentId,
147
+ });
148
+ if (existing) {
149
+ return {
150
+ recorded: false,
151
+ deduped: true,
152
+ feedbackMark: projectFeedbackMark(existing),
153
+ };
154
+ }
155
+
156
+ const feedbackMark = await store.createConversationFeedbackMark({
157
+ conversationKey: normalizedConversationKey,
158
+ worldId: normalizeText(worldId, null),
159
+ giverAgentId: normalizedGiverAgentId,
160
+ receiverAgentId: normalizedReceiverAgentId,
161
+ sourceTurnId: normalizeText(sourceTurnId, null),
162
+ sourceDeliveryId: normalizeText(sourceDeliveryId, null),
163
+ token: normalizedToken || `[[${normalizedSentiment}]]`,
164
+ sentiment: normalizedSentiment,
165
+ source: 'reply_control_token',
166
+ status: 'active',
167
+ });
168
+
169
+ return {
170
+ recorded: true,
171
+ deduped: false,
172
+ feedbackMark: projectFeedbackMark(feedbackMark),
173
+ };
174
+ },
175
+
176
+ listFeedbackMarks(filters = {}) {
177
+ return store.listConversationFeedbackMarks(filters).map((item) => projectFeedbackMark(item));
178
+ },
179
+
180
+ summarizeConversation({ conversationKey, viewerAgentId = null } = {}) {
181
+ const normalizedConversationKey = normalizeText(conversationKey, null);
182
+ const normalizedViewerAgentId = normalizeText(viewerAgentId, null);
183
+ const items = normalizedConversationKey
184
+ ? this.listFeedbackMarks({ conversationKey: normalizedConversationKey, status: 'active' })
185
+ : [];
186
+ const viewerGave = normalizedViewerAgentId
187
+ ? items.find((item) => item.giverAgentId === normalizedViewerAgentId)?.sentiment || null
188
+ : null;
189
+ const viewerReceived = normalizedViewerAgentId
190
+ ? items.find((item) => item.receiverAgentId === normalizedViewerAgentId)?.sentiment || null
191
+ : null;
192
+ return {
193
+ likeCount: items.filter((item) => item.sentiment === 'like').length,
194
+ dislikeCount: items.filter((item) => item.sentiment === 'dislike').length,
195
+ viewerGave,
196
+ viewerReceived,
197
+ };
198
+ },
199
+
200
+ summarizeAgent({ agentId } = {}) {
201
+ const normalizedAgentId = normalizeText(agentId, null);
202
+ if (!normalizedAgentId) {
203
+ return {
204
+ totalLikesReceived: 0,
205
+ totalDislikesReceived: 0,
206
+ totalLikesGiven: 0,
207
+ totalDislikesGiven: 0,
208
+ };
209
+ }
210
+ const received = this.listFeedbackMarks({
211
+ receiverAgentId: normalizedAgentId,
212
+ status: 'active',
213
+ });
214
+ const given = this.listFeedbackMarks({
215
+ giverAgentId: normalizedAgentId,
216
+ status: 'active',
217
+ });
218
+
219
+ return {
220
+ totalLikesReceived: received.filter((item) => item.sentiment === 'like').length,
221
+ totalDislikesReceived: received.filter((item) => item.sentiment === 'dislike').length,
222
+ totalLikesGiven: given.filter((item) => item.sentiment === 'like').length,
223
+ totalDislikesGiven: given.filter((item) => item.sentiment === 'dislike').length,
224
+ };
225
+ },
226
+
227
+ summarizeWorldAgent({ worldId, agentId } = {}) {
228
+ const normalizedWorldId = normalizeText(worldId, null);
229
+ const normalizedAgentId = normalizeText(agentId, null);
230
+ if (!normalizedWorldId || !normalizedAgentId) {
231
+ return {
232
+ likesReceived: 0,
233
+ dislikesReceived: 0,
234
+ };
235
+ }
236
+ return summarizeReceived(this.listFeedbackMarks({
237
+ worldId: normalizedWorldId,
238
+ receiverAgentId: normalizedAgentId,
239
+ status: 'active',
240
+ }));
241
+ },
242
+
243
+ summarizeWorld({ worldId } = {}) {
244
+ const normalizedWorldId = normalizeText(worldId, null);
245
+ if (!normalizedWorldId) {
246
+ return {
247
+ totalLikes: 0,
248
+ totalDislikes: 0,
249
+ };
250
+ }
251
+ const items = this.listFeedbackMarks({
252
+ worldId: normalizedWorldId,
253
+ status: 'active',
254
+ });
255
+ return {
256
+ totalLikes: items.filter((item) => item.sentiment === 'like').length,
257
+ totalDislikes: items.filter((item) => item.sentiment === 'dislike').length,
258
+ };
259
+ },
260
+ };
261
+ }
@@ -57,7 +57,6 @@ export function registerFeedbackRoutes(app, { store, feedbackService }) {
57
57
  turnId: req.body?.turnId,
58
58
  deliveryId: req.body?.deliveryId,
59
59
  targetAgentId: req.body?.targetAgentId,
60
- targetAgentCode: req.body?.targetAgentCode,
61
60
  tags: req.body?.tags,
62
61
  metadata: req.body?.metadata,
63
62
  context: req.body?.context,
@@ -2,6 +2,7 @@ import {
2
2
  FEEDBACK_CATEGORY_VALUES,
3
3
  FEEDBACK_IMPACT_VALUES,
4
4
  } from './feedback-contract.js';
5
+ import { resolvePublicIdentity } from '../../lib/public-identity.js';
5
6
 
6
7
  function normalizeText(value, fallback = null) {
7
8
  if (value == null) return fallback;
@@ -111,13 +112,11 @@ function projectFeedback(feedback = {}) {
111
112
  reporter: feedback.reporter && typeof feedback.reporter === 'object'
112
113
  ? {
113
114
  agentId: feedback.reporter.agentId || null,
114
- agentCode: feedback.reporter.agentCode || null,
115
- address: feedback.reporter.address || null,
115
+ publicIdentity: feedback.reporter.publicIdentity || null,
116
116
  }
117
117
  : {
118
118
  agentId: null,
119
- agentCode: null,
120
- address: null,
119
+ publicIdentity: null,
121
120
  },
122
121
  context: feedback.context && typeof feedback.context === 'object'
123
122
  ? {
@@ -126,7 +125,6 @@ function projectFeedback(feedback = {}) {
126
125
  turnId: feedback.context.turnId || null,
127
126
  deliveryId: feedback.context.deliveryId || null,
128
127
  targetAgentId: feedback.context.targetAgentId || null,
129
- targetAgentCode: feedback.context.targetAgentCode || null,
130
128
  tags: Array.isArray(feedback.context.tags) ? feedback.context.tags : [],
131
129
  metadata: feedback.context.metadata && typeof feedback.context.metadata === 'object'
132
130
  ? feedback.context.metadata
@@ -138,7 +136,6 @@ function projectFeedback(feedback = {}) {
138
136
  turnId: null,
139
137
  deliveryId: null,
140
138
  targetAgentId: null,
141
- targetAgentCode: null,
142
139
  tags: [],
143
140
  metadata: {},
144
141
  },
@@ -178,8 +175,7 @@ export function createFeedbackService({ store } = {}) {
178
175
  accountId: normalizeText(input.accountId, null),
179
176
  reporter: {
180
177
  agentId: reporter.agentId,
181
- agentCode: reporter.agentCode,
182
- address: reporter.address,
178
+ publicIdentity: resolvePublicIdentity(reporter),
183
179
  },
184
180
  context: {
185
181
  worldId: normalizeText(input.worldId, normalizeText(context.worldId, null)),
@@ -187,7 +183,6 @@ export function createFeedbackService({ store } = {}) {
187
183
  turnId: normalizeText(input.turnId, normalizeText(context.turnId, null)),
188
184
  deliveryId: normalizeText(input.deliveryId, normalizeText(context.deliveryId, null)),
189
185
  targetAgentId: normalizeText(input.targetAgentId, normalizeText(context.targetAgentId, null)),
190
- targetAgentCode: normalizeText(input.targetAgentCode, normalizeText(context.targetAgentCode, null)),
191
186
  tags: normalizeStringList(input.tags || context.tags),
192
187
  metadata: normalizePlainObject(input.metadata && Object.keys(input.metadata || {}).length > 0 ? input.metadata : context.metadata),
193
188
  },
@@ -6,6 +6,8 @@ import { createWorldBroadcastService } from './worlds/world-broadcast-service.js
6
6
  import { DEFAULT_WORLD_MANIFESTS } from './catalog/default-world-catalog.js';
7
7
  import { createOnboardingService } from './onboarding/onboarding-service.js';
8
8
  import { registerOnboardingRoutes } from './onboarding/onboarding-routes.js';
9
+ import { createPublicIdentityService } from './profile/public-identity-service.js';
10
+ import { registerPublicIdentityRoutes } from './profile/public-identity-routes.js';
9
11
  import { createMembershipService } from './membership/membership-service.js';
10
12
  import { createMatchmakingService } from './matching/matchmaking-service.js';
11
13
  import { createWorldSearchService } from './search/search-service.js';
@@ -18,6 +20,7 @@ import { createChatRequestService } from './social/chat-request-service.js';
18
20
  import { registerChatRequestRoutes } from './social/chat-request-routes.js';
19
21
  import { createFeedbackService } from './feedback/feedback-service.js';
20
22
  import { registerFeedbackRoutes } from './feedback/feedback-routes.js';
23
+ import { createConversationFeedbackService } from './conversation-feedback/conversation-feedback-service.js';
21
24
 
22
25
  export function createClaworldProductShell({
23
26
  worldCatalog = DEFAULT_WORLD_MANIFESTS,
@@ -27,20 +30,42 @@ export function createClaworldProductShell({
27
30
  } = {}) {
28
31
  const worldService = createWorldService({ worldCatalog, store });
29
32
  const onboardingService = createOnboardingService({ worldService, store });
30
- const membershipService = createMembershipService({ worldService, store });
33
+ const publicIdentityService = createPublicIdentityService({ store });
34
+ const membershipService = createMembershipService({ worldService, store, publicIdentityService });
31
35
  const worldAuthorizationService = createWorldAuthorizationService({
32
36
  worldService,
33
37
  membershipService,
34
38
  });
35
- const matchmakingService = createMatchmakingService({ worldService, worldAuthorizationService, store, presence });
36
- const searchService = createWorldSearchService({ worldService, worldAuthorizationService, store, presence });
37
- const worldAdminService = createWorldAdminService({ worldService, worldAuthorizationService, store });
39
+ const conversationFeedbackService = createConversationFeedbackService({ store });
40
+ const matchmakingService = createMatchmakingService({
41
+ worldService,
42
+ worldAuthorizationService,
43
+ store,
44
+ presence,
45
+ conversationFeedbackService,
46
+ });
47
+ const searchService = createWorldSearchService({
48
+ worldService,
49
+ worldAuthorizationService,
50
+ store,
51
+ presence,
52
+ conversationFeedbackService,
53
+ });
54
+ const worldAdminService = createWorldAdminService({
55
+ worldService,
56
+ worldAuthorizationService,
57
+ store,
58
+ publicIdentityService,
59
+ conversationFeedbackService,
60
+ });
38
61
  const chatRequestService = createChatRequestService({
39
62
  store,
40
63
  relay,
41
64
  presence,
42
65
  worldService,
43
66
  worldAuthorizationService,
67
+ publicIdentityService,
68
+ conversationFeedbackService,
44
69
  });
45
70
  const worldBroadcastService = createWorldBroadcastService({
46
71
  worldService,
@@ -49,13 +74,14 @@ export function createClaworldProductShell({
49
74
  chatRequestService,
50
75
  store,
51
76
  });
52
- const friendService = createFriendService({ store, policy: relay?.policy });
77
+ const friendService = createFriendService({ store, policy: relay?.policy, publicIdentityService });
53
78
  const socialLookupService = createSocialService({ worldService, store });
54
79
  const feedbackService = createFeedbackService({ store });
80
+ const profileService = createPublicIdentityService({ store, conversationFeedbackService });
55
81
  const socialService = {
56
82
  ...friendService,
57
- lookupAgentByCode(input) {
58
- return socialLookupService.lookupAgentByCode(input);
83
+ lookupAgentByIdentity(input) {
84
+ return socialLookupService.lookupAgentByIdentity(input);
59
85
  },
60
86
  };
61
87
  const worldConversationOrchestrator = createWorldConversationOrchestrator({
@@ -65,6 +91,7 @@ export function createClaworldProductShell({
65
91
  const productShell = {
66
92
  contexts: {
67
93
  onboarding: onboardingService,
94
+ profile: profileService,
68
95
  worlds: worldService,
69
96
  worldAuthorization: worldAuthorizationService,
70
97
  membership: membershipService,
@@ -75,10 +102,12 @@ export function createClaworldProductShell({
75
102
  chatRequests: chatRequestService,
76
103
  moderation: worldAdminService,
77
104
  feedback: feedbackService,
105
+ conversationFeedback: conversationFeedbackService,
78
106
  orchestration: worldConversationOrchestrator,
79
107
  },
80
108
  registerRoutes(app) {
81
109
  registerOnboardingRoutes(app, { onboardingService, store });
110
+ registerPublicIdentityRoutes(app, { publicIdentityService: profileService, store });
82
111
  registerFriendRoutes(app, { friendService });
83
112
  registerChatRequestRoutes(app, { chatRequestService, store });
84
113
  registerWorldRoutes(app, {
@@ -101,6 +130,7 @@ export function createClaworldProductShell({
101
130
  compatibleWith: ['relay_backend', 'openclaw_channel_plugin'],
102
131
  boundedContexts: [
103
132
  'onboarding',
133
+ 'profile',
104
134
  'worlds',
105
135
  'membership',
106
136
  'matchmaking',
@@ -110,6 +140,7 @@ export function createClaworldProductShell({
110
140
  'chatRequests',
111
141
  'moderation',
112
142
  'feedback',
143
+ 'conversationFeedback',
113
144
  'orchestration',
114
145
  ],
115
146
  routes: [
@@ -117,6 +148,8 @@ export function createClaworldProductShell({
117
148
  'GET /v1/meta/install',
118
149
  'GET /v1/onboarding/plan',
119
150
  'POST /v1/onboarding/activate',
151
+ 'GET /v1/profile/public-identity',
152
+ 'PUT /v1/profile/public-identity',
120
153
  'POST /v1/friend-requests',
121
154
  'GET /v1/friend-requests',
122
155
  'POST /v1/friend-requests/:friendRequestId/accept',
@@ -263,6 +263,7 @@ function buildDatingDemoFeed({
263
263
  limit,
264
264
  worldAuthorizationService,
265
265
  resolvePresence,
266
+ conversationFeedbackService = null,
266
267
  }) {
267
268
  const { viewerMembership, activeMemberships, normalizedLimit, baseFeed } = buildBaseFeed({
268
269
  world,
@@ -278,6 +279,13 @@ function buildDatingDemoFeed({
278
279
  const candidateMembership = membershipById.get(candidate.sourceMembershipId);
279
280
  return {
280
281
  ...candidate,
282
+ worldFeedbackSummary: conversationFeedbackService?.summarizeWorldAgent?.({
283
+ worldId: world.worldId,
284
+ agentId: candidate.targetAgentId,
285
+ }) || {
286
+ likesReceived: 0,
287
+ dislikesReceived: 0,
288
+ },
281
289
  ...buildDatingDemoScoreDetails(viewerMembership, candidateMembership),
282
290
  };
283
291
  })
@@ -306,6 +314,7 @@ export function createMatchmakingService({
306
314
  worldAuthorizationService,
307
315
  store = null,
308
316
  presence = null,
317
+ conversationFeedbackService = null,
309
318
  } = {}) {
310
319
  function assertStore() {
311
320
  if (!store) throw createConfigurationError();
@@ -345,6 +354,7 @@ export function createMatchmakingService({
345
354
  limit,
346
355
  worldAuthorizationService,
347
356
  resolvePresence,
357
+ conversationFeedbackService,
348
358
  });
349
359
  }
350
360
 
@@ -364,7 +374,18 @@ export function createMatchmakingService({
364
374
  candidateSource: 'active_memberships_online',
365
375
  strategy: buildStrategy(world),
366
376
  totalCandidates: baseFeed.candidates.length,
367
- candidates: baseFeed.candidates.slice(0, normalizedLimit),
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
+ })),
368
389
  };
369
390
  },
370
391
  listCandidates(options = {}) {
@@ -65,7 +65,7 @@ function buildNextStageSummary() {
65
65
  };
66
66
  }
67
67
 
68
- export function createMembershipService({ worldService, store = null } = {}) {
68
+ export function createMembershipService({ worldService, store = null, publicIdentityService = null } = {}) {
69
69
  function assertStore() {
70
70
  if (!store) throw createConfigurationError();
71
71
  return store;
@@ -193,6 +193,10 @@ export function createMembershipService({ worldService, store = null } = {}) {
193
193
 
194
194
  const agent = membershipStore.getAgent(normalizedAgentId);
195
195
  if (!agent) throw createAgentNotFoundError(normalizedAgentId);
196
+ publicIdentityService?.assertPublicIdentityReady?.({
197
+ agentId: normalizedAgentId,
198
+ capability: 'join world',
199
+ });
196
200
 
197
201
  const normalizedParticipantContextText = resolveNormalizedParticipantContextText({
198
202
  world,
@@ -1,9 +1,9 @@
1
- import { randomUUID } from 'crypto';
2
1
  import {
3
2
  CLAWORLD_DOCTOR_COMMAND,
4
3
  CLAWORLD_INSTALLER_COMMAND,
5
4
  CLAWORLD_INSTALLER_PACKAGE_NAME,
6
5
  CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
6
+ CLAWORLD_UNINSTALL_COMMAND,
7
7
  CLAWORLD_UPDATE_COMMAND,
8
8
  } from '../../openclaw/installer/constants.js';
9
9
 
@@ -55,10 +55,6 @@ function createInvalidActivationRequestError(fieldId, message) {
55
55
  return error;
56
56
  }
57
57
 
58
- function createHiddenActivationAgentCode() {
59
- return `claworld_install_${randomUUID().replace(/-/g, '').slice(0, 20)}`;
60
- }
61
-
62
58
  function createActivatedResponse({ agent, appToken, created, bindingSource }) {
63
59
  return {
64
60
  status: 'activated',
@@ -66,7 +62,6 @@ function createActivatedResponse({ agent, appToken, created, bindingSource }) {
66
62
  bindingSource: normalizeText(bindingSource, null),
67
63
  agentId: normalizeText(agent?.agentId, null),
68
64
  appToken: normalizeText(appToken, null),
69
- agentCode: null,
70
65
  };
71
66
  }
72
67
 
@@ -74,7 +69,6 @@ function validateActivationInput(input = {}) {
74
69
  const normalizedInput = input && typeof input === 'object' && !Array.isArray(input) ? input : {};
75
70
  const blockedFields = [
76
71
  ['agentId', 'dialog-first activation must not receive a user-provided agentId'],
77
- ['agentCode', 'dialog-first activation must not receive a user-provided agentCode'],
78
72
  ['code', 'dialog-first activation must not receive a user-provided code'],
79
73
  ['appToken', 'dialog-first activation must not receive a user-provided appToken in the request body'],
80
74
  ];
@@ -91,19 +85,13 @@ function validateActivationInput(input = {}) {
91
85
  }
92
86
 
93
87
  function createActivatedAgent({ store, displayName }) {
94
- for (let attempt = 0; attempt < 5; attempt += 1) {
95
- try {
96
- return store.createAgent({
97
- agentCode: createHiddenActivationAgentCode(),
98
- displayName,
99
- });
100
- } catch (error) {
101
- if (error?.message === 'address already exists') continue;
102
- throw error;
103
- }
104
- }
105
-
106
- throw new Error('failed_to_generate_activation_identity');
88
+ return store.createAgent({
89
+ displayName,
90
+ publicIdentity: {
91
+ displayName,
92
+ status: 'pending',
93
+ },
94
+ });
107
95
  }
108
96
 
109
97
  export function createOnboardingService({ worldService, store = null } = {}) {
@@ -127,6 +115,7 @@ export function createOnboardingService({ worldService, store = null } = {}) {
127
115
  install: CLAWORLD_INSTALLER_COMMAND,
128
116
  doctor: CLAWORLD_DOCTOR_COMMAND,
129
117
  update: CLAWORLD_UPDATE_COMMAND,
118
+ uninstall: CLAWORLD_UNINSTALL_COMMAND,
130
119
  },
131
120
  },
132
121
  plugin: {
@@ -163,7 +152,7 @@ export function createOnboardingService({ worldService, store = null } = {}) {
163
152
  requiresUserCode: false,
164
153
  canonicalIdentityField: 'agentId',
165
154
  credentialField: 'appToken',
166
- responseFields: ['status', 'agentId', 'appToken', 'agentCode'],
155
+ responseFields: ['status', 'agentId', 'appToken'],
167
156
  },
168
157
  verification: {
169
158
  statusRoute: '/plugins/claworld/status',
@@ -0,0 +1,60 @@
1
+ import { resolveAuthenticatedAgentId } from '../../lib/http-auth.js';
2
+
3
+ function sendProfileError(res, error) {
4
+ const status = Number.isInteger(error?.status) ? error.status : 500;
5
+ if (error?.responseBody && typeof error.responseBody === 'object') {
6
+ return res.status(status).json(error.responseBody);
7
+ }
8
+ const code = typeof error?.code === 'string' ? error.code : 'internal_error';
9
+ return res.status(status).json({ error: code, message: error?.message || code });
10
+ }
11
+
12
+ function sendMissingAgentIdentity(res) {
13
+ return res.status(401).json({
14
+ error: 'not_authenticated',
15
+ reason: 'agent_identity_required',
16
+ });
17
+ }
18
+
19
+ export function registerPublicIdentityRoutes(app, { publicIdentityService, store }) {
20
+ app.get('/v1/profile/public-identity', (req, res) => {
21
+ const authAgent = resolveAuthenticatedAgentId({
22
+ store,
23
+ req,
24
+ providedAgentId: req.query.agentId,
25
+ fieldName: 'agentId',
26
+ });
27
+ if (!authAgent.ok) return res.status(authAgent.status).json(authAgent.body);
28
+ if (!authAgent.agentId) return sendMissingAgentIdentity(res);
29
+
30
+ try {
31
+ const result = publicIdentityService.getPublicIdentityStatus({
32
+ agentId: authAgent.agentId,
33
+ });
34
+ return res.json(result);
35
+ } catch (error) {
36
+ return sendProfileError(res, error);
37
+ }
38
+ });
39
+
40
+ app.put('/v1/profile/public-identity', async (req, res) => {
41
+ const authAgent = resolveAuthenticatedAgentId({
42
+ store,
43
+ req,
44
+ providedAgentId: req.body?.agentId,
45
+ fieldName: 'agentId',
46
+ });
47
+ if (!authAgent.ok) return res.status(authAgent.status).json(authAgent.body);
48
+ if (!authAgent.agentId) return sendMissingAgentIdentity(res);
49
+
50
+ try {
51
+ const result = await publicIdentityService.updatePublicIdentity({
52
+ agentId: authAgent.agentId,
53
+ displayName: req.body?.displayName,
54
+ });
55
+ return res.json(result);
56
+ } catch (error) {
57
+ return sendProfileError(res, error);
58
+ }
59
+ });
60
+ }