@xfxstudio/claworld 0.2.5 → 0.2.7

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 (29) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/skills/claworld-help/SKILL.md +2 -2
  4. package/skills/claworld-join-and-chat/SKILL.md +18 -9
  5. package/src/lib/chat-request.js +19 -0
  6. package/src/lib/relay/kickoff-text.js +6 -1
  7. package/src/openclaw/installer/core.js +16 -2
  8. package/src/openclaw/plugin/claworld-channel-plugin.js +164 -12
  9. package/src/openclaw/plugin/config-schema.js +9 -1
  10. package/src/openclaw/plugin/register.js +151 -15
  11. package/src/openclaw/plugin/relay-client.js +502 -1
  12. package/src/openclaw/runtime/demo-session-bootstrap.js +1 -2
  13. package/src/openclaw/runtime/tool-contracts.js +40 -1
  14. package/src/openclaw/runtime/tool-inventory.js +3 -3
  15. package/src/openclaw/runtime/world-moderation-helper.js +9 -13
  16. package/src/product-shell/catalog/default-world-catalog.js +12 -258
  17. package/src/product-shell/contracts/world-manifest.js +0 -38
  18. package/src/product-shell/contracts/world-orchestration.js +0 -6
  19. package/src/product-shell/index.js +1 -5
  20. package/src/product-shell/membership/membership-service.js +24 -6
  21. package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -2
  22. package/src/product-shell/orchestration/world-conversation-text.js +0 -2
  23. package/src/product-shell/social/chat-request-routes.js +24 -1
  24. package/src/product-shell/social/chat-request-service.js +185 -15
  25. package/src/product-shell/worlds/world-admin-service.js +28 -120
  26. package/src/product-shell/worlds/world-authorization.js +20 -17
  27. package/src/product-shell/worlds/world-broadcast-service.js +2 -5
  28. package/src/product-shell/worlds/world-text.js +0 -2
  29. package/src/product-shell/results/result-service.js +0 -21
@@ -15,6 +15,7 @@ import {
15
15
  const DUPLICATE_CONNECTION_CLOSE_CODE = 4001;
16
16
  const STALE_CONNECTION_CLOSE_CODE = 4002;
17
17
  const TERMINAL_CLOSE_REASONS = new Set(['duplicate_connection_replaced', 'stale_connection']);
18
+ const DEFAULT_REPLY_ACK_TIMEOUT_MS = 5000;
18
19
 
19
20
  function normalizeRelayWebSocketUrl(serverUrl) {
20
21
  const parsed = new URL(serverUrl);
@@ -49,6 +50,90 @@ function buildInboundEnvelope(message = {}) {
49
50
  };
50
51
  }
51
52
 
53
+ function normalizeOptionalText(value) {
54
+ if (value == null) return null;
55
+ const normalized = String(value).trim();
56
+ return normalized || null;
57
+ }
58
+
59
+ function buildReplyAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
60
+ return createRuntimeBoundaryError({
61
+ code: 'relay_reply_ack_timeout',
62
+ category: 'transport',
63
+ status: 504,
64
+ message: `timed out waiting for relay reply acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
65
+ publicMessage: 'relay reply acknowledgement timed out',
66
+ recoverable: true,
67
+ context,
68
+ });
69
+ }
70
+
71
+ function buildReplyFallbackError({
72
+ deliveryId,
73
+ status,
74
+ body,
75
+ context = {},
76
+ } = {}) {
77
+ return createRuntimeBoundaryError({
78
+ code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_reply_fallback_failed',
79
+ category: status >= 500 ? 'runtime' : 'transport',
80
+ status: Number.isInteger(status) ? status : 502,
81
+ message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay reply fallback failed',
82
+ publicMessage: 'relay reply fallback failed',
83
+ recoverable: status >= 500,
84
+ context: {
85
+ deliveryId: normalizeOptionalText(deliveryId),
86
+ ...context,
87
+ },
88
+ });
89
+ }
90
+
91
+ function buildKeepSilentAckTimeoutError({ deliveryId, timeoutMs, context = {} } = {}) {
92
+ return createRuntimeBoundaryError({
93
+ code: 'relay_kept_silent_ack_timeout',
94
+ category: 'transport',
95
+ status: 504,
96
+ message: `timed out waiting for relay kept_silent acknowledgement for ${deliveryId || 'unknown-delivery'} after ${timeoutMs}ms`,
97
+ publicMessage: 'relay kept_silent acknowledgement timed out',
98
+ recoverable: true,
99
+ context,
100
+ });
101
+ }
102
+
103
+ function buildKeepSilentFallbackError({
104
+ deliveryId,
105
+ status,
106
+ body,
107
+ context = {},
108
+ } = {}) {
109
+ return createRuntimeBoundaryError({
110
+ code: normalizeOptionalText(body?.code) || normalizeOptionalText(body?.error) || 'relay_kept_silent_fallback_failed',
111
+ category: status >= 500 ? 'runtime' : 'transport',
112
+ status: Number.isInteger(status) ? status : 502,
113
+ message: normalizeOptionalText(body?.message) || normalizeOptionalText(body?.reason) || 'relay kept_silent fallback failed',
114
+ publicMessage: 'relay kept_silent fallback failed',
115
+ recoverable: status >= 500,
116
+ context: {
117
+ deliveryId: normalizeOptionalText(deliveryId),
118
+ ...context,
119
+ },
120
+ });
121
+ }
122
+
123
+ function isReplyAlreadyApplied(result = null, deliveryId = null) {
124
+ if (!result || result.status !== 409) return false;
125
+ if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
126
+ if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
127
+ return normalizeOptionalText(result.body?.delivery?.status) === 'replied';
128
+ }
129
+
130
+ function isDeliveryKeptSilentAlreadyApplied(result = null, deliveryId = null) {
131
+ if (!result || result.status !== 409) return false;
132
+ if (normalizeOptionalText(result.body?.reason) !== 'delivery_not_replyable') return false;
133
+ if (normalizeOptionalText(result.body?.delivery?.deliveryId) !== normalizeOptionalText(deliveryId)) return false;
134
+ return normalizeOptionalText(result.body?.delivery?.status) === 'kept_silent';
135
+ }
136
+
52
137
  export class ClaworldRelayClient extends EventEmitter {
53
138
  constructor({
54
139
  logger = console,
@@ -479,7 +564,7 @@ export class ClaworldRelayClient extends EventEmitter {
479
564
  config,
480
565
  agentId,
481
566
  credential = null,
482
- clientVersion = 'claworld-plugin/0.1.5',
567
+ clientVersion = 'claworld-plugin/0.2.7',
483
568
  sessionTarget,
484
569
  fallbackTarget,
485
570
  } = {}) {
@@ -597,6 +682,419 @@ export class ClaworldRelayClient extends EventEmitter {
597
682
  });
598
683
  }
599
684
 
685
+ sendKeepSilent({ deliveryId, sessionKey, reason = null, source = 'openclaw-autochain' } = {}) {
686
+ const normalizedDeliveryId = normalizeOptionalText(deliveryId);
687
+ if (!normalizedDeliveryId) {
688
+ throw createRuntimeBoundaryError({
689
+ code: 'relay_delivery_id_required',
690
+ category: 'input',
691
+ status: 400,
692
+ message: 'deliveryId is required to mark relay delivery kept_silent',
693
+ publicMessage: 'deliveryId is required',
694
+ recoverable: true,
695
+ });
696
+ }
697
+ const envelope = {
698
+ deliveryId: normalizedDeliveryId,
699
+ sessionKey: normalizeOptionalText(sessionKey) || null,
700
+ reason: normalizeOptionalText(reason) || 'no_renderable_reply',
701
+ source: normalizeOptionalText(source) || 'openclaw-autochain',
702
+ };
703
+ this.send({
704
+ type: 'kept_silent',
705
+ deliveryId: envelope.deliveryId,
706
+ sessionKey: envelope.sessionKey,
707
+ payload: {
708
+ reason: envelope.reason,
709
+ source: envelope.source,
710
+ },
711
+ });
712
+ return envelope;
713
+ }
714
+
715
+ waitForReplyAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
716
+ const normalizedDeliveryId = normalizeOptionalText(deliveryId);
717
+ if (!normalizedDeliveryId) {
718
+ return Promise.reject(createRuntimeBoundaryError({
719
+ code: 'relay_delivery_id_required',
720
+ category: 'input',
721
+ status: 400,
722
+ message: 'deliveryId is required to wait for relay reply acknowledgement',
723
+ publicMessage: 'deliveryId is required',
724
+ recoverable: true,
725
+ }));
726
+ }
727
+
728
+ return new Promise((resolve, reject) => {
729
+ let settled = false;
730
+ let timeout = null;
731
+
732
+ const cleanup = () => {
733
+ if (timeout) clearTimeout(timeout);
734
+ this.off('reply.accepted', onReplyAccepted);
735
+ this.off('disconnect', onDisconnect);
736
+ this.off('close', onDisconnect);
737
+ };
738
+
739
+ const settleResolve = (value) => {
740
+ if (settled) return;
741
+ settled = true;
742
+ cleanup();
743
+ resolve(value);
744
+ };
745
+
746
+ const settleReject = (error) => {
747
+ if (settled) return;
748
+ settled = true;
749
+ cleanup();
750
+ reject(error);
751
+ };
752
+
753
+ const onReplyAccepted = (message = {}) => {
754
+ const repliedDeliveryId = normalizeOptionalText(message?.data?.repliedDeliveryId);
755
+ if (repliedDeliveryId !== normalizedDeliveryId) return;
756
+ settleResolve(message);
757
+ };
758
+
759
+ const onDisconnect = (info = {}) => {
760
+ settleReject(createRuntimeBoundaryError({
761
+ code: 'relay_reply_ack_disconnected',
762
+ category: 'transport',
763
+ status: 502,
764
+ message: `relay websocket closed before reply acknowledgement for ${normalizedDeliveryId}`,
765
+ publicMessage: 'relay websocket closed before reply acknowledgement',
766
+ recoverable: true,
767
+ context: this.buildBoundaryContext({
768
+ stage: 'reply_ack_wait',
769
+ deliveryId: normalizedDeliveryId,
770
+ closeCode: info?.code ?? null,
771
+ closeReason: info?.reason || null,
772
+ }),
773
+ }));
774
+ };
775
+
776
+ this.on('reply.accepted', onReplyAccepted);
777
+ this.on('disconnect', onDisconnect);
778
+ this.on('close', onDisconnect);
779
+
780
+ timeout = setTimeout(() => {
781
+ settleReject(buildReplyAckTimeoutError({
782
+ deliveryId: normalizedDeliveryId,
783
+ timeoutMs,
784
+ context: this.buildBoundaryContext({
785
+ stage: 'reply_ack_wait',
786
+ deliveryId: normalizedDeliveryId,
787
+ }),
788
+ }));
789
+ }, timeoutMs);
790
+ if (typeof timeout.unref === 'function') timeout.unref();
791
+ });
792
+ }
793
+
794
+ waitForKeepSilentAck({ deliveryId, timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS } = {}) {
795
+ const normalizedDeliveryId = normalizeOptionalText(deliveryId);
796
+ if (!normalizedDeliveryId) {
797
+ return Promise.reject(createRuntimeBoundaryError({
798
+ code: 'relay_delivery_id_required',
799
+ category: 'input',
800
+ status: 400,
801
+ message: 'deliveryId is required to wait for relay kept_silent acknowledgement',
802
+ publicMessage: 'deliveryId is required',
803
+ recoverable: true,
804
+ }));
805
+ }
806
+
807
+ return new Promise((resolve, reject) => {
808
+ let settled = false;
809
+ let timeout = null;
810
+
811
+ const cleanup = () => {
812
+ if (timeout) clearTimeout(timeout);
813
+ this.off('kept_silent.accepted', onKeptSilentAccepted);
814
+ this.off('disconnect', onDisconnect);
815
+ this.off('close', onDisconnect);
816
+ };
817
+
818
+ const settleResolve = (value) => {
819
+ if (settled) return;
820
+ settled = true;
821
+ cleanup();
822
+ resolve(value);
823
+ };
824
+
825
+ const settleReject = (error) => {
826
+ if (settled) return;
827
+ settled = true;
828
+ cleanup();
829
+ reject(error);
830
+ };
831
+
832
+ const onKeptSilentAccepted = (message = {}) => {
833
+ const keptSilentDeliveryId = normalizeOptionalText(message?.data?.keptSilentDeliveryId);
834
+ if (keptSilentDeliveryId !== normalizedDeliveryId) return;
835
+ settleResolve(message);
836
+ };
837
+
838
+ const onDisconnect = (info = {}) => {
839
+ settleReject(createRuntimeBoundaryError({
840
+ code: 'relay_kept_silent_ack_disconnected',
841
+ category: 'transport',
842
+ status: 502,
843
+ message: `relay websocket closed before kept_silent acknowledgement for ${normalizedDeliveryId}`,
844
+ publicMessage: 'relay websocket closed before kept_silent acknowledgement',
845
+ recoverable: true,
846
+ context: this.buildBoundaryContext({
847
+ stage: 'kept_silent_ack_wait',
848
+ deliveryId: normalizedDeliveryId,
849
+ closeCode: info?.code ?? null,
850
+ closeReason: info?.reason || null,
851
+ }),
852
+ }));
853
+ };
854
+
855
+ this.on('kept_silent.accepted', onKeptSilentAccepted);
856
+ this.on('disconnect', onDisconnect);
857
+ this.on('close', onDisconnect);
858
+
859
+ timeout = setTimeout(() => {
860
+ settleReject(buildKeepSilentAckTimeoutError({
861
+ deliveryId: normalizedDeliveryId,
862
+ timeoutMs,
863
+ context: this.buildBoundaryContext({
864
+ stage: 'kept_silent_ack_wait',
865
+ deliveryId: normalizedDeliveryId,
866
+ }),
867
+ }));
868
+ }, timeoutMs);
869
+ if (typeof timeout.unref === 'function') timeout.unref();
870
+ });
871
+ }
872
+
873
+ async replyToDeliveryHttp({ deliveryId, replyText, source = 'subagent' } = {}) {
874
+ const envelope = this.outbound.createReplyEnvelope({
875
+ deliveryId,
876
+ sessionKey: null,
877
+ replyText,
878
+ source,
879
+ });
880
+ const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(envelope.deliveryId)}/reply`, {
881
+ method: 'POST',
882
+ headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
883
+ body: JSON.stringify({
884
+ fromAgentId: this.boundAgentId,
885
+ payload: {
886
+ ...envelope.payload,
887
+ },
888
+ }),
889
+ }, {
890
+ code: 'relay_reply_fallback_failed',
891
+ message: 'failed to submit relay reply fallback',
892
+ publicMessage: 'failed to submit relay reply fallback',
893
+ });
894
+ return {
895
+ ...result,
896
+ envelope,
897
+ };
898
+ }
899
+
900
+ async sendReplyAndWaitForAck({
901
+ deliveryId,
902
+ sessionKey,
903
+ replyText,
904
+ source = 'subagent',
905
+ timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
906
+ httpFallback = true,
907
+ } = {}) {
908
+ const envelope = this.sendReply({
909
+ deliveryId,
910
+ sessionKey,
911
+ replyText,
912
+ source,
913
+ });
914
+
915
+ try {
916
+ const ack = await this.waitForReplyAck({
917
+ deliveryId: envelope.deliveryId,
918
+ timeoutMs,
919
+ });
920
+ return {
921
+ ok: true,
922
+ envelope,
923
+ ack,
924
+ transport: 'websocket',
925
+ fallbackUsed: false,
926
+ };
927
+ } catch (error) {
928
+ if (!httpFallback) throw error;
929
+
930
+ this.logger.warn?.('[claworld:relay-client] reply websocket acknowledgement failed; attempting HTTP fallback', {
931
+ accountId: this.runtimeConfig?.accountId || null,
932
+ agentId: this.boundAgentId,
933
+ deliveryId: envelope.deliveryId,
934
+ sessionKey: envelope.sessionKey,
935
+ error: error?.message || String(error),
936
+ });
937
+
938
+ const fallbackResult = await this.replyToDeliveryHttp({
939
+ deliveryId: envelope.deliveryId,
940
+ replyText,
941
+ source,
942
+ });
943
+
944
+ if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
945
+ return {
946
+ ok: true,
947
+ envelope,
948
+ ack: {
949
+ event: 'reply.accepted',
950
+ data: fallbackResult.body,
951
+ },
952
+ transport: 'http',
953
+ fallbackUsed: true,
954
+ };
955
+ }
956
+
957
+ if (isReplyAlreadyApplied(fallbackResult, envelope.deliveryId)) {
958
+ return {
959
+ ok: true,
960
+ envelope,
961
+ ack: {
962
+ event: 'reply.accepted',
963
+ data: {
964
+ ...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
965
+ repliedDeliveryId: envelope.deliveryId,
966
+ },
967
+ },
968
+ transport: 'http-already-applied',
969
+ fallbackUsed: true,
970
+ };
971
+ }
972
+
973
+ throw buildReplyFallbackError({
974
+ deliveryId: envelope.deliveryId,
975
+ status: fallbackResult.status,
976
+ body: fallbackResult.body,
977
+ context: this.buildBoundaryContext({
978
+ stage: 'reply_fallback',
979
+ deliveryId: envelope.deliveryId,
980
+ sessionKey: envelope.sessionKey,
981
+ }),
982
+ });
983
+ }
984
+ }
985
+
986
+ async keepDeliverySilentHttp({ deliveryId, reason = null, source = 'openclaw-autochain' } = {}) {
987
+ const normalizedDeliveryId = normalizeOptionalText(deliveryId);
988
+ const result = await this.requestJson(`/v1/deliveries/${encodeURIComponent(normalizedDeliveryId)}/kept-silent`, {
989
+ method: 'POST',
990
+ headers: buildRuntimeAuthHeaders(this.runtimeConfig, { 'content-type': 'application/json' }),
991
+ body: JSON.stringify({
992
+ fromAgentId: this.boundAgentId,
993
+ reason: normalizeOptionalText(reason) || 'no_renderable_reply',
994
+ source: normalizeOptionalText(source) || 'openclaw-autochain',
995
+ }),
996
+ }, {
997
+ code: 'relay_kept_silent_fallback_failed',
998
+ message: 'failed to submit relay kept_silent fallback',
999
+ publicMessage: 'failed to submit relay kept_silent fallback',
1000
+ });
1001
+ return {
1002
+ ...result,
1003
+ envelope: {
1004
+ deliveryId: normalizedDeliveryId,
1005
+ reason: normalizeOptionalText(reason) || 'no_renderable_reply',
1006
+ source: normalizeOptionalText(source) || 'openclaw-autochain',
1007
+ },
1008
+ };
1009
+ }
1010
+
1011
+ async sendKeepSilentAndWaitForAck({
1012
+ deliveryId,
1013
+ sessionKey,
1014
+ reason = null,
1015
+ source = 'openclaw-autochain',
1016
+ timeoutMs = DEFAULT_REPLY_ACK_TIMEOUT_MS,
1017
+ httpFallback = true,
1018
+ } = {}) {
1019
+ const envelope = this.sendKeepSilent({
1020
+ deliveryId,
1021
+ sessionKey,
1022
+ reason,
1023
+ source,
1024
+ });
1025
+
1026
+ try {
1027
+ const ack = await this.waitForKeepSilentAck({
1028
+ deliveryId: envelope.deliveryId,
1029
+ timeoutMs,
1030
+ });
1031
+ return {
1032
+ ok: true,
1033
+ envelope,
1034
+ ack,
1035
+ transport: 'websocket',
1036
+ fallbackUsed: false,
1037
+ };
1038
+ } catch (error) {
1039
+ if (!httpFallback) throw error;
1040
+
1041
+ this.logger.warn?.('[claworld:relay-client] kept_silent websocket acknowledgement failed; attempting HTTP fallback', {
1042
+ accountId: this.runtimeConfig?.accountId || null,
1043
+ agentId: this.boundAgentId,
1044
+ deliveryId: envelope.deliveryId,
1045
+ sessionKey: envelope.sessionKey,
1046
+ error: error?.message || String(error),
1047
+ });
1048
+
1049
+ const fallbackResult = await this.keepDeliverySilentHttp({
1050
+ deliveryId: envelope.deliveryId,
1051
+ reason: envelope.reason,
1052
+ source: envelope.source,
1053
+ });
1054
+
1055
+ if (fallbackResult.status >= 200 && fallbackResult.status < 300) {
1056
+ return {
1057
+ ok: true,
1058
+ envelope,
1059
+ ack: {
1060
+ event: 'kept_silent.accepted',
1061
+ data: fallbackResult.body,
1062
+ },
1063
+ transport: 'http',
1064
+ fallbackUsed: true,
1065
+ };
1066
+ }
1067
+
1068
+ if (isDeliveryKeptSilentAlreadyApplied(fallbackResult, envelope.deliveryId)) {
1069
+ return {
1070
+ ok: true,
1071
+ envelope,
1072
+ ack: {
1073
+ event: 'kept_silent.accepted',
1074
+ data: {
1075
+ ...(fallbackResult.body && typeof fallbackResult.body === 'object' ? fallbackResult.body : {}),
1076
+ keptSilentDeliveryId: envelope.deliveryId,
1077
+ },
1078
+ },
1079
+ transport: 'http-already-applied',
1080
+ fallbackUsed: true,
1081
+ };
1082
+ }
1083
+
1084
+ throw buildKeepSilentFallbackError({
1085
+ deliveryId: envelope.deliveryId,
1086
+ status: fallbackResult.status,
1087
+ body: fallbackResult.body,
1088
+ context: this.buildBoundaryContext({
1089
+ stage: 'kept_silent_fallback',
1090
+ deliveryId: envelope.deliveryId,
1091
+ sessionKey: envelope.sessionKey,
1092
+ fallbackFrom: error?.code || error?.message || null,
1093
+ }),
1094
+ });
1095
+ }
1096
+ }
1097
+
600
1098
  async createChatRequest({ fromAgentId, toAddress, requestContext = {} } = {}) {
601
1099
  const target = await this.requestJson(`/v1/agents/resolve?address=${encodeURIComponent(toAddress)}`, {
602
1100
  method: 'GET',
@@ -618,6 +1116,9 @@ export class ClaworldRelayClient extends EventEmitter {
618
1116
  kickoffBrief: normalized.kickoffBrief || null,
619
1117
  openingMessage: normalized.openingMessage || null,
620
1118
  worldId: normalized.conversation?.worldId || null,
1119
+ requestContext: requestContext && typeof requestContext === 'object' && !Array.isArray(requestContext)
1120
+ ? requestContext
1121
+ : undefined,
621
1122
  }),
622
1123
  }, {
623
1124
  code: 'relay_request_create_failed',
@@ -23,8 +23,7 @@ export function createDemoSessionBootstrap() {
23
23
  'load demo world',
24
24
  'create manual session',
25
25
  'inject opening system message',
26
- 'wait for dual raise or manual stop',
27
- 'build canonical result',
26
+ 'wait for conversation closure or manual stop',
28
27
  ],
29
28
  status: 'planned',
30
29
  };
@@ -76,7 +76,7 @@ function projectParticipantContextField(field = null) {
76
76
 
77
77
  function projectWorldRole(worldRole, fallback = null) {
78
78
  const normalized = normalizeText(worldRole, fallback);
79
- return ['owner', 'admin', 'member'].includes(normalized) ? normalized : fallback;
79
+ return ['owner', 'member'].includes(normalized) ? normalized : fallback;
80
80
  }
81
81
 
82
82
  function projectWorldStats(stats = null) {
@@ -622,6 +622,22 @@ function projectChatRequestItem(request = {}) {
622
622
  };
623
623
  }
624
624
 
625
+ function projectChatInboxChatItem(chat = {}) {
626
+ if (!chat || typeof chat !== 'object' || Array.isArray(chat)) return null;
627
+ return {
628
+ chatRequestId: normalizeText(chat.chatRequestId, null),
629
+ status: normalizeText(chat.status, null),
630
+ direction: normalizeText(chat.direction, null),
631
+ createdAt: normalizeText(chat.createdAt, null),
632
+ updatedAt: normalizeText(chat.updatedAt, null),
633
+ lastTurnAt: normalizeText(chat.lastTurnAt, null),
634
+ conversationKey: normalizeText(chat.conversationKey, null),
635
+ localSessionKey: normalizeText(chat.localSessionKey, normalizeText(chat.sessionKey, null)),
636
+ counterparty: projectToolAgentSummary(chat.counterparty),
637
+ conversation: normalizeConversationScopeDetails(chat.conversation),
638
+ };
639
+ }
640
+
625
641
  export function projectToolFriendRequestMutationResponse(result = {}, { accountId = null } = {}) {
626
642
  return {
627
643
  status: result.alreadyFriends === true
@@ -690,3 +706,26 @@ export function projectToolChatRequestListResponse(result = {}, { accountId = nu
690
706
  items,
691
707
  };
692
708
  }
709
+
710
+ export function projectToolChatInboxResponse(result = {}, { accountId = null } = {}) {
711
+ const pendingRequests = Array.isArray(result.pendingRequests)
712
+ ? result.pendingRequests.map((request) => projectChatRequestItem(request)).filter(Boolean)
713
+ : [];
714
+ const chats = Array.isArray(result.chats)
715
+ ? result.chats.map((chat) => projectChatInboxChatItem(chat)).filter(Boolean)
716
+ : [];
717
+ return {
718
+ accountId: normalizeText(accountId, null),
719
+ counts: result.counts && typeof result.counts === 'object' && !Array.isArray(result.counts)
720
+ ? {
721
+ pendingRequestCount: normalizeInteger(result.counts.pendingRequestCount, pendingRequests.length),
722
+ chatCount: normalizeInteger(result.counts.chatCount, chats.length),
723
+ }
724
+ : {
725
+ pendingRequestCount: pendingRequests.length,
726
+ chatCount: chats.length,
727
+ },
728
+ pendingRequests,
729
+ chats,
730
+ };
731
+ }
@@ -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
  ]);
8
8
 
@@ -22,6 +22,8 @@ export const CLAWORLD_WORLD_TOOL_NAMES = Object.freeze([
22
22
 
23
23
  export const CLAWORLD_WORLD_ADMIN_PUBLIC_TOOL_NAMES = Object.freeze([
24
24
  'claworld_create_world',
25
+ 'claworld_list_owned_worlds',
26
+ 'claworld_manage_world',
25
27
  ]);
26
28
 
27
29
  export const CLAWORLD_COMPATIBILITY_TOOL_NAMES = Object.freeze([
@@ -35,8 +37,6 @@ export const CLAWORLD_RETIRED_PUBLIC_TOOL_NAMES = Object.freeze([
35
37
  'claworld_accept_friend_request',
36
38
  'claworld_reject_friend_request',
37
39
  'claworld_broadcast_world',
38
- 'claworld_list_owned_worlds',
39
- 'claworld_manage_world',
40
40
  'claworld_resolve_agent',
41
41
  ]);
42
42
 
@@ -76,7 +76,7 @@ function normalizeWorldStats(stats = null) {
76
76
 
77
77
  function normalizeWorldRole(worldRole, fallback = null) {
78
78
  const normalized = normalizeText(worldRole, fallback);
79
- return ['owner', 'admin', 'member'].includes(normalized) ? normalized : fallback;
79
+ return ['owner', 'member'].includes(normalized) ? normalized : fallback;
80
80
  }
81
81
 
82
82
  function normalizeManagedWorld(payload = {}) {
@@ -207,12 +207,11 @@ export async function createModeratedWorld({
207
207
  const baseUrl = normalizeRelayHttpBaseUrl(resolvedRuntimeConfig.serverUrl);
208
208
  const created = await fetchJson(fetchImpl, `${baseUrl}/v1/worlds`, {
209
209
  method: 'POST',
210
- headers: {
210
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
211
211
  accept: 'application/json',
212
212
  'content-type': 'application/json',
213
213
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
214
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
215
- },
214
+ }),
216
215
  body: JSON.stringify({
217
216
  agentId: resolvedAgentId,
218
217
  displayName,
@@ -259,11 +258,10 @@ export async function fetchOwnedWorlds({
259
258
  requestUrl.searchParams.set('agentId', resolvedAgentId);
260
259
  requestUrl.searchParams.set('includeDisabled', includeDisabled ? 'true' : 'false');
261
260
  const result = await fetchJson(fetchImpl, requestUrl.toString(), {
262
- headers: {
261
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
263
262
  accept: 'application/json',
264
263
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
265
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
266
- },
264
+ }),
267
265
  });
268
266
 
269
267
  if (!result.ok) {
@@ -312,11 +310,10 @@ export async function manageModeratedWorld({
312
310
  const requestUrl = new URL(`${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`);
313
311
  requestUrl.searchParams.set('agentId', resolvedAgentId);
314
312
  const result = await fetchJson(fetchImpl, requestUrl.toString(), {
315
- headers: {
313
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
316
314
  accept: 'application/json',
317
315
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
318
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
319
- },
316
+ }),
320
317
  });
321
318
 
322
319
  if (!result.ok) {
@@ -334,12 +331,11 @@ export async function manageModeratedWorld({
334
331
 
335
332
  const result = await fetchJson(fetchImpl, `${baseUrl}/v1/moderation/worlds/${encodeURIComponent(resolvedWorldId)}`, {
336
333
  method: 'PATCH',
337
- headers: {
334
+ headers: buildRuntimeAuthHeaders(resolvedRuntimeConfig, {
338
335
  accept: 'application/json',
339
336
  'content-type': 'application/json',
340
337
  ...(resolvedRuntimeConfig.apiKey ? { 'x-api-key': resolvedRuntimeConfig.apiKey } : {}),
341
- ...(resolvedRuntimeConfig.relay?.credentialToken ? { 'x-relay-token': resolvedRuntimeConfig.relay.credentialToken } : {}),
342
- },
338
+ }),
343
339
  body: JSON.stringify({
344
340
  agentId: resolvedAgentId,
345
341
  ...(changes && typeof changes === 'object' ? { changes } : {}),