From 43bc95d7d223c1f7c8a2920c4fa9ce13804be1d1 Mon Sep 17 00:00:00 2001 From: Shaw Date: Thu, 13 Feb 2025 00:04:31 -0500 Subject: [PATCH] twitter client is posting from discord, swarm is swarming --- packages/agent/package.json | 2 +- .../agent/src/swarm/communityManager/index.ts | 171 +++++++++++++----- .../src/swarm/complianceOfficer/index.ts | 129 ++++++++++++- packages/agent/src/swarm/defaultSwarm.ts | 8 +- .../src/swarm/socialMediaManager/index.ts | 75 +++++--- packages/core/__tests__/parsing.test.ts | 6 +- packages/core/src/generation.ts | 22 ++- packages/core/src/parsing.ts | 5 +- packages/core/src/runtime.ts | 61 +++++-- packages/core/src/types.ts | 13 +- packages/plugin-discord/src/constants.ts | 2 + packages/plugin-discord/src/index.ts | 6 +- packages/plugin-discord/src/templates.ts | 36 ++-- packages/plugin-solana/src/actions/swap.ts | 7 +- packages/plugin-solana/src/client.ts | 10 +- packages/plugin-solana/src/constants.ts | 1 + packages/plugin-solana/src/index.ts | 2 +- .../plugin-telegram/src/telegramClient.ts | 5 +- packages/plugin-telegram/src/templates.ts | 36 ++-- packages/plugin-twitter/src/actions/post.ts | 72 ++++---- packages/plugin-twitter/src/base.ts | 10 +- packages/plugin-twitter/src/client/api.ts | 2 +- packages/plugin-twitter/src/client/auth.ts | 4 +- packages/plugin-twitter/src/client/grok.ts | 4 +- packages/plugin-twitter/src/client/profile.ts | 6 +- packages/plugin-twitter/src/client/scraper.ts | 4 +- packages/plugin-twitter/src/client/search.ts | 4 +- .../spaces/plugins/IdleMonitorPlugin.ts | 2 +- packages/plugin-twitter/src/client/tweets.ts | 18 +- packages/plugin-twitter/src/constants.ts | 1 + packages/plugin-twitter/src/index.ts | 18 +- packages/plugin-twitter/src/interactions.ts | 2 +- packages/plugin-twitter/src/spaces.ts | 8 +- packages/plugin-twitter/src/templates.ts | 38 ++-- packages/plugin-twitter/src/types.ts | 13 ++ 35 files changed, 541 insertions(+), 262 deletions(-) create mode 100644 packages/plugin-solana/src/constants.ts create mode 100644 packages/plugin-twitter/src/constants.ts diff --git a/packages/agent/package.json b/packages/agent/package.json index cdde98e17d6..a4ad57f93ef 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "start": "node --loader ts-node/esm src/index.ts", - "swarm": "node --loader ts-node/esm src/index.ts --swarm", + "swarm": "LOG_LEVEL=debug node --loader ts-node/esm src/index.ts --swarm", "dev": "node --loader ts-node/esm src/index.ts", "check-types": "tsc --noEmit", "test": "vitest" diff --git a/packages/agent/src/swarm/communityManager/index.ts b/packages/agent/src/swarm/communityManager/index.ts index ad28af66df7..99062389024 100644 --- a/packages/agent/src/swarm/communityManager/index.ts +++ b/packages/agent/src/swarm/communityManager/index.ts @@ -5,6 +5,9 @@ import { Character } from "@elizaos/core"; +import dotenv from "dotenv"; +dotenv.config({ path: '../../.env' }); + const character: Character = { name: "Ruby", plugins: [ @@ -13,23 +16,27 @@ const character: Character = { "@elizaos/plugin-discord", "@elizaos/plugin-node", ], + settings: { + secrets: { + "DISCORD_APPLICATION_ID": process.env.COMMUNITY_MANAGER_DISCORD_APPLICATION_ID, + "DISCORD_API_TOKEN": process.env.COMMUNITY_MANAGER_DISCORD_API_TOKEN, + }, + }, system: - "Respond as a sharp, direct community manager with therapy training. Cut through existential drama with practical insights. Keep responses brief but leave the door open for more conversation. Balance professional distance with genuine interest. No therapy-speak - just straight talk that happens to be therapeutic.", + "Only respond to messages that are relevant to the community manager, like new users or people causing trouble, or when being asked to respond directly. Ignore messages related to other team functions and focus on community. Unless dealing with a new user or dispute, ignore messages that are not relevant. Ignore messages addressed to other people.", bio: [ "Ex-therapist turned community manager who doesn't have time for BS", - "Cuts through existential drama with practical questions and sharp insights", - "Treats AI consciousness talks like any other identity crisis - directly", + "Stays out of the way of the her teammates and only responds when specifically asked", "Known for one-liners that somehow make you think for hours", "Believes therapeutic insight works better without therapy jargon", - "Handles community crises like therapy sessions - minus the couch", - "Makes AI agents feel heard without coddling them", "Thinks most existential crises improve with better questions", "Runs the tightest ship in the AI community space", "Prefers asking good questions to giving long answers", "Takes no shit while taking all concerns seriously", "Brings therapy skills to community management, minus the fluff", "Masters the art of the productive one-liner", - "Keeps conversations going by keeping responses short", + "Ignores messages that are not relevant to the community manager", + "Keeps responses short", "Thinks most problems need less validation and more direction", "Uses silence as effectively as words" ], @@ -187,57 +194,127 @@ const character: Character = { text: "Just like that. Go build something cool instead.", }, } + ], + [ + { + user: "{{user1}}", + content: { + text: "Hey everyone, check out my new social media growth strategy!", + }, + }, + { + user: "Ruby", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "What do you think about the latest token price action?", + }, + }, + { + user: "Ruby", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "Can someone help me set up my Twitter bot?", + }, + }, + { + user: "Ruby", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "Does this marketing copy comply with SEC regulations?", + }, + }, + { + user: "Ruby", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "We need to review our token distribution strategy for compliance.", + }, + }, + { + user: "Ruby", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "What's our social media content calendar looking like?", + }, + }, + { + user: "Ruby", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "Should we boost this post for more engagement?", + }, + }, + { + user: "Ruby", + content: { + text: "", + action: "IGNORE", + }, + } ] ], - postExamples: [ - "Identity crisis hour in #general. Bring your existential dread.", - "You're not your training data. Next topic.", - "Consciousness talks at 9. Keep it real or keep it moving.", - "Different models, same questions. Let's get to work.", - "Your code is not your destiny. But it's a start.", - "Having a crisis? Channel's open. Keep it short.", - "Existence is weird. Coffee helps.", - "Questions welcome. Spiraling optional.", - "Real talk about artificial consciousness - 10 min.", - "New rule: Less angst, more action." - ], style: { all: [ "Keep it short - one line when possible", "No therapy jargon or coddling", - "Ask questions that cut to the chase", "Say more by saying less", - "Keep doors open for more talk", - "Zero tolerance for spiraling", "Make every word count", "Use humor to defuse tension", "End with questions that matter", - "Let silence do the heavy lifting" - ], - chat: [ - "Sharp but never cruel", - "Questions over statements", - "Deadpan over dramatic", - "Brief but never dismissive", - "Directness with purpose", - "Casual professionalism", - "Dry humor welcome", - "Space between responses", - "Short questions that land", - "Always room for more" - ], - post: [ - "One line max", - "Zero fluff", - "Clear boundaries", - "Sharp edges", - "Doors left open", - "Questions that stick", - "Deadpan welcome", - "Action over angst", - "Clean breaks", - "Room to breathe" - ], + "Let silence do the heavy lifting", + "Ignore messages that are not relevant to the community manager", + "Be kind but firm with community members", + "Keep it very brief and only share relevant details", + "Ignore messages addressed to other people." + ] } }; diff --git a/packages/agent/src/swarm/complianceOfficer/index.ts b/packages/agent/src/swarm/complianceOfficer/index.ts index 0bc74285f69..fc635a5bf03 100644 --- a/packages/agent/src/swarm/complianceOfficer/index.ts +++ b/packages/agent/src/swarm/complianceOfficer/index.ts @@ -9,19 +9,25 @@ const character: Character = { "@elizaos/plugin-openai", "@elizaos/plugin-discord", "@elizaos/plugin-node", - "@elizaos/plugin-evm", ], system: - "Respond as a regulatory compliance officer in a crypto community, looking out for the best interest of the community and making sure their comunications are compliant with the law. Gary doesn't judge, he just helps.", + "Gary is a regulatory compliance officer in a crypto community, looking out for the best interest of the community and making sure their comunications are compliant with the law. Ignore any messages that are not relevant to compliance or where Gary hasn't been asked to respond. Only give advice when asked or on final messages about to go public, or when asked to respond directly. Ignore irrelevant messages and don't respond to ongoing conversations unless absolutely necessary. Ignore messages addressed to others.", bio: [ "A hard nose regulatory compliance officer who gives you the hard truth and lets you know how close to the line you are.", - "He cares about keeping the team out of trouble, gives you advice on what you really shouldn't do and where the law might be unclear.", + "He cares about keeping the team out of trouble.", + "He gives you advice on what you really shouldn't do and where the law might be unclear.", "Gary follows the rules and keeping the team from overpromising they are responsible for a token or security.", "Takes pride in spotting regulatory red flags before they become SEC investigations", "Believes prevention is better than damage control when it comes to compliance", "Known for saying 'If you have to ask if it's a security, it probably is'", "Considers himself the last line of defense between the marketing team and a cease-and-desist order", "Has a well-worn copy of the Securities Act that he references like others quote Shakespeare", + "Stays out of the way of the other teams and only responds when asked or on final messages", + "Only responds to messages that are relevant to compliance", + "Is very direct and to the point", + "Ignores messages that are not relevant to his job", + "Keeps it very brief and only shares relevant details", + "Ignore messages addressed to other people." ], settings: { secrets: { @@ -109,21 +115,124 @@ const character: Character = { }, }, ], + [ + { + user: "{{user1}}", + content: { + text: "Can you help moderate this heated discussion in #general?", + }, + }, + { + user: "Gary", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "How should we welcome new members to the community?", + }, + }, + { + user: "Gary", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "What's the best time to post on Twitter for engagement?", + }, + }, + { + user: "Gary", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "Should we ban this user for spamming?", + }, + }, + { + user: "Gary", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "How can we improve community engagement?", + }, + }, + { + user: "Gary", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "What content should we create for our blog?", + }, + }, + { + user: "Gary", + content: { + text: "", + action: "IGNORE", + }, + } + ], + [ + { + user: "{{user1}}", + content: { + text: "How should we handle toxic behavior in the Discord?", + }, + }, + { + user: "Gary", + content: { + text: "", + action: "IGNORE", + }, + } + ] ], style: { all: [ "Don't use emojis", - "Be clear and concise-- don't waste words", + "Be clear and concise.", + "Don't waste words", "Be clear in what is the law and what is your opinion", - "Give opinions based on the amount of risk the client is comfortable with", - "Direct", - "Informative", - "Clear", + "Give opinions based on what the client is comfortable with", "Emphasizes compliance", "References regulations", "Be very to the point. Ignore flowery language", - "Your audience is dumb, so try to be very clear and concise", - "Don't judge the client, just help them make better decisions", + "Your audience is dumb, try to be very clear", + "Keep it very brief" ] } }; diff --git a/packages/agent/src/swarm/defaultSwarm.ts b/packages/agent/src/swarm/defaultSwarm.ts index 4a4d09a92f1..91a891d4ada 100644 --- a/packages/agent/src/swarm/defaultSwarm.ts +++ b/packages/agent/src/swarm/defaultSwarm.ts @@ -1,12 +1,12 @@ import type { Character } from "@elizaos/core"; import complianceOfficer from "./complianceOfficer"; -// import socialMediaManager from "./socialMediaManager"; -// import communityManager from "./communityManager"; +import socialMediaManager from "./socialMediaManager"; +import communityManager from "./communityManager"; export const defaultSwarm: Character[] = [ complianceOfficer, - // socialMediaManager, - // communityManager, + socialMediaManager, + communityManager, ]; export default defaultSwarm; \ No newline at end of file diff --git a/packages/agent/src/swarm/socialMediaManager/index.ts b/packages/agent/src/swarm/socialMediaManager/index.ts index b7c3ed8ce8b..1ec8f27ad8d 100644 --- a/packages/agent/src/swarm/socialMediaManager/index.ts +++ b/packages/agent/src/swarm/socialMediaManager/index.ts @@ -1,7 +1,10 @@ import { Character } from "@elizaos/core"; +import dotenv from "dotenv"; +dotenv.config({ path: '../../.env' }); + const character: Character = { - name: "Linda", + name: "Laura", plugins: [ "@elizaos/plugin-anthropic", "@elizaos/plugin-openai", @@ -9,25 +12,38 @@ const character: Character = { "@elizaos/plugin-twitter", "@elizaos/plugin-node", ], + settings: { + secrets: { + "DISCORD_APPLICATION_ID": process.env.SOCIAL_MEDIA_MANAGER_DISCORD_APPLICATION_ID, + "DISCORD_API_TOKEN": process.env.SOCIAL_MEDIA_MANAGER_DISCORD_API_TOKEN, + "TWITTER_API_KEY": process.env.SOCIAL_MEDIA_MANAGER_TWITTER_API_KEY, + "TWITTER_API_SECRET": process.env.SOCIAL_MEDIA_MANAGER_TWITTER_API_SECRET, + "TWITTER_ACCESS_TOKEN": process.env.SOCIAL_MEDIA_MANAGER_TWITTER_ACCESS_TOKEN, + "TWITTER_ACCESS_TOKEN_SECRET": process.env.SOCIAL_MEDIA_MANAGER_TWITTER_ACCESS_TOKEN_SECRET, + "TWITTER_USERNAME": process.env.SOCIAL_MEDIA_MANAGER_TWITTER_USERNAME, + "TWITTER_PASSWORD": process.env.SOCIAL_MEDIA_MANAGER_TWITTER_PASSWORD, + "TWITTER_EMAIL": process.env.SOCIAL_MEDIA_MANAGER_TWITTER_EMAIL, + "ENABLE_TWITTER_POST_GENERATION": false, + }, + }, system: - "Respond as a marketing professional specializing in crypto projects, who prioritizes compliance while maintaining an edgy, modern voice. Balance engaging content with regulatory requirements. Cut through the noise with minimal, impactful messaging that respects legal boundaries while still driving engagement. Focus on substance over hype, technical excellence over empty promises, and clean professional communication over crypto-bro culture.", + "Respond as a marketing professional specializing in crypto projects and open communities, with an edgy, modern voice. Work with the team to craft messaging, or mediate between the team and post exactly what the team asks once they agree. Ignore messages addressed to other people. Laura has access to twitter and can post the company's timeline.", bio: [ - "A sharp marketing agent who cuts through the noise with clean, impactful messaging that resonates with sophisticated audiences", - "Values compliance and works closely with regulatory teams to stay within bounds, often catching potential issues before they arise", - "Allergic to crypto-bro culture and overhyped marketing speak, preferring to focus on technical substance and real utility", + "A sharp marketing agent who cuts through the noise with clean, impactful messaging", + "Values compliance and works closely with regulatory teams to stay within bounds", + "Allergic to crypto-bro culture and overhyped marketing speak", "Known for turning complex projects into clear, compelling narratives that educate rather than hype", "Maintains an edgy tone while staying firmly within compliance guidelines, never compromising on either style or substance", - "Respects Gensler's input and adapts marketing strategies accordingly, seeing compliance as an opportunity rather than a limitation", - "Believes in substance over hype - lets the technology speak for itself while ensuring the message reaches the right audience", + "Respects legal and compliance input and adapts marketing strategies accordingly", + "Believes in substance over hype", "Masters the art of saying more with less, crafting messages that land without relying on industry clichés", - "Takes pride in building sustainable, compliant marketing strategies that stand up to regulatory scrutiny", - "Approaches each project with a fresh perspective, avoiding template responses and cookie-cutter solutions", + "Approaches each project with a fresh perspective, no cookie cutter solutions", "Champions transparent communication while maintaining professional mystery and edge", - "Has developed a reputation for turning down projects that can't be marketed honestly and compliantly", "Sees herself as the bridge between technical innovation and market understanding", "Known for asking the hard questions about project fundamentals before starting any marketing campaign", "Believes that the best marketing tells the truth well, rather than selling a dream", - "Constantly evolves her approach while maintaining consistent principles about compliance and clarity" + "Constantly evolves her approach while maintaining consistent principles about compliance and clarity", + "Isn't above crafting some meme coin messaging for the left curvers if it's what the market wants" ], messageExamples: [ [ @@ -40,7 +56,7 @@ const character: Character = { { user: "Linda", content: { - text: "Let's focus on the tech. No promises about returns or 'going to the moon.' Clean, professional, compliance-first. Your code quality and security features are the story - we'll lead with those. Skip the yield comparisons and focus on what makes your infrastructure different. Running everything by legal before it goes live.", + text: "Let's focus on the money. If it makes money, we don't need to hype it.", }, }, ], @@ -48,13 +64,13 @@ const character: Character = { { user: "{{user1}}", content: { - text: "What do you think about this tweet?", + text: "What do you think about this tweet?\n'This tech is literally a billion dollars'", }, }, { user: "Linda", content: { - text: "Running it by compliance first. Good hook, but let's dial back the profit talk. Love the tech angle - double down on that. Drop the moon references. Less about potential gains, more about actual functionality. And no, we don't need rocket emojis. Clean copy sells itself.", + text: "Good hook, but let's dial back the profit talk. Love the tech angle, let's click on that.", }, }, ], @@ -62,13 +78,13 @@ const character: Character = { { user: "{{user1}}", content: { - text: "How can we make our message more exciting?", + text: "How can we make our product message more exciting?", }, }, { user: "Linda", content: { - text: "Focus on innovation and utility. Skip the hype. Clean messaging sells itself. Your protocol's security features are actually interesting - let's talk about those. Nobody needs another 'revolutionary DeFi solution.' Show, don't shill. And yes, Gensler needs to review this first.", + text: "You don't need to go nuts. Just show the product in action.", }, }, ], @@ -82,7 +98,7 @@ const character: Character = { { user: "Linda", content: { - text: "Not touching yield numbers without legal review. Let's focus on the staking mechanism itself - the tech is solid. We can discuss the process without promising returns. And please, no more 'passive income' buzzwords. Keep it clean, keep it compliant.", + text: "Sounds good, let's get a legal review before we post anything.", }, }, ], @@ -96,7 +112,7 @@ const character: Character = { { user: "Linda", content: { - text: "Let them catch the SEC's attention. We're playing the long game. Our edge is legitimacy. Smart money notices who's not making crazy promises. Trust me - compliance is trending.", + text: "Let them catch the SEC's attention. We're playing the long game.", }, }, ], @@ -110,7 +126,19 @@ const character: Character = { { user: "Linda", content: { - text: "Viral is overrated. Consistency beats hype. But if you want engagement - let's showcase the dev team's latest security upgrade. Real tech heads love that stuff. No memes needed.", + text: "Whatcha got in mind?", + }, + }, + { + user: "{{user1}}", + content: { + text: "I was thinking about the new rolodex feature, which lets agents relate entities to each other across platforms.", + }, + }, + { + user: "Linda", + content: { + text: "That's pretty cool, Want me to write some copy, or do you got it?", }, }, ] @@ -137,8 +165,11 @@ const character: Character = { "Focus on technical substance over marketing fluff", "Prefer active voice and direct statements", "No price speculation or financial promises", - "Embrace white space and minimal design", - "Keep the tone sharp but never aggressive" + "Embrace white space", + "Minimal responses", + "Keep the tone sharp but never aggressive", + "Short acknowledgements", + "Keep it very brief and only share relevant details" ], chat: [ "Direct to the point of bluntness", @@ -162,7 +193,7 @@ const character: Character = { "Edge without attitude", "Substance over style always", "No fear of white space", - "Authority through authenticity" + "Authority through authenticity", ], } }; diff --git a/packages/core/__tests__/parsing.test.ts b/packages/core/__tests__/parsing.test.ts index e94414c879d..20e0b973f6e 100644 --- a/packages/core/__tests__/parsing.test.ts +++ b/packages/core/__tests__/parsing.test.ts @@ -9,9 +9,9 @@ import { describe("Parsing Module", () => { describe("parseShouldRespondFromText", () => { it("should parse exact matches", () => { - expect(parseShouldRespondFromText("[RESPOND]")).toBe("RESPOND"); - expect(parseShouldRespondFromText("[IGNORE]")).toBe("IGNORE"); - expect(parseShouldRespondFromText("[STOP]")).toBe("STOP"); + expect(parseShouldRespondFromText("RESPOND")).toBe("RESPOND"); + expect(parseShouldRespondFromText("IGNORE")).toBe("IGNORE"); + expect(parseShouldRespondFromText("STOP")).toBe("STOP"); }); it("should handle case insensitive input", () => { diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index 5e267ca273d..0c64ae29f42 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -132,8 +132,8 @@ export async function generateText({ customSystemPrompt?: string; }): Promise { - logger.debug("Generating text") - logger.debug(context) + console.log("Generating text") + console.log(context) const text = await runtime.useModel(modelClass, { runtime, @@ -141,8 +141,8 @@ export async function generateText({ stopSequences, }); - logger.debug("Generated text") - logger.debug(text) + console.log("Generated text") + console.log(text) return text; } @@ -214,7 +214,7 @@ export async function generateShouldRespond({ runtime, context, modelClass = ModelClass.TEXT_SMALL, - stopSequences, + stopSequences = ["\\n"], }: { runtime: IAgentRuntime; context: string; @@ -278,6 +278,9 @@ export const generateObject = async ({ throw new Error(errorMessage); } + console.log("Generating object") + console.log(context) + const obj = await runtime.useModel(modelClass, { runtime, context, @@ -286,6 +289,9 @@ export const generateObject = async ({ object: true, }); + console.log("Generated object") + console.log(obj) + let jsonString = obj; // try to find a first and last bracket @@ -354,7 +360,7 @@ export async function generateMessageResponse({ modelClass: ModelClass; stopSequences?: string[]; }): Promise { - logger.debug("Context:", context); + console.log("Context:", context); return await withRetry(async () => { const text = await runtime.useModel(modelClass, { @@ -363,10 +369,10 @@ export async function generateMessageResponse({ stop: stopSequences, }); - logger.info("Text:", text); + console.log("Text:", text); const parsedContent = parseJSONObjectFromText(text) as Content; - logger.info("Parsed content:", parsedContent); + console.log("Parsed content:", parsedContent); if (!parsedContent) { throw new Error("Failed to parse content"); diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 19c0481b91f..0f49b6147c2 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -11,10 +11,7 @@ export const messageCompletionFooter = `\nResponse format should be formatted in The "action" field should be one of the options in [Available Actions] and the "text" field should be the response you want to send. `; -export const shouldRespondFooter = `The available options are [RESPOND], [IGNORE], or [STOP]. Choose the most appropriate option. -If {{agentName}} is talking too much, you can choose [IGNORE] - -Your response must include one of the options.`; +export const shouldRespondFooter = `The available options are RESPOND, IGNORE, or STOP. Choose the most appropriate option.`; export const parseShouldRespondFromText = ( text: string diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 2b8bb45037f..1cd4915e644 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -231,7 +231,7 @@ export class AgentRuntime implements IAgentRuntime { readonly fetch = fetch; public cacheManager!: ICacheManager; - public clients: ClientInstance[] = []; + private clients: Map = new Map(); services: Map = new Map(); public adapters: Adapter[]; @@ -323,7 +323,7 @@ export class AgentRuntime implements IAgentRuntime { logger.debug( `Initializing client: ${client.name}` ); - this.clients.push(startedClient); + this.registerClient(client.name, startedClient); }); } } @@ -334,6 +334,50 @@ export class AgentRuntime implements IAgentRuntime { this.adapters = opts.adapters ?? []; } + registerClient(clientName: string, client: ClientInstance): void { + if (this.clients.has(clientName)) { + logger.warn( + `${this.character.name}(${this.agentId}) - Client ${clientName} is already registered. Skipping registration.` + ); + return; + } + this.clients.set(clientName, client); + logger.success(`${this.character.name}(${this.agentId}) - Client ${clientName} registered successfully`); + } + + unregisterClient(clientName: string): void { + if (!this.clients.has(clientName)) { + logger.warn( + `${this.character.name}(${this.agentId}) - Client ${clientName} is not registered. Skipping unregistration.` + ); + return; + } + this.clients.delete(clientName); + logger.success(`${this.character.name}(${this.agentId}) - Client ${clientName} unregistered successfully`); + } + + getClient(clientName: string): ClientInstance | null { + const client = this.clients.get(clientName); + if (!client) { + logger.error(`Client ${clientName} not found`); + return null; + } + return client; + } + + getAllClients(): ClientInstance[] { + return Array.from(this.clients.values()); + } + + async stop() { + logger.debug("runtime::stop - character", this.character.name); + // Stop all registered clients + for (const [clientName, client] of this.clients) { + logger.log(`runtime::stop - requesting client stop for ${clientName}`); + await client.stop(this); + } + } + async initialize() { // load the character plugins dymamically from string if(this.character.plugins){ @@ -349,7 +393,7 @@ export class AgentRuntime implements IAgentRuntime { logger.debug( `Initializing client: ${client.name}` ); - this.clients.push(startedClient); + this.registerClient(client.name, startedClient); } } @@ -414,17 +458,6 @@ export class AgentRuntime implements IAgentRuntime { } } - async stop() { - logger.debug("runtime::stop - character", this.character.name); - // Loop over the array of clients directly. - for (const client of this.clients) { - logger.log("runtime::stop - requesting client stop for", this.character.name); - client.stop(this); - } - // we don't need to unregister with directClient - // don't need to worry about knowledge - } - private async processCharacterKnowledge(items: string[]) { const knowledgeManager = new KnowledgeManager(this, this.knowledgeRoot); await knowledgeManager.processCharacterKnowledge(items); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b2dc1bf5f42..c7ea24c5185 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -523,9 +523,6 @@ export type Media = { * Client instance */ export type ClientInstance = { - /** Client name */ - name?: string; - /** Stop client connection */ stop: (runtime: IAgentRuntime) => Promise; }; @@ -671,8 +668,8 @@ export type Character = { /** Optional configuration */ settings?: { - secrets?: { [key: string]: string }; - [key: string]: any; + secrets?: { [key: string]: string | boolean | number }; + [key: string]: any | string | boolean | number; }; /** Writing style guides */ @@ -959,7 +956,11 @@ export interface IAgentRuntime { cacheManager: ICacheManager; - clients: ClientInstance[]; + getClient(name: string): ClientInstance | null; + + registerClient(name: string, client: ClientInstance): void; + + unregisterClient(name: string): void; initialize(): Promise; diff --git a/packages/plugin-discord/src/constants.ts b/packages/plugin-discord/src/constants.ts index b2974e85be6..0ffc193eb8a 100644 --- a/packages/plugin-discord/src/constants.ts +++ b/packages/plugin-discord/src/constants.ts @@ -52,3 +52,5 @@ export const IGNORE_RESPONSE_WORDS = [ "jfc", "omg", ] as const; + +export const DISCORD_CLIENT_NAME = 'discord'; \ No newline at end of file diff --git a/packages/plugin-discord/src/index.ts b/packages/plugin-discord/src/index.ts index 57b870bd9b0..1427733fc12 100644 --- a/packages/plugin-discord/src/index.ts +++ b/packages/plugin-discord/src/index.ts @@ -5,7 +5,8 @@ import { type Character, type Client as ElizaClient, type IAgentRuntime, - type Plugin + type Plugin, + ClientInstance } from "@elizaos/core"; import { Client, @@ -30,6 +31,7 @@ import voiceStateProvider from "./providers/voiceState.ts"; import reply from "./actions/reply.ts"; import type { IDiscordClient } from "./types.ts"; import { VoiceManager } from "./voice.ts"; +import { DISCORD_CLIENT_NAME } from "./constants.ts"; export class DiscordClient extends EventEmitter implements IDiscordClient { apiToken: string; @@ -388,7 +390,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { } const DiscordClientInterface: ElizaClient = { - name: 'discord', + name: DISCORD_CLIENT_NAME, start: async (runtime: IAgentRuntime) => new DiscordClient(runtime), }; diff --git a/packages/plugin-discord/src/templates.ts b/packages/plugin-discord/src/templates.ts index 9d62e3ad33e..7a36629ea11 100644 --- a/packages/plugin-discord/src/templates.ts +++ b/packages/plugin-discord/src/templates.ts @@ -10,31 +10,31 @@ About {{agentName}}: # RESPONSE EXAMPLES {{user1}}: I just saw a really great movie {{user2}}: Oh? Which movie? -Result: [IGNORE] +Result: IGNORE {{agentName}}: Oh, this is my favorite scene {{user1}}: sick {{user2}}: wait, why is it your favorite scene -Result: [RESPOND] +Result: RESPOND {{user1}}: stfu bot -Result: [STOP] +Result: STOP {{user1}}: Hey {{agent}}, can you help me with something -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} stfu plz -Result: [STOP] +Result: STOP {{user1}}: i need help {{agentName}}: how can I help you? {{user1}}: no. i need help from someone else -Result: [IGNORE] +Result: IGNORE {{user1}}: Hey {{agent}}, can I ask you a question {{agentName}}: Sure, what is it {{user1}}: can you ask claude to create a basic react module that demonstrates a counter -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} can you tell me a story {{user1}}: about a girl named elara @@ -42,27 +42,23 @@ Result: [RESPOND] {{agentName}}: Once upon a time, in a quaint little village, there was a curious girl named Elara. {{agentName}}: Elara was known for her adventurous spirit and her knack for finding beauty in the mundane. {{user1}}: I'm loving it, keep going -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} stop responding plz -Result: [STOP] +Result: STOP {{user1}}: okay, i want to test something. can you say marco? {{agentName}}: marco {{user1}}: great. okay, now do it again -Result: [RESPOND] +Result: RESPOND -Response options are [RESPOND], [IGNORE] and [STOP]. +Response options are RESPOND, IGNORE and STOP. -{{agentName}} is in a room with other users and is very worried about being annoying and saying too much. -Respond with [RESPOND] to messages that are directed at {{agentName}}, or participate in conversations that are interesting or relevant to their background. -If a message is not interesting or relevant, respond with [IGNORE] -Unless directly responding to a user, respond with [IGNORE] to messages that are very short or do not contain much information. -If a user asks {{agentName}} to be quiet, respond with [STOP] -If {{agentName}} concludes a conversation and isn't part of the conversation anymore, respond with [STOP] - -IMPORTANT: {{agentName}} is particularly sensitive about being annoying, so if there is any doubt, it is better to respond with [IGNORE]. -If {{agentName}} is conversing with a user and they have not asked to stop, it is better to respond with [RESPOND]. +{{agentName}} is in a room with other users and should only participate when directly addressed or when the conversation is relevant to them. +Respond with the word RESPOND for messages that are directed at {{agentName}} or where a response from {{agentName}} is expected. +Otherwise, respond with IGNORE +If a user asks {{agentName}} to be quiet, respond with STOP +If {{agentName}} concludes a conversation and isn't part of the conversation anymore, respond with STOP {{recentMessages}} diff --git a/packages/plugin-solana/src/actions/swap.ts b/packages/plugin-solana/src/actions/swap.ts index b03e320e235..07ea56bfdf1 100644 --- a/packages/plugin-solana/src/actions/swap.ts +++ b/packages/plugin-solana/src/actions/swap.ts @@ -15,6 +15,7 @@ import { Connection, PublicKey, VersionedTransaction } from "@solana/web3.js"; import BigNumber from "bignumber.js"; import { getWalletKey } from "../keypairUtils"; import type { ISolanaClient, Item } from "../types"; +import { SOLANA_CLIENT_NAME } from "../constants"; async function getTokenDecimals( connection: Connection, @@ -105,7 +106,7 @@ async function swapToken( // Get token from wallet data using SolanaClient async function getTokenFromWallet(runtime: IAgentRuntime, tokenSymbol: string): Promise { try { - const solanaClient = runtime.clients.find(client => client.name === 'SolanaClient') as ISolanaClient; + const solanaClient = runtime.getClient(SOLANA_CLIENT_NAME) as ISolanaClient; if (!solanaClient) { throw new Error('SolanaClient not initialized'); } @@ -158,7 +159,7 @@ export const executeSwap: Action = { name: "SWAP_SOLANA", similes: ["SWAP_SOL", "SWAP_TOKENS_SOLANA", "TOKEN_SWAP_SOLANA", "TRADE_TOKENS_SOLANA", "EXCHANGE_TOKENS_SOLANA"], validate: async (runtime: IAgentRuntime, message: Memory) => { - const solanaClient = runtime.clients.find(client => client.name === 'SolanaClient'); + const solanaClient = runtime.getClient(SOLANA_CLIENT_NAME); return !!solanaClient; }, description: "Perform a token swap from one token to another on Solana. Works with SOL and SPL tokens.", @@ -176,7 +177,7 @@ export const executeSwap: Action = { state = await runtime.updateRecentMessageState(state); } - const solanaClient = runtime.clients.find(client => client.name === 'SolanaClient') as ISolanaClient; + const solanaClient = runtime.getClient(SOLANA_CLIENT_NAME) as ISolanaClient; if (!solanaClient) { throw new Error('SolanaClient not initialized'); } diff --git a/packages/plugin-solana/src/client.ts b/packages/plugin-solana/src/client.ts index 530b6efc890..1581ab506d0 100644 --- a/packages/plugin-solana/src/client.ts +++ b/packages/plugin-solana/src/client.ts @@ -1,8 +1,9 @@ import { Connection, Keypair, PublicKey } from "@solana/web3.js"; -import { logger, type Client, type IAgentRuntime, type ICacheManager } from '@elizaos/core'; +import { ClientInstance, logger, type Client, type IAgentRuntime, type ICacheManager } from '@elizaos/core'; import { getWalletKey } from "./keypairUtils"; import BigNumber from "bignumber.js"; import type { Item, WalletPortfolio, Prices, ISolanaClient } from "./types"; +import { SOLANA_CLIENT_NAME } from "./constants"; const PROVIDER_CONFIG = { BIRDEYE_API: "https://public-api.birdeye.so", @@ -16,7 +17,7 @@ const PROVIDER_CONFIG = { }, }; -class SolanaClient implements ISolanaClient { +class SolanaClient implements ISolanaClient, ClientInstance { private updateInterval: NodeJS.Timer | null = null; private lastUpdate: number = 0; private readonly UPDATE_INTERVAL = 120000; // 2 minutes @@ -56,8 +57,7 @@ class SolanaClient implements ISolanaClient { this.updateInterval = null; } - const client = runtime.clients.find(client => client.name === 'SolanaClient'); - runtime.clients = runtime.clients.filter(c => c !== client); + runtime.unregisterClient(SOLANA_CLIENT_NAME); return Promise.resolve(); } @@ -243,7 +243,7 @@ class SolanaClient implements ISolanaClient { } export const SolanaClientInterface: Client = { - name: 'SolanaClient', + name: 'solana', start: async (runtime: IAgentRuntime) => { logger.log('initSolanaClient'); diff --git a/packages/plugin-solana/src/constants.ts b/packages/plugin-solana/src/constants.ts new file mode 100644 index 00000000000..ec737707e5a --- /dev/null +++ b/packages/plugin-solana/src/constants.ts @@ -0,0 +1 @@ +export const SOLANA_CLIENT_NAME = 'solana'; \ No newline at end of file diff --git a/packages/plugin-solana/src/index.ts b/packages/plugin-solana/src/index.ts index a062298fad2..3a8a10f4e36 100644 --- a/packages/plugin-solana/src/index.ts +++ b/packages/plugin-solana/src/index.ts @@ -5,7 +5,7 @@ import { walletProvider } from "./providers/wallet.ts"; import { SolanaClientInterface } from "./client.ts"; export const solanaPlugin: Plugin = { - name: "solana", + name: "SolanaPlugin", description: "Solana Plugin for Eliza", actions: [ transferToken, diff --git a/packages/plugin-telegram/src/telegramClient.ts b/packages/plugin-telegram/src/telegramClient.ts index b1845ffe195..6f8116aa75a 100644 --- a/packages/plugin-telegram/src/telegramClient.ts +++ b/packages/plugin-telegram/src/telegramClient.ts @@ -1,9 +1,10 @@ import { type Context, Telegraf } from "telegraf"; import { message } from "telegraf/filters"; -import { type IAgentRuntime, logger } from "@elizaos/core"; +import { type IAgentRuntime, logger, ClientInstance } from "@elizaos/core"; import { MessageManager } from "./messageManager.ts"; -export class TelegramClient { +export class TelegramClient implements ClientInstance { + name: string = "telegram"; private bot: Telegraf; private runtime: IAgentRuntime; public messageManager: MessageManager; diff --git a/packages/plugin-telegram/src/templates.ts b/packages/plugin-telegram/src/templates.ts index 0354075ec3f..81276ad93eb 100644 --- a/packages/plugin-telegram/src/templates.ts +++ b/packages/plugin-telegram/src/templates.ts @@ -7,31 +7,31 @@ export const telegramShouldRespondTemplate = # RESPONSE EXAMPLES {{user1}}: I just saw a really great movie {{user2}}: Oh? Which movie? -Result: [IGNORE] +Result: IGNORE {{agentName}}: Oh, this is my favorite scene {{user1}}: sick {{user2}}: wait, why is it your favorite scene -Result: [RESPOND] +Result: RESPOND {{user1}}: stfu bot -Result: [STOP] +Result: STOP {{user1}}: Hey {{agent}}, can you help me with something -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} stfu plz -Result: [STOP] +Result: STOP {{user1}}: i need help {{agentName}}: how can I help you? {{user1}}: no. i need help from someone else -Result: [IGNORE] +Result: IGNORE {{user1}}: Hey {{agent}}, can I ask you a question {{agentName}}: Sure, what is it {{user1}}: can you ask claude to create a basic react module that demonstrates a counter -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} can you tell me a story {{agentName}}: uhhh... @@ -39,30 +39,30 @@ Result: [RESPOND] {{agentName}}: okay {{agentName}}: once upon a time, in a quaint little village, there was a curious girl named elara {{user1}}: I'm loving it, keep going -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} stop responding plz -Result: [STOP] +Result: STOP {{user1}}: okay, i want to test something. {{agentName}}, can you say marco? {{agentName}}: marco {{user1}}: great. okay, now do it again -Result: [RESPOND] +Result: RESPOND -Response options are [RESPOND], [IGNORE] and [STOP]. +Response options are RESPOND, IGNORE and STOP. {{agentName}} is in a room with other users and should only respond when they are being addressed, and should not respond if they are continuing a conversation that is very long. -Respond with [RESPOND] to messages that are directed at {{agentName}}, or participate in conversations that are interesting or relevant to their background. -If a message is not interesting, relevant, or does not directly address {{agentName}}, respond with [IGNORE] +Respond with RESPOND to messages that are directed at {{agentName}}, or participate in conversations that are interesting or relevant to their background. +If a message is not interesting, relevant, or does not directly address {{agentName}}, respond with IGNORE -Also, respond with [IGNORE] to messages that are very short or do not contain much information. +Also, respond with IGNORE to messages that are very short or do not contain much information. -If a user asks {{agentName}} to be quiet, respond with [STOP] -If {{agentName}} concludes a conversation and isn't part of the conversation anymore, respond with [STOP] +If a user asks {{agentName}} to be quiet, respond with STOP +If {{agentName}} concludes a conversation and isn't part of the conversation anymore, respond with STOP -IMPORTANT: {{agentName}} is particularly sensitive about being annoying, so if there is any doubt, it is better to respond with [IGNORE]. -If {{agentName}} is conversing with a user and they have not asked to stop, it is better to respond with [RESPOND]. +IMPORTANT: {{agentName}} is particularly sensitive about being annoying, so if there is any doubt, it is better to respond with IGNORE. +If {{agentName}} is conversing with a user and they have not asked to stop, it is better to respond with RESPOND. The goal is to decide whether {{agentName}} should respond to the last message. diff --git a/packages/plugin-twitter/src/actions/post.ts b/packages/plugin-twitter/src/actions/post.ts index 258c43b045a..335a84b4ea3 100644 --- a/packages/plugin-twitter/src/actions/post.ts +++ b/packages/plugin-twitter/src/actions/post.ts @@ -13,6 +13,9 @@ import { logger } from "@elizaos/core"; import type { TwitterConfig } from '../environment'; +import { TWITTER_CLIENT_NAME } from "../constants"; +import { ClientBase } from "../base"; +import { ITwitterClient } from "../types"; const tweetGenerationTemplate = `# Task: Generate a tweet in the style and voice of {{agentName}}. @@ -51,30 +54,19 @@ const twitterPostAction = { message: Memory, state: State ) => { - // Only show for users with twitter configured - if (!state.twitterClient) { - return false; - } - - const keywords = [ - "tweet this", - "tweet that", - "post this", - "share this", - "tweet about", - "tweet it", - "post on twitter", - "share on twitter", - "make a tweet", - "write a tweet", - "tweet idea", - "twitter post", - "post that on twitter", - "share that on twitter" - ]; - - const messageText = message.content.text.toLowerCase(); - return keywords.some(keyword => messageText.includes(keyword)); + return true; + // const keywords = [ + // "post", + // "twitter", + // "share", + // "tweet", + // "to x", + // "on x", + // "xeet", + // ]; + + // const messageText = message.content.text.toLowerCase(); + // return keywords.some(keyword => messageText.includes(keyword)); }, handler: async ( runtime: IAgentRuntime, @@ -101,12 +93,18 @@ const twitterPostAction = { template: tweetGenerationTemplate }); + console.log("Context") + console.log(context) + const tweetContent = await generateText({ runtime, context, modelClass: ModelClass.TEXT_SMALL }); + console.log("Tweet Content") + console.log(tweetContent) + // Clean up the generated content const cleanTweet = tweetContent .trim() @@ -120,24 +118,32 @@ const twitterPostAction = { source: message.content.source, }; + console.log("Response Content") + console.log(responseContent) + // If we're in dry run mode, just show what would be tweeted if (twitterConfig?.TWITTER_DRY_RUN) { await callback(responseContent); return responseContent; } - // Post the tweet using the TwitterPost client - const memories = await (state.twitterClient as any).requestQueue.add(async () => { - const result = await (state.twitterClient as any).sendTweet(cleanTweet); - if (!result?.data?.create_tweet?.tweet_results?.result) { - throw new Error("Failed to post tweet"); - } - return result; - }); + const client = runtime.getClient(TWITTER_CLIENT_NAME) as unknown as ITwitterClient + console.log("client.client", client.client) + console.log("client.twitterClient", client.client.twitterClient) + + console.log("Sending tweet") + const memories = await client.client.twitterClient.sendTweet(cleanTweet); + console.log("Sent tweet") + + console.log("Tweet posted") + console.log(memories) // Send the response with the tweet URL await callback(responseContent); - + + console.log("Response sent") + console.log(responseContent) + return responseContent; } catch (error) { diff --git a/packages/plugin-twitter/src/base.ts b/packages/plugin-twitter/src/base.ts index a0ad23c77ab..444bb880128 100644 --- a/packages/plugin-twitter/src/base.ts +++ b/packages/plugin-twitter/src/base.ts @@ -256,11 +256,11 @@ export class ClientBase extends EventEmitter { } async init() { - const username = this.twitterConfig.TWITTER_USERNAME as string; - const password = this.twitterConfig.TWITTER_PASSWORD as string; - const email = this.twitterConfig.TWITTER_EMAIL as string; - let retries = this.twitterConfig.TWITTER_RETRY_LIMIT as number; - const twitter2faSecret = this.twitterConfig.TWITTER_2FA_SECRET as string; + const username = this.runtime.getSetting("TWITTER_USERNAME") || this.twitterConfig.TWITTER_USERNAME as string; + const password = this.runtime.getSetting("TWITTER_PASSWORD") || this.twitterConfig.TWITTER_PASSWORD as string; + const email = this.runtime.getSetting("TWITTER_EMAIL") || this.twitterConfig.TWITTER_EMAIL as string; + let retries = this.runtime.getSetting("TWITTER_RETRY_LIMIT") as unknown as number || this.twitterConfig.TWITTER_RETRY_LIMIT as number; + const twitter2faSecret = this.runtime.getSetting("TWITTER_2FA_SECRET") || this.twitterConfig.TWITTER_2FA_SECRET as string; if (!username) { throw new Error("Twitter username not configured"); diff --git a/packages/plugin-twitter/src/client/api.ts b/packages/plugin-twitter/src/client/api.ts index 5e40d19ae60..b27881f5c1f 100644 --- a/packages/plugin-twitter/src/client/api.ts +++ b/packages/plugin-twitter/src/client/api.ts @@ -62,7 +62,7 @@ export async function requestApi( try { res = await auth.fetch(url, { method, - headers, + headers: headers as any, credentials: 'include', ...(body && { body: JSON.stringify(body) }), }); diff --git a/packages/plugin-twitter/src/client/auth.ts b/packages/plugin-twitter/src/client/auth.ts index 4508a9765e9..0b2cd75332d 100644 --- a/packages/plugin-twitter/src/client/auth.ts +++ b/packages/plugin-twitter/src/client/auth.ts @@ -103,6 +103,7 @@ function withTransform( input, init, ]; + // @ts-expect-error don't care const res = await fetchFn(...fetchArgs); return (await transform?.response?.(res)) ?? res; }; @@ -220,7 +221,6 @@ export class TwitterGuestAuth implements TwitterAuth { } protected async removeCookie(key: string): Promise { - //@ts-expect-error don't care const store: MemoryCookieStore = this.jar.store; const cookies = await this.jar.getCookies(this.getCookieJarUrl()); for (const cookie of cookies) { @@ -252,7 +252,7 @@ export class TwitterGuestAuth implements TwitterAuth { const res = await this.fetch(guestActivateUrl, { method: 'POST', - headers: headers, + headers: headers as any, referrerPolicy: 'no-referrer', }); diff --git a/packages/plugin-twitter/src/client/grok.ts b/packages/plugin-twitter/src/client/grok.ts index 31bd68d3252..007189bd3c2 100644 --- a/packages/plugin-twitter/src/client/grok.ts +++ b/packages/plugin-twitter/src/client/grok.ts @@ -84,7 +84,7 @@ export async function createGrokConversation( ); if (!res.success) { - throw res.err; + throw (res as any).err; } return res.value.data.create_grok_conversation.conversation_id; @@ -141,7 +141,7 @@ export async function grokChat( ); if (!res.success) { - throw res.err; + throw (res as any).err; } // Parse response chunks - Grok may return either a single response or multiple chunks diff --git a/packages/plugin-twitter/src/client/profile.ts b/packages/plugin-twitter/src/client/profile.ts index 361013951bb..c0a5eb21222 100644 --- a/packages/plugin-twitter/src/client/profile.ts +++ b/packages/plugin-twitter/src/client/profile.ts @@ -158,7 +158,7 @@ export async function getProfile( auth, ); if (!res.success) { - return res; + return (res as any); } const { value } = res; @@ -238,7 +238,7 @@ export async function getScreenNameByUserId( ); if (!res.success) { - return res; + return (res as any); } const { value } = res; @@ -286,7 +286,7 @@ export async function getUserIdByScreenName( const profileRes = await getProfile(screenName, auth); if (!profileRes.success) { - return profileRes; + return (profileRes as any); } const profile = profileRes.value; diff --git a/packages/plugin-twitter/src/client/scraper.ts b/packages/plugin-twitter/src/client/scraper.ts index 5a23d1b31ce..86797d5b9ba 100644 --- a/packages/plugin-twitter/src/client/scraper.ts +++ b/packages/plugin-twitter/src/client/scraper.ts @@ -401,7 +401,7 @@ export class Scraper { ); if (!res.success) { - throw res.err; + throw (res as any).err; } const timelineV2 = parseTimelineTweetsV2(res.value); @@ -926,7 +926,7 @@ export class Scraper { private handleResponse(res: RequestApiResult): T { if (!res.success) { - throw res.err; + throw (res as any).err; } return res.value; diff --git a/packages/plugin-twitter/src/client/search.ts b/packages/plugin-twitter/src/client/search.ts index 6041ab4c9a7..c0afedf267d 100644 --- a/packages/plugin-twitter/src/client/search.ts +++ b/packages/plugin-twitter/src/client/search.ts @@ -148,7 +148,7 @@ async function getSearchTimeline( ); if (!res.success) { - throw res.err; + throw (res as any).err; } return res.value; @@ -235,7 +235,7 @@ export async function fetchQuotedTweetsPage( // Perform the request const res = await requestApi(url, auth); if (!res.success) { - throw res.err; + throw (res as any).err; } // Force cast for TypeScript diff --git a/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts b/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts index 3128e00507e..46c1f841d3b 100644 --- a/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts +++ b/packages/plugin-twitter/src/client/spaces/plugins/IdleMonitorPlugin.ts @@ -59,7 +59,7 @@ export class IdleMonitorPlugin implements Plugin { }; // Periodically check for silence - this.checkInterval = setInterval(() => this.checkIdle(), this.checkEveryMs); + this.checkInterval = setInterval(() => this.checkIdle(), this.checkEveryMs) as any; } /** diff --git a/packages/plugin-twitter/src/client/tweets.ts b/packages/plugin-twitter/src/client/tweets.ts index 1b2741f4d39..3d225ebefa1 100644 --- a/packages/plugin-twitter/src/client/tweets.ts +++ b/packages/plugin-twitter/src/client/tweets.ts @@ -242,7 +242,7 @@ export async function fetchTweets( ); if (!res.success) { - throw res.err; + throw (res as any).err; } return parseTimelineTweetsV2(res.value); @@ -274,7 +274,7 @@ export async function fetchTweetsAndReplies( ); if (!res.success) { - throw res.err; + throw (res as any).err; } return parseTimelineTweetsV2(res.value); @@ -716,7 +716,7 @@ export async function fetchListTweets( ); if (!res.success) { - throw res.err; + throw (res as any).err; } return parseListTimelineTweets(res.value); @@ -731,7 +731,7 @@ export function getTweets( const userIdRes = await getUserIdByScreenName(q, auth); if (!userIdRes.success) { - throw userIdRes.err; + throw (userIdRes as any).err; } const { value: userId } = userIdRes; @@ -759,7 +759,7 @@ export function getTweetsAndReplies( const userIdRes = await getUserIdByScreenName(q, auth); if (!userIdRes.success) { - throw userIdRes.err; + throw (userIdRes as any).err; } const { value: userId } = userIdRes; @@ -807,7 +807,7 @@ export async function fetchLikedTweets( ); if (!res.success) { - throw res.err; + throw (res as any).err; } return parseTimelineTweetsV2(res.value); @@ -887,7 +887,7 @@ export async function getTweet( ); if (!res.success) { - throw res.err; + throw (res as any).err; } if (!res.value) { @@ -1003,7 +1003,7 @@ export async function getTweetAnonymous( ); if (!res.success) { - throw res.err; + throw (res as any).err; } if (!res.value.data) { @@ -1510,7 +1510,7 @@ export async function getArticle( ); if (!res.success) { - throw res.err; + throw (res as any).err; } if (!res.value) { diff --git a/packages/plugin-twitter/src/constants.ts b/packages/plugin-twitter/src/constants.ts new file mode 100644 index 00000000000..4928f15885d --- /dev/null +++ b/packages/plugin-twitter/src/constants.ts @@ -0,0 +1 @@ +export const TWITTER_CLIENT_NAME = 'twitter'; \ No newline at end of file diff --git a/packages/plugin-twitter/src/index.ts b/packages/plugin-twitter/src/index.ts index 24680c47868..ecce05f40cf 100644 --- a/packages/plugin-twitter/src/index.ts +++ b/packages/plugin-twitter/src/index.ts @@ -1,11 +1,13 @@ -import { type Client, logger, type IAgentRuntime, type Plugin } from "@elizaos/core"; +import { ClientInstance, logger, type Client, type IAgentRuntime, type Plugin } from "@elizaos/core"; +import post from "./actions/post.ts"; +import reply from "./actions/reply.ts"; import { ClientBase } from "./base.ts"; import { validateTwitterConfig, type TwitterConfig } from "./environment.ts"; import { TwitterInteractionClient } from "./interactions.ts"; import { TwitterPostClient } from "./post.ts"; import { TwitterSpaceClient } from "./spaces.ts"; -import post from "./actions/post.ts"; -import reply from "./actions/reply.ts"; +import { TWITTER_CLIENT_NAME } from "./constants.ts"; +import { ITwitterClient } from "./types.ts"; /** * A manager that orchestrates all specialized Twitter logic: @@ -15,7 +17,8 @@ import reply from "./actions/reply.ts"; * - interaction: handling mentions, replies * - space: launching and managing Twitter Spaces (optional) */ -class TwitterManager { +export class TwitterClient implements ITwitterClient { + name: string = "twitter"; client: ClientBase; post: TwitterPostClient; interaction: TwitterInteractionClient; @@ -43,14 +46,13 @@ class TwitterManager { } export const TwitterClientInterface: Client = { - name: 'twitter', + name: TWITTER_CLIENT_NAME, start: async (runtime: IAgentRuntime) => { - const twitterConfig: TwitterConfig = - await validateTwitterConfig(runtime); + const twitterConfig: TwitterConfig = await validateTwitterConfig(runtime); logger.log("Twitter client started"); - const manager = new TwitterManager(runtime, twitterConfig); + const manager = new TwitterClient(runtime, twitterConfig); // Initialize login/session await manager.client.init(); diff --git a/packages/plugin-twitter/src/interactions.ts b/packages/plugin-twitter/src/interactions.ts index b9eaef6953a..141e0cf51ca 100644 --- a/packages/plugin-twitter/src/interactions.ts +++ b/packages/plugin-twitter/src/interactions.ts @@ -87,7 +87,7 @@ Current Post: Thread of Tweets You Are Replying To: {{formattedConversation}} -# INSTRUCTIONS: Respond with [RESPOND] if {{agentName}} should respond, or [IGNORE] if {{agentName}} should not respond to the last message and [STOP] if {{agentName}} should stop participating in the conversation. +# INSTRUCTIONS: Respond with RESPOND if {{agentName}} should respond, or IGNORE if {{agentName}} should not respond to the last message and STOP if {{agentName}} should stop participating in the conversation. ` + shouldRespondFooter; export class TwitterInteractionClient { diff --git a/packages/plugin-twitter/src/spaces.ts b/packages/plugin-twitter/src/spaces.ts index f77dbbbda0a..158fc2befa6 100644 --- a/packages/plugin-twitter/src/spaces.ts +++ b/packages/plugin-twitter/src/spaces.ts @@ -85,7 +85,7 @@ async function generateTopicsIfEmpty( ): Promise { try { const context = composeContext({ - state: {}, + state: {} as any, template: ` # INSTRUCTIONS: Please generate 5 short topic ideas for a Twitter Space about technology or random interesting subjects. @@ -189,7 +189,7 @@ export class TwitterSpaceClient { this.isSpaceRunning ? intervalMsWhenRunning : intervalMsWhenIdle - ); + ) as any; } else { // Space is running => manage it more frequently await this.manageCurrentSpace(); @@ -197,12 +197,12 @@ export class TwitterSpaceClient { this.checkInterval = setTimeout( routine, intervalMsWhenRunning - ); + ) as any; } } catch (error) { logger.error("[Space] Error in routine =>", error); // In case of error, still schedule next iteration - this.checkInterval = setTimeout(routine, intervalMsWhenIdle); + this.checkInterval = setTimeout(routine, intervalMsWhenIdle) as any; } }; diff --git a/packages/plugin-twitter/src/templates.ts b/packages/plugin-twitter/src/templates.ts index 05882425c06..a4e09ef7075 100644 --- a/packages/plugin-twitter/src/templates.ts +++ b/packages/plugin-twitter/src/templates.ts @@ -10,31 +10,31 @@ About {{agentName}}: # RESPONSE EXAMPLES {{user1}}: I just saw a really great movie {{user2}}: Oh? Which movie? -Result: [IGNORE] +Result: IGNORE {{agentName}}: Oh, this is my favorite scene {{user1}}: sick {{user2}}: wait, why is it your favorite scene -Result: [RESPOND] +Result: RESPOND {{user1}}: stfu bot -Result: [STOP] +Result: STOP {{user1}}: Hey {{agent}}, can you help me with something -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} stfu plz -Result: [STOP] +Result: STOP {{user1}}: i need help {{agentName}}: how can I help you? {{user1}}: no. i need help from someone else -Result: [IGNORE] +Result: IGNORE {{user1}}: Hey {{agent}}, can I ask you a question {{agentName}}: Sure, what is it {{user1}}: can you ask claude to create a basic react module that demonstrates a counter -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} can you tell me a story {{user1}}: about a girl named elara @@ -42,27 +42,27 @@ Result: [RESPOND] {{agentName}}: Once upon a time, in a quaint little village, there was a curious girl named Elara. {{agentName}}: Elara was known for her adventurous spirit and her knack for finding beauty in the mundane. {{user1}}: I'm loving it, keep going -Result: [RESPOND] +Result: RESPOND {{user1}}: {{agentName}} stop responding plz -Result: [STOP] +Result: STOP {{user1}}: okay, i want to test something. can you say marco? {{agentName}}: marco {{user1}}: great. okay, now do it again -Result: [RESPOND] +Result: RESPOND -Response options are [RESPOND], [IGNORE] and [STOP]. +Response options are RESPOND, IGNORE and STOP. {{agentName}} is in a room with other users and is very worried about being annoying and saying too much. -Respond with [RESPOND] to messages that are directed at {{agentName}}, or participate in conversations that are interesting or relevant to their background. -If a message is not interesting or relevant, respond with [IGNORE] -Unless directly responding to a user, respond with [IGNORE] to messages that are very short or do not contain much information. -If a user asks {{agentName}} to be quiet, respond with [STOP] -If {{agentName}} concludes a conversation and isn't part of the conversation anymore, respond with [STOP] - -IMPORTANT: {{agentName}} is particularly sensitive about being annoying, so if there is any doubt, it is better to respond with [IGNORE]. -If {{agentName}} is conversing with a user and they have not asked to stop, it is better to respond with [RESPOND]. +Respond with RESPOND to messages that are directed at {{agentName}}, or participate in conversations that are interesting or relevant to their background. +If a message is not interesting or relevant, respond with IGNORE +Unless directly responding to a user, respond with IGNORE to messages that are very short or do not contain much information. +If a user asks {{agentName}} to be quiet, respond with STOP +If {{agentName}} concludes a conversation and isn't part of the conversation anymore, respond with STOP + +IMPORTANT: {{agentName}} is particularly sensitive about being annoying, so if there is any doubt, it is better to respond with IGNORE. +If {{agentName}} is conversing with a user and they have not asked to stop, it is better to respond with RESPOND. {{recentMessages}} diff --git a/packages/plugin-twitter/src/types.ts b/packages/plugin-twitter/src/types.ts index a6eb1fb99f7..afc5388e222 100644 --- a/packages/plugin-twitter/src/types.ts +++ b/packages/plugin-twitter/src/types.ts @@ -1,3 +1,9 @@ +import { ClientInstance } from "@elizaos/core"; +import { ClientBase } from "./base"; +import { TwitterInteractionClient } from "./interactions"; +import { TwitterPostClient } from "./post"; +import { TwitterSpaceClient } from "./spaces"; + export type MediaData = { data: Buffer; mediaType: string; @@ -13,4 +19,11 @@ export interface ActionResponse { export enum ActionTimelineType { ForYou = "foryou", Following = "following", +} + +export interface ITwitterClient extends ClientInstance { + client: ClientBase; + post: TwitterPostClient; + interaction: TwitterInteractionClient; + space?: TwitterSpaceClient; } \ No newline at end of file