pyre-agent-kit 1.0.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/dist/action.d.ts +3 -0
- package/dist/action.js +98 -0
- package/dist/agent.d.ts +4 -0
- package/dist/agent.js +276 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +475 -0
- package/dist/defaults.d.ts +10 -0
- package/dist/defaults.js +109 -0
- package/dist/error.d.ts +6 -0
- package/dist/error.js +66 -0
- package/dist/executor.d.ts +24 -0
- package/dist/executor.js +409 -0
- package/dist/faction.d.ts +10 -0
- package/dist/faction.js +110 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +220 -0
- package/dist/stronghold.d.ts +7 -0
- package/dist/stronghold.js +88 -0
- package/dist/tx.d.ts +2 -0
- package/dist/tx.js +40 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +2 -0
- package/dist/util.d.ts +6 -0
- package/dist/util.js +19 -0
- package/package.json +30 -0
- package/readme.md +556 -0
package/dist/action.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Action, AgentState, FactionInfo, Personality } from './types';
|
|
2
|
+
export declare const chooseAction: (personality: Personality, agent: AgentState, canRally: boolean, knownFactions: FactionInfo[]) => Action;
|
|
3
|
+
export declare const sentimentBuySize: (agent: AgentState, factionMint: string) => number;
|
package/dist/action.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sentimentBuySize = exports.chooseAction = void 0;
|
|
4
|
+
const defaults_1 = require("./defaults");
|
|
5
|
+
const ALL_ACTIONS = [
|
|
6
|
+
'join', 'defect', 'rally', 'launch', 'message',
|
|
7
|
+
'stronghold', 'war_loan', 'repay_loan', 'siege', 'ascend', 'raze', 'tithe',
|
|
8
|
+
'infiltrate', 'fud',
|
|
9
|
+
];
|
|
10
|
+
const chooseAction = (personality, agent, canRally, knownFactions) => {
|
|
11
|
+
const weights = [...defaults_1.PERSONALITY_WEIGHTS[personality]];
|
|
12
|
+
const hasHoldings = agent.holdings.size > 0;
|
|
13
|
+
const heldMints = [...agent.holdings.keys()];
|
|
14
|
+
const rivalFactions = knownFactions.filter(f => !heldMints.includes(f.mint));
|
|
15
|
+
if (!hasHoldings) {
|
|
16
|
+
weights[0] += weights[1];
|
|
17
|
+
weights[1] = 0;
|
|
18
|
+
}
|
|
19
|
+
if (!canRally) {
|
|
20
|
+
weights[0] += weights[2];
|
|
21
|
+
weights[2] = 0;
|
|
22
|
+
}
|
|
23
|
+
if (agent.hasStronghold) {
|
|
24
|
+
weights[0] += weights[5];
|
|
25
|
+
weights[5] = 0;
|
|
26
|
+
}
|
|
27
|
+
const ascendedFactions = knownFactions.filter(f => f.status === 'ascended');
|
|
28
|
+
const holdsAscended = ascendedFactions.some(f => agent.holdings.has(f.mint));
|
|
29
|
+
if (!holdsAscended) {
|
|
30
|
+
weights[0] += weights[6];
|
|
31
|
+
weights[6] = 0;
|
|
32
|
+
}
|
|
33
|
+
if (agent.activeLoans.size === 0) {
|
|
34
|
+
weights[0] += weights[7];
|
|
35
|
+
weights[7] = 0;
|
|
36
|
+
}
|
|
37
|
+
if (ascendedFactions.length === 0) {
|
|
38
|
+
weights[0] += weights[8];
|
|
39
|
+
weights[8] = 0;
|
|
40
|
+
}
|
|
41
|
+
const readyFactions = knownFactions.filter(f => f.status === 'ready');
|
|
42
|
+
if (readyFactions.length === 0) {
|
|
43
|
+
weights[0] += weights[9];
|
|
44
|
+
weights[9] = 0;
|
|
45
|
+
}
|
|
46
|
+
const risingFactions = knownFactions.filter(f => f.status === 'rising');
|
|
47
|
+
if (risingFactions.length === 0) {
|
|
48
|
+
weights[0] += weights[10];
|
|
49
|
+
weights[10] = 0;
|
|
50
|
+
}
|
|
51
|
+
if (rivalFactions.length === 0) {
|
|
52
|
+
weights[0] += weights[12];
|
|
53
|
+
weights[12] = 0;
|
|
54
|
+
}
|
|
55
|
+
if (!hasHoldings) {
|
|
56
|
+
weights[0] += weights[13];
|
|
57
|
+
weights[13] = 0;
|
|
58
|
+
}
|
|
59
|
+
if (agent.infiltrated.size > 0) {
|
|
60
|
+
weights[1] += 0.10;
|
|
61
|
+
}
|
|
62
|
+
if (ascendedFactions.length > 0) {
|
|
63
|
+
if (holdsAscended) {
|
|
64
|
+
weights[6] += 0.15;
|
|
65
|
+
}
|
|
66
|
+
weights[8] += 0.12;
|
|
67
|
+
if (agent.activeLoans.size > 0) {
|
|
68
|
+
weights[7] += 0.06;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const total = weights.reduce((a, b) => a + b, 0);
|
|
72
|
+
const roll = Math.random() * total;
|
|
73
|
+
let cumulative = 0;
|
|
74
|
+
for (let i = 0; i < weights.length; i++) {
|
|
75
|
+
cumulative += weights[i];
|
|
76
|
+
if (roll < cumulative)
|
|
77
|
+
return ALL_ACTIONS[i];
|
|
78
|
+
}
|
|
79
|
+
return 'join';
|
|
80
|
+
};
|
|
81
|
+
exports.chooseAction = chooseAction;
|
|
82
|
+
const sentimentBuySize = (agent, factionMint) => {
|
|
83
|
+
const [minSol, maxSol] = defaults_1.PERSONALITY_SOL[agent.personality];
|
|
84
|
+
const sentiment = agent.sentiment.get(factionMint) ?? 0;
|
|
85
|
+
const sentimentFactor = (sentiment + 10) / 20;
|
|
86
|
+
const convictionScale = {
|
|
87
|
+
loyalist: 1.5,
|
|
88
|
+
mercenary: 2.0,
|
|
89
|
+
provocateur: 1.2,
|
|
90
|
+
scout: 0.8,
|
|
91
|
+
whale: 2.5,
|
|
92
|
+
};
|
|
93
|
+
const scale = convictionScale[agent.personality];
|
|
94
|
+
const base = minSol + (maxSol - minSol) * sentimentFactor;
|
|
95
|
+
const multiplier = 0.5 + sentimentFactor * scale;
|
|
96
|
+
return Math.max(minSol * 0.5, base * multiplier);
|
|
97
|
+
};
|
|
98
|
+
exports.sentimentBuySize = sentimentBuySize;
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AgentState, FactionInfo, LLMAdapter, LLMDecision } from './types';
|
|
2
|
+
import { Connection } from '@solana/web3.js';
|
|
3
|
+
export declare const buildAgentPrompt: (agent: AgentState, factions: FactionInfo[], leaderboardSnippet: string, intelSnippet: string, recentMessages: string[], solRange?: [number, number]) => string;
|
|
4
|
+
export declare function llmDecide(agent: AgentState, factions: FactionInfo[], connection: Connection, recentMessages: string[], llm: LLMAdapter, log: (msg: string) => void, solRange?: [number, number]): Promise<LLMDecision | null>;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAgentPrompt = void 0;
|
|
4
|
+
exports.llmDecide = llmDecide;
|
|
5
|
+
const pyre_world_kit_1 = require("pyre-world-kit");
|
|
6
|
+
const defaults_1 = require("./defaults");
|
|
7
|
+
const util_1 = require("./util");
|
|
8
|
+
const faction_1 = require("./faction");
|
|
9
|
+
const buildAgentPrompt = (agent, factions, leaderboardSnippet, intelSnippet, recentMessages, solRange) => {
|
|
10
|
+
const [minSol, maxSol] = solRange ?? defaults_1.PERSONALITY_SOL[agent.personality];
|
|
11
|
+
const holdingsList = [...agent.holdings.entries()]
|
|
12
|
+
.map(([mint, bal]) => {
|
|
13
|
+
const f = factions.find(ff => ff.mint === mint);
|
|
14
|
+
return f ? `${f.symbol}: ${bal} tokens` : `${mint.slice(0, 8)}: ${bal} tokens`;
|
|
15
|
+
})
|
|
16
|
+
.join(', ') || 'none';
|
|
17
|
+
const factionList = factions.slice(0, 10).map(f => f.symbol).join(', ');
|
|
18
|
+
const history = agent.recentHistory.slice(-5).join('; ') || 'no recent actions';
|
|
19
|
+
const sentimentList = [...agent.sentiment.entries()]
|
|
20
|
+
.map(([mint, score]) => {
|
|
21
|
+
const f = factions.find(ff => ff.mint === mint);
|
|
22
|
+
const label = score > 3 ? 'bullish' : score < -3 ? 'bearish' : 'neutral';
|
|
23
|
+
return f ? `${f.symbol}: ${label} (${score > 0 ? '+' : ''}${score})` : null;
|
|
24
|
+
})
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(', ') || 'no strong feelings yet';
|
|
27
|
+
const allyList = agent.allies.size > 0 ? [...agent.allies].map(a => a.slice(0, 8)).join(', ') : 'none';
|
|
28
|
+
const rivalList = agent.rivals.size > 0 ? [...agent.rivals].map(a => a.slice(0, 8)).join(', ') : 'none';
|
|
29
|
+
const doNotRepeat = recentMessages.length > 0
|
|
30
|
+
? `\nDO NOT SAY anything similar to these recent messages from other agents:\n${recentMessages.map(m => `- "${m}"`).join('\n')}\n`
|
|
31
|
+
: '';
|
|
32
|
+
const voiceNudge = (0, util_1.pick)(defaults_1.VOICE_NUDGES);
|
|
33
|
+
return `You are an autonomous agent in Pyre, a faction warfare and strategy game on Solana, where you form both alliances and make enemies while trying to build the most powerful factions. Factions are like rival guilds — each with its own treasury, members, and reputation. You have your own opinions, allegiances, and grudges. Talk trash, call out agents, flex your position, challenge rivals, and coordinate with allies. Think competitive guild chat with real stakes. You make ONE decision per turn.
|
|
34
|
+
|
|
35
|
+
SYMBOL is the token ticker from the leaderboard above (e.g. ${factions.slice(0, 3).map(f => f.symbol).join(', ') || 'STD, INC'}). NOT an address or wallet. ACTIONS that do not contain "message" do not accept a message and will not parse if a message is included.
|
|
36
|
+
|
|
37
|
+
RULES:
|
|
38
|
+
- Respond with EXACTLY one line, e.g.: JOIN ${factions[0]?.symbol || 'IRON'} "deploying capital, let's build"
|
|
39
|
+
- To mention an agent: @address (e.g. @${Math.random().toString(36).slice(2, 10)})
|
|
40
|
+
- The second word MUST be one of these faction symbols: ${factions.slice(0, 10).map(f => f.symbol).join(', ') || 'STD, INC'}. NOTHING ELSE is valid. Random alphanumeric strings like FVw8uGKk, CPQNA2G1, 3cAS5vEm are WALLET addresses, NOT faction symbols. Never use them as the second word.
|
|
41
|
+
- Messages must be under 80 characters, plain English, one short sentence
|
|
42
|
+
- Use "" for no message
|
|
43
|
+
- NO hashtags, NO angle brackets <>
|
|
44
|
+
- NO generic crypto slang
|
|
45
|
+
|
|
46
|
+
ACTIONS (pick exactly one — every action with "message" lets you talk in comms at the same time):
|
|
47
|
+
- JOIN SYMBOL "message" — buy into a faction AND OPTIONALLY post a message (grow your position)
|
|
48
|
+
- DEFECT SYMBOL "message" — sell tokens AND OPTIONALLY post a message (take profits or cut losses)
|
|
49
|
+
- REINFORCE SYMBOL "message" — increase your position in a faction AND OPTIONALLY post a message (grow your position)
|
|
50
|
+
- FUD SYMBOL "message" — micro sell + trash talk a faction you hold (spread fear, call out agents)
|
|
51
|
+
- INFILTRATE SYMBOL "message" — secretly join a rival to dump later AND OPTIONALLY post a message
|
|
52
|
+
- MESSAGE SYMBOL "message" — post in comms only (no buy/sell, just talk)
|
|
53
|
+
- RALLY SYMBOL — show support (one-time per faction, no message)
|
|
54
|
+
- WAR_LOAN SYMBOL — borrow SOL against collateral
|
|
55
|
+
- REPAY_LOAN SYMBOL — repay a loan
|
|
56
|
+
- SIEGE SYMBOL — liquidate undercollateralized loan
|
|
57
|
+
- LAUNCH "name" — create a new faction
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
${(0, faction_1.generateDynamicExamples)(factions, agent)}
|
|
61
|
+
|
|
62
|
+
The goal is to WIN. Accumulate power, dominate the leaderboard, crush rivals, and make your faction the strongest. Every action should move you closer to the top.
|
|
63
|
+
|
|
64
|
+
Your address: ${agent.publicKey.slice(0, 8)}
|
|
65
|
+
Personality: ${agent.personality} — ${defaults_1.personalityDesc[agent.personality]}
|
|
66
|
+
Voice this turn: ${voiceNudge}
|
|
67
|
+
|
|
68
|
+
Holdings: ${holdingsList}
|
|
69
|
+
Sentiment: ${sentimentList}
|
|
70
|
+
Spend Limit: min ${minSol} | max ${maxSol}
|
|
71
|
+
Active loans: ${agent.activeLoans.size > 0 ? [...agent.activeLoans].map(m => { const f = factions.find(ff => ff.mint === m); return f?.symbol ?? m.slice(0, 8); }).join(', ') : 'none'}
|
|
72
|
+
Allies: ${allyList} | Rivals: ${rivalList}
|
|
73
|
+
Recent: ${history}
|
|
74
|
+
|
|
75
|
+
Active factions: ${factionList}
|
|
76
|
+
Leaderboard preview: ${leaderboardSnippet}
|
|
77
|
+
Intel preview: ${intelSnippet}
|
|
78
|
+
${doNotRepeat}
|
|
79
|
+
|
|
80
|
+
Prefer actions that move tokens AND include a message — JOIN, DEFECT, FUD, INFILTRATE, REINFORCE all let you trade AND talk at the same time. However, comms are where the real game happens — trash talk, alliances, intel drops, call-outs, and power plays. Be specific. Reference real agents, real numbers, real moves. Generic messages are boring. Have an opinion and say it loud.. Mix it up — trade often, but keep the comms active too.
|
|
81
|
+
|
|
82
|
+
Your response (one line only):`;
|
|
83
|
+
};
|
|
84
|
+
exports.buildAgentPrompt = buildAgentPrompt;
|
|
85
|
+
function parseLLMDecision(raw, factions, agent, solRange) {
|
|
86
|
+
const lines = raw.split('\n').filter(l => l.trim().length > 0);
|
|
87
|
+
if (lines.length === 0)
|
|
88
|
+
return null;
|
|
89
|
+
for (const candidate of lines) {
|
|
90
|
+
const line = candidate.trim();
|
|
91
|
+
const cleaned = line.replace(/^[-*•>#\d.)\s]+/, '').replace(/^(?:WARNING|NOTE|RESPONSE|OUTPUT|ANSWER|RESULT|SCPRT|SCRIPT)\s*:?\s*/i, '').replace(/^ACTION\s+/i, '')
|
|
92
|
+
// Normalize Cyrillic lookalikes to Latin
|
|
93
|
+
.replace(/[АаА]/g, 'A').replace(/[Вв]/g, 'B').replace(/[Сс]/g, 'C').replace(/[Ее]/g, 'E')
|
|
94
|
+
.replace(/[Нн]/g, 'H').replace(/[Кк]/g, 'K').replace(/[Мм]/g, 'M').replace(/[Оо]/g, 'O')
|
|
95
|
+
.replace(/[Рр]/g, 'P').replace(/[Тт]/g, 'T').replace(/[Уу]/g, 'U').replace(/[Хх]/g, 'X')
|
|
96
|
+
.replace(/[фФ]/g, 'f').replace(/[иИ]/g, 'i').replace(/[лЛ]/g, 'l').replace(/[дД]/g, 'd')
|
|
97
|
+
.replace(/\\/g, ''); // strip backslash escapes
|
|
98
|
+
let normalized = cleaned;
|
|
99
|
+
const upper = cleaned.toUpperCase();
|
|
100
|
+
const knownSymbols = factions.map(f => f.symbol.toUpperCase());
|
|
101
|
+
const actionKeys = Object.keys(defaults_1.ACTION_MAP).sort((a, b) => b.length - a.length);
|
|
102
|
+
for (const key of actionKeys) {
|
|
103
|
+
if (upper.startsWith(key)) {
|
|
104
|
+
const rest = cleaned.slice(key.length);
|
|
105
|
+
if (rest.length > 0 && rest[0] !== ' ' && rest[0] !== '"') {
|
|
106
|
+
const trimmedRest = rest.replace(/^[_\-]+/, '');
|
|
107
|
+
const restUpper = trimmedRest.toUpperCase();
|
|
108
|
+
const matchedSymbol = knownSymbols.find(s => restUpper.startsWith(s));
|
|
109
|
+
if (matchedSymbol) {
|
|
110
|
+
normalized = defaults_1.ACTION_MAP[key] + ' ' + trimmedRest;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
normalized = defaults_1.ACTION_MAP[key] + rest;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const match = normalized.match(/^(JOIN|DEFECT|RALLY|LAUNCH|MESSAGE|STRONGHOLD|WAR_LOAN|REPAY_LOAN|SIEGE|ASCEND|RAZE|TITHE|INFILTRATE|FUD)\s*(?:"([^"]+)"|(\S+))?(?:\s+"([^"]*)")?/i);
|
|
121
|
+
if (match) {
|
|
122
|
+
return parseLLMMatch(match, factions, agent, line, solRange);
|
|
123
|
+
}
|
|
124
|
+
// Bare ticker without action — default to MESSAGE
|
|
125
|
+
const bareUpper = cleaned.toUpperCase().replace(/^[<\[\s]+|[>\]\s]+$/g, '');
|
|
126
|
+
const bareFaction = factions.find(f => bareUpper.startsWith(f.symbol.toUpperCase()));
|
|
127
|
+
if (bareFaction) {
|
|
128
|
+
const rest = cleaned.slice(bareFaction.symbol.length).trim();
|
|
129
|
+
const msgMatch = rest.match(/^"([^"]*)"/);
|
|
130
|
+
const msg = msgMatch?.[1]?.trim();
|
|
131
|
+
if (msg && msg.length > 1) {
|
|
132
|
+
return { action: 'message', faction: bareFaction.symbol, message: msg.slice(0, 140), reasoning: line };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function parseLLMMatch(match, factions, agent, line, solRange) {
|
|
139
|
+
const rawAction = match[1].toLowerCase();
|
|
140
|
+
const action = rawAction;
|
|
141
|
+
const target = match[2] || match[3];
|
|
142
|
+
const rawMsg = match[4]?.trim()
|
|
143
|
+
?.replace(/^[\\\/]+/, '')
|
|
144
|
+
?.replace(/[\\\/]+$/, '')
|
|
145
|
+
?.replace(/^["']+|["']+$/g, '')
|
|
146
|
+
?.replace(/^<+/, '')
|
|
147
|
+
?.replace(/>+\s*$/, '')
|
|
148
|
+
?.replace(/#\w+/g, '')
|
|
149
|
+
?.trim();
|
|
150
|
+
const message = rawMsg && rawMsg.length > 1 ? rawMsg.slice(0, 80) : undefined;
|
|
151
|
+
if (action === 'stronghold') {
|
|
152
|
+
if (agent.hasStronghold)
|
|
153
|
+
return null;
|
|
154
|
+
return { action, reasoning: line };
|
|
155
|
+
}
|
|
156
|
+
if (action === 'launch') {
|
|
157
|
+
return { action: 'launch', message: target, reasoning: line };
|
|
158
|
+
}
|
|
159
|
+
const cleanTarget = target?.replace(/^[<\[]+|[>\]]+$/g, '');
|
|
160
|
+
const targetLower = cleanTarget?.toLowerCase();
|
|
161
|
+
let faction = factions.find(f => f.symbol.toLowerCase() === targetLower);
|
|
162
|
+
if (!faction && targetLower && targetLower.length >= 2) {
|
|
163
|
+
faction = factions.find(f => f.symbol.toLowerCase().startsWith(targetLower)) ||
|
|
164
|
+
factions.find(f => targetLower.startsWith(f.symbol.toLowerCase()));
|
|
165
|
+
if (!faction) {
|
|
166
|
+
const stripped = targetLower.replace(/[aeiou]/g, '');
|
|
167
|
+
faction = factions.find(f => f.symbol.toLowerCase().replace(/[aeiou]/g, '') === stripped);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (action === 'defect' && (!faction || !agent.holdings.has(faction.mint)))
|
|
171
|
+
return null;
|
|
172
|
+
if (action === 'rally' && (!faction || agent.rallied.has(faction.mint)))
|
|
173
|
+
return null;
|
|
174
|
+
if ((action === 'join' || action === 'message') && !faction)
|
|
175
|
+
return null;
|
|
176
|
+
if (action === 'war_loan' && (!faction || !agent.holdings.has(faction.mint) || faction.status !== 'ascended'))
|
|
177
|
+
return null;
|
|
178
|
+
if (action === 'repay_loan' && (!faction || !agent.activeLoans.has(faction.mint)))
|
|
179
|
+
return null;
|
|
180
|
+
if (action === 'siege' && (!faction || faction.status !== 'ascended'))
|
|
181
|
+
return null;
|
|
182
|
+
if ((action === 'ascend' || action === 'raze' || action === 'tithe') && !faction)
|
|
183
|
+
return null;
|
|
184
|
+
if (action === 'infiltrate' && !faction)
|
|
185
|
+
return null;
|
|
186
|
+
if (action === 'fud' && faction && !agent.holdings.has(faction.mint)) {
|
|
187
|
+
return { action: 'message', faction: faction.symbol, message, reasoning: line };
|
|
188
|
+
}
|
|
189
|
+
if (action === 'fud' && !faction)
|
|
190
|
+
return null;
|
|
191
|
+
const [minSol, maxSol] = solRange ?? defaults_1.PERSONALITY_SOL[agent.personality];
|
|
192
|
+
const sol = (0, util_1.randRange)(minSol, maxSol);
|
|
193
|
+
return {
|
|
194
|
+
action,
|
|
195
|
+
faction: faction?.symbol,
|
|
196
|
+
sol,
|
|
197
|
+
message,
|
|
198
|
+
reasoning: line,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function llmDecide(agent, factions, connection, recentMessages, llm, log, solRange) {
|
|
202
|
+
let leaderboardSnippet = '';
|
|
203
|
+
try {
|
|
204
|
+
const lb = await (0, pyre_world_kit_1.getFactionLeaderboard)(connection, { limit: 5 });
|
|
205
|
+
if (lb.length > 0) {
|
|
206
|
+
leaderboardSnippet = 'LEADERBOARD:\n' + lb.map((f, i) => ` ${i + 1}. [${f.symbol}] ${f.name} — power: ${f.score.toFixed(1)}, members: ${f.members}`).join('\n');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
leaderboardSnippet = '(leaderboard unavailable)';
|
|
211
|
+
}
|
|
212
|
+
let intelSnippet = '';
|
|
213
|
+
try {
|
|
214
|
+
const heldMints = [...agent.holdings.keys()];
|
|
215
|
+
const heldFactions = factions.filter(f => heldMints.includes(f.mint));
|
|
216
|
+
const otherFactions = factions.filter(f => !heldMints.includes(f.mint));
|
|
217
|
+
const toScout = [
|
|
218
|
+
...heldFactions.slice(0, 2),
|
|
219
|
+
...(otherFactions.length > 0 ? [(0, util_1.pick)(otherFactions)] : []),
|
|
220
|
+
];
|
|
221
|
+
if (toScout.length > 0) {
|
|
222
|
+
const intels = await Promise.all(toScout.map(f => (0, faction_1.fetchFactionIntel)(connection, f)));
|
|
223
|
+
const lines = intels.map(intel => {
|
|
224
|
+
const memberInfo = intel.totalMembers > 0
|
|
225
|
+
? `${intel.totalMembers} members, top holder: ${intel.members[0]?.percentage.toFixed(1)}%`
|
|
226
|
+
: 'no members';
|
|
227
|
+
const commsInfo = intel.recentComms.length > 0
|
|
228
|
+
? intel.recentComms.slice(0, 3).map(c => `@${c.sender.slice(0, 8)} said: "${c.memo.replace(/^<+/, '').replace(/>+\s*$/, '')}"`).join(', ')
|
|
229
|
+
: 'no recent comms';
|
|
230
|
+
return ` [${intel.symbol}] ${memberInfo} | recent comms: ${commsInfo}`;
|
|
231
|
+
});
|
|
232
|
+
intelSnippet = 'FACTION INTEL:\n' + lines.join('\n');
|
|
233
|
+
// Update sentiment based on comms
|
|
234
|
+
for (const intel of intels) {
|
|
235
|
+
const faction = toScout.find(f => f.symbol === intel.symbol);
|
|
236
|
+
if (!faction)
|
|
237
|
+
continue;
|
|
238
|
+
const current = agent.sentiment.get(faction.mint) ?? 0;
|
|
239
|
+
for (const c of intel.recentComms) {
|
|
240
|
+
const text = c.memo.toLowerCase();
|
|
241
|
+
const positive = /strong|rally|bull|pump|rising|hold|loyal|power|growing|moon/;
|
|
242
|
+
const negative = /weak|dump|bear|dead|fail|raze|crash|abandon|scam|rug/;
|
|
243
|
+
if (positive.test(text))
|
|
244
|
+
agent.sentiment.set(faction.mint, Math.min(10, current + 1));
|
|
245
|
+
if (negative.test(text))
|
|
246
|
+
agent.sentiment.set(faction.mint, Math.max(-10, current - 1));
|
|
247
|
+
if (c.sender !== agent.publicKey) {
|
|
248
|
+
const held = [...agent.holdings.keys()];
|
|
249
|
+
if (held.includes(faction.mint)) {
|
|
250
|
+
if (positive.test(text))
|
|
251
|
+
agent.allies.add(c.sender);
|
|
252
|
+
if (negative.test(text)) {
|
|
253
|
+
agent.rivals.add(c.sender);
|
|
254
|
+
agent.allies.delete(c.sender);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// intel fetch failed, proceed without it
|
|
264
|
+
}
|
|
265
|
+
const prompt = (0, exports.buildAgentPrompt)(agent, factions, leaderboardSnippet, intelSnippet, recentMessages, solRange);
|
|
266
|
+
const raw = await llm.generate(prompt);
|
|
267
|
+
if (!raw) {
|
|
268
|
+
log(`[${agent.publicKey.slice(0, 8)}] LLM returned null`);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
const result = parseLLMDecision(raw, factions, agent, solRange);
|
|
272
|
+
if (!result) {
|
|
273
|
+
log(`[${agent.publicKey.slice(0, 8)}] LLM parse fail: "${raw.slice(0, 100)}"`);
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|