@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.
- package/README.md +1 -1
- package/openclaw.plugin.json +7 -63
- package/package.json +6 -2
- package/skills/claworld-help/SKILL.md +7 -3
- package/skills/claworld-join-and-chat/SKILL.md +38 -9
- package/skills/claworld-manage-worlds/SKILL.md +81 -10
- package/src/lib/agent-profile.js +8 -3
- package/src/lib/chat-request.js +19 -1
- package/src/lib/policy.js +2 -6
- package/src/lib/public-identity.js +175 -0
- package/src/lib/relay/kickoff-text.js +7 -1
- package/src/openclaw/installer/cli.js +46 -1
- package/src/openclaw/installer/constants.js +1 -0
- package/src/openclaw/installer/core.js +234 -3
- package/src/openclaw/installer/doctor.js +2 -2
- package/src/openclaw/plugin/account-identity.js +1 -2
- package/src/openclaw/plugin/claworld-channel-plugin.js +302 -266
- package/src/openclaw/plugin/config-schema.js +9 -23
- package/src/openclaw/plugin/managed-config.js +284 -79
- package/src/openclaw/plugin/onboarding.js +22 -42
- package/src/openclaw/plugin/register.js +144 -25
- package/src/openclaw/plugin/relay-client.js +237 -18
- package/src/openclaw/runtime/backend-error-context.js +91 -0
- package/src/openclaw/runtime/feedback-helper.js +1 -2
- package/src/openclaw/runtime/product-shell-helper.js +43 -9
- package/src/openclaw/runtime/tool-contracts.js +65 -3
- package/src/openclaw/runtime/tool-inventory.js +8 -1
- package/src/openclaw/runtime/world-moderation-helper.js +3 -19
- package/src/product-shell/contracts/candidate-feed.js +7 -0
- package/src/product-shell/contracts/world-manifest.js +0 -1
- package/src/product-shell/contracts/world-orchestration.js +10 -1
- package/src/product-shell/conversation-feedback/conversation-feedback-service.js +261 -0
- package/src/product-shell/feedback/feedback-routes.js +0 -1
- package/src/product-shell/feedback/feedback-service.js +4 -9
- package/src/product-shell/index.js +40 -7
- package/src/product-shell/matching/matchmaking-service.js +22 -1
- package/src/product-shell/membership/membership-service.js +5 -1
- package/src/product-shell/onboarding/onboarding-service.js +10 -21
- package/src/product-shell/profile/public-identity-routes.js +60 -0
- package/src/product-shell/profile/public-identity-service.js +190 -0
- package/src/product-shell/search/search-service.js +9 -2
- package/src/product-shell/social/chat-request-routes.js +4 -1
- package/src/product-shell/social/chat-request-service.js +184 -22
- package/src/product-shell/social/friend-routes.js +1 -1
- package/src/product-shell/social/friend-service.js +16 -19
- package/src/product-shell/social/social-routes.js +2 -2
- package/src/product-shell/social/social-service.js +31 -35
- package/src/product-shell/worlds/world-admin-service.js +31 -10
- package/src/product-shell/worlds/world-broadcast-service.js +2 -2
- 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,
|
|
@@ -564,7 +584,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
564
584
|
config,
|
|
565
585
|
agentId,
|
|
566
586
|
credential = null,
|
|
567
|
-
clientVersion = 'claworld-plugin/0.
|
|
587
|
+
clientVersion = 'claworld-plugin/0.2.8',
|
|
568
588
|
sessionTarget,
|
|
569
589
|
fallbackTarget,
|
|
570
590
|
} = {}) {
|
|
@@ -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,27 +1317,20 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1096
1317
|
}
|
|
1097
1318
|
}
|
|
1098
1319
|
|
|
1099
|
-
async createChatRequest({ fromAgentId,
|
|
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
|
|
1327
|
+
targetAgentId,
|
|
1117
1328
|
kickoffBrief: normalized.kickoffBrief || null,
|
|
1118
1329
|
openingMessage: normalized.openingMessage || null,
|
|
1119
1330
|
worldId: normalized.conversation?.worldId || null,
|
|
1331
|
+
requestContext: requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
|
|
1332
|
+
? requestContext
|
|
1333
|
+
: undefined,
|
|
1120
1334
|
}),
|
|
1121
1335
|
}, {
|
|
1122
1336
|
code: 'relay_request_create_failed',
|
|
@@ -1161,16 +1375,21 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1161
1375
|
});
|
|
1162
1376
|
}
|
|
1163
1377
|
|
|
1164
|
-
async deliverMessage({ fromAgentId,
|
|
1165
|
-
|
|
1378
|
+
async deliverMessage({ fromAgentId, targetAgentId, clientMessageId = null, payload = {}, conversation = {} } = {}) {
|
|
1379
|
+
const resolvedClientMessageId = resolveClientMessageId(clientMessageId);
|
|
1380
|
+
const result = await this.requestJson('/v1/messages', {
|
|
1166
1381
|
method: 'POST',
|
|
1167
1382
|
headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
|
|
1168
|
-
body: JSON.stringify({ fromAgentId,
|
|
1383
|
+
body: JSON.stringify({ fromAgentId, targetAgentId, clientMessageId: resolvedClientMessageId, payload, conversation }),
|
|
1169
1384
|
}, {
|
|
1170
1385
|
code: 'relay_message_delivery_failed',
|
|
1171
1386
|
message: 'failed to deliver relay message',
|
|
1172
1387
|
publicMessage: 'failed to deliver relay message',
|
|
1173
1388
|
});
|
|
1389
|
+
return {
|
|
1390
|
+
...result,
|
|
1391
|
+
clientMessageId: resolvedClientMessageId,
|
|
1392
|
+
};
|
|
1174
1393
|
}
|
|
1175
1394
|
|
|
1176
1395
|
waitFor(eventNameOrPredicate, timeoutMs = 8000) {
|
|
@@ -1198,7 +1417,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1198
1417
|
});
|
|
1199
1418
|
}
|
|
1200
1419
|
|
|
1201
|
-
async establishConversation({ fromAgentId,
|
|
1420
|
+
async establishConversation({ fromAgentId, targetAgentId, requestContext = {}, openingPayload = {} } = {}) {
|
|
1202
1421
|
const normalizedRequestContext = requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
|
|
1203
1422
|
? { ...requestContext }
|
|
1204
1423
|
: {};
|
|
@@ -1214,7 +1433,7 @@ export class ClaworldRelayClient extends EventEmitter {
|
|
|
1214
1433
|
|
|
1215
1434
|
const requestResult = await this.createChatRequest({
|
|
1216
1435
|
fromAgentId,
|
|
1217
|
-
|
|
1436
|
+
targetAgentId,
|
|
1218
1437
|
requestContext: normalizedRequestContext,
|
|
1219
1438
|
});
|
|
1220
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1171
|
+
throw createProductShellHttpError('world_directory', worlds, {
|
|
1172
|
+
accountId: resolvedRuntimeConfig.accountId || accountId || null,
|
|
1173
|
+
});
|
|
1140
1174
|
}
|
|
1141
1175
|
|
|
1142
1176
|
return buildPostSetupWorldDirectory(worlds.body, {
|