@elizaos/plugin-social-alpha 2.0.3-beta.6 → 2.0.3-beta.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.
- package/dist/clients.d.ts +354 -0
- package/dist/clients.d.ts.map +1 -0
- package/dist/clients.js +670 -0
- package/dist/clients.js.map +1 -0
- package/dist/config.d.ts +144 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +122 -0
- package/dist/config.js.map +1 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +426 -0
- package/dist/events.js.map +1 -0
- package/dist/frontend/LeaderboardView.helpers.d.ts +6 -0
- package/dist/frontend/LeaderboardView.helpers.d.ts.map +1 -0
- package/dist/frontend/LeaderboardView.helpers.js +59 -0
- package/dist/frontend/LeaderboardView.helpers.js.map +1 -0
- package/dist/frontend/SocialAlphaSpatialView.d.ts +52 -0
- package/dist/frontend/SocialAlphaSpatialView.d.ts.map +1 -0
- package/dist/frontend/SocialAlphaSpatialView.js +72 -0
- package/dist/frontend/SocialAlphaSpatialView.js.map +1 -0
- package/dist/frontend/SocialAlphaView.d.ts +35 -0
- package/dist/frontend/SocialAlphaView.d.ts.map +1 -0
- package/dist/frontend/SocialAlphaView.js +125 -0
- package/dist/frontend/SocialAlphaView.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/mockPriceService.d.ts +22 -0
- package/dist/mockPriceService.d.ts.map +1 -0
- package/dist/mockPriceService.js +21 -0
- package/dist/mockPriceService.js.map +1 -0
- package/dist/providers/socialAlphaProvider.d.ts +15 -0
- package/dist/providers/socialAlphaProvider.d.ts.map +1 -0
- package/dist/providers/socialAlphaProvider.js +261 -0
- package/dist/providers/socialAlphaProvider.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +10 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +5 -0
- package/dist/register.js.map +1 -0
- package/dist/reports.d.ts +57 -0
- package/dist/reports.d.ts.map +1 -0
- package/dist/reports.js +455 -0
- package/dist/reports.js.map +1 -0
- package/dist/routes.d.ts +3 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +59 -0
- package/dist/routes.js.map +1 -0
- package/dist/schemas.d.ts +151 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +258 -0
- package/dist/schemas.js.map +1 -0
- package/dist/service.d.ts +306 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +3078 -0
- package/dist/service.js.map +1 -0
- package/dist/services/balancedTrustScoreCalculator.d.ts +61 -0
- package/dist/services/balancedTrustScoreCalculator.d.ts.map +1 -0
- package/dist/services/balancedTrustScoreCalculator.js +207 -0
- package/dist/services/balancedTrustScoreCalculator.js.map +1 -0
- package/dist/services/historicalPriceService.d.ts +59 -0
- package/dist/services/historicalPriceService.d.ts.map +1 -0
- package/dist/services/historicalPriceService.js +291 -0
- package/dist/services/historicalPriceService.js.map +1 -0
- package/dist/services/index.d.ts +12 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +17 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/priceEnrichmentService.d.ts +109 -0
- package/dist/services/priceEnrichmentService.d.ts.map +1 -0
- package/dist/services/priceEnrichmentService.js +780 -0
- package/dist/services/priceEnrichmentService.js.map +1 -0
- package/dist/services/simulationActorsV2.d.ts +54 -0
- package/dist/services/simulationActorsV2.d.ts.map +1 -0
- package/dist/services/simulationActorsV2.js +362 -0
- package/dist/services/simulationActorsV2.js.map +1 -0
- package/dist/services/simulationRunner.d.ts +113 -0
- package/dist/services/simulationRunner.d.ts.map +1 -0
- package/dist/services/simulationRunner.js +771 -0
- package/dist/services/simulationRunner.js.map +1 -0
- package/dist/services/tokenSimulationService.d.ts +34 -0
- package/dist/services/tokenSimulationService.d.ts.map +1 -0
- package/dist/services/tokenSimulationService.js +297 -0
- package/dist/services/tokenSimulationService.js.map +1 -0
- package/dist/services/trustScoreOptimizer.d.ts +110 -0
- package/dist/services/trustScoreOptimizer.d.ts.map +1 -0
- package/dist/services/trustScoreOptimizer.js +635 -0
- package/dist/services/trustScoreOptimizer.js.map +1 -0
- package/dist/simulationActors.d.ts +35 -0
- package/dist/simulationActors.d.ts.map +1 -0
- package/dist/simulationActors.js +160 -0
- package/dist/simulationActors.js.map +1 -0
- package/dist/social-alpha-view-bundle.d.ts +2 -0
- package/dist/social-alpha-view-bundle.d.ts.map +1 -0
- package/dist/social-alpha-view-bundle.js +5 -0
- package/dist/social-alpha-view-bundle.js.map +1 -0
- package/dist/types.d.ts +937 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/dist/views/brand/background/clouds_background.jpg +0 -0
- package/dist/views/brand/banners/eliza_banner.svg +20 -0
- package/dist/views/brand/banners/elizacloud_banner.svg +20 -0
- package/dist/views/brand/banners/elizaos_banner.svg +20 -0
- package/dist/views/brand/concepts/billboard_concept_1200.jpg +0 -0
- package/dist/views/brand/concepts/chibi_usb_concept_900.jpg +0 -0
- package/dist/views/brand/concepts/concept_minipc_900.jpg +0 -0
- package/dist/views/brand/concepts/concept_phone_800.jpg +0 -0
- package/dist/views/brand/concepts/concept_usbdrive_900.jpg +0 -0
- package/dist/views/brand/favicons/android-chrome-192x192.png +0 -0
- package/dist/views/brand/favicons/android-chrome-512x512.png +0 -0
- package/dist/views/brand/favicons/apple-touch-icon.png +0 -0
- package/dist/views/brand/favicons/favicon-16x16.png +0 -0
- package/dist/views/brand/favicons/favicon-32x32.png +0 -0
- package/dist/views/brand/favicons/favicon.ico +0 -0
- package/dist/views/brand/favicons/favicon.svg +17 -0
- package/dist/views/brand/logos/elizaOS_text_black.svg +3 -0
- package/dist/views/brand/logos/elizaOS_text_white.svg +3 -0
- package/dist/views/brand/logos/eliza_logotext.svg +26 -0
- package/dist/views/brand/logos/eliza_logotext_black.svg +26 -0
- package/dist/views/brand/logos/eliza_text_black.svg +3 -0
- package/dist/views/brand/logos/eliza_text_white.svg +3 -0
- package/dist/views/brand/logos/elizacloud_logotext.svg +26 -0
- package/dist/views/brand/logos/elizacloud_logotext_black.svg +26 -0
- package/dist/views/brand/logos/elizacloud_text_black.svg +3 -0
- package/dist/views/brand/logos/elizacloud_text_white.svg +3 -0
- package/dist/views/brand/logos/elizaos_logotext.svg +26 -0
- package/dist/views/brand/logos/elizaos_logotext_black.svg +26 -0
- package/dist/views/brand/logos/logo_blue_blackbg.svg +18 -0
- package/dist/views/brand/logos/logo_blue_nobg.svg +17 -0
- package/dist/views/brand/logos/logo_orange_blackbg.svg +18 -0
- package/dist/views/brand/logos/logo_orange_nobg.svg +17 -0
- package/dist/views/brand/logos/logo_white_blackbg.svg +25 -0
- package/dist/views/brand/logos/logo_white_bluebg.svg +25 -0
- package/dist/views/brand/logos/logo_white_graybg.svg +18 -0
- package/dist/views/brand/logos/logo_white_nobg.svg +24 -0
- package/dist/views/brand/logos/logo_white_orangebg.svg +25 -0
- package/dist/views/brand/ogembeds/eliza_ogembed.png +0 -0
- package/dist/views/brand/ogembeds/eliza_ogembed.svg +20 -0
- package/dist/views/brand/ogembeds/elizacloud_ogembed.png +0 -0
- package/dist/views/brand/ogembeds/elizacloud_ogembed.svg +20 -0
- package/dist/views/brand/ogembeds/elizaos_ogembed.png +0 -0
- package/dist/views/brand/ogembeds/elizaos_ogembed.svg +20 -0
- package/dist/views/bundle.js +268 -0
- package/dist/views/bundle.js.map +1 -0
- package/dist/views/site.webmanifest +19 -0
- package/package.json +5 -5
package/dist/events.js
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asUUID,
|
|
3
|
+
ChannelType,
|
|
4
|
+
logger as coreLogger,
|
|
5
|
+
createUniqueUuid,
|
|
6
|
+
ModelType,
|
|
7
|
+
withStandaloneTrajectory
|
|
8
|
+
} from "@elizaos/core";
|
|
9
|
+
import { v4 as uuidv4 } from "uuid";
|
|
10
|
+
import { TRUST_LEADERBOARD_WORLD_SEED } from "./config.js";
|
|
11
|
+
import {
|
|
12
|
+
ServiceType,
|
|
13
|
+
SupportedChain,
|
|
14
|
+
TRUST_MARKETPLACE_COMPONENT_TYPE
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
function logValue(value) {
|
|
17
|
+
if (typeof value === "string") return value;
|
|
18
|
+
if (value instanceof Error) return value.stack ?? value.message;
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(value);
|
|
21
|
+
} catch {
|
|
22
|
+
return String(value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const logger = {
|
|
26
|
+
debug: (...args) => coreLogger.debug(args.map(logValue).join(" ")),
|
|
27
|
+
info: (...args) => coreLogger.info(args.map(logValue).join(" ")),
|
|
28
|
+
warn: (...args) => coreLogger.warn(args.map(logValue).join(" ")),
|
|
29
|
+
error: (...args) => coreLogger.error(args.map(logValue).join(" "))
|
|
30
|
+
};
|
|
31
|
+
const RELEVANCE_AND_EXTRACTION_TEMPLATE = `
|
|
32
|
+
# Task: Crypto Relevance + Recommendation Extraction
|
|
33
|
+
Given the current message and recent conversation context, do BOTH at once:
|
|
34
|
+
1. Decide whether the message is relevant to cryptocurrency discussions
|
|
35
|
+
(token mentions, trading, market sentiment, buy/sell signals, DeFi, NFTs,
|
|
36
|
+
or financial advice related to crypto).
|
|
37
|
+
2. If and only if it IS relevant, extract every explicit or strongly implied
|
|
38
|
+
recommendation to buy or sell a cryptocurrency token, or strong criticism.
|
|
39
|
+
|
|
40
|
+
# Conversation Context
|
|
41
|
+
Current Message Sender: {{senderName}}
|
|
42
|
+
Current Message: "{{currentMessageText}}"
|
|
43
|
+
|
|
44
|
+
Recent Messages (if any):
|
|
45
|
+
{{recentMessagesContext}}
|
|
46
|
+
|
|
47
|
+
# Extraction rules (apply only when the message is crypto-relevant)
|
|
48
|
+
For each recommendation/criticism:
|
|
49
|
+
1. Identify the token mentioned (ticker like $SOL, or a contract address \u2014
|
|
50
|
+
a contract address must look like one, e.g. a long alphanumeric string).
|
|
51
|
+
2. Determine if the mention is a ticker (true/false).
|
|
52
|
+
3. Determine the sentiment: 'positive' (buy, pump, moon, good investment),
|
|
53
|
+
'negative' (sell, dump, scam, bad investment), or 'neutral' (general
|
|
54
|
+
discussion without clear buy/sell intent).
|
|
55
|
+
4. Estimate the sender's conviction: 'NONE', 'LOW', 'MEDIUM', 'HIGH'.
|
|
56
|
+
5. Extract the direct quote from the message that forms the basis of the
|
|
57
|
+
recommendation/criticism.
|
|
58
|
+
|
|
59
|
+
# Output
|
|
60
|
+
Respond with JSON only. Use this shape:
|
|
61
|
+
{"recommendations":[{"tokenMentioned":"SOL","isTicker":true,"sentiment":"positive","conviction":"HIGH","quote":"$SOL is going to moon"}]}
|
|
62
|
+
|
|
63
|
+
If the message is NOT crypto-relevant, OR is relevant but contains no
|
|
64
|
+
actionable recommendation or strong criticism, return an empty list:
|
|
65
|
+
{"recommendations":[]}
|
|
66
|
+
|
|
67
|
+
An empty \`recommendations\` list means "not relevant or nothing to extract" \u2014
|
|
68
|
+
the caller treats both cases the same way.
|
|
69
|
+
|
|
70
|
+
# Your Analysis:
|
|
71
|
+
`;
|
|
72
|
+
const MAX_RECENT_MESSAGES_FOR_CONTEXT = 5;
|
|
73
|
+
const MAX_RECOMMENDATIONS_IN_PROFILE = 50;
|
|
74
|
+
const DEFAULT_CHAIN = SupportedChain.SOLANA;
|
|
75
|
+
const RECENT_REC_DUPLICATION_TIMEFRAME_MS = 30 * 60 * 1e3;
|
|
76
|
+
function parseJsonObject(value) {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(value.trim());
|
|
79
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const messageReceivedHandler = async ({
|
|
85
|
+
runtime,
|
|
86
|
+
message
|
|
87
|
+
}) => {
|
|
88
|
+
const {
|
|
89
|
+
entityId: currentMessageSenderId,
|
|
90
|
+
roomId,
|
|
91
|
+
id: messageId,
|
|
92
|
+
content,
|
|
93
|
+
createdAt,
|
|
94
|
+
worldId: msgWorldId
|
|
95
|
+
} = message;
|
|
96
|
+
const agentId = runtime.agentId;
|
|
97
|
+
const componentWorldId = createUniqueUuid(
|
|
98
|
+
runtime,
|
|
99
|
+
TRUST_LEADERBOARD_WORLD_SEED
|
|
100
|
+
);
|
|
101
|
+
const _componentRoomId = componentWorldId;
|
|
102
|
+
const _connectionWorldId = msgWorldId || createUniqueUuid(runtime, currentMessageSenderId);
|
|
103
|
+
logger.debug(
|
|
104
|
+
`[CommunityInvestor] ensureConnection PRE-CHECK: Message ID: ${messageId}, Room ID: ${roomId}, Message Content: ${JSON.stringify(content)}`
|
|
105
|
+
);
|
|
106
|
+
if (!roomId) {
|
|
107
|
+
logger.error(
|
|
108
|
+
`[CommunityInvestor] CRITICAL: roomId is missing for message ${messageId}. Aborting handler.`
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!content || typeof content !== "object") {
|
|
113
|
+
logger.error(
|
|
114
|
+
`[CommunityInvestor] CRITICAL: message.content is null, undefined, or not an object for message ${messageId}. Aborting handler.`
|
|
115
|
+
);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
let connectionChannelType = content.type;
|
|
119
|
+
if (!connectionChannelType) {
|
|
120
|
+
logger.warn(
|
|
121
|
+
`[CommunityInvestor] message.content.type is missing for message ${messageId} from source ${content.source} in room ${roomId}. Defaulting to ChannelType.GROUP.`
|
|
122
|
+
);
|
|
123
|
+
connectionChannelType = ChannelType.GROUP;
|
|
124
|
+
}
|
|
125
|
+
let connectionChannelId = content.channelId;
|
|
126
|
+
if (!connectionChannelId) {
|
|
127
|
+
logger.warn(
|
|
128
|
+
`[CommunityInvestor] WARNING: message.content.channelId is missing for message ${messageId} from source ${content.source} in room ${roomId}. Using roomId as fallback for channelId. Message content: ${JSON.stringify(content)}`
|
|
129
|
+
);
|
|
130
|
+
connectionChannelId = roomId;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const messageRoomId = roomId || createUniqueUuid(runtime, currentMessageSenderId);
|
|
134
|
+
logger.debug(
|
|
135
|
+
`[CommunityInvestor] Processing message from user ${currentMessageSenderId} in room ${messageRoomId}`
|
|
136
|
+
);
|
|
137
|
+
try {
|
|
138
|
+
await runtime.ensureWorldExists({
|
|
139
|
+
id: componentWorldId,
|
|
140
|
+
// This is now runtime.agentId
|
|
141
|
+
name: `Social Alpha World for Agent ${componentWorldId}`,
|
|
142
|
+
agentId: runtime.agentId,
|
|
143
|
+
// The agent responsible for this world
|
|
144
|
+
metadata: {}
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.debug(
|
|
148
|
+
`[CommunityInvestor] World ${componentWorldId} already exists or error ensuring world: ${error}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
logger.debug(
|
|
153
|
+
`[CommunityInvestor] Message from ${currentMessageSenderId} in room ${roomId}. Text: "${content.text?.substring(0, 50)}..."`
|
|
154
|
+
);
|
|
155
|
+
if (currentMessageSenderId === agentId) {
|
|
156
|
+
logger.debug("[CommunityInvestor] Skipping self-message.");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const agentUserState = await runtime.getParticipantUserState(
|
|
160
|
+
messageRoomId,
|
|
161
|
+
agentId
|
|
162
|
+
);
|
|
163
|
+
if (agentUserState === "MUTED" && !content.text?.toLowerCase().includes((runtime.character.name ?? "").toLowerCase())) {
|
|
164
|
+
logger.debug(
|
|
165
|
+
"[CommunityInvestor] Agent muted and not mentioned. Ignoring."
|
|
166
|
+
);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const recentMessagesForContext = await runtime.getMemories({
|
|
170
|
+
tableName: "messages",
|
|
171
|
+
roomId: messageRoomId,
|
|
172
|
+
count: MAX_RECENT_MESSAGES_FOR_CONTEXT,
|
|
173
|
+
unique: false
|
|
174
|
+
});
|
|
175
|
+
const history = recentMessagesForContext.slice(0, 10).map((msg) => {
|
|
176
|
+
const name = msg.content?.name ?? msg.entityId.toString();
|
|
177
|
+
const text = msg.content?.text ?? "";
|
|
178
|
+
return `${name}: ${text}`;
|
|
179
|
+
}).join("\n");
|
|
180
|
+
const combinedPrompt = RELEVANCE_AND_EXTRACTION_TEMPLATE.replace(
|
|
181
|
+
"{{senderName}}",
|
|
182
|
+
String(content.name || currentMessageSenderId.toString())
|
|
183
|
+
).replace("{{currentMessageText}}", String(content.text || "")).replace("{{recentMessagesContext}}", history);
|
|
184
|
+
const extractionResponseRaw = await withStandaloneTrajectory(
|
|
185
|
+
runtime,
|
|
186
|
+
{
|
|
187
|
+
source: "social-alpha-event",
|
|
188
|
+
metadata: {
|
|
189
|
+
messageId,
|
|
190
|
+
type: "relevance-extraction"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
() => runtime.useModel(ModelType.TEXT_LARGE, {
|
|
194
|
+
prompt: combinedPrompt
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
const extractionResult = parseJsonObject(extractionResponseRaw);
|
|
198
|
+
const extractedRecommendations = extractionResult?.recommendations ?? [];
|
|
199
|
+
if (extractedRecommendations.length === 0) {
|
|
200
|
+
logger.debug(
|
|
201
|
+
"[CommunityInvestor] No recommendations extracted (not relevant or nothing actionable)."
|
|
202
|
+
);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
logger.info(
|
|
206
|
+
`[CommunityInvestor] Found ${extractedRecommendations.length} recommendations to process`
|
|
207
|
+
);
|
|
208
|
+
const communityInvestorService = runtime.getService(
|
|
209
|
+
ServiceType.COMMUNITY_INVESTOR
|
|
210
|
+
);
|
|
211
|
+
if (!communityInvestorService) {
|
|
212
|
+
logger.error("[CommunityInvestor] Service not found!");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const userProfileComponent = await runtime.getComponent(
|
|
216
|
+
currentMessageSenderId,
|
|
217
|
+
TRUST_MARKETPLACE_COMPONENT_TYPE,
|
|
218
|
+
componentWorldId,
|
|
219
|
+
agentId
|
|
220
|
+
);
|
|
221
|
+
let userProfile;
|
|
222
|
+
if (!userProfileComponent?.data) {
|
|
223
|
+
userProfile = {
|
|
224
|
+
version: "1.0.0",
|
|
225
|
+
userId: currentMessageSenderId,
|
|
226
|
+
trustScore: 0,
|
|
227
|
+
lastTrustScoreCalculationTimestamp: Date.now(),
|
|
228
|
+
recommendations: []
|
|
229
|
+
};
|
|
230
|
+
logger.debug(
|
|
231
|
+
`[CommunityInvestor] Initializing new profile for ${currentMessageSenderId}`
|
|
232
|
+
);
|
|
233
|
+
} else {
|
|
234
|
+
userProfile = userProfileComponent.data;
|
|
235
|
+
if (!Array.isArray(userProfile.recommendations))
|
|
236
|
+
userProfile.recommendations = [];
|
|
237
|
+
}
|
|
238
|
+
let profileUpdated = false;
|
|
239
|
+
for (const extractedRec of extractedRecommendations) {
|
|
240
|
+
if (extractedRec.sentiment === "neutral" || !extractedRec.tokenMentioned?.trim()) {
|
|
241
|
+
logger.debug(
|
|
242
|
+
`[CommunityInvestor] Skipping neutral or empty token mention: "${extractedRec.quote}"`
|
|
243
|
+
);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
logger.debug(
|
|
247
|
+
`[E2E TRACE] Extracted rec: ${JSON.stringify(extractedRec)}`
|
|
248
|
+
);
|
|
249
|
+
let resolvedToken = null;
|
|
250
|
+
const isTicker = extractedRec.isTicker === true || String(extractedRec.isTicker).toLowerCase() === "true";
|
|
251
|
+
if (isTicker) {
|
|
252
|
+
resolvedToken = await communityInvestorService.resolveTicker(
|
|
253
|
+
extractedRec.tokenMentioned,
|
|
254
|
+
DEFAULT_CHAIN,
|
|
255
|
+
recentMessagesForContext
|
|
256
|
+
);
|
|
257
|
+
} else if (extractedRec.tokenMentioned.length > 20 && extractedRec.tokenMentioned.match(/^[a-zA-Z0-9]+$/)) {
|
|
258
|
+
resolvedToken = {
|
|
259
|
+
address: extractedRec.tokenMentioned,
|
|
260
|
+
chain: DEFAULT_CHAIN,
|
|
261
|
+
ticker: void 0
|
|
262
|
+
};
|
|
263
|
+
} else {
|
|
264
|
+
logger.debug(
|
|
265
|
+
`[CommunityInvestor] Invalid address-like token: ${extractedRec.tokenMentioned}`
|
|
266
|
+
);
|
|
267
|
+
logger.debug(
|
|
268
|
+
`[E2E TRACE] Token mention ${extractedRec.tokenMentioned} not considered a valid address format.`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
logger.debug(
|
|
272
|
+
`[E2E TRACE] resolvedToken for "${extractedRec.quote}": ${JSON.stringify(resolvedToken)}`
|
|
273
|
+
);
|
|
274
|
+
if (!resolvedToken) {
|
|
275
|
+
logger.warn(
|
|
276
|
+
`[CommunityInvestor] Could not resolve token for: "${extractedRec.quote}".`
|
|
277
|
+
);
|
|
278
|
+
logger.debug(
|
|
279
|
+
`[E2E TRACE] Skipping rec due to unresolved token: "${extractedRec.quote}"`
|
|
280
|
+
);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
logger.debug(
|
|
284
|
+
`[E2E TRACE] Attempting to get token API data for ${resolvedToken.address}`
|
|
285
|
+
);
|
|
286
|
+
const tokenAPIData = await communityInvestorService.getTokenAPIData(
|
|
287
|
+
resolvedToken.address,
|
|
288
|
+
resolvedToken.chain
|
|
289
|
+
);
|
|
290
|
+
logger.debug(
|
|
291
|
+
`[E2E TRACE] tokenAPIData for ${resolvedToken.address}: ${JSON.stringify(tokenAPIData)}`
|
|
292
|
+
);
|
|
293
|
+
const priceAtRecommendation = tokenAPIData?.currentPrice;
|
|
294
|
+
const recTimestamp = createdAt || Date.now();
|
|
295
|
+
const existingRecent = userProfile.recommendations.find(
|
|
296
|
+
(r) => r.tokenAddress === resolvedToken?.address && r.recommendationType === (extractedRec.sentiment === "positive" ? "BUY" : "SELL") && recTimestamp - r.timestamp < RECENT_REC_DUPLICATION_TIMEFRAME_MS
|
|
297
|
+
);
|
|
298
|
+
logger.debug(
|
|
299
|
+
`[E2E TRACE] Existing recent duplicate check. Target: ${resolvedToken.address}, Type: ${extractedRec.sentiment === "positive" ? "BUY" : "SELL"}`
|
|
300
|
+
);
|
|
301
|
+
if (existingRecent) {
|
|
302
|
+
logger.debug(
|
|
303
|
+
`[CommunityInvestor] Skipping duplicate rec for ${resolvedToken.address}`
|
|
304
|
+
);
|
|
305
|
+
logger.debug(
|
|
306
|
+
`[E2E TRACE] Found existing recent duplicate for ${resolvedToken.address}. Skipping.`
|
|
307
|
+
);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const newRecommendation = {
|
|
311
|
+
id: asUUID(uuidv4()),
|
|
312
|
+
userId: currentMessageSenderId,
|
|
313
|
+
messageId: messageId || asUUID(
|
|
314
|
+
createUniqueUuid(
|
|
315
|
+
runtime,
|
|
316
|
+
`${currentMessageSenderId}-${recTimestamp}`
|
|
317
|
+
)
|
|
318
|
+
),
|
|
319
|
+
timestamp: recTimestamp,
|
|
320
|
+
tokenTicker: resolvedToken.ticker?.toUpperCase(),
|
|
321
|
+
tokenAddress: resolvedToken.address,
|
|
322
|
+
chain: resolvedToken.chain,
|
|
323
|
+
recommendationType: extractedRec.sentiment === "positive" ? "BUY" : "SELL",
|
|
324
|
+
conviction: extractedRec.conviction,
|
|
325
|
+
rawMessageQuote: extractedRec.quote,
|
|
326
|
+
priceAtRecommendation,
|
|
327
|
+
// Store price at time of recommendation
|
|
328
|
+
processedForTradeDecision: false
|
|
329
|
+
};
|
|
330
|
+
logger.debug(
|
|
331
|
+
`[E2E TRACE] newRecommendation created: ${JSON.stringify(newRecommendation)}`
|
|
332
|
+
);
|
|
333
|
+
userProfile.recommendations.unshift(newRecommendation);
|
|
334
|
+
if (userProfile.recommendations.length > MAX_RECOMMENDATIONS_IN_PROFILE)
|
|
335
|
+
userProfile.recommendations.pop();
|
|
336
|
+
profileUpdated = true;
|
|
337
|
+
logger.debug(
|
|
338
|
+
`[E2E TRACE] profileUpdated is now true. Profile recommendation count: ${userProfile.recommendations.length}`
|
|
339
|
+
);
|
|
340
|
+
logger.info(
|
|
341
|
+
`[CommunityInvestor] Added ${newRecommendation.recommendationType} rec for user ${currentMessageSenderId}, token ${newRecommendation.tokenAddress}`
|
|
342
|
+
);
|
|
343
|
+
await runtime.createTask({
|
|
344
|
+
name: "PROCESS_TRADE_DECISION",
|
|
345
|
+
description: `Process trade decision for rec ${newRecommendation.id}`,
|
|
346
|
+
metadata: {
|
|
347
|
+
recommendationId: newRecommendation.id,
|
|
348
|
+
userId: currentMessageSenderId
|
|
349
|
+
},
|
|
350
|
+
tags: ["socialAlpha", "tradeDecision"],
|
|
351
|
+
roomId: messageRoomId,
|
|
352
|
+
worldId: componentWorldId,
|
|
353
|
+
entityId: currentMessageSenderId
|
|
354
|
+
});
|
|
355
|
+
logger.debug(
|
|
356
|
+
`[CommunityInvestor] Created PROCESS_TRADE_DECISION task for rec ID ${newRecommendation.id} in room/world ${componentWorldId}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
logger.debug(`[E2E TRACE] After loop, profileUpdated: ${profileUpdated}`);
|
|
360
|
+
if (profileUpdated) {
|
|
361
|
+
logger.debug(
|
|
362
|
+
`[E2E TRACE] profileUpdated is true. Checking if userProfileComponent exists.`
|
|
363
|
+
);
|
|
364
|
+
if (userProfileComponent) {
|
|
365
|
+
logger.debug(
|
|
366
|
+
`[E2E TRACE] Attempting to update component ${userProfileComponent.id}`
|
|
367
|
+
);
|
|
368
|
+
await runtime.updateComponent({
|
|
369
|
+
...userProfileComponent,
|
|
370
|
+
data: userProfile
|
|
371
|
+
});
|
|
372
|
+
logger.debug(
|
|
373
|
+
`[CommunityInvestor] Updated component ${userProfileComponent.id} for ${currentMessageSenderId}`
|
|
374
|
+
);
|
|
375
|
+
} else {
|
|
376
|
+
const newComponentId = asUUID(uuidv4());
|
|
377
|
+
logger.debug(
|
|
378
|
+
`[E2E TRACE] Attempting to create new component with id ${newComponentId} for user ${currentMessageSenderId} in world ${componentWorldId} and room (set to world) ${componentWorldId}`
|
|
379
|
+
);
|
|
380
|
+
await runtime.createComponent({
|
|
381
|
+
id: newComponentId,
|
|
382
|
+
entityId: currentMessageSenderId,
|
|
383
|
+
agentId,
|
|
384
|
+
worldId: componentWorldId,
|
|
385
|
+
roomId: messageRoomId,
|
|
386
|
+
sourceEntityId: agentId,
|
|
387
|
+
type: TRUST_MARKETPLACE_COMPONENT_TYPE,
|
|
388
|
+
createdAt: Date.now(),
|
|
389
|
+
data: userProfile
|
|
390
|
+
});
|
|
391
|
+
logger.info(
|
|
392
|
+
`[CommunityInvestor] Created new component ${newComponentId} for ${currentMessageSenderId} with roomId ${componentWorldId}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
logger.info(
|
|
396
|
+
`[CommunityInvestor] Triggering trust score calculation for user ${currentMessageSenderId}`
|
|
397
|
+
);
|
|
398
|
+
await communityInvestorService.calculateUserTrustScore(
|
|
399
|
+
currentMessageSenderId,
|
|
400
|
+
runtime
|
|
401
|
+
);
|
|
402
|
+
logger.info(
|
|
403
|
+
`[CommunityInvestor] Trust score calculation completed for user ${currentMessageSenderId}`
|
|
404
|
+
);
|
|
405
|
+
} else {
|
|
406
|
+
logger.info(
|
|
407
|
+
`[CommunityInvestor] Profile NOT updated for message ${messageId}, user ${currentMessageSenderId}. No new valid recommendations extracted or token resolution failed.`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
} catch (error) {
|
|
411
|
+
logger.error(
|
|
412
|
+
"[CommunityInvestor] Error in messageReceivedHandler:",
|
|
413
|
+
error
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
logger.error("[CommunityInvestor] Error in messageReceivedHandler:", error);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const events = {
|
|
421
|
+
MESSAGE_RECEIVED: [messageReceivedHandler]
|
|
422
|
+
};
|
|
423
|
+
export {
|
|
424
|
+
events
|
|
425
|
+
};
|
|
426
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts"],"sourcesContent":["import {\n\tasUUID,\n\tChannelType,\n\tlogger as coreLogger,\n\tcreateUniqueUuid,\n\ttype Memory,\n\ttype MessagePayload,\n\tModelType,\n\twithStandaloneTrajectory,\n} from \"@elizaos/core\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { TRUST_LEADERBOARD_WORLD_SEED } from \"./config.js\";\nimport type { CommunityInvestorService } from \"./service.js\";\nimport {\n\ttype Conviction,\n\ttype Recommendation,\n\tServiceType,\n\tSupportedChain,\n\tTRUST_MARKETPLACE_COMPONENT_TYPE,\n\ttype UserTrustProfile,\n} from \"./types.js\";\n\nfunction logValue(value: unknown): string {\n\tif (typeof value === \"string\") return value;\n\tif (value instanceof Error) return value.stack ?? value.message;\n\ttry {\n\t\treturn JSON.stringify(value);\n\t} catch {\n\t\treturn String(value);\n\t}\n}\n\nconst logger = {\n\tdebug: (...args: unknown[]) => coreLogger.debug(args.map(logValue).join(\" \")),\n\tinfo: (...args: unknown[]) => coreLogger.info(args.map(logValue).join(\" \")),\n\twarn: (...args: unknown[]) => coreLogger.warn(args.map(logValue).join(\" \")),\n\terror: (...args: unknown[]) => coreLogger.error(args.map(logValue).join(\" \")),\n};\n\n// Combined relevance + extraction in a single LLM call. An empty\n// `recommendations` array == not relevant. This saves one TEXT_LARGE per\n// inbound message versus the old two-call relevance-then-extraction flow.\nconst RELEVANCE_AND_EXTRACTION_TEMPLATE = `\n# Task: Crypto Relevance + Recommendation Extraction\nGiven the current message and recent conversation context, do BOTH at once:\n1. Decide whether the message is relevant to cryptocurrency discussions\n (token mentions, trading, market sentiment, buy/sell signals, DeFi, NFTs,\n or financial advice related to crypto).\n2. If and only if it IS relevant, extract every explicit or strongly implied\n recommendation to buy or sell a cryptocurrency token, or strong criticism.\n\n# Conversation Context\nCurrent Message Sender: {{senderName}}\nCurrent Message: \"{{currentMessageText}}\"\n\nRecent Messages (if any):\n{{recentMessagesContext}}\n\n# Extraction rules (apply only when the message is crypto-relevant)\nFor each recommendation/criticism:\n1. Identify the token mentioned (ticker like $SOL, or a contract address —\n a contract address must look like one, e.g. a long alphanumeric string).\n2. Determine if the mention is a ticker (true/false).\n3. Determine the sentiment: 'positive' (buy, pump, moon, good investment),\n 'negative' (sell, dump, scam, bad investment), or 'neutral' (general\n discussion without clear buy/sell intent).\n4. Estimate the sender's conviction: 'NONE', 'LOW', 'MEDIUM', 'HIGH'.\n5. Extract the direct quote from the message that forms the basis of the\n recommendation/criticism.\n\n# Output\nRespond with JSON only. Use this shape:\n{\"recommendations\":[{\"tokenMentioned\":\"SOL\",\"isTicker\":true,\"sentiment\":\"positive\",\"conviction\":\"HIGH\",\"quote\":\"$SOL is going to moon\"}]}\n\nIf the message is NOT crypto-relevant, OR is relevant but contains no\nactionable recommendation or strong criticism, return an empty list:\n{\"recommendations\":[]}\n\nAn empty \\`recommendations\\` list means \"not relevant or nothing to extract\" —\nthe caller treats both cases the same way.\n\n# Your Analysis:\n`;\n\nconst MAX_RECENT_MESSAGES_FOR_CONTEXT = 5;\nconst MAX_RECOMMENDATIONS_IN_PROFILE = 50;\nconst DEFAULT_CHAIN = SupportedChain.SOLANA;\nconst RECENT_REC_DUPLICATION_TIMEFRAME_MS = 30 * 60 * 1000; // 30 minutes\n\nfunction parseJsonObject<T extends Record<string, unknown>>(\n\tvalue: string,\n): T | null {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(value.trim());\n\t\treturn parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n\t\t\t? (parsed as T)\n\t\t\t: null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Handles incoming messages and generates responses based on the provided runtime and message information.\n *\n * @param {MessagePayload} params - The parameters needed for message handling.\n * @returns {Promise<void>} - A promise that resolves once the message handling and response generation is complete.\n */\nconst messageReceivedHandler = async ({\n\truntime,\n\tmessage,\n}: MessagePayload): Promise<void> => {\n\tconst {\n\t\tentityId: currentMessageSenderId,\n\t\troomId,\n\t\tid: messageId,\n\t\tcontent,\n\t\tcreatedAt,\n\t\tworldId: msgWorldId,\n\t} = message;\n\tconst agentId = runtime.agentId;\n\t// Use the consistent, seeded ID for storing community investor plugin-specific components.\n\tconst componentWorldId = createUniqueUuid(\n\t\truntime,\n\t\tTRUST_LEADERBOARD_WORLD_SEED,\n\t);\n\tconst _componentRoomId = componentWorldId; // Components will have their room set to this ID too.\n\n\t// Determine the worldId for the connection context (message's origin)\n\tconst _connectionWorldId =\n\t\tmsgWorldId || createUniqueUuid(runtime, currentMessageSenderId);\n\n\t// Critical: Log the content object and its type to diagnose the missing 'type' issue.\n\tlogger.debug(\n\t\t`[CommunityInvestor] ensureConnection PRE-CHECK: Message ID: ${messageId}, Room ID: ${roomId}, Message Content: ${JSON.stringify(content)}`,\n\t);\n\n\tif (!roomId) {\n\t\tlogger.error(\n\t\t\t`[CommunityInvestor] CRITICAL: roomId is missing for message ${messageId}. Aborting handler.`,\n\t\t);\n\t\treturn;\n\t}\n\n\tif (!content || typeof content !== \"object\") {\n\t\tlogger.error(\n\t\t\t`[CommunityInvestor] CRITICAL: message.content is null, undefined, or not an object for message ${messageId}. Aborting handler.`,\n\t\t);\n\t\treturn;\n\t}\n\n\t// Default content.type if it's missing, as per user instruction.\n\tlet connectionChannelType = content.type as ChannelType;\n\tif (!connectionChannelType) {\n\t\tlogger.warn(\n\t\t\t`[CommunityInvestor] message.content.type is missing for message ${messageId} from source ${content.source} in room ${roomId}. ` +\n\t\t\t\t`Defaulting to ChannelType.GROUP.`,\n\t\t);\n\t\tconnectionChannelType = ChannelType.GROUP; // Defaulting to GROUP\n\t}\n\n\t// Also check channelId as it's used in ensureConnection too\n\tlet connectionChannelId = content.channelId as string;\n\tif (!connectionChannelId) {\n\t\tlogger.warn(\n\t\t\t`[CommunityInvestor] WARNING: message.content.channelId is missing for message ${messageId} from source ${content.source} in room ${roomId}. ` +\n\t\t\t\t`Using roomId as fallback for channelId. Message content: ${JSON.stringify(content)}`,\n\t\t);\n\t\tconnectionChannelId = roomId; // Fallback for channelId, might not always be correct but better than undefined for some runtimes\n\t}\n\n\ttry {\n\t\t// Create a simple roomId for this message context if none provided\n\t\tconst messageRoomId =\n\t\t\troomId || createUniqueUuid(runtime, currentMessageSenderId);\n\n\t\tlogger.debug(\n\t\t\t`[CommunityInvestor] Processing message from user ${currentMessageSenderId} in room ${messageRoomId}`,\n\t\t);\n\n\t\t// Ensure the agent's world exists before creating components within it\n\t\ttry {\n\t\t\tawait runtime.ensureWorldExists({\n\t\t\t\tid: componentWorldId, // This is now runtime.agentId\n\t\t\t\tname: `Social Alpha World for Agent ${componentWorldId}`,\n\t\t\t\tagentId: runtime.agentId, // The agent responsible for this world\n\t\t\t\tmetadata: {},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger.debug(\n\t\t\t\t`[CommunityInvestor] World ${componentWorldId} already exists or error ensuring world: ${error}`,\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tlogger.debug(\n\t\t\t\t`[CommunityInvestor] Message from ${currentMessageSenderId} in room ${roomId}. Text: \"${content.text?.substring(0, 50)}...\"`,\n\t\t\t);\n\n\t\t\tif (currentMessageSenderId === agentId) {\n\t\t\t\tlogger.debug(\"[CommunityInvestor] Skipping self-message.\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst agentUserState = await runtime.getParticipantUserState(\n\t\t\t\tmessageRoomId,\n\t\t\t\tagentId,\n\t\t\t);\n\t\t\tif (\n\t\t\t\tagentUserState === \"MUTED\" &&\n\t\t\t\t!content.text\n\t\t\t\t\t?.toLowerCase()\n\t\t\t\t\t.includes((runtime.character.name ?? \"\").toLowerCase())\n\t\t\t) {\n\t\t\t\tlogger.debug(\n\t\t\t\t\t\"[CommunityInvestor] Agent muted and not mentioned. Ignoring.\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst recentMessagesForContext = await runtime.getMemories({\n\t\t\t\ttableName: \"messages\",\n\t\t\t\troomId: messageRoomId,\n\t\t\t\tcount: MAX_RECENT_MESSAGES_FOR_CONTEXT,\n\t\t\t\tunique: false,\n\t\t\t});\n\t\t\tconst history = recentMessagesForContext\n\t\t\t\t.slice(0, 10)\n\t\t\t\t.map((msg: Memory) => {\n\t\t\t\t\tconst name =\n\t\t\t\t\t\t(msg.content as Record<string, string>)?.name ??\n\t\t\t\t\t\tmsg.entityId.toString();\n\t\t\t\t\tconst text = msg.content?.text ?? \"\";\n\t\t\t\t\treturn `${name}: ${text}`;\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\n\t\t\tconst combinedPrompt = RELEVANCE_AND_EXTRACTION_TEMPLATE.replace(\n\t\t\t\t\"{{senderName}}\",\n\t\t\t\tString(content.name || currentMessageSenderId.toString()),\n\t\t\t)\n\t\t\t\t.replace(\"{{currentMessageText}}\", String(content.text || \"\"))\n\t\t\t\t.replace(\"{{recentMessagesContext}}\", history);\n\n\t\t\ttype ExtractedRec = {\n\t\t\t\ttokenMentioned: string;\n\t\t\t\tisTicker: boolean;\n\t\t\t\tsentiment: \"positive\" | \"negative\" | \"neutral\";\n\t\t\t\tconviction: \"NONE\" | \"LOW\" | \"MEDIUM\" | \"HIGH\";\n\t\t\t\tquote: string;\n\t\t\t};\n\n\t\t\t// Single combined relevance + extraction call. An empty\n\t\t\t// `recommendations` list means either the message was not\n\t\t\t// crypto-relevant or had nothing actionable — both short-circuit\n\t\t\t// the rest of the pipeline. Wrapped in a standalone trajectory so\n\t\t\t// the event-loop call is anchored even though no Action is active.\n\t\t\tconst extractionResponseRaw = await withStandaloneTrajectory(\n\t\t\t\truntime,\n\t\t\t\t{\n\t\t\t\t\tsource: \"social-alpha-event\",\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\ttype: \"relevance-extraction\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t() =>\n\t\t\t\t\truntime.useModel(ModelType.TEXT_LARGE, {\n\t\t\t\t\t\tprompt: combinedPrompt,\n\t\t\t\t\t}),\n\t\t\t);\n\n\t\t\tconst extractionResult = parseJsonObject<{\n\t\t\t\trecommendations?: ExtractedRec[];\n\t\t\t}>(extractionResponseRaw);\n\n\t\t\tconst extractedRecommendations = extractionResult?.recommendations ?? [];\n\n\t\t\tif (extractedRecommendations.length === 0) {\n\t\t\t\tlogger.debug(\n\t\t\t\t\t\"[CommunityInvestor] No recommendations extracted (not relevant or nothing actionable).\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlogger.info(\n\t\t\t\t`[CommunityInvestor] Found ${extractedRecommendations.length} recommendations to process`,\n\t\t\t);\n\n\t\t\tconst communityInvestorService = runtime.getService(\n\t\t\t\tServiceType.COMMUNITY_INVESTOR,\n\t\t\t) as CommunityInvestorService;\n\t\t\tif (!communityInvestorService) {\n\t\t\t\tlogger.error(\"[CommunityInvestor] Service not found!\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst userProfileComponent = await runtime.getComponent(\n\t\t\t\tcurrentMessageSenderId,\n\t\t\t\tTRUST_MARKETPLACE_COMPONENT_TYPE,\n\t\t\t\tcomponentWorldId,\n\t\t\t\tagentId,\n\t\t\t);\n\t\t\tlet userProfile: UserTrustProfile;\n\n\t\t\tif (!userProfileComponent?.data) {\n\t\t\t\tuserProfile = {\n\t\t\t\t\tversion: \"1.0.0\",\n\t\t\t\t\tuserId: currentMessageSenderId,\n\t\t\t\t\ttrustScore: 0,\n\t\t\t\t\tlastTrustScoreCalculationTimestamp: Date.now(),\n\t\t\t\t\trecommendations: [],\n\t\t\t\t};\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[CommunityInvestor] Initializing new profile for ${currentMessageSenderId}`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tuserProfile = userProfileComponent.data as UserTrustProfile;\n\t\t\t\tif (!Array.isArray(userProfile.recommendations))\n\t\t\t\t\tuserProfile.recommendations = [];\n\t\t\t}\n\n\t\t\tlet profileUpdated = false;\n\n\t\t\tfor (const extractedRec of extractedRecommendations) {\n\t\t\t\tif (\n\t\t\t\t\textractedRec.sentiment === \"neutral\" ||\n\t\t\t\t\t!extractedRec.tokenMentioned?.trim()\n\t\t\t\t) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[CommunityInvestor] Skipping neutral or empty token mention: \"${extractedRec.quote}\"`,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] Extracted rec: ${JSON.stringify(extractedRec)}`,\n\t\t\t\t);\n\n\t\t\t\tlet resolvedToken: {\n\t\t\t\t\taddress: string;\n\t\t\t\t\tchain: SupportedChain;\n\t\t\t\t\tticker?: string;\n\t\t\t\t} | null = null;\n\t\t\t\tconst isTicker =\n\t\t\t\t\textractedRec.isTicker === true ||\n\t\t\t\t\tString(extractedRec.isTicker).toLowerCase() === \"true\";\n\t\t\t\tif (isTicker) {\n\t\t\t\t\tresolvedToken = await communityInvestorService.resolveTicker(\n\t\t\t\t\t\textractedRec.tokenMentioned,\n\t\t\t\t\t\tDEFAULT_CHAIN,\n\t\t\t\t\t\trecentMessagesForContext,\n\t\t\t\t\t);\n\t\t\t\t} else if (\n\t\t\t\t\textractedRec.tokenMentioned.length > 20 &&\n\t\t\t\t\textractedRec.tokenMentioned.match(/^[a-zA-Z0-9]+$/)\n\t\t\t\t) {\n\t\t\t\t\tresolvedToken = {\n\t\t\t\t\t\taddress: extractedRec.tokenMentioned,\n\t\t\t\t\t\tchain: DEFAULT_CHAIN,\n\t\t\t\t\t\tticker: undefined,\n\t\t\t\t\t}; // Address-like strings without chain metadata use the default chain.\n\t\t\t\t} else {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[CommunityInvestor] Invalid address-like token: ${extractedRec.tokenMentioned}`,\n\t\t\t\t\t);\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[E2E TRACE] Token mention ${extractedRec.tokenMentioned} not considered a valid address format.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] resolvedToken for \"${extractedRec.quote}\": ${JSON.stringify(resolvedToken)}`,\n\t\t\t\t);\n\n\t\t\t\tif (!resolvedToken) {\n\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t`[CommunityInvestor] Could not resolve token for: \"${extractedRec.quote}\".`,\n\t\t\t\t\t);\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[E2E TRACE] Skipping rec due to unresolved token: \"${extractedRec.quote}\"`,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] Attempting to get token API data for ${resolvedToken.address}`,\n\t\t\t\t);\n\t\t\t\tconst tokenAPIData = await communityInvestorService.getTokenAPIData(\n\t\t\t\t\tresolvedToken.address,\n\t\t\t\t\tresolvedToken.chain,\n\t\t\t\t);\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] tokenAPIData for ${resolvedToken.address}: ${JSON.stringify(tokenAPIData)}`,\n\t\t\t\t);\n\t\t\t\tconst priceAtRecommendation = tokenAPIData?.currentPrice; // Use current price as of message time\n\n\t\t\t\tconst recTimestamp = createdAt || Date.now();\n\t\t\t\tconst existingRecent = userProfile.recommendations.find(\n\t\t\t\t\t(r) =>\n\t\t\t\t\t\tr.tokenAddress === resolvedToken?.address &&\n\t\t\t\t\t\tr.recommendationType ===\n\t\t\t\t\t\t\t(extractedRec.sentiment === \"positive\" ? \"BUY\" : \"SELL\") &&\n\t\t\t\t\t\trecTimestamp - r.timestamp < RECENT_REC_DUPLICATION_TIMEFRAME_MS,\n\t\t\t\t);\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] Existing recent duplicate check. Target: ${resolvedToken.address}, Type: ${extractedRec.sentiment === \"positive\" ? \"BUY\" : \"SELL\"}`,\n\t\t\t\t);\n\t\t\t\tif (existingRecent) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[CommunityInvestor] Skipping duplicate rec for ${resolvedToken.address}`,\n\t\t\t\t\t);\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[E2E TRACE] Found existing recent duplicate for ${resolvedToken.address}. Skipping.`,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst newRecommendation: Recommendation = {\n\t\t\t\t\tid: asUUID(uuidv4()),\n\t\t\t\t\tuserId: currentMessageSenderId,\n\t\t\t\t\tmessageId:\n\t\t\t\t\t\tmessageId ||\n\t\t\t\t\t\tasUUID(\n\t\t\t\t\t\t\tcreateUniqueUuid(\n\t\t\t\t\t\t\t\truntime,\n\t\t\t\t\t\t\t\t`${currentMessageSenderId}-${recTimestamp}`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\ttimestamp: recTimestamp,\n\t\t\t\t\ttokenTicker: resolvedToken.ticker?.toUpperCase(),\n\t\t\t\t\ttokenAddress: resolvedToken.address,\n\t\t\t\t\tchain: resolvedToken.chain,\n\t\t\t\t\trecommendationType:\n\t\t\t\t\t\textractedRec.sentiment === \"positive\" ? \"BUY\" : \"SELL\",\n\t\t\t\t\tconviction: extractedRec.conviction as Conviction,\n\t\t\t\t\trawMessageQuote: extractedRec.quote,\n\t\t\t\t\tpriceAtRecommendation: priceAtRecommendation, // Store price at time of recommendation\n\t\t\t\t\tprocessedForTradeDecision: false,\n\t\t\t\t};\n\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] newRecommendation created: ${JSON.stringify(newRecommendation)}`,\n\t\t\t\t);\n\n\t\t\t\tuserProfile.recommendations.unshift(newRecommendation);\n\t\t\t\tif (userProfile.recommendations.length > MAX_RECOMMENDATIONS_IN_PROFILE)\n\t\t\t\t\tuserProfile.recommendations.pop();\n\t\t\t\tprofileUpdated = true;\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] profileUpdated is now true. Profile recommendation count: ${userProfile.recommendations.length}`,\n\t\t\t\t);\n\t\t\t\tlogger.info(\n\t\t\t\t\t`[CommunityInvestor] Added ${newRecommendation.recommendationType} rec for user ${currentMessageSenderId}, token ${newRecommendation.tokenAddress}`,\n\t\t\t\t);\n\n\t\t\t\tawait runtime.createTask({\n\t\t\t\t\tname: \"PROCESS_TRADE_DECISION\",\n\t\t\t\t\tdescription: `Process trade decision for rec ${newRecommendation.id}`,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\trecommendationId: newRecommendation.id,\n\t\t\t\t\t\tuserId: currentMessageSenderId,\n\t\t\t\t\t},\n\t\t\t\t\ttags: [\"socialAlpha\", \"tradeDecision\"],\n\t\t\t\t\troomId: messageRoomId,\n\t\t\t\t\tworldId: componentWorldId,\n\t\t\t\t\tentityId: currentMessageSenderId,\n\t\t\t\t});\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[CommunityInvestor] Created PROCESS_TRADE_DECISION task for rec ID ${newRecommendation.id} in room/world ${componentWorldId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tlogger.debug(`[E2E TRACE] After loop, profileUpdated: ${profileUpdated}`);\n\t\t\tif (profileUpdated) {\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`[E2E TRACE] profileUpdated is true. Checking if userProfileComponent exists.`,\n\t\t\t\t);\n\t\t\t\tif (userProfileComponent) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[E2E TRACE] Attempting to update component ${userProfileComponent.id}`,\n\t\t\t\t\t);\n\t\t\t\t\tawait runtime.updateComponent({\n\t\t\t\t\t\t...userProfileComponent,\n\t\t\t\t\t\tdata: userProfile,\n\t\t\t\t\t});\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[CommunityInvestor] Updated component ${userProfileComponent.id} for ${currentMessageSenderId}`,\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconst newComponentId = asUUID(uuidv4());\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`[E2E TRACE] Attempting to create new component with id ${newComponentId} for user ${currentMessageSenderId} in world ${componentWorldId} and room (set to world) ${componentWorldId}`,\n\t\t\t\t\t);\n\t\t\t\t\tawait runtime.createComponent({\n\t\t\t\t\t\tid: newComponentId,\n\t\t\t\t\t\tentityId: currentMessageSenderId,\n\t\t\t\t\t\tagentId: agentId,\n\t\t\t\t\t\tworldId: componentWorldId,\n\t\t\t\t\t\troomId: messageRoomId,\n\t\t\t\t\t\tsourceEntityId: agentId,\n\t\t\t\t\t\ttype: TRUST_MARKETPLACE_COMPONENT_TYPE,\n\t\t\t\t\t\tcreatedAt: Date.now(),\n\t\t\t\t\t\tdata: userProfile,\n\t\t\t\t\t});\n\t\t\t\t\tlogger.info(\n\t\t\t\t\t\t`[CommunityInvestor] Created new component ${newComponentId} for ${currentMessageSenderId} with roomId ${componentWorldId}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Trigger trust score calculation which will also register the user\n\t\t\t\tlogger.info(\n\t\t\t\t\t`[CommunityInvestor] Triggering trust score calculation for user ${currentMessageSenderId}`,\n\t\t\t\t);\n\t\t\t\tawait communityInvestorService.calculateUserTrustScore(\n\t\t\t\t\tcurrentMessageSenderId,\n\t\t\t\t\truntime,\n\t\t\t\t);\n\t\t\t\tlogger.info(\n\t\t\t\t\t`[CommunityInvestor] Trust score calculation completed for user ${currentMessageSenderId}`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tlogger.info(\n\t\t\t\t\t`[CommunityInvestor] Profile NOT updated for message ${messageId}, user ${currentMessageSenderId}. No new valid recommendations extracted or token resolution failed.`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlogger.error(\n\t\t\t\t\"[CommunityInvestor] Error in messageReceivedHandler:\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tlogger.error(\"[CommunityInvestor] Error in messageReceivedHandler:\", error);\n\t}\n};\n\nexport const events = {\n\tMESSAGE_RECEIVED: [messageReceivedHandler],\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LeaderboardEntry } from "../types";
|
|
2
|
+
/** True when the agent has at least one wallet address configured. */
|
|
3
|
+
export declare function hasWalletConfigured(): Promise<boolean>;
|
|
4
|
+
/** Fetch + rank the leaderboard via the plugin's route. */
|
|
5
|
+
export declare function fetchLeaderboardData(): Promise<LeaderboardEntry[]>;
|
|
6
|
+
//# sourceMappingURL=LeaderboardView.helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LeaderboardView.helpers.d.ts","sourceRoot":"","sources":["../../src/frontend/LeaderboardView.helpers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEX,gBAAgB,EAIhB,MAAM,UAAU,CAAC;AA6ClB,sEAAsE;AACtE,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO5D;AAED,2DAA2D;AAC3D,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA2BxE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { client } from "@elizaos/ui/api";
|
|
2
|
+
function jsonRow(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4
|
+
throw new Error("Leaderboard row is not a JSON object");
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
function parseRecommendationRow(record) {
|
|
9
|
+
return {
|
|
10
|
+
id: record.id,
|
|
11
|
+
userId: record.userId,
|
|
12
|
+
messageId: record.messageId,
|
|
13
|
+
timestamp: typeof record.timestamp === "number" ? record.timestamp : Number(record.timestamp),
|
|
14
|
+
tokenTicker: typeof record.tokenTicker === "string" ? record.tokenTicker : void 0,
|
|
15
|
+
tokenAddress: typeof record.tokenAddress === "string" ? record.tokenAddress : String(record.tokenAddress ?? ""),
|
|
16
|
+
chain: record.chain,
|
|
17
|
+
recommendationType: record.recommendationType,
|
|
18
|
+
conviction: record.conviction,
|
|
19
|
+
rawMessageQuote: typeof record.rawMessageQuote === "string" ? record.rawMessageQuote : String(record.rawMessageQuote ?? ""),
|
|
20
|
+
priceAtRecommendation: typeof record.priceAtRecommendation === "number" ? record.priceAtRecommendation : void 0,
|
|
21
|
+
metrics: record.metrics,
|
|
22
|
+
processedForTradeDecision: typeof record.processedForTradeDecision === "boolean" ? record.processedForTradeDecision : void 0
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function hasWalletConfigured() {
|
|
26
|
+
try {
|
|
27
|
+
const addresses = await client.getWalletAddresses();
|
|
28
|
+
return Boolean(addresses?.evmAddress || addresses?.solanaAddress);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function fetchLeaderboardData() {
|
|
34
|
+
const data = await client.fetch(
|
|
35
|
+
"/api/social-alpha/leaderboard"
|
|
36
|
+
);
|
|
37
|
+
const rows = data.data;
|
|
38
|
+
if (!Array.isArray(rows)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
data.message ?? "Leaderboard API response did not include a data array"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const transformed = rows.map((entryRaw) => {
|
|
44
|
+
const entry = jsonRow(entryRaw);
|
|
45
|
+
const recs = Array.isArray(entry.recommendations) ? entry.recommendations : [];
|
|
46
|
+
return {
|
|
47
|
+
userId: entry.userId,
|
|
48
|
+
username: typeof entry.username === "string" ? entry.username : void 0,
|
|
49
|
+
trustScore: typeof entry.trustScore === "number" ? entry.trustScore : 0,
|
|
50
|
+
recommendations: recs.map((rec) => parseRecommendationRow(jsonRow(rec)))
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
return transformed.sort((a, b) => b.trustScore - a.trustScore).map((entry, index) => ({ ...entry, rank: index + 1 }));
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
fetchLeaderboardData,
|
|
57
|
+
hasWalletConfigured
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=LeaderboardView.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/frontend/LeaderboardView.helpers.ts"],"sourcesContent":["import type { UUID } from \"@elizaos/core\";\nimport { client } from \"@elizaos/ui/api\";\nimport type {\n\tConviction,\n\tLeaderboardEntry,\n\tRecommendation,\n\tRecommendationMetric,\n\tSupportedChain,\n} from \"../types.js\";\n\nfunction jsonRow(value: unknown): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new Error(\"Leaderboard row is not a JSON object\");\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction parseRecommendationRow(\n\trecord: Record<string, unknown>,\n): Recommendation {\n\treturn {\n\t\tid: record.id as UUID,\n\t\tuserId: record.userId as UUID,\n\t\tmessageId: record.messageId as UUID,\n\t\ttimestamp:\n\t\t\ttypeof record.timestamp === \"number\"\n\t\t\t\t? record.timestamp\n\t\t\t\t: Number(record.timestamp),\n\t\ttokenTicker:\n\t\t\ttypeof record.tokenTicker === \"string\" ? record.tokenTicker : undefined,\n\t\ttokenAddress:\n\t\t\ttypeof record.tokenAddress === \"string\"\n\t\t\t\t? record.tokenAddress\n\t\t\t\t: String(record.tokenAddress ?? \"\"),\n\t\tchain: record.chain as SupportedChain,\n\t\trecommendationType: record.recommendationType as \"BUY\" | \"SELL\",\n\t\tconviction: record.conviction as Conviction,\n\t\trawMessageQuote:\n\t\t\ttypeof record.rawMessageQuote === \"string\"\n\t\t\t\t? record.rawMessageQuote\n\t\t\t\t: String(record.rawMessageQuote ?? \"\"),\n\t\tpriceAtRecommendation:\n\t\t\ttypeof record.priceAtRecommendation === \"number\"\n\t\t\t\t? record.priceAtRecommendation\n\t\t\t\t: undefined,\n\t\tmetrics: record.metrics as RecommendationMetric | undefined,\n\t\tprocessedForTradeDecision:\n\t\t\ttypeof record.processedForTradeDecision === \"boolean\"\n\t\t\t\t? record.processedForTradeDecision\n\t\t\t\t: undefined,\n\t};\n}\n\n/** True when the agent has at least one wallet address configured. */\nexport async function hasWalletConfigured(): Promise<boolean> {\n\ttry {\n\t\tconst addresses = await client.getWalletAddresses();\n\t\treturn Boolean(addresses?.evmAddress || addresses?.solanaAddress);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Fetch + rank the leaderboard via the plugin's route. */\nexport async function fetchLeaderboardData(): Promise<LeaderboardEntry[]> {\n\tconst data = await client.fetch<{ message?: string; data?: unknown }>(\n\t\t\"/api/social-alpha/leaderboard\",\n\t);\n\tconst rows = data.data;\n\tif (!Array.isArray(rows)) {\n\t\tthrow new Error(\n\t\t\tdata.message ?? \"Leaderboard API response did not include a data array\",\n\t\t);\n\t}\n\n\tconst transformed: LeaderboardEntry[] = rows.map((entryRaw) => {\n\t\tconst entry = jsonRow(entryRaw);\n\t\tconst recs = Array.isArray(entry.recommendations)\n\t\t\t? entry.recommendations\n\t\t\t: [];\n\t\treturn {\n\t\t\tuserId: entry.userId as UUID,\n\t\t\tusername: typeof entry.username === \"string\" ? entry.username : undefined,\n\t\t\ttrustScore: typeof entry.trustScore === \"number\" ? entry.trustScore : 0,\n\t\t\trecommendations: recs.map((rec) => parseRecommendationRow(jsonRow(rec))),\n\t\t};\n\t});\n\n\treturn transformed\n\t\t.sort((a, b) => b.trustScore - a.trustScore)\n\t\t.map((entry, index) => ({ ...entry, rank: index + 1 }));\n}\n"],"mappings":"AACA,SAAS,cAAc;AASvB,SAAS,QAAQ,OAAyC;AACzD,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG;AACxE,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACvD;AACA,SAAO;AACR;AAEA,SAAS,uBACR,QACiB;AACjB,SAAO;AAAA,IACN,IAAI,OAAO;AAAA,IACX,QAAQ,OAAO;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,WACC,OAAO,OAAO,cAAc,WACzB,OAAO,YACP,OAAO,OAAO,SAAS;AAAA,IAC3B,aACC,OAAO,OAAO,gBAAgB,WAAW,OAAO,cAAc;AAAA,IAC/D,cACC,OAAO,OAAO,iBAAiB,WAC5B,OAAO,eACP,OAAO,OAAO,gBAAgB,EAAE;AAAA,IACpC,OAAO,OAAO;AAAA,IACd,oBAAoB,OAAO;AAAA,IAC3B,YAAY,OAAO;AAAA,IACnB,iBACC,OAAO,OAAO,oBAAoB,WAC/B,OAAO,kBACP,OAAO,OAAO,mBAAmB,EAAE;AAAA,IACvC,uBACC,OAAO,OAAO,0BAA0B,WACrC,OAAO,wBACP;AAAA,IACJ,SAAS,OAAO;AAAA,IAChB,2BACC,OAAO,OAAO,8BAA8B,YACzC,OAAO,4BACP;AAAA,EACL;AACD;AAGA,eAAsB,sBAAwC;AAC7D,MAAI;AACH,UAAM,YAAY,MAAM,OAAO,mBAAmB;AAClD,WAAO,QAAQ,WAAW,cAAc,WAAW,aAAa;AAAA,EACjE,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAGA,eAAsB,uBAAoD;AACzE,QAAM,OAAO,MAAM,OAAO;AAAA,IACzB;AAAA,EACD;AACA,QAAM,OAAO,KAAK;AAClB,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACzB,UAAM,IAAI;AAAA,MACT,KAAK,WAAW;AAAA,IACjB;AAAA,EACD;AAEA,QAAM,cAAkC,KAAK,IAAI,CAAC,aAAa;AAC9D,UAAM,QAAQ,QAAQ,QAAQ;AAC9B,UAAM,OAAO,MAAM,QAAQ,MAAM,eAAe,IAC7C,MAAM,kBACN,CAAC;AACJ,WAAO;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,UAAU,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;AAAA,MAChE,YAAY,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa;AAAA,MACtE,iBAAiB,KAAK,IAAI,CAAC,QAAQ,uBAAuB,QAAQ,GAAG,CAAC,CAAC;AAAA,IACxE;AAAA,EACD,CAAC;AAED,SAAO,YACL,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAC1C,IAAI,CAAC,OAAO,WAAW,EAAE,GAAG,OAAO,MAAM,QAAQ,EAAE,EAAE;AACxD;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SocialAlphaSpatialView — the alpha trust leaderboard authored once with the
|
|
3
|
+
* spatial vocabulary, so it renders correctly wherever it is displayed:
|
|
4
|
+
*
|
|
5
|
+
* - GUI / XR — mounted in `<SpatialSurface>` (DOM; XR scales up).
|
|
6
|
+
* - TUI — rendered to real terminal lines by the agent terminal, via
|
|
7
|
+
* `registerSpatialTerminalView` (see `../register-terminal-view.tsx`).
|
|
8
|
+
*
|
|
9
|
+
* It is purely presentational (a snapshot + an action callback in, primitives
|
|
10
|
+
* out) and imports only the cross-modality primitives, so it is safe to render
|
|
11
|
+
* in the Node agent process where the terminal lives (no browser/client import).
|
|
12
|
+
*
|
|
13
|
+
* Every derived value — the leading-caller line, each row's formatted trust
|
|
14
|
+
* score — is computed in the data wrapper ({@link ./SocialAlphaView.tsx}) and
|
|
15
|
+
* handed in already formatted; this component never fetches or computes — it
|
|
16
|
+
* displays the snapshot and dispatches actions.
|
|
17
|
+
*/
|
|
18
|
+
/** A single leaderboard caller, already projected to display shape. */
|
|
19
|
+
export interface LeaderRow {
|
|
20
|
+
/** Stable id used to key rows and build the open-caller action. */
|
|
21
|
+
userId: string;
|
|
22
|
+
/** Pre-formatted rank label (e.g. "1"), or empty when unranked. */
|
|
23
|
+
rank: string;
|
|
24
|
+
/** Display name (username, or a short id when anonymous). */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Pre-formatted trust score (e.g. "12.50"). */
|
|
27
|
+
score: string;
|
|
28
|
+
}
|
|
29
|
+
/** Which render state the leaderboard is in. */
|
|
30
|
+
export type SocialAlphaViewState = "loading" | "wallet-required" | "error" | "empty" | "ready";
|
|
31
|
+
export interface SocialAlphaSnapshot {
|
|
32
|
+
/** The leaderboard state machine. */
|
|
33
|
+
state: SocialAlphaViewState;
|
|
34
|
+
/** Ranked callers (only meaningful when state === "ready"). */
|
|
35
|
+
rows: LeaderRow[];
|
|
36
|
+
/** Pre-formatted leading-caller line, or empty when none. */
|
|
37
|
+
leading: string;
|
|
38
|
+
/** Error message when state === "error". */
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare const EMPTY_SOCIAL_ALPHA_SNAPSHOT: SocialAlphaSnapshot;
|
|
42
|
+
export interface SocialAlphaSpatialViewProps {
|
|
43
|
+
snapshot: SocialAlphaSnapshot;
|
|
44
|
+
/**
|
|
45
|
+
* Dispatch by action id: `retry` (reload after an error),
|
|
46
|
+
* `connect-wallet` (route a wallet-setup request), `open:<userId>`
|
|
47
|
+
* (drill into a caller through chat).
|
|
48
|
+
*/
|
|
49
|
+
onAction?: (action: string) => void;
|
|
50
|
+
}
|
|
51
|
+
export declare function SocialAlphaSpatialView({ snapshot, onAction, }: SocialAlphaSpatialViewProps): import("react/jsx-runtime").JSX.Element;
|
|
52
|
+
//# sourceMappingURL=SocialAlphaSpatialView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SocialAlphaSpatialView.d.ts","sourceRoot":"","sources":["../../src/frontend/SocialAlphaSpatialView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACzB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;CACd;AAED,gDAAgD;AAChD,MAAM,MAAM,oBAAoB,GAC7B,SAAS,GACT,iBAAiB,GACjB,OAAO,GACP,OAAO,GACP,OAAO,CAAC;AAEX,MAAM,WAAW,mBAAmB;IACnC,qCAAqC;IACrC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,2BAA2B,EAAE,mBAIzC,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,sBAAsB,CAAC,EACtC,QAAQ,EACR,QAAQ,GACR,EAAE,2BAA2B,2CAoB7B"}
|