@elizaos/plugin-twitter 1.2.10 → 1.2.12

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/index.js CHANGED
@@ -452,7 +452,10 @@ async function followUser(username, auth) {
452
452
  if (!meResponse.data) {
453
453
  throw new Error("Failed to get authenticated user");
454
454
  }
455
- const result = await client.v2.follow(meResponse.data.id, userResponse.data.id);
455
+ const result = await client.v2.follow(
456
+ meResponse.data.id,
457
+ userResponse.data.id
458
+ );
456
459
  return new Response(JSON.stringify(result), {
457
460
  status: result.data?.following ? 200 : 400,
458
461
  headers: new Headers({ "Content-Type": "application/json" })
@@ -748,7 +751,9 @@ async function fetchTweetsAndReplies(userId, maxTweets, cursor, auth) {
748
751
  next: response.meta.next_token
749
752
  };
750
753
  } catch (error) {
751
- throw new Error(`Failed to fetch tweets and replies: ${error?.message || error}`);
754
+ throw new Error(
755
+ `Failed to fetch tweets and replies: ${error?.message || error}`
756
+ );
752
757
  }
753
758
  }
754
759
  async function createCreateTweetRequestV2(text, auth, tweetId, options) {
@@ -822,9 +827,15 @@ function parseTweetV2ToV1(tweetV2, includes) {
822
827
  isReply: tweetV2.referenced_tweets?.some((ref) => ref.type === "replied_to") ?? false,
823
828
  isRetweet: tweetV2.referenced_tweets?.some((ref) => ref.type === "retweeted") ?? false,
824
829
  isQuoted: tweetV2.referenced_tweets?.some((ref) => ref.type === "quoted") ?? false,
825
- inReplyToStatusId: tweetV2.referenced_tweets?.find((ref) => ref.type === "replied_to")?.id,
826
- quotedStatusId: tweetV2.referenced_tweets?.find((ref) => ref.type === "quoted")?.id,
827
- retweetedStatusId: tweetV2.referenced_tweets?.find((ref) => ref.type === "retweeted")?.id
830
+ inReplyToStatusId: tweetV2.referenced_tweets?.find(
831
+ (ref) => ref.type === "replied_to"
832
+ )?.id,
833
+ quotedStatusId: tweetV2.referenced_tweets?.find(
834
+ (ref) => ref.type === "quoted"
835
+ )?.id,
836
+ retweetedStatusId: tweetV2.referenced_tweets?.find(
837
+ (ref) => ref.type === "retweeted"
838
+ )?.id
828
839
  };
829
840
  if (includes?.polls?.length) {
830
841
  const poll = includes.polls[0];
@@ -976,7 +987,12 @@ async function* getTweets(user, maxTweets, auth) {
976
987
  let cursor;
977
988
  let totalFetched = 0;
978
989
  while (totalFetched < maxTweets) {
979
- const response = await fetchTweets(userId, maxTweets - totalFetched, cursor, auth);
990
+ const response = await fetchTweets(
991
+ userId,
992
+ maxTweets - totalFetched,
993
+ cursor,
994
+ auth
995
+ );
980
996
  for (const tweet of response.tweets) {
981
997
  yield tweet;
982
998
  totalFetched++;
@@ -990,7 +1006,12 @@ async function* getTweetsByUserId(userId, maxTweets, auth) {
990
1006
  let cursor;
991
1007
  let totalFetched = 0;
992
1008
  while (totalFetched < maxTweets) {
993
- const response = await fetchTweets(userId, maxTweets - totalFetched, cursor, auth);
1009
+ const response = await fetchTweets(
1010
+ userId,
1011
+ maxTweets - totalFetched,
1012
+ cursor,
1013
+ auth
1014
+ );
994
1015
  for (const tweet of response.tweets) {
995
1016
  yield tweet;
996
1017
  totalFetched++;
@@ -1009,7 +1030,12 @@ async function* getTweetsAndReplies(user, maxTweets, auth) {
1009
1030
  let cursor;
1010
1031
  let totalFetched = 0;
1011
1032
  while (totalFetched < maxTweets) {
1012
- const response = await fetchTweetsAndReplies(userId, maxTweets - totalFetched, cursor, auth);
1033
+ const response = await fetchTweetsAndReplies(
1034
+ userId,
1035
+ maxTweets - totalFetched,
1036
+ cursor,
1037
+ auth
1038
+ );
1013
1039
  for (const tweet of response.tweets) {
1014
1040
  yield tweet;
1015
1041
  totalFetched++;
@@ -1023,7 +1049,12 @@ async function* getTweetsAndRepliesByUserId(userId, maxTweets, auth) {
1023
1049
  let cursor;
1024
1050
  let totalFetched = 0;
1025
1051
  while (totalFetched < maxTweets) {
1026
- const response = await fetchTweetsAndReplies(userId, maxTweets - totalFetched, cursor, auth);
1052
+ const response = await fetchTweetsAndReplies(
1053
+ userId,
1054
+ maxTweets - totalFetched,
1055
+ cursor,
1056
+ auth
1057
+ );
1027
1058
  for (const tweet of response.tweets) {
1028
1059
  yield tweet;
1029
1060
  totalFetched++;
@@ -1115,10 +1146,7 @@ async function getTweetV2(id, auth, options = defaultOptions) {
1115
1146
  console.warn(`Tweet data not found for ID: ${id}`);
1116
1147
  return null;
1117
1148
  }
1118
- const parsedTweet = parseTweetV2ToV1(
1119
- tweetData.data,
1120
- tweetData?.includes
1121
- );
1149
+ const parsedTweet = parseTweetV2ToV1(tweetData.data, tweetData?.includes);
1122
1150
  return parsedTweet;
1123
1151
  } catch (error) {
1124
1152
  console.error(`Error fetching tweet ${id}:`, error);
@@ -1920,7 +1948,7 @@ import {
1920
1948
  } from "@elizaos/core";
1921
1949
 
1922
1950
  // src/constants.ts
1923
- var TWEET_MAX_LENGTH = 4e3;
1951
+ var TWEET_MAX_LENGTH = 280;
1924
1952
 
1925
1953
  // src/utils/error-handler.ts
1926
1954
  import { logger } from "@elizaos/core";
@@ -6098,18 +6126,49 @@ async function validateTwitterConfig(runtime, config = {}) {
6098
6126
  TWITTER_API_SECRET_KEY: config.TWITTER_API_SECRET_KEY ?? getSetting(runtime, "TWITTER_API_SECRET_KEY") ?? "",
6099
6127
  TWITTER_ACCESS_TOKEN: config.TWITTER_ACCESS_TOKEN ?? getSetting(runtime, "TWITTER_ACCESS_TOKEN") ?? "",
6100
6128
  TWITTER_ACCESS_TOKEN_SECRET: config.TWITTER_ACCESS_TOKEN_SECRET ?? getSetting(runtime, "TWITTER_ACCESS_TOKEN_SECRET") ?? "",
6101
- TWITTER_DRY_RUN: String((config.TWITTER_DRY_RUN ?? getSetting(runtime, "TWITTER_DRY_RUN") ?? "false").toLowerCase() === "true"),
6129
+ TWITTER_DRY_RUN: String(
6130
+ (config.TWITTER_DRY_RUN ?? getSetting(runtime, "TWITTER_DRY_RUN") ?? "false").toLowerCase() === "true"
6131
+ ),
6102
6132
  TWITTER_TARGET_USERS: config.TWITTER_TARGET_USERS ?? getSetting(runtime, "TWITTER_TARGET_USERS") ?? "",
6103
- TWITTER_ENABLE_POST: String((config.TWITTER_ENABLE_POST ?? getSetting(runtime, "TWITTER_ENABLE_POST") ?? "false").toLowerCase() === "true"),
6133
+ TWITTER_ENABLE_POST: String(
6134
+ (config.TWITTER_ENABLE_POST ?? getSetting(runtime, "TWITTER_ENABLE_POST") ?? "false").toLowerCase() === "true"
6135
+ ),
6104
6136
  TWITTER_ENABLE_REPLIES: String(
6105
6137
  config.TWITTER_ENABLE_REPLIES !== void 0 ? config.TWITTER_ENABLE_REPLIES.toLowerCase() === "true" : (getSetting(runtime, "TWITTER_ENABLE_REPLIES") ?? "true").toLowerCase() === "true"
6106
6138
  ),
6107
- TWITTER_ENABLE_ACTIONS: String((config.TWITTER_ENABLE_ACTIONS ?? getSetting(runtime, "TWITTER_ENABLE_ACTIONS") ?? "false").toLowerCase() === "true"),
6108
- TWITTER_POST_INTERVAL: String(safeParseInt(config.TWITTER_POST_INTERVAL ?? getSetting(runtime, "TWITTER_POST_INTERVAL"), 120)),
6109
- TWITTER_ENGAGEMENT_INTERVAL: String(safeParseInt(config.TWITTER_ENGAGEMENT_INTERVAL ?? getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL"), 30)),
6110
- TWITTER_MAX_ENGAGEMENTS_PER_RUN: String(safeParseInt(config.TWITTER_MAX_ENGAGEMENTS_PER_RUN ?? getSetting(runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN"), 10)),
6111
- TWITTER_MAX_TWEET_LENGTH: String(safeParseInt(config.TWITTER_MAX_TWEET_LENGTH ?? getSetting(runtime, "TWITTER_MAX_TWEET_LENGTH"), 280)),
6112
- TWITTER_RETRY_LIMIT: String(safeParseInt(config.TWITTER_RETRY_LIMIT ?? getSetting(runtime, "TWITTER_RETRY_LIMIT"), 5))
6139
+ TWITTER_ENABLE_ACTIONS: String(
6140
+ (config.TWITTER_ENABLE_ACTIONS ?? getSetting(runtime, "TWITTER_ENABLE_ACTIONS") ?? "false").toLowerCase() === "true"
6141
+ ),
6142
+ TWITTER_POST_INTERVAL: String(
6143
+ safeParseInt(
6144
+ config.TWITTER_POST_INTERVAL ?? getSetting(runtime, "TWITTER_POST_INTERVAL"),
6145
+ 120
6146
+ )
6147
+ ),
6148
+ TWITTER_ENGAGEMENT_INTERVAL: String(
6149
+ safeParseInt(
6150
+ config.TWITTER_ENGAGEMENT_INTERVAL ?? getSetting(runtime, "TWITTER_ENGAGEMENT_INTERVAL"),
6151
+ 30
6152
+ )
6153
+ ),
6154
+ TWITTER_MAX_ENGAGEMENTS_PER_RUN: String(
6155
+ safeParseInt(
6156
+ config.TWITTER_MAX_ENGAGEMENTS_PER_RUN ?? getSetting(runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN"),
6157
+ 10
6158
+ )
6159
+ ),
6160
+ TWITTER_MAX_TWEET_LENGTH: String(
6161
+ safeParseInt(
6162
+ config.TWITTER_MAX_TWEET_LENGTH ?? getSetting(runtime, "TWITTER_MAX_TWEET_LENGTH"),
6163
+ 280
6164
+ )
6165
+ ),
6166
+ TWITTER_RETRY_LIMIT: String(
6167
+ safeParseInt(
6168
+ config.TWITTER_RETRY_LIMIT ?? getSetting(runtime, "TWITTER_RETRY_LIMIT"),
6169
+ 5
6170
+ )
6171
+ )
6113
6172
  };
6114
6173
  if (!validatedConfig.TWITTER_API_KEY || !validatedConfig.TWITTER_API_SECRET_KEY || !validatedConfig.TWITTER_ACCESS_TOKEN || !validatedConfig.TWITTER_ACCESS_TOKEN_SECRET) {
6115
6174
  throw new Error(
@@ -6161,7 +6220,9 @@ var TwitterInteractionClient = class {
6161
6220
  this.state?.TWITTER_ENGAGEMENT_INTERVAL || getSetting(this.runtime, "TWITTER_ENGAGEMENT_INTERVAL") || process.env.TWITTER_ENGAGEMENT_INTERVAL || "30"
6162
6221
  );
6163
6222
  const interactionInterval = engagementIntervalMinutes * 60 * 1e3;
6164
- logger3.info(`Twitter interaction client will check every ${engagementIntervalMinutes} minutes`);
6223
+ logger3.info(
6224
+ `Twitter interaction client will check every ${engagementIntervalMinutes} minutes`
6225
+ );
6165
6226
  this.handleTwitterInteractions();
6166
6227
  if (this.isRunning) {
6167
6228
  setTimeout(handleTwitterInteractionsLoop, interactionInterval);
@@ -6230,7 +6291,9 @@ var TwitterInteractionClient = class {
6230
6291
  if (targetUsers.length === 0 && !targetUsersConfig.includes("*")) {
6231
6292
  return;
6232
6293
  }
6233
- logger3.info(`Checking posts from target users: ${targetUsers.join(", ") || "everyone (*)"}`);
6294
+ logger3.info(
6295
+ `Checking posts from target users: ${targetUsers.join(", ") || "everyone (*)"}`
6296
+ );
6234
6297
  for (const targetUser of targetUsers) {
6235
6298
  try {
6236
6299
  const normalizedUsername = targetUser.replace(/^@/, "");
@@ -6242,8 +6305,13 @@ var TwitterInteractionClient = class {
6242
6305
  1 /* Latest */
6243
6306
  );
6244
6307
  if (searchResult.tweets.length > 0) {
6245
- logger3.info(`Found ${searchResult.tweets.length} posts from @${normalizedUsername}`);
6246
- await this.processTargetUserTweets(searchResult.tweets, normalizedUsername);
6308
+ logger3.info(
6309
+ `Found ${searchResult.tweets.length} posts from @${normalizedUsername}`
6310
+ );
6311
+ await this.processTargetUserTweets(
6312
+ searchResult.tweets,
6313
+ normalizedUsername
6314
+ );
6247
6315
  }
6248
6316
  } catch (error) {
6249
6317
  logger3.error(`Error searching posts from @${targetUser}:`, error);
@@ -6281,7 +6349,9 @@ var TwitterInteractionClient = class {
6281
6349
  }
6282
6350
  const shouldEngage = await this.shouldEngageWithTweet(tweet);
6283
6351
  if (shouldEngage) {
6284
- logger3.info(`Engaging with tweet from @${username}: ${tweet.text.substring(0, 50)}...`);
6352
+ logger3.info(
6353
+ `Engaging with tweet from @${username}: ${tweet.text.substring(0, 50)}...`
6354
+ );
6285
6355
  await this.ensureTweetContext(tweet);
6286
6356
  const engaged = await this.engageWithTweet(tweet);
6287
6357
  if (engaged) {
@@ -6305,7 +6375,9 @@ var TwitterInteractionClient = class {
6305
6375
  return tweetAge < 12 * 60 * 60 * 1e3;
6306
6376
  });
6307
6377
  if (relevantTweets.length > 0) {
6308
- logger3.info(`Found ${relevantTweets.length} relevant tweets from timeline`);
6378
+ logger3.info(
6379
+ `Found ${relevantTweets.length} relevant tweets from timeline`
6380
+ );
6309
6381
  await this.processTargetUserTweets(relevantTweets, "timeline");
6310
6382
  }
6311
6383
  } catch (error) {
@@ -6476,8 +6548,13 @@ Response (YES/NO):`;
6476
6548
  const maxInteractionsPerRun = parseInt(
6477
6549
  getSetting(this.runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN") || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "10"
6478
6550
  );
6479
- const tweetsToProcess = uniqueTweetCandidates.slice(0, maxInteractionsPerRun);
6480
- logger3.info(`Processing ${tweetsToProcess.length} of ${uniqueTweetCandidates.length} mention tweets (max: ${maxInteractionsPerRun})`);
6551
+ const tweetsToProcess = uniqueTweetCandidates.slice(
6552
+ 0,
6553
+ maxInteractionsPerRun
6554
+ );
6555
+ logger3.info(
6556
+ `Processing ${tweetsToProcess.length} of ${uniqueTweetCandidates.length} mention tweets (max: ${maxInteractionsPerRun})`
6557
+ );
6481
6558
  for (const tweet of tweetsToProcess) {
6482
6559
  if (!this.client.lastCheckedTweetId || BigInt(tweet.id) > this.client.lastCheckedTweetId) {
6483
6560
  const tweetId = createUniqueUuid2(this.runtime, tweet.id);
@@ -6817,19 +6894,21 @@ Response (YES/NO):`;
6817
6894
 
6818
6895
  // src/post.ts
6819
6896
  import {
6820
- ChannelType as ChannelType2,
6821
6897
  createUniqueUuid as createUniqueUuid3,
6822
- logger as logger4
6898
+ logger as logger4,
6899
+ ModelType as ModelType2
6823
6900
  } from "@elizaos/core";
6824
6901
  var TwitterPostClient = class {
6902
+ // Add lock to prevent concurrent posting
6825
6903
  /**
6826
- * Constructor for initializing a new Twitter client with the provided client, runtime, and state
6827
- * @param {ClientBase} client - The client used for interacting with Twitter API
6828
- * @param {IAgentRuntime} runtime - The runtime environment for the agent
6904
+ * Creates an instance of TwitterPostClient.
6905
+ * @param {ClientBase} client - The client instance.
6906
+ * @param {IAgentRuntime} runtime - The runtime instance.
6829
6907
  * @param {any} state - The state object containing configuration settings
6830
6908
  */
6831
6909
  constructor(client, runtime, state) {
6832
6910
  this.isRunning = false;
6911
+ this.isPosting = false;
6833
6912
  this.client = client;
6834
6913
  this.state = state;
6835
6914
  this.runtime = runtime;
@@ -6871,11 +6950,11 @@ var TwitterPostClient = class {
6871
6950
  }
6872
6951
  };
6873
6952
  await new Promise((resolve) => setTimeout(resolve, 1e3));
6874
- const postImmediately = parseInt(
6875
- this.state?.TWITTER_POST_INTERVAL || getSetting(this.runtime, "TWITTER_POST_INTERVAL") || process.env.TWITTER_POST_INTERVAL || "120"
6876
- ) === 0;
6877
- if (postImmediately) {
6878
- logger4.info("TWITTER_POST_IMMEDIATELY is true, generating initial tweet now");
6953
+ const postImmediately = this.state?.TWITTER_POST_IMMEDIATELY || getSetting(this.runtime, "TWITTER_POST_IMMEDIATELY") || process.env.TWITTER_POST_IMMEDIATELY;
6954
+ if (postImmediately === "true" || postImmediately === true) {
6955
+ logger4.info(
6956
+ "TWITTER_POST_IMMEDIATELY is true, generating initial tweet now"
6957
+ );
6879
6958
  await this.generateNewTweet();
6880
6959
  }
6881
6960
  generateNewTweetLoop();
@@ -6886,78 +6965,124 @@ var TwitterPostClient = class {
6886
6965
  */
6887
6966
  async generateNewTweet() {
6888
6967
  logger4.info("Attempting to generate new tweet...");
6968
+ if (this.isPosting) {
6969
+ logger4.info("Already posting a tweet, skipping concurrent attempt");
6970
+ return;
6971
+ }
6972
+ this.isPosting = true;
6889
6973
  try {
6890
6974
  const userId = this.client.profile?.id;
6891
6975
  if (!userId) {
6892
6976
  logger4.error("Cannot generate tweet: Twitter profile not available");
6893
6977
  return;
6894
6978
  }
6895
- logger4.info(`Generating tweet for user: ${this.client.profile?.username} (${userId})`);
6979
+ logger4.info(
6980
+ `Generating tweet for user: ${this.client.profile?.username} (${userId})`
6981
+ );
6896
6982
  const worldId = createUniqueUuid3(this.runtime, userId);
6897
6983
  const roomId = createUniqueUuid3(this.runtime, `${userId}-home`);
6898
- const callback = async (content) => {
6899
- logger4.info("Tweet generation callback triggered");
6900
- try {
6901
- if (this.isDryRun) {
6902
- logger4.info(`[DRY RUN] Would post tweet: ${content.text}`);
6903
- return [];
6904
- }
6905
- if (content.text.includes("Error: Missing")) {
6906
- logger4.error("Error: Missing some context", content);
6907
- return [];
6908
- }
6909
- logger4.info(`Posting tweet: ${content.text}`);
6910
- const result = await this.postToTwitter(
6911
- content.text,
6912
- content.mediaData
6913
- );
6914
- if (result === null) {
6915
- logger4.info("Skipped posting duplicate tweet");
6916
- return [];
6917
- }
6918
- const tweetId = result.id;
6919
- logger4.info(`Tweet posted successfully! ID: ${tweetId}`);
6920
- if (result) {
6921
- const postedTweetId = createUniqueUuid3(this.runtime, tweetId);
6922
- const postedMemory = {
6923
- id: postedTweetId,
6924
- entityId: this.runtime.agentId,
6925
- agentId: this.runtime.agentId,
6926
- roomId,
6927
- content: {
6928
- ...content,
6929
- source: "twitter",
6930
- channelType: ChannelType2.FEED,
6931
- type: "post",
6932
- metadata: {
6933
- tweetId,
6934
- postedAt: Date.now()
6935
- }
6936
- },
6937
- createdAt: Date.now()
6938
- };
6939
- await this.runtime.createMemory(postedMemory, "messages");
6940
- return [postedMemory];
6941
- }
6942
- return [];
6943
- } catch (error) {
6944
- logger4.error("Error in tweet generation callback:", error);
6945
- return [];
6946
- }
6947
- };
6948
- this.runtime.emitEvent(
6949
- "TWITTER_POST_GENERATED" /* POST_GENERATED */,
6984
+ const state = await this.runtime.composeState({
6985
+ agentId: this.runtime.agentId,
6986
+ entityId: this.runtime.agentId,
6987
+ roomId,
6988
+ content: { text: "", type: "post" },
6989
+ createdAt: Date.now()
6990
+ });
6991
+ const tweetPrompt = `You are ${this.runtime.character.name}.
6992
+ ${this.runtime.character.bio}
6993
+
6994
+ CRITICAL: Generate a tweet that sounds like YOU, not a generic motivational poster or LinkedIn influencer.
6995
+
6996
+ ${this.runtime.character.messageExamples && this.runtime.character.messageExamples.length > 0 ? `
6997
+ Example tweets that capture your voice:
6998
+ ${this.runtime.character.messageExamples.map(
6999
+ (example) => Array.isArray(example) ? example[1]?.content?.text || "" : example
7000
+ ).filter(Boolean).slice(0, 5).join("\n")}
7001
+ ` : ""}
7002
+
7003
+ Style guidelines:
7004
+ - Be authentic, opinionated, and specific - no generic platitudes
7005
+ - Use your unique voice and perspective
7006
+ - Share hot takes, unpopular opinions, or specific insights
7007
+ - Be conversational, not preachy
7008
+ - If you use emojis, use them sparingly and purposefully
7009
+ - Length: 50-280 characters (keep it punchy)
7010
+ - NO hashtags unless absolutely essential
7011
+ - NO generic motivational content
7012
+
7013
+ Your interests: ${this.runtime.character.topics?.join(", ") || "technology, crypto, AI"}
7014
+
7015
+ ${this.runtime.character.style ? `Your style: ${typeof this.runtime.character.style === "object" ? this.runtime.character.style.all?.join(", ") || JSON.stringify(this.runtime.character.style) : this.runtime.character.style}` : ""}
7016
+
7017
+ Recent context:
7018
+ ${state.recentMemories?.slice(0, 3).map((m) => m.content.text).join("\n") || "No recent context"}
7019
+
7020
+ Generate a single tweet that sounds like YOU would actually write it:`;
7021
+ const generatedContent = await this.runtime.useModel(
7022
+ ModelType2.TEXT_SMALL,
6950
7023
  {
6951
- callback,
6952
- entityId: this.runtime.agentId,
6953
- userId,
6954
- roomId,
6955
- source: "twitter"
7024
+ prompt: tweetPrompt,
7025
+ temperature: 0.9,
7026
+ // Increased for more creativity
7027
+ maxTokens: 100
6956
7028
  }
6957
7029
  );
6958
- logger4.info("POST_GENERATED event emitted successfully");
7030
+ const tweetText = generatedContent.trim();
7031
+ if (!tweetText || tweetText.length === 0) {
7032
+ logger4.error("Generated empty tweet content");
7033
+ return;
7034
+ }
7035
+ if (tweetText.includes("Error: Missing")) {
7036
+ logger4.error("Error in generated content:", tweetText);
7037
+ return;
7038
+ }
7039
+ if (tweetText.length > 280) {
7040
+ logger4.warn(`Generated tweet too long (${tweetText.length} chars), truncating...`);
7041
+ const sentences = tweetText.match(/[^.!?]+[.!?]+/g) || [tweetText];
7042
+ let truncated = "";
7043
+ for (const sentence of sentences) {
7044
+ if ((truncated + sentence).length <= 280) {
7045
+ truncated += sentence;
7046
+ } else {
7047
+ break;
7048
+ }
7049
+ }
7050
+ const finalTweet = truncated.trim() || tweetText.substring(0, 277) + "...";
7051
+ logger4.info(`Truncated tweet: ${finalTweet}`);
7052
+ if (this.isDryRun) {
7053
+ logger4.info(`[DRY RUN] Would post tweet: ${finalTweet}`);
7054
+ return;
7055
+ }
7056
+ const result2 = await this.postToTwitter(finalTweet, []);
7057
+ if (result2 === null) {
7058
+ logger4.info("Skipped posting duplicate tweet");
7059
+ return;
7060
+ }
7061
+ const tweetId2 = result2.id;
7062
+ logger4.info(`Tweet posted successfully! ID: ${tweetId2}`);
7063
+ logger4.info("Tweet posted successfully (memory saving disabled due to room constraints)");
7064
+ return;
7065
+ }
7066
+ logger4.info(`Generated tweet: ${tweetText}`);
7067
+ if (this.isDryRun) {
7068
+ logger4.info(`[DRY RUN] Would post tweet: ${tweetText}`);
7069
+ return;
7070
+ }
7071
+ const result = await this.postToTwitter(tweetText, []);
7072
+ if (result === null) {
7073
+ logger4.info("Skipped posting duplicate tweet");
7074
+ return;
7075
+ }
7076
+ const tweetId = result.id;
7077
+ logger4.info(`Tweet posted successfully! ID: ${tweetId}`);
7078
+ if (result) {
7079
+ const postedTweetId = createUniqueUuid3(this.runtime, tweetId);
7080
+ logger4.info("Tweet posted successfully (memory saving temporarily disabled)");
7081
+ }
6959
7082
  } catch (error) {
6960
7083
  logger4.error("Error generating tweet:", error);
7084
+ } finally {
7085
+ this.isPosting = false;
6961
7086
  }
6962
7087
  }
6963
7088
  /**
@@ -7014,7 +7139,7 @@ import {
7014
7139
  ChannelType as ChannelType3,
7015
7140
  composePromptFromState,
7016
7141
  createUniqueUuid as createUniqueUuid4,
7017
- ModelType as ModelType2,
7142
+ ModelType as ModelType3,
7018
7143
  parseKeyValueXml
7019
7144
  } from "@elizaos/core";
7020
7145
  import { logger as logger5 } from "@elizaos/core";
@@ -7026,20 +7151,23 @@ var twitterActionTemplate = `
7026
7151
  {{postDirections}}
7027
7152
 
7028
7153
  Guidelines:
7029
- - ONLY engage with content that DIRECTLY relates to character's core interests
7030
- - Direct mentions are priority IF they are on-topic
7031
- - Skip ALL content that is:
7032
- - Off-topic or tangentially related
7033
- - From high-profile accounts unless explicitly relevant
7034
- - Generic/viral content without specific relevance
7035
- - Political/controversial unless central to character
7036
- - Promotional/marketing unless directly relevant
7154
+ - Engage with content that relates to character's interests and expertise
7155
+ - Direct mentions should be prioritized when relevant
7156
+ - Consider engaging with:
7157
+ - Content directly related to your topics
7158
+ - Interesting discussions you can contribute to
7159
+ - Questions you can help answer
7160
+ - Content from users you've interacted with before
7161
+ - Skip content that is:
7162
+ - Completely off-topic or spam
7163
+ - Inflammatory or highly controversial (unless it's your area)
7164
+ - Pure marketing/promotional with no value
7037
7165
 
7038
7166
  Actions (respond only with tags):
7039
- [LIKE] - Perfect topic match AND aligns with character (9.8/10)
7040
- [RETWEET] - Exceptional content that embodies character's expertise (9.5/10)
7041
- [QUOTE] - Can add substantial domain expertise (9.5/10)
7042
- [REPLY] - Can contribute meaningful, expert-level insight (9.5/10)
7167
+ [LIKE] - Content is relevant and interesting (7/10 or higher)
7168
+ [RETWEET] - Content is valuable and worth sharing (8/10 or higher)
7169
+ [QUOTE] - You can add meaningful commentary (7.5/10 or higher)
7170
+ [REPLY] - You can contribute helpful insights (7/10 or higher)
7043
7171
  `;
7044
7172
  var quoteTweetTemplate = `# Task: Write a quote tweet in the voice, style, and perspective of {{agentName}} @{{twitterUserName}}.
7045
7173
 
@@ -7105,7 +7233,9 @@ var TwitterTimelineClient = class {
7105
7233
  this.state?.TWITTER_ENGAGEMENT_INTERVAL || getSetting(this.runtime, "TWITTER_ENGAGEMENT_INTERVAL") || process.env.TWITTER_ENGAGEMENT_INTERVAL || "30"
7106
7234
  );
7107
7235
  const actionInterval = engagementIntervalMinutes * 60 * 1e3;
7108
- logger5.info(`Timeline client will check every ${engagementIntervalMinutes} minutes`);
7236
+ logger5.info(
7237
+ `Timeline client will check every ${engagementIntervalMinutes} minutes`
7238
+ );
7109
7239
  this.handleTimeline();
7110
7240
  if (this.isRunning) {
7111
7241
  setTimeout(handleTwitterTimelineLoop, actionInterval);
@@ -7173,7 +7303,7 @@ ${tweet.text}
7173
7303
 
7174
7304
  Choose any combination of [LIKE], [RETWEET], [QUOTE], and [REPLY] that are appropriate. Each action must be on its own line. Your response must only include the chosen actions.`;
7175
7305
  const actionResponse = await this.runtime.useModel(
7176
- ModelType2.TEXT_SMALL,
7306
+ ModelType3.TEXT_SMALL,
7177
7307
  {
7178
7308
  prompt: actionRespondPrompt
7179
7309
  }
@@ -7227,7 +7357,12 @@ ${actionSummary.join("\n")}`);
7227
7357
  }
7228
7358
  async processTimelineActions(tweetDecisions) {
7229
7359
  const results = [];
7230
- for (const { tweet, actionResponse, tweetState, roomId } of tweetDecisions) {
7360
+ for (const {
7361
+ tweet,
7362
+ actionResponse,
7363
+ tweetState,
7364
+ roomId
7365
+ } of tweetDecisions) {
7231
7366
  const tweetId = this.createTweetId(this.runtime, tweet);
7232
7367
  const executedActions = [];
7233
7368
  await this.runtime.createMemory(
@@ -7345,13 +7480,15 @@ ${actionSummary.join("\n")}`);
7345
7480
  }) + `
7346
7481
  You are responding to this tweet:
7347
7482
  ${tweet.text}`;
7348
- const quoteResponse = await this.runtime.useModel(ModelType2.TEXT_SMALL, {
7483
+ const quoteResponse = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
7349
7484
  prompt: quotePrompt
7350
7485
  });
7351
7486
  const responseObject = parseKeyValueXml(quoteResponse);
7352
7487
  if (responseObject.post) {
7353
7488
  if (this.isDryRun) {
7354
- logger5.log(`[DRY RUN] Would have quoted tweet ${tweet.id} with: ${responseObject.post}`);
7489
+ logger5.log(
7490
+ `[DRY RUN] Would have quoted tweet ${tweet.id} with: ${responseObject.post}`
7491
+ );
7355
7492
  return;
7356
7493
  }
7357
7494
  const result = await this.client.requestQueue.add(
@@ -7396,13 +7533,15 @@ ${tweet.text}`;
7396
7533
  }) + `
7397
7534
  You are replying to this tweet:
7398
7535
  ${tweet.text}`;
7399
- const replyResponse = await this.runtime.useModel(ModelType2.TEXT_SMALL, {
7536
+ const replyResponse = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
7400
7537
  prompt: replyPrompt
7401
7538
  });
7402
7539
  const responseObject = parseKeyValueXml(replyResponse);
7403
7540
  if (responseObject.post) {
7404
7541
  if (this.isDryRun) {
7405
- logger5.log(`[DRY RUN] Would have replied to tweet ${tweet.id} with: ${responseObject.post}`);
7542
+ logger5.log(
7543
+ `[DRY RUN] Would have replied to tweet ${tweet.id} with: ${responseObject.post}`
7544
+ );
7406
7545
  return;
7407
7546
  }
7408
7547
  const result = await sendTweet(
@@ -7438,7 +7577,7 @@ ${tweet.text}`;
7438
7577
  import {
7439
7578
  createUniqueUuid as createUniqueUuid5,
7440
7579
  logger as logger6,
7441
- ModelType as ModelType3
7580
+ ModelType as ModelType4
7442
7581
  } from "@elizaos/core";
7443
7582
  var TwitterDiscoveryClient = class {
7444
7583
  constructor(client, runtime, state) {
@@ -7489,7 +7628,9 @@ var TwitterDiscoveryClient = class {
7489
7628
  topics = this.extractTopicsFromBio(character.bio);
7490
7629
  }
7491
7630
  } else {
7492
- logger6.warn("Character not available in runtime, using default topics for discovery");
7631
+ logger6.warn(
7632
+ "Character not available in runtime, using default topics for discovery"
7633
+ );
7493
7634
  }
7494
7635
  return {
7495
7636
  topics,
@@ -7500,11 +7641,17 @@ var TwitterDiscoveryClient = class {
7500
7641
  getSetting(this.runtime, "TWITTER_MAX_FOLLOWS_PER_CYCLE") || process.env.TWITTER_MAX_FOLLOWS_PER_CYCLE || "5"
7501
7642
  ),
7502
7643
  maxEngagementsPerCycle: parseInt(
7503
- getSetting(this.runtime, "TWITTER_MAX_ENGAGEMENTS_PER_RUN") || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "10"
7644
+ getSetting(
7645
+ this.runtime,
7646
+ "TWITTER_MAX_ENGAGEMENTS_PER_RUN"
7647
+ ) || process.env.TWITTER_MAX_ENGAGEMENTS_PER_RUN || "10"
7504
7648
  ),
7505
- likeThreshold: 0.6,
7506
- replyThreshold: 0.8,
7507
- quoteThreshold: 0.85
7649
+ likeThreshold: 0.3,
7650
+ // Lowered from 0.6
7651
+ replyThreshold: 0.5,
7652
+ // Lowered from 0.8
7653
+ quoteThreshold: 0.7
7654
+ // Lowered from 0.85
7508
7655
  };
7509
7656
  }
7510
7657
  extractTopicsFromBio(bio) {
@@ -7512,7 +7659,16 @@ var TwitterDiscoveryClient = class {
7512
7659
  return [];
7513
7660
  }
7514
7661
  const bioText = Array.isArray(bio) ? bio.join(" ") : bio;
7515
- const words = bioText.toLowerCase().split(/\s+/).filter((word) => word.length > 4).filter((word) => !["about", "helping", "working", "people", "making", "building"].includes(word));
7662
+ const words = bioText.toLowerCase().split(/\s+/).filter((word) => word.length > 4).filter(
7663
+ (word) => ![
7664
+ "about",
7665
+ "helping",
7666
+ "working",
7667
+ "people",
7668
+ "making",
7669
+ "building"
7670
+ ].includes(word)
7671
+ );
7516
7672
  return [...new Set(words)].slice(0, 5);
7517
7673
  }
7518
7674
  async start() {
@@ -7533,7 +7689,9 @@ var TwitterDiscoveryClient = class {
7533
7689
  );
7534
7690
  const variance = Math.random() * 20 - 10;
7535
7691
  const nextInterval = (baseInterval + variance) * 60 * 1e3;
7536
- logger6.info(`Next discovery cycle in ${(baseInterval + variance).toFixed(1)} minutes`);
7692
+ logger6.info(
7693
+ `Next discovery cycle in ${(baseInterval + variance).toFixed(1)} minutes`
7694
+ );
7537
7695
  if (this.isRunning) {
7538
7696
  setTimeout(discoveryLoop, nextInterval);
7539
7697
  }
@@ -7548,7 +7706,9 @@ var TwitterDiscoveryClient = class {
7548
7706
  logger6.info("Starting Twitter discovery cycle...");
7549
7707
  const discoveries = await this.discoverContent();
7550
7708
  const { tweets, accounts } = discoveries;
7551
- logger6.info(`Discovered ${tweets.length} tweets and ${accounts.length} accounts`);
7709
+ logger6.info(
7710
+ `Discovered ${tweets.length} tweets and ${accounts.length} accounts`
7711
+ );
7552
7712
  const followedCount = await this.processAccounts(accounts);
7553
7713
  const engagementCount = await this.processTweets(tweets);
7554
7714
  logger6.info(
@@ -7561,9 +7721,7 @@ var TwitterDiscoveryClient = class {
7561
7721
  try {
7562
7722
  const topicContent = await this.discoverFromTopics();
7563
7723
  allTweets.push(...topicContent.tweets);
7564
- topicContent.accounts.forEach(
7565
- (acc) => allAccounts.set(acc.user.id, acc)
7566
- );
7724
+ topicContent.accounts.forEach((acc) => allAccounts.set(acc.user.id, acc));
7567
7725
  } catch (error) {
7568
7726
  logger6.error("Failed to discover from topics:", error);
7569
7727
  }
@@ -7586,7 +7744,9 @@ var TwitterDiscoveryClient = class {
7586
7744
  logger6.error("Failed to discover from popular accounts:", error);
7587
7745
  }
7588
7746
  const sortedTweets = allTweets.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, 50);
7589
- const sortedAccounts = Array.from(allAccounts.values()).sort((a, b) => b.qualityScore * b.relevanceScore - a.qualityScore * a.relevanceScore).slice(0, 20);
7747
+ const sortedAccounts = Array.from(allAccounts.values()).sort(
7748
+ (a, b) => b.qualityScore * b.relevanceScore - a.qualityScore * a.relevanceScore
7749
+ ).slice(0, 20);
7590
7750
  return { tweets: sortedTweets, accounts: sortedAccounts };
7591
7751
  }
7592
7752
  async discoverFromTopics() {
@@ -7652,17 +7812,17 @@ var TwitterDiscoveryClient = class {
7652
7812
  );
7653
7813
  for (const tweet of searchResults.tweets) {
7654
7814
  const engagementScore = (tweet.likes || 0) + (tweet.retweets || 0) * 2;
7655
- if (engagementScore < 50) continue;
7815
+ if (engagementScore < 10) continue;
7656
7816
  const scored = this.scoreTweet(tweet, "thread");
7657
7817
  tweets.push(scored);
7658
7818
  const account = this.scoreAccount({
7659
7819
  id: tweet.userId,
7660
7820
  username: tweet.username,
7661
7821
  name: tweet.name || tweet.username,
7662
- followersCount: 5e3
7663
- // Estimate for viral content creators
7822
+ followersCount: 1e3
7823
+ // Reasonable estimate for engaged users
7664
7824
  });
7665
- if (account.qualityScore > 0.6) {
7825
+ if (account.qualityScore > 0.5) {
7666
7826
  accounts.set(tweet.userId, account);
7667
7827
  }
7668
7828
  }
@@ -7687,7 +7847,7 @@ var TwitterDiscoveryClient = class {
7687
7847
  );
7688
7848
  for (const tweet of results.tweets) {
7689
7849
  const engagement = (tweet.likes || 0) + (tweet.retweets || 0) * 2;
7690
- if (engagement < 20) continue;
7850
+ if (engagement < 5) continue;
7691
7851
  const scored = this.scoreTweet(tweet, "topic");
7692
7852
  tweets.push(scored);
7693
7853
  const estimatedFollowers = Math.max(
@@ -7706,7 +7866,10 @@ var TwitterDiscoveryClient = class {
7706
7866
  }
7707
7867
  }
7708
7868
  } catch (error) {
7709
- logger6.error(`Failed to discover popular accounts for ${topic}:`, error);
7869
+ logger6.error(
7870
+ `Failed to discover popular accounts for ${topic}:`,
7871
+ error
7872
+ );
7710
7873
  }
7711
7874
  }
7712
7875
  return { tweets, accounts: Array.from(accounts.values()) };
@@ -7716,12 +7879,15 @@ var TwitterDiscoveryClient = class {
7716
7879
  scoreTweet(tweet, source) {
7717
7880
  let relevanceScore = 0;
7718
7881
  const sourceScores = {
7719
- topic: 0.5,
7720
- thread: 0.4
7882
+ topic: 0.4,
7883
+ thread: 0.35
7721
7884
  };
7722
7885
  relevanceScore += sourceScores[source];
7723
7886
  const engagementScore = Math.min(
7724
- (tweet.likes || 0) / 1e3 + (tweet.retweets || 0) / 500 + (tweet.replies || 0) / 100,
7887
+ (tweet.likes || 0) / 100 + // 100 likes = 0.1 points (was 1000)
7888
+ (tweet.retweets || 0) / 50 + // 50 retweets = 0.1 points (was 500)
7889
+ (tweet.replies || 0) / 20,
7890
+ // 20 replies = 0.1 points (was 100)
7725
7891
  0.3
7726
7892
  );
7727
7893
  relevanceScore += engagementScore;
@@ -7729,7 +7895,7 @@ var TwitterDiscoveryClient = class {
7729
7895
  const topicMatches = this.config.topics.filter(
7730
7896
  (topic) => textLower.includes(topic.toLowerCase())
7731
7897
  ).length;
7732
- relevanceScore += Math.min(topicMatches * 0.1, 0.3);
7898
+ relevanceScore += Math.min(topicMatches * 0.15, 0.3);
7733
7899
  relevanceScore = Math.min(relevanceScore, 1);
7734
7900
  let engagementType = "skip";
7735
7901
  if (relevanceScore >= this.config.quoteThreshold) {
@@ -7783,7 +7949,10 @@ var TwitterDiscoveryClient = class {
7783
7949
  followedCount++;
7784
7950
  await this.delay(2e3 + Math.random() * 3e3);
7785
7951
  } catch (error) {
7786
- logger6.error(`Failed to follow @${scoredAccount.user.username}:`, error);
7952
+ logger6.error(
7953
+ `Failed to follow @${scoredAccount.user.username}:`,
7954
+ error
7955
+ );
7787
7956
  }
7788
7957
  }
7789
7958
  return followedCount;
@@ -7794,51 +7963,76 @@ var TwitterDiscoveryClient = class {
7794
7963
  if (engagementCount >= this.config.maxEngagementsPerCycle) break;
7795
7964
  if (scoredTweet.engagementType === "skip") continue;
7796
7965
  try {
7797
- const tweetMemoryId = createUniqueUuid5(this.runtime, scoredTweet.tweet.id);
7966
+ const tweetMemoryId = createUniqueUuid5(
7967
+ this.runtime,
7968
+ scoredTweet.tweet.id
7969
+ );
7798
7970
  const existingMemory = await this.runtime.getMemoryById(tweetMemoryId);
7799
7971
  if (existingMemory) {
7800
- logger6.debug(`Already engaged with tweet ${scoredTweet.tweet.id}, skipping`);
7972
+ logger6.debug(
7973
+ `Already engaged with tweet ${scoredTweet.tweet.id}, skipping`
7974
+ );
7801
7975
  continue;
7802
7976
  }
7803
7977
  switch (scoredTweet.engagementType) {
7804
7978
  case "like":
7805
7979
  if (this.isDryRun) {
7806
- logger6.info(`[DRY RUN] Would like tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`);
7980
+ logger6.info(
7981
+ `[DRY RUN] Would like tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
7982
+ );
7807
7983
  } else {
7808
7984
  await this.twitterClient.likeTweet(scoredTweet.tweet.id);
7809
- logger6.info(`Liked tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`);
7985
+ logger6.info(
7986
+ `Liked tweet: ${scoredTweet.tweet.id} (score: ${scoredTweet.relevanceScore.toFixed(2)})`
7987
+ );
7810
7988
  }
7811
7989
  break;
7812
7990
  case "reply":
7813
7991
  const replyText = await this.generateReply(scoredTweet.tweet);
7814
7992
  if (this.isDryRun) {
7815
- logger6.info(`[DRY RUN] Would reply to tweet ${scoredTweet.tweet.id} with: "${replyText}"`);
7993
+ logger6.info(
7994
+ `[DRY RUN] Would reply to tweet ${scoredTweet.tweet.id} with: "${replyText}"`
7995
+ );
7816
7996
  } else {
7817
- await this.twitterClient.sendTweet(replyText, scoredTweet.tweet.id);
7997
+ await this.twitterClient.sendTweet(
7998
+ replyText,
7999
+ scoredTweet.tweet.id
8000
+ );
7818
8001
  logger6.info(`Replied to tweet: ${scoredTweet.tweet.id}`);
7819
8002
  }
7820
8003
  break;
7821
8004
  case "quote":
7822
8005
  const quoteText = await this.generateQuote(scoredTweet.tweet);
7823
8006
  if (this.isDryRun) {
7824
- logger6.info(`[DRY RUN] Would quote tweet ${scoredTweet.tweet.id} with: "${quoteText}"`);
8007
+ logger6.info(
8008
+ `[DRY RUN] Would quote tweet ${scoredTweet.tweet.id} with: "${quoteText}"`
8009
+ );
7825
8010
  } else {
7826
- await this.twitterClient.sendQuoteTweet(quoteText, scoredTweet.tweet.id);
8011
+ await this.twitterClient.sendQuoteTweet(
8012
+ quoteText,
8013
+ scoredTweet.tweet.id
8014
+ );
7827
8015
  logger6.info(`Quoted tweet: ${scoredTweet.tweet.id}`);
7828
8016
  }
7829
8017
  break;
7830
8018
  }
7831
- await this.saveEngagementMemory(scoredTweet.tweet, scoredTweet.engagementType);
8019
+ await this.saveEngagementMemory(
8020
+ scoredTweet.tweet,
8021
+ scoredTweet.engagementType
8022
+ );
7832
8023
  engagementCount++;
7833
8024
  await this.delay(3e3 + Math.random() * 5e3);
7834
8025
  } catch (error) {
7835
- logger6.error(`Failed to engage with tweet ${scoredTweet.tweet.id}:`, error);
8026
+ logger6.error(
8027
+ `Failed to engage with tweet ${scoredTweet.tweet.id}:`,
8028
+ error
8029
+ );
7836
8030
  }
7837
8031
  }
7838
8032
  return engagementCount;
7839
8033
  }
7840
8034
  async checkIfFollowing(userId) {
7841
- const embedding = await this.runtime.useModel(ModelType3.TEXT_EMBEDDING, {
8035
+ const embedding = await this.runtime.useModel(ModelType4.TEXT_EMBEDDING, {
7842
8036
  text: `followed twitter user ${userId}`
7843
8037
  });
7844
8038
  const followMemories = await this.runtime.searchMemories({
@@ -7874,7 +8068,7 @@ Keep the reply:
7874
8068
  - Respectful and constructive
7875
8069
 
7876
8070
  Reply:`;
7877
- const response = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
8071
+ const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
7878
8072
  prompt,
7879
8073
  max_tokens: 100,
7880
8074
  temperature: 0.8
@@ -7906,7 +8100,7 @@ Create a quote tweet that:
7906
8100
  - Encourages further discussion
7907
8101
 
7908
8102
  Quote tweet:`;
7909
- const response = await this.runtime.useModel(ModelType3.TEXT_SMALL, {
8103
+ const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
7910
8104
  prompt,
7911
8105
  max_tokens: 100,
7912
8106
  temperature: 0.8
@@ -7914,37 +8108,43 @@ Quote tweet:`;
7914
8108
  return response.trim();
7915
8109
  }
7916
8110
  async saveEngagementMemory(tweet, engagementType) {
7917
- const memoryId = await this.runtime.createMemory({
7918
- id: createUniqueUuid5(this.runtime, tweet.id),
7919
- entityId: createUniqueUuid5(this.runtime, tweet.userId),
7920
- content: {
7921
- text: `${engagementType} tweet from @${tweet.username}: ${tweet.text}`,
7922
- metadata: {
7923
- tweetId: tweet.id,
7924
- engagementType,
7925
- source: "discovery",
7926
- isDryRun: this.isDryRun
7927
- }
8111
+ const memoryId = await this.runtime.createMemory(
8112
+ {
8113
+ id: createUniqueUuid5(this.runtime, tweet.id),
8114
+ entityId: createUniqueUuid5(this.runtime, tweet.userId),
8115
+ content: {
8116
+ text: `${engagementType} tweet from @${tweet.username}: ${tweet.text}`,
8117
+ metadata: {
8118
+ tweetId: tweet.id,
8119
+ engagementType,
8120
+ source: "discovery",
8121
+ isDryRun: this.isDryRun
8122
+ }
8123
+ },
8124
+ roomId: createUniqueUuid5(this.runtime, tweet.conversationId)
7928
8125
  },
7929
- roomId: createUniqueUuid5(this.runtime, tweet.conversationId)
7930
- }, "messages");
8126
+ "messages"
8127
+ );
7931
8128
  }
7932
8129
  async saveFollowMemory(user) {
7933
- const memoryId = await this.runtime.createMemory({
7934
- entityId: createUniqueUuid5(this.runtime, user.id),
7935
- content: {
7936
- text: `followed twitter user ${user.id} @${user.username}`,
7937
- metadata: {
7938
- userId: user.id,
7939
- username: user.username,
7940
- name: user.name,
7941
- followersCount: user.followersCount,
7942
- source: "discovery",
7943
- isDryRun: this.isDryRun
7944
- }
8130
+ const memoryId = await this.runtime.createMemory(
8131
+ {
8132
+ entityId: createUniqueUuid5(this.runtime, user.id),
8133
+ content: {
8134
+ text: `followed twitter user ${user.id} @${user.username}`,
8135
+ metadata: {
8136
+ userId: user.id,
8137
+ username: user.username,
8138
+ name: user.name,
8139
+ followersCount: user.followersCount,
8140
+ source: "discovery",
8141
+ isDryRun: this.isDryRun
8142
+ }
8143
+ },
8144
+ roomId: createUniqueUuid5(this.runtime, `twitter-follows`)
7945
8145
  },
7946
- roomId: createUniqueUuid5(this.runtime, `twitter-follows`)
7947
- }, "messages");
8146
+ "messages"
8147
+ );
7948
8148
  }
7949
8149
  delay(ms) {
7950
8150
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -8008,7 +8208,9 @@ var RequestQueue = class {
8008
8208
  await this.exponentialBackoff(retryCount);
8009
8209
  break;
8010
8210
  } else {
8011
- logger7.error(`Max retries (${this.maxRetries}) exceeded for request, skipping`);
8211
+ logger7.error(
8212
+ `Max retries (${this.maxRetries}) exceeded for request, skipping`
8213
+ );
8012
8214
  this.retryAttempts.delete(request);
8013
8215
  }
8014
8216
  }
@@ -8541,13 +8743,17 @@ var TwitterClientInstance = class {
8541
8743
  constructor(runtime, state) {
8542
8744
  this.client = new ClientBase(runtime, state);
8543
8745
  const postEnabledSetting = getSetting(runtime, "TWITTER_ENABLE_POST") ?? process.env.TWITTER_ENABLE_POST;
8544
- logger8.debug(`TWITTER_ENABLE_POST setting value: ${JSON.stringify(postEnabledSetting)}, type: ${typeof postEnabledSetting}`);
8746
+ logger8.debug(
8747
+ `TWITTER_ENABLE_POST setting value: ${JSON.stringify(postEnabledSetting)}, type: ${typeof postEnabledSetting}`
8748
+ );
8545
8749
  const postEnabled = postEnabledSetting === "true" || postEnabledSetting === true;
8546
8750
  if (postEnabled) {
8547
8751
  logger8.info("Twitter posting is ENABLED - creating post client");
8548
8752
  this.post = new TwitterPostClient(this.client, runtime, state);
8549
8753
  } else {
8550
- logger8.info("Twitter posting is DISABLED - set TWITTER_ENABLE_POST=true to enable automatic posting");
8754
+ logger8.info(
8755
+ "Twitter posting is DISABLED - set TWITTER_ENABLE_POST=true to enable automatic posting"
8756
+ );
8551
8757
  }
8552
8758
  const repliesEnabled = (getSetting(runtime, "TWITTER_ENABLE_REPLIES") ?? process.env.TWITTER_ENABLE_REPLIES) !== "false";
8553
8759
  if (repliesEnabled) {
@@ -8572,7 +8778,9 @@ var TwitterClientInstance = class {
8572
8778
  logger8.info("Twitter discovery service is ENABLED");
8573
8779
  this.discovery = new TwitterDiscoveryClient(this.client, runtime, state);
8574
8780
  } else {
8575
- logger8.info("Twitter discovery service is DISABLED - set TWITTER_ENABLE_DISCOVERY=true to enable");
8781
+ logger8.info(
8782
+ "Twitter discovery service is DISABLED - set TWITTER_ENABLE_DISCOVERY=true to enable"
8783
+ );
8576
8784
  }
8577
8785
  }
8578
8786
  };
@@ -8635,11 +8843,17 @@ var TwitterService = _TwitterService;
8635
8843
  // src/actions/postTweet.ts
8636
8844
  import {
8637
8845
  logger as logger9,
8638
- ModelType as ModelType4
8846
+ ModelType as ModelType5
8639
8847
  } from "@elizaos/core";
8640
8848
  var postTweetAction = {
8641
8849
  name: "POST_TWEET",
8642
- similes: ["TWEET", "SEND_TWEET", "TWITTER_POST", "POST_ON_TWITTER", "SHARE_ON_TWITTER"],
8850
+ similes: [
8851
+ "TWEET",
8852
+ "SEND_TWEET",
8853
+ "TWITTER_POST",
8854
+ "POST_ON_TWITTER",
8855
+ "SHARE_ON_TWITTER"
8856
+ ],
8643
8857
  validate: async (runtime, message) => {
8644
8858
  logger9.debug("Validating POST_TWEET action");
8645
8859
  const text = message.content?.text?.trim();
@@ -8660,32 +8874,70 @@ var postTweetAction = {
8660
8874
  if (!client.twitterClient) {
8661
8875
  await client.init();
8662
8876
  }
8877
+ let text = message.content?.text?.trim();
8878
+ if (!text) {
8879
+ logger9.error("No text content for tweet");
8880
+ if (callback) {
8881
+ callback({
8882
+ text: "I need something to tweet! Please provide the text.",
8883
+ action: "POST_TWEET"
8884
+ });
8885
+ }
8886
+ return;
8887
+ }
8888
+ if (text.length > 280) {
8889
+ logger9.info(`Truncating tweet from ${text.length} to 280 characters`);
8890
+ const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
8891
+ let truncated = "";
8892
+ for (const sentence of sentences) {
8893
+ if ((truncated + sentence).length <= 280) {
8894
+ truncated += sentence;
8895
+ } else {
8896
+ break;
8897
+ }
8898
+ }
8899
+ text = truncated.trim() || text.substring(0, 277) + "...";
8900
+ logger9.info(`Truncated tweet: ${text}`);
8901
+ }
8663
8902
  if (!client.profile) {
8664
- throw new Error("Twitter client not properly initialized - no profile found");
8903
+ throw new Error(
8904
+ "Twitter client not properly initialized - no profile found"
8905
+ );
8665
8906
  }
8666
- const tweetText = message.content?.text?.trim() || "";
8667
- let finalTweetText = tweetText;
8668
- if (tweetText.length < 50 || tweetText.toLowerCase().includes("post") || tweetText.toLowerCase().includes("tweet")) {
8669
- const tweetPrompt = `You are ${runtime.character.name}. Create an interesting tweet based on this context:
8907
+ let finalTweetText = text;
8908
+ if (text.length < 50 || text.toLowerCase().includes("post") || text.toLowerCase().includes("tweet")) {
8909
+ const tweetPrompt = `You are ${runtime.character.name}.
8910
+ ${runtime.character.bio || ""}
8670
8911
 
8671
- Context: ${tweetText}
8912
+ CRITICAL: Generate a tweet based on the context that sounds like YOU, not generic corporate speak.
8672
8913
 
8673
- Your interests: ${runtime.character.topics?.join(", ") || "technology, AI, web3"}
8674
- Your style: ${runtime.character.style?.all?.join(", ") || "thoughtful, engaging"}
8914
+ Context: ${text}
8675
8915
 
8676
- Generate a tweet that:
8677
- - Is under 280 characters
8678
- - Reflects your personality and interests
8679
- - Is engaging and conversational
8680
- - Doesn't use hashtags unless truly relevant
8681
- - Doesn't ask questions at the end
8682
- - Is not generic or promotional
8916
+ ${runtime.character.messageExamples && runtime.character.messageExamples.length > 0 ? `
8917
+ Your voice examples:
8918
+ ${runtime.character.messageExamples.map(
8919
+ (example) => Array.isArray(example) ? example[1]?.content?.text || "" : example
8920
+ ).filter(Boolean).slice(0, 3).join("\n")}
8921
+ ` : ""}
8922
+
8923
+ Style rules:
8924
+ - Be specific, opinionated, authentic
8925
+ - No generic motivational content or platitudes
8926
+ - Share actual insights, hot takes, or unique perspectives
8927
+ - Keep it conversational and punchy
8928
+ - Under 280 characters
8929
+ - Skip hashtags unless essential
8930
+ - Don't end with generic questions
8931
+
8932
+ Your interests: ${runtime.character.topics?.join(", ") || "technology, AI, web3"}
8933
+ ${runtime.character.style ? `Your style: ${typeof runtime.character.style === "object" ? runtime.character.style.all?.join(", ") || "" : runtime.character.style}` : ""}
8683
8934
 
8684
8935
  Tweet:`;
8685
- const response = await runtime.useModel(ModelType4.TEXT_SMALL, {
8936
+ const response = await runtime.useModel(ModelType5.TEXT_SMALL, {
8686
8937
  prompt: tweetPrompt,
8687
8938
  max_tokens: 100,
8688
- temperature: 0.8
8939
+ temperature: 0.9
8940
+ // Higher for more creativity
8689
8941
  });
8690
8942
  finalTweetText = response.trim();
8691
8943
  }
@@ -8702,16 +8954,19 @@ Tweet:`;
8702
8954
  }
8703
8955
  const tweetUrl = `https://twitter.com/${client.profile.username}/status/${tweetId}`;
8704
8956
  logger9.info(`Successfully posted tweet: ${tweetId}`);
8705
- await runtime.createMemory({
8706
- entityId: runtime.agentId,
8707
- content: {
8708
- text: finalTweetText,
8709
- url: tweetUrl,
8710
- source: "twitter",
8711
- action: "POST_TWEET"
8957
+ await runtime.createMemory(
8958
+ {
8959
+ entityId: runtime.agentId,
8960
+ content: {
8961
+ text: finalTweetText,
8962
+ url: tweetUrl,
8963
+ source: "twitter",
8964
+ action: "POST_TWEET"
8965
+ },
8966
+ roomId: message.roomId
8712
8967
  },
8713
- roomId: message.roomId
8714
- }, "messages");
8968
+ "messages"
8969
+ );
8715
8970
  if (callback) {
8716
8971
  await callback({
8717
8972
  text: `I've posted a tweet: "${finalTweetText}"
@@ -8723,7 +8978,7 @@ View it here: ${tweetUrl}`,
8723
8978
  }
8724
8979
  });
8725
8980
  }
8726
- return true;
8981
+ return;
8727
8982
  } else {
8728
8983
  throw new Error("Failed to post tweet - no response data");
8729
8984
  }
@@ -8735,7 +8990,7 @@ View it here: ${tweetUrl}`,
8735
8990
  metadata: { error: error.message }
8736
8991
  });
8737
8992
  }
8738
- return false;
8993
+ return;
8739
8994
  }
8740
8995
  },
8741
8996
  examples: [