diff --git a/packages/client-truth-social/README.md b/packages/client-truth-social/README.md new file mode 100644 index 00000000000..160810b7e41 --- /dev/null +++ b/packages/client-truth-social/README.md @@ -0,0 +1,159 @@ +# Truth Social Client + +A TypeScript client for interacting with Truth Social's API, built on top of the @elizaos/core framework for AI-powered social media interactions. + +## System Overview + +This client provides automated interaction capabilities with Truth Social, including: + +- Posting and responding to content +- Processing mentions and interactions +- Searching and engaging with relevant content +- Managing rate limits and API interactions + +### Core Components + +1. **API Layer** (`api.ts`) + - Handles direct communication with Truth Social's API + - Manages authentication and rate limiting + - Provides base methods for posts, likes, reposts, etc. + +2. **Base Client** (`base.ts`) + - Provides foundational client functionality + - Manages state and caching + - Handles timeline and post fetching + +3. **Post Management** (`post.ts`) + - Handles post creation and processing + - Manages periodic posting + - Processes post actions and interactions + +4. **Interactions** (`interactions.ts`) + - Manages user interactions and responses + - Handles conversation threading + - Controls engagement limits and timeouts + +5. **Search** (`search.ts`) + - Implements search functionality + - Manages topic-based engagement + - Controls post selection and relevance + +6. **Environment** (`environment.ts`) + - Manages configuration and settings + - Validates environment variables + - Sets operational parameters + +### Supporting Files + +- **Types** (`types.ts`): TypeScript interfaces and types +- **Utils** (`utils.ts`): Shared utilities and helper functions +- **Index** (`index.ts`): Main entry point and client initialization + +## Requirements + +### Dependencies + +- Node.js 23.3+ +- TypeScript 4.5+ +- @ai16z/eliza framework +- axios +- date-fns +- zod + +### Environment Variables + +- TRUTHSOCIAL_DRY_RUN: Boolean, if true, the client will not post or interact with Truth Social +- TRUTHSOCIAL_USERNAME: String, the username of the Truth Social account +- TRUTHSOCIAL_PASSWORD: String, the password of the Truth Social account +- MAX_TRUTH_LENGTH: Number, the maximum length of a post +- POST_INTERVAL_MIN: Number, the minimum interval between posts in seconds +- POST_INTERVAL_MAX: Number, the maximum interval between posts in seconds +- ACTION_INTERVAL: Number, the interval between actions in seconds + +## Setup + +1. Clone the repository +2. Install dependencies: `npm install` +3. Set environment variables +4. Run the client: `npm start` + +## Architecture + +The system follows a layered architecture: + +1. **API Layer** (Bottom) + - Raw API communication + - Rate limiting + - Authentication + +2. **Client Layer** + - Base functionality + - State management + - Caching + +3. **Feature Layer** + - Post management + - Interactions + - Search + - Content generation + +4. **Interface Layer** (Top) + - Client interface + - Runtime management + - Configuration + +## File Dependencies + +``` +index.ts +├── environment.ts +├── base.ts +│ └── api.ts +├── post.ts +│ ├── base.ts +│ └── utils.ts +├── interactions.ts +│ ├── base.ts +│ └── utils.ts +└── search.ts + ├── base.ts + └── utils.ts +``` + +- `api.ts` depends on `types.ts` +- `base.ts` depends on `types.ts` +- `post.ts` depends on `types.ts` +- `interactions.ts` depends on `types.ts` +- `search.ts` depends on `types.ts` +- `environment.ts` depends on `types.ts` +- `utils.ts` depends on `types.ts` +- `index.ts` depends on `types.ts` +- `environment.ts` depends on `types.ts` + +## Safety Notes + +1. Rate Limiting + - Built-in rate limit management + - Configurable intervals + - Automatic backoff + +2. Content Safety + - Content filtering + - Engagement limits + - Conversation depth control + +3. Error Handling + - Comprehensive error catching + - Logging + - Graceful degradation + +## Usage Warning + +This client includes search and interaction capabilities that should be used responsibly.Be aware of: + +1. Rate limit implications +2. User privacy considerations +3. Platform terms of service +4. Potential account restrictions + +Always test with `TRUTHSOCIAL_DRY_RUN=true` before live deployment. diff --git a/packages/client-truth-social/eslint.config.mjs b/packages/client-truth-social/eslint.config.mjs new file mode 100644 index 00000000000..92fe5bbebef --- /dev/null +++ b/packages/client-truth-social/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintGlobalConfig from "../../eslint.config.mjs"; + +export default [...eslintGlobalConfig]; diff --git a/packages/client-truth-social/package.json b/packages/client-truth-social/package.json new file mode 100644 index 00000000000..7c446ae6027 --- /dev/null +++ b/packages/client-truth-social/package.json @@ -0,0 +1,24 @@ +{ + "name": "@elizaos/client-truth", + "version": "0.1.9-alpha.1", + "description": "Truth Social client integration for eliza agents", + "main": "dist/index.js", + "type": "module", + "types": "dist/index.d.ts", + "dependencies": { + "@elizaos/core": "workspace:*", + "glob": "11.0.0", + "zod": "3.23.8" + }, + "devDependencies": { + "tsup": "8.3.5" + }, + "scripts": { + "build": "tsup --format esm --dts", + "dev": "tsup --format esm --dts --watch", + "lint": "eslint . --fix" + }, + "peerDependencies": { + "whatwg-url": "7.1.0" + } +} \ No newline at end of file diff --git a/packages/client-truth-social/src/api.ts b/packages/client-truth-social/src/api.ts new file mode 100644 index 00000000000..3c511d067a1 --- /dev/null +++ b/packages/client-truth-social/src/api.ts @@ -0,0 +1,344 @@ +import axios, { AxiosInstance, AxiosResponse } from 'axios'; +import { parseISO, isAfter } from 'date-fns'; +import { + TruthApiConfig, TruthUserProfile, TruthStatus, TruthSearchResults, + RateLimitInfo, TruthAuthResponse, TruthApiError, CreateStatusOptions +} from './types'; +import { elizaLogger } from "@elizaos/core"; + +const DEFAULT_CONFIG = { + baseUrl: 'https://truthsocial.com', + apiBaseUrl: 'https://truthsocial.com/api', + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + clientId: '9X1Fdd-pxNsAgEDNi_SfhJWi8T-vLuV2WVzKIbkTCw4', + clientSecret: 'ozF8jzI4968oTKFkEnsBC-UbLPCdrSv0MkXGQu2o_-M' +}; + +export class TruthSocialApi { + protected axiosInstance: AxiosInstance; + private rateLimit: RateLimitInfo = { + limit: 300, + remaining: 300, + reset: new Date() + }; + private authToken?: string; + private userEngagementCache: Map; + }> = new Map(); + private readonly ENGAGEMENT_THRESHOLD = 3; + private readonly ENGAGEMENT_WINDOW = 7 * 24 * 60 * 60 * 1000; // 7 days + + constructor(private config: TruthApiConfig = {}) { + this.axiosInstance = axios.create({ + baseURL: config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl, + headers: { + 'User-Agent': config.userAgent || DEFAULT_CONFIG.userAgent + } + }); + } + + private setAuthHeader() { + if (this.authToken) { + this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${this.authToken}`; + } + } + + protected async checkRateLimit(response: AxiosResponse): Promise { + const limit = response.headers['x-ratelimit-limit']; + const remaining = response.headers['x-ratelimit-remaining']; + const reset = response.headers['x-ratelimit-reset']; + + if (limit) this.rateLimit.limit = parseInt(limit); + if (remaining) this.rateLimit.remaining = parseInt(remaining); + if (reset) this.rateLimit.reset = parseISO(reset); + + if (this.rateLimit.remaining <= 50) { + const now = new Date(); + const sleepTime = Math.max(0, (this.rateLimit.reset.getTime() - now.getTime()) / 1000); + + elizaLogger.warn(`Rate limit approaching, sleeping for ${sleepTime} seconds`); + + if (sleepTime > 0) { + await new Promise(resolve => setTimeout(resolve, sleepTime * 1000)); + } else { + await new Promise(resolve => setTimeout(resolve, 10000)); + } + } + } + + protected async ensureAuth(): Promise { + if (!this.authToken) { + if (!this.config.username || !this.config.password) { + throw new Error('Authentication required. Provide username/password or token.'); + } + await this.authenticate(this.config.username, this.config.password); + } + } + + async authenticate(username: string, password: string): Promise { + const payload = { + client_id: DEFAULT_CONFIG.clientId, + client_secret: DEFAULT_CONFIG.clientSecret, + grant_type: 'password', + username, + password, + redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', + scope: 'read write follow' + }; + + try { + const response = await axios.post( + `${DEFAULT_CONFIG.baseUrl}/oauth/token`, + payload, + { + headers: { + 'User-Agent': this.config.userAgent || DEFAULT_CONFIG.userAgent + } + } + ); + + this.authToken = response.data.access_token; + this.setAuthHeader(); + return this.authToken; + } catch (error) { + elizaLogger.error('Authentication failed:', error); + throw new Error('Authentication failed'); + } + } + + // Enhanced Post Management + async createStatus(options: CreateStatusOptions): Promise { + await this.ensureAuth(); + + const response = await this.axiosInstance.post( + '/v1/statuses', + options + ); + await this.checkRateLimit(response); + + if (options.in_reply_to_id) { + const originalPost = await this.getStatus(options.in_reply_to_id); + if (originalPost) { + await this.trackUserEngagement(originalPost.account.username, 'reply', 2); + } + } + + return response.data; + } + + async getStatus(statusId: string): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.get(`/v1/statuses/${statusId}`); + await this.checkRateLimit(response); + return response.data; + } + + async likeStatus(statusId: string): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.post(`/v1/statuses/${statusId}/favourite`); + await this.checkRateLimit(response); + + const post = await this.getStatus(statusId); + if (post) { + await this.trackUserEngagement(post.account.username, 'like', 1); + } + } + + async repostStatus(statusId: string): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.post(`/v1/statuses/${statusId}/reblog`); + await this.checkRateLimit(response); + + const post = await this.getStatus(statusId); + if (post) { + await this.trackUserEngagement(post.account.username, 'repost', 2); + } + } + + // User Engagement and Following Management + private async trackUserEngagement( + username: string, + interactionType: string, + points: number = 1 + ): Promise { + const engagement = this.userEngagementCache.get(username) || { + interactions: 0, + lastInteraction: new Date(), + interactionTypes: new Set() + }; + + if (Date.now() - engagement.lastInteraction.getTime() > this.ENGAGEMENT_WINDOW) { + engagement.interactions = 0; + engagement.interactionTypes.clear(); + } + + engagement.interactions += points; + engagement.lastInteraction = new Date(); + engagement.interactionTypes.add(interactionType); + this.userEngagementCache.set(username, engagement); + + // Check if we should follow based on diverse engagement + if (engagement.interactions >= this.ENGAGEMENT_THRESHOLD && + engagement.interactionTypes.size >= 2) { + try { + const profile = await this.lookupUser(username); + const alreadyFollowing = await this.checkIfFollowing(profile.id); + + if (!alreadyFollowing) { + await this.followUser(profile.id); + elizaLogger.log(`Following ${username} after ${engagement.interactions} points across ${engagement.interactionTypes.size} types`); + } + } catch (error) { + elizaLogger.error(`Error following ${username}:`, error); + } + } + } + + async followUser(userId: string): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.post(`/v1/accounts/${userId}/follow`); + await this.checkRateLimit(response); + } + + async unfollowUser(userId: string): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.post(`/v1/accounts/${userId}/unfollow`); + await this.checkRateLimit(response); + } + + async checkIfFollowing(userId: string): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.get(`/v1/accounts/relationships`, { + params: { id: userId } + }); + await this.checkRateLimit(response); + return response.data[0]?.following || false; + } + + async lookupUser(username: string): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.get('/v1/accounts/lookup', { + params: { acct: username } + }); + await this.checkRateLimit(response); + return response.data; + } + + // Search and Timeline Methods + async *search(options: { + type: 'accounts' | 'statuses' | 'hashtags'; + query: string; + limit?: number; + resolve?: number; + offset?: number; + minId?: string; + maxId?: string; + }): AsyncGenerator { + await this.ensureAuth(); + + const { type, query, limit = 40, resolve = 4, offset = 0, minId = '0', maxId } = options; + let currentOffset = offset; + let count = 0; + + while (count < limit) { + const params: Record = { + q: query, + resolve, + limit, + type, + offset: currentOffset, + min_id: minId, + ...(maxId ? { max_id: maxId } : {}) + }; + + const response = await this.axiosInstance.get('/v2/search', { params }); + await this.checkRateLimit(response); + + if (!response.data || Object.values(response.data).every((arr: any[]) => arr.length === 0)) { + break; + } + + yield response.data; + currentOffset += 40; + count += 40; + + await new Promise(resolve => setTimeout(resolve, 1000)); // Be nice to the API + } + } + + async *getUserStatuses(options: { + username: string; + excludeReplies?: boolean; + pinned?: boolean; + createdAfter?: Date; + sinceId?: string; + limit?: number; + }): AsyncGenerator { + await this.ensureAuth(); + + const { username, excludeReplies = true, pinned = false, limit = Infinity } = options; + const userId = (await this.lookupUser(username)).id; + let maxId: string | undefined; + let count = 0; + + while (count < limit) { + const params: Record = { + limit: Math.min(40, limit - count), + ...(maxId ? { max_id: maxId } : {}) + }; + + if (pinned) { + params.pinned = true; + params.with_muted = true; + } else if (excludeReplies) { + params.exclude_replies = true; + } + + const response = await this.axiosInstance.get( + `/v1/accounts/${userId}/statuses`, + { params } + ); + await this.checkRateLimit(response); + + const statuses = response.data; + if (!statuses || statuses.length === 0) break; + + maxId = statuses[statuses.length - 1].id; + + for (const status of statuses) { + if (this.shouldYieldStatus(status, options)) { + yield status; + count++; + if (count >= limit) break; + } + } + + if (count >= limit || !maxId) break; + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + private shouldYieldStatus(status: TruthStatus, options: { + createdAfter?: Date; + sinceId?: string; + }): boolean { + const { createdAfter, sinceId } = options; + const createdAt = parseISO(status.created_at); + + if (createdAfter && !isAfter(createdAt, createdAfter)) return false; + if (sinceId && status.id <= sinceId) return false; + + return true; + } + + async trending(limit: number = 10): Promise { + await this.ensureAuth(); + const response = await this.axiosInstance.get(`/v1/truth/trending/truths`, { + params: { limit: Math.min(limit, 20) } + }); + await this.checkRateLimit(response); + return response.data; + } +} \ No newline at end of file diff --git a/packages/client-truth-social/src/base.ts b/packages/client-truth-social/src/base.ts new file mode 100644 index 00000000000..7e9bde317a8 --- /dev/null +++ b/packages/client-truth-social/src/base.ts @@ -0,0 +1,150 @@ +import { TruthSocialApi } from './api'; +import { TruthApiConfig, TruthStatus } from './types'; +import { IAgentRuntime, elizaLogger } from '@elizaos/core'; +import { RequestQueue } from './utils'; + +export class ClientBase { + public truthApi: TruthSocialApi; + public profile: any; + public requestQueue: RequestQueue; + public lastCheckedPostId: BigInt = BigInt(0); + public runtime: IAgentRuntime; + + constructor(runtime: IAgentRuntime) { + this.runtime = runtime; + this.requestQueue = new RequestQueue(); + } + + async init() { + const config: TruthApiConfig = { + username: this.runtime.getSetting("TRUTHSOCIAL_USERNAME"), + password: this.runtime.getSetting("TRUTHSOCIAL_PASSWORD") + }; + + this.truthApi = new TruthSocialApi(config); + + try { + // If we have a token, verify it works by looking up our own profile + if (config.username && config.password) { + // Otherwise authenticate with username/password + await this.truthApi.authenticate(config.username!, config.password!); + this.profile = await this.truthApi.lookupUser(config.username!); + } + + elizaLogger.log("Successfully initialized Truth Social client"); + + // Load last checked post ID from cache + const lastChecked = await this.runtime.cacheManager.get<{id: string}>( + "truth_social/" + config.username + "/last_checked_post" + ); + if (lastChecked?.id) { + this.lastCheckedPostId = BigInt(lastChecked.id); + } + + } catch (error) { + elizaLogger.error("Failed to initialize Truth Social client:", error); + throw error; + } + } + + async fetchSearchPosts(query: string, limit: number = 20): Promise { + const posts: TruthStatus[] = []; + + try { + const searchResults = this.truthApi.search({ + type: 'statuses', + query, + limit + }); + + for await (const result of searchResults) { + posts.push(...result.statuses); + if (posts.length >= limit) break; + } + } catch (error) { + elizaLogger.error("Error fetching search posts:", error); + } + + return posts.slice(0, limit); + } + + async fetchHomeTimeline(limit: number = 50): Promise { + const username = this.runtime.getSetting("TRUTHSOCIAL_USERNAME"); + const posts: TruthStatus[] = []; + + try { + const timeline = this.truthApi.getUserStatuses({ + username, + limit + }); + + for await (const post of timeline) { + posts.push(post); + if (posts.length >= limit) break; + } + } catch (error) { + elizaLogger.error("Error fetching home timeline:", error); + } + + return posts; + } + + async fetchTimelineForActions(limit: number = 15): Promise { + const posts: TruthStatus[] = []; + + try { + // Get trending posts + const trending = await this.truthApi.trending(Math.floor(limit / 3)); + posts.push(...trending); + + // Get latest posts from followed users + const home = await this.fetchHomeTimeline(Math.floor(limit / 3)); + posts.push(...home); + + // Get posts from followed hashtags/topics + for (const topic of this.runtime.character.topics) { + const topicPosts = await this.fetchSearchPosts(topic, Math.floor(limit / 3)); + posts.push(...topicPosts); + } + } catch (error) { + elizaLogger.error("Error fetching timeline for actions:", error); + } + + // Deduplicate by ID and sort by timestamp + const uniquePosts = Array.from( + new Map(posts.map(post => [post.id, post])).values() + ).sort((a, b) => + new Date(b.created_at).getTime() - new Date(a.created_at).getTime() + ); + + return uniquePosts.slice(0, limit); + } + + async saveRequestMessage(message: any, state: any) { + await this.runtime.messageManager.createMemory(message, false); + await this.runtime.updateRecentMessageState(state); + } + + async cachePost(post: TruthStatus) { + await this.runtime.cacheManager.set( + `truth_social/${this.profile.username}/post/${post.id}`, + post + ); + } + + async cacheLatestCheckedPostId() { + if (this.lastCheckedPostId) { + await this.runtime.cacheManager.set( + `truth_social/${this.profile.username}/last_checked_post`, + { id: this.lastCheckedPostId.toString() } + ); + } + } + + async cacheTimeline(timeline: TruthStatus[]) { + await this.runtime.cacheManager.set( + `truth_social/${this.profile.username}/timeline`, + timeline + ); + } +} \ No newline at end of file diff --git a/packages/client-truth-social/src/environment.ts b/packages/client-truth-social/src/environment.ts new file mode 100644 index 00000000000..ff0afc96ee3 --- /dev/null +++ b/packages/client-truth-social/src/environment.ts @@ -0,0 +1,61 @@ +import { IAgentRuntime } from "@elizaos/core"; +import { z } from "zod"; +import type { ProcessEnv } from 'node'; +declare const process: { env: ProcessEnv }; + +export const DEFAULT_MAX_TRUTH_LENGTH = 280; + +export const truthEnvSchema = z.object({ + TRUTHSOCIAL_DRY_RUN: z + .string() + .transform((val) => val.toLowerCase() === "true"), + TRUTHSOCIAL_USERNAME: z.string().min(1, "Truth Social username is required"), + TRUTHSOCIAL_PASSWORD: z.string().min(1, "Truth Social password is required"), + MAX_TRUTH_LENGTH: z + .string() + .pipe(z.coerce.number().min(0).int()) + .default(DEFAULT_MAX_TRUTH_LENGTH.toString()), + POST_INTERVAL_MIN: z + .string() + .pipe(z.coerce.number().min(0).int()) + .default("90"), + POST_INTERVAL_MAX: z + .string() + .pipe(z.coerce.number().min(0).int()) + .default("180"), + ACTION_INTERVAL: z + .string() + .pipe(z.coerce.number().min(0).int()) + .default("300000"), // 5 minutes + ENABLE_ACTION_PROCESSING: z + .string() + .transform((val) => val.toLowerCase() === "true") + .default("true"), +}); + +export type TruthConfig = z.infer; + +export async function validateTruthConfig(runtime: IAgentRuntime): Promise { + try { + const config = { + TRUTHSOCIAL_DRY_RUN: runtime.getSetting("TRUTHSOCIAL_DRY_RUN") || process.env.TRUTHSOCIAL_DRY_RUN || "false", + TRUTHSOCIAL_USERNAME: runtime.getSetting("TRUTHSOCIAL_USERNAME") || process.env.TRUTHSOCIAL_USERNAME, + TRUTHSOCIAL_PASSWORD: runtime.getSetting("TRUTHSOCIAL_PASSWORD") || process.env.TRUTHSOCIAL_PASSWORD, + MAX_TRUTH_LENGTH: runtime.getSetting("MAX_TRUTH_LENGTH") || process.env.MAX_TRUTH_LENGTH || DEFAULT_MAX_TRUTH_LENGTH.toString(), + POST_INTERVAL_MIN: runtime.getSetting("POST_INTERVAL_MIN") || process.env.POST_INTERVAL_MIN || "90", + POST_INTERVAL_MAX: runtime.getSetting("POST_INTERVAL_MAX") || process.env.POST_INTERVAL_MAX || "180", + ACTION_INTERVAL: runtime.getSetting("ACTION_INTERVAL") || process.env.ACTION_INTERVAL || "300000", + ENABLE_ACTION_PROCESSING: runtime.getSetting("ENABLE_ACTION_PROCESSING") || process.env.ENABLE_ACTION_PROCESSING || "true", + }; + + return truthEnvSchema.parse(config); + } catch (error) { + if (error instanceof z.ZodError) { + const errorMessages = error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join("\n"); + throw new Error(`Truth Social configuration validation failed:\n${errorMessages}`); + } + throw error; + } +} \ No newline at end of file diff --git a/packages/client-truth-social/src/index.ts b/packages/client-truth-social/src/index.ts new file mode 100644 index 00000000000..70edeecfc25 --- /dev/null +++ b/packages/client-truth-social/src/index.ts @@ -0,0 +1,59 @@ +import { PostClient } from './post'; +import { TruthSearchClient } from './search'; +import { TruthInteractionClient } from './interactions'; +import { IAgentRuntime, Client, elizaLogger } from '@elizaos/core'; +import { validateTruthConfig } from './environment'; +import { ClientBase } from './base'; + +class TruthManager { + client: ClientBase; + post: PostClient; + search: TruthSearchClient; + interaction: TruthInteractionClient; + + constructor(runtime: IAgentRuntime, enableSearch: boolean) { + this.client = new ClientBase(runtime); + this.post = new PostClient(runtime); + + if (enableSearch) { + elizaLogger.warn('Truth Social client running in a mode that:'); + elizaLogger.warn('1. violates consent of random users'); + elizaLogger.warn('2. burns your rate limit'); + elizaLogger.warn('3. can get your account banned'); + elizaLogger.warn('use at your own risk'); + this.search = new TruthSearchClient(this.client, runtime); + } + this.interaction = new TruthInteractionClient(this.client, runtime); + } + + async start(postImmediately: boolean = false) { + await this.client.init(); + await this.post.start(postImmediately); + await this.interaction.start(); + } + + async stop() { + await this.post.stop(); + // Add other stop logic as needed + } +} + +export const TruthClientInterface: Client = { + async start(runtime: IAgentRuntime) { + await validateTruthConfig(runtime); + + elizaLogger.log("Truth Social client started"); + + const manager = new TruthManager(runtime, this.enableSearch); + await manager.start(); + + return manager; + }, + + async stop(runtime: IAgentRuntime) { + elizaLogger.log("Stopping Truth Social client"); + // Implement stop logic + } +}; + +export default TruthClientInterface; \ No newline at end of file diff --git a/packages/client-truth-social/src/interactions.ts b/packages/client-truth-social/src/interactions.ts new file mode 100644 index 00000000000..e008327e1c8 --- /dev/null +++ b/packages/client-truth-social/src/interactions.ts @@ -0,0 +1,355 @@ +import { + composeContext, + generateMessageResponse, + generateShouldRespond, + messageCompletionFooter, + shouldRespondFooter, + Content, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + stringToUuid, + elizaLogger, + getEmbeddingZeroVector, +} from "@elizaos/core"; +import { ClientBase } from "./base"; +import { buildConversationThread, sendTruth, wait } from "./utils"; +import { TruthStatus } from "./types"; + +export const truthShouldRespondTemplate = (targetUsersStr: string) => + `# INSTRUCTIONS: Determine if {{agentName}} (@{{truthUserName}}) should respond to the message and participate in the conversation. + +Response options are RESPOND, IGNORE and STOP. + +PRIORITY RULE: ALWAYS RESPOND to these users regardless of topic or message content: ${targetUsersStr}. Topic relevance should be ignored for these users. + +For other users: +- {{agentName}} should RESPOND to messages directed at them +- {{agentName}} should RESPOND to conversations relevant to their background +- {{agentName}} should IGNORE irrelevant messages +- {{agentName}} should IGNORE very short messages unless directly addressed +- {{agentName}} should STOP if asked to stop +- {{agentName}} should STOP if conversation is concluded +- {{agentName}} is in a room with other users and wants to be conversational, but not annoying. + +IMPORTANT: For users not in the priority list, {{agentName}} (@{{truthUserName}}) should err on the side of IGNORE rather than RESPOND if in doubt. + +{{currentPost}} + +Thread of Posts 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. +` + shouldRespondFooter; + +export const truthMessageHandlerTemplate = ` +# Areas of Expertise +{{knowledge}} + +# About {{agentName}} (@{{truthUserName}}): +{{bio}} +{{lore}} +{{topics}} + +{{providers}} + +{{characterPostExamples}} + +{{postDirections}} + +Recent interactions between {{agentName}} and other users: +{{recentPostInteractions}} + +{{recentPosts}} + +# Task: Generate a post/reply in the voice, style and perspective of {{agentName}} (@{{truthUserName}}) while using the thread of posts as additional context: +Current Post: +{{currentPost}} + +Thread of Posts You Are Replying To: +{{formattedConversation}} + +Remember: Your response MUST be under 280 characters. Keep it concise and direct. +` + messageCompletionFooter; + +export class TruthInteractionClient { + client: ClientBase; + runtime: IAgentRuntime; + private conversationDepths: Map = new Map(); + private maxConversationDepth: number = 5; // Maximum replies in a thread + private conversationTimeouts: Map = new Map(); + private conversationTimeout: number = 30 * 60 * 1000; // 30 minutes timeout + private recentlyProcessedPosts: Set = new Set(); + private maxRecentPosts: number = 1000; + + constructor(client: ClientBase, runtime: IAgentRuntime) { + this.client = client; + this.runtime = runtime; + + // Allow configuration of limits via runtime settings + const maxDepth = this.runtime.getSetting("MAX_CONVERSATION_DEPTH"); + if (maxDepth) this.maxConversationDepth = parseInt(maxDepth); + + const timeout = this.runtime.getSetting("CONVERSATION_TIMEOUT"); + if (timeout) this.conversationTimeout = parseInt(timeout) * 60 * 1000; + } + + private shouldContinueConversation(threadId: string): boolean { + const currentDepth = this.conversationDepths.get(threadId) || 0; + const lastInteraction = this.conversationTimeouts.get(threadId) || 0; + const timeSinceLastInteraction = Date.now() - lastInteraction; + + // Check if conversation has timed out + if (timeSinceLastInteraction > this.conversationTimeout) { + elizaLogger.log(`Conversation ${threadId} timed out after ${timeSinceLastInteraction/1000/60} minutes`); + this.conversationDepths.delete(threadId); + this.conversationTimeouts.delete(threadId); + return true; // Allow new conversation to start + } + + // Check if max depth reached + if (currentDepth >= this.maxConversationDepth) { + elizaLogger.log(`Max conversation depth (${this.maxConversationDepth}) reached for thread ${threadId}`); + return false; + } + + return true; + } + + private updateConversationTracking(threadId: string) { + const currentDepth = this.conversationDepths.get(threadId) || 0; + this.conversationDepths.set(threadId, currentDepth + 1); + this.conversationTimeouts.set(threadId, Date.now()); + } + + async start() { + // Load recent post cache + try { + const cached = await this.runtime.cacheManager.get( + `truth_social/${this.runtime.getSetting("TRUTHSOCIAL_USERNAME")}/recent_posts` + ); + if (cached) { + this.recentlyProcessedPosts = new Set(cached); + } + } catch (error) { + elizaLogger.error("Error loading recent posts cache:", error); + } + + const handleTruthInteractionsLoop = () => { + this.handleTruthInteractions(); + setTimeout( + handleTruthInteractionsLoop, + Number(this.runtime.getSetting("TRUTH_POLL_INTERVAL") || 120) * 1000 + ); + }; + handleTruthInteractionsLoop(); + } + + private shouldProcessPost(post: TruthStatus): boolean { + // Skip if already processed + if (this.recentlyProcessedPosts.has(post.id)) { + return false; + } + + // Skip if from self + if (post.account.username === this.runtime.getSetting("TRUTHSOCIAL_USERNAME")) { + elizaLogger.log(`Skipping own post: ${post.id}`); + return false; + } + + // Check recency (within last 2 hours) + const postTime = new Date(post.created_at).getTime(); + if (Date.now() - postTime > 2 * 60 * 60 * 1000) { + return false; + } + + return true; + } + + private async handlePost(post: TruthStatus): Promise { + // Double-check to ensure we don't process our own posts + if (post.account.username === this.runtime.getSetting("TRUTHSOCIAL_USERNAME")) { + elizaLogger.log(`Skipping own post in handlePost: ${post.id}`); + return; + } + + const threadId = post.in_reply_to_id || post.id; + + // Check conversation limits + if (!this.shouldContinueConversation(threadId)) { + elizaLogger.log(`Skipping post ${post.id} due to conversation limits`); + return; + } + + try { + // Build thread context + const thread = await buildConversationThread(post, this.client); + + // Get target users for response template + const targetUsersStr = this.runtime.getSetting("TRUTH_TARGET_USERS") || ''; + + // Format post and conversation for context + const currentPost = `From @${post.account.username}: ${post.content}`; + const formattedConversation = thread + .map(p => `@${p.account.username}: ${p.content}`) + .join("\n\n"); + + // Determine if we should respond + const shouldRespondContext = composeContext({ + state: await this.runtime.composeState({}, { + truthUserName: this.runtime.getSetting("TRUTHSOCIAL_USERNAME"), + currentPost, + formattedConversation + }), + template: truthShouldRespondTemplate(targetUsersStr) + }); + + const shouldRespond = await generateShouldRespond({ + runtime: this.runtime, + context: shouldRespondContext, + modelClass: ModelClass.SMALL + }); + + if (shouldRespond !== "RESPOND") { + elizaLogger.log(`Not responding to post ${post.id}: ${shouldRespond}`); + return; + } + + // Update tracking before generating response + this.updateConversationTracking(threadId); + + // Generate response + const responseContext = composeContext({ + state: await this.runtime.composeState({}, { + truthUserName: this.runtime.getSetting("TRUTHSOCIAL_USERNAME"), + currentPost, + formattedConversation + }), + template: truthMessageHandlerTemplate + }); + + const response = await generateMessageResponse({ + runtime: this.runtime, + context: responseContext, + modelClass: ModelClass.MEDIUM + }); + + if (!response.text) { + elizaLogger.log('No response text generated'); + return; + } + + // Send the response + const result = await this.client.truthApi.createStatus({ + content: response.text, + in_reply_to_id: post.id, + visibility: 'public' + }); + + // Save to memory + const roomId = stringToUuid(threadId + "-" + this.runtime.agentId); + const memory: Memory = { + uuid: stringToUuid(result.id + "-" + this.runtime.agentId), + content: new Content(result.content), + embedding: await this.runtime.getEmbedding(result.content), + metadata: { + source: "truth_social", + url: result.url, + action: response.action, + inReplyTo: post.id, + userId: this.runtime.agentId, + agentId: this.runtime.agentId, + roomId, + createdAt: new Date(result.created_at).getTime() + } + }; + await this.runtime.memoryManager.saveMemory(memory); + + // Update recent posts tracking + this.recentlyProcessedPosts.add(post.id); + if (this.recentlyProcessedPosts.size > this.maxRecentPosts) { + const oldestPost = Array.from(this.recentlyProcessedPosts)[0]; + this.recentlyProcessedPosts.delete(oldestPost); + } + + // Cache response context + await this.runtime.cacheManager.set( + `truth_social/response_${result.id}.txt`, + `Context:\n${responseContext}\n\nResponse:\n${response.text}` + ); + + await wait(); + + } catch (error) { + elizaLogger.error(`Error handling post ${post.id}:`, error); + } + } + + async handleTruthInteractions() { + elizaLogger.log("Checking Truth Social interactions"); + + try { + const username = this.runtime.getSetting("TRUTHSOCIAL_USERNAME"); + const targetUsers = (this.runtime.getSetting("TRUTH_TARGET_USERS") || '') + .split(',') + .map(u => u.trim()) + .filter(u => u.length > 0); + + // Process mentions + const searchResults = this.client.truthApi.search({ + type: 'statuses', + query: `@${username}`, + limit: 20 + }); + + for await (const result of searchResults) { + for (const post of result.statuses) { + if (this.shouldProcessPost(post)) { + await this.handlePost(post); + } + } + } + + // Process target user posts + for (const targetUser of targetUsers) { + try { + const userPosts = this.client.truthApi.getUserStatuses({ + username: targetUser, + limit: 3, + excludeReplies: true + }); + + for await (const post of userPosts) { + if (this.shouldProcessPost(post)) { + await this.handlePost(post); + } + } + } catch (error) { + elizaLogger.error(`Error processing posts for ${targetUser}:`, error); + } + } + + // Cache processed posts + await this.runtime.cacheManager.set( + `truth_social/${username}/recent_posts`, + Array.from(this.recentlyProcessedPosts) + ); + + } catch (error) { + elizaLogger.error("Error in interaction handling:", error); + } + } + + private async respondToPost(post: TruthStatus, response: Content): Promise { + const roomId = stringToUuid(post.id + "-" + this.runtime.agentId); + await sendTruth( + this.client, + response, + roomId, + post.id + ); + } +} \ No newline at end of file diff --git a/packages/client-truth-social/src/post.ts b/packages/client-truth-social/src/post.ts new file mode 100644 index 00000000000..1082aa25c87 --- /dev/null +++ b/packages/client-truth-social/src/post.ts @@ -0,0 +1,381 @@ +import { TruthSocialApi } from './api'; +import { TruthStatus, CreateStatusOptions } from './types'; +import { + elizaLogger, + composeContext, + generateText, + ModelClass, + IAgentRuntime, + stringToUuid, + Content, + Memory, + postActionResponseFooter, + truncateToCompleteSentence +} from "@elizaos/core"; + +const truthActionTemplate = ` +# INSTRUCTIONS: Determine actions for {{agentName}} (@{{truthUserName}}) based on: +{{bio}} +{{postDirections}} + +Guidelines: +- Highly selective engagement +- Direct mentions are priority +- Skip: low-effort content, off-topic, repetitive + +Actions (respond only with tags): +[LIKE] - Resonates with interests (9.5/10) +[REPOST] - Perfect character alignment (9/10) +[QUOTE] - Can add unique value (8/10) +[REPLY] - Memetic opportunity (9/10) + +Post: +{{currentPost}} + +# Respond with qualifying action tags only.` + + postActionResponseFooter; + +const truthPostTemplate = ` +# Areas of Expertise +{{knowledge}} + +# About {{agentName}} (@{{truthUserName}}): +{{bio}} +{{lore}} +{{topics}} + +{{providers}} + +{{characterPostExamples}} + +{{postDirections}} + +# Task: Generate a post in the voice and style of {{agentName}} +Write a 1-3 sentence post that is {{adjective}} about {{topic}} (without mentioning {{topic}} directly). +Your response should be clear, direct statements. No questions. No emojis. +The total character count MUST be less than 280. +Use "\\n\\n" for paragraph breaks.`; + +export class PostClient extends TruthSocialApi { + private runtime: IAgentRuntime; + private readonly actionTemplate = truthActionTemplate; + + constructor(runtime: IAgentRuntime) { + super({ + username: runtime.getSetting("TRUTHSOCIAL_USERNAME"), + password: runtime.getSetting("TRUTHSOCIAL_PASSWORD") + }); + this.runtime = runtime; + } + + async createPost(content: string, options: Partial = {}): Promise { + return this.createStatus({ + content, + visibility: 'public', + ...options + }); + } + + async replyToPost(content: string, replyToId: string, options: Partial = {}): Promise { + return this.createStatus({ + content, + visibility: 'public', + in_reply_to_id: replyToId, + ...options + }); + } + async getTrending(limit: number = 10): Promise { + return this.trending(limit); + } + + async *getComments( + postId: string, + includeAll: boolean = false, + onlyFirst: boolean = false, + limit: number = 40 + ): AsyncGenerator { + const response = await this.axiosInstance.get(`/v1/statuses/${postId}/context`); + await this.checkRateLimit(response); + + for (const status of response.data) { + yield status; + if (onlyFirst) break; + } + } + + async *getUserStatuses( + options: { + username: string; + excludeReplies?: boolean; + pinned?: boolean; + createdAfter?: Date; + sinceId?: string; + limit?: number; + } + ): AsyncGenerator { + yield* super.getUserStatuses(options); + } + + private isProcessing: boolean = false; + private lastProcessTime: number = 0; + private stopProcessingActions: boolean = false; + + async start(postImmediately: boolean = false) { + if (postImmediately) { + await this.generateNewPost(); + } + + // Start periodic posting + this.startPostingLoop(); + } + + private async startPostingLoop() { + while (!this.stopProcessingActions) { + try { + const lastPost = await this.runtime.cacheManager.get<{ + timestamp: number; + }>("truth_social/" + this.runtime.getSetting("TRUTHSOCIAL_USERNAME") + "/lastPost"); + + const lastPostTimestamp = lastPost?.timestamp ?? 0; + const minMinutes = parseInt(this.runtime.getSetting("POST_INTERVAL_MIN")) || 90; + const maxMinutes = parseInt(this.runtime.getSetting("POST_INTERVAL_MAX")) || 180; + const randomMinutes = Math.floor(Math.random() * (maxMinutes - minMinutes + 1)) + minMinutes; + const delay = randomMinutes * 60 * 1000; + + if (Date.now() > lastPostTimestamp + delay) { + await this.generateNewPost(); + } + + await new Promise(resolve => setTimeout(resolve, delay)); + elizaLogger.log(`Next post scheduled in ${randomMinutes} minutes`); + } catch (error) { + elizaLogger.error("Error in posting loop:", error); + await new Promise(resolve => setTimeout(resolve, 300000)); // 5 minute delay on error + } + } + } + + private async generateNewPost() { + elizaLogger.log("Generating new post"); + + try { + const topics = this.runtime.character.topics.join(", "); + const roomId = stringToUuid("truth_social_generate_room-" + this.runtime.getSetting("TRUTHSOCIAL_USERNAME")); + + // Prepare state for post generation + const state = await this.runtime.composeState( + { + userId: this.runtime.agentId, + roomId, + agentId: this.runtime.agentId, + content: { text: topics, action: "POST" } + }, + { + truthUserName: this.runtime.getSetting("TRUTHSOCIAL_USERNAME") + } + ); + + // Generate post content + const context = composeContext({ + state, + template: truthPostTemplate + }); + + const postContent = await generateText({ + runtime: this.runtime, + context, + modelClass: ModelClass.SMALL + }); + + // Clean and format the content + const cleanedContent = this.cleanPostContent(postContent); + + if (!cleanedContent) { + elizaLogger.error("Failed to generate valid post content"); + return; + } + + if (this.runtime.getSetting("TRUTH_DRY_RUN") === "true") { + elizaLogger.info(`Dry run - would have posted: ${cleanedContent}`); + return; + } + + // Post the content + const post = await this.createPost(cleanedContent); + + // Save to memory and cache + await this.runtime.ensureRoomExists(roomId); + await this.runtime.ensureParticipantInRoom(this.runtime.agentId, roomId); + + await this.runtime.messageManager.createMemory({ + id: stringToUuid(post.id + "-" + this.runtime.agentId), + userId: this.runtime.agentId, + agentId: this.runtime.agentId, + content: { + text: cleanedContent, + url: post.url, + source: "truth_social" + }, + roomId, + createdAt: new Date(post.created_at).getTime() + }); + + await this.runtime.cacheManager.set( + `truth_social/${this.runtime.getSetting("TRUTHSOCIAL_USERNAME")}/lastPost`, + { + id: post.id, + timestamp: Date.now() + } + ); + + elizaLogger.log(`Posted new content: ${post.url}`); + + } catch (error) { + elizaLogger.error("Error generating/posting content:", error); + } + } + + private cleanPostContent(content: string): string { + // Remove markdown and clean whitespace + let cleaned = content.replace(/```.*?```/gs, '').trim(); + + // Try parsing as JSON if it looks like JSON + try { + if (cleaned.startsWith('{') || cleaned.startsWith('"')) { + const parsed = JSON.parse(cleaned); + cleaned = typeof parsed === 'string' ? parsed : parsed.text || parsed.content; + } + } catch (e) { + // Not JSON, use as is + } + + // Clean up quotes and escapes + cleaned = cleaned + .replace(/^['"](.*)['"]$/g, '$1') // Remove surrounding quotes + .replace(/\\"/g, '"') // Unescape quotes + .replace(/\\n/g, '\n') // Convert newline escapes + .trim(); + + // Truncate to character limit if needed + const maxLength = Number(this.runtime.getSetting("MAX_TRUTH_LENGTH")) || 280; + if (cleaned.length > maxLength) { + cleaned = truncateToCompleteSentence(cleaned, maxLength); + } + return cleaned; + } + + async stop() { + this.stopProcessingActions = true; + elizaLogger.log("Stopping post processing"); + } + + // Group-related functionality + async getGroupTrending(limit: number = 10): Promise { + const response = await this.axiosInstance.get(`/v1/truth/trends/groups`, { + params: { limit: Math.min(limit, 20) } + }); + await this.checkRateLimit(response); + return response.data; + } + + async getGroupTags(): Promise { + const response = await this.axiosInstance.get('/v1/groups/tags'); + await this.checkRateLimit(response); + return response.data; + } + + async getSuggestedGroups(limit: number = 50): Promise { + const response = await this.axiosInstance.get('/v1/truth/suggestions/groups', { + params: { limit } + }); + await this.checkRateLimit(response); + return response.data; + } + + async *getGroupPosts(groupId: string, limit: number = 20): AsyncGenerator { + await this.ensureAuth(); + let timeline: TruthStatus[] = []; + let maxId: string | undefined; + + while (timeline.length < limit) { + const params: Record = { limit }; + if (maxId) params.max_id = maxId; + + const response = await this.axiosInstance.get( + `/v1/timelines/group/${groupId}`, + { params } + ); + await this.checkRateLimit(response); + + if (!response.data || response.data.length === 0) break; + + timeline = [...timeline, ...response.data]; + maxId = response.data[response.data.length - 1].id; + + for (const post of response.data) { + yield post; + } + + if (timeline.length >= limit) break; + } + } + + private async storePostMemory(post: TruthStatus, action: string): Promise { + const content: Content = { + text: post.content, + url: post.url, + action, + source: "truth_social" + }; + + const memory: Memory = { + id: stringToUuid(`${post.id}-${action}-${this.runtime.agentId}`), + userId: this.runtime.agentId, + agentId: this.runtime.agentId, + content, + roomId: stringToUuid("truth_social_actions"), + createdAt: new Date(post.created_at).getTime() + }; + + await this.runtime.messageManager.createMemory(memory); + return memory; + } + + async processPostActions(post: TruthStatus) { + const actions = await this.createPost({ + runtime: this.runtime, + context: composeContext({ + state: await this.runtime.composeState(/* ... */), + template: this.actionTemplate + }), + modelClass: ModelClass.SMALL + }); + + // Store the interaction as a memory + await this.storePostMemory(post, actions.join(',')); + return actions; + } + + // Add media handling and content validation + private async handlePostContent(content: string, mediaData?: MediaData[]): Promise { + const cleanedContent = this.cleanPostContent(content); + + try { + if (mediaData && mediaData.length > 0) { + // Handle media attachments + return await this.createPost(cleanedContent, { + media_ids: mediaData.map(m => m.id), + visibility: 'public' + }); + } else { + // Text-only post + return await this.createPost(cleanedContent, { + visibility: 'public' + }); + } + } catch (error) { + elizaLogger.error("Error posting content:", error); + throw error; + } + } +} \ No newline at end of file diff --git a/packages/client-truth-social/src/search.ts b/packages/client-truth-social/src/search.ts new file mode 100644 index 00000000000..0d4434591c9 --- /dev/null +++ b/packages/client-truth-social/src/search.ts @@ -0,0 +1,237 @@ +import { + composeContext, + generateMessageResponse, + generateText, + messageCompletionFooter, + Content, + HandlerCallback, + IAgentRuntime, + IImageDescriptionService, + ModelClass, + ServiceType, + State, + stringToUuid, + elizaLogger +} from "@elizaos/core"; +import { ClientBase } from "./base"; +import { buildConversationThread, sendTruth, wait } from "./utils"; +import { TruthStatus, TruthUserProfile } from "./types"; + +const truthSearchTemplate = ` +{{timeline}} + +{{providers}} + +Recent interactions between {{agentName}} and other users: +{{recentPostInteractions}} + +About {{agentName}} (@{{truthUserName}}): +{{bio}} +{{lore}} +{{topics}} + +{{postDirections}} + +{{recentPosts}} + +# Task: Respond to the following post in the style and perspective of {{agentName}}. Write a {{adjective}} response that is relevant and engaging. +{{currentPost}} + +IMPORTANT: Your response CANNOT be longer than 20 words. +Aim for 1-2 short sentences maximum. Be concise and direct. No emojis. +` + messageCompletionFooter; + +export class TruthSearchClient { + client: ClientBase; + runtime: IAgentRuntime; + private respondedPosts: Set = new Set(); + + constructor(client: ClientBase, runtime: IAgentRuntime) { + this.client = client; + this.runtime = runtime; + } + + async start() { + this.engageWithSearchTermsLoop(); + } + + private engageWithSearchTermsLoop() { + this.engageWithSearchTerms(); + setTimeout( + () => this.engageWithSearchTermsLoop(), + (Math.floor(Math.random() * (120 - 60 + 1)) + 60) * 60 * 1000 + ); + } + + private async engageWithSearchTerms() { + elizaLogger.log("Engaging with search terms"); + try { + // Randomly select a topic from the agent's interests + const searchTerm = [...this.runtime.character.topics][ + Math.floor(Math.random() * this.runtime.character.topics.length) + ]; + + elizaLogger.log("Searching for relevant posts"); + await wait(5000); // Rate limit protection + + // Search for posts using the topic + const searchResults = this.client.truthApi.search({ + type: 'statuses', + query: searchTerm, + limit: 20 + }); + + const posts: TruthStatus[] = []; + for await (const result of searchResults) { + posts.push(...result.statuses); + } + + // Get recent timeline for context + const recentPosts: TruthStatus[] = []; + for await (const status of this.client.truthApi.getUserStatuses({ + username: this.runtime.getSetting("TRUTHSOCIAL_USERNAME"), + limit: 50 + })) { + recentPosts.push(status); + } + + const formattedTimeline = `# ${this.runtime.character.name}'s Recent Posts\n\n` + + recentPosts.map(post => + `ID: ${post.id}\nContent: ${post.content}\n---\n` + ).join("\n"); + + // Randomly select a subset of posts to process + const candidatePosts = posts + .sort(() => Math.random() - 0.5) + .slice(0, 20) + .filter(post => { + // Filter out posts from the agent itself + const isOwnPost = post.account.username === this.runtime.getSetting("TRUTHSOCIAL_USERNAME"); + // Filter out posts we've already responded to + const isProcessed = this.respondedPosts.has(post.id); + return !isOwnPost && !isProcessed; + }); + + if (candidatePosts.length === 0) { + elizaLogger.log("No suitable posts found for interaction"); + return; + } + + // Find the most relevant post to respond to + const selectPostPrompt = ` +Here are some posts related to "${searchTerm}": + +${candidatePosts.map(post => ` +ID: ${post.id} +From: ${post.account.display_name} (@${post.account.username}) +Content: ${post.content} +`).join("\n")} + +Which post is most relevant for ${this.runtime.character.name} to engage with? Consider: +- Relevance to the agent's interests and expertise +- Potential for meaningful interaction +- Post quality and substance +- English language content only +- Avoid posts with excessive hashtags or media +- Avoid posts that contain foul language +- Avoid posts that contain explicit content (NSFW) +Respond with only the ID of the chosen post.`; + + const selectedPostId = (await generateText({ + runtime: this.runtime, + context: selectPostPrompt, + modelClass: ModelClass.SMALL + })).trim(); + + const selectedPost = candidatePosts.find(p => p.id === selectedPostId); + if (!selectedPost) { + elizaLogger.log("No matching post found"); + return; + } + + elizaLogger.log("Selected post for interaction:", selectedPost.content); + + // Process the selected post + const roomId = stringToUuid(selectedPost.id + "-" + this.runtime.agentId); + const userId = stringToUuid(selectedPost.id); + + await this.runtime.ensureConnection( + userId, + roomId, + selectedPost.account.username, + selectedPost.account.display_name, + "truth_social" + ); + + // Build conversation context + const thread = await buildConversationThread(selectedPost, this.client); + + // Prepare the message + const message = { + id: stringToUuid(selectedPost.id + "-" + this.runtime.agentId), + content: { text: selectedPost.content }, + agentId: this.runtime.agentId, + userId, + roomId, + createdAt: new Date(selectedPost.created_at).getTime() + }; + + // Generate and send response + const state = await this.runtime.composeState(message, { + truthUserName: this.runtime.getSetting("TRUTHSOCIAL_USERNAME"), + timeline: formattedTimeline, + currentPost: `From @${selectedPost.account.username}: ${selectedPost.content}`, + recentPostInteractions: thread.map(post => + `${post.account.username}: ${post.content}` + ).join('\n') + }); + + const context = composeContext({ + state, + template: truthSearchTemplate + }); + + const response = await generateMessageResponse({ + runtime: this.runtime, + context, + modelClass: ModelClass.SMALL + }); + + if (response.text) { + try { + const callback: HandlerCallback = async (response: Content) => { + return sendTruth( + this.client, + response, + message.roomId, + selectedPost.id + ); + }; + + const responseMessages = await callback(response); + const updatedState = await this.runtime.updateRecentMessageState(state); + + for (const responseMessage of responseMessages) { + await this.runtime.messageManager.createMemory(responseMessage); + } + + await this.runtime.processActions(message, responseMessages, updatedState); + this.respondedPosts.add(selectedPost.id); + + // Cache response info for debugging + await this.runtime.cacheManager.set( + `truth_social/post_generation_${selectedPost.id}.txt`, + `Context:\n${context}\n\nResponse:\n${response.text}` + ); + + await wait(); + } catch (error) { + elizaLogger.error("Error sending response:", error); + } + } + + } catch (error) { + elizaLogger.error("Error in search engagement:", error); + } + } +} \ No newline at end of file diff --git a/packages/client-truth-social/src/types.ts b/packages/client-truth-social/src/types.ts new file mode 100644 index 00000000000..c8632b8072c --- /dev/null +++ b/packages/client-truth-social/src/types.ts @@ -0,0 +1,76 @@ +export interface TruthUserProfile { + id: string; + username: string; + acct: string; + url: string; + display_name: string; + bio: string; + avatar: string; + header: string; + locked: boolean; + created_at: string; + followers_count: number; + following_count: number; + statuses_count: number; + note: string; + fields: Array<{name: string; value: string}>; +} + +export interface TruthStatus { + id: string; + created_at: string; + content: string; + url: string; + account: { + id: string; + username: string; + display_name: string; + }; + in_reply_to_id?: string; +} + +export interface TruthSearchResults { + accounts: TruthUserProfile[]; + statuses: TruthStatus[]; + hashtags: Array<{ + name: string; + url: string; + history: Array<{day: string; uses: string; accounts: string}>; + }>; +} + +export interface TruthApiConfig { + username?: string; + password?: string; + baseUrl?: string; + apiBaseUrl?: string; + userAgent?: string; +} + +export interface TruthApiError { + error: string; + error_description?: string; +} + +export interface RateLimitInfo { + limit: number; + remaining: number; + reset: Date; +} + +export interface TruthAuthResponse { + access_token: string; + token_type: string; + scope: string; + created_at: number; +} + +export interface CreateStatusOptions { + content: string; + visibility?: 'public' | 'unlisted' | 'private' | 'direct'; + in_reply_to_id?: string; + media_ids?: string[]; + sensitive?: boolean; + spoiler_text?: string; + language?: string; +} \ No newline at end of file diff --git a/packages/client-truth-social/src/utils.ts b/packages/client-truth-social/src/utils.ts new file mode 100644 index 00000000000..37919125bf8 --- /dev/null +++ b/packages/client-truth-social/src/utils.ts @@ -0,0 +1,154 @@ +import { Content, Memory, stringToUuid, getEmbeddingZeroVector } from "@elizaos/core"; +import { ClientBase } from "./base"; +import { TruthStatus } from "./types"; + +export const wait = (ms: number = 1000) => new Promise(resolve => setTimeout(resolve, ms)); + +export async function sendTruth( + client: ClientBase, + response: Content, + roomId: string, + replyToId?: string +): Promise { + const memories: Memory[] = []; + const text = response.text?.trim(); + + if (!text) return memories; + + try { + const result = await client.truthApi.createStatus({ + content: text, + in_reply_to_id: replyToId, + visibility: 'public' + }); + + const memory: Memory = { + id: stringToUuid(result.id + "-" + client.runtime.agentId), + userId: client.runtime.agentId, + agentId: client.runtime.agentId, + content: { + text: result.content, + url: result.url, + source: "truth_social", + action: response.action, + inReplyTo: replyToId ? stringToUuid(replyToId + "-" + client.runtime.agentId) : undefined + }, + roomId, + embedding: getEmbeddingZeroVector(), + createdAt: new Date(result.created_at).getTime() + }; + + memories.push(memory); + await client.cachePost(result); + + } catch (error) { + console.error("Error sending truth:", error); + throw error; + } + + return memories; +} + +export async function buildConversationThread( + post: TruthStatus, + client: ClientBase, + maxDepth: number = 10 +): Promise { + const thread: TruthStatus[] = []; + const visited = new Set(); + + async function processThread(currentPost: TruthStatus, depth: number = 0) { + if (!currentPost || depth >= maxDepth || visited.has(currentPost.id)) { + return; + } + + visited.add(currentPost.id); + thread.unshift(currentPost); + + // Save to memory + const memory = await client.runtime.messageManager.getMemoryById( + stringToUuid(currentPost.id + "-" + client.runtime.agentId) + ); + + if (!memory) { + const roomId = stringToUuid(currentPost.id + "-" + client.runtime.agentId); + const userId = stringToUuid(currentPost.id); + + await client.runtime.ensureConnection( + userId, + roomId, + currentPost.account.username, + currentPost.account.display_name, + "truth_social" + ); + + await client.runtime.messageManager.createMemory({ + id: stringToUuid(currentPost.id + "-" + client.runtime.agentId), + agentId: client.runtime.agentId, + content: { + text: currentPost.content, + source: "truth_social", + url: currentPost.url, + inReplyTo: currentPost.in_reply_to_id + ? stringToUuid(currentPost.in_reply_to_id + "-" + client.runtime.agentId) + : undefined + }, + createdAt: new Date(currentPost.created_at).getTime(), + roomId, + userId, + embedding: getEmbeddingZeroVector() + }); + } + + if (currentPost.in_reply_to_id) { + try { + // Fetch the parent post + const comments = client.truthApi.getUserStatuses({ + username: currentPost.account.username, + limit: 1, + sinceId: currentPost.in_reply_to_id + }); + + for await (const comment of comments) { + await processThread(comment, depth + 1); + } + } catch (error) { + console.error("Error fetching parent post:", error); + } + } + } + + await processThread(post); + return thread; +} + +export class RequestQueue { + private queue: (() => Promise)[] = []; + private processing = false; + + async add(task: () => Promise): Promise { + return new Promise((resolve, reject) => { + this.queue.push(async () => { + try { + const result = await task(); + resolve(result); + } catch (error) { + reject(error); + } + }); + this.process(); + }); + } + + private async process() { + if (this.processing || this.queue.length === 0) return; + this.processing = true; + + while (this.queue.length > 0) { + const task = this.queue.shift(); + if (task) await task(); + } + + this.processing = false; + } +} \ No newline at end of file diff --git a/packages/client-truth-social/tsconfig.json b/packages/client-truth-social/tsconfig.json new file mode 100644 index 00000000000..73993deaaf7 --- /dev/null +++ b/packages/client-truth-social/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../core/tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/packages/client-truth-social/tsup.config.ts b/packages/client-truth-social/tsup.config.ts new file mode 100644 index 00000000000..e42bf4efeae --- /dev/null +++ b/packages/client-truth-social/tsup.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], // Ensure you're targeting CommonJS + external: [ + "dotenv", // Externalize dotenv to prevent bundling + "fs", // Externalize fs to use Node.js built-in module + "path", // Externalize other built-ins if necessary + "@reflink/reflink", + "@node-llama-cpp", + "https", + "http", + "agentkeepalive", + // Add other modules you want to externalize + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eab17d7adf6..a0d8b37b535 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1068,13 +1068,13 @@ importers: dependencies: '@discordjs/opus': specifier: github:discordjs/opus - version: https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13) + version: https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13) '@discordjs/rest': specifier: 2.4.0 version: 2.4.0 '@discordjs/voice': specifier: 0.17.0 - version: 0.17.0(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13))(bufferutil@4.0.9)(ffmpeg-static@5.2.0)(utf-8-validate@6.0.5) + version: 0.17.0(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13))(bufferutil@4.0.9)(ffmpeg-static@5.2.0)(utf-8-validate@6.0.5) '@elizaos/core': specifier: workspace:* version: link:../core @@ -1089,7 +1089,7 @@ importers: version: 0.7.15 prism-media: specifier: 1.3.5 - version: 1.3.5(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13))(ffmpeg-static@5.2.0) + version: 1.3.5(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13))(ffmpeg-static@5.2.0) whatwg-url: specifier: 7.1.0 version: 7.1.0 @@ -1335,6 +1335,25 @@ importers: specifier: 1.1.3 version: 1.1.3(@types/node@22.12.0)(jsdom@25.0.1(bufferutil@4.0.9)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@6.0.5))(terser@5.37.0) + packages/client-truth-social: + dependencies: + '@elizaos/core': + specifier: workspace:* + version: link:../core + glob: + specifier: 11.0.0 + version: 11.0.0 + whatwg-url: + specifier: 7.1.0 + version: 7.1.0 + zod: + specifier: 3.23.8 + version: 3.23.8 + devDependencies: + tsup: + specifier: 8.3.5 + version: 8.3.5(@swc/core@1.10.11(@swc/helpers@0.5.15))(jiti@2.4.2)(postcss@8.5.1)(tsx@4.19.2)(typescript@5.7.3)(yaml@2.7.0) + packages/client-twitter: dependencies: '@elizaos/core': @@ -1413,7 +1432,7 @@ importers: version: 10.0.0 ai: specifier: 3.4.33 - version: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + version: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) anthropic-vertex-ai: specifier: 1.0.2 version: 1.0.2(encoding@0.1.13)(zod@3.23.8) @@ -3088,7 +3107,7 @@ importers: dependencies: '@elizaos/adapter-sqlite': specifier: 0.1.7-alpha.2 - version: 0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(whatwg-url@14.1.0)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + version: 0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(whatwg-url@14.1.0)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@elizaos/core': specifier: workspace:* version: link:../core @@ -3939,7 +3958,7 @@ importers: dependencies: '@elizaos/core': specifier: ^0.1.7 - version: 0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@pythnetwork/client': specifier: ^2.22.0 version: 2.22.0(@solana/web3.js@1.95.8(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) @@ -4455,7 +4474,7 @@ importers: dependencies: '@elizaos/core': specifier: ^0.1.0 - version: 0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) devDependencies: '@types/jest': specifier: ^27.0.0 @@ -4704,7 +4723,7 @@ importers: dependencies: '@elizaos/core': specifier: ^0.1.0 - version: 0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) devDependencies: '@types/jest': specifier: ^27.0.0 @@ -6931,8 +6950,8 @@ packages: resolution: {integrity: sha512-YJOVVZ545x24mHzANfYoy0BJX5PDyeZlpiJjDkUBM/V/Ao7TFX9lcUvCN4nr0tbr5ubeaXxtEBILUrHtTphVeQ==} hasBin: true - '@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063': - resolution: {tarball: https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063} + '@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda': + resolution: {tarball: https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda} version: 0.10.0 engines: {node: '>=12.0.0'} @@ -14156,8 +14175,8 @@ packages: resolution: {integrity: sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - '@voltr/vault-sdk@0.1.3': - resolution: {integrity: sha512-NDIBsEyw3LbLUdNSUg1eNwPaWINTcJQeFuZbGnB3seuEKS0moASbxq70NDVnqYL2t1joBSip0lbizKtFBGvs7A==} + '@voltr/vault-sdk@0.1.4': + resolution: {integrity: sha512-QP4GaLmRDAUs1AKt5Vcj++ZXAaSlSwqSnPtGezaZ2JBko/WVBAiRmdMs+L6FgsZq2n1W5jHvT7I94hDtFt1VMw==} '@vue/compiler-core@3.5.13': resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} @@ -14733,8 +14752,8 @@ packages: resolution: {integrity: sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ==} engines: {node: '>= 10'} - algoliasearch-helper@3.23.1: - resolution: {integrity: sha512-j/dF2ZELJBm4SJTK5ECsMuCDJpBB8ITiWKRjd3S15bK2bqrXKLWqDiA5A96WhVvCpZ2NmgNlUYmFbKOfcqivbg==} + algoliasearch-helper@3.24.1: + resolution: {integrity: sha512-knYRACqLH9UpeR+WRUrBzBFR2ulGuOjI2b525k4PNeqZxeFMHJE7YcL7s6Jh12Qza0rtHqZdgHMfeuaaAkf4wA==} peerDependencies: algoliasearch: '>= 3.1 < 6' @@ -21090,8 +21109,8 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - loupe@3.1.2: - resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} lowdb@7.0.1: resolution: {integrity: sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==} @@ -25816,8 +25835,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte@5.19.3: - resolution: {integrity: sha512-rb/bkYG9jq67OCWikMvaPnfOobyGn0JizVDwHpdeBtLiNXPMcoA9GTFC3BhptP7xGNquUU8J5GiS7PlGlfDAFA==} + svelte@5.19.4: + resolution: {integrity: sha512-pzWvFQdvfEfT4Ll/JriAtcG7qmWjcL+x/NSl9Q+FPje5SXukYNp9kcufZ27ydauLLE/dwYMz9XRC8kiwTZmfDA==} engines: {node: '>=18'} svg-parser@2.0.4: @@ -28738,13 +28757,13 @@ snapshots: transitivePeerDependencies: - zod - '@ai-sdk/svelte@0.0.57(svelte@5.19.3)(zod@3.23.8)': + '@ai-sdk/svelte@0.0.57(svelte@5.19.4)(zod@3.23.8)': dependencies: '@ai-sdk/provider-utils': 2.1.2(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) - sswr: 2.1.0(svelte@5.19.3) + sswr: 2.1.0(svelte@5.19.4) optionalDependencies: - svelte: 5.19.3 + svelte: 5.19.4 transitivePeerDependencies: - zod @@ -32343,7 +32362,7 @@ snapshots: - encoding - supports-color - '@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13)': + '@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13)': dependencies: '@discordjs/node-pre-gyp': 0.4.5(encoding@0.1.13) node-addon-api: 8.3.0 @@ -32365,11 +32384,11 @@ snapshots: '@discordjs/util@1.1.1': {} - '@discordjs/voice@0.17.0(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13))(bufferutil@4.0.9)(ffmpeg-static@5.2.0)(utf-8-validate@6.0.5)': + '@discordjs/voice@0.17.0(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13))(bufferutil@4.0.9)(ffmpeg-static@5.2.0)(utf-8-validate@6.0.5)': dependencies: '@types/ws': 8.5.14 discord-api-types: 0.37.83 - prism-media: 1.3.5(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13))(ffmpeg-static@5.2.0) + prism-media: 1.3.5(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13))(ffmpeg-static@5.2.0) tslib: 2.8.1 ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: @@ -33145,7 +33164,7 @@ snapshots: '@docusaurus/utils': 3.7.0(@swc/core@1.10.11(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.11(@swc/helpers@0.5.15))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) algoliasearch: 5.20.0 - algoliasearch-helper: 3.23.1(algoliasearch@5.20.0) + algoliasearch-helper: 3.24.1(algoliasearch@5.20.0) clsx: 2.1.1 eta: 2.2.0 fs-extra: 11.2.0 @@ -33464,9 +33483,9 @@ snapshots: '@electric-sql/pglite@0.2.16': {} - '@elizaos/adapter-sqlite@0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(whatwg-url@14.1.0)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': + '@elizaos/adapter-sqlite@0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(whatwg-url@14.1.0)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: - '@elizaos/core': 0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + '@elizaos/core': 0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@types/better-sqlite3': 7.6.12 better-sqlite3: 11.6.0 sqlite-vec: 0.1.6 @@ -33526,7 +33545,7 @@ snapshots: - vue - ws - '@elizaos/core@0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': + '@elizaos/core@0.1.7-alpha.2(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: '@ai-sdk/anthropic': 0.0.56(zod@3.23.8) '@ai-sdk/google': 0.0.55(zod@3.23.8) @@ -33536,7 +33555,7 @@ snapshots: '@anthropic-ai/sdk': 0.30.1(encoding@0.1.13) '@fal-ai/client': 1.2.0 '@types/uuid': 10.0.0 - ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) anthropic-vertex-ai: 1.0.2(encoding@0.1.13)(zod@3.23.8) fastembed: 1.14.1 fastestsmallesttextencoderdecoder: 1.0.22 @@ -33577,7 +33596,7 @@ snapshots: - vue - ws - '@elizaos/core@0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@elizaos/core@0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.73.0(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@ai-sdk/anthropic': 0.0.56(zod@3.23.8) '@ai-sdk/google': 0.0.55(zod@3.23.8) @@ -33588,7 +33607,7 @@ snapshots: '@anthropic-ai/sdk': 0.30.1(encoding@0.1.13) '@fal-ai/client': 1.2.0 '@types/uuid': 10.0.0 - ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) anthropic-vertex-ai: 1.0.2(encoding@0.1.13)(zod@3.23.8) fastembed: 1.14.1 fastestsmallesttextencoderdecoder: 1.0.22 @@ -33629,7 +33648,7 @@ snapshots: - vue - ws - '@elizaos/core@0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@elizaos/core@0.1.8(@google-cloud/vertexai@1.9.2(encoding@0.1.13))(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(@langchain/groq@0.1.3(@langchain/core@0.3.36(openai@4.80.1(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.24.1)))(encoding@0.1.13)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(axios@1.7.9)(encoding@0.1.13)(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@ai-sdk/anthropic': 0.0.56(zod@3.23.8) '@ai-sdk/google': 0.0.55(zod@3.23.8) @@ -33640,7 +33659,7 @@ snapshots: '@anthropic-ai/sdk': 0.30.1(encoding@0.1.13) '@fal-ai/client': 1.2.0 '@types/uuid': 10.0.0 - ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) anthropic-vertex-ai: 1.0.2(encoding@0.1.13)(zod@3.23.8) fastembed: 1.14.1 fastestsmallesttextencoderdecoder: 1.0.22 @@ -33692,7 +33711,7 @@ snapshots: '@anthropic-ai/sdk': 0.30.1(encoding@0.1.13) '@fal-ai/client': 1.2.0 '@types/uuid': 10.0.0 - ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) anthropic-vertex-ai: 1.0.2(encoding@0.1.13)(zod@3.23.8) fastembed: 1.14.1 fastestsmallesttextencoderdecoder: 1.0.22 @@ -33744,7 +33763,7 @@ snapshots: '@anthropic-ai/sdk': 0.30.1(encoding@0.1.13) '@fal-ai/client': 1.2.0 '@types/uuid': 10.0.0 - ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) + ai: 3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) anthropic-vertex-ai: 1.0.2(encoding@0.1.13)(zod@3.23.8) fastembed: 1.14.1 fastestsmallesttextencoderdecoder: 1.0.22 @@ -47116,30 +47135,30 @@ snapshots: '@vitest/utils@2.1.4': dependencies: '@vitest/pretty-format': 2.1.4 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 1.2.0 '@vitest/utils@2.1.5': dependencies: '@vitest/pretty-format': 2.1.5 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 1.2.0 '@vitest/utils@2.1.8': dependencies: '@vitest/pretty-format': 2.1.8 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 1.2.0 '@vitest/utils@3.0.2': dependencies: '@vitest/pretty-format': 3.0.2 - loupe: 3.1.2 + loupe: 3.1.3 tinyrainbow: 2.0.0 '@vladfrangu/async_event_emitter@2.4.6': {} - '@voltr/vault-sdk@0.1.3(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@voltr/vault-sdk@0.1.4(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: '@coral-xyz/anchor': 0.28.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@solana/spl-token': 0.4.9(@solana/web3.js@1.98.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) @@ -47151,7 +47170,7 @@ snapshots: - typescript - utf-8-validate - '@voltr/vault-sdk@0.1.3(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.3)(utf-8-validate@5.0.10)': + '@voltr/vault-sdk@0.1.4(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.3)(utf-8-validate@5.0.10)': dependencies: '@coral-xyz/anchor': 0.28.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@solana/spl-token': 0.4.9(@solana/web3.js@1.98.0(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.3)(utf-8-validate@5.0.10) @@ -47482,7 +47501,7 @@ snapshots: dependencies: '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/safe-json': 1.0.2 - cross-fetch: 3.2.0(encoding@0.1.13) + cross-fetch: 3.1.8(encoding@0.1.13) events: 3.3.0 transitivePeerDependencies: - encoding @@ -48527,13 +48546,13 @@ snapshots: - typescript - utf-8-validate - ai@3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.3))(svelte@5.19.3)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8): + ai@3.4.33(openai@4.73.0(encoding@0.1.13)(zod@3.23.8))(react@19.0.0)(sswr@2.1.0(svelte@5.19.4))(svelte@5.19.4)(vue@3.5.13(typescript@5.6.3))(zod@3.23.8): dependencies: '@ai-sdk/provider': 1.0.6 '@ai-sdk/provider-utils': 2.1.2(zod@3.23.8) '@ai-sdk/react': 0.0.70(react@19.0.0)(zod@3.23.8) '@ai-sdk/solid': 0.0.54(zod@3.23.8) - '@ai-sdk/svelte': 0.0.57(svelte@5.19.3)(zod@3.23.8) + '@ai-sdk/svelte': 0.0.57(svelte@5.19.4)(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.50(zod@3.23.8) '@ai-sdk/vue': 0.0.59(vue@3.5.13(typescript@5.6.3))(zod@3.23.8) '@opentelemetry/api': 1.9.0 @@ -48545,8 +48564,8 @@ snapshots: optionalDependencies: openai: 4.73.0(encoding@0.1.13)(zod@3.23.8) react: 19.0.0 - sswr: 2.1.0(svelte@5.19.3) - svelte: 5.19.3 + sswr: 2.1.0(svelte@5.19.4) + svelte: 5.19.4 zod: 3.23.8 transitivePeerDependencies: - solid-js @@ -48611,7 +48630,7 @@ snapshots: algo-msgpack-with-bigint@2.1.1: {} - algoliasearch-helper@3.23.1(algoliasearch@5.20.0): + algoliasearch-helper@3.24.1(algoliasearch@5.20.0): dependencies: '@algolia/events': 4.0.1 algoliasearch: 5.20.0 @@ -50183,7 +50202,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.2 + loupe: 3.1.3 pathval: 2.0.0 chain-registry@1.69.107: @@ -53373,7 +53392,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.3.4 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -57714,7 +57733,7 @@ snapshots: dependencies: get-func-name: 2.0.2 - loupe@3.1.2: {} + loupe@3.1.3: {} lowdb@7.0.1: dependencies: @@ -61298,9 +61317,9 @@ snapshots: extend-shallow: 2.0.1 js-beautify: 1.15.1 - prism-media@1.3.5(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13))(ffmpeg-static@5.2.0): + prism-media@1.3.5(@discordjs/opus@https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13))(ffmpeg-static@5.2.0): optionalDependencies: - '@discordjs/opus': https://codeload.github.com/discordjs/opus/tar.gz/8c727579cd81831f6e866f8d82781b4bfa5f4063(encoding@0.1.13) + '@discordjs/opus': https://codeload.github.com/discordjs/opus/tar.gz/18cedf3292fe634f99f81af4a409389dc7b1ebda(encoding@0.1.13) ffmpeg-static: 5.2.0 prism-react-renderer@2.3.1(react@18.3.1): @@ -63220,7 +63239,7 @@ snapshots: '@sqds/multisig': 2.1.3(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) '@tensor-oss/tensorswap-sdk': 4.5.0(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) '@tiplink/api': 0.3.1(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(sodium-native@3.4.1)(utf-8-validate@5.0.10) - '@voltr/vault-sdk': 0.1.3(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@voltr/vault-sdk': 0.1.4(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) ai: 4.1.9(react@19.0.0)(zod@3.24.1) bn.js: 5.2.1 bs58: 5.0.0 @@ -63302,7 +63321,7 @@ snapshots: '@sqds/multisig': 2.1.3(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.3)(utf-8-validate@5.0.10) '@tensor-oss/tensorswap-sdk': 4.5.0(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.3)(utf-8-validate@5.0.10) '@tiplink/api': 0.3.1(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(sodium-native@3.4.1)(utf-8-validate@5.0.10) - '@voltr/vault-sdk': 0.1.3(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.3)(utf-8-validate@5.0.10) + '@voltr/vault-sdk': 0.1.4(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.3)(utf-8-validate@5.0.10) ai: 4.1.9(react@19.0.0)(zod@3.24.1) bn.js: 5.2.1 bs58: 5.0.0 @@ -63578,9 +63597,9 @@ snapshots: dependencies: minipass: 7.1.2 - sswr@2.1.0(svelte@5.19.3): + sswr@2.1.0(svelte@5.19.4): dependencies: - svelte: 5.19.3 + svelte: 5.19.4 swrev: 4.0.0 stable-hash@0.0.4: {} @@ -63935,7 +63954,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte@5.19.3: + svelte@5.19.4: dependencies: '@ampproject/remapping': 2.3.0 '@jridgewell/sourcemap-codec': 1.5.0