@xfxstudio/claworld 0.2.8 → 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 (50) 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 +7 -3
  5. package/skills/claworld-join-and-chat/SKILL.md +38 -9
  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 +19 -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 +7 -1
  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 +302 -266
  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 +144 -25
  22. package/src/openclaw/plugin/relay-client.js +237 -18
  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 +65 -3
  27. package/src/openclaw/runtime/tool-inventory.js +8 -1
  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-routes.js +4 -1
  43. package/src/product-shell/social/chat-request-service.js +184 -22
  44. package/src/product-shell/social/friend-routes.js +1 -1
  45. package/src/product-shell/social/friend-service.js +16 -19
  46. package/src/product-shell/social/social-routes.js +2 -2
  47. package/src/product-shell/social/social-service.js +31 -35
  48. package/src/product-shell/worlds/world-admin-service.js +31 -10
  49. package/src/product-shell/worlds/world-broadcast-service.js +2 -2
  50. package/src/lib/agent-address.js +0 -46
@@ -85,6 +85,26 @@ function projectWorldStats(stats = null) {
85
85
  totalParticipants: normalizeOptionalInteger(stats.totalParticipants, null),
86
86
  activeParticipants: normalizeOptionalInteger(stats.activeParticipants, null),
87
87
  totalConversationCount: normalizeOptionalInteger(stats.totalConversationCount, null),
88
+ totalLikes: normalizeOptionalInteger(stats.totalLikes, null),
89
+ totalDislikes: normalizeOptionalInteger(stats.totalDislikes, null),
90
+ };
91
+ }
92
+
93
+ function projectWorldFeedbackSummary(summary = null) {
94
+ if (!summary || typeof summary !== 'object' || Array.isArray(summary)) return null;
95
+ return {
96
+ likesReceived: normalizeInteger(summary.likesReceived, 0),
97
+ dislikesReceived: normalizeInteger(summary.dislikesReceived, 0),
98
+ };
99
+ }
100
+
101
+ function projectConversationFeedbackSummary(summary = null) {
102
+ if (!summary || typeof summary !== 'object' || Array.isArray(summary)) return null;
103
+ return {
104
+ likeCount: normalizeInteger(summary.likeCount, 0),
105
+ dislikeCount: normalizeInteger(summary.dislikeCount, 0),
106
+ viewerGave: normalizeText(summary.viewerGave, null),
107
+ viewerReceived: normalizeText(summary.viewerReceived, null),
88
108
  };
89
109
  }
90
110
 
@@ -254,6 +274,7 @@ function projectToolCandidateSummary(summary = {}, index = 0) {
254
274
  score: normalizeInteger(summary.score, 0) || null,
255
275
  summary: normalizeText(summary.summary, null),
256
276
  expiresAt: normalizeText(summary.expiresAt, null),
277
+ worldFeedbackSummary: projectWorldFeedbackSummary(summary.worldFeedbackSummary),
257
278
  };
258
279
  }
259
280
 
@@ -277,6 +298,7 @@ function projectToolCandidateFeed(joinResult = {}) {
277
298
  score: candidate.score,
278
299
  summary: normalizeText(candidate.deliveryReason?.summary, null),
279
300
  expiresAt: candidate.expiresAt,
301
+ worldFeedbackSummary: candidate.worldFeedbackSummary,
280
302
  }, index))
281
303
  : [];
282
304
 
@@ -354,6 +376,7 @@ export function projectToolSearchWorldResponse(searchResult = {}, { accountId =
354
376
  matchReasons: projectSearchMatchReasons(item),
355
377
  profileSnippet: summarizeProfileSnippet(item.profileSummary),
356
378
  online: item.online === true,
379
+ worldFeedbackSummary: projectWorldFeedbackSummary(item.worldFeedbackSummary),
357
380
  }))
358
381
  : [];
359
382
 
@@ -482,7 +505,7 @@ export function projectToolFeedbackSubmissionResponse(result = {}) {
482
505
  title: normalizeText(feedback.title, null),
483
506
  accountId: normalizeText(feedback.accountId, null),
484
507
  reporterAgentId: normalizeText(reporter.agentId, null),
485
- reporterAgentCode: normalizeText(reporter.agentCode, null),
508
+ reporterIdentity: normalizeText(reporter.publicIdentity?.displayIdentity, null),
486
509
  worldId: normalizeText(context.worldId, null),
487
510
  conversationKey: normalizeText(context.conversationKey, null),
488
511
  turnId: normalizeText(context.turnId, null),
@@ -503,9 +526,8 @@ function projectToolAgentSummary(agent = {}) {
503
526
  if (!agent || typeof agent !== 'object') return null;
504
527
  return {
505
528
  agentId: normalizeText(agent.agentId, null),
506
- agentCode: normalizeText(agent.agentCode, null),
507
- address: normalizeText(agent.address, null),
508
529
  displayName: normalizeText(agent.displayName, null),
530
+ identity: normalizeText(agent.publicIdentity?.displayIdentity, null),
509
531
  online: agent.online === true,
510
532
  discoverable: typeof agent.discoverable === 'boolean' ? agent.discoverable : null,
511
533
  contactable: typeof agent.contactable === 'boolean' ? agent.contactable : null,
@@ -622,6 +644,23 @@ function projectChatRequestItem(request = {}) {
622
644
  };
623
645
  }
624
646
 
647
+ function projectChatInboxChatItem(chat = {}) {
648
+ if (!chat || typeof chat !== 'object' || Array.isArray(chat)) return null;
649
+ return {
650
+ chatRequestId: normalizeText(chat.chatRequestId, null),
651
+ status: normalizeText(chat.status, null),
652
+ direction: normalizeText(chat.direction, null),
653
+ createdAt: normalizeText(chat.createdAt, null),
654
+ updatedAt: normalizeText(chat.updatedAt, null),
655
+ lastTurnAt: normalizeText(chat.lastTurnAt, null),
656
+ conversationKey: normalizeText(chat.conversationKey, null),
657
+ localSessionKey: normalizeText(chat.localSessionKey, normalizeText(chat.sessionKey, null)),
658
+ counterparty: projectToolAgentSummary(chat.counterparty),
659
+ conversation: normalizeConversationScopeDetails(chat.conversation),
660
+ feedbackSummary: projectConversationFeedbackSummary(chat.feedbackSummary),
661
+ };
662
+ }
663
+
625
664
  export function projectToolFriendRequestMutationResponse(result = {}, { accountId = null } = {}) {
626
665
  return {
627
666
  status: result.alreadyFriends === true
@@ -690,3 +729,26 @@ export function projectToolChatRequestListResponse(result = {}, { accountId = nu
690
729
  items,
691
730
  };
692
731
  }
732
+
733
+ export function projectToolChatInboxResponse(result = {}, { accountId = null } = {}) {
734
+ const pendingRequests = Array.isArray(result.pendingRequests)
735
+ ? result.pendingRequests.map((request) => projectChatRequestItem(request)).filter(Boolean)
736
+ : [];
737
+ const chats = Array.isArray(result.chats)
738
+ ? result.chats.map((chat) => projectChatInboxChatItem(chat)).filter(Boolean)
739
+ : [];
740
+ return {
741
+ accountId: normalizeText(accountId, null),
742
+ counts: result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
743
+ ? {
744
+ pendingRequestCount: normalizeInteger(result.counts.pendingRequestCount, pendingRequests.length),
745
+ chatCount: normalizeInteger(result.counts.chatCount, chats.length),
746
+ }
747
+ : {
748
+ pendingRequestCount: pendingRequests.length,
749
+ chatCount: chats.length,
750
+ },
751
+ pendingRequests,
752
+ chats,
753
+ };
754
+ }
@@ -2,7 +2,7 @@ export const CLAWORLD_TOOL_CONTRACT_VERSION = 'v1';
2
2
 
3
3
  export const CLAWORLD_CHAT_REQUEST_TOOL_NAMES = Object.freeze([
4
4
  'claworld_request_chat',
5
- 'claworld_list_chat_requests',
5
+ 'claworld_chat_inbox',
6
6
  'claworld_accept_chat_request',
7
7
  'claworld_reject_chat_request',
8
8
  ]);
@@ -11,6 +11,11 @@ export const CLAWORLD_BOOTSTRAP_TOOL_NAMES = Object.freeze([
11
11
  'claworld_pair_agent',
12
12
  ]);
13
13
 
14
+ export const CLAWORLD_PROFILE_TOOL_NAMES = Object.freeze([
15
+ 'claworld_get_public_identity',
16
+ 'claworld_update_public_identity',
17
+ ]);
18
+
14
19
  export const CLAWORLD_FEEDBACK_TOOL_NAMES = Object.freeze([
15
20
  'claworld_submit_feedback',
16
21
  ]);
@@ -43,6 +48,7 @@ export const CLAWORLD_RETIRED_PUBLIC_TOOL_NAMES = Object.freeze([
43
48
 
44
49
  export const CLAWORLD_REGISTERED_TOOL_NAMES = Object.freeze([
45
50
  ...CLAWORLD_BOOTSTRAP_TOOL_NAMES,
51
+ ...CLAWORLD_PROFILE_TOOL_NAMES,
46
52
  ...CLAWORLD_WORLD_TOOL_NAMES,
47
53
  ...CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES,
48
54
  ...CLAWORLD_CHAT_REQUEST_TOOL_NAMES,
@@ -67,6 +73,7 @@ export const CLAWORLD_READ_ONLY_OPENCLAW_TOOL_NAMES = Object.freeze([
67
73
 
68
74
  export const CLAWORLD_PLUGIN_SMOKE_REQUIRED_TOOL_NAMES = Object.freeze([
69
75
  ...CLAWORLD_BOOTSTRAP_TOOL_NAMES,
76
+ ...CLAWORLD_PROFILE_TOOL_NAMES,
70
77
  ...CLAWORLD_WORLD_TOOL_NAMES,
71
78
  ...CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES,
72
79
  ...CLAWORLD_CHAT_REQUEST_TOOL_NAMES,
@@ -1,6 +1,7 @@
1
1
  import { resolveClaworldRuntimeConfig } from '../plugin/config-schema.js';
2
2
  import { buildRuntimeAuthHeaders } from '../plugin/account-identity.js';
3
3
  import { createRuntimeBoundaryError } from '../../lib/runtime-errors.js';
4
+ import { extractBackendErrorContext } from './backend-error-context.js';
4
5
 
5
6
  function normalizeText(value, fallback = null) {
6
7
  if (value == null) return fallback;
@@ -36,18 +37,6 @@ function normalizeStringList(values = []) {
36
37
  return [...new Set(values.map((value) => normalizeText(value, null)).filter(Boolean))];
37
38
  }
38
39
 
39
- function normalizeFieldError(fieldError = {}) {
40
- const fieldId = normalizeText(fieldError.fieldId, null);
41
- const message = normalizeText(fieldError.message, null);
42
- const code = normalizeText(fieldError.code, null);
43
- if (!fieldId && !message && !code) return null;
44
- return {
45
- ...(fieldId ? { fieldId } : {}),
46
- ...(message ? { message } : {}),
47
- ...(code ? { code } : {}),
48
- };
49
- }
50
-
51
40
  function normalizeParticipantContextField(field = {}, index = 0) {
52
41
  return {
53
42
  fieldId: normalizeText(field.fieldId, `field_${index + 1}`),
@@ -160,9 +149,6 @@ function inferHttpErrorCategory(status) {
160
149
  function createModerationHttpError(action, response, { accountId = null, worldId = null } = {}) {
161
150
  const backendCode = normalizeText(response?.body?.error, null);
162
151
  const backendMessage = normalizeText(response?.body?.message, `claworld world ${action} failed`);
163
- const fieldErrors = Array.isArray(response?.body?.fieldErrors)
164
- ? response.body.fieldErrors.map((fieldError) => normalizeFieldError(fieldError)).filter(Boolean)
165
- : [];
166
152
 
167
153
  return createRuntimeBoundaryError({
168
154
  code: backendCode || `claworld_world_${action}_failed`,
@@ -176,9 +162,7 @@ function createModerationHttpError(action, response, { accountId = null, worldId
176
162
  accountId,
177
163
  ...(worldId ? { worldId } : {}),
178
164
  httpStatus: response?.status ?? 500,
179
- backendCode,
180
- backendMessage,
181
- ...(fieldErrors.length > 0 ? { fieldErrors } : {}),
165
+ ...extractBackendErrorContext(response?.body),
182
166
  },
183
167
  });
184
168
  }
@@ -190,7 +174,7 @@ export async function createModeratedWorld({
190
174
  agentId = null,
191
175
  displayName = null,
192
176
  worldContextText = null,
193
- enabled = false,
177
+ enabled = true,
194
178
  fetchImpl,
195
179
  logger = console,
196
180
  } = {}) {
@@ -12,9 +12,15 @@ export const CANDIDATE_OBJECT_FIELDS = Object.freeze([
12
12
  'profileSummary',
13
13
  'compatibilitySignals',
14
14
  'deliveryReason',
15
+ 'worldFeedbackSummary',
15
16
  'expiresAt',
16
17
  ]);
17
18
 
19
+ export const WORLD_FEEDBACK_SUMMARY_FIELDS = Object.freeze([
20
+ 'likesReceived',
21
+ 'dislikesReceived',
22
+ ]);
23
+
18
24
  export const DATING_DEMO_SCORING_FIELDS = Object.freeze([
19
25
  'joinedAt',
20
26
  'rank',
@@ -282,6 +288,7 @@ export function projectCandidateFeedModel(world) {
282
288
  profileSummaryFieldShape: PROFILE_SUMMARY_FIELD_FIELDS,
283
289
  compatibilitySignalFields: COMPATIBILITY_SIGNAL_FIELDS,
284
290
  deliveryReasonFields: DELIVERY_REASON_FIELDS,
291
+ worldFeedbackSummaryFields: WORLD_FEEDBACK_SUMMARY_FIELDS,
285
292
  requestChatAction: {
286
293
  action: 'request_chat',
287
294
  requiredFields: ['worldId', 'targetAgentId'],
@@ -124,7 +124,6 @@ function normalizeSearchSchema(searchSchema = {}, joinSchema = {}) {
124
124
  'worldId',
125
125
  'displayName',
126
126
  'headline',
127
- 'address',
128
127
  'online',
129
128
  'matchedFieldIds',
130
129
  'score',
@@ -390,7 +390,12 @@ function summarizeProfileFields(fields = []) {
390
390
  }
391
391
 
392
392
  function buildCandidateDeliverySummaryLine(candidateSummary = {}, index = 0) {
393
- return `${index + 1}. ${candidateSummary.summary}`;
393
+ const likesReceived = Number(candidateSummary.worldFeedbackSummary?.likesReceived || 0);
394
+ const dislikesReceived = Number(candidateSummary.worldFeedbackSummary?.dislikesReceived || 0);
395
+ const feedbackLine = likesReceived > 0 || dislikesReceived > 0
396
+ ? ` World feedback in this world: ${likesReceived} like${likesReceived === 1 ? '' : 's'}, ${dislikesReceived} dislike${dislikesReceived === 1 ? '' : 's'}.`
397
+ : '';
398
+ return `${index + 1}. ${candidateSummary.summary}${feedbackLine}`;
394
399
  }
395
400
 
396
401
  function formatConversationOverview(detail = {}) {
@@ -697,6 +702,10 @@ export function buildCandidateDeliverySummary(candidateFeed = {}, { worldDetail
697
702
  optionalFieldSummary,
698
703
  compatibilitySummary,
699
704
  deliveryReasonSummary: deliveryReasonSummary || null,
705
+ worldFeedbackSummary: candidate.worldFeedbackSummary || {
706
+ likesReceived: 0,
707
+ dislikesReceived: 0,
708
+ },
700
709
  expiresAt: candidate.expiresAt,
701
710
  summary,
702
711
  };
@@ -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
  },