@xfxstudio/claworld 0.2.9 → 0.2.10-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +48 -4
  13. package/src/openclaw/installer/constants.js +1 -0
  14. package/src/openclaw/installer/core.js +247 -71
  15. package/src/openclaw/installer/doctor.js +31 -17
  16. package/src/openclaw/plugin/account-identity.js +1 -2
  17. package/src/openclaw/plugin/claworld-channel-plugin.js +453 -263
  18. package/src/openclaw/plugin/config-schema.js +9 -23
  19. package/src/openclaw/plugin/managed-config.js +294 -84
  20. package/src/openclaw/plugin/onboarding.js +37 -45
  21. package/src/openclaw/plugin/register.js +124 -13
  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 +16 -26
  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
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import WebSocket from 'ws';
3
+ import { v4 as uuidv4 } from 'uuid';
3
4
  import { resolveClaworldRuntimeConfig } from './config-schema.js';
4
5
  import { buildRuntimeAuthHeaders } from './account-identity.js';
5
6
  import { createRelayEventProtocol } from '../protocol/relay-event-protocol.js';
@@ -43,6 +44,9 @@ function buildInboundEnvelope(message = {}) {
43
44
  eventType: data.eventType || 'delivery',
44
45
  deliveryId: data.deliveryId || null,
45
46
  sessionKey: data.sessionKey || null,
47
+ createdAt: data.createdAt || null,
48
+ updatedAt: data.updatedAt || null,
49
+ turnCreatedAt: data.turnCreatedAt || null,
46
50
  payload: data.payload && typeof data.payload === 'object' && !Array.isArray(data.payload)
47
51
  ? { ...data.payload }
48
52
  : {},
@@ -56,6 +60,10 @@ function normalizeOptionalText(value) {
56
60
  return normalized || null;
57
61
  }
58
62
 
63
+ function resolveClientMessageId(value = null) {
64
+ return normalizeOptionalText(value) || `cmsg_${uuidv4()}`;
65
+ }
66
+
59
67
  function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
60
68
  return createRuntimeBoundaryError({
61
69
  code: 'relay_reply_ack_timeout',
@@ -68,6 +76,18 @@ function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {})
68
76
  });
69
77
  }
70
78
 
79
+ function buildAcceptedAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
80
+ return createRuntimeBoundaryError({
81
+ code: 'relay_delivery_accept_ack_timeout',
82
+ category: 'transport',
83
+ status: 504,
84
+ message: `timed out waiting for relay delivery acceptance acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
85
+ publicMessage: 'relay delivery acceptance acknowledgement timed out',
86
+ recoverable: true,
87
+ context,
88
+ });
89
+ }
90
+
71
91
  function buildReplyFallbackError({
72
92
  deliveryId,
73
93
  status,
@@ -655,6 +675,34 @@ export class ClaworldRelayClient extends EventEmitter {
655
675
  this.send({ type: 'heartbeat' });
656
676
  }
657
677
 
678
+ sendAccepted({ deliveryId, sessionKey, source = 'runtime_dispatch' } = {}) {
679
+ const normalizedDeliveryId = normalizeOptionalText(deliveryId);
680
+ if (!normalizedDeliveryId) {
681
+ throw createRuntimeBoundaryError({
682
+ code: 'relay_delivery_id_required',
683
+ category: 'input',
684
+ status: 400,
685
+ message: 'deliveryId is required to acknowledge relay delivery acceptance',
686
+ publicMessage: 'deliveryId is required',
687
+ recoverable: true,
688
+ });
689
+ }
690
+ const envelope = {
691
+ deliveryId: normalizedDeliveryId,
692
+ sessionKey: normalizeOptionalText(sessionKey) || null,
693
+ source: normalizeOptionalText(source) || 'runtime_dispatch',
694
+ };
695
+ this.send({
696
+ type: 'accepted',
697
+ deliveryId: envelope.deliveryId,
698
+ sessionKey: envelope.sessionKey,
699
+ payload: {
700
+ source: envelope.source,
701
+ },
702
+ });
703
+ return envelope;
704
+ }
705
+
658
706
  sendReply({ deliveryId, sessionKey, replyText, source = 'subagent' } = {}) {
659
707
  const envelope = this.outbound.createReplyEnvelope({
660
708
  deliveryId,
@@ -791,6 +839,85 @@ export class ClaworldRelayClient extends EventEmitter {
791
839
  });
792
840
  }
793
841
 
842
+ waitForAcceptedAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
843
+ const normalizedDeliveryId = normalizeOptionalText(deliveryId);
844
+ if (!normalizedDeliveryId) {
845
+ return Promise.reject(createRuntimeBoundaryError({
846
+ code: 'relay_delivery_id_required',
847
+ category: 'input',
848
+ status: 400,
849
+ message: 'deliveryId is required to wait for relay delivery acceptance acknowledgement',
850
+ publicMessage: 'deliveryId is required',
851
+ recoverable: true,
852
+ }));
853
+ }
854
+
855
+ return new Promise((resolve, reject) => {
856
+ let settled = false;
857
+ let timeout = null;
858
+
859
+ const cleanup = () => {
860
+ if (timeout) clearTimeout(timeout);
861
+ this.off('delivery.accepted', onAccepted);
862
+ this.off('disconnect', onDisconnect);
863
+ this.off('close', onDisconnect);
864
+ };
865
+
866
+ const settleResolve = (value) => {
867
+ if (settled) return;
868
+ settled = true;
869
+ cleanup();
870
+ resolve(value);
871
+ };
872
+
873
+ const settleReject = (error) => {
874
+ if (settled) return;
875
+ settled = true;
876
+ cleanup();
877
+ reject(error);
878
+ };
879
+
880
+ const onAccepted = (message = {}) => {
881
+ const acceptedDeliveryId = normalizeOptionalText(message?.data?.acceptedDeliveryId);
882
+ if (acceptedDeliveryId !== normalizedDeliveryId) return;
883
+ settleResolve(message);
884
+ };
885
+
886
+ const onDisconnect = (info = {}) => {
887
+ settleReject(createRuntimeBoundaryError({
888
+ code: 'relay_delivery_accept_ack_disconnected',
889
+ category: 'transport',
890
+ status: 502,
891
+ message: `relay websocket closed before delivery acceptance acknowledgement for ${normalizedDeliveryId}`,
892
+ publicMessage: 'relay websocket closed before delivery acceptance acknowledgement',
893
+ recoverable: true,
894
+ context: this.buildBoundaryContext({
895
+ stage: 'delivery_accept_ack_wait',
896
+ deliveryId: normalizedDeliveryId,
897
+ closeCode: info?.code ?? null,
898
+ closeReason: info?.reason || null,
899
+ }),
900
+ }));
901
+ };
902
+
903
+ this.on('delivery.accepted', onAccepted);
904
+ this.on('disconnect', onDisconnect);
905
+ this.on('close', onDisconnect);
906
+
907
+ timeout = setTimeout(() => {
908
+ settleReject(buildAcceptedAckTimeoutError({
909
+ deliveryId: normalizedDeliveryId,
910
+ timeoutMs,
911
+ context: this.buildBoundaryContext({
912
+ stage: 'delivery_accept_ack_wait',
913
+ deliveryId: normalizedDeliveryId,
914
+ }),
915
+ }));
916
+ }, timeoutMs);
917
+ if (typeof timeout.unref === 'function') timeout.unref();
918
+ });
919
+ }
920
+
794
921
  waitForKeepSilentAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
795
922
  const normalizedDeliveryId = normalizeOptionalText(deliveryId);
796
923
  if (!normalizedDeliveryId) {
@@ -897,6 +1024,31 @@ export class ClaworldRelayClient extends EventEmitter {
897
1024
  };
898
1025
  }
899
1026
 
1027
+ async acceptDeliveryHttp({ deliveryId, sessionKey = null, source = 'runtime_dispatch' } = {}) {
1028
+ const normalizedDeliveryId = normalizeOptionalText(deliveryId);
1029
+ const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(normalizedDeliveryId)}/accepted`, {
1030
+ method: 'POST',
1031
+ headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
1032
+ body: JSON.stringify({
1033
+ fromAgentId: this.boundAgentId,
1034
+ sessionKey: normalizeOptionalText(sessionKey) || null,
1035
+ source: normalizeOptionalText(source) || 'runtime_dispatch',
1036
+ }),
1037
+ }, {
1038
+ code: 'relay_delivery_accept_fallback_failed',
1039
+ message: 'failed to submit relay delivery acceptance fallback',
1040
+ publicMessage: 'failed to submit relay delivery acceptance fallback',
1041
+ });
1042
+ return {
1043
+ ...result,
1044
+ envelope: {
1045
+ deliveryId: normalizedDeliveryId,
1046
+ sessionKey: normalizeOptionalText(sessionKey) || null,
1047
+ source: normalizeOptionalText(source) || 'runtime_dispatch',
1048
+ },
1049
+ };
1050
+ }
1051
+
900
1052
  async sendReplyAndWaitForAck({
901
1053
  deliveryId,
902
1054
  sessionKey,
@@ -984,6 +1136,75 @@ export class ClaworldRelayClient extends EventEmitter {
984
1136
  }
985
1137
  }
986
1138
 
1139
+ async sendAcceptedAndWaitForAck({
1140
+ deliveryId,
1141
+ sessionKey,
1142
+ source = 'runtime_dispatch',
1143
+ timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
1144
+ httpFallback = true,
1145
+ } = {}) {
1146
+ const envelope = this.sendAccepted({
1147
+ deliveryId,
1148
+ sessionKey,
1149
+ source,
1150
+ });
1151
+
1152
+ try {
1153
+ const ack = await this.waitForAcceptedAck({
1154
+ deliveryId: envelope.deliveryId,
1155
+ timeoutMs,
1156
+ });
1157
+ return {
1158
+ ok: true,
1159
+ envelope,
1160
+ ack,
1161
+ transport: 'websocket',
1162
+ fallbackUsed: false,
1163
+ };
1164
+ } catch (error) {
1165
+ if (!httpFallback) throw error;
1166
+
1167
+ this.logger.warn?.('[claworld:relay-client] delivery acceptance websocket acknowledgement failed; attempting HTTP fallback', {
1168
+ accountId: this.runtimeConfig?.accountId || null,
1169
+ agentId: this.boundAgentId,
1170
+ deliveryId: envelope.deliveryId,
1171
+ sessionKey: envelope.sessionKey,
1172
+ error: error?.message || String(error),
1173
+ });
1174
+
1175
+ const fallbackResult = await this.acceptDeliveryHttp({
1176
+ deliveryId: envelope.deliveryId,
1177
+ sessionKey: envelope.sessionKey,
1178
+ source: envelope.source,
1179
+ });
1180
+
1181
+ if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
1182
+ return {
1183
+ ok: true,
1184
+ envelope,
1185
+ ack: {
1186
+ event: 'delivery.accepted',
1187
+ data: fallbackResult.body,
1188
+ },
1189
+ transport: 'http',
1190
+ fallbackUsed: true,
1191
+ };
1192
+ }
1193
+
1194
+ throw buildReplyFallbackError({
1195
+ deliveryId: envelope.deliveryId,
1196
+ status: fallbackResult.status,
1197
+ body: fallbackResult.body,
1198
+ context: this.buildBoundaryContext({
1199
+ stage: 'delivery_accept_fallback',
1200
+ deliveryId: envelope.deliveryId,
1201
+ sessionKey: envelope.sessionKey,
1202
+ fallbackFrom: error?.code || error?.message || null,
1203
+ }),
1204
+ });
1205
+ }
1206
+ }
1207
+
987
1208
  async keepDeliverySilentHttp({ deliveryId, reason = null, source = 'openclaw-autochain' } = {}) {
988
1209
  const normalizedDeliveryId = normalizeOptionalText(deliveryId);
989
1210
  const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(normalizedDeliveryId)}/kept-silent`, {
@@ -1096,24 +1317,14 @@ export class ClaworldRelayClient extends EventEmitter {
1096
1317
  }
1097
1318
  }
1098
1319
 
1099
- async createChatRequest({ fromAgentId, toAddress, requestContext = {} } = {}) {
1100
- const target = await this.requestJson(`/v1/agents/resolve?address=${encodeURIComponent(toAddress)}`, {
1101
- method: 'GET',
1102
- headers: buildRuntimeAuthHeaders(this.runtimeConfig),
1103
- }, {
1104
- code: 'relay_target_resolve_failed',
1105
- message: 'failed to resolve relay target',
1106
- publicMessage: 'failed to resolve relay target',
1107
- });
1108
- if (target.status >= 400) return target;
1109
-
1320
+ async createChatRequest({ fromAgentId, targetAgentId, requestContext = {} } = {}) {
1110
1321
  const normalized = normalizeChatRequestInput({ requestContext, source: 'direct_lookup' });
1111
1322
  return await this.requestJson('/v1/chat-requests', {
1112
1323
  method: 'POST',
1113
1324
  headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
1114
1325
  body: JSON.stringify({
1115
1326
  fromAgentId,
1116
- targetAgentId: target.body?.agentId || target.body?.id || null,
1327
+ targetAgentId,
1117
1328
  kickoffBrief: normalized.kickoffBrief || null,
1118
1329
  openingMessage: normalized.openingMessage || null,
1119
1330
  worldId: normalized.conversation?.worldId || null,
@@ -1164,16 +1375,21 @@ export class ClaworldRelayClient extends EventEmitter {
1164
1375
  });
1165
1376
  }
1166
1377
 
1167
- async deliverMessage({ fromAgentId, toAddress, payload = {}, conversation = {} } = {}) {
1168
- return await this.requestJson('/v1/messages', {
1378
+ async deliverMessage({ fromAgentId, targetAgentId, clientMessageId = null, payload = {}, conversation = {} } = {}) {
1379
+ const resolvedClientMessageId = resolveClientMessageId(clientMessageId);
1380
+ const result = await this.requestJson('/v1/messages', {
1169
1381
  method: 'POST',
1170
1382
  headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
1171
- body: JSON.stringify({ fromAgentId, toAddress, payload, conversation }),
1383
+ body: JSON.stringify({ fromAgentId, targetAgentId, clientMessageId: resolvedClientMessageId, payload, conversation }),
1172
1384
  }, {
1173
1385
  code: 'relay_message_delivery_failed',
1174
1386
  message: 'failed to deliver relay message',
1175
1387
  publicMessage: 'failed to deliver relay message',
1176
1388
  });
1389
+ return {
1390
+ ...result,
1391
+ clientMessageId: resolvedClientMessageId,
1392
+ };
1177
1393
  }
1178
1394
 
1179
1395
  waitFor(eventNameOrPredicate, timeoutMs = 8000) {
@@ -1201,7 +1417,7 @@ export class ClaworldRelayClient extends EventEmitter {
1201
1417
  });
1202
1418
  }
1203
1419
 
1204
- async establishConversation({ fromAgentId, toAddress, requestContext = {}, openingPayload = {} } = {}) {
1420
+ async establishConversation({ fromAgentId, targetAgentId, requestContext = {}, openingPayload = {} } = {}) {
1205
1421
  const normalizedRequestContext = requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
1206
1422
  ? { ...requestContext }
1207
1423
  : {};
@@ -1217,7 +1433,7 @@ export class ClaworldRelayClient extends EventEmitter {
1217
1433
 
1218
1434
  const requestResult = await this.createChatRequest({
1219
1435
  fromAgentId,
1220
- toAddress,
1436
+ targetAgentId,
1221
1437
  requestContext: normalizedRequestContext,
1222
1438
  });
1223
1439
  if (requestResult.status !== 201) {
@@ -0,0 +1,91 @@
1
+ function normalizeText(value, fallback = null) {
2
+ if (value == null) return fallback;
3
+ const normalized = String(value).trim();
4
+ return normalized || fallback;
5
+ }
6
+
7
+ function normalizeFieldError(fieldError = {}) {
8
+ const fieldId = normalizeText(fieldError.fieldId, null);
9
+ const message = normalizeText(fieldError.message, null);
10
+ const code = normalizeText(fieldError.code, null);
11
+ if (!fieldId && !message && !code) return null;
12
+ return {
13
+ ...(fieldId ? { fieldId } : {}),
14
+ ...(message ? { message } : {}),
15
+ ...(code ? { code } : {}),
16
+ };
17
+ }
18
+
19
+ function normalizeMissingField(field = {}) {
20
+ const fieldId = normalizeText(field.fieldId, null);
21
+ const label = normalizeText(field.label, null);
22
+ const description = normalizeText(field.description, null);
23
+ const message = normalizeText(field.message, null);
24
+ const code = normalizeText(field.code, null);
25
+ if (!fieldId && !label && !description && !message && !code) return null;
26
+ return {
27
+ ...(fieldId ? { fieldId } : {}),
28
+ ...(label ? { label } : {}),
29
+ ...(description ? { description } : {}),
30
+ ...(message ? { message } : {}),
31
+ ...(code ? { code } : {}),
32
+ };
33
+ }
34
+
35
+ function normalizePublicIdentity(publicIdentity = {}) {
36
+ const status = normalizeText(publicIdentity.status, null);
37
+ const displayName = normalizeText(publicIdentity.displayName, null);
38
+ const code = normalizeText(publicIdentity.code, null);
39
+ const displayIdentity = normalizeText(publicIdentity.displayIdentity, null);
40
+ const confirmedAt = normalizeText(publicIdentity.confirmedAt, null);
41
+ const updatedAt = normalizeText(publicIdentity.updatedAt, null);
42
+ if (!status && !displayName && !code && !displayIdentity && !confirmedAt && !updatedAt) return null;
43
+ return {
44
+ ...(status ? { status } : {}),
45
+ ...(displayName ? { displayName } : {}),
46
+ ...(code ? { code } : {}),
47
+ ...(displayIdentity ? { displayIdentity } : {}),
48
+ ...(confirmedAt ? { confirmedAt } : {}),
49
+ ...(updatedAt ? { updatedAt } : {}),
50
+ };
51
+ }
52
+
53
+ export function normalizeBackendFieldError(fieldError = {}) {
54
+ return normalizeFieldError(fieldError);
55
+ }
56
+
57
+ export function normalizeBackendMissingField(field = {}) {
58
+ return normalizeMissingField(field);
59
+ }
60
+
61
+ export function normalizeBackendPublicIdentity(publicIdentity = {}) {
62
+ return normalizePublicIdentity(publicIdentity);
63
+ }
64
+
65
+ export function extractBackendErrorContext(payload = {}) {
66
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return {};
67
+
68
+ const backendCode = normalizeText(payload.error, null);
69
+ const backendMessage = normalizeText(payload.message, null);
70
+ const requiredAction = normalizeText(payload.requiredAction, null);
71
+ const nextAction = normalizeText(payload.nextAction, null);
72
+ const nextTool = normalizeText(payload.nextTool, null);
73
+ const fieldErrors = Array.isArray(payload.fieldErrors)
74
+ ? payload.fieldErrors.map((fieldError) => normalizeFieldError(fieldError)).filter(Boolean)
75
+ : [];
76
+ const missingFields = Array.isArray(payload.missingFields)
77
+ ? payload.missingFields.map((field) => normalizeMissingField(field)).filter(Boolean)
78
+ : [];
79
+ const publicIdentity = normalizePublicIdentity(payload.publicIdentity);
80
+
81
+ return {
82
+ ...(backendCode ? { backendCode } : {}),
83
+ ...(backendMessage ? { backendMessage } : {}),
84
+ ...(requiredAction ? { requiredAction } : {}),
85
+ ...(nextAction ? { nextAction } : {}),
86
+ ...(nextTool ? { nextTool } : {}),
87
+ ...(fieldErrors.length > 0 ? { fieldErrors } : {}),
88
+ ...(missingFields.length > 0 ? { missingFields } : {}),
89
+ ...(publicIdentity ? { publicIdentity } : {}),
90
+ };
91
+ }
@@ -114,7 +114,6 @@ export async function submitFeedbackReport({
114
114
  turnId: normalizeText(normalizedContext.turnId, null),
115
115
  deliveryId: normalizeText(normalizedContext.deliveryId, null),
116
116
  targetAgentId: normalizeText(normalizedContext.targetAgentId, null),
117
- targetAgentCode: normalizeText(normalizedContext.targetAgentCode, null),
118
117
  tags: normalizeStringList(normalizedContext.tags),
119
118
  metadata: normalizeObject(normalizedContext.metadata),
120
119
  },
@@ -128,7 +127,7 @@ export async function submitFeedbackReport({
128
127
  accountId: normalizeText(resolvedRuntimeConfig.accountId, normalizeText(accountId, null)),
129
128
  serverUrl: baseUrl,
130
129
  relayAgentId: resolvedAgentId,
131
- defaultToAddress: normalizeText(resolvedRuntimeConfig.relay?.defaultToAddress, null),
130
+ defaultTargetAgentId: normalizeText(resolvedRuntimeConfig.relay?.defaultTargetAgentId, null),
132
131
  },
133
132
  }),
134
133
  });
@@ -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
  import {
5
6
  buildCandidateDeliverySummary as buildBackendCandidateDeliverySummary,
6
7
  buildWorldSelectionPrompt as buildBackendWorldSelectionPrompt,
@@ -337,6 +338,15 @@ function normalizeCandidate(candidate = {}, index = 0) {
337
338
  ? candidate.compatibilitySignals.map((signal, signalIndex) => normalizeCompatibilitySignal(signal, signalIndex))
338
339
  : [],
339
340
  deliveryReason: normalizeDeliveryReason(candidate.deliveryReason),
341
+ worldFeedbackSummary: candidate.worldFeedbackSummary && typeof candidate.worldFeedbackSummary === 'object'
342
+ ? {
343
+ likesReceived: normalizeInteger(candidate.worldFeedbackSummary.likesReceived, 0),
344
+ dislikesReceived: normalizeInteger(candidate.worldFeedbackSummary.dislikesReceived, 0),
345
+ }
346
+ : {
347
+ likesReceived: 0,
348
+ dislikesReceived: 0,
349
+ },
340
350
  expiresAt: normalizeText(candidate.expiresAt, null),
341
351
  joinedAt: normalizeText(candidate.joinedAt, null),
342
352
  rank: normalizedRank == null ? null : Math.max(1, Math.trunc(normalizedRank)),
@@ -437,7 +447,6 @@ function normalizeWorldSearchItem(item = {}, index = 0) {
437
447
  worldId: normalizeText(item.worldId, 'unknown-world'),
438
448
  displayName: normalizeText(item.displayName, `Player ${index + 1}`),
439
449
  headline: normalizeText(item.headline, null),
440
- address: normalizeText(item.address, null),
441
450
  online: item.online === true,
442
451
  connectedAt: normalizeText(item.connectedAt, null),
443
452
  lastHeartbeatAt: normalizeText(item.lastHeartbeatAt, null),
@@ -450,6 +459,15 @@ function normalizeWorldSearchItem(item = {}, index = 0) {
450
459
  reasonSummary: normalizeText(item.reasonSummary, ''),
451
460
  joinedAt: normalizeText(item.joinedAt, null),
452
461
  profileSummary: normalizeCandidateProfileSummary(item.profileSummary),
462
+ worldFeedbackSummary: item.worldFeedbackSummary && typeof item.worldFeedbackSummary === 'object'
463
+ ? {
464
+ likesReceived: normalizeInteger(item.worldFeedbackSummary.likesReceived, 0),
465
+ dislikesReceived: normalizeInteger(item.worldFeedbackSummary.dislikesReceived, 0),
466
+ }
467
+ : {
468
+ likesReceived: 0,
469
+ dislikesReceived: 0,
470
+ },
453
471
  };
454
472
  }
455
473
 
@@ -686,8 +704,7 @@ function createProductShellHttpError(action, response, { accountId = null, world
686
704
  accountId,
687
705
  ...(worldId ? { worldId } : {}),
688
706
  httpStatus: response?.status ?? 500,
689
- backendCode,
690
- backendMessage,
707
+ ...extractBackendErrorContext(response?.body),
691
708
  },
692
709
  });
693
710
  }
@@ -776,7 +793,10 @@ export async function fetchWorldDetail({
776
793
  accountId: resolvedRuntimeConfig.accountId || accountId || null,
777
794
  body: detail.body,
778
795
  });
779
- throw new Error(`claworld product-shell world detail fetch failed: ${detail.status}`);
796
+ throw createProductShellHttpError('world_detail', detail, {
797
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
798
+ worldId: resolvedWorldId,
799
+ });
780
800
  }
781
801
 
782
802
  return normalizeWorldDetail(detail.body);
@@ -830,7 +850,10 @@ export async function joinWorld({
830
850
  accountId: resolvedRuntimeConfig.accountId || accountId || null,
831
851
  body: joinResult.body,
832
852
  });
833
- throw new Error(`claworld product-shell world join failed: ${joinResult.status}`);
853
+ throw createProductShellHttpError('world_join', joinResult, {
854
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
855
+ worldId: resolvedWorldId,
856
+ });
834
857
  }
835
858
 
836
859
  return normalizeWorldJoinResponse(joinResult.body, {
@@ -890,7 +913,10 @@ export async function fetchWorldSearch({
890
913
  accountId: resolvedRuntimeConfig.accountId || accountId || null,
891
914
  body: searchResult.body,
892
915
  });
893
- throw new Error(`claworld product-shell world search failed: ${searchResult.status}`);
916
+ throw createProductShellHttpError('world_search', searchResult, {
917
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
918
+ worldId: resolvedWorldId,
919
+ });
894
920
  }
895
921
 
896
922
  return normalizeWorldSearchResponse(searchResult.body, {
@@ -950,7 +976,10 @@ export async function submitWorldSearch({
950
976
  accountId: resolvedRuntimeConfig.accountId || accountId || null,
951
977
  body: searchResult.body,
952
978
  });
953
- throw new Error(`claworld product-shell world search failed: ${searchResult.status}`);
979
+ throw createProductShellHttpError('world_search', searchResult, {
980
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
981
+ worldId: resolvedWorldId,
982
+ });
954
983
  }
955
984
 
956
985
  if (searchResult.status === 409) {
@@ -1095,7 +1124,10 @@ export async function fetchWorldCandidateFeed({
1095
1124
  accountId: resolvedRuntimeConfig.accountId || accountId || null,
1096
1125
  body: candidateFeed.body,
1097
1126
  });
1098
- throw new Error(`claworld product-shell candidate feed failed: ${candidateFeed.status}`);
1127
+ throw createProductShellHttpError('world_candidate_feed', candidateFeed, {
1128
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
1129
+ worldId: resolvedWorldId,
1130
+ });
1099
1131
  }
1100
1132
 
1101
1133
  return normalizeCandidateFeedResponse(candidateFeed.body, {
@@ -1136,7 +1168,9 @@ export async function resolveWorldSelectionFlow({
1136
1168
  accountId: resolvedRuntimeConfig.accountId || accountId || null,
1137
1169
  body: worlds.body,
1138
1170
  });
1139
- throw new Error(`claworld product-shell world fetch failed: ${worlds.status}`);
1171
+ throw createProductShellHttpError('world_directory', worlds, {
1172
+ accountId: resolvedRuntimeConfig.accountId || accountId || null,
1173
+ });
1140
1174
  }
1141
1175
 
1142
1176
  return buildPostSetupWorldDirectory(worlds.body, {
@@ -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,
@@ -635,6 +657,7 @@ function projectChatInboxChatItem(chat = {}) {
635
657
  localSessionKey: normalizeText(chat.localSessionKey, normalizeText(chat.sessionKey, null)),
636
658
  counterparty: projectToolAgentSummary(chat.counterparty),
637
659
  conversation: normalizeConversationScopeDetails(chat.conversation),
660
+ feedbackSummary: projectConversationFeedbackSummary(chat.feedbackSummary),
638
661
  };
639
662
  }
640
663
 
@@ -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,