From f5609e5f1af6c39bb2b0ad639404aa341ff7aba5 Mon Sep 17 00:00:00 2001 From: Shaw Date: Wed, 26 Feb 2025 22:21:42 -0800 Subject: [PATCH 01/13] userA -> entityA, fix up relationships, add discord userId and globalName to metadata --- packages/agent/src/server/api/agent.ts | 11 ++-- packages/agent/src/server/helper.ts | 34 +----------- packages/core/src/bootstrap.ts | 7 ++- packages/core/src/database.ts | 12 ++--- packages/core/src/generation.ts | 4 ++ packages/core/src/index.ts | 1 + packages/core/src/messages.ts | 6 +-- packages/core/src/posts.ts | 3 +- packages/core/src/relationships.ts | 32 +++++------ packages/core/src/runtime.ts | 18 ++++--- packages/core/src/types.ts | 32 +++++------ packages/plugin-discord/src/index.ts | 30 ++++++++--- .../migrations/20250225233329_init.sql | 14 ++--- .../meta/20250225233329_snapshot.json | 20 +++---- packages/plugin-sql/src/base.ts | 54 +++++++++---------- .../plugin-sql/src/schema/relationship.ts | 22 +++----- 16 files changed, 147 insertions(+), 153 deletions(-) diff --git a/packages/agent/src/server/api/agent.ts b/packages/agent/src/server/api/agent.ts index 8edc98ef2c3..dedc7196b25 100644 --- a/packages/agent/src/server/api/agent.ts +++ b/packages/agent/src/server/api/agent.ts @@ -1,14 +1,11 @@ +import type { Character, Content, IAgentRuntime, Media, Memory } from '@elizaos/core'; +import { ChannelType, composeContext, generateMessageResponse, logger, messageHandlerTemplate, ModelClass, stringToUuid, validateCharacterConfig } from '@elizaos/core'; import express from 'express'; -import type { Character, IAgentRuntime, Media } from '@elizaos/core'; -import { ChannelType, composeContext, generateMessageResponse, logger, ModelClass, stringToUuid, validateCharacterConfig } from '@elizaos/core'; import fs from 'node:fs'; -import type { AgentServer } from '..'; -import { validateUUIDParams } from './api-utils'; - -import type { Content, Memory } from '@elizaos/core'; import path from 'node:path'; -import { messageHandlerTemplate } from '../helper'; +import type { AgentServer } from '..'; import { upload } from '../loader'; +import { validateUUIDParams } from './api-utils'; interface CustomRequest extends express.Request { file?: Express.Multer.File; diff --git a/packages/agent/src/server/helper.ts b/packages/agent/src/server/helper.ts index 00878e5f03e..d1d8682b777 100644 --- a/packages/agent/src/server/helper.ts +++ b/packages/agent/src/server/helper.ts @@ -1,42 +1,12 @@ -import { messageCompletionFooter } from "@elizaos/core"; - -export const messageHandlerTemplate = - `{{actionExamples}} -(Action examples are for reference only. Do not use the information from them in your response.) - -# Knowledge -{{knowledge}} - -# Task: Generate dialog and actions for the character {{agentName}}. -About {{agentName}}: -{{bio}} - -{{system}} - -{{providers}} - -{{attachments}} - -# Capabilities -Note that {{agentName}} is capable of reading/seeing/hearing various forms of media, including images, videos, audio, plaintext and PDFs. Recent attachments have been included above under the "Attachments" section. - -{{messageDirections}} - -{{recentMessages}} - -{{actions}} - -# Instructions: Write the next message for {{agentName}}. -${messageCompletionFooter}`; - export const hyperfiHandlerTemplate = `Task: Generate dialog and actions for the character {{agentName}}. {{actionExamples}} (Action examples are for reference only. Do not use the information from them in your response.) -# Knowledge {{knowledge}} +{{actors}} + About {{agentName}}: {{bio}} diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index 212b45726a4..aaa0cdc591a 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -69,6 +69,8 @@ type UserJoinedParams = { export const shouldRespondTemplate = `{{system}} # Task: Decide on behalf of {{agentName}} whether they should respond to the message, ignore it or stop the conversation. +{{actors}} + About {{agentName}}: {{bio}} @@ -77,15 +79,16 @@ About {{agentName}}: # INSTRUCTIONS: Respond with the word RESPOND if {{agentName}} should respond to the message. Respond with STOP if a user asks {{agentName}} to be quiet. Respond with IGNORE if {{agentName}} should ignore the message. ${shouldRespondFooter}`; -const messageHandlerTemplate = `# Task: Generate dialog and actions for the character {{agentName}}. +export const messageHandlerTemplate = `# Task: Generate dialog and actions for the character {{agentName}}. {{system}} {{actionExamples}} (Action examples are for reference only. Do not use the information from them in your response.) -# Knowledge {{knowledge}} +{{actors}} + About {{agentName}}: {{bio}} diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index d8b4e3b4b4c..f625b4be13d 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -372,22 +372,22 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { /** * Creates a new relationship between two users. - * @param params An object containing the UUIDs of the two users (userA and userB). + * @param params An object containing the UUIDs of the two users (entityA and entityB). * @returns A Promise that resolves to a boolean indicating success or failure of the creation. */ abstract createRelationship(params: { - userA: UUID; - userB: UUID; + entityA: UUID; + entityB: UUID; }): Promise; /** * Retrieves a relationship between two users if it exists. - * @param params An object containing the UUIDs of the two users (userA and userB). + * @param params An object containing the UUIDs of the two users (entityA and entityB). * @returns A Promise that resolves to the Relationship object or null if not found. */ abstract getRelationship(params: { - userA: UUID; - userB: UUID; + entityA: UUID; + entityB: UUID; }): Promise; /** diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index ddc49016da6..243b2654814 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -397,11 +397,15 @@ export async function generateMessageResponse({ stopSequences?: string[]; }): Promise { return await withRetry(async () => { + console.log('**** GENERATE MESSAGE RESPONSE') + console.log(context) const text = await runtime.useModel(modelClass, { runtime, context, stop: stopSequences, }); + console.log('**** GENERATE MESSAGE RESPONSE TEXT') + console.log(text) const parsedContent = parseJSONObjectFromText(text) as Content; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index aba3d565156..2792a7188a6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -22,4 +22,5 @@ export * from "./relationships.ts"; export * from "./roles.ts"; export * from "./runtime.ts"; export * from "./settings.ts"; +export * from "./bootstrap.ts"; export * from "./uuid.ts"; diff --git a/packages/core/src/messages.ts b/packages/core/src/messages.ts index 3a85825c8d4..811fbe89e3d 100644 --- a/packages/core/src/messages.ts +++ b/packages/core/src/messages.ts @@ -31,8 +31,8 @@ export async function getActorDetails({ if (account) { return { id: account.id, - name: account.metadata.name, - username: account.metadata.username, + name: account.metadata.default.name || account.names.join(" aka "), + names: account.names, }; } return null; @@ -59,7 +59,7 @@ export async function getActorDetails({ */ export function formatActors({ actors }: { actors: Actor[] }) { const actorStrings = actors.map((actor: Actor) => { - const header = `${actor.name}`; + const header = `${actor.name} (${actor.names.join(" aka ")})`; return header; }); const finalActorStrings = actorStrings.join("\n"); diff --git a/packages/core/src/posts.ts b/packages/core/src/posts.ts index 71f098cd7c0..3cf3222732b 100644 --- a/packages/core/src/posts.ts +++ b/packages/core/src/posts.ts @@ -40,8 +40,9 @@ export const formatPosts = ({ const actor = actors.find( (actor: Actor) => actor.id === message.userId ); + // TODO: These are okay but not great const userName = actor?.name || "Unknown User"; - const displayName = actor?.username || "unknown"; + const displayName = actor?.names[0] || "unknown"; return `Name: ${userName} (@${displayName}) ID: ${message.id}${message.content.inReplyTo ? `\nIn reply to: ${message.content.inReplyTo}` : ""} diff --git a/packages/core/src/relationships.ts b/packages/core/src/relationships.ts index 82c735a8292..debb32dde35 100644 --- a/packages/core/src/relationships.ts +++ b/packages/core/src/relationships.ts @@ -2,32 +2,32 @@ import type { IAgentRuntime, Relationship, UUID } from "./types.ts"; export async function createRelationship({ runtime, - userA, - userB, + entityA, + entityB, }: { runtime: IAgentRuntime; - userA: UUID; - userB: UUID; + entityA: UUID; + entityB: UUID; }): Promise { return runtime.databaseAdapter.createRelationship({ - userA, - userB, + entityA, + entityB, agentId: runtime.agentId, }); } export async function getRelationship({ runtime, - userA, - userB, + entityA, + entityB, }: { runtime: IAgentRuntime; - userA: UUID; - userB: UUID; + entityA: UUID; + entityB: UUID; }) { return runtime.databaseAdapter.getRelationship({ - userA, - userB, + entityA, + entityB, agentId: runtime.agentId, }); } @@ -53,13 +53,13 @@ export async function formatRelationships({ const formattedRelationships = relationships.map( (relationship: Relationship) => { - const { userA, userB } = relationship; + const { entityA, entityB } = relationship; - if (userA === userId) { - return userB; + if (entityA === userId) { + return entityB; } - return userA; + return entityA; } ); diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 5b977f73f01..ec2b31c01d4 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -1219,6 +1219,10 @@ export class AgentRuntime implements IAgentRuntime { const actors = formatActors({ actors: actorsData ?? [] }); + console.log('**** ACTOR STATE') + console.log(actorsData) + console.log(actors) + const recentMessages = formatMessages({ messages: recentMessagesData, actors: actorsData, @@ -1317,16 +1321,16 @@ export class AgentRuntime implements IAgentRuntime { .join("\n\n"); const getRecentInteractions = async ( - userA: UUID, - userB: UUID + entityA: UUID, + entityB: UUID ): Promise => { // Convert to tenant-specific ID if needed const tenantUserA = - userA === this.agentId ? userA : this.generateTenantUserId(userA); + entityA === this.agentId ? entityA : this.generateTenantUserId(entityA); - // Find all rooms where userA and userB are participants + // Find all rooms where entityA and entityB are participants const rooms = await this.databaseAdapter.getRoomsForParticipants( - [tenantUserA, userB], + [tenantUserA, entityB], this.agentId ); @@ -1420,7 +1424,7 @@ export class AgentRuntime implements IAgentRuntime { Math.floor(Math.random() * this.character.adjectives.length) ] : "", - knowledge: formattedKnowledge, + knowledge: addHeader("# Knowledge", formattedKnowledge), knowledgeData: knowledgeData, // Recent interactions between the sender and receiver, formatted as messages recentMessageInteractions: formattedMessageInteractions, @@ -1496,7 +1500,7 @@ export class AgentRuntime implements IAgentRuntime { // Agent runtime stuff senderName, - actors: actors && actors.length > 0 ? addHeader("# Actors", actors) : "", + actors: actors && actors.length > 0 ? addHeader("# Actors in the Room", actors) : "", actorsData, roomId, recentMessages: diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2d1cc245269..f7c61703c4a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -55,14 +55,14 @@ export interface ConversationExample { * Represents an actor/participant in a conversation */ export interface Actor { + /** Unique identifier */ + id: UUID; + /** Display name */ name: string; - /** Username/handle */ - username: string; - - /** Unique identifier */ - id: UUID; + /** All names for the actor */ + names: string[]; } /** @@ -476,19 +476,21 @@ export interface Relationship { id: UUID; /** First user ID */ - userA: UUID; + entityA: UUID; /** Second user ID */ - userB: UUID; + entityB: UUID; /** Primary user ID */ - userId: UUID; + agentId: UUID; - /** Associated room ID */ - roomId: UUID; + /** Any tags (no structured ontology) */ + tags: string[]; - /** Relationship status */ - status: string; + /** Any metadata you might want to add */ + metadata: { + [key: string]: any + } /** Optional creation timestamp */ createdAt?: string; @@ -906,11 +908,11 @@ export interface IDatabaseAdapter { state: "FOLLOWED" | "MUTED" | null ): Promise; - createRelationship(params: { userA: UUID; userB: UUID; agentId: UUID }): Promise; + createRelationship(params: { entityA: UUID; entityB: UUID; agentId: UUID }): Promise; getRelationship(params: { - userA: UUID; - userB: UUID; + entityA: UUID; + entityB: UUID; agentId: UUID; }): Promise; diff --git a/packages/plugin-discord/src/index.ts b/packages/plugin-discord/src/index.ts index f46ee11ec4e..8672def627a 100644 --- a/packages/plugin-discord/src/index.ts +++ b/packages/plugin-discord/src/index.ts @@ -642,9 +642,15 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { username: tag, name: member.displayName || member.user.username, }, - discord: { + discord: member.user.globalName ? { username: tag, displayName: member.displayName || member.user.username, + globalName: member.user.globalName, + userId: member.id, + } : { + username: tag, + displayName: member.displayName || member.user.username, + userId: member.id, }, }, }); @@ -674,14 +680,20 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { new Set([member.user.username, member.displayName]) ), metadata: { - discord: { - username: tag, - displayName: member.displayName || member.user.username, - }, default: { username: tag, name: member.displayName || member.user.username, }, + discord: member.user.globalName ? { + username: tag, + displayName: member.displayName || member.user.username, + globalName: member.user.globalName, + userId: member.id, + } : { + username: tag, + displayName: member.displayName || member.user.username, + userId: member.id, + }, }, }); } @@ -715,9 +727,15 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { username: tag, name: member.displayName || member.user.username, }, - discord: { + discord: member.user.globalName ? { + username: tag, + displayName: member.displayName || member.user.username, + globalName: member.user.globalName, + userId: member.id, + } : { username: tag, displayName: member.displayName || member.user.username, + userId: member.id, }, }, }); diff --git a/packages/plugin-sql/drizzle/migrations/20250225233329_init.sql b/packages/plugin-sql/drizzle/migrations/20250225233329_init.sql index 1977dc7735a..520af6b144a 100644 --- a/packages/plugin-sql/drizzle/migrations/20250225233329_init.sql +++ b/packages/plugin-sql/drizzle/migrations/20250225233329_init.sql @@ -115,8 +115,8 @@ CREATE TABLE "participants" ( CREATE TABLE "relationships" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "createdAt" timestamptz DEFAULT now() NOT NULL, - "userA" uuid NOT NULL, - "userB" uuid NOT NULL, + "entityA" uuid NOT NULL, + "entityB" uuid NOT NULL, "agentId" uuid NOT NULL, "status" text, "userId" uuid NOT NULL @@ -167,12 +167,12 @@ ALTER TABLE "participants" ADD CONSTRAINT "participants_roomId_rooms_id_fk" FORE ALTER TABLE "participants" ADD CONSTRAINT "participants_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "participants" ADD CONSTRAINT "fk_room" FOREIGN KEY ("roomId") REFERENCES "public"."rooms"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "participants" ADD CONSTRAINT "fk_user" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userA_entities_id_fk" FOREIGN KEY ("userA") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userB_entities_id_fk" FOREIGN KEY ("userB") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userA_entities_id_fk" FOREIGN KEY ("entityA") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userB_entities_id_fk" FOREIGN KEY ("entityB") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "relationships" ADD CONSTRAINT "relationships_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userId_entities_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_a" FOREIGN KEY ("userA") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_b" FOREIGN KEY ("userB") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_a" FOREIGN KEY ("entityA") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_b" FOREIGN KEY ("entityB") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "relationships" ADD CONSTRAINT "fk_user" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "rooms" ADD CONSTRAINT "rooms_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "rooms" ADD CONSTRAINT "rooms_worldId_worlds_id_fk" FOREIGN KEY ("worldId") REFERENCES "public"."worlds"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint @@ -183,7 +183,7 @@ CREATE INDEX "idx_memories_document_id" ON "memories" USING btree (((metadata->> CREATE INDEX "idx_fragments_order" ON "memories" USING btree (((metadata->>'documentId')),((metadata->>'position')));--> statement-breakpoint CREATE INDEX "idx_participants_user" ON "participants" USING btree ("userId");--> statement-breakpoint CREATE INDEX "idx_participants_room" ON "participants" USING btree ("roomId");--> statement-breakpoint -CREATE INDEX "idx_relationships_users" ON "relationships" USING btree ("userA","userB"); +CREATE INDEX "idx_relationships_users" ON "relationships" USING btree ("entityA","entityB"); CREATE EXTENSION IF NOT EXISTS vector; --> statement-breakpoint diff --git a/packages/plugin-sql/drizzle/migrations/meta/20250225233329_snapshot.json b/packages/plugin-sql/drizzle/migrations/meta/20250225233329_snapshot.json index 81e0e8cbedf..79119cee8e4 100644 --- a/packages/plugin-sql/drizzle/migrations/meta/20250225233329_snapshot.json +++ b/packages/plugin-sql/drizzle/migrations/meta/20250225233329_snapshot.json @@ -1042,14 +1042,14 @@ "notNull": true, "default": "now()" }, - "userA": { - "name": "userA", + "entityA": { + "name": "entityA", "type": "uuid", "primaryKey": false, "notNull": true }, - "userB": { - "name": "userB", + "entityB": { + "name": "entityB", "type": "uuid", "primaryKey": false, "notNull": true @@ -1078,13 +1078,13 @@ "name": "idx_relationships_users", "columns": [ { - "expression": "userA", + "expression": "entityA", "isExpression": false, "asc": true, "nulls": "last" }, { - "expression": "userB", + "expression": "entityB", "isExpression": false, "asc": true, "nulls": "last" @@ -1102,7 +1102,7 @@ "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "userA" + "entityA" ], "columnsTo": [ "id" @@ -1115,7 +1115,7 @@ "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "userB" + "entityB" ], "columnsTo": [ "id" @@ -1154,7 +1154,7 @@ "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "userA" + "entityA" ], "columnsTo": [ "id" @@ -1167,7 +1167,7 @@ "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "userB" + "entityB" ], "columnsTo": [ "id" diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index 12e51b44545..7616273a110 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -1267,11 +1267,11 @@ export abstract class BaseDrizzleAdapter } async createRelationship(params: { - userA: UUID; - userB: UUID; + entityA: UUID; + entityB: UUID; }): Promise { - if (!params.userA || !params.userB) { - throw new Error("userA and userB are required"); + if (!params.entityA || !params.entityB) { + throw new Error("entityA and entityB are required"); } return this.withDatabase(async () => { @@ -1280,15 +1280,15 @@ export abstract class BaseDrizzleAdapter const relationshipId = v4(); await tx.insert(relationshipTable).values({ id: relationshipId, - userA: params.userA, - userB: params.userB, - userId: params.userA, + entityA: params.entityA, + entityB: params.entityB, + userId: params.entityA, }); logger.debug("Relationship created successfully:", { relationshipId, - userA: params.userA, - userB: params.userB, + entityA: params.entityA, + entityB: params.entityB, }); return true; @@ -1296,8 +1296,8 @@ export abstract class BaseDrizzleAdapter } catch (error) { if ((error as { code?: string }).code === "23505") { logger.warn("Relationship already exists:", { - userA: params.userA, - userB: params.userB, + entityA: params.entityA, + entityB: params.entityB, error: error instanceof Error ? error.message @@ -1305,8 +1305,8 @@ export abstract class BaseDrizzleAdapter }); } else { logger.error("Failed to create relationship:", { - userA: params.userA, - userB: params.userB, + entityA: params.entityA, + entityB: params.entityB, error: error instanceof Error ? error.message @@ -1319,11 +1319,11 @@ export abstract class BaseDrizzleAdapter } async getRelationship(params: { - userA: UUID; - userB: UUID; + entityA: UUID; + entityB: UUID; }): Promise { - if (!params.userA || !params.userB) { - throw new Error("userA and userB are required"); + if (!params.entityA || !params.entityB) { + throw new Error("entityA and entityB are required"); } return this.withDatabase(async () => { @@ -1334,12 +1334,12 @@ export abstract class BaseDrizzleAdapter .where( or( and( - eq(relationshipTable.userA, params.userA), - eq(relationshipTable.userB, params.userB) + eq(relationshipTable.entityA, params.entityA), + eq(relationshipTable.entityB, params.entityB) ), and( - eq(relationshipTable.userA, params.userB), - eq(relationshipTable.userB, params.userA) + eq(relationshipTable.entityA, params.entityB), + eq(relationshipTable.entityB, params.entityA) ) ) ) @@ -1350,14 +1350,14 @@ export abstract class BaseDrizzleAdapter } logger.debug("No relationship found between users:", { - userA: params.userA, - userB: params.userB, + entityA: params.entityA, + entityB: params.entityB, }); return null; } catch (error) { logger.error("Error fetching relationship:", { - userA: params.userA, - userB: params.userB, + entityA: params.entityA, + entityB: params.entityB, error: error instanceof Error ? error.message : String(error), }); @@ -1377,8 +1377,8 @@ export abstract class BaseDrizzleAdapter .from(relationshipTable) .where( or( - eq(relationshipTable.userA, params.userId), - eq(relationshipTable.userB, params.userId) + eq(relationshipTable.entityA, params.userId), + eq(relationshipTable.entityB, params.userId) ) ) .orderBy(desc(relationshipTable.createdAt)); diff --git a/packages/plugin-sql/src/schema/relationship.ts b/packages/plugin-sql/src/schema/relationship.ts index 3227b36d1cd..36eabfd6067 100644 --- a/packages/plugin-sql/src/schema/relationship.ts +++ b/packages/plugin-sql/src/schema/relationship.ts @@ -4,6 +4,7 @@ import { text, index, foreignKey, + jsonb, } from "drizzle-orm/pg-core"; import { sql } from "drizzle-orm"; import { numberTimestamp } from "./types"; @@ -17,35 +18,28 @@ export const relationshipTable = pgTable( createdAt: numberTimestamp("createdAt") .default(sql`now()`) .notNull(), - userA: uuid("userA") + entityA: uuid("entityA") .notNull() .references(() => entityTable.id), - userB: uuid("userB") + entityB: uuid("entityB") .notNull() .references(() => entityTable.id), agentId: uuid("agentId") .notNull() .references(() => agentTable.id), - status: text("status"), - userId: uuid("userId") - .notNull() - .references(() => entityTable.id), + tags: text("tags").array(), + metadata: jsonb("metadata"), }, (table) => [ - index("idx_relationships_users").on(table.userA, table.userB), + index("idx_relationships_users").on(table.entityA, table.entityB), foreignKey({ name: "fk_user_a", - columns: [table.userA], + columns: [table.entityA], foreignColumns: [entityTable.id], }).onDelete("cascade"), foreignKey({ name: "fk_user_b", - columns: [table.userB], - foreignColumns: [entityTable.id], - }).onDelete("cascade"), - foreignKey({ - name: "fk_user", - columns: [table.userId], + columns: [table.entityB], foreignColumns: [entityTable.id], }).onDelete("cascade"), ] From 3d8817c9a4ee30c32df63225b9bd224d15f86a6e Mon Sep 17 00:00:00 2001 From: Shaw Date: Wed, 26 Feb 2025 23:10:16 -0800 Subject: [PATCH 02/13] Refactor confirm/cancel to be an options system - needs teseting --- .../swarm/socialMediaManager/actions/post.ts | 36 ++- packages/core/src/actions/cancel.ts | 4 +- packages/core/src/actions/confirm.ts | 110 --------- packages/core/src/actions/options.ts | 229 ++++++++++++++++++ packages/core/src/bootstrap.ts | 8 +- packages/core/src/providers/confirmation.ts | 44 ---- packages/core/src/providers/options.ts | 75 ++++++ packages/core/src/types.ts | 8 +- packages/plugin-discord/src/index.ts | 6 +- 9 files changed, 353 insertions(+), 167 deletions(-) delete mode 100644 packages/core/src/actions/confirm.ts create mode 100644 packages/core/src/actions/options.ts delete mode 100644 packages/core/src/providers/confirmation.ts create mode 100644 packages/core/src/providers/options.ts diff --git a/packages/agent/src/swarm/socialMediaManager/actions/post.ts b/packages/agent/src/swarm/socialMediaManager/actions/post.ts index 763e3b8fe37..f09cfadbd29 100644 --- a/packages/agent/src/swarm/socialMediaManager/actions/post.ts +++ b/packages/agent/src/swarm/socialMediaManager/actions/post.ts @@ -245,7 +245,7 @@ const twitterPostAction: Action = { // Check if there are any pending Twitter posts awaiting confirmation const pendingTasks = runtime.getTasks({ roomId: message.roomId, - tags: ["AWAITING_CONFIRMATION", "TWITTER_POST"], + tags: ["TWITTER_POST"], }); if (pendingTasks && pendingTasks.length > 0) { @@ -266,8 +266,38 @@ const twitterPostAction: Action = { roomId: message.roomId, name: "Confirm Twitter Post", description: "Confirm the tweet to be posted.", - tags: ["TWITTER_POST", "AWAITING_CONFIRMATION"], - handler: async (runtime: IAgentRuntime) => { + tags: ["TWITTER_POST", "AWAITING_CHOICE"], + metadata: { + options: [ + { + name: "post", + description: "Post the tweet to Twitter", + }, + { + name: "cancel", + description: "Cancel the tweet and don't post it", + }, + ], + }, + handler: async (runtime: IAgentRuntime, options: { option: string }) => { + if (options.option === "cancel") { + await callback({ + ...responseContent, + text: "Tweet cancelled. I won't post it.", + action: "TWITTER_POST_CANCELLED" + }); + return; + } + + if(options.option !== "post") { + await callback({ + ...responseContent, + text: "Invalid option. Should be 'post' or 'cancel'.", + action: "TWITTER_POST_INVALID_OPTION" + }); + return; + } + const vals = { TWITTER_USERNAME: worldSettings.TWITTER_USERNAME.value, TWITTER_EMAIL: worldSettings.TWITTER_EMAIL.value, diff --git a/packages/core/src/actions/cancel.ts b/packages/core/src/actions/cancel.ts index 2001da288ba..c49912ff401 100644 --- a/packages/core/src/actions/cancel.ts +++ b/packages/core/src/actions/cancel.ts @@ -13,7 +13,7 @@ export const cancelTaskAction: Action = { ): Promise => { const pendingTasks = runtime.getTasks({ roomId: message.roomId, - tags: ["AWAITING_CONFIRMATION"], + tags: ["AWAITING_CHOICE"], }); // Only validate if there are pending tasks @@ -37,7 +37,7 @@ export const cancelTaskAction: Action = { // Get pending tasks for this room const pendingTasks = runtime.getTasks({ roomId: message.roomId, - tags: ["AWAITING_CONFIRMATION"], + tags: ["AWAITING_CHOICE"], }); if (!pendingTasks || pendingTasks.length === 0) { diff --git a/packages/core/src/actions/confirm.ts b/packages/core/src/actions/confirm.ts deleted file mode 100644 index 34ec49247e1..00000000000 --- a/packages/core/src/actions/confirm.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { logger } from "../logger"; -import { Action, ActionExample, HandlerCallback, IAgentRuntime, Memory, State } from "../types"; - -export const confirmTaskAction: Action = { - name: "CONFIRM_TASK", - similes: ["APPROVE_TASK", "EXECUTE_TASK", "PROCEED", "GO_AHEAD", "CONFIRM"], - description: - "Confirms and executes a pending task that's awaiting confirmation", - - validate: async ( - runtime: IAgentRuntime, - _message: Memory, - _state: State - ): Promise => { - // Get all tasks with AWAITING_CONFIRMATION tag - const pendingTasks = runtime.getTasks({}); - - // Only validate if there are pending tasks - return pendingTasks && pendingTasks.length > 0; - }, - - handler: async ( - runtime: IAgentRuntime, - message: Memory, - _state: State, - _options: any, - callback: HandlerCallback, - responses: Memory[] - ): Promise => { - try { - // First handle any initial responses - for (const response of responses) { - await callback(response.content); - } - - // Get pending tasks for this room - const pendingTasks = runtime.getTasks({ - roomId: message.roomId, - tags: ["AWAITING_CONFIRMATION"], - }); - - if (!pendingTasks || pendingTasks.length === 0) { - await callback({ - text: "No tasks currently awaiting confirmation.", - action: "CONFIRM_TASK", - source: message.content.source, - }); - return; - } - - // Process each pending task - for (const task of pendingTasks) { - try { - // Execute the task handler - await task.handler(runtime); - - // Delete the task after successful execution - runtime.deleteTask(task.id); - } catch (error) { - logger.error("Error executing task:", error); - await callback({ - text: "There was an error executing the task.", - action: "CONFIRM_TASK_ERROR", - source: message.content.source, - }); - } - } - } catch (error) { - logger.error("Error in confirm task handler:", error); - await callback({ - text: "There was an error processing the task confirmation.", - action: "CONFIRM_TASK_ERROR", - source: message.content.source, - }); - } - }, - - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Yes, go ahead and post that tweet", - }, - }, - { - user: "{{user2}}", - content: { - text: "Task confirmed and executed successfully!", - action: "CONFIRM_TASK", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "Confirm", - }, - }, - { - user: "{{user2}}", - content: { - text: "Task confirmed and executed successfully!", - action: "CONFIRM_TASK", - }, - }, - ], - ] as ActionExample[][], -}; diff --git a/packages/core/src/actions/options.ts b/packages/core/src/actions/options.ts new file mode 100644 index 00000000000..740aed674a3 --- /dev/null +++ b/packages/core/src/actions/options.ts @@ -0,0 +1,229 @@ +import { composeContext } from "../context"; +import { logger } from "../logger"; +import { + Action, + ActionExample, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, +} from "../types"; + +const optionExtractionTemplate = `# Task: Extract selected task and option from user message + +# Available Tasks: +{{#each tasks}} +Task ${taskId}: ${name} +Available options: +{{#each options}} +- {{name}}: {{description}} +{{/each}} +- ABORT: Cancel this task + +{{/each}} + +# Recent Messages: +{{recentMessages}} + +# Instructions: +1. Review the user's message and identify which task and option they are selecting +2. Match against the available tasks and their options, including ABORT +3. Return the task ID and selected option name exactly as listed above +4. If no clear selection is made, return null for both fields + +Return in JSON format: +{ + "taskId": number | null, + "selectedOption": "OPTION_NAME" | null +} +`; + +export const selectOptionAction: Action = { + name: "SELECT_OPTION", + similes: ["CHOOSE_OPTION", "SELECT", "PICK", "CHOOSE"], + description: "Selects an option for a pending task that has multiple options", + + validate: async ( + runtime: IAgentRuntime, + message: Memory, + _state: State + ): Promise => { + // Get all tasks with options metadata + const pendingTasks = runtime.getTasks({ + roomId: message.roomId, + tags: ["AWAITING_CHOICE"], + }); + + // Only validate if there are pending tasks with options + return ( + pendingTasks && + pendingTasks.length > 0 && + pendingTasks.some((task) => task.metadata?.options) + ); + }, + + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback: HandlerCallback, + responses: Memory[] + ): Promise => { + try { + // Handle initial responses + for (const response of responses) { + await callback(response.content); + } + + const pendingTasks = runtime.getTasks({ + roomId: message.roomId, + tags: ["AWAITING_CHOICE"], + }); + + if (!pendingTasks?.length) { + await callback({ + text: "No tasks currently awaiting options selection.", + action: "SELECT_OPTION", + source: message.content.source, + }); + return; + } + + const tasksWithOptions = pendingTasks.filter( + (task) => task.metadata?.options + ); + + if (!tasksWithOptions.length) { + await callback({ + text: "No tasks currently have options to select from.", + action: "SELECT_OPTION", + source: message.content.source, + }); + return; + } + + // Format tasks with their options for the LLM + const formattedTasks = tasksWithOptions.map((task, index) => ({ + taskId: index + 1, + name: task.name, + options: task.metadata.options.map(opt => ({ + name: typeof opt === 'string' ? opt : opt.name, + description: typeof opt === 'string' ? opt : opt.description || opt.name + })) + })); + + const context = composeContext({ + state: { + ...state, + tasks: formattedTasks, + recentMessages: message.content.text + }, + template: optionExtractionTemplate + }); + + const result = await runtime.useModel(ModelClass.TEXT_SMALL, { + context, + stopSequences: ['}'] + }); + + const parsed = JSON.parse(result); + const { taskId, selectedOption } = parsed; + + if (taskId && selectedOption) { + const selectedTask = tasksWithOptions[taskId - 1]; + + if (selectedOption === 'ABORT') { + runtime.deleteTask(selectedTask.id); + await callback({ + text: `Task "${selectedTask.name}" has been cancelled.`, + action: "SELECT_OPTION", + source: message.content.source, + }); + return; + } + + try { + await selectedTask.handler(runtime, { option: selectedOption }); + runtime.deleteTask(selectedTask.id); + await callback({ + text: `Selected option: ${selectedOption} for task: ${selectedTask.name}`, + action: "SELECT_OPTION", + source: message.content.source, + }); + return; + } catch (error) { + logger.error("Error executing task with option:", error); + await callback({ + text: "There was an error processing your selection.", + action: "SELECT_OPTION_ERROR", + source: message.content.source, + }); + return; + } + } + + // If no task/option was selected, list available options + let optionsText = "Please select a valid option from one of these tasks:\n\n"; + tasksWithOptions.forEach((task, index) => { + optionsText += `${index + 1}. **${task.name}**:\n`; + const options = task.metadata.options.map(opt => + typeof opt === 'string' ? opt : opt.name + ); + options.push('ABORT'); + optionsText += options.map(opt => `- ${opt}`).join('\n'); + optionsText += '\n\n'; + }); + + await callback({ + text: optionsText, + action: "SELECT_OPTION_INVALID", + source: message.content.source, + }); + + } catch (error) { + logger.error("Error in select option handler:", error); + await callback({ + text: "There was an error processing the option selection.", + action: "SELECT_OPTION_ERROR", + source: message.content.source, + }); + } + }, + + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "post", + }, + }, + { + user: "{{user2}}", + content: { + text: "Selected option: post for task: Confirm Twitter Post", + action: "SELECT_OPTION", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "I choose cancel", + }, + }, + { + user: "{{user2}}", + content: { + text: "Selected option: cancel for task: Confirm Twitter Post", + action: "SELECT_OPTION", + }, + }, + ], + ] as ActionExample[][], +}; + +export default selectOptionAction; diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index aaa0cdc591a..94c77ba2ee3 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -1,7 +1,7 @@ import { UUID } from "crypto"; import { v4 } from "uuid"; import { cancelTaskAction } from "./actions/cancel.ts"; -import { confirmTaskAction } from "./actions/confirm.ts"; +import { selectOptionAction } from "./actions/options.ts"; import { followRoomAction } from "./actions/followRoom.ts"; import { ignoreAction } from "./actions/ignore.ts"; import { muteRoomAction } from "./actions/muteRoom.ts"; @@ -22,7 +22,7 @@ import { } from "./index.ts"; import { logger } from "./logger.ts"; import { messageCompletionFooter, shouldRespondFooter } from "./parsing.ts"; -import { confirmationTasksProvider } from "./providers/confirmation.ts"; +import { optionsProvider } from "./providers/options.ts"; import { factsProvider } from "./providers/facts.ts"; import { roleProvider } from "./providers/roles.ts"; import { settingsProvider } from "./providers/settings.ts"; @@ -852,7 +852,7 @@ export const bootstrapPlugin: Plugin = { muteRoomAction, unmuteRoomAction, cancelTaskAction, - confirmTaskAction, + selectOptionAction, updateRoleAction, updateSettingsAction, ], @@ -861,7 +861,7 @@ export const bootstrapPlugin: Plugin = { providers: [ timeProvider, factsProvider, - confirmationTasksProvider, + optionsProvider, roleProvider, settingsProvider, ], diff --git a/packages/core/src/providers/confirmation.ts b/packages/core/src/providers/confirmation.ts deleted file mode 100644 index 450181d4971..00000000000 --- a/packages/core/src/providers/confirmation.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { logger } from "../logger"; -import { IAgentRuntime, Memory, Provider, State } from "../types"; - -export const confirmationTasksProvider: Provider = { - get: async ( - runtime: IAgentRuntime, - message: Memory, - _state?: State - ): Promise => { - try { - // Get all pending tasks for this room - const pendingTasks = runtime.getTasks({ - roomId: message.roomId, - tags: ["AWAITING_CONFIRMATION"] - }); - - if (!pendingTasks || pendingTasks.length === 0) { - return ""; - } - - // Format tasks into a readable list - let output = "# Pending Tasks\n\n"; - output += "The following tasks are awaiting confirmation:\n\n"; - - pendingTasks.forEach((task, index) => { - output += `${index + 1}. **${task.name}**\n`; - if (task.description) { - output += ` ${task.description}\n`; - } - output += "\n"; - }); - - output += "To confirm a task, say 'confirm' or 'approve'.\n"; - output += "To cancel a task, say 'cancel' or 'reject'.\n"; - - return output.trim(); - } catch (error) { - logger.error("Error in confirmation tasks provider:", error); - return "Error retrieving pending tasks."; - } - } -}; - -export default confirmationTasksProvider; \ No newline at end of file diff --git a/packages/core/src/providers/options.ts b/packages/core/src/providers/options.ts new file mode 100644 index 00000000000..5f6295f3afe --- /dev/null +++ b/packages/core/src/providers/options.ts @@ -0,0 +1,75 @@ +import { logger } from "../logger"; +import { IAgentRuntime, Memory, Provider, State } from "../types"; + +// Define an interface for option objects +interface OptionObject { + name: string; + description?: string; +} + +export const optionsProvider: Provider = { + get: async ( + runtime: IAgentRuntime, + message: Memory, + _state?: State + ): Promise => { + try { + // Get all pending tasks for this room with options + const pendingTasks = runtime.getTasks({ + roomId: message.roomId, + tags: ["AWAITING_CHOICE"] + }); + + if (!pendingTasks || pendingTasks.length === 0) { + return ""; + } + + // Filter tasks that have options + const tasksWithOptions = pendingTasks.filter(task => task.metadata?.options); + + if (tasksWithOptions.length === 0) { + return ""; + } + + // Format tasks into a readable list + let output = "# Pending Tasks\n\n"; + output += "The following tasks are awaiting your selection:\n\n"; + + tasksWithOptions.forEach((task, index) => { + output += `${index + 1}. **${task.name}**\n`; + if (task.description) { + output += ` ${task.description}\n`; + } + + // List available options + if (task.metadata?.options) { + output += " Options:\n"; + + // Handle both string[] and OptionObject[] formats + const options = task.metadata.options as string[] | OptionObject[]; + + options.forEach(option => { + if (typeof option === 'string') { + // Handle string option + const description = task.metadata?.options.find(o => o.name === option)?.description || ''; + output += ` - \`${option}\` ${description ? `- ${description}` : ''}\n`; + } else { + // Handle option object + output += ` - \`${option.name}\` ${option.description ? `- ${option.description}` : ''}\n`; + } + }); + } + output += "\n"; + }); + + output += "To select an option, reply with the option name (e.g., 'post' or 'cancel').\n"; + + return output.trim(); + } catch (error) { + logger.error("Error in options provider:", error); + return "Error retrieving pending tasks with options."; + } + } +}; + +export default optionsProvider; \ No newline at end of file diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f7c61703c4a..c72514101e9 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1428,10 +1428,16 @@ export interface TeePluginConfig { export interface Task { id?: UUID; name: string; + metadata?: { + options?: { + name: string; + description: string; + }[]; + }; description: string; roomId: UUID; tags: string[]; - handler: (runtime: IAgentRuntime) => Promise; + handler: (runtime: IAgentRuntime, options: { [key: string]: unknown }) => Promise; validate?: (runtime: IAgentRuntime, message: Memory, state: State) => Promise; } diff --git a/packages/plugin-discord/src/index.ts b/packages/plugin-discord/src/index.ts index 8672def627a..c216a2c1238 100644 --- a/packages/plugin-discord/src/index.ts +++ b/packages/plugin-discord/src/index.ts @@ -635,7 +635,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { users.push({ id: stringToUuid(`${member.id}-${this.runtime.agentId}`), names: Array.from( - new Set([member.user.username, member.displayName]) + new Set([member.user.username, member.displayName, member.user.globalName]) ), metadata: { default: { @@ -677,7 +677,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { users.push({ id: userId, names: Array.from( - new Set([member.user.username, member.displayName]) + new Set([member.user.username, member.displayName, member.user.globalName]) ), metadata: { default: { @@ -720,7 +720,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { users.push({ id: stringToUuid(`${member.id}-${this.runtime.agentId}`), names: Array.from( - new Set([member.user.username, member.displayName]) + new Set([member.user.username, member.displayName, member.user.globalName]) ), metadata: { default: { From 0a56e668372a96ae7284eabf93d9a66f1bc091d6 Mon Sep 17 00:00:00 2001 From: Shaw Date: Thu, 27 Feb 2025 00:46:23 -0800 Subject: [PATCH 03/13] getEntitiesForRoom, getActorDetails should be faster --- packages/core/src/actions/options.ts | 2 +- packages/core/src/bootstrap.ts | 5 ++--- packages/core/src/database.ts | 2 ++ packages/core/src/messages.ts | 25 +++++---------------- packages/core/src/providers/roles.ts | 4 ++-- packages/core/src/types.ts | 17 ++++++++++++-- packages/plugin-sql/src/base.ts | 22 ++++++++++++++++++ packages/plugin-sql/src/schema/component.ts | 21 +++++++++++++++++ packages/plugin-sql/src/schema/index.ts | 5 +++-- 9 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 packages/plugin-sql/src/schema/component.ts diff --git a/packages/core/src/actions/options.ts b/packages/core/src/actions/options.ts index 740aed674a3..8fa5b62102b 100644 --- a/packages/core/src/actions/options.ts +++ b/packages/core/src/actions/options.ts @@ -14,7 +14,7 @@ const optionExtractionTemplate = `# Task: Extract selected task and option from # Available Tasks: {{#each tasks}} -Task ${taskId}: ${name} +Task {{taskId}}: {{name}} Available options: {{#each options}} - {{name}}: {{description}} diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index 94c77ba2ee3..825cb949397 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -689,10 +689,9 @@ const handleServerSync = async ({ userId: user.id, roomId: defaultRoom.id, userName: - user.metadata[source].username || - user.metadata.default.username, + user.metadata[source].username, userScreenName: - user.metadata[source].name || user.metadata.default.name, + user.metadata[source].name, source: source, channelId: defaultRoom.channelId, serverId: world.serverId, diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index f625b4be13d..e98083cd20d 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -44,6 +44,8 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { */ abstract getEntityById(userId: UUID, agentId: UUID): Promise; + abstract getEntitiesForRoom(roomId: UUID, agentId: UUID): Promise; + abstract getAgent(agentId: UUID): Promise; abstract createAgent(agent: Agent): Promise; diff --git a/packages/core/src/messages.ts b/packages/core/src/messages.ts index 811fbe89e3d..2637eced5e8 100644 --- a/packages/core/src/messages.ts +++ b/packages/core/src/messages.ts @@ -19,25 +19,12 @@ export async function getActorDetails({ runtime: IAgentRuntime; roomId: UUID; }) { - const participantIds = await runtime.databaseAdapter.getParticipantsForRoom( - roomId, - runtime.agentId - ); - - // Fetch all actor details - const actors = await Promise.all( - participantIds.map(async (userId) => { - const account = await runtime.databaseAdapter.getEntityById(userId, runtime.agentId); - if (account) { - return { - id: account.id, - name: account.metadata.default.name || account.names.join(" aka "), - names: account.names, - }; - } - return null; - }) - ); + const room = await runtime.getRoom(roomId); + const actors = (await runtime.databaseAdapter.getEntitiesForRoom(roomId, runtime.agentId)).map(entity => ({ + id: entity.id, + name: entity.metadata[room.source].name, + names: entity.names, + })); // Filter out nulls and ensure uniqueness by ID const uniqueActors = new Map(); diff --git a/packages/core/src/providers/roles.ts b/packages/core/src/providers/roles.ts index 76bccd658ce..fc1c06e80ae 100644 --- a/packages/core/src/providers/roles.ts +++ b/packages/core/src/providers/roles.ts @@ -57,8 +57,8 @@ export const roleProvider: Provider = { // get the user from the database const user = await runtime.getEntity(userId as UUID); - const name = user.metadata[message.content.source ?? room.source]?.name ?? user.metadata.default.name; - const username = user.metadata[message.content.source ?? room.source].username ?? user.metadata.default.username; + const name = user.metadata[room.source]?.name; + const username = user.metadata[room.source]?.username; // Skip duplicates (we store both UUID and original ID) if (owners.some(owner => owner.username === username) || admins.some(admin => admin.username === username) || members.some(member => member.username === username)) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c72514101e9..40a125dae48 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -496,6 +496,15 @@ export interface Relationship { createdAt?: string; } +export interface Component { + id: UUID; + entityId: UUID; + name: string; + data: { + [key: string]: any; + }; +} + /** * Represents a user account */ @@ -760,12 +769,16 @@ export interface IDatabaseAdapter { updateAgent(agent: Agent): Promise; - /** Get account by ID */ + /** Get entity by ID */ getEntityById(userId: UUID, agentId: UUID): Promise; - /** Create new account */ + /** Get entities for room */ + getEntitiesForRoom(roomId: UUID, agentId: UUID): Promise; + + /** Create new entity */ createEntity(entity: Entity): Promise; + /** Update entity */ updateEntity(entity: Entity): Promise; /** Get memories matching criteria */ diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index 7616273a110..6f8f059a2d5 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -60,6 +60,7 @@ import { relationshipTable, roomTable, worldTable, + componentTable, } from "./schema/index"; import { DrizzleOperations } from "./types"; @@ -215,6 +216,7 @@ export abstract class BaseDrizzleAdapter const result = await this.db .select() .from(entityTable) + .leftJoin(componentTable, eq(entityTable.id, componentTable.entityId)) .where(and(eq(entityTable.id, userId), eq(entityTable.agentId, agentId))) .limit(1); @@ -226,6 +228,26 @@ export abstract class BaseDrizzleAdapter }); } + async getEntitiesForRoom(roomId: UUID, agentId: UUID): Promise { + return this.withDatabase(async () => { + const result = await this.db + .select({ + entity: entityTable + }) + .from(participantTable) + .leftJoin( + entityTable, + and( + eq(participantTable.userId, entityTable.id), + eq(entityTable.agentId, agentId) + ) + ) + .where(eq(participantTable.roomId, roomId)); + + return result.map(row => row.entity).filter(Boolean); + }); + } + async createEntity(entity: Entity): Promise { return this.withDatabase(async () => { try { diff --git a/packages/plugin-sql/src/schema/component.ts b/packages/plugin-sql/src/schema/component.ts new file mode 100644 index 00000000000..da1fdfab9ee --- /dev/null +++ b/packages/plugin-sql/src/schema/component.ts @@ -0,0 +1,21 @@ +import { pgTable, uuid, jsonb, text } from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm"; +import { entityTable } from "./entity"; +import { numberTimestamp } from "./types"; +import { agentTable } from "./agent"; +import { roomTable } from "./room"; +import { worldTable } from "./worldTable"; + +export const componentTable = pgTable("components", { + id: uuid("id").primaryKey().defaultRandom(), + entityId: uuid("entityId").notNull().references(() => entityTable.id), + agentId: uuid("agentId").notNull().references(() => agentTable.id), + roomId: uuid("roomId").notNull().references(() => roomTable.id), + worldId: uuid("worldId").references(() => worldTable.id), + sourceEntityId: uuid("sourceEntityId").references(() => entityTable.id), + type: text("type").notNull(), + data: jsonb("data").default(sql`'{}'::jsonb`), + createdAt: numberTimestamp("createdAt") + .default(sql`now()`) + .notNull(), +}); \ No newline at end of file diff --git a/packages/plugin-sql/src/schema/index.ts b/packages/plugin-sql/src/schema/index.ts index e0c40b885aa..40359e8c356 100644 --- a/packages/plugin-sql/src/schema/index.ts +++ b/packages/plugin-sql/src/schema/index.ts @@ -1,12 +1,13 @@ -export { entityTable } from "./entity"; export { agentTable } from "./agent"; export { cacheTable } from "./cache"; export { characterTable } from "./character"; +export { componentTable } from "./component"; export { embeddingTable } from "./embedding"; +export { entityTable } from "./entity"; export { goalTable } from "./goal"; export { logTable } from "./log"; export { memoryTable } from "./memory"; export { participantTable } from "./participant"; export { relationshipTable } from "./relationship"; export { roomTable } from "./room"; -export { worldTable } from "./worldTable"; \ No newline at end of file +export { worldTable } from "./worldTable"; From 82b94c19d0f11c0d6ae71e120888347923d75d43 Mon Sep 17 00:00:00 2001 From: Shaw Date: Thu, 27 Feb 2025 02:08:21 -0800 Subject: [PATCH 04/13] redo migration, add components, getOrCreateUser --- packages/agent/src/index.ts | 1 - packages/agent/src/single-agent/character.ts | 1 - packages/agent/src/swarm/settings.ts | 11 +- packages/cli/src/commands/character.ts | 3 - packages/core/src/actions/roles.ts | 8 - packages/core/src/bootstrap.ts | 2 +- packages/core/src/generation.ts | 4 - packages/core/src/messages.ts | 13 +- packages/core/src/runtime.ts | 101 +++----- packages/core/src/types.ts | 11 +- packages/plugin-discord/src/index.ts | 12 +- ...33329_init.sql => 20250227085330_init.sql} | 32 ++- ...shot.json => 20250227085330_snapshot.json} | 236 ++++++++++++++---- .../drizzle/migrations/meta/_journal.json | 4 +- packages/plugin-sql/src/base.ts | 26 +- packages/plugin-twitter/src/base.ts | 12 +- packages/plugin-twitter/src/post.ts | 11 +- packages/plugin-twitter/src/sttTtsSpaces.ts | 10 +- 18 files changed, 322 insertions(+), 176 deletions(-) rename packages/plugin-sql/drizzle/migrations/{20250225233329_init.sql => 20250227085330_init.sql} (82%) rename packages/plugin-sql/drizzle/migrations/meta/{20250225233329_snapshot.json => 20250227085330_snapshot.json} (87%) diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index c1ad070ccd7..10598dbea3d 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -152,7 +152,6 @@ async function startAgent( let db: IDatabaseAdapter & IDatabaseCacheAdapter; try { character.id ??= stringToUuid(character.name); - character.username ??= character.name; const runtime: IAgentRuntime = await createAgent(character); diff --git a/packages/agent/src/single-agent/character.ts b/packages/agent/src/single-agent/character.ts index 8c768e1c367..3df3089b5a0 100644 --- a/packages/agent/src/single-agent/character.ts +++ b/packages/agent/src/single-agent/character.ts @@ -6,7 +6,6 @@ dotenv.config({ export const defaultCharacter: Character = { name: "Eliza", - username: "eliza", plugins: [ "@elizaos/plugin-anthropic", "@elizaos/plugin-openai", diff --git a/packages/agent/src/swarm/settings.ts b/packages/agent/src/swarm/settings.ts index a8744754333..442012d1ba0 100644 --- a/packages/agent/src/swarm/settings.ts +++ b/packages/agent/src/swarm/settings.ts @@ -165,9 +165,14 @@ export async function startOnboardingDM( await runtime.getOrCreateUser( runtime.agentId, - runtime.character.name, - runtime.character.name, - "discord" + [runtime.character.name], + { + default: { + name: runtime.character.name, + userName: runtime.character.name, + originalUserId: runtime.agentId, + }, + }, ); // Create memory of the initial message diff --git a/packages/cli/src/commands/character.ts b/packages/cli/src/commands/character.ts index c6d37e110ac..aa9bf942aef 100644 --- a/packages/cli/src/commands/character.ts +++ b/packages/cli/src/commands/character.ts @@ -24,7 +24,6 @@ import { withConnection } from "../utils/with-connection"; const characterSchema = z.object({ id: z.string().uuid().optional(), name: z.string(), - username: z.string(), plugins: z.array(z.string()).optional(), secrets: z.record(z.string(), z.string()).optional(), bio: z.array(z.string()).optional(), @@ -376,7 +375,6 @@ character.command("create") const charData = { ...getDefaultCharacterFields(), name: formData.name, - username: formData.username, bio: formData.bio, adjectives: formData.adjectives, postExamples: formData.postExamples, @@ -428,7 +426,6 @@ character.command("edit") const formData = await collectCharacterData({ name: existing.name, - username: existing.username, bio: Array.isArray(existing.bio) ? existing.bio : [existing.bio], adjectives: existing.adjectives || [], postExamples: existing.postExamples || [], diff --git a/packages/core/src/actions/roles.ts b/packages/core/src/actions/roles.ts index 11bd414c24a..8e7ac902dd6 100644 --- a/packages/core/src/actions/roles.ts +++ b/packages/core/src/actions/roles.ts @@ -107,16 +107,8 @@ const updateRoleAction: Action = { // Get requester ID and convert to UUID for consistent lookup const requesterId = message.userId; - const requesterUuid = stringToUuid(requesterId); - const requesterUuidCombined = stringToUuid(`${requesterId}-${runtime.agentId}`); const tenantSpecificUserId = runtime.generateTenantUserId(requesterId); - console.log('requesterId', requesterId); - console.log('requesterUuid', requesterUuid); - console.log('requesterUuidCombined', requesterUuidCombined); - console.log('tenantSpecificUserId', tenantSpecificUserId); - console.log("world.metadata.roles", world.metadata.roles) - // Get roles from world metadata if (!world.metadata?.roles) { logger.info(`No roles found for server ${serverId}`); diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index 825cb949397..abf887ddd75 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -609,7 +609,7 @@ const syncSingleUser = async ( await runtime.ensureConnection({ userId: user.id, roomId, - userName: user.username || `User${user.id}`, + userName: user.username || user.displayName || `User${user.id}`, userScreenName: user.displayName || user.username || `User${user.id}`, source, channelId, diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index 243b2654814..ddc49016da6 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -397,15 +397,11 @@ export async function generateMessageResponse({ stopSequences?: string[]; }): Promise { return await withRetry(async () => { - console.log('**** GENERATE MESSAGE RESPONSE') - console.log(context) const text = await runtime.useModel(modelClass, { runtime, context, stop: stopSequences, }); - console.log('**** GENERATE MESSAGE RESPONSE TEXT') - console.log(text) const parsedContent = parseJSONObjectFromText(text) as Content; diff --git a/packages/core/src/messages.ts b/packages/core/src/messages.ts index 2637eced5e8..ae1e8884be3 100644 --- a/packages/core/src/messages.ts +++ b/packages/core/src/messages.ts @@ -1,10 +1,4 @@ -import { v4 } from "uuid"; -import { composeContext } from "./context.ts"; -import { generateMessageResponse, generateShouldRespond } from "./generation.ts"; -import { logger } from "./logger.ts"; -import { messageCompletionFooter, shouldRespondFooter } from "./parsing.ts"; -import { ModelClass, type Actor, type Content, type HandlerCallback, type IAgentRuntime, type Memory, type State, type UUID } from "./types.ts"; -import { stringToUuid } from "./uuid.ts"; +import { type Actor, type Content, type IAgentRuntime, type Memory, type UUID } from "./types.ts"; export * as actions from "./actions"; export * as evaluators from "./evaluators"; export * as providers from "./providers"; @@ -20,9 +14,10 @@ export async function getActorDetails({ roomId: UUID; }) { const room = await runtime.getRoom(roomId); - const actors = (await runtime.databaseAdapter.getEntitiesForRoom(roomId, runtime.agentId)).map(entity => ({ + const entities = await runtime.databaseAdapter.getEntitiesForRoom(roomId, runtime.agentId); + const actors = entities.map(entity => ({ id: entity.id, - name: entity.metadata[room.source].name, + name: entity.metadata[room.source]?.name || entity.names[0], names: entity.names, })); diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index ec2b31c01d4..b4efea71133 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -419,27 +419,25 @@ export class AgentRuntime implements IAgentRuntime { async initialize() { // First create the agent entity directly try { + await this.ensureAgentExists(); + // No need to transform agent's own ID const agentEntity = await this.databaseAdapter.getEntityById( this.agentId, this.agentId ); + if (!agentEntity) { const created = await this.databaseAdapter.createEntity({ id: this.agentId, agentId: this.agentId, names: Array.from( new Set( - [this.character.name, this.character.username].filter(Boolean) + [this.character.name].filter(Boolean) ) ) as string[], metadata: { originalUserId: this.agentId, - default: { - name: this.character.name || "Agent", - username: - this.character.username || this.character.name || "Agent", - }, }, }); @@ -460,9 +458,6 @@ export class AgentRuntime implements IAgentRuntime { throw error; } - // Continue with agent setup - await this.ensureAgentExists(); - // Load plugins before trying to access models or services if (this.character.plugins) { const plugins = (await handlePluginImporting( @@ -895,9 +890,14 @@ export class AgentRuntime implements IAgentRuntime { */ async getOrCreateUser( userId: UUID, - userName: string | null, - name: string | null, - source: string | null + names: string[], + metadata: { + [source: string]: { + name: string; + userName: string; + originalUserId: UUID; + }; + } ) { // Generate tenant-specific user ID - apply the transformation const tenantSpecificUserId = this.generateTenantUserId(userId); @@ -910,31 +910,19 @@ export class AgentRuntime implements IAgentRuntime { const created = await this.databaseAdapter.createEntity({ id: tenantSpecificUserId, agentId: this.agentId, - names: Array.from( - new Set([name, userName].filter(Boolean)) - ) as string[], - metadata: { - default: { - name: name || "Unknown User", - username: userName || "Unknown", - }, - [source]: { - name: name || "Unknown User", - username: userName || "Unknown", - }, - originalUserId: userId, // Store original ID for reference - }, + names, + metadata, }); if (!created) { logger.error( - `Failed to create user ${userName} for agent ${this.agentId}.` + `Failed to create user ${name} for agent ${this.agentId}.` ); return null; } logger.success( - `User ${userName} created successfully for agent ${this.agentId}.` + `User ${name} created successfully for agent ${this.agentId}.` ); } @@ -950,42 +938,9 @@ export class AgentRuntime implements IAgentRuntime { tenantSpecificUserId, this.agentId ); - if (!entity) { - // get the room by room id - const room = await this.databaseAdapter.getRoom(roomId, this.agentId); - if (!room) { - throw new Error(`Room ${roomId} does not exist`); - } - - // get the source of the room - const source = room.source; - - // Create entity if it doesn't exist - const createdUserId = await this.getOrCreateUser( - userId, // Original ID will be transformed inside getOrCreateUser - userId === this.agentId - ? this.character.username || "Agent" - : `User${userId.substring(0, 8)}`, - userId === this.agentId - ? this.character.name || "Agent" - : `User${userId.substring(0, 8)}`, - source - ); - - if (!createdUserId) { - throw new Error(`Failed to create entity for user ${userId}`); - } - - // Verify the entity was created - const createdEntity = await this.databaseAdapter.getEntityById( - tenantSpecificUserId, - this.agentId - ); - if (!createdEntity) { - throw new Error(`Failed to create entity for user ${userId}`); - } + if(!entity) { + throw new Error(`User ${tenantSpecificUserId} not found`); } - // Get current participants const participants = await this.databaseAdapter.getParticipantsForRoom( roomId, @@ -1029,6 +984,7 @@ export class AgentRuntime implements IAgentRuntime { channelId, serverId, worldId, + originalUserId, }: { userId: UUID; roomId: UUID; @@ -1039,6 +995,7 @@ export class AgentRuntime implements IAgentRuntime { channelId?: string; serverId?: string; worldId?: UUID; + originalUserId?: UUID; }) { if (userId === this.agentId) { throw new Error("Agent should not connect to itself"); @@ -1048,12 +1005,20 @@ export class AgentRuntime implements IAgentRuntime { worldId = stringToUuid(`${serverId}-${this.agentId}`); } + const names = [userScreenName, userName] + const metadata = { + [source]: { + name: userScreenName, + userName: userName, + originalUserId, + }, + }; + // Get tenant-specific user ID and ensure the user exists const tenantSpecificUserId = await this.getOrCreateUser( userId, - userName ?? `User${userId}`, - userScreenName ?? `User${userId}`, - source + names, + metadata, ); if (!tenantSpecificUserId) { @@ -1219,10 +1184,6 @@ export class AgentRuntime implements IAgentRuntime { const actors = formatActors({ actors: actorsData ?? [] }); - console.log('**** ACTOR STATE') - console.log(actorsData) - console.log(actors) - const recentMessages = formatMessages({ messages: recentMessagesData, actors: actorsData, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 40a125dae48..f12dd25f966 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1107,9 +1107,14 @@ export interface IAgentRuntime { getOrCreateUser( userId: UUID, - userName: string | null, - name: string | null, - source: string | null + names: string[], + metadata: { + [source: string]: { + name: string; + userName: string; + [key: string]: unknown; + }; + } ): Promise; registerProvider(provider: Provider): void; diff --git a/packages/plugin-discord/src/index.ts b/packages/plugin-discord/src/index.ts index c216a2c1238..34c9d48b7d9 100644 --- a/packages/plugin-discord/src/index.ts +++ b/packages/plugin-discord/src/index.ts @@ -644,12 +644,12 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { }, discord: member.user.globalName ? { username: tag, - displayName: member.displayName || member.user.username, + name: member.displayName || member.user.username, globalName: member.user.globalName, userId: member.id, } : { username: tag, - displayName: member.displayName || member.user.username, + name: member.displayName || member.user.username, userId: member.id, }, }, @@ -686,12 +686,12 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { }, discord: member.user.globalName ? { username: tag, - displayName: member.displayName || member.user.username, + name: member.displayName || member.user.username, globalName: member.user.globalName, userId: member.id, } : { username: tag, - displayName: member.displayName || member.user.username, + name: member.displayName || member.user.username, userId: member.id, }, }, @@ -729,12 +729,12 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { }, discord: member.user.globalName ? { username: tag, - displayName: member.displayName || member.user.username, + name: member.displayName || member.user.username, globalName: member.user.globalName, userId: member.id, } : { username: tag, - displayName: member.displayName || member.user.username, + name: member.displayName || member.user.username, userId: member.id, }, }, diff --git a/packages/plugin-sql/drizzle/migrations/20250225233329_init.sql b/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql similarity index 82% rename from packages/plugin-sql/drizzle/migrations/20250225233329_init.sql rename to packages/plugin-sql/drizzle/migrations/20250227085330_init.sql index 520af6b144a..51a5f42bd5a 100644 --- a/packages/plugin-sql/drizzle/migrations/20250225233329_init.sql +++ b/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql @@ -8,7 +8,7 @@ CREATE TABLE "agents" ( CREATE TABLE "cache" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "key" text NOT NULL, - "agentId" text NOT NULL, + "agentId" uuid NOT NULL, "value" jsonb DEFAULT '{}'::jsonb, "createdAt" timestamptz DEFAULT now() NOT NULL, "expiresAt" timestamptz, @@ -33,6 +33,18 @@ CREATE TABLE "characters" ( "created_at" timestamptz DEFAULT now() ); --> statement-breakpoint +CREATE TABLE "components" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "entityId" uuid NOT NULL, + "agentId" uuid NOT NULL, + "roomId" uuid NOT NULL, + "worldId" uuid, + "sourceEntityId" uuid, + "type" text NOT NULL, + "data" jsonb DEFAULT '{}'::jsonb, + "createdAt" timestamptz DEFAULT now() NOT NULL +); +--> statement-breakpoint CREATE TABLE "embeddings" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "memory_id" uuid, @@ -118,8 +130,8 @@ CREATE TABLE "relationships" ( "entityA" uuid NOT NULL, "entityB" uuid NOT NULL, "agentId" uuid NOT NULL, - "status" text, - "userId" uuid NOT NULL + "tags" text[], + "metadata" jsonb ); --> statement-breakpoint CREATE TABLE "rooms" ( @@ -145,8 +157,15 @@ CREATE TABLE "worlds" ( ); --> statement-breakpoint ALTER TABLE "agents" ADD CONSTRAINT "agents_characterId_characters_id_fk" FOREIGN KEY ("characterId") REFERENCES "public"."characters"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "cache" ADD CONSTRAINT "cache_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "components" ADD CONSTRAINT "components_entityId_entities_id_fk" FOREIGN KEY ("entityId") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "components" ADD CONSTRAINT "components_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "components" ADD CONSTRAINT "components_roomId_rooms_id_fk" FOREIGN KEY ("roomId") REFERENCES "public"."rooms"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "components" ADD CONSTRAINT "components_worldId_worlds_id_fk" FOREIGN KEY ("worldId") REFERENCES "public"."worlds"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "components" ADD CONSTRAINT "components_sourceEntityId_entities_id_fk" FOREIGN KEY ("sourceEntityId") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "embeddings" ADD CONSTRAINT "embeddings_memory_id_memories_id_fk" FOREIGN KEY ("memory_id") REFERENCES "public"."memories"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "embeddings" ADD CONSTRAINT "fk_embedding_memory" FOREIGN KEY ("memory_id") REFERENCES "public"."memories"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entities" ADD CONSTRAINT "entities_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "goals" ADD CONSTRAINT "goals_userId_entities_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "goals" ADD CONSTRAINT "goals_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "goals" ADD CONSTRAINT "goals_roomId_rooms_id_fk" FOREIGN KEY ("roomId") REFERENCES "public"."rooms"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint @@ -167,15 +186,14 @@ ALTER TABLE "participants" ADD CONSTRAINT "participants_roomId_rooms_id_fk" FORE ALTER TABLE "participants" ADD CONSTRAINT "participants_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "participants" ADD CONSTRAINT "fk_room" FOREIGN KEY ("roomId") REFERENCES "public"."rooms"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "participants" ADD CONSTRAINT "fk_user" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userA_entities_id_fk" FOREIGN KEY ("entityA") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userB_entities_id_fk" FOREIGN KEY ("entityB") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "relationships_entityA_entities_id_fk" FOREIGN KEY ("entityA") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "relationships_entityB_entities_id_fk" FOREIGN KEY ("entityB") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "relationships" ADD CONSTRAINT "relationships_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "relationships_userId_entities_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_a" FOREIGN KEY ("entityA") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_b" FOREIGN KEY ("entityB") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "fk_user" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "rooms" ADD CONSTRAINT "rooms_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "rooms" ADD CONSTRAINT "rooms_worldId_worlds_id_fk" FOREIGN KEY ("worldId") REFERENCES "public"."worlds"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "worlds" ADD CONSTRAINT "worlds_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint CREATE INDEX "idx_embedding_memory" ON "embeddings" USING btree ("memory_id");--> statement-breakpoint CREATE INDEX "idx_memories_type_room" ON "memories" USING btree ("type","roomId");--> statement-breakpoint CREATE INDEX "idx_memories_metadata_type" ON "memories" USING btree (((metadata->>'type')));--> statement-breakpoint diff --git a/packages/plugin-sql/drizzle/migrations/meta/20250225233329_snapshot.json b/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json similarity index 87% rename from packages/plugin-sql/drizzle/migrations/meta/20250225233329_snapshot.json rename to packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json index 79119cee8e4..b92a42d9550 100644 --- a/packages/plugin-sql/drizzle/migrations/meta/20250225233329_snapshot.json +++ b/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json @@ -1,5 +1,5 @@ { - "id": "fe6cf216-3ff6-4d2c-866e-9f132bc12f13", + "id": "c737db6d-154c-40a5-bdf2-361303475c68", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -77,7 +77,7 @@ }, "agentId": { "name": "agentId", - "type": "text", + "type": "uuid", "primaryKey": false, "notNull": true }, @@ -103,7 +103,21 @@ } }, "indexes": {}, - "foreignKeys": {}, + "foreignKeys": { + "cache_agentId_agents_id_fk": { + "name": "cache_agentId_agents_id_fk", + "tableFrom": "cache", + "tableTo": "agents", + "columnsFrom": [ + "agentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, "compositePrimaryKeys": {}, "uniqueConstraints": { "cache_key_agent_unique": { @@ -233,6 +247,142 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.components": { + "name": "components", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "entityId": { + "name": "entityId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agentId": { + "name": "agentId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "roomId": { + "name": "roomId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "worldId": { + "name": "worldId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "sourceEntityId": { + "name": "sourceEntityId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamptz", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "components_entityId_entities_id_fk": { + "name": "components_entityId_entities_id_fk", + "tableFrom": "components", + "tableTo": "entities", + "columnsFrom": [ + "entityId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "components_agentId_agents_id_fk": { + "name": "components_agentId_agents_id_fk", + "tableFrom": "components", + "tableTo": "agents", + "columnsFrom": [ + "agentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "components_roomId_rooms_id_fk": { + "name": "components_roomId_rooms_id_fk", + "tableFrom": "components", + "tableTo": "rooms", + "columnsFrom": [ + "roomId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "components_worldId_worlds_id_fk": { + "name": "components_worldId_worlds_id_fk", + "tableFrom": "components", + "tableTo": "worlds", + "columnsFrom": [ + "worldId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "components_sourceEntityId_entities_id_fk": { + "name": "components_sourceEntityId_entities_id_fk", + "tableFrom": "components", + "tableTo": "entities", + "columnsFrom": [ + "sourceEntityId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.embeddings": { "name": "embeddings", "schema": "", @@ -389,7 +539,21 @@ } }, "indexes": {}, - "foreignKeys": {}, + "foreignKeys": { + "entities_agentId_agents_id_fk": { + "name": "entities_agentId_agents_id_fk", + "tableFrom": "entities", + "tableTo": "agents", + "columnsFrom": [ + "agentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, "compositePrimaryKeys": {}, "uniqueConstraints": { "id_agent_id_unique": { @@ -1060,17 +1224,17 @@ "primaryKey": false, "notNull": true }, - "status": { - "name": "status", - "type": "text", + "tags": { + "name": "tags", + "type": "text[]", "primaryKey": false, "notNull": false }, - "userId": { - "name": "userId", - "type": "uuid", + "metadata": { + "name": "metadata", + "type": "jsonb", "primaryKey": false, - "notNull": true + "notNull": false } }, "indexes": { @@ -1097,8 +1261,8 @@ } }, "foreignKeys": { - "relationships_userA_entities_id_fk": { - "name": "relationships_userA_entities_id_fk", + "relationships_entityA_entities_id_fk": { + "name": "relationships_entityA_entities_id_fk", "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ @@ -1110,8 +1274,8 @@ "onDelete": "no action", "onUpdate": "no action" }, - "relationships_userB_entities_id_fk": { - "name": "relationships_userB_entities_id_fk", + "relationships_entityB_entities_id_fk": { + "name": "relationships_entityB_entities_id_fk", "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ @@ -1136,19 +1300,6 @@ "onDelete": "no action", "onUpdate": "no action" }, - "relationships_userId_entities_id_fk": { - "name": "relationships_userId_entities_id_fk", - "tableFrom": "relationships", - "tableTo": "entities", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, "fk_user_a": { "name": "fk_user_a", "tableFrom": "relationships", @@ -1174,19 +1325,6 @@ ], "onDelete": "cascade", "onUpdate": "no action" - }, - "fk_user": { - "name": "fk_user", - "tableFrom": "relationships", - "tableTo": "entities", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" } }, "compositePrimaryKeys": {}, @@ -1341,7 +1479,21 @@ } }, "indexes": {}, - "foreignKeys": {}, + "foreignKeys": { + "worlds_agentId_agents_id_fk": { + "name": "worlds_agentId_agents_id_fk", + "tableFrom": "worlds", + "tableTo": "agents", + "columnsFrom": [ + "agentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, "compositePrimaryKeys": {}, "uniqueConstraints": {}, "policies": {}, diff --git a/packages/plugin-sql/drizzle/migrations/meta/_journal.json b/packages/plugin-sql/drizzle/migrations/meta/_journal.json index 7b971e9883e..91cc08634e2 100644 --- a/packages/plugin-sql/drizzle/migrations/meta/_journal.json +++ b/packages/plugin-sql/drizzle/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1740526409252, - "tag": "20250225233329_init", + "when": 1740646410010, + "tag": "20250227085330_init", "breakpoints": true } ] diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index 6f8f059a2d5..bd058f910a5 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -214,17 +214,31 @@ export abstract class BaseDrizzleAdapter async getEntityById(userId: UUID, agentId: UUID): Promise { return this.withDatabase(async () => { const result = await this.db - .select() + .select({ + entity: entityTable, + components: componentTable + }) .from(entityTable) - .leftJoin(componentTable, eq(entityTable.id, componentTable.entityId)) - .where(and(eq(entityTable.id, userId), eq(entityTable.agentId, agentId))) - .limit(1); + .leftJoin( + componentTable, + eq(componentTable.entityId, entityTable.id) + ) + .where( + and( + eq(entityTable.id, userId), + eq(entityTable.agentId, agentId) + ) + ); if (result.length === 0) return null; - const account = result[0]; + // Group components by entity + const entity = result[0].entity; + entity.components = result + .filter(row => row.components) + .map(row => row.components); - return account; + return entity; }); } diff --git a/packages/plugin-twitter/src/base.ts b/packages/plugin-twitter/src/base.ts index 275589652cd..75a1eb036e2 100644 --- a/packages/plugin-twitter/src/base.ts +++ b/packages/plugin-twitter/src/base.ts @@ -586,11 +586,15 @@ export class ClientBase extends EventEmitter { await this.runtime.getOrCreateUser( this.runtime.agentId, - this.profile.username, - this.runtime.character.name, - "twitter" + [this.runtime.character.name], + { + twitter: { + name: this.runtime.character.name, + userName: this.runtime.character.name, + originalUserId: this.runtime.agentId, + }, + } ); - // Save the new tweets as memories for (const tweet of tweetsToSave) { logger.log("Saving Tweet", tweet.id); diff --git a/packages/plugin-twitter/src/post.ts b/packages/plugin-twitter/src/post.ts index ccb4d0dfced..39bb20f138e 100644 --- a/packages/plugin-twitter/src/post.ts +++ b/packages/plugin-twitter/src/post.ts @@ -297,9 +297,14 @@ export class TwitterPostClient { ); await this.runtime.getOrCreateUser( this.runtime.agentId, - this.client.profile.username, - this.runtime.character.name, - "twitter" + [this.client.profile.username], + { + twitter: { + name: this.client.profile.username, + userName: this.client.profile.username, + originalUserId: this.runtime.agentId, + }, + } ); const topics = this.runtime.character.topics diff --git a/packages/plugin-twitter/src/sttTtsSpaces.ts b/packages/plugin-twitter/src/sttTtsSpaces.ts index 12550309e2d..e5dcccd66c0 100644 --- a/packages/plugin-twitter/src/sttTtsSpaces.ts +++ b/packages/plugin-twitter/src/sttTtsSpaces.ts @@ -353,9 +353,13 @@ export class SttTtsPlugin implements Plugin { // Ensure the user exists in the accounts table await this.runtime.getOrCreateUser( userUuid, - userId, // Use full Twitter ID as username - `Twitter User ${numericId}`, - "twitter", + [userId], + { + twitter: { + name: userId, + userName: userId, + }, + }, ); // Ensure room exists and user is in it From 4a2864432f3611b18d3de6f417ab4ddc8ce71036 Mon Sep 17 00:00:00 2001 From: Shaw Date: Thu, 27 Feb 2025 03:06:19 -0800 Subject: [PATCH 05/13] getComponent --- packages/core/src/actions/updateEntity.ts | 245 ++++++++++++++++++ packages/core/src/database.ts | 43 ++- packages/core/src/types.ts | 21 +- .../src/actions/summarizeConversation.ts | 2 - packages/plugin-sql/src/base.ts | 43 +++ 5 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/actions/updateEntity.ts diff --git a/packages/core/src/actions/updateEntity.ts b/packages/core/src/actions/updateEntity.ts new file mode 100644 index 00000000000..1ac928ed073 --- /dev/null +++ b/packages/core/src/actions/updateEntity.ts @@ -0,0 +1,245 @@ +// I want to create an action that lets anyone create or update a component for an entity. +// Specifically, they can edit the data to add data organized by source type (kind of like a namespace), like twitter, discord, etc. +// The action should first check if the component exists for the entity, and if not, create it. +// We want to use an LLM (runtime.useModel) to generate the component data. +// We should include the prior component data if it exists, and have the LLM output an update to the component. +// Aside from the source type, we should also have an array of names for the entity. +// Components need to store a worldId, entityId (for who it belongs to), agentId obv + +import { v4 as uuidv4 } from 'uuid'; +import { logger } from "../logger"; +import { + Action, + ActionExample, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + UUID +} from "../types"; +import { composeContext } from "../context"; + +interface EntityComponentData { + names: string[]; + sources: { + [sourceType: string]: any; + }; +} + +const componentGenerationTemplate = `# Task: Update Entity Component Data + +{{#if existingData}} +# Existing Component Data: +\`\`\`json +{{existingData}} +\`\`\` +{{/if}} + +# Recent Messages: +{{recentMessages}} + +# Instructions: +Generate updated entity component data based on the conversation. The component should: +1. Include an array of names that can be used to refer to this entity +2. Have data organized by source type (e.g., twitter, discord, etc.) +3. Preserve existing data when appropriate +4. Add or update information based on the new conversation + +Return a valid JSON object with the following structure: +{ + "names": ["name1", "name2", ...], + "sources": { + "sourceType1": { /* relevant data */ }, + "sourceType2": { /* relevant data */ }, + ... + } +} + +Ensure that the output is valid JSON.`; + +export const updateEntityAction: Action = { + name: "UPDATE_ENTITY", + similes: ["CREATE_ENTITY", "EDIT_ENTITY", "UPDATE_COMPONENT", "CREATE_COMPONENT"], + description: "Creates or updates a component for an entity with data organized by source type (like twitter, discord, etc.)", + + validate: async ( + runtime: IAgentRuntime, + message: Memory, + _state: State + ): Promise => { + // Check if the message mentions entity data that could be updated + const text = message.content.text.toLowerCase(); + const entityUpdateKeywords = [ + "profile", "information", "data", "details", "about", + "update", "store", "save", "record", "component" + ]; + + return entityUpdateKeywords.some(keyword => text.includes(keyword)); + }, + + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback: HandlerCallback, + responses: Memory[] + ): Promise => { + try { + // Handle initial responses + for (const response of responses) { + await callback(response.content); + } + + const entityId = message.userId; + const worldId = message.roomId; + const agentId = runtime.agentId; + + // First, check if the component exists for the entity + const entityComponent = await runtime.databaseAdapter.getComponent(entityId, "entityData", worldId, agentId); + let existingData: EntityComponentData | null = null; + + if (entityComponent) { + existingData = entityComponent.data as EntityComponentData; + logger.info(`Found existing entity component for ${entityId}`); + } else { + logger.info(`No existing entity component found for ${entityId}, will create new one`); + } + + // Generate updated component data using LLM + const context = composeContext({ + state: { + ...state, + existingData: existingData ? JSON.stringify(existingData, null, 2) : null, + }, + template: componentGenerationTemplate, + }); + + const generatedDataText = await runtime.useModel(ModelClass.TEXT_LARGE, { + context, + stopSequences: ["}"] + }); + + // Parse the generated data + let componentData: EntityComponentData; + try { + // Find the JSON object in the response + const jsonMatch = generatedDataText.match(/\{[\s\S]*\}/); + if (!jsonMatch) { + throw new Error("No valid JSON found in the LLM response"); + } + + componentData = JSON.parse(jsonMatch[0]); + + // Validate the structure + if (!componentData.names || !Array.isArray(componentData.names)) { + componentData.names = (existingData?.names || [message.content.user || "Unknown"]) as string[]; + } + + if (!componentData.sources || typeof componentData.sources !== 'object') { + componentData.sources = existingData?.sources || {}; + } + } catch (error) { + logger.error(`Failed to parse component data: ${error.message}`); + await callback({ + text: "I couldn't properly generate the entity data. Please try again with more specific information.", + action: "UPDATE_ENTITY_ERROR", + source: message.content.source, + }); + return; + } + + // Create or update the component + if (entityComponent) { + // Update existing component + await runtime.databaseAdapter.updateComponent({ + entityId: entityId as UUID, + worldId: worldId as UUID, + type: "entityData", + data: componentData, + id: entityComponent.id, + agentId, + roomId: message.roomId, + sourceEntityId: message.userId + }); + } else { + // Create new component + await runtime.databaseAdapter.createComponent({ + id: uuidv4() as UUID, + worldId: worldId as UUID, + entityId: entityId as UUID, + agentId, + type: "entityData", + data: componentData, + roomId: message.roomId, + sourceEntityId: message.userId + }); + } + + // Confirm the update to the user + await callback({ + text: `I've updated your entity information with the new details.`, + action: "UPDATE_ENTITY", + source: message.content.source, + }); + } catch (error) { + logger.error(`Error in updateEntity handler: ${error}`); + await callback({ + text: "There was an error processing your entity information.", + action: "UPDATE_ENTITY_ERROR", + source: message.content.source, + }); + } + }, + + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Please update my profile with my Twitter handle @codehacker and my Discord username dev_guru#1234", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your entity information with your Twitter and Discord details.", + action: "UPDATE_ENTITY", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Can you store that I'm a software engineer at TechCorp and I like hiking on weekends?", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've saved your profession and hobby information to your profile.", + action: "UPDATE_ENTITY", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Add to my LinkedIn data that I graduated from MIT in 2018 with a Computer Science degree", + }, + }, + { + user: "{{user2}}", + content: { + text: "I've updated your LinkedIn information with your education details.", + action: "UPDATE_ENTITY", + }, + }, + ], + ] as ActionExample[][], +}; + +export default updateEntityAction; \ No newline at end of file diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index e98083cd20d..d3f7966ab40 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -12,7 +12,8 @@ import type { RoomData, UUID, WorldData, - Agent + Agent, + Component } from "./types.ts"; /** @@ -66,6 +67,46 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { */ abstract updateEntity(entity: Entity): Promise; + /** + * Retrieves a single component by entity ID and type. + * @param entityId The UUID of the entity the component belongs to + * @param type The type identifier for the component + * @param worldId Optional UUID of the world the component belongs to + * @param sourceEntityId Optional UUID of the source entity + * @returns Promise resolving to the Component if found, null otherwise + */ + abstract getComponent(entityId: UUID, type: string, worldId?: UUID, sourceEntityId?: UUID): Promise; + + /** + * Retrieves all components for an entity. + * @param entityId The UUID of the entity to get components for + * @param worldId Optional UUID of the world to filter components by + * @param sourceEntityId Optional UUID of the source entity to filter by + * @returns Promise resolving to array of Component objects + */ + abstract getComponents(entityId: UUID, worldId?: UUID, sourceEntityId?: UUID): Promise; + + /** + * Creates a new component in the database. + * @param component The component object to create + * @returns Promise resolving to true if creation was successful + */ + abstract createComponent(component: Component): Promise; + + /** + * Updates an existing component in the database. + * @param component The component object with updated properties + * @returns Promise that resolves when the update is complete + */ + abstract updateComponent(component: Component): Promise; + + /** + * Deletes a component from the database. + * @param componentId The UUID of the component to delete + * @returns Promise that resolves when the deletion is complete + */ + abstract deleteComponent(componentId: UUID): Promise; + /** * Retrieves memories based on the specified parameters. * @param params An object containing parameters for the memory retrieval. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f12dd25f966..2b0b4f64860 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -499,7 +499,11 @@ export interface Relationship { export interface Component { id: UUID; entityId: UUID; - name: string; + agentId: UUID; + roomId: UUID; + worldId: UUID; + sourceEntityId: UUID; + type: string; data: { [key: string]: any; }; @@ -781,6 +785,21 @@ export interface IDatabaseAdapter { /** Update entity */ updateEntity(entity: Entity): Promise; + /** Get component by ID */ + getComponent(entityId: UUID, type: string, worldId?: UUID, sourceEntityId?: UUID): Promise; + + /** Get all components for an entity */ + getComponents(entityId: UUID, worldId?: UUID, sourceEntityId?: UUID): Promise; + + /** Create component */ + createComponent(component: Component): Promise; + + /** Update component */ + updateComponent(component: Component): Promise; + + /** Delete component */ + deleteComponent(componentId: UUID): Promise; + /** Get memories matching criteria */ getMemories(params: { roomId: UUID; diff --git a/packages/plugin-discord/src/actions/summarizeConversation.ts b/packages/plugin-discord/src/actions/summarizeConversation.ts index 143c24aab59..e1089773a7f 100644 --- a/packages/plugin-discord/src/actions/summarizeConversation.ts +++ b/packages/plugin-discord/src/actions/summarizeConversation.ts @@ -219,8 +219,6 @@ const summarizeAction = { return; } - console.log("dateRange", dateRange); - const { objective, start, end } = dateRange; // 2. get these memories from the database diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index bd058f910a5..e9755affd1c 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -1,6 +1,7 @@ import { Actor, Agent, + Component, DatabaseAdapter, logger, type Character, @@ -291,6 +292,48 @@ export abstract class BaseDrizzleAdapter }); } + async getComponent(entityId: UUID, type: string, worldId?: UUID, sourceEntityId?: UUID): Promise { + return this.withDatabase(async () => { + const conditions = [ + eq(componentTable.entityId, entityId), + eq(componentTable.type, type) + ]; + + if (worldId) { + conditions.push(eq(componentTable.worldId, worldId)); + } + + if (sourceEntityId) { + conditions.push(eq(componentTable.sourceEntityId, sourceEntityId)); + } + + const result = await this.db + .select() + .from(componentTable) + .where(and(...conditions)); + return result.length > 0 ? result[0] : null; + }); + } + + async createComponent(component: Component): Promise { + return this.withDatabase(async () => { + await this.db.insert(componentTable).values(component); + return true; + }); + } + + async updateComponent(component: Component): Promise { + return this.withDatabase(async () => { + await this.db.update(componentTable).set(component).where(eq(componentTable.id, component.id)); + }); + } + + async deleteComponent(componentId: UUID): Promise { + return this.withDatabase(async () => { + await this.db.delete(componentTable).where(eq(componentTable.id, componentId)); + }); + } + async getMemories(params: { roomId: UUID; count?: number; From 5d0c9cbdbcb6b2a94f5f74312df04979cb8665b8 Mon Sep 17 00:00:00 2001 From: Shaw Date: Thu, 27 Feb 2025 18:24:08 -0800 Subject: [PATCH 06/13] get entity details + add components, draft out findEntityByName --- packages/core/src/actions/updateEntity.ts | 249 ++++++++++++++-------- packages/core/src/bootstrap.ts | 7 +- packages/core/src/database.ts | 9 +- packages/core/src/entities.ts | 168 +++++++++++++++ packages/core/src/index.ts | 1 + packages/core/src/messages.ts | 34 ++- packages/core/src/runtime.ts | 2 +- packages/core/src/types.ts | 10 +- packages/plugin-sql/src/base.ts | 123 ++++------- 9 files changed, 406 insertions(+), 197 deletions(-) create mode 100644 packages/core/src/entities.ts diff --git a/packages/core/src/actions/updateEntity.ts b/packages/core/src/actions/updateEntity.ts index 1ac928ed073..2471c87e48c 100644 --- a/packages/core/src/actions/updateEntity.ts +++ b/packages/core/src/actions/updateEntity.ts @@ -1,34 +1,69 @@ // I want to create an action that lets anyone create or update a component for an entity. -// Specifically, they can edit the data to add data organized by source type (kind of like a namespace), like twitter, discord, etc. +// Components represent different sources of data about an entity (telegram, twitter, etc) +// Sources can be registered by plugins or inferred from room context and available components // The action should first check if the component exists for the entity, and if not, create it. // We want to use an LLM (runtime.useModel) to generate the component data. // We should include the prior component data if it exists, and have the LLM output an update to the component. -// Aside from the source type, we should also have an array of names for the entity. -// Components need to store a worldId, entityId (for who it belongs to), agentId obv +// sourceEntityId represents who is making the update, entityId is who they are talking about import { v4 as uuidv4 } from 'uuid'; import { logger } from "../logger"; import { Action, ActionExample, + Component, HandlerCallback, IAgentRuntime, Memory, ModelClass, State, - UUID + UUID, + Entity } from "../types"; import { composeContext } from "../context"; +import { findEntityByName } from "../entities"; +import { parseJSONObjectFromText } from "../parsing"; -interface EntityComponentData { - names: string[]; - sources: { - [sourceType: string]: any; - }; +const sourceExtractionTemplate = `# Task: Extract Source and Entity Information + +# Recent Messages: +{{recentMessages}} + +# Instructions: +Analyze the conversation to identify: +1. The source/platform being referenced (e.g. telegram, twitter, discord) +2. Any specific component data being shared (e.g. username, handle, display name) + +Return a JSON object with: +{ + "source": "platform-name", + "data": { + // Relevant fields for that platform + // e.g. username, handle, displayName, etc. + } +} + +Example outputs: +1. For "my telegram username is @dev_guru": +{ + "source": "telegram", + "data": { + "username": "dev_guru" + } } +2. For "update my twitter handle to @tech_master": +{ + "source": "twitter", + "data": { + "handle": "tech_master" + } +}`; + const componentGenerationTemplate = `# Task: Update Entity Component Data +Component Type: {{componentType}} + {{#if existingData}} # Existing Component Data: \`\`\`json @@ -36,46 +71,51 @@ const componentGenerationTemplate = `# Task: Update Entity Component Data \`\`\` {{/if}} -# Recent Messages: -{{recentMessages}} +# New Information: +\`\`\`json +{{newData}} +\`\`\` # Instructions: -Generate updated entity component data based on the conversation. The component should: -1. Include an array of names that can be used to refer to this entity -2. Have data organized by source type (e.g., twitter, discord, etc.) -3. Preserve existing data when appropriate -4. Add or update information based on the new conversation +Generate updated component data for the {{componentType}} component. The data should: +1. Be specific to the {{componentType}} platform/source +2. Preserve existing data when appropriate +3. Merge new information with existing data +4. Return only valid data for this component type -Return a valid JSON object with the following structure: -{ - "names": ["name1", "name2", ...], - "sources": { - "sourceType1": { /* relevant data */ }, - "sourceType2": { /* relevant data */ }, - ... - } -} +Return a valid JSON object with data relevant to {{componentType}}. +For example: +- telegram: username, display_name +- twitter: handle, display_name +- discord: username, display_name, discriminator -Ensure that the output is valid JSON.`; +Ensure the output is valid JSON and contains ONLY fields relevant to {{componentType}}.`; export const updateEntityAction: Action = { name: "UPDATE_ENTITY", similes: ["CREATE_ENTITY", "EDIT_ENTITY", "UPDATE_COMPONENT", "CREATE_COMPONENT"], - description: "Creates or updates a component for an entity with data organized by source type (like twitter, discord, etc.)", + description: "Creates or updates components for entities with data organized by source type (like twitter, discord, etc.)", validate: async ( runtime: IAgentRuntime, message: Memory, - _state: State + state: State ): Promise => { - // Check if the message mentions entity data that could be updated - const text = message.content.text.toLowerCase(); - const entityUpdateKeywords = [ - "profile", "information", "data", "details", "about", - "update", "store", "save", "record", "component" - ]; + // Check if we have any registered sources or existing components that could be updated + const worldId = message.roomId; + const agentId = runtime.agentId; - return entityUpdateKeywords.some(keyword => text.includes(keyword)); + // Get all components for the current room to understand available sources + const roomComponents = await runtime.databaseAdapter.getComponents(message.roomId, worldId, agentId); + + // Get source types from room components + const availableSources = new Set(roomComponents.map(c => c.type)); + + // TODO: Add ability for plugins to register their sources + // const registeredSources = runtime.getRegisteredSources?.() || []; + // availableSources.add(...registeredSources); + + return availableSources.size > 0; }, handler: async ( @@ -92,26 +132,62 @@ export const updateEntityAction: Action = { await callback(response.content); } - const entityId = message.userId; - const worldId = message.roomId; + const sourceEntityId = message.userId; + const roomId = message.roomId; const agentId = runtime.agentId; + const room = await runtime.getRoom(roomId); + const worldId = room.worldId; + + // First, find the entity being referenced + const entity = await findEntityByName(runtime, message.content.text, message, state); - // First, check if the component exists for the entity - const entityComponent = await runtime.databaseAdapter.getComponent(entityId, "entityData", worldId, agentId); - let existingData: EntityComponentData | null = null; - - if (entityComponent) { - existingData = entityComponent.data as EntityComponentData; - logger.info(`Found existing entity component for ${entityId}`); - } else { - logger.info(`No existing entity component found for ${entityId}, will create new one`); + if (!entity) { + await callback({ + text: "I'm not sure which entity you're trying to update. Could you please specify who you're talking about?", + action: "UPDATE_ENTITY_ERROR", + source: message.content.source, + }); + return; } + + // Extract source and component data from the message + const sourceContext = composeContext({ + state, + template: sourceExtractionTemplate, + }); + + const sourceResult = await runtime.useModel(ModelClass.TEXT_LARGE, { + context: sourceContext, + stopSequences: ["}"] + }); + + const sourceData = parseJSONObjectFromText(sourceResult); + if (!sourceData?.source) { + await callback({ + text: "I couldn't determine what information you want to update. Could you please specify the platform (like telegram, twitter, etc.) and the information you want to update?", + action: "UPDATE_ENTITY_ERROR", + source: message.content.source, + }); + return; + } + + const componentType = sourceData.source.toLowerCase(); - // Generate updated component data using LLM + // Get existing component if it exists + const existingComponent = await runtime.databaseAdapter.getComponent( + entity.id!, + componentType, + worldId, + sourceEntityId + ); + + // Generate updated component data const context = composeContext({ state: { ...state, - existingData: existingData ? JSON.stringify(existingData, null, 2) : null, + componentType, + existingData: existingComponent ? JSON.stringify(existingComponent.data, null, 2) : null, + newData: JSON.stringify(sourceData.data, null, 2), }, template: componentGenerationTemplate, }); @@ -122,28 +198,18 @@ export const updateEntityAction: Action = { }); // Parse the generated data - let componentData: EntityComponentData; + let componentData: any; try { - // Find the JSON object in the response const jsonMatch = generatedDataText.match(/\{[\s\S]*\}/); if (!jsonMatch) { throw new Error("No valid JSON found in the LLM response"); } componentData = JSON.parse(jsonMatch[0]); - - // Validate the structure - if (!componentData.names || !Array.isArray(componentData.names)) { - componentData.names = (existingData?.names || [message.content.user || "Unknown"]) as string[]; - } - - if (!componentData.sources || typeof componentData.sources !== 'object') { - componentData.sources = existingData?.sources || {}; - } } catch (error) { logger.error(`Failed to parse component data: ${error.message}`); await callback({ - text: "I couldn't properly generate the entity data. Please try again with more specific information.", + text: "I couldn't properly generate the component data. Please try again with more specific information.", action: "UPDATE_ENTITY_ERROR", source: message.content.source, }); @@ -151,42 +217,45 @@ export const updateEntityAction: Action = { } // Create or update the component - if (entityComponent) { - // Update existing component + if (existingComponent) { await runtime.databaseAdapter.updateComponent({ - entityId: entityId as UUID, - worldId: worldId as UUID, - type: "entityData", - data: componentData, - id: entityComponent.id, - agentId, - roomId: message.roomId, - sourceEntityId: message.userId + id: existingComponent.id, + entityId: entity.id!, + worldId, + type: componentType, + data: componentData, + agentId, + roomId: message.roomId, + sourceEntityId + }); + + await callback({ + text: `I've updated the ${componentType} information for ${entity.names[0]}.`, + action: "UPDATE_ENTITY", + source: message.content.source, }); } else { - // Create new component await runtime.databaseAdapter.createComponent({ id: uuidv4() as UUID, - worldId: worldId as UUID, - entityId: entityId as UUID, - agentId, - type: "entityData", + entityId: entity.id!, + worldId, + type: componentType, data: componentData, + agentId, roomId: message.roomId, - sourceEntityId: message.userId + sourceEntityId + }); + + await callback({ + text: `I've added new ${componentType} information for ${entity.names[0]}.`, + action: "UPDATE_ENTITY", + source: message.content.source, }); } - - // Confirm the update to the user - await callback({ - text: `I've updated your entity information with the new details.`, - action: "UPDATE_ENTITY", - source: message.content.source, - }); } catch (error) { logger.error(`Error in updateEntity handler: ${error}`); await callback({ - text: "There was an error processing your entity information.", + text: "There was an error processing the entity information.", action: "UPDATE_ENTITY_ERROR", source: message.content.source, }); @@ -198,13 +267,13 @@ export const updateEntityAction: Action = { { user: "{{user1}}", content: { - text: "Please update my profile with my Twitter handle @codehacker and my Discord username dev_guru#1234", + text: "Please update my telegram username to @dev_guru", }, }, { user: "{{user2}}", content: { - text: "I've updated your entity information with your Twitter and Discord details.", + text: "I've updated your telegram information.", action: "UPDATE_ENTITY", }, }, @@ -213,13 +282,13 @@ export const updateEntityAction: Action = { { user: "{{user1}}", content: { - text: "Can you store that I'm a software engineer at TechCorp and I like hiking on weekends?", + text: "Set Jimmy's twitter handle to @jimmy_codes", }, }, { user: "{{user2}}", content: { - text: "I've saved your profession and hobby information to your profile.", + text: "I've updated Jimmy's twitter information.", action: "UPDATE_ENTITY", }, }, @@ -228,13 +297,13 @@ export const updateEntityAction: Action = { { user: "{{user1}}", content: { - text: "Add to my LinkedIn data that I graduated from MIT in 2018 with a Computer Science degree", + text: "Update my discord username to dev_guru#1234", }, }, { user: "{{user2}}", content: { - text: "I've updated your LinkedIn information with your education details.", + text: "I've updated your discord information.", action: "UPDATE_ENTITY", }, }, diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index abf887ddd75..b6621e1c610 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -1,11 +1,11 @@ import { UUID } from "crypto"; import { v4 } from "uuid"; import { cancelTaskAction } from "./actions/cancel.ts"; -import { selectOptionAction } from "./actions/options.ts"; import { followRoomAction } from "./actions/followRoom.ts"; import { ignoreAction } from "./actions/ignore.ts"; import { muteRoomAction } from "./actions/muteRoom.ts"; import { noneAction } from "./actions/none.ts"; +import { selectOptionAction } from "./actions/options.ts"; import updateRoleAction from "./actions/roles.ts"; import updateSettingsAction from "./actions/settings.ts"; import { unfollowRoomAction } from "./actions/unfollowRoom.ts"; @@ -14,16 +14,15 @@ import { composeContext } from "./context.ts"; import { factEvaluator } from "./evaluators/fact.ts"; import { goalEvaluator } from "./evaluators/goal.ts"; import { - formatActors, formatMessages, generateMessageResponse, generateShouldRespond, - getActorDetails, + getActorDetails } from "./index.ts"; import { logger } from "./logger.ts"; import { messageCompletionFooter, shouldRespondFooter } from "./parsing.ts"; -import { optionsProvider } from "./providers/options.ts"; import { factsProvider } from "./providers/facts.ts"; +import { optionsProvider } from "./providers/options.ts"; import { roleProvider } from "./providers/roles.ts"; import { settingsProvider } from "./providers/settings.ts"; import { timeProvider } from "./providers/time.ts"; diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index d3f7966ab40..9a57665b171 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -45,7 +45,7 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { */ abstract getEntityById(userId: UUID, agentId: UUID): Promise; - abstract getEntitiesForRoom(roomId: UUID, agentId: UUID): Promise; + abstract getEntitiesForRoom(roomId: UUID, agentId: UUID, includeComponents?: boolean): Promise; abstract getAgent(agentId: UUID): Promise; @@ -178,13 +178,6 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { type: string; }): Promise; - /** - * Retrieves details of actors in a given room. - * @param params An object containing the roomId to search for actors. - * @returns A Promise that resolves to an array of Actor objects. - */ - abstract getActorDetails(params: { roomId: UUID, agentId: UUID }): Promise; - /** * Searches for memories based on embeddings and other specified parameters. * @param params An object containing parameters for the memory search. diff --git a/packages/core/src/entities.ts b/packages/core/src/entities.ts new file mode 100644 index 00000000000..0f5d0a5c0a0 --- /dev/null +++ b/packages/core/src/entities.ts @@ -0,0 +1,168 @@ +import { composeContext } from "./context.ts"; +import { logger } from "./index.ts"; +import { parseJSONObjectFromText } from "./parsing"; +import { + type Entity, + type IAgentRuntime, + type Memory, + ModelClass, + type State, + type UUID +} from "./types.ts"; + +const entityResolutionTemplate = `# Task: Resolve Entity Name + +# Examples: +1. Query: "me" + Result: { + "entityId": "user-123", + "type": "EXACT_MATCH", + "matches": [{ + "name": "Alice", + "reason": "Message sender referring to themselves" + }] + } + +2. Query: "you" + Result: { + "entityId": "agent-456", + "type": "EXACT_MATCH", + "matches": [{ + "name": "Assistant", + "reason": "Direct reference to the agent" + }] + } + +3. Query: "@username" + Result: { + "entityId": null, + "type": "USERNAME_MATCH", + "matches": [{ + "name": "username", + "reason": "Exact match with user's handle" + }] + } + +4. Query: "John" + Result: { + "entityId": null, + "type": "NAME_MATCH", + "matches": [{ + "name": "John Smith", + "reason": "Name matches entity's display name" + }] + } + +Current Room: {{roomName}} +Current World: {{worldName}} +Message Sender: {{senderName}} (ID: {{userId}}) +Agent: {{agentName}} (ID: {{agentId}}) + +# Entities in Room: +{{#if entitiesInRoom}} +{{entitiesInRoom}} +{{/if}} + +# Recent Messages: +{{recentMessages}} + +# Query: +{{query}} + +# Instructions: +1. Analyze the query and context to identify which entity is being referenced +2. Consider special references like "me" (message sender) or "you" (agent) +3. Look for usernames/handles in standard formats (e.g. @username, user#1234) +4. Consider context from recent messages for pronouns and references +5. If multiple matches exist, use context to disambiguate + +Return a JSON object with: +{ + "entityId": "exact-id-if-known-otherwise-null", + "type": "EXACT_MATCH | USERNAME_MATCH | NAME_MATCH | AMBIGUOUS | UNKNOWN", + "matches": [{ + "name": "matched-name", + "reason": "why this entity matches" + }] +}`; + +export async function findEntityByName( + runtime: IAgentRuntime, + query: string, + message: Memory, + state: State, + options: { + worldId?: UUID; + } = {} +): Promise { + try { + const room = await runtime.getRoom(message.roomId); + if (!room) { + logger.warn("Room not found for entity search"); + return null; + } + + const world = room.worldId ? await runtime.getWorld(room.worldId) : null; + + // Get all entities in the room with their components + const entitiesInRoom = await runtime.databaseAdapter.getEntitiesForRoom(room.id, runtime.agentId, true); + + // Compose context for LLM + const context = composeContext({ + state: { + ...state, + roomName: room.name || room.id, + worldName: world?.name || "Unknown", + entitiesInRoom: JSON.stringify(entitiesInRoom, null, 2), + userId: message.userId, + query + }, + template: entityResolutionTemplate + }); + + // Use LLM to analyze and resolve the entity + const result = await runtime.useModel(ModelClass.TEXT_LARGE, { + context, + stopSequences: ["}"] + }); + + // Parse LLM response + const resolution = parseJSONObjectFromText(result); + if (!resolution) { + logger.warn("Failed to parse entity resolution result"); + return null; + } + + // If we got an exact entity ID match + if (resolution.type === "EXACT_MATCH" && resolution.entityId) { + const entity = await runtime.databaseAdapter.getEntityById(resolution.entityId as UUID, runtime.agentId); + if (entity) return entity; + } + + // For username/name matches, search through formatted entities + if (resolution.matches?.[0]?.name) { + const matchName = resolution.matches[0].name.toLowerCase(); + + // Find matching entity by username/handle in components or by name + const matchingEntity = entitiesInRoom.find(entity => { + // Check names + if (entity.names.some(n => n.toLowerCase() === matchName)) return true; + + // Check components for username/handle match + return entity.components?.some(c => + c.data.username?.toLowerCase() === matchName || + c.data.handle?.toLowerCase() === matchName + ); + }); + + if (matchingEntity) { + return matchingEntity; + } + } + + return null; + } catch (error) { + logger.error("Error in findEntityByName:", error); + return null; + } +} \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2792a7188a6..db0ec42a4eb 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,6 +7,7 @@ export * from "./cache.ts"; export * from "./context.ts"; export * from "./database.ts"; export * from "./environment.ts"; +export * from "./entities.ts"; export * from "./evaluators.ts"; export * from "./generation.ts"; export * from "./goals.ts"; diff --git a/packages/core/src/messages.ts b/packages/core/src/messages.ts index ae1e8884be3..dba7c4013ad 100644 --- a/packages/core/src/messages.ts +++ b/packages/core/src/messages.ts @@ -14,12 +14,32 @@ export async function getActorDetails({ roomId: UUID; }) { const room = await runtime.getRoom(roomId); - const entities = await runtime.databaseAdapter.getEntitiesForRoom(roomId, runtime.agentId); - const actors = entities.map(entity => ({ - id: entity.id, - name: entity.metadata[room.source]?.name || entity.names[0], - names: entity.names, - })); + const entities = await runtime.databaseAdapter.getEntitiesForRoom(roomId, runtime.agentId, true); + const actors = entities.map(entity => { + // join all fields of all component.data together + const allData = entity.components.reduce((acc, component) => { + return { ...acc, ...component.data }; + }, {}); + + // combine arrays and merge the values of objects + const mergedData = Object.entries(allData).reduce((acc, [key, value]) => { + if (!acc[key]) { + acc[key] = value; + } else if (Array.isArray(acc[key]) && Array.isArray(value)) { + acc[key] = [...new Set([...acc[key], ...value])]; + } else if (typeof acc[key] === 'object' && typeof value === 'object') { + acc[key] = { ...acc[key], ...value }; + } + return acc; + }, {}); + + return { + id: entity.id, + name: entity.metadata[room.source]?.name || entity.names[0], + names: entity.names, + data: JSON.stringify(mergedData) + }; + }); // Filter out nulls and ensure uniqueness by ID const uniqueActors = new Map(); @@ -41,7 +61,7 @@ export async function getActorDetails({ */ export function formatActors({ actors }: { actors: Actor[] }) { const actorStrings = actors.map((actor: Actor) => { - const header = `${actor.name} (${actor.names.join(" aka ")})`; + const header = `${actor.name} (${actor.names.join(" aka ")})\nData: ${actor.data}`; return header; }); const finalActorStrings = actorStrings.join("\n"); diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index b4efea71133..2ae8ae03dd9 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -49,7 +49,7 @@ import { type State, type Task, type UUID, - type WorldData, + type WorldData } from "./types.ts"; import { stringToUuid } from "./uuid.ts"; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2b0b4f64860..d4edf0f628c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -63,6 +63,9 @@ export interface Actor { /** All names for the actor */ names: string[]; + + /** Arbitrary data which can be displayed */ + data: any; } /** @@ -524,6 +527,9 @@ export interface Entity { /** Agent ID this account is related to, for agents should be themselves */ agentId: UUID; + + /** Optional array of components */ + components?: Component[]; } /** @@ -777,7 +783,7 @@ export interface IDatabaseAdapter { getEntityById(userId: UUID, agentId: UUID): Promise; /** Get entities for room */ - getEntitiesForRoom(roomId: UUID, agentId: UUID): Promise; + getEntitiesForRoom(roomId: UUID, agentId: UUID, includeComponents?: boolean): Promise; /** Create new entity */ createEntity(entity: Entity): Promise; @@ -838,8 +844,6 @@ export interface IDatabaseAdapter { type: string; }): Promise; - getActorDetails(params: { roomId: UUID }): Promise; - updateGoalStatus(params: { goalId: UUID; status: GoalStatus }): Promise; searchMemories(params: { diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index e9755affd1c..20a8273c676 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -243,11 +243,12 @@ export abstract class BaseDrizzleAdapter }); } - async getEntitiesForRoom(roomId: UUID, agentId: UUID): Promise { + async getEntitiesForRoom(roomId: UUID, agentId: UUID, includeComponents?: boolean): Promise { return this.withDatabase(async () => { - const result = await this.db + const query = this.db .select({ - entity: entityTable + entity: entityTable, + ...(includeComponents && { components: componentTable }) }) .from(participantTable) .leftJoin( @@ -256,10 +257,42 @@ export abstract class BaseDrizzleAdapter eq(participantTable.userId, entityTable.id), eq(entityTable.agentId, agentId) ) - ) - .where(eq(participantTable.roomId, roomId)); + ); - return result.map(row => row.entity).filter(Boolean); + if (includeComponents) { + query.leftJoin( + componentTable, + eq(componentTable.entityId, entityTable.id) + ); + } + + const result = await query.where(eq(participantTable.roomId, roomId)); + + // Group components by entity if includeComponents is true + const entitiesByIdMap = new Map(); + + result.forEach(row => { + if (!row.entity) return; + + const entityId = row.entity.id as UUID; + if (!entitiesByIdMap.has(entityId)) { + const entity: Entity = { + ...row.entity, + components: includeComponents ? [] : undefined + }; + entitiesByIdMap.set(entityId, entity); + } + + if (includeComponents && row.components) { + const entity = entitiesByIdMap.get(entityId)!; + if (!entity.components) { + entity.components = []; + } + entity.components.push(row.components); + } + }); + + return Array.from(entitiesByIdMap.values()); }); } @@ -762,84 +795,6 @@ export abstract class BaseDrizzleAdapter }); } - async getActorDetails(params: { roomId: string }): Promise { - if (!params.roomId) { - throw new Error("roomId is required"); - } - - return this.withDatabase(async () => { - try { - const result = await this.db - .select({ - id: entityTable.id, - metadata: entityTable.metadata, - }) - .from(participantTable) - .leftJoin( - entityTable, - eq(participantTable.userId, entityTable.id) - ) - .where(eq(participantTable.roomId, params.roomId)) - .orderBy(entityTable.metadata?.name ?? entityTable.id); - - logger.debug("Retrieved actor details:", { - roomId: params.roomId, - actorCount: result.length, - }); - - return result.map((row) => { - try { - const details = - typeof row.details === "string" - ? JSON.parse(row.details) - : row.details || {}; - - return { - id: row.id as UUID, - name: row.name ?? "", - username: row.username ?? "", - details: { - tagline: details.tagline ?? "", - summary: details.summary ?? "", - quote: details.quote ?? "", - }, - }; - } catch (error) { - logger.warn("Failed to parse actor details:", { - actorId: row.id, - error: - error instanceof Error - ? error.message - : String(error), - }); - - return { - id: row.id as UUID, - name: row.name ?? "", - username: row.username ?? "", - details: { - tagline: "", - summary: "", - quote: "", - }, - }; - } - }); - } catch (error) { - logger.error("Failed to fetch actor details:", { - roomId: params.roomId, - error: - error instanceof Error ? error.message : String(error), - }); - throw new Error( - `Failed to fetch actor details: ${ - error instanceof Error ? error.message : String(error) - }` - ); - } - }); - } - async createMemory(memory: Memory & { metadata?: KnowledgeMetadata }, tableName: string): Promise { logger.debug("DrizzleAdapter createMemory:", { memoryId: memory.id, From bdec0bb3792cbd1c0b51154240e719f707e5938e Mon Sep 17 00:00:00 2001 From: Shaw Date: Thu, 27 Feb 2025 18:56:35 -0800 Subject: [PATCH 07/13] prototype sendMessage --- packages/core/src/actions/cancel.ts | 93 -------- packages/core/src/actions/sendMessage.ts | 289 +++++++++++++++++++++++ packages/core/src/bootstrap.ts | 6 +- packages/core/src/database.ts | 10 +- packages/core/src/types.ts | 51 ++++ packages/plugin-sql/src/base.ts | 10 + 6 files changed, 363 insertions(+), 96 deletions(-) delete mode 100644 packages/core/src/actions/cancel.ts create mode 100644 packages/core/src/actions/sendMessage.ts diff --git a/packages/core/src/actions/cancel.ts b/packages/core/src/actions/cancel.ts deleted file mode 100644 index c49912ff401..00000000000 --- a/packages/core/src/actions/cancel.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { logger } from "../logger"; -import { Action, ActionExample, HandlerCallback, IAgentRuntime, Memory, State } from "../types"; - -export const cancelTaskAction: Action = { - name: "CANCEL_TASK", - similes: ["REJECT_TASK", "STOP_TASK", "NEVERMIND", "CANCEL", "ABORT"], - description: "Cancels a pending task that's awaiting confirmation", - - validate: async ( - runtime: IAgentRuntime, - message: Memory, - _state: State - ): Promise => { - const pendingTasks = runtime.getTasks({ - roomId: message.roomId, - tags: ["AWAITING_CHOICE"], - }); - - // Only validate if there are pending tasks - return pendingTasks && pendingTasks.length > 0; - }, - - handler: async ( - runtime: IAgentRuntime, - message: Memory, - _state: State, - _options: any, - callback: HandlerCallback, - responses: Memory[] - ): Promise => { - try { - // First handle any initial responses - for (const response of responses) { - await callback(response.content); - } - - // Get pending tasks for this room - const pendingTasks = runtime.getTasks({ - roomId: message.roomId, - tags: ["AWAITING_CHOICE"], - }); - - if (!pendingTasks || pendingTasks.length === 0) { - await callback({ - text: "No tasks currently awaiting confirmation.", - action: "CANCEL_TASK", - source: message.content.source, - }); - return; - } - - // Cancel each pending task - for (const task of pendingTasks) { - runtime.deleteTask(task.id); - } - } catch (error) { - logger.error("Error in cancel task handler:", error); - } - }, - - examples: [ - [ - { - user: "{{user1}}", - content: { - text: "Actually, don't post that tweet", - }, - }, - { - user: "{{user2}}", - content: { - text: "Task cancelled successfully.", - action: "CANCEL_TASK", - }, - }, - ], - [ - { - user: "{{user1}}", - content: { - text: "Cancel", - }, - }, - { - user: "{{user2}}", - content: { - text: "Task cancelled successfully.", - action: "CANCEL_TASK", - }, - }, - ], - ] as ActionExample[][], -}; diff --git a/packages/core/src/actions/sendMessage.ts b/packages/core/src/actions/sendMessage.ts new file mode 100644 index 00000000000..82772e910cc --- /dev/null +++ b/packages/core/src/actions/sendMessage.ts @@ -0,0 +1,289 @@ +// action: SEND_MESSAGE +// send message to a user or room (other than this room we are in) + +import { v4 as uuidv4 } from 'uuid'; +import { logger } from "../logger"; +import { + Action, + ActionExample, + Component, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + UUID, + Entity, + sendDirectMessage, + sendRoomMessage +} from "../types"; +import { composeContext } from "../context"; +import { findEntityByName } from "../entities"; +import { parseJSONObjectFromText } from "../parsing"; + +const targetExtractionTemplate = `# Task: Extract Target and Source Information + +# Recent Messages: +{{recentMessages}} + +# Instructions: +Analyze the conversation to identify: +1. The target type (user or room) +2. The target platform/source (e.g. telegram, discord, etc) +3. Any identifying information about the target + +Return a JSON object with: +{ + "targetType": "user|room", + "source": "platform-name", + "identifiers": { + // Relevant identifiers for that target + // e.g. username, roomName, etc. + } +} + +Example outputs: +1. For "send a message to @dev_guru on telegram": +{ + "targetType": "user", + "source": "telegram", + "identifiers": { + "username": "dev_guru" + } +} + +2. For "post this in #announcements": +{ + "targetType": "room", + "source": "discord", + "identifiers": { + "roomName": "announcements" + } +}`; + +export const sendMessageAction: Action = { + name: "SEND_MESSAGE", + similes: ["DM", "MESSAGE", "SEND_DM", "POST_MESSAGE"], + description: "Send a message to a user or room (other than the current one)", + + validate: async ( + runtime: IAgentRuntime, + message: Memory, + state: State + ): Promise => { + // Check if we have permission to send messages + const worldId = message.roomId; + const agentId = runtime.agentId; + + // Get all components for the current room to understand available sources + const roomComponents = await runtime.databaseAdapter.getComponents(message.roomId, worldId, agentId); + + // Get source types from room components + const availableSources = new Set(roomComponents.map(c => c.type)); + + // TODO: Add ability for plugins to register their sources + // const registeredSources = runtime.getRegisteredSources?.() || []; + // availableSources.add(...registeredSources); + + return availableSources.size > 0; + }, + + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: any, + callback: HandlerCallback, + responses: Memory[] + ): Promise => { + try { + // Handle initial responses + for (const response of responses) { + await callback(response.content); + } + + const sourceEntityId = message.userId; + const roomId = message.roomId; + const agentId = runtime.agentId; + const room = await runtime.getRoom(roomId); + const worldId = room.worldId; + + // Extract target and source information + const targetContext = composeContext({ + state, + template: targetExtractionTemplate, + }); + + const targetResult = await runtime.useModel(ModelClass.TEXT_LARGE, { + context: targetContext, + stopSequences: ["}"] + }); + + const targetData = parseJSONObjectFromText(targetResult); + if (!targetData?.targetType || !targetData?.source) { + await callback({ + text: "I couldn't determine where you want me to send the message. Could you please specify the target (user or room) and platform?", + action: "SEND_MESSAGE_ERROR", + source: message.content.source, + }); + return; + } + + const source = targetData.source.toLowerCase(); + + if (targetData.targetType === "user") { + // Try to find the target user entity + const targetEntity = await findEntityByName(runtime, JSON.stringify(targetData.identifiers), message, state); + + if (!targetEntity) { + await callback({ + text: "I couldn't find the user you want me to send a message to. Could you please provide more details about who they are?", + action: "SEND_MESSAGE_ERROR", + source: message.content.source, + }); + return; + } + + // Get the component for the specified source + const userComponent = await runtime.databaseAdapter.getComponent( + targetEntity.id!, + source, + worldId, + sourceEntityId + ); + + if (!userComponent) { + await callback({ + text: `I couldn't find ${source} information for that user. Could you please provide their ${source} details?`, + action: "SEND_MESSAGE_ERROR", + source: message.content.source, + }); + return; + } + + // Send the message using the appropriate client + try { + await sendDirectMessage( + runtime, + targetEntity.id!, + source, + message.content.text, + worldId + ); + + await callback({ + text: `Message sent to ${targetEntity.names[0]} on ${source}.`, + action: "SEND_MESSAGE", + source: message.content.source, + }); + } catch (error) { + logger.error(`Failed to send direct message: ${error.message}`); + await callback({ + text: "I encountered an error trying to send the message. Please try again.", + action: "SEND_MESSAGE_ERROR", + source: message.content.source, + }); + } + + } else if (targetData.targetType === "room") { + // Try to find the target room + const rooms = await runtime.databaseAdapter.getRooms(worldId); + const targetRoom = rooms.find(r => { + // Match room name from identifiers + return r.name.toLowerCase() === targetData.identifiers.roomName?.toLowerCase(); + }); + + if (!targetRoom) { + await callback({ + text: "I couldn't find the room you want me to send a message to. Could you please specify the exact room name?", + action: "SEND_MESSAGE_ERROR", + source: message.content.source, + }); + return; + } + + // Send the message to the room + try { + await sendRoomMessage( + runtime, + targetRoom.id, + source, + message.content.text, + worldId + ); + + await callback({ + text: `Message sent to ${targetRoom.name} on ${source}.`, + action: "SEND_MESSAGE", + source: message.content.source, + }); + } catch (error) { + logger.error(`Failed to send room message: ${error.message}`); + await callback({ + text: "I encountered an error trying to send the message to the room. Please try again.", + action: "SEND_MESSAGE_ERROR", + source: message.content.source, + }); + } + } + + } catch (error) { + logger.error(`Error in sendMessage handler: ${error}`); + await callback({ + text: "There was an error processing your message request.", + action: "SEND_MESSAGE_ERROR", + source: message.content.source, + }); + } + }, + + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Send a message to @dev_guru on telegram saying 'Hello!'", + }, + }, + { + user: "{{user2}}", + content: { + text: "Message sent to dev_guru on telegram.", + action: "SEND_MESSAGE", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Post 'Important announcement!' in #announcements", + }, + }, + { + user: "{{user2}}", + content: { + text: "Message sent to announcements.", + action: "SEND_MESSAGE", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "DM Jimmy and tell him 'Meeting at 3pm'", + }, + }, + { + user: "{{user2}}", + content: { + text: "Message sent to Jimmy.", + action: "SEND_MESSAGE", + }, + }, + ], + ] as ActionExample[][], +}; + +export default sendMessageAction; \ No newline at end of file diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index b6621e1c610..95473b794d1 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -1,6 +1,7 @@ import { UUID } from "crypto"; import { v4 } from "uuid"; -import { cancelTaskAction } from "./actions/cancel.ts"; +import { updateEntityAction } from "./actions/updateEntity.ts"; +import { sendMessageAction } from "./actions/sendMessage.ts" import { followRoomAction } from "./actions/followRoom.ts"; import { ignoreAction } from "./actions/ignore.ts"; import { muteRoomAction } from "./actions/muteRoom.ts"; @@ -849,7 +850,8 @@ export const bootstrapPlugin: Plugin = { noneAction, muteRoomAction, unmuteRoomAction, - cancelTaskAction, + sendMessageAction, + updateEntityAction, selectOptionAction, updateRoleAction, updateSettingsAction, diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index 9a57665b171..4d77d08c136 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -13,7 +13,8 @@ import type { UUID, WorldData, Agent, - Component + Component, + Room } from "./types.ts"; /** @@ -328,6 +329,13 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { */ abstract getRoom(roomId: UUID, agentId: UUID): Promise; + /** + * Retrieves all rooms for a given world. + * @param worldId The UUID of the world to retrieve rooms for. + * @returns A Promise that resolves to an array of Room objects. + */ + abstract getRooms(worldId: UUID): Promise; + /** * Creates a new room with an optional specified ID. * @param roomId Optional UUID to assign to the new room. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d4edf0f628c..19318962bf6 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -550,6 +550,9 @@ export interface Room { /** Unique identifier */ id: UUID; + /** Room name */ + name: string; + /** Room participants */ participants: Participant[]; } @@ -923,6 +926,8 @@ export interface IDatabaseAdapter { getRoomsForParticipants(userIds: UUID[], agentId: UUID): Promise; + getRooms(worldId: UUID): Promise; + addParticipant(userId: UUID, roomId: UUID, agentId: UUID): Promise; removeParticipant(userId: UUID, roomId: UUID, agentId: UUID): Promise; @@ -1538,4 +1543,50 @@ export interface OnboardingConfig { settings: { [key: string]: Omit; }; +} + +/** + * Send a direct message to a user + * @param runtime The agent runtime instance + * @param targetEntityId The ID of the user to send the message to + * @param source The platform/source to send on (e.g. telegram, discord) + * @param message The message content to send + * @param worldId The world ID context + */ +export async function sendDirectMessage( + runtime: IAgentRuntime, + targetEntityId: UUID, + source: string, + message: string, + worldId: UUID +): Promise { + const client = runtime.getClient(source); + if (!client) { + throw new Error(`No client found for source: ${source}`); + } + + await client.sendDirectMessage?.(targetEntityId, message, worldId); +} + +/** + * Send a message to a room + * @param runtime The agent runtime instance + * @param roomId The ID of the room to send to + * @param source The platform/source to send on (e.g. telegram, discord) + * @param message The message content to send + * @param worldId The world ID context + */ +export async function sendRoomMessage( + runtime: IAgentRuntime, + roomId: UUID, + source: string, + message: string, + worldId: UUID +): Promise { + const client = runtime.getClient(source); + if (!client) { + throw new Error(`No client found for source: ${source}`); + } + + await client.sendRoomMessage?.(roomId, message, worldId); } \ No newline at end of file diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index 20a8273c676..ffc4860db1e 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -1087,6 +1087,16 @@ export abstract class BaseDrizzleAdapter }); } + async getRooms(worldId: UUID): Promise { + return this.withDatabase(async () => { + const result = await this.db + .select() + .from(roomTable) + .where(eq(roomTable.worldId, worldId)); + return result; + }); + } + async updateRoom(room: RoomData): Promise { return this.withDatabase(async () => { await this.db.update(roomTable).set(room).where(eq(roomTable.id, room.id)); From 16de4882bac0a218fe2a02d3fa1378b584922157 Mon Sep 17 00:00:00 2001 From: Shaw Date: Thu, 27 Feb 2025 22:06:15 -0800 Subject: [PATCH 08/13] extract relationships --- packages/core/src/bootstrap.ts | 28 +- packages/core/src/database.ts | 23 +- packages/core/src/entities.ts | 163 +++++++++- packages/core/src/evaluators/fact.ts | 252 --------------- packages/core/src/evaluators/index.ts | 2 +- packages/core/src/evaluators/reflection.ts | 297 ++++++++++++++++++ packages/core/src/memory.ts | 6 +- packages/core/src/messages.ts | 25 +- packages/core/src/providers/facts.ts | 8 +- packages/core/src/relationships.ts | 32 +- packages/core/src/runtime.ts | 14 +- packages/core/src/types.ts | 47 ++- .../migrations/20250227085330_init.sql | 14 +- .../meta/20250227085330_snapshot.json | 28 +- packages/plugin-sql/src/base.ts | 197 ++++++------ packages/plugin-sql/src/pg-lite/manager.ts | 2 +- packages/plugin-sql/src/pg/manager.ts | 2 +- .../plugin-sql/src/schema/relationship.ts | 10 +- 18 files changed, 702 insertions(+), 448 deletions(-) delete mode 100644 packages/core/src/evaluators/fact.ts create mode 100644 packages/core/src/evaluators/reflection.ts diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index 95473b794d1..c2643745111 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -12,7 +12,7 @@ import updateSettingsAction from "./actions/settings.ts"; import { unfollowRoomAction } from "./actions/unfollowRoom.ts"; import { unmuteRoomAction } from "./actions/unmuteRoom.ts"; import { composeContext } from "./context.ts"; -import { factEvaluator } from "./evaluators/fact.ts"; +import { reflectionEvaluator } from "./evaluators/reflection.ts"; import { goalEvaluator } from "./evaluators/goal.ts"; import { formatMessages, @@ -673,29 +673,24 @@ const handleServerSync = async ({ for (let i = 0; i < users.length; i += batchSize) { const userBatch = users.slice(i, i + batchSize); - // Find a default text channel for these users if possible - const defaultRoom = - rooms.find( - (room) => - room.type === ChannelType.GROUP && room.name.includes("general") - ) || rooms.find((room) => room.type === ChannelType.GROUP); - - if (defaultRoom) { - // Process each user in the batch - await Promise.all( - userBatch.map(async (user: Entity) => { + // check if user is in any of these rooms in rooms + const firstRoomUserIsIn = rooms.length > 0 ? rooms[0] : null; + + // Process each user in the batch + await Promise.all( + userBatch.map(async (user: Entity) => { try { await runtime.ensureConnection({ userId: user.id, - roomId: defaultRoom.id, + roomId: firstRoomUserIsIn.id, userName: user.metadata[source].username, userScreenName: user.metadata[source].name, source: source, - channelId: defaultRoom.channelId, + channelId: firstRoomUserIsIn.channelId, serverId: world.serverId, - type: defaultRoom.type, + type: firstRoomUserIsIn.type, worldId: world.id, }); } catch (err) { @@ -705,7 +700,6 @@ const handleServerSync = async ({ } }) ); - } // Add a small delay between batches if not the last batch if (i + batchSize < users.length) { @@ -857,7 +851,7 @@ export const bootstrapPlugin: Plugin = { updateSettingsAction, ], events, - evaluators: [factEvaluator, goalEvaluator], + evaluators: [reflectionEvaluator, goalEvaluator], providers: [ timeProvider, factsProvider, diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index 4d77d08c136..11272607b07 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -215,7 +215,7 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { memory: Memory, tableName: string, unique?: boolean - ): Promise; + ): Promise; /** * Removes a specific memory from the database. @@ -416,34 +416,39 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { /** * Creates a new relationship between two users. - * @param params An object containing the UUIDs of the two users (entityA and entityB). + * @param params Object containing the relationship details including entity IDs, agent ID, optional tags and metadata * @returns A Promise that resolves to a boolean indicating success or failure of the creation. */ abstract createRelationship(params: { - entityA: UUID; - entityB: UUID; + sourceEntityId: UUID; + targetEntityId: UUID; + agentId: UUID; + tags?: string[]; + metadata?: { [key: string]: any }; }): Promise; /** * Retrieves a relationship between two users if it exists. - * @param params An object containing the UUIDs of the two users (entityA and entityB). + * @param params Object containing the entity IDs and agent ID * @returns A Promise that resolves to the Relationship object or null if not found. */ abstract getRelationship(params: { - entityA: UUID; - entityB: UUID; + sourceEntityId: UUID; + targetEntityId: UUID; + agentId: UUID; }): Promise; /** * Retrieves all relationships for a specific user. - * @param params An object containing the UUID of the user. + * @param params Object containing the user ID, agent ID and optional tags to filter by * @returns A Promise that resolves to an array of Relationship objects. */ abstract getRelationships(params: { userId: UUID; + agentId: UUID; + tags?: string[]; }): Promise; - /** * Creates a new character in the database. * @param character The Character object to create. diff --git a/packages/core/src/entities.ts b/packages/core/src/entities.ts index 0f5d0a5c0a0..e3aa12ec429 100644 --- a/packages/core/src/entities.ts +++ b/packages/core/src/entities.ts @@ -7,7 +7,8 @@ import { type Memory, ModelClass, type State, - type UUID + type UUID, + type Relationship } from "./types.ts"; const entityResolutionTemplate = `# Task: Resolve Entity Name @@ -53,9 +54,7 @@ const entityResolutionTemplate = `# Task: Resolve Entity Name }] } -Current Room: {{roomName}} -Current World: {{worldName}} -Message Sender: {{senderName}} (ID: {{userId}}) +Message Sender: {{senderName}} (ID: {{senderId}}) Agent: {{agentName}} (ID: {{agentId}}) # Entities in Room: @@ -66,6 +65,11 @@ Agent: {{agentName}} (ID: {{agentId}}) # Recent Messages: {{recentMessages}} +# Recent Interactions: +{{#if recentInteractions}} +{{recentInteractions}} +{{/if}} + # Query: {{query}} @@ -75,25 +79,76 @@ Agent: {{agentName}} (ID: {{agentId}}) 3. Look for usernames/handles in standard formats (e.g. @username, user#1234) 4. Consider context from recent messages for pronouns and references 5. If multiple matches exist, use context to disambiguate +6. Consider recent interactions and relationship strength when resolving ambiguity Return a JSON object with: { "entityId": "exact-id-if-known-otherwise-null", - "type": "EXACT_MATCH | USERNAME_MATCH | NAME_MATCH | AMBIGUOUS | UNKNOWN", + "type": "EXACT_MATCH | USERNAME_MATCH | NAME_MATCH | RELATIONSHIP_MATCH | AMBIGUOUS | UNKNOWN", "matches": [{ "name": "matched-name", "reason": "why this entity matches" }] }`; +async function getRecentInteractions( + runtime: IAgentRuntime, + sourceEntityId: UUID, + candidateEntities: Entity[], + roomId: UUID, + relationships: Relationship[] +): Promise<{ entity: Entity; interactions: Memory[]; count: number }[]> { + const results = []; + + // Get recent messages from the room - just for context + const recentMessages = await runtime.messageManager.getMemories({ + roomId, + count: 20 // Reduced from 100 since we only need context + }); + + for (const entity of candidateEntities) { + let interactions: Memory[] = []; + let interactionScore = 0; + + // First get direct replies using inReplyTo + const directReplies = recentMessages.filter(msg => + (msg.userId === sourceEntityId && msg.content.inReplyTo === entity.id) || + (msg.userId === entity.id && msg.content.inReplyTo === sourceEntityId) + ); + + interactions.push(...directReplies); + + // Get relationship strength from metadata + const relationship = relationships.find(rel => + (rel.sourceEntityId === sourceEntityId && rel.targetEntityId === entity.id) || + (rel.targetEntityId === sourceEntityId && rel.sourceEntityId === entity.id) + ); + + if (relationship?.metadata?.interactionStrength) { + interactionScore = relationship.metadata.interactionStrength; + } + + // Add bonus points for recent direct replies + interactionScore += directReplies.length; + + // Keep last few messages for context + const uniqueInteractions = [...new Set(interactions)]; + results.push({ + entity, + interactions: uniqueInteractions.slice(-5), // Only keep last 5 messages for context + count: Math.round(interactionScore) + }); + } + + // Sort by interaction score descending + return results.sort((a, b) => b.count - a.count); +} + export async function findEntityByName( runtime: IAgentRuntime, query: string, message: Memory, state: State, - options: { - worldId?: UUID; - } = {} ): Promise { try { const room = await runtime.getRoom(message.roomId); @@ -107,13 +162,72 @@ export async function findEntityByName( // Get all entities in the room with their components const entitiesInRoom = await runtime.databaseAdapter.getEntitiesForRoom(room.id, runtime.agentId, true); + // Filter components for each entity based on permissions + const filteredEntities = await Promise.all(entitiesInRoom.map(async entity => { + if (!entity.components) return entity; + + // Get world roles if we have a world + const worldRoles = world?.metadata?.roles || {}; + + // Filter components based on permissions + entity.components = entity.components.filter(component => { + // 1. Pass if sourceEntityId matches the requesting entity + if (component.sourceEntityId === message.userId) return true; + + // 2. Pass if sourceEntityId is an owner/admin of the current world + if (world && component.sourceEntityId) { + const sourceRole = worldRoles[component.sourceEntityId]; + if (sourceRole === "OWNER" || sourceRole === "ADMIN") return true; + } + + // 3. Pass if sourceEntityId is the agentId + if (component.sourceEntityId === runtime.agentId) return true; + + // Filter out components that don't meet any criteria + return false; + }); + + return entity; + })); + + // Get relationships for the message sender + const relationships = await runtime.databaseAdapter.getRelationships({ + userId: message.userId, + agentId: runtime.agentId + }); + + // Get entities from relationships + const relationshipEntities = await Promise.all( + relationships.map(async rel => { + const entityId = rel.sourceEntityId === message.userId ? rel.targetEntityId : rel.sourceEntityId; + return runtime.databaseAdapter.getEntityById(entityId, runtime.agentId); + }) + ); + + // Filter out nulls and combine with room entities + const allEntities = [...filteredEntities, ...relationshipEntities.filter((e): e is Entity => e !== null)]; + + // Get interaction strength data for relationship entities + const interactionData = await getRecentInteractions(runtime, message.userId, allEntities, room.id, relationships); + + // Format interaction data for LLM context + const recentInteractions = interactionData.map(data => ({ + entityName: data.entity.names[0], + interactionStrength: data.count, + recentMessages: data.interactions.map(msg => ({ + from: msg.userId === message.userId ? "sender" : "entity", + text: msg.content.text + })) + })); + // Compose context for LLM const context = composeContext({ state: { ...state, roomName: room.name || room.id, worldName: world?.name || "Unknown", - entitiesInRoom: JSON.stringify(entitiesInRoom, null, 2), + entitiesInRoom: JSON.stringify(filteredEntities, null, 2), + recentInteractions: JSON.stringify(recentInteractions, null, 2), userId: message.userId, query }, @@ -136,15 +250,30 @@ export async function findEntityByName( // If we got an exact entity ID match if (resolution.type === "EXACT_MATCH" && resolution.entityId) { const entity = await runtime.databaseAdapter.getEntityById(resolution.entityId as UUID, runtime.agentId); - if (entity) return entity; + if (entity) { + // Filter components again for the returned entity + if (entity.components) { + const worldRoles = world?.metadata?.roles || {}; + entity.components = entity.components.filter(component => { + if (component.sourceEntityId === message.userId) return true; + if (world && component.sourceEntityId) { + const sourceRole = worldRoles[component.sourceEntityId]; + if (sourceRole === "OWNER" || sourceRole === "ADMIN") return true; + } + if (component.sourceEntityId === runtime.agentId) return true; + return false; + }); + } + return entity; + } } - // For username/name matches, search through formatted entities + // For username/name/relationship matches, search through all entities if (resolution.matches?.[0]?.name) { const matchName = resolution.matches[0].name.toLowerCase(); // Find matching entity by username/handle in components or by name - const matchingEntity = entitiesInRoom.find(entity => { + const matchingEntity = allEntities.find(entity => { // Check names if (entity.names.some(n => n.toLowerCase() === matchName)) return true; @@ -156,7 +285,15 @@ export async function findEntityByName( }); if (matchingEntity) { - return matchingEntity; + // If this is a relationship match, sort by interaction strength + if (resolution.type === "RELATIONSHIP_MATCH") { + const interactionInfo = interactionData.find(d => d.entity.id === matchingEntity.id); + if (interactionInfo && interactionInfo.count > 0) { + return matchingEntity; + } + } else { + return matchingEntity; + } } } diff --git a/packages/core/src/evaluators/fact.ts b/packages/core/src/evaluators/fact.ts deleted file mode 100644 index 9f7991978ee..00000000000 --- a/packages/core/src/evaluators/fact.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { generateObjectArray } from "../generation"; -import { composeContext } from "../context"; -import { MemoryManager } from "../memory"; -import { ActionExample, Evaluator, IAgentRuntime, Memory, ModelClass } from "../types"; - -import { z } from "zod"; - -export const formatFacts = (facts: Memory[]) => { - const messageStrings = facts - .reverse() - .map((fact: Memory) => fact.content.text); - const finalMessageStrings = messageStrings.join("\n"); - return finalMessageStrings; -}; - -const factsTemplate = - // {{actors}} - `# Task: Extract Claims from the conversation - -# START OF EXAMPLES -These are examples of the expected output of this task: -{{evaluationExamples}} -# END OF EXAMPLES - -# INSTRUCTIONS - -Extract any claims from the conversation that are not already present in the list of known facts above: -- Try not to include already-known facts. If you think a fact is already known, but you're not sure, respond with already_known: true. -- If the fact is already in the user's description, set in_bio to true -- If we've already extracted this fact, set already_known to true -- Set the claim type to 'status', 'fact' or 'opinion' -- For true facts about the world or the character that do not change, set the claim type to 'fact' -- For facts that are true but change over time, set the claim type to 'status' -- For non-facts, set the type to 'opinion' -- 'opinion' includes non-factual opinions and also includes the character's thoughts, feelings, judgments or recommendations -- Include any factual detail, including where the user lives, works, or goes to school, what they do for a living, their hobbies, and any other relevant information - -Recent Messages: -{{recentMessages}} - -Response should be a JSON object array inside a JSON markdown block. Correct response format: -\`\`\`json -[ - {"claim": string, "type": enum, in_bio: boolean, already_known: boolean }, - {"claim": string, "type": enum, in_bio: boolean, already_known: boolean }, - ... -] -\`\`\``; - -// Updated schema with an explicit type cast to bypass mismatches between Zod versions. -const claimSchema = (z.array( - z.object({ - claim: z.string(), - type: z.enum(["fact", "opinion", "status"]), - in_bio: z.boolean(), - already_known: z.boolean(), - }) -) as unknown) as any; - -async function handler(runtime: IAgentRuntime, message: Memory) { - const state = await runtime.composeState(message); - - const { agentId, roomId } = state; - - const context = composeContext({ - state, - template: runtime.character.templates?.factsTemplate || factsTemplate, - }); - - const facts = await generateObjectArray({ - runtime, - context, - modelClass: ModelClass.TEXT_LARGE, - schema: claimSchema, - schemaName: "Fact", - schemaDescription: "A fact about the user or the world", - }); - - const factsManager = new MemoryManager({ - runtime, - tableName: "facts", - }); - - if (!facts) { - return []; - } - - // If the fact is known or corrupted, remove it - const filteredFacts = facts - .filter((fact) => { - return ( - !fact.already_known && - fact.type === "fact" && - !fact.in_bio && - fact.claim && - fact.claim.trim() !== "" - ); - }) - .map((fact) => fact.claim); - - for (const fact of filteredFacts) { - const factMemory = await factsManager.addEmbeddingToMemory({ - userId: agentId, - agentId, - content: { text: fact }, - roomId, - createdAt: Date.now(), - }); - - await factsManager.createMemory(factMemory, true); - - await new Promise((resolve) => setTimeout(resolve, 250)); - } - return filteredFacts; -} - -export const factEvaluator: Evaluator = { - name: "GET_FACTS", - similes: [ - "GET_CLAIMS", - "EXTRACT_CLAIMS", - "EXTRACT_FACTS", - "EXTRACT_CLAIM", - "EXTRACT_INFORMATION", - ], - validate: async ( - runtime: IAgentRuntime, - - message: Memory - ): Promise => { - const messageCount = (await runtime.messageManager.countMemories( - message.roomId - )) as number; - - const reflectionCount = Math.ceil(runtime.getConversationLength() / 2); - - return messageCount % reflectionCount === 0; - }, - description: - "Extract factual information about the people in the conversation, the current events in the world, and anything else that might be important to remember.", - handler, - examples: [ - { - context: `Actors in the scene: -{{user1}}: Programmer and moderator of the local story club. -{{user2}}: New member of the club. Likes to write and read. - -Facts about the actors: -None`, - messages: [ - { - user: "{{user1}}", - content: { text: "So where are you from" }, - }, - { - user: "{{user2}}", - content: { text: "I'm from the city" }, - }, - { - user: "{{user1}}", - content: { text: "Which city?" }, - }, - { - user: "{{user2}}", - content: { text: "Oakland" }, - }, - { - user: "{{user1}}", - content: { - text: "Oh, I've never been there, but I know it's in California", - }, - }, - ] as ActionExample[], - outcome: `{ "claim": "{{user2}} is from Oakland", "type": "fact", "in_bio": false, "already_known": false },`, - }, - { - context: `Actors in the scene: -{{user1}}: Athelete and cyclist. Worked out every day for a year to prepare for a marathon. -{{user2}}: Likes to go to the beach and shop. - -Facts about the actors: -{{user1}} and {{user2}} are talking about the marathon -{{user1}} and {{user2}} have just started dating`, - messages: [ - { - user: "{{user1}}", - content: { - text: "I finally completed the marathon this year!", - }, - }, - { - user: "{{user2}}", - content: { text: "Wow! How long did it take?" }, - }, - { - user: "{{user1}}", - content: { text: "A little over three hours." }, - }, - { - user: "{{user1}}", - content: { text: "I'm so proud of myself." }, - }, - ] as ActionExample[], - outcome: `Claims: -json\`\`\` -[ - { "claim": "Alex just completed a marathon in just under 4 hours.", "type": "fact", "in_bio": false, "already_known": false }, - { "claim": "Alex worked out 2 hours a day at the gym for a year.", "type": "fact", "in_bio": true, "already_known": false }, - { "claim": "Alex is really proud of himself.", "type": "opinion", "in_bio": false, "already_known": false } -] -\`\`\` -`, - }, - { - context: `Actors in the scene: -{{user1}}: Likes to play poker and go to the park. Friends with Eva. -{{user2}}: Also likes to play poker. Likes to write and read. - -Facts about the actors: -Mike and Eva won a regional poker tournament about six months ago -Mike is married to Alex -Eva studied Philosophy before switching to Computer Science`, - messages: [ - { - user: "{{user1}}", - content: { - text: "Remember when we won the regional poker tournament last spring", - }, - }, - { - user: "{{user2}}", - content: { - text: "That was one of the best days of my life", - }, - }, - { - user: "{{user1}}", - content: { - text: "It really put our poker club on the map", - }, - }, - ] as ActionExample[], - outcome: `Claims: -json\`\`\` -[ - { "claim": "Mike and Eva won the regional poker tournament last spring", "type": "fact", "in_bio": false, "already_known": true }, - { "claim": "Winning the regional poker tournament put the poker club on the map", "type": "opinion", "in_bio": false, "already_known": false } -] -\`\`\``, - }, - ], -}; diff --git a/packages/core/src/evaluators/index.ts b/packages/core/src/evaluators/index.ts index 8496906e433..7f7f247673b 100644 --- a/packages/core/src/evaluators/index.ts +++ b/packages/core/src/evaluators/index.ts @@ -1,2 +1,2 @@ -export * from "./fact.ts"; +export * from "./reflection.ts"; export * from "./goal.ts"; diff --git a/packages/core/src/evaluators/reflection.ts b/packages/core/src/evaluators/reflection.ts new file mode 100644 index 00000000000..940573e0074 --- /dev/null +++ b/packages/core/src/evaluators/reflection.ts @@ -0,0 +1,297 @@ +import { z } from "zod"; +import { composeContext } from "../context"; +import { generateObject } from "../generation"; +import { MemoryManager } from "../memory"; +import { Evaluator, IAgentRuntime, Memory, ModelClass, UUID } from "../types"; +import { getActorDetails, resolveActorId } from "../messages"; + +// Schema definitions for the reflection output +const relationshipSchema = z.object({ + sourceEntityId: z.string(), + targetEntityId: z.string(), + tags: z.array(z.string()), + metadata: z.object({ + interactions: z.number(), + }).optional(), +}); + +const reflectionSchema = z.object({ + reflection: z.string(), + facts: z.array(z.object({ + claim: z.string(), + type: z.enum(["fact", "opinion", "status"]), + in_bio: z.boolean(), + already_known: z.boolean(), + })), + relationships: z.array(relationshipSchema), +}); + +const reflectionTemplate = `# Task: Generate Agent Reflection, Extract Facts and Relationships + +# Examples: +{{evaluationExamples}} + +{{actors}} + +{{bio}} + +# Current Context: +Agent Name: {{agentName}} +Room Type: {{roomType}} +Message Sender: {{senderName}} (ID: {{senderId}}) + +# Recent Messages: +{{recentMessages}} + +# Known Facts: +{{knownFacts}} + +# Instructions: +1. Generate a self-reflection monologue about recent interactions +2. Extract new facts from the conversation +3. Identify and describe relationships between entities + +Generate a response in the following format: +\`\`\`json +{ + "reflection": "A thoughtful self-reflection monologue about how the interaction is going...", + "facts": [ + { + "claim": "factual statement", + "type": "fact|opinion|status", + "in_bio": false, + "already_known": false + } + ], + "relationships": [ + { + "sourceEntityId": "entity_initiating_interaction", + "targetEntityId": "entity_being_interacted_with", + "tags": ["group_interaction|voice_interaction|dm_interaction", "additional_tag1", "additional_tag2"] + } + ] +} +\`\`\``; + +async function handler(runtime: IAgentRuntime, message: Memory) { + const state = await runtime.composeState(message); + const { agentId, roomId } = state; + + // Get existing relationships for the room + const existingRelationships = await runtime.databaseAdapter.getRelationships({ + userId: message.userId, + agentId + }); + + // Get actors in the room for name resolution + const actors = await getActorDetails({ runtime, roomId }); + + // Get known facts + const factsManager = new MemoryManager({ + runtime, + tableName: "facts", + }); + + const knownFacts = await factsManager.getMemories({ + roomId, + agentId + }); + + const context = composeContext({ + state: { + ...state, + knownFacts: formatFacts(knownFacts), + roomType: state.roomType || "group", // Can be "group", "voice", or "dm" + }, + template: runtime.character.templates?.reflectionTemplate || reflectionTemplate, + }); + + const reflection = await generateObject({ + runtime, + context, + modelClass: ModelClass.TEXT_LARGE, + schema: reflectionSchema, + schemaName: "Reflection", + schemaDescription: "Agent reflection including facts and relationships", + }); + + // Store new facts + const newFacts = reflection.facts.filter(fact => + !fact.already_known && + !fact.in_bio && + fact.claim && + fact.claim.trim() !== "" + ); + + for (const fact of newFacts) { + const factMemory = await factsManager.addEmbeddingToMemory({ + userId: agentId, + agentId, + content: { text: fact.claim }, + roomId, + createdAt: Date.now(), + }); + await factsManager.createMemory(factMemory, true); + } + + // Update or create relationships + for (const relationship of reflection.relationships) { + let sourceId: UUID; + let targetId: UUID; + + try { + sourceId = resolveActorId(relationship.sourceEntityId, actors); + targetId = resolveActorId(relationship.targetEntityId, actors); + } catch (error) { + console.warn('Failed to resolve relationship entities:', error); + continue; // Skip this relationship if we can't resolve the IDs + } + + const existingRelationship = existingRelationships.find(r => + r.sourceEntityId === sourceId && + r.targetEntityId === targetId + ); + + if (existingRelationship) { + // Update existing relationship by creating a new one + const updatedMetadata = { + ...existingRelationship.metadata, + interactions: (existingRelationship.metadata?.interactions || 0) + 1 + }; + + // Merge tags, removing duplicates + const updatedTags = Array.from(new Set([ + ...(existingRelationship.tags || []), + ...relationship.tags + ])); + + // TODO, switch to updateRelationship + await runtime.databaseAdapter.createRelationship({ + sourceEntityId: sourceId, + targetEntityId: targetId, + agentId, + tags: updatedTags, + metadata: updatedMetadata, + }); + } else { + // Create new relationship + await runtime.databaseAdapter.createRelationship({ + sourceEntityId: sourceId, + targetEntityId: targetId, + agentId, + tags: relationship.tags, + metadata: { + interactions: 1, + ...relationship.metadata + } + }); + } + } + + // Store the reflection itself as a memory + const reflectionMemory = await runtime.messageManager.addEmbeddingToMemory({ + userId: agentId, + agentId, + content: { + text: `(Reflecting to self: ${reflection.reflection}`, + action: "REFLECTION" + }, + roomId, + createdAt: Date.now(), + }); + const memoryId = await runtime.messageManager.createMemory(reflectionMemory, true); + + await runtime.cacheManager.set(`${message.roomId}-reflection-last-processed`, memoryId); + + return reflection; +} + +export const reflectionEvaluator: Evaluator = { + name: "REFLECTION", + similes: [ + "REFLECT", + "SELF_REFLECT", + "EVALUATE_INTERACTION", + "ASSESS_SITUATION" + ], + validate: async ( + runtime: IAgentRuntime, + message: Memory + ): Promise => { + const lastMessageId = await runtime.cacheManager.get(`${message.roomId}-reflection-last-processed`) + const messages = await runtime.messageManager.getMemories({ roomId: message.roomId, count: runtime.getConversationLength() }) + + if (lastMessageId) { + const lastMessageIndex = messages.findIndex(msg => msg.id === lastMessageId); + if (lastMessageIndex !== -1) { + messages.splice(0, lastMessageIndex + 1); + } + } + + const reflectionInterval = Math.ceil(runtime.getConversationLength() / 4); + + return messages.length > reflectionInterval; + }, + description: "Generate self-reflection, extract facts, and track relationships between entities in the conversation.", + handler, + examples: [ + { + context: `Agent Name: Sarah +Agent Role: Community Manager +Room Type: group +Current Room: general-chat +Message Sender: John (user-123)`, + messages: [ + { + user: "John", + content: { text: "Hey everyone, I'm new here!" }, + }, + { + user: "Sarah", + content: { text: "Welcome John! How did you find our community?" }, + }, + { + user: "John", + content: { text: "Through a friend who's really into AI" }, + } + ], + outcome: `{ + "reflection": "I'm engaging appropriately with a new community member, maintaining a welcoming and professional tone. My questions are helping to learn more about John and make him feel welcome.", + "facts": [ + { + "claim": "John is new to the community", + "type": "fact", + "in_bio": false, + "already_known": false + }, + { + "claim": "John found the community through a friend interested in AI", + "type": "fact", + "in_bio": false, + "already_known": false + } + ], + "relationships": [ + { + "sourceEntityId": "sarah-agent", + "targetEntityId": "user-123", + "tags": ["group_interaction"] + }, + { + "sourceEntityId": "user-123", + "targetEntityId": "sarah-agent", + "tags": ["group_interaction"] + } + ] +}` + } + ] +}; + +// Helper function to format facts for context +function formatFacts(facts: Memory[]) { + return facts + .reverse() + .map((fact: Memory) => fact.content.text) + .join("\n"); +} diff --git a/packages/core/src/memory.ts b/packages/core/src/memory.ts index db75ce9b174..5db16f073c6 100644 --- a/packages/core/src/memory.ts +++ b/packages/core/src/memory.ts @@ -204,7 +204,7 @@ export class MemoryManager implements IMemoryManager { * @param unique Whether to check for similarity before insertion. * @returns A Promise that resolves when the operation completes. */ - async createMemory(memory: Memory, unique = false): Promise { + async createMemory(memory: Memory, unique = false): Promise { memory = this.transformUserIdIfNeeded(memory); if (memory.metadata) { @@ -248,11 +248,13 @@ export class MemoryManager implements IMemoryManager { memory.embedding = await this.runtime.useModel(ModelClass.TEXT_EMBEDDING, null); } - await this.runtime.databaseAdapter.createMemory( + const memoryId = await this.runtime.databaseAdapter.createMemory( memory, this.tableName, unique ); + + return memoryId; } async getMemoriesByRoomIds(params: { roomIds: UUID[], limit?: number; agentId?: UUID }): Promise { diff --git a/packages/core/src/messages.ts b/packages/core/src/messages.ts index dba7c4013ad..3d2e89e1f3b 100644 --- a/packages/core/src/messages.ts +++ b/packages/core/src/messages.ts @@ -61,13 +61,36 @@ export async function getActorDetails({ */ export function formatActors({ actors }: { actors: Actor[] }) { const actorStrings = actors.map((actor: Actor) => { - const header = `${actor.name} (${actor.names.join(" aka ")})\nData: ${actor.data}`; + const header = `${actor.name} (${actor.names.join(" aka ")})` + (actor.data && Object.entries(actor.data).length > 0) ? `\nData: ${actor.data}` : ""; return header; }); const finalActorStrings = actorStrings.join("\n"); return finalActorStrings; } +/** + * Resolve an actor name to their UUID + * @param name - Name to resolve + * @param actors - List of actors to search through + * @returns UUID if found, throws error if not found or if input is not a valid UUID + */ +export function resolveActorId(name: string, actors: Actor[]): UUID { + // If the name is already a valid UUID, return it + if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(name)) { + return name as UUID; + } + + const actor = actors.find(a => + a.names.some(n => n.toLowerCase() === name.toLowerCase()) + ); + + if (!actor) { + throw new Error(`Could not resolve name "${name}" to a valid UUID`); + } + + return actor.id; +} + /** * Format messages into a string * @param {Object} params - The formatting parameters diff --git a/packages/core/src/providers/facts.ts b/packages/core/src/providers/facts.ts index 4ee08c6d79c..828cc3ee15c 100644 --- a/packages/core/src/providers/facts.ts +++ b/packages/core/src/providers/facts.ts @@ -1,9 +1,15 @@ -import { formatFacts } from "../evaluators/fact.ts"; import { MemoryManager } from "../memory.ts"; import { formatMessages } from "../messages.ts"; import { IAgentRuntime, Memory, ModelClass, Provider, State } from "../types.ts"; +function formatFacts(facts: Memory[]) { + return facts + .reverse() + .map((fact: Memory) => fact.content.text) + .join("\n"); +} + const factsProvider: Provider = { get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { const recentMessagesData = state?.recentMessagesData?.slice(-10); diff --git a/packages/core/src/relationships.ts b/packages/core/src/relationships.ts index debb32dde35..aa32e12b04c 100644 --- a/packages/core/src/relationships.ts +++ b/packages/core/src/relationships.ts @@ -2,32 +2,32 @@ import type { IAgentRuntime, Relationship, UUID } from "./types.ts"; export async function createRelationship({ runtime, - entityA, - entityB, + sourceEntityId, + targetEntityId, }: { runtime: IAgentRuntime; - entityA: UUID; - entityB: UUID; + sourceEntityId: UUID; + targetEntityId: UUID; }): Promise { return runtime.databaseAdapter.createRelationship({ - entityA, - entityB, + sourceEntityId, + targetEntityId, agentId: runtime.agentId, }); } export async function getRelationship({ runtime, - entityA, - entityB, + sourceEntityId, + targetEntityId, }: { runtime: IAgentRuntime; - entityA: UUID; - entityB: UUID; + sourceEntityId: UUID; + targetEntityId: UUID; }) { return runtime.databaseAdapter.getRelationship({ - entityA, - entityB, + sourceEntityId, + targetEntityId, agentId: runtime.agentId, }); } @@ -53,13 +53,13 @@ export async function formatRelationships({ const formattedRelationships = relationships.map( (relationship: Relationship) => { - const { entityA, entityB } = relationship; + const { sourceEntityId, targetEntityId } = relationship; - if (entityA === userId) { - return entityB; + if (sourceEntityId === userId) { + return targetEntityId; } - return entityA; + return sourceEntityId; } ); diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 2ae8ae03dd9..49fd6e8b3f4 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -1282,16 +1282,16 @@ export class AgentRuntime implements IAgentRuntime { .join("\n\n"); const getRecentInteractions = async ( - entityA: UUID, - entityB: UUID + sourceEntityId: UUID, + targetEntityId: UUID ): Promise => { // Convert to tenant-specific ID if needed const tenantUserA = - entityA === this.agentId ? entityA : this.generateTenantUserId(entityA); + sourceEntityId === this.agentId ? sourceEntityId : this.generateTenantUserId(sourceEntityId); - // Find all rooms where entityA and entityB are participants + // Find all rooms where sourceEntityId and targetEntityId are participants const rooms = await this.databaseAdapter.getRoomsForParticipants( - [tenantUserA, entityB], + [tenantUserA, targetEntityId], this.agentId ); @@ -1675,7 +1675,9 @@ export class AgentRuntime implements IAgentRuntime { if (!model) { throw new Error(`No handler found for delegate type: ${modelClass}`); } - return await model(this, params); + + const response = await model(this, params); + return response; } registerEvent(event: string, handler: (params: any) => void) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 19318962bf6..a5012f9aafa 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -479,18 +479,18 @@ export interface Relationship { id: UUID; /** First user ID */ - entityA: UUID; + sourceEntityId: UUID; /** Second user ID */ - entityB: UUID; + targetEntityId: UUID; - /** Primary user ID */ + /** Agent ID */ agentId: UUID; - /** Any tags (no structured ontology) */ + /** Tags for filtering/categorizing relationships */ tags: string[]; - /** Any metadata you might want to add */ + /** Additional metadata about the relationship */ metadata: { [key: string]: any } @@ -863,7 +863,7 @@ export interface IDatabaseAdapter { memory: Memory, tableName: string, unique?: boolean - ): Promise; + ): Promise; removeMemory(memoryId: UUID, tableName: string): Promise; @@ -949,15 +949,40 @@ export interface IDatabaseAdapter { state: "FOLLOWED" | "MUTED" | null ): Promise; - createRelationship(params: { entityA: UUID; entityB: UUID; agentId: UUID }): Promise; + /** + * Creates a new relationship between two entities. + * @param params Object containing the relationship details + * @returns Promise resolving to boolean indicating success + */ + createRelationship(params: { + sourceEntityId: UUID; + targetEntityId: UUID; + agentId: UUID; + tags?: string[]; + metadata?: { [key: string]: any }; + }): Promise; + /** + * Retrieves a relationship between two entities if it exists. + * @param params Object containing the entity IDs and agent ID + * @returns Promise resolving to the Relationship object or null if not found + */ getRelationship(params: { - entityA: UUID; - entityB: UUID; + sourceEntityId: UUID; + targetEntityId: UUID; agentId: UUID; }): Promise; - getRelationships(params: { userId: UUID; agentId: UUID }): Promise; + /** + * Retrieves all relationships for a specific entity. + * @param params Object containing the user ID, agent ID and optional tags to filter by + * @returns Promise resolving to an array of Relationship objects + */ + getRelationships(params: { + userId: UUID; + agentId: UUID; + tags?: string[]; + }): Promise; createCharacter(character: Character): Promise; @@ -1019,7 +1044,7 @@ export interface IMemoryManager { limit?: number; }): Promise; - createMemory(memory: Memory, unique?: boolean): Promise; + createMemory(memory: Memory, unique?: boolean): Promise; removeMemory(memoryId: UUID): Promise; diff --git a/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql b/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql index 51a5f42bd5a..c6af80d9f43 100644 --- a/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql +++ b/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql @@ -127,8 +127,8 @@ CREATE TABLE "participants" ( CREATE TABLE "relationships" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "createdAt" timestamptz DEFAULT now() NOT NULL, - "entityA" uuid NOT NULL, - "entityB" uuid NOT NULL, + "sourceEntityId" uuid NOT NULL, + "targetEntityId" uuid NOT NULL, "agentId" uuid NOT NULL, "tags" text[], "metadata" jsonb @@ -186,11 +186,11 @@ ALTER TABLE "participants" ADD CONSTRAINT "participants_roomId_rooms_id_fk" FORE ALTER TABLE "participants" ADD CONSTRAINT "participants_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "participants" ADD CONSTRAINT "fk_room" FOREIGN KEY ("roomId") REFERENCES "public"."rooms"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "participants" ADD CONSTRAINT "fk_user" FOREIGN KEY ("userId") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "relationships_entityA_entities_id_fk" FOREIGN KEY ("entityA") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "relationships_entityB_entities_id_fk" FOREIGN KEY ("entityB") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "relationships_sourceEntityId_entities_id_fk" FOREIGN KEY ("sourceEntityId") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "relationships_targetEntityId_entities_id_fk" FOREIGN KEY ("targetEntityId") REFERENCES "public"."entities"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "relationships" ADD CONSTRAINT "relationships_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_a" FOREIGN KEY ("entityA") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_b" FOREIGN KEY ("entityB") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_a" FOREIGN KEY ("sourceEntityId") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relationships" ADD CONSTRAINT "fk_user_b" FOREIGN KEY ("targetEntityId") REFERENCES "public"."entities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "rooms" ADD CONSTRAINT "rooms_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "rooms" ADD CONSTRAINT "rooms_worldId_worlds_id_fk" FOREIGN KEY ("worldId") REFERENCES "public"."worlds"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "worlds" ADD CONSTRAINT "worlds_agentId_agents_id_fk" FOREIGN KEY ("agentId") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint @@ -201,7 +201,7 @@ CREATE INDEX "idx_memories_document_id" ON "memories" USING btree (((metadata->> CREATE INDEX "idx_fragments_order" ON "memories" USING btree (((metadata->>'documentId')),((metadata->>'position')));--> statement-breakpoint CREATE INDEX "idx_participants_user" ON "participants" USING btree ("userId");--> statement-breakpoint CREATE INDEX "idx_participants_room" ON "participants" USING btree ("roomId");--> statement-breakpoint -CREATE INDEX "idx_relationships_users" ON "relationships" USING btree ("entityA","entityB"); +CREATE INDEX "idx_relationships_users" ON "relationships" USING btree ("sourceEntityId","targetEntityId"); CREATE EXTENSION IF NOT EXISTS vector; --> statement-breakpoint diff --git a/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json b/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json index b92a42d9550..4185b7aaf03 100644 --- a/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json +++ b/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json @@ -1206,14 +1206,14 @@ "notNull": true, "default": "now()" }, - "entityA": { - "name": "entityA", + "sourceEntityId": { + "name": "sourceEntityId", "type": "uuid", "primaryKey": false, "notNull": true }, - "entityB": { - "name": "entityB", + "targetEntityId": { + "name": "targetEntityId", "type": "uuid", "primaryKey": false, "notNull": true @@ -1242,13 +1242,13 @@ "name": "idx_relationships_users", "columns": [ { - "expression": "entityA", + "expression": "sourceEntityId", "isExpression": false, "asc": true, "nulls": "last" }, { - "expression": "entityB", + "expression": "targetEntityId", "isExpression": false, "asc": true, "nulls": "last" @@ -1261,12 +1261,12 @@ } }, "foreignKeys": { - "relationships_entityA_entities_id_fk": { - "name": "relationships_entityA_entities_id_fk", + "relationships_sourceEntityId_entities_id_fk": { + "name": "relationships_sourceEntityId_entities_id_fk", "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "entityA" + "sourceEntityId" ], "columnsTo": [ "id" @@ -1274,12 +1274,12 @@ "onDelete": "no action", "onUpdate": "no action" }, - "relationships_entityB_entities_id_fk": { - "name": "relationships_entityB_entities_id_fk", + "relationships_targetEntityId_entities_id_fk": { + "name": "relationships_targetEntityId_entities_id_fk", "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "entityB" + "targetEntityId" ], "columnsTo": [ "id" @@ -1305,7 +1305,7 @@ "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "entityA" + "sourceEntityId" ], "columnsTo": [ "id" @@ -1318,7 +1318,7 @@ "tableFrom": "relationships", "tableTo": "entities", "columnsFrom": [ - "entityB" + "targetEntityId" ], "columnsTo": [ "id" diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index ffc4860db1e..9373b2dba9c 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -348,6 +348,36 @@ export abstract class BaseDrizzleAdapter }); } + async getComponents(entityId: UUID, worldId?: UUID, sourceEntityId?: UUID): Promise { + return this.withDatabase(async () => { + const conditions = [ + eq(componentTable.entityId, entityId) + ]; + + if (worldId) { + conditions.push(eq(componentTable.worldId, worldId)); + } + + if (sourceEntityId) { + conditions.push(eq(componentTable.sourceEntityId, sourceEntityId)); + } + + const result = await this.db + .select({ + id: componentTable.id, + entityId: componentTable.entityId, + type: componentTable.type, + data: componentTable.data, + worldId: componentTable.worldId, + sourceEntityId: componentTable.sourceEntityId, + createdAt: componentTable.createdAt, + }) + .from(componentTable) + .where(and(...conditions)); + return result; + }); + } + async createComponent(component: Component): Promise { return this.withDatabase(async () => { await this.db.insert(componentTable).values(component); @@ -795,7 +825,7 @@ export abstract class BaseDrizzleAdapter }); } - async createMemory(memory: Memory & { metadata?: KnowledgeMetadata }, tableName: string): Promise { + async createMemory(memory: Memory & { metadata?: KnowledgeMetadata }, tableName: string): Promise { logger.debug("DrizzleAdapter createMemory:", { memoryId: memory.id, embeddingLength: memory.embedding?.length, @@ -822,7 +852,7 @@ export abstract class BaseDrizzleAdapter ? JSON.parse(memory.content) : memory.content; - const memoryId = memory.id ?? v4(); + const memoryId = memory.id ?? v4() as UUID; await this.db.transaction(async (tx) => { await tx.insert(memoryTable).values([{ @@ -853,6 +883,8 @@ export abstract class BaseDrizzleAdapter await tx.insert(embeddingTable).values([embeddingValues]); } }); + + return memoryId; } async removeMemory(memoryId: UUID, tableName: string): Promise { @@ -1311,135 +1343,118 @@ export abstract class BaseDrizzleAdapter } async createRelationship(params: { - entityA: UUID; - entityB: UUID; + sourceEntityId: UUID; + targetEntityId: UUID; + agentId: UUID; + tags?: string[]; + metadata?: { [key: string]: any }; }): Promise { - if (!params.entityA || !params.entityB) { - throw new Error("entityA and entityB are required"); - } - return this.withDatabase(async () => { try { - return await this.db.transaction(async (tx) => { - const relationshipId = v4(); - await tx.insert(relationshipTable).values({ - id: relationshipId, - entityA: params.entityA, - entityB: params.entityB, - userId: params.entityA, - }); - - logger.debug("Relationship created successfully:", { - relationshipId, - entityA: params.entityA, - entityB: params.entityB, - }); - - return true; + const id = v4(); + await this.db.insert(relationshipTable).values({ + id, + sourceEntityId: params.sourceEntityId, + targetEntityId: params.targetEntityId, + agentId: params.agentId, + tags: params.tags || [], + metadata: params.metadata || {}, }); + return true; } catch (error) { - if ((error as { code?: string }).code === "23505") { - logger.warn("Relationship already exists:", { - entityA: params.entityA, - entityB: params.entityB, - error: - error instanceof Error - ? error.message - : String(error), - }); - } else { - logger.error("Failed to create relationship:", { - entityA: params.entityA, - entityB: params.entityB, - error: - error instanceof Error - ? error.message - : String(error), - }); - } + logger.error("Error creating relationship:", { + error: error instanceof Error ? error.message : String(error), + params, + }); return false; } }); } async getRelationship(params: { - entityA: UUID; - entityB: UUID; + sourceEntityId: UUID; + targetEntityId: UUID; + agentId: UUID; }): Promise { - if (!params.entityA || !params.entityB) { - throw new Error("entityA and entityB are required"); - } - return this.withDatabase(async () => { try { const result = await this.db .select() .from(relationshipTable) .where( - or( - and( - eq(relationshipTable.entityA, params.entityA), - eq(relationshipTable.entityB, params.entityB) - ), - and( - eq(relationshipTable.entityA, params.entityB), - eq(relationshipTable.entityB, params.entityA) - ) + and( + eq(relationshipTable.sourceEntityId, params.sourceEntityId), + eq(relationshipTable.targetEntityId, params.targetEntityId), + eq(relationshipTable.agentId, params.agentId) ) ) .limit(1); - if (result.length > 0) { - return result[0] as unknown as Relationship; + if (result.length === 0) { + return null; } - logger.debug("No relationship found between users:", { - entityA: params.entityA, - entityB: params.entityB, - }); - return null; + return { + id: result[0].id, + sourceEntityId: result[0].sourceEntityId, + targetEntityId: result[0].targetEntityId, + agentId: result[0].agentId, + tags: result[0].tags || [], + metadata: result[0].metadata || {}, + createdAt: result[0].createdAt?.toString() + }; } catch (error) { - logger.error("Error fetching relationship:", { - entityA: params.entityA, - entityB: params.entityB, - error: - error instanceof Error ? error.message : String(error), + logger.error("Error getting relationship:", { + error: error instanceof Error ? error.message : String(error), + params, }); - throw error; + return null; } }); } - async getRelationships(params: { userId: UUID }): Promise { - if (!params.userId) { - throw new Error("userId is required"); - } + async getRelationships(params: { + userId: UUID; + agentId: UUID; + tags?: string[]; + }): Promise { return this.withDatabase(async () => { try { - const result = await this.db + let query = this.db .select() .from(relationshipTable) .where( - or( - eq(relationshipTable.entityA, params.userId), - eq(relationshipTable.entityB, params.userId) + and( + or( + eq(relationshipTable.sourceEntityId, params.userId), + eq(relationshipTable.targetEntityId, params.userId) + ), + eq(relationshipTable.agentId, params.agentId) ) - ) - .orderBy(desc(relationshipTable.createdAt)); + ); - logger.debug("Retrieved relationships:", { - userId: params.userId, - count: result.length, - }); + // Filter by tags if provided + if (params.tags && params.tags.length > 0) { + query = query.where(sql`${relationshipTable.tags} && ARRAY[${sql.join(params.tags)}]::text[]`); + } - return result as unknown as Relationship[]; + const results = await query; + + return results.map(result => ({ + id: result.id, + sourceEntityId: result.sourceEntityId, + targetEntityId: result.targetEntityId, + agentId: result.agentId, + tags: result.tags || [], + metadata: result.metadata || {}, + createdAt: result.createdAt?.toString() + })); } catch (error) { - logger.error("Failed to fetch relationships:", { - userId: params.userId, - error: - error instanceof Error ? error.message : String(error), + logger.error("Error getting relationships:", { + error: error instanceof Error ? error.message : String(error), + params, }); - throw error; + return []; } }); } diff --git a/packages/plugin-sql/src/pg-lite/manager.ts b/packages/plugin-sql/src/pg-lite/manager.ts index ff78cb4370c..63486efc104 100644 --- a/packages/plugin-sql/src/pg-lite/manager.ts +++ b/packages/plugin-sql/src/pg-lite/manager.ts @@ -118,7 +118,7 @@ export class PGliteClientManager implements IDatabaseClientManager { logger.info("Migrations completed successfully!"); } catch (error) { logger.error("Failed to run database migrations:", error); - throw error; + // throw error; } } } diff --git a/packages/plugin-sql/src/pg/manager.ts b/packages/plugin-sql/src/pg/manager.ts index 37d9018f2c0..bc2ef05f0f3 100644 --- a/packages/plugin-sql/src/pg/manager.ts +++ b/packages/plugin-sql/src/pg/manager.ts @@ -178,7 +178,7 @@ export class PostgresConnectionManager implements IDatabaseClientManager logger.info("Migrations completed successfully!"); } catch (error) { logger.error("Failed to run database migrations:", error); - throw error; + // throw error; } } } diff --git a/packages/plugin-sql/src/schema/relationship.ts b/packages/plugin-sql/src/schema/relationship.ts index 36eabfd6067..4e1ce63a420 100644 --- a/packages/plugin-sql/src/schema/relationship.ts +++ b/packages/plugin-sql/src/schema/relationship.ts @@ -18,10 +18,10 @@ export const relationshipTable = pgTable( createdAt: numberTimestamp("createdAt") .default(sql`now()`) .notNull(), - entityA: uuid("entityA") + sourceEntityId: uuid("sourceEntityId") .notNull() .references(() => entityTable.id), - entityB: uuid("entityB") + targetEntityId: uuid("targetEntityId") .notNull() .references(() => entityTable.id), agentId: uuid("agentId") @@ -31,15 +31,15 @@ export const relationshipTable = pgTable( metadata: jsonb("metadata"), }, (table) => [ - index("idx_relationships_users").on(table.entityA, table.entityB), + index("idx_relationships_users").on(table.sourceEntityId, table.targetEntityId), foreignKey({ name: "fk_user_a", - columns: [table.entityA], + columns: [table.sourceEntityId], foreignColumns: [entityTable.id], }).onDelete("cascade"), foreignKey({ name: "fk_user_b", - columns: [table.entityB], + columns: [table.targetEntityId], foreignColumns: [entityTable.id], }).onDelete("cascade"), ] From 883754be5da0dd48d4ed3d72a4ca9ff2443efeb8 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 28 Feb 2025 01:35:16 -0800 Subject: [PATCH 09/13] relationship provider showing something, create unique entity --- packages/agent/src/server/api/agent.ts | 18 ++-- packages/agent/src/swarm/scenario.ts | 19 +++-- packages/agent/src/swarm/settings.ts | 16 ++-- .../swarm/socialMediaManager/actions/post.ts | 14 ++-- packages/client/tsconfig.app.tsbuildinfo | 2 +- packages/core/src/actions/roles.ts | 65 ++++++++------- packages/core/src/actions/settings.ts | 31 ++++--- packages/core/src/bootstrap.ts | 31 ++++--- packages/core/src/database.ts | 13 +++ packages/core/src/entities.ts | 22 ++++- packages/core/src/evaluators/reflection.ts | 7 +- packages/core/src/generation.ts | 1 + packages/core/src/knowledge.ts | 8 +- packages/core/src/memory.ts | 2 +- packages/core/src/providers/relationships.ts | 57 +++++++++++++ packages/core/src/providers/roles.ts | 6 +- packages/core/src/relationships.ts | 45 ++++++++++ packages/core/src/roles.ts | 24 +----- packages/core/src/runtime.ts | 68 ++++----------- packages/core/src/settings.ts | 8 +- packages/core/src/types.ts | 9 +- .../plugin-discord/src/actions/voiceJoin.ts | 8 +- packages/plugin-discord/src/index.ts | 83 ++++++++----------- packages/plugin-discord/src/messages.ts | 30 +++---- packages/plugin-discord/src/voice.ts | 18 ++-- packages/plugin-sql/src/base.ts | 31 ++++++- .../plugin-telegram/src/messageManager.ts | 31 ++++--- .../plugin-telegram/src/telegramClient.ts | 3 +- packages/plugin-twitter/src/base.ts | 47 +++++------ packages/plugin-twitter/src/index.ts | 8 +- packages/plugin-twitter/src/interactions.ts | 66 ++++++--------- packages/plugin-twitter/src/post.ts | 10 +-- packages/plugin-twitter/src/sttTtsSpaces.ts | 24 +++--- packages/plugin-twitter/src/tests.ts | 15 ++-- packages/plugin-twitter/src/utils.ts | 34 +++----- 35 files changed, 458 insertions(+), 416 deletions(-) create mode 100644 packages/core/src/providers/relationships.ts diff --git a/packages/agent/src/server/api/agent.ts b/packages/agent/src/server/api/agent.ts index dedc7196b25..bc28611d9d3 100644 --- a/packages/agent/src/server/api/agent.ts +++ b/packages/agent/src/server/api/agent.ts @@ -1,5 +1,5 @@ import type { Character, Content, IAgentRuntime, Media, Memory } from '@elizaos/core'; -import { ChannelType, composeContext, generateMessageResponse, logger, messageHandlerTemplate, ModelClass, stringToUuid, validateCharacterConfig } from '@elizaos/core'; +import { ChannelType, composeContext, createUniqueUuid, generateMessageResponse, logger, messageHandlerTemplate, ModelClass, validateCharacterConfig } from '@elizaos/core'; import express from 'express'; import fs from 'node:fs'; import path from 'node:path'; @@ -93,8 +93,8 @@ export function agentRouter( return; } - const roomId = stringToUuid(req.body.roomId ?? `default-room-${agentId}`); - const userId = stringToUuid(req.body.userId ?? "user"); + const roomId = createUniqueUuid(this.runtime, req.body.roomId ?? `default-room-${agentId}`); + const userId = createUniqueUuid(this.runtime, req.body.userId ?? "user"); let runtime = agents.get(agentId); @@ -126,7 +126,7 @@ export function agentRouter( logger.info(`[MESSAGE ENDPOINT] req.body: ${JSON.stringify(req.body)}`); - const messageId = stringToUuid(Date.now().toString()); + const messageId = createUniqueUuid(this.runtime, Date.now().toString()); const attachments: Media[] = []; if (req.file) { @@ -162,7 +162,7 @@ export function agentRouter( }; const memory: Memory = { - id: stringToUuid(`${messageId}-${userId}`), + id: createUniqueUuid(this.runtime, messageId), ...userMessage, agentId: runtime.agentId, userId, @@ -202,7 +202,7 @@ export function agentRouter( // save response to memory const responseMessage: Memory = { - id: stringToUuid(`${messageId}-${runtime.agentId}`), + id: createUniqueUuid(runtime, messageId), ...userMessage, userId: runtime.agentId, content: response, @@ -461,8 +461,8 @@ export function agentRouter( if (!agentId) return; const { text, roomId: rawRoomId, userId: rawUserId } = req.body; - const roomId = stringToUuid(rawRoomId ?? `default-room-${agentId}`); - const userId = stringToUuid(rawUserId ?? "user"); + const roomId = createUniqueUuid(this.runtime, rawRoomId ?? `default-room-${agentId}`); + const userId = createUniqueUuid(this.runtime, rawUserId ?? "user"); if (!text) { res.status(400).send("No text provided"); @@ -492,7 +492,7 @@ export function agentRouter( type: ChannelType.API, }); - const messageId = stringToUuid(Date.now().toString()); + const messageId = createUniqueUuid(this.runtime, Date.now().toString()); const content: Content = { text, diff --git a/packages/agent/src/swarm/scenario.ts b/packages/agent/src/swarm/scenario.ts index 740f0e9502d..3da35053d35 100644 --- a/packages/agent/src/swarm/scenario.ts +++ b/packages/agent/src/swarm/scenario.ts @@ -5,7 +5,7 @@ import { IAgentRuntime, Memory, UUID, - stringToUuid, + createUniqueUuid } from "@elizaos/core"; import { v4 as uuidv4 } from "uuid"; @@ -49,13 +49,13 @@ export class ScenarioClient implements Client { ) { for (const receiver of receivers) { - const participantId = stringToUuid(sender.agentId + "-" + receiver.agentId); const roomData = this.rooms.get(receiver.agentId); if (!roomData) continue; - + const userId = createUniqueUuid(receiver, sender.agentId) + // Ensure connection exists await receiver.ensureConnection({ - userId: participantId, + userId, roomId: roomData.roomId, userName: sender.character.name, userScreenName: sender.character.name, @@ -64,7 +64,7 @@ export class ScenarioClient implements Client { }); const memory: Memory = { - userId: participantId, + userId, agentId: receiver.agentId, roomId: roomData.roomId, content: { @@ -87,14 +87,15 @@ export class ScenarioClient implements Client { ) { for (const receiver of receivers) { - const participantId = stringToUuid(sender.agentId + "-" + receiver.agentId); const roomData = this.rooms.get(receiver.agentId); if (!roomData) continue; + + const userId = createUniqueUuid(receiver, sender.agentId); if (receiver.agentId !== sender.agentId) { // Ensure connection exists await receiver.ensureConnection({ - userId: participantId, + userId, roomId: roomData.roomId, userName: sender.character.name, userScreenName: sender.character.name, @@ -113,7 +114,7 @@ export class ScenarioClient implements Client { } const memory: Memory = { - userId: receiver.agentId !== sender.agentId ? participantId : sender.agentId, + userId: receiver.agentId !== sender.agentId ? userId : sender.agentId, agentId: receiver.agentId, roomId: roomData.roomId, content: { @@ -128,7 +129,7 @@ export class ScenarioClient implements Client { runtime: receiver, message: memory, roomId: roomData.roomId, - userId: receiver.agentId !== sender.agentId ? participantId : sender.agentId, + userId: receiver.agentId !== sender.agentId ? userId : sender.agentId, source: "scenario", type: ChannelType.GROUP, }); diff --git a/packages/agent/src/swarm/settings.ts b/packages/agent/src/swarm/settings.ts index 442012d1ba0..46a346d8737 100644 --- a/packages/agent/src/swarm/settings.ts +++ b/packages/agent/src/swarm/settings.ts @@ -1,6 +1,7 @@ import { Action, ChannelType, + createUniqueUuid, Evaluator, type IAgentRuntime, initializeOnboardingConfig, @@ -8,7 +9,6 @@ import { type OnboardingConfig, Provider, RoleName, - stringToUuid, type UUID } from "@elizaos/core"; import type { Guild } from "discord.js"; @@ -77,13 +77,8 @@ export async function initializeAllSystems( try { for (const server of servers) { - const worldId = stringToUuid(`${server.id}-${runtime.agentId}`); - - const ownerId = stringToUuid( - `${server.ownerId}-${runtime.agentId}` - ); - - const tenantSpecificOwnerId = runtime.generateTenantUserId(ownerId); + const worldId = createUniqueUuid(runtime, server.id); + const ownerId = createUniqueUuid(runtime, server.ownerId); await runtime.ensureWorldExists({ id: worldId, @@ -93,7 +88,7 @@ export async function initializeAllSystems( metadata: { ownership: server.ownerId ? { ownerId } : undefined, roles: { - [tenantSpecificOwnerId]: RoleName.OWNER, + [ownerId]: RoleName.OWNER, }, } }); @@ -151,7 +146,7 @@ export async function startOnboardingDM( const randomMessage = onboardingMessages[Math.floor(Math.random() * onboardingMessages.length)]; const msg = await owner.send(randomMessage); - const roomId = stringToUuid(`${msg.channel.id}-${runtime.agentId}`); + const roomId = createUniqueUuid(runtime, msg.channel.id); await runtime.ensureRoomExists({ id: roomId, @@ -170,7 +165,6 @@ export async function startOnboardingDM( default: { name: runtime.character.name, userName: runtime.character.name, - originalUserId: runtime.agentId, }, }, ); diff --git a/packages/agent/src/swarm/socialMediaManager/actions/post.ts b/packages/agent/src/swarm/socialMediaManager/actions/post.ts index f09cfadbd29..07752bd5339 100644 --- a/packages/agent/src/swarm/socialMediaManager/actions/post.ts +++ b/packages/agent/src/swarm/socialMediaManager/actions/post.ts @@ -9,11 +9,10 @@ import { RoleName, type State, composeContext, + createUniqueUuid, generateText, getWorldSettings, - logger, - normalizeUserId, - stringToUuid + logger } from "@elizaos/core"; /** @@ -25,18 +24,15 @@ export async function getUserServerRole( serverId: string ): Promise { try { - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const worldId = createUniqueUuid(this.runtime, serverId); const world = await runtime.getWorld(worldId); if (!world || !world.metadata?.roles) { return RoleName.NONE; } - // Check both formats (UUID and original ID) - const normalizedUserId = normalizeUserId(userId); - - if (world.metadata.roles[normalizedUserId]?.role) { - return world.metadata.roles[normalizedUserId].role as RoleName; + if (world.metadata.roles[userId]?.role) { + return world.metadata.roles[userId].role as RoleName; } // Also check original ID format diff --git a/packages/client/tsconfig.app.tsbuildinfo b/packages/client/tsconfig.app.tsbuildinfo index f4f70251c18..0cb74826b7e 100644 --- a/packages/client/tsconfig.app.tsbuildinfo +++ b/packages/client/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/app-sidebar.tsx","./src/components/array-input.tsx","./src/components/audio-recorder.tsx","./src/components/chat.tsx","./src/components/connection-status.tsx","./src/components/copy-button.tsx","./src/components/input-copy.tsx","./src/components/overview.tsx","./src/components/page-title.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/tooltip.tsx","./src/components/ui/chat/chat-bubble.tsx","./src/components/ui/chat/chat-input.tsx","./src/components/ui/chat/chat-message-list.tsx","./src/components/ui/chat/chat-tts-button.tsx","./src/components/ui/chat/expandable-chat.tsx","./src/components/ui/chat/message-loading.tsx","./src/components/ui/chat/hooks/useautoscroll.tsx","./src/hooks/use-mobile.tsx","./src/hooks/use-toast.ts","./src/hooks/use-version.tsx","./src/lib/api.ts","./src/lib/utils.ts","./src/routes/chat.tsx","./src/routes/home.tsx","./src/routes/overview.tsx","./src/types/index.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/app-sidebar.tsx","./src/components/array-input.tsx","./src/components/audio-recorder.tsx","./src/components/chat.tsx","./src/components/connection-status.tsx","./src/components/copy-button.tsx","./src/components/input-copy.tsx","./src/components/overview.tsx","./src/components/page-title.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/separator.tsx","./src/components/ui/sheet.tsx","./src/components/ui/sidebar.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/toast.tsx","./src/components/ui/toaster.tsx","./src/components/ui/tooltip.tsx","./src/components/ui/chat/chat-bubble.tsx","./src/components/ui/chat/chat-input.tsx","./src/components/ui/chat/chat-message-list.tsx","./src/components/ui/chat/chat-tts-button.tsx","./src/components/ui/chat/expandable-chat.tsx","./src/components/ui/chat/message-loading.tsx","./src/components/ui/chat/hooks/useautoscroll.tsx","./src/hooks/use-mobile.tsx","./src/hooks/use-plugins.ts","./src/hooks/use-toast.ts","./src/hooks/use-version.tsx","./src/lib/api.ts","./src/lib/utils.ts","./src/routes/chat.tsx","./src/routes/home.tsx","./src/routes/overview.tsx","./src/types/index.ts"],"version":"5.6.3"} \ No newline at end of file diff --git a/packages/core/src/actions/roles.ts b/packages/core/src/actions/roles.ts index 8e7ac902dd6..ec36916dcee 100644 --- a/packages/core/src/actions/roles.ts +++ b/packages/core/src/actions/roles.ts @@ -1,8 +1,7 @@ -import { generateObjectArray } from ".."; +import { createUniqueUuid, generateObjectArray } from ".."; import { composeContext } from "../context"; import { logger } from "../logger"; import { Action, ActionExample, ChannelType, HandlerCallback, IAgentRuntime, Memory, ModelClass, RoleName, State, UUID } from "../types"; -import { stringToUuid } from "../uuid"; // Role modification validation helper const canModifyRole = ( @@ -102,12 +101,11 @@ const updateRoleAction: Action = { } try { // Get world data instead of ownership state from cache - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, serverId); const world = await runtime.getWorld(worldId); // Get requester ID and convert to UUID for consistent lookup const requesterId = message.userId; - const tenantSpecificUserId = runtime.generateTenantUserId(requesterId); // Get roles from world metadata if (!world.metadata?.roles) { @@ -116,9 +114,9 @@ const updateRoleAction: Action = { } // Lookup using UUID for consistency - const requesterRole = world.metadata.roles[tenantSpecificUserId] as RoleName + const requesterRole = world.metadata.roles[requesterId] as RoleName - logger.info(`Requester ${tenantSpecificUserId} role:`, requesterRole); + logger.info(`Requester ${requesterId} role:`, requesterRole); if (!requesterRole) { logger.info("Validation failed: No requester role found"); @@ -147,24 +145,21 @@ const updateRoleAction: Action = { callback: HandlerCallback, responses: Memory[] ): Promise => { + console.log("**** UPDATE_ROLE handler") // Handle initial responses for (const response of responses) { await callback(response.content); } const room = await runtime.getRoom(message.roomId); + const world = await runtime.getWorld(room.worldId); if (!room) { throw new Error("No room found"); } - const serverId = room.serverId; + const serverId = world.serverId; const requesterId = message.userId; - const tenantSpecificRequesterId = runtime.generateTenantUserId(requesterId); - - // Get world data instead of role cache - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); - let world = await runtime.getWorld(worldId); if (!world || !world.metadata) { logger.error(`No world or metadata found for server ${serverId}`); @@ -183,15 +178,22 @@ const updateRoleAction: Action = { // Get requester's role from world metadata const requesterRole = - (world.metadata.roles[tenantSpecificRequesterId] as RoleName) || RoleName.NONE; - - const discordClient = runtime.getClient("discord").client; - const guild = await discordClient.guilds.fetch(serverId); - - // Build server members context - const members = await guild.members.fetch(); - const serverMembersContext = Array.from(members.values()) - .map((member: any) => `${member.username} (${member.id})`) + (world.metadata.roles[requesterId] as RoleName) || RoleName.NONE; + + // Get all entities in the room + const entities = await runtime.databaseAdapter.getEntitiesForRoom(room.id, runtime.agentId, true); + + console.log('***** entities are') + console.log(entities) + + // Build server members context from entities + const serverMembersContext = entities + .map(entity => { + const discordData = entity.components?.find(c => c.type === 'discord')?.data; + const name = discordData?.username || entity.names[0]; + const id = entity.id; + return `${name} (${id})`; + }) .join("\n"); // Create extraction context @@ -204,6 +206,8 @@ const updateRoleAction: Action = { template: extractionTemplate, }); + console.log('****** extractionContext\n', extractionContext) + // Extract role assignments const result = (await generateObjectArray({ runtime, @@ -225,16 +229,21 @@ const updateRoleAction: Action = { let worldUpdated = false; for (const assignment of result) { - const targetUser = members.get(assignment.userId); - if (!targetUser) continue; + let targetEntity = entities.find(e => e.id === assignment.userId); + if(!targetEntity) { + targetEntity = entities.find(e => e.id === assignment.userId); + console.log("Trying to write to generated tenant ID") + } + if (!targetEntity) { + console.log("Could not find an ID ot assign to") + } - const tenantSpecificTargetId = runtime.generateTenantUserId(assignment.userId); - const currentRole = world.metadata.roles[tenantSpecificTargetId]; + const currentRole = world.metadata.roles[assignment.userId]; // Validate role modification permissions if (!canModifyRole(requesterRole, currentRole, assignment.newRole)) { await callback({ - text: `You don't have permission to change ${targetUser.user.username}'s role to ${assignment.newRole}.`, + text: `You don't have permission to change ${targetEntity.names[0]}'s role to ${assignment.newRole}.`, action: "UPDATE_ROLE", source: "discord", }); @@ -242,12 +251,12 @@ const updateRoleAction: Action = { } // Update role in world metadata - world.metadata.roles[tenantSpecificTargetId] = assignment.newRole; + world.metadata.roles[assignment.userId] = assignment.newRole; worldUpdated = true; await callback({ - text: `Updated ${targetUser.user.username}'s role to ${assignment.newRole}.`, + text: `Updated ${targetEntity.names[0]}'s role to ${assignment.newRole}.`, action: "UPDATE_ROLE", source: "discord", }); diff --git a/packages/core/src/actions/settings.ts b/packages/core/src/actions/settings.ts index 95693691587..cd6ba33dd18 100644 --- a/packages/core/src/actions/settings.ts +++ b/packages/core/src/actions/settings.ts @@ -1,21 +1,21 @@ import { composeContext } from "../context"; +import { createUniqueUuid } from "../entities"; import { generateMessageResponse, generateObjectArray } from "../generation"; import { logger } from "../logger"; import { messageCompletionFooter } from "../parsing"; -import { findWorldForOwner, normalizeUserId } from "../roles"; +import { findWorldForOwner } from "../roles"; import { - Action, - ActionExample, - ChannelType, - HandlerCallback, - IAgentRuntime, - Memory, - ModelClass, - OnboardingSetting, - WorldSettings, - State, + Action, + ActionExample, + ChannelType, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + OnboardingSetting, + State, + WorldSettings, } from "../types"; -import { stringToUuid } from "../uuid"; interface SettingUpdate { key: string; @@ -63,7 +63,7 @@ export async function getWorldSettings( serverId: string ): Promise { try { - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, serverId); const world = await runtime.getWorld(worldId); if (!world || !world.metadata?.settings) { @@ -86,7 +86,7 @@ export async function updateWorldSettings( worldSettings: WorldSettings ): Promise { try { - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, serverId); const world = await runtime.getWorld(worldId); if (!world) { @@ -600,9 +600,8 @@ const updateSettingsAction: Action = { } // Log the user ID for debugging - const normalizedUserId = normalizeUserId(message.userId); logger.info( - `Validating settings action for user ${message.userId} (normalized: ${normalizedUserId})` + `Validating settings action for user ${message.userId} (normalized: ${message.userId})` ); // Validate that we're in a DM channel diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index c2643745111..3017f1719c8 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -1,19 +1,19 @@ import { UUID } from "crypto"; import { v4 } from "uuid"; -import { updateEntityAction } from "./actions/updateEntity.ts"; -import { sendMessageAction } from "./actions/sendMessage.ts" import { followRoomAction } from "./actions/followRoom.ts"; import { ignoreAction } from "./actions/ignore.ts"; import { muteRoomAction } from "./actions/muteRoom.ts"; import { noneAction } from "./actions/none.ts"; import { selectOptionAction } from "./actions/options.ts"; import updateRoleAction from "./actions/roles.ts"; +import { sendMessageAction } from "./actions/sendMessage.ts"; import updateSettingsAction from "./actions/settings.ts"; import { unfollowRoomAction } from "./actions/unfollowRoom.ts"; import { unmuteRoomAction } from "./actions/unmuteRoom.ts"; +import { updateEntityAction } from "./actions/updateEntity.ts"; import { composeContext } from "./context.ts"; -import { reflectionEvaluator } from "./evaluators/reflection.ts"; import { goalEvaluator } from "./evaluators/goal.ts"; +import { reflectionEvaluator } from "./evaluators/reflection.ts"; import { formatMessages, generateMessageResponse, @@ -24,6 +24,7 @@ import { logger } from "./logger.ts"; import { messageCompletionFooter, shouldRespondFooter } from "./parsing.ts"; import { factsProvider } from "./providers/facts.ts"; import { optionsProvider } from "./providers/options.ts"; +import { relationshipsProvider } from "./providers/relationships.ts"; import { roleProvider } from "./providers/roles.ts"; import { settingsProvider } from "./providers/settings.ts"; import { timeProvider } from "./providers/time.ts"; @@ -40,7 +41,7 @@ import { State, WorldData, } from "./types.ts"; -import { stringToUuid } from "./uuid.ts"; +import { createUniqueUuid } from "./entities.ts"; type ServerJoinedParams = { runtime: IAgentRuntime; @@ -252,9 +253,7 @@ const messageReceivedHandler = async ({ } responseContent.text = responseContent.text?.trim(); - responseContent.inReplyTo = stringToUuid( - `${message.id}-${runtime.agentId}` - ); + responseContent.inReplyTo = createUniqueUuid(runtime, message.id); const responseMessages: Memory[] = [ { @@ -311,9 +310,8 @@ const syncServerUsers = async ( try { // Create/ensure the world exists for this server - const worldId = stringToUuid(`${server.id}-${runtime.agentId}`); - - const ownerId = stringToUuid(`${server.ownerId}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, server.id); + const ownerId = createUniqueUuid(runtime, server.ownerId); await runtime.ensureWorldExists({ id: worldId, @@ -476,14 +474,14 @@ const syncServerChannels = async ( try { if (source === "discord") { const guild = await server.fetch(); - const worldId = stringToUuid(`${guild.id}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, guild.id); // Loop through all channels and create room entities for (const [channelId, channel] of guild.channels.cache) { // Only process text and voice channels if (channel.type === 0 || channel.type === 2) { // GUILD_TEXT or GUILD_VOICE - const roomId = stringToUuid(`${channelId}-${runtime.agentId}`); + const roomId = createUniqueUuid(runtime, channelId); const room = await runtime.getRoom(roomId); // Skip if room already exists @@ -603,8 +601,8 @@ const syncSingleUser = async ( return; } - const roomId = stringToUuid(`${channelId}-${runtime.agentId}`); - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const roomId = createUniqueUuid(runtime, channelId); + const worldId = createUniqueUuid(runtime, serverId); await runtime.ensureConnection({ userId: user.id, @@ -739,8 +737,8 @@ const syncMultipleUsers = async ( logger.info(`Syncing ${users.length} users for channel ${channelId}`); try { - const roomId = stringToUuid(`${channelId}-${runtime.agentId}`); - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const roomId = createUniqueUuid(runtime, channelId); + const worldId = createUniqueUuid(runtime, serverId); // Process users in batches to avoid overwhelming the system const batchSize = 10; for (let i = 0; i < users.length; i += batchSize) { @@ -858,6 +856,7 @@ export const bootstrapPlugin: Plugin = { optionsProvider, roleProvider, settingsProvider, + relationshipsProvider, ], }; diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index 11272607b07..9483cc61b8d 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -449,6 +449,19 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { tags?: string[]; }): Promise; + /** + * Updates an existing relationship between two users. + * @param params Object containing the relationship details to update including entity IDs, agent ID, optional tags and metadata + * @returns A Promise that resolves to a boolean indicating success or failure of the update. + */ + abstract updateRelationship(params: { + sourceEntityId: UUID; + targetEntityId: UUID; + agentId: UUID; + tags?: string[]; + metadata?: { [key: string]: any }; + }): Promise; + /** * Creates a new character in the database. * @param character The Character object to create. diff --git a/packages/core/src/entities.ts b/packages/core/src/entities.ts index e3aa12ec429..8f743adb89d 100644 --- a/packages/core/src/entities.ts +++ b/packages/core/src/entities.ts @@ -1,5 +1,5 @@ import { composeContext } from "./context.ts"; -import { logger } from "./index.ts"; +import { logger, stringToUuid } from "./index.ts"; import { parseJSONObjectFromText } from "./parsing"; import { type Entity, @@ -124,8 +124,8 @@ async function getRecentInteractions( (rel.targetEntityId === sourceEntityId && rel.sourceEntityId === entity.id) ); - if (relationship?.metadata?.interactionStrength) { - interactionScore = relationship.metadata.interactionStrength; + if (relationship?.metadata?.interactions) { + interactionScore = relationship.metadata.interactions; } // Add bonus points for recent direct replies @@ -213,7 +213,7 @@ export async function findEntityByName( // Format interaction data for LLM context const recentInteractions = interactionData.map(data => ({ entityName: data.entity.names[0], - interactionStrength: data.count, + interactions: data.count, recentMessages: data.interactions.map(msg => ({ from: msg.userId === message.userId ? "sender" : "entity", text: msg.content.text @@ -302,4 +302,18 @@ export async function findEntityByName( logger.error("Error in findEntityByName:", error); return null; } +} + +export const createUniqueUuid = (runtime, baseUserId: UUID | string): UUID => { + // If the base user ID is the agent ID, return it directly + if (baseUserId === runtime.agentId) { + return runtime.agentId; + } + + // Use a deterministic approach to generate a new UUID based on both IDs + // This creates a unique ID for each user+agent combination while still being deterministic + const combinedString = `${baseUserId}:${runtime.agentId}`; + + // Create a namespace UUID (version 5) from the combined string + return stringToUuid(combinedString); } \ No newline at end of file diff --git a/packages/core/src/evaluators/reflection.ts b/packages/core/src/evaluators/reflection.ts index 940573e0074..d7a590e203a 100644 --- a/packages/core/src/evaluators/reflection.ts +++ b/packages/core/src/evaluators/reflection.ts @@ -19,7 +19,7 @@ const reflectionSchema = z.object({ reflection: z.string(), facts: z.array(z.object({ claim: z.string(), - type: z.enum(["fact", "opinion", "status"]), + type: z.string(), in_bio: z.boolean(), already_known: z.boolean(), })), @@ -136,6 +136,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { // Update or create relationships for (const relationship of reflection.relationships) { + console.log("*** resolving relationship", relationship); let sourceId: UUID; let targetId: UUID; @@ -165,8 +166,8 @@ async function handler(runtime: IAgentRuntime, message: Memory) { ...relationship.tags ])); - // TODO, switch to updateRelationship - await runtime.databaseAdapter.createRelationship({ + await runtime.databaseAdapter.updateRelationship({ + id: existingRelationship.id, sourceEntityId: sourceId, targetEntityId: targetId, agentId, diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index ddc49016da6..fba30ca20aa 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -396,6 +396,7 @@ export async function generateMessageResponse({ modelClass: ModelClass; stopSequences?: string[]; }): Promise { + console.log('*** generateMessageResponse', context) return await withRetry(async () => { const text = await runtime.useModel(modelClass, { runtime, diff --git a/packages/core/src/knowledge.ts b/packages/core/src/knowledge.ts index 3a09f875d97..277827cf748 100644 --- a/packages/core/src/knowledge.ts +++ b/packages/core/src/knowledge.ts @@ -1,8 +1,8 @@ -import { splitChunks } from "./parsing.ts"; +import { createUniqueUuid } from "./entities.ts"; import logger from "./logger.ts"; +import { splitChunks } from "./parsing.ts"; import type { AgentRuntime } from "./runtime.ts"; -import { type KnowledgeItem, type Memory, ModelClass, type UUID, MemoryType } from "./types.ts"; -import { stringToUuid } from "./uuid.ts"; +import { type KnowledgeItem, type Memory, MemoryType, ModelClass, type UUID } from "./types.ts"; async function get( runtime: AgentRuntime, @@ -105,7 +105,7 @@ async function set( // Store each fragment with link to source document for (let i = 0; i < fragments.length; i++) { const fragmentMemory: Memory = { - id: stringToUuid(`${item.id}-fragment-${i}`), + id: createUniqueUuid(this.runtime, `${item.id}-fragment-${i}`), agentId: runtime.agentId, roomId: runtime.agentId, userId: runtime.agentId, diff --git a/packages/core/src/memory.ts b/packages/core/src/memory.ts index 5db16f073c6..8e1b507c6f9 100644 --- a/packages/core/src/memory.ts +++ b/packages/core/src/memory.ts @@ -70,7 +70,7 @@ export class MemoryManager implements IMemoryManager { private transformUserIdIfNeeded(memory: Memory): Memory { return { ...memory, - userId: this.runtime.generateTenantUserId(memory.userId) + userId: memory.userId }; } diff --git a/packages/core/src/providers/relationships.ts b/packages/core/src/providers/relationships.ts new file mode 100644 index 00000000000..a02dd191944 --- /dev/null +++ b/packages/core/src/providers/relationships.ts @@ -0,0 +1,57 @@ +import { getRelationships } from "../relationships.ts"; +import { IAgentRuntime, Memory, Provider, State, Relationship } from "../types.ts"; + +function formatRelationships(relationships: Relationship[]) { + // Sort relationships by interaction strength (descending) + const sortedRelationships = relationships + .filter(rel => rel.metadata?.interactions) + .sort((a, b) => + (b.metadata?.interactions || 0) - (a.metadata?.interactions || 0) + ) + .slice(0, 30); // Get top 30 + + if (sortedRelationships.length === 0) { + return ""; + } + + console.log("*** sortedRelationships", sortedRelationships) + + return sortedRelationships + .map((rel, index) => { + const strength = rel.metadata?.interactions || 0; + const name = rel.metadata?.name || "Unknown"; + const description = rel.metadata?.description || ""; + return `${index + 1}. ${name} (Interaction Strength: ${strength})${description ? ` - ${description}` : ""}`; + }) + .join("\n"); +} + +const relationshipsProvider: Provider = { + get: async (runtime: IAgentRuntime, message: Memory, state?: State) => { + // Get all relationships for the current user + const relationships = await getRelationships({ + runtime, + userId: message.userId, + }); + + console.log("*** relationships", relationships) + + if (!relationships || relationships.length === 0) { + return ""; + } + + const formattedRelationships = formatRelationships(relationships); + + console.log("*** formattedRelationships", formattedRelationships) + + if (!formattedRelationships) { + return ""; + } + + console.log('******* formattedRelationships', formattedRelationships) + + return `Top relationships that ${runtime.character.name} has observed:\n${formattedRelationships}`; + }, +}; + +export { relationshipsProvider }; \ No newline at end of file diff --git a/packages/core/src/providers/roles.ts b/packages/core/src/providers/roles.ts index fc1c06e80ae..30d40de4ec1 100644 --- a/packages/core/src/providers/roles.ts +++ b/packages/core/src/providers/roles.ts @@ -1,6 +1,6 @@ +import { createUniqueUuid } from "../entities"; import { logger } from "../logger"; -import { Provider, IAgentRuntime, Memory, State, ChannelType, UUID } from "../types"; -import { stringToUuid } from "../uuid"; +import { ChannelType, IAgentRuntime, Memory, Provider, State, UUID } from "../types"; export const roleProvider: Provider = { get: async ( @@ -27,7 +27,7 @@ export const roleProvider: Provider = { logger.info(`Using server ID: ${serverId}`); // Get world data instead of using cache - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, serverId); const world = await runtime.getWorld(worldId); if (!world || !world.metadata?.ownership?.ownerId) { diff --git a/packages/core/src/relationships.ts b/packages/core/src/relationships.ts index aa32e12b04c..d7924ff11d6 100644 --- a/packages/core/src/relationships.ts +++ b/packages/core/src/relationships.ts @@ -4,18 +4,63 @@ export async function createRelationship({ runtime, sourceEntityId, targetEntityId, + metadata = {}, }: { runtime: IAgentRuntime; sourceEntityId: UUID; targetEntityId: UUID; + metadata?: { [key: string]: any }; }): Promise { return runtime.databaseAdapter.createRelationship({ sourceEntityId, targetEntityId, agentId: runtime.agentId, + metadata, }); } +export async function updateRelationshipInteractionStrength({ + runtime, + sourceEntityId, + targetEntityId, + increment = 1, +}: { + runtime: IAgentRuntime; + sourceEntityId: UUID; + targetEntityId: UUID; + increment?: number; +}): Promise { + // Get existing relationship + let relationship = await getRelationship({ + runtime, + sourceEntityId, + targetEntityId, + }); + + if (!relationship) { + // Create new relationship if it doesn't exist + await createRelationship({ + runtime, + sourceEntityId, + targetEntityId, + metadata: { + interactions: increment, + }, + }); + return; + } + + // Update interaction strength + const currentStrength = relationship.metadata?.interactions || 0; + relationship.metadata = { + ...relationship.metadata, + interactions: currentStrength + increment, + }; + + // Update the relationship in the database + await runtime.databaseAdapter.updateRelationship(relationship); +} + export async function getRelationship({ runtime, sourceEntityId, diff --git a/packages/core/src/roles.ts b/packages/core/src/roles.ts index 068ca1e4d9f..1782f74192f 100644 --- a/packages/core/src/roles.ts +++ b/packages/core/src/roles.ts @@ -3,7 +3,6 @@ import { logger } from "./logger"; import { IAgentRuntime, WorldData } from "./types"; -import { stringToUuid } from "./uuid"; export interface ServerOwnershipState { servers: { @@ -11,18 +10,6 @@ export interface ServerOwnershipState { }; } -/** - * Normalizes user IDs to UUID format - * Both stringToUuid and direct values are supported for robustness - */ -export function normalizeUserId(id: string): string { - // Avoid double-conversion by checking if already a UUID format - if (id.includes("-") && id.length === 36) { - return id; - } - return stringToUuid(id); -} - /** * Finds a server where the given user is the owner */ @@ -36,11 +23,6 @@ export async function findWorldForOwner( return null; } - const normalizedUserId = normalizeUserId(userId); - logger.info( - `Looking for server where ${normalizedUserId} is owner (original ID: ${userId})` - ); - // Get all worlds for this agent const worlds = await runtime.getAllWorlds(); @@ -51,9 +33,9 @@ export async function findWorldForOwner( // Find world where the user is the owner for (const world of worlds) { - if (world.metadata?.ownership?.ownerId === normalizedUserId) { + if (world.metadata?.ownership?.ownerId === userId) { logger.info( - `Found server ${world.serverId} for owner ${normalizedUserId}` + `Found server ${world.serverId} for owner ${userId}` ); return world; } @@ -67,7 +49,7 @@ export async function findWorldForOwner( } } - logger.info(`No server found for owner ${normalizedUserId}`); + logger.info(`No server found for owner ${userId}`); return null; } catch (error) { logger.error(`Error finding server for owner: ${error}`); diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 49fd6e8b3f4..2dba60d7ba1 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -16,7 +16,7 @@ import { formatEvaluators, } from "./evaluators.ts"; import { generateText } from "./generation.ts"; -import { handlePluginImporting, logger } from "./index.ts"; +import { createUniqueUuid, handlePluginImporting, logger } from "./index.ts"; import knowledge from "./knowledge.ts"; import { MemoryManager } from "./memory.ts"; import { formatActors, formatMessages, getActorDetails } from "./messages.ts"; @@ -85,7 +85,7 @@ class KnowledgeManager { async processCharacterKnowledge(items: string[]) { for (const item of items) { try { - const knowledgeId = stringToUuid(item); + const knowledgeId = createUniqueUuid(this.runtime, item); if (await this.checkExistingKnowledge(knowledgeId)) { continue; } @@ -436,9 +436,7 @@ export class AgentRuntime implements IAgentRuntime { [this.character.name].filter(Boolean) ) ) as string[], - metadata: { - originalUserId: this.agentId, - }, + metadata: {}, }); if (!created) { @@ -536,7 +534,7 @@ export class AgentRuntime implements IAgentRuntime { // Create room for the agent try { await this.ensureRoomExists({ - id: this.generateTenantUserId(this.agentId), + id: this.agentId, name: this.character.name, source: "self", type: ChannelType.SELF, @@ -621,20 +619,6 @@ export class AgentRuntime implements IAgentRuntime { ); } - generateTenantUserId(baseUserId: UUID): UUID { - // If the base user ID is the agent ID, return it directly - if (baseUserId === this.agentId) { - return this.agentId; - } - - // Use a deterministic approach to generate a new UUID based on both IDs - // This creates a unique ID for each user+agent combination while still being deterministic - const combinedString = `${baseUserId}:${this.agentId}`; - - // Create a namespace UUID (version 5) from the combined string - return stringToUuid(combinedString); - } - async ensureAgentExists() { const agent = await this.databaseAdapter.getAgent(this.agentId); if (!agent) { @@ -895,20 +879,16 @@ export class AgentRuntime implements IAgentRuntime { [source: string]: { name: string; userName: string; - originalUserId: UUID; }; } ) { - // Generate tenant-specific user ID - apply the transformation - const tenantSpecificUserId = this.generateTenantUserId(userId); - const account = await this.databaseAdapter.getEntityById( - tenantSpecificUserId, + userId, this.agentId ); if (!account) { const created = await this.databaseAdapter.createEntity({ - id: tenantSpecificUserId, + id: userId, agentId: this.agentId, names, metadata, @@ -926,20 +906,17 @@ export class AgentRuntime implements IAgentRuntime { ); } - return tenantSpecificUserId; + return userId; } async ensureParticipantInRoom(userId: UUID, roomId: UUID) { - // Always get the tenant-specific user ID using our helper method - const tenantSpecificUserId = this.generateTenantUserId(userId); - // Make sure entity exists in database before adding as participant const entity = await this.databaseAdapter.getEntityById( - tenantSpecificUserId, + userId, this.agentId ); if(!entity) { - throw new Error(`User ${tenantSpecificUserId} not found`); + throw new Error(`User ${userId} not found`); } // Get current participants const participants = await this.databaseAdapter.getParticipantsForRoom( @@ -948,17 +925,17 @@ export class AgentRuntime implements IAgentRuntime { ); // Only add if not already a participant - if (!participants.includes(tenantSpecificUserId)) { + if (!participants.includes(userId)) { // Add participant using the tenant-specific ID that now exists in the entities table const added = await this.databaseAdapter.addParticipant( - tenantSpecificUserId, + userId, roomId, this.agentId ); if (!added) { throw new Error( - `Failed to add participant ${tenantSpecificUserId} to room ${roomId}` + `Failed to add participant ${userId} to room ${roomId}` ); } @@ -968,7 +945,7 @@ export class AgentRuntime implements IAgentRuntime { ); } else { logger.log( - `User ${tenantSpecificUserId} linked to room ${roomId} successfully.` + `User ${userId} linked to room ${roomId} successfully.` ); } } @@ -984,7 +961,6 @@ export class AgentRuntime implements IAgentRuntime { channelId, serverId, worldId, - originalUserId, }: { userId: UUID; roomId: UUID; @@ -995,14 +971,13 @@ export class AgentRuntime implements IAgentRuntime { channelId?: string; serverId?: string; worldId?: UUID; - originalUserId?: UUID; }) { if (userId === this.agentId) { throw new Error("Agent should not connect to itself"); } if (!worldId && serverId) { - worldId = stringToUuid(`${serverId}-${this.agentId}`); + worldId = createUniqueUuid(this, serverId); } const names = [userScreenName, userName] @@ -1010,7 +985,6 @@ export class AgentRuntime implements IAgentRuntime { [source]: { name: userScreenName, userName: userName, - originalUserId, }, }; @@ -1163,12 +1137,6 @@ export class AgentRuntime implements IAgentRuntime { message: Memory, additionalKeys: { [key: string]: unknown } = {} ) { - // Convert user ID to tenant-specific ID if needed - const tenantSpecificUserId = - message.userId === this.agentId - ? message.userId - : this.generateTenantUserId(message.userId); - const { roomId } = message; const conversationLength = this.getConversationLength(); @@ -1196,7 +1164,7 @@ export class AgentRuntime implements IAgentRuntime { }); const senderName = actorsData?.find( - (actor: Actor) => actor.id === tenantSpecificUserId + (actor: Actor) => actor.id === message.userId )?.name; // TODO: We may wish to consolidate and just accept character.name here instead of the actor name @@ -1285,13 +1253,9 @@ export class AgentRuntime implements IAgentRuntime { sourceEntityId: UUID, targetEntityId: UUID ): Promise => { - // Convert to tenant-specific ID if needed - const tenantUserA = - sourceEntityId === this.agentId ? sourceEntityId : this.generateTenantUserId(sourceEntityId); - // Find all rooms where sourceEntityId and targetEntityId are participants const rooms = await this.databaseAdapter.getRoomsForParticipants( - [tenantUserA, targetEntityId], + [sourceEntityId, targetEntityId], this.agentId ); diff --git a/packages/core/src/settings.ts b/packages/core/src/settings.ts index 7552b613ab1..8ae76282a06 100644 --- a/packages/core/src/settings.ts +++ b/packages/core/src/settings.ts @@ -1,6 +1,6 @@ +import { createUniqueUuid } from "./entities"; import { logger } from "./logger"; -import { OnboardingSetting, IAgentRuntime, WorldSettings, OnboardingConfig, WorldData } from "./types"; -import { stringToUuid } from "./uuid"; +import { IAgentRuntime, OnboardingConfig, OnboardingSetting, WorldData, WorldSettings } from "./types"; function createSettingFromConfig( configSetting: Omit @@ -29,7 +29,7 @@ export async function updateWorldSettings( worldSettings: WorldSettings ): Promise { try { - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, serverId); const world = await runtime.getWorld(worldId); if (!world) { @@ -63,7 +63,7 @@ export async function getWorldSettings( serverId: string ): Promise { try { - const worldId = stringToUuid(`${serverId}-${runtime.agentId}`); + const worldId = createUniqueUuid(runtime, serverId); const world = await runtime.getWorld(worldId); if (!world || !world.metadata?.settings) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a5012f9aafa..f1f2fc82e8b 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -962,6 +962,13 @@ export interface IDatabaseAdapter { metadata?: { [key: string]: any }; }): Promise; + /** + * Updates an existing relationship between two entities. + * @param relationship The relationship object with updated data + * @returns Promise resolving to void + */ + updateRelationship(relationship: Relationship): Promise; + /** * Retrieves a relationship between two entities if it exists. * @param params Object containing the entity IDs and agent ID @@ -1116,8 +1123,6 @@ export interface IAgentRuntime { getClient(name: string): ClientInstance | null; getAllClients(): Map; - generateTenantUserId(userId: UUID): UUID; - registerClientInterface(name: string, client: Client): void; registerClient(name: string, client: ClientInstance): void; diff --git a/packages/plugin-discord/src/actions/voiceJoin.ts b/packages/plugin-discord/src/actions/voiceJoin.ts index 4916aeeb426..2f001d41159 100644 --- a/packages/plugin-discord/src/actions/voiceJoin.ts +++ b/packages/plugin-discord/src/actions/voiceJoin.ts @@ -7,11 +7,11 @@ import { type State, ChannelType, composeContext, + createUniqueUuid, generateText, HandlerCallback, logger, - ModelClass, - stringToUuid, + ModelClass } from "@elizaos/core"; import { type Channel, @@ -133,9 +133,7 @@ export default { const members = guild?.members.cache; // get the member who's stringTouuid(id) === message userId - const member = members?.find((member) => stringToUuid(member.id) === message.userId); - - console.log("member", member); + const member = members?.find((member) => createUniqueUuid(runtime, member.id) === message.userId); if (member?.voice?.channel) { voiceManager.joinChannel(member?.voice?.channel as BaseGuildVoiceChannel); diff --git a/packages/plugin-discord/src/index.ts b/packages/plugin-discord/src/index.ts index 34c9d48b7d9..d2640f2f59e 100644 --- a/packages/plugin-discord/src/index.ts +++ b/packages/plugin-discord/src/index.ts @@ -1,6 +1,7 @@ import { ChannelType, type Character, + createUniqueUuid, type Client as ElizaClient, type HandlerCallback, type IAgentRuntime, @@ -8,9 +9,8 @@ import { type Memory, type Plugin, RoleName, - stringToUuid, UUID, - WorldData, + WorldData } from "@elizaos/core"; import { Client, @@ -103,16 +103,14 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { const guildChannels = await guild.fetch(); // for channel in channels for (const [, channel] of guildChannels.channels.cache) { - const roomId = stringToUuid(`${channel.id}-${runtime.agentId}`); + const roomId = createUniqueUuid(this.runtime, channel.id) const room = await runtime.getRoom(roomId); // if the room already exists, skip if (room) { continue; } - const worldId = stringToUuid(`${guild.id}-${runtime.agentId}`); - - const ownerId = stringToUuid(`${guildObj.ownerId}-${runtime.agentId}`); - const tenantSpecificOwnerId = runtime.generateTenantUserId(ownerId); + const worldId = createUniqueUuid(runtime, guild.id); + const ownerId = createUniqueUuid(this.runtime, guildObj.ownerId); await runtime.ensureWorldExists({ id: worldId, name: guild.name, @@ -121,7 +119,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { metadata: { ownership: guildObj.ownerId ? { ownerId } : undefined, roles: { - [tenantSpecificOwnerId]: RoleName.OWNER, + [ownerId]: RoleName.OWNER, }, }, }); @@ -187,6 +185,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { // Emit standardized USER_JOINED event this.runtime.emitEvent("USER_JOINED", { runtime: this.runtime, + entityId: createUniqueUuid(this.runtime, member.id), user: { id: member.id, username: tag, @@ -200,6 +199,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { this.runtime.emitEvent("DISCORD_USER_JOINED", { runtime: this.runtime, + entityId: createUniqueUuid(this.runtime, member.id), member, guild, }); @@ -316,13 +316,9 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { // Generate IDs with timestamp to ensure uniqueness const timestamp = Date.now(); - const roomId = stringToUuid( - `${reaction.message.channel.id}-${this.runtime.agentId}` - ); - const userIdUUID = stringToUuid(`${user.id}-${this.runtime.agentId}`); - const reactionUUID = stringToUuid( - `${reaction.message.id}-${user.id}-${emoji}-${timestamp}-${this.runtime.agentId}` - ); + const roomId = createUniqueUuid(this.runtime, reaction.message.channel.id) + const userIdUUID = createUniqueUuid(this.runtime, user.id); + const reactionUUID = createUniqueUuid(this.runtime, `${reaction.message.id}-${user.id}-${emoji}-${timestamp}`); // Validate IDs if (!userIdUUID || !roomId) { @@ -358,6 +354,8 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { type: await this.getChannelType(reaction.message.channel.id), }); + const inReplyTo = createUniqueUuid(this.runtime, reaction.message.id); + const memory: Memory = { id: reactionUUID, userId: userIdUUID, @@ -367,9 +365,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { userName, text: reactionMessage, source: "discord", - inReplyTo: stringToUuid( - `${reaction.message.id}-${this.runtime.agentId}` - ), + inReplyTo, }, roomId, createdAt: timestamp, @@ -427,13 +423,11 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { const reactionMessage = `*Removed <${emoji}> from: "${truncatedContent}"*`; - const roomId = stringToUuid( - `${reaction.message.channel.id}-${this.runtime.agentId}` - ); - const userIdUUID = stringToUuid(`${user.id}-${this.runtime.agentId}`); - const reactionUUID = stringToUuid( - `${reaction.message.id}-${user.id}-${emoji}-removed-${this.runtime.agentId}` - ); + const roomId = createUniqueUuid(this.runtime, reaction.message.channel.id) + + const userIdUUID = createUniqueUuid(this.runtime, user.id); + const timestamp = Date.now(); + const reactionUUID = createUniqueUuid(this.runtime, `${reaction.message.id}-${user.id}-${emoji}-${timestamp}`); const userName = reaction.message.author?.username || "unknown"; const name = reaction.message.author?.displayName || userName; @@ -458,9 +452,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { userName, text: reactionMessage, source: "discord", - inReplyTo: stringToUuid( - `${reaction.message.id}-${this.runtime.agentId}` - ), + inReplyTo: createUniqueUuid(this.runtime, reaction.message.id), }, roomId, createdAt: Date.now(), @@ -490,14 +482,11 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { const fullGuild = await guild.fetch(); this.voiceManager.scanGuild(guild); - const ownerId = stringToUuid( - `${fullGuild.ownerId}-${this.runtime.agentId}` - ); + const ownerId = createUniqueUuid(this.runtime, fullGuild.ownerId); // Create standardized world data structure - const worldId = stringToUuid(`${fullGuild.id}-${this.runtime.agentId}`); - const tenantSpecificOwnerId = this.runtime.generateTenantUserId(ownerId); - const standardizedData = { + const worldId = createUniqueUuid(this.runtime, fullGuild.id); + const standardizedData = { runtime: this.runtime, rooms: await this.buildStandardizedRooms(fullGuild, worldId), users: await this.buildStandardizedUsers(fullGuild), @@ -507,9 +496,9 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { agentId: this.runtime.agentId, serverId: fullGuild.id, metadata: { - ownership: fullGuild.ownerId ? { ownerId: tenantSpecificOwnerId } : undefined, + ownership: fullGuild.ownerId ? { ownerId: ownerId } : undefined, roles: { - [tenantSpecificOwnerId]: RoleName.OWNER, + [ownerId]: RoleName.OWNER, }, }, } as WorldData, @@ -555,7 +544,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { channel.type === DiscordChannelType.GuildText || channel.type === DiscordChannelType.GuildVoice ) { - const roomId = stringToUuid(`${channelId}-${this.runtime.agentId}`); + const roomId = createUniqueUuid(this.runtime, channelId) let channelType; switch (channel.type) { @@ -587,7 +576,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { ?.has(PermissionsBitField.Flags.ViewChannel) ) .map((member) => - stringToUuid(`${member.id}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, member.id) ); } catch (error) { logger.warn( @@ -633,7 +622,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { if (member.id !== botId) { users.push({ - id: stringToUuid(`${member.id}-${this.runtime.agentId}`), + id: createUniqueUuid(this.runtime, member.id), names: Array.from( new Set([member.user.username, member.displayName, member.user.globalName]) ), @@ -665,9 +654,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { for (const [, member] of onlineMembers) { if (member.id !== botId) { - const userId = stringToUuid( - `${member.id}-${this.runtime.agentId}` - ); + const userId = createUniqueUuid(this.runtime, member.id); // Avoid duplicates if (!users.some((u) => u.id === userId)) { const tag = member.user.bot @@ -718,7 +705,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { : member.user.username; users.push({ - id: stringToUuid(`${member.id}-${this.runtime.agentId}`), + id: createUniqueUuid(this.runtime, member.id), names: Array.from( new Set([member.user.username, member.displayName, member.user.globalName]) ), @@ -770,12 +757,8 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { }); // Create platform-agnostic world data structure with simplified structure - const worldId = stringToUuid(`${fullGuild.id}-${this.runtime.agentId}`); - - const ownerId = stringToUuid( - `${fullGuild.ownerId}-${this.runtime.agentId}` - ); - const tenantSpecificOwnerId = this.runtime.generateTenantUserId(ownerId); + const worldId = createUniqueUuid(this.runtime, fullGuild.id); + const ownerId = createUniqueUuid(this.runtime, fullGuild.ownerId); const standardizedData = { runtime: this.runtime, @@ -789,7 +772,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { metadata: { ownership: fullGuild.ownerId ? { ownerId } : undefined, roles: { - [tenantSpecificOwnerId]: RoleName.OWNER, + [ownerId]: RoleName.OWNER, }, }, } as WorldData, diff --git a/packages/plugin-discord/src/messages.ts b/packages/plugin-discord/src/messages.ts index 76c9902dc17..14bfe9c1a6e 100644 --- a/packages/plugin-discord/src/messages.ts +++ b/packages/plugin-discord/src/messages.ts @@ -1,5 +1,7 @@ import { + ChannelType, type Content, + createUniqueUuid, type HandlerCallback, type IAgentRuntime, type IBrowserService, @@ -7,14 +9,11 @@ import { logger, type Media, type Memory, - ServiceType, - stringToUuid, - type UUID, - ChannelType, + ServiceType } from "@elizaos/core"; import { - ChannelType as DiscordChannelType, type Client, + ChannelType as DiscordChannelType, type Message as DiscordMessage, type TextChannel, } from "discord.js"; @@ -61,15 +60,14 @@ export class MessageManager { return; } - const userIdUUID = stringToUuid( - `${message.author.id}-${this.runtime.agentId}` - ); + const userIdUUID = createUniqueUuid(this.runtime, message.author.id); + const userName = message.author.bot ? `${message.author.username}#${message.author.discriminator}` : message.author.username; const name = message.author.displayName; const channelId = message.channel.id; - const roomId = stringToUuid(`${channelId}-${this.runtime.agentId}`); + const roomId = createUniqueUuid(this.runtime, channelId); let type: ChannelType; let serverId: string | undefined; @@ -122,11 +120,9 @@ export class MessageManager { return; } - const userIdUUID = stringToUuid( - `${message.author.id}-${this.runtime.agentId}` - ); + const userIdUUID = createUniqueUuid(this.runtime, message.author.id); - const messageId = stringToUuid(`${message.id}-${this.runtime.agentId}`); + const messageId = createUniqueUuid(this.runtime, message.id); const newMessage: Memory = { id: messageId, @@ -141,7 +137,7 @@ export class MessageManager { source: "discord", url: message.url, inReplyTo: message.reference?.messageId - ? stringToUuid(message.reference.messageId) + ? createUniqueUuid(this.runtime, message.reference?.messageId) : undefined, }, createdAt: message.createdTimestamp, @@ -153,9 +149,7 @@ export class MessageManager { ) => { try { if (message.id && !content.inReplyTo) { - content.inReplyTo = stringToUuid( - `${message.id}-${this.runtime.agentId}` - ); + content.inReplyTo = createUniqueUuid(this.runtime, message.id); } const messages = await sendMessageInChunks( message.channel as TextChannel, @@ -172,7 +166,7 @@ export class MessageManager { } const memory: Memory = { - id: stringToUuid(`${m.id}-${this.runtime.agentId}`), + id: createUniqueUuid(this.runtime, m.id), userId: this.runtime.agentId, agentId: this.runtime.agentId, content: { diff --git a/packages/plugin-discord/src/voice.ts b/packages/plugin-discord/src/voice.ts index 56ef6bcc4bc..47d34f8e109 100644 --- a/packages/plugin-discord/src/voice.ts +++ b/packages/plugin-discord/src/voice.ts @@ -12,28 +12,28 @@ import { joinVoiceChannel, } from "@discordjs/voice"; import { + type ChannelType, type Content, type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, type UUID, - logger, - stringToUuid, - type ChannelType + createUniqueUuid, + logger } from "@elizaos/core"; import { type BaseGuildVoiceChannel, - ChannelType as DiscordChannelType, type Client, + ChannelType as DiscordChannelType, type Guild, type GuildMember, type VoiceChannel, type VoiceState, } from "discord.js"; import EventEmitter from "node:events"; -import prism from "prism-media"; import { type Readable, pipeline } from "node:stream"; +import prism from "prism-media"; import type { DiscordClient } from "./index.ts"; import { getWavHeader } from "./utils.ts"; @@ -635,8 +635,8 @@ export class VoiceManager extends EventEmitter { return { text: "", action: "IGNORE" }; } - const roomId = stringToUuid(`${channelId}-${this.runtime.agentId}`); - const userIdUUID = stringToUuid(`${userId}-${this.runtime.agentId}`); + const roomId = createUniqueUuid(this.runtime, channelId); + const userIdUUID = createUniqueUuid(this.runtime, userId); const guild = await channel.guild.fetch(); const type = await this.getChannelType(guild.id); @@ -652,7 +652,7 @@ export class VoiceManager extends EventEmitter { }); const memory: Memory = { - id: stringToUuid(`${channelId}-voice-message-${Date.now()}`), + id: createUniqueUuid(this.runtime, `${channelId}-voice-message-${Date.now()}`), agentId: this.runtime.agentId, userId: userIdUUID, roomId, @@ -670,7 +670,7 @@ export class VoiceManager extends EventEmitter { const callback: HandlerCallback = async (content: Content, _files: any[] = []) => { try { const responseMemory: Memory = { - id: stringToUuid(`${memory.id}-voice-response-${Date.now()}`), + id: createUniqueUuid(this.runtime, `${memory.id}-voice-response-${Date.now()}`), userId: this.runtime.agentId, agentId: this.runtime.agentId, content: { diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index 9373b2dba9c..fa3951332e3 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -1371,6 +1371,25 @@ export abstract class BaseDrizzleAdapter }); } + async updateRelationship(relationship: Relationship): Promise { + return this.withDatabase(async () => { + try { + await this.db.update(relationshipTable) + .set({ + tags: relationship.tags || [], + metadata: relationship.metadata || {}, + }) + .where(eq(relationshipTable.id, relationship.id)); + } catch (error) { + logger.error("Error updating relationship:", { + error: error instanceof Error ? error.message : String(error), + relationship, + }); + throw error; + } + }); + } + async getRelationship(params: { sourceEntityId: UUID; targetEntityId: UUID; @@ -1419,16 +1438,14 @@ export abstract class BaseDrizzleAdapter tags?: string[]; }): Promise { return this.withDatabase(async () => { + console.log("*** Attempting to get relationships for ", params.userId) try { let query = this.db .select() .from(relationshipTable) .where( and( - or( - eq(relationshipTable.sourceEntityId, params.userId), - eq(relationshipTable.targetEntityId, params.userId) - ), + eq(relationshipTable.sourceEntityId, params.userId), eq(relationshipTable.agentId, params.agentId) ) ); @@ -1440,6 +1457,12 @@ export abstract class BaseDrizzleAdapter const results = await query; + console.log('****** relationship results ', results) + + if(results.length === 0) { + console.warn("Empty results") + console.trace() + } return results.map(result => ({ id: result.id, sourceEntityId: result.sourceEntityId, diff --git a/packages/plugin-telegram/src/messageManager.ts b/packages/plugin-telegram/src/messageManager.ts index 3ef4ab997e0..e7781ceea52 100644 --- a/packages/plugin-telegram/src/messageManager.ts +++ b/packages/plugin-telegram/src/messageManager.ts @@ -1,6 +1,7 @@ import { ChannelType, type Content, + createUniqueUuid, type HandlerCallback, type IAgentRuntime, logger, @@ -8,7 +9,6 @@ import { type Memory, ModelClass, RoleName, - stringToUuid, type UUID } from "@elizaos/core"; import type { Chat, Message, ReactionType, Update } from "@telegraf/types"; @@ -226,15 +226,13 @@ export class MessageManager { try { // Convert IDs to UUIDs - const userId = stringToUuid(ctx.from.id.toString()) as UUID; + const userId = createUniqueUuid(this.runtime, ctx.from.id.toString()) as UUID; const userName = ctx.from.username || ctx.from.first_name || "Unknown User"; - const chatId = stringToUuid(`${ctx.chat?.id.toString()}-${this.runtime.agentId}`) as UUID; + const chatId = createUniqueUuid(this.runtime, ctx.chat?.id.toString()); const roomId = chatId; // Get message ID - const messageId = stringToUuid( - `${roomId}-${message?.message_id?.toString()}` - ) as UUID; + const messageId = createUniqueUuid(this.runtime, message?.message_id?.toString()); // Handle images const imageInfo = await this.processImage(message); @@ -264,7 +262,7 @@ export class MessageManager { userName: userName, // Safely access reply_to_message with type guard inReplyTo: 'reply_to_message' in message && message.reply_to_message ? - stringToUuid(`${message.reply_to_message.message_id.toString()}-${this.runtime.agentId}`) : + createUniqueUuid(this.runtime, message.reply_to_message.message_id.toString()) : undefined }, createdAt: message.date * 1000 @@ -312,10 +310,10 @@ export class MessageManager { // TODO: chat.id is probably used incorrectly here and needs to be fixed const channelType = getChannelType(chat); - const worldId = stringToUuid(`${chat.id.toString()}-${this.runtime.agentId}`) as UUID; + const worldId = createUniqueUuid(this.runtime, chat.id.toString()); const room = {id: roomId, name: roomName, source: "telegram", type: channelType, channelId: ctx.chat.id.toString(), serverId: ctx.chat.id.toString(), worldId: worldId} // TODO: chat.id is probably used incorrectly here and needs to be fixed - const tenantSpecificOwnerId = this.runtime.generateTenantUserId(stringToUuid(chat.id.toString())); + const ownerId = chat.id; // this might be wrong if (channelType === ChannelType.GROUP) { // if the type is a group, we need to get the world id from the supergroup/channel id await this.runtime.ensureWorldExists({ @@ -327,7 +325,7 @@ export class MessageManager { ownership: chat.type === 'supergroup' ? { ownerId: chat.id.toString() } : undefined, roles: { // TODO: chat.id is probably wrong key for this - [tenantSpecificOwnerId]: RoleName.OWNER, + [ownerId]: RoleName.OWNER, }, } }); @@ -349,7 +347,7 @@ export class MessageManager { const isLastMessage = i === sentMessages.length - 1; const responseMemory: Memory = { - id: stringToUuid(`${roomId}-${sentMessage.message_id.toString()}`), + id: createUniqueUuid(this.runtime, sentMessage.message_id.toString()), userId: this.runtime.agentId, agentId: this.runtime.agentId, roomId, @@ -396,9 +394,10 @@ export class MessageManager { const reactionEmoji = (reaction.new_reaction[0] as ReactionType).type; try { - const userId = stringToUuid(ctx.from.id.toString()); - const roomId = stringToUuid(`${ctx.chat.id.toString()}-${this.runtime.agentId}`); - const reactionId = stringToUuid(`${reaction.message_id}-${ctx.from.id}-${Date.now()}-${this.runtime.agentId}`); + const userId = createUniqueUuid(this.runtime, ctx.from.id.toString()) as UUID; + const roomId = createUniqueUuid(this.runtime, ctx.chat.id.toString()); + + const reactionId = createUniqueUuid(this.runtime, `${reaction.message_id}-${ctx.from.id}-${Date.now()}`); // Create reaction memory const memory: Memory = { @@ -411,7 +410,7 @@ export class MessageManager { source: "telegram", name: ctx.from.first_name, userName: ctx.from.username, - inReplyTo: stringToUuid(`${reaction.message_id.toString()}-${this.runtime.agentId}`) + inReplyTo: createUniqueUuid(this.runtime, reaction.message_id.toString()) }, createdAt: Date.now() }; @@ -422,7 +421,7 @@ export class MessageManager { try { const sentMessage = await ctx.reply(content.text); const responseMemory: Memory = { - id: stringToUuid(`${roomId}-${sentMessage.message_id.toString()}`), + id: createUniqueUuid(this.runtime, sentMessage.message_id.toString()), userId: this.runtime.agentId, agentId: this.runtime.agentId, roomId, diff --git a/packages/plugin-telegram/src/telegramClient.ts b/packages/plugin-telegram/src/telegramClient.ts index b6c89c64a90..68e3737b8b0 100644 --- a/packages/plugin-telegram/src/telegramClient.ts +++ b/packages/plugin-telegram/src/telegramClient.ts @@ -1,6 +1,5 @@ +import { type ClientInstance, type IAgentRuntime, logger } from "@elizaos/core"; import { type Context, Telegraf } from "telegraf"; -import { message } from "telegraf/filters"; -import { type IAgentRuntime, logger, type ClientInstance, stringToUuid, Memory, HandlerCallback, Content } from "@elizaos/core"; import { MessageManager } from "./messageManager.ts"; export class TelegramClient implements ClientInstance { diff --git a/packages/plugin-twitter/src/base.ts b/packages/plugin-twitter/src/base.ts index 75a1eb036e2..fc9073bc940 100644 --- a/packages/plugin-twitter/src/base.ts +++ b/packages/plugin-twitter/src/base.ts @@ -5,16 +5,16 @@ import { type Memory, type State, type UUID, - logger, - stringToUuid, + createUniqueUuid, + logger } from "@elizaos/core"; +import { EventEmitter } from "node:events"; import { type QueryTweetsResponse, Scraper, SearchMode, type Tweet, } from "./client/index.ts"; -import { EventEmitter } from "node:events"; export function extractAnswer(text: string): string { const startIndex = text.indexOf("Answer: ") + 8; @@ -435,7 +435,7 @@ export class ClientBase extends EventEmitter { const existingMemories = await this.runtime.messageManager.getMemoriesByRoomIds({ roomIds: cachedTimeline.map((tweet) => - stringToUuid(`${tweet.conversationId}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, tweet.conversationId) ), }); @@ -449,7 +449,7 @@ export class ClientBase extends EventEmitter { // Check if any of the cached tweets exist in the existing memories const someCachedTweetsExist = cachedTimeline.some((tweet) => existingMemoryIds.has( - stringToUuid(`${tweet.id}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, tweet.id), ) ); @@ -459,26 +459,20 @@ export class ClientBase extends EventEmitter { (tweet) => tweet.userId !== this.profile.id && !existingMemoryIds.has( - stringToUuid(`${tweet.id}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, tweet.id) ) ); - console.log({ - processingTweets: tweetsToSave.map((tweet) => tweet.id).join(","), - }); - // Save the missing tweets as memories for (const tweet of tweetsToSave) { logger.log("Saving Tweet", tweet.id); - const roomId = stringToUuid( - `${tweet.conversationId}-${this.runtime.agentId}` - ); + const roomId = createUniqueUuid(this.runtime, tweet.conversationId); - const userId = + const userId = createUniqueUuid(this.runtime, tweet.userId === this.profile.id ? this.runtime.agentId - : stringToUuid(tweet.userId); + : tweet.userId); if (tweet.userId === this.profile.id) { continue; @@ -498,9 +492,7 @@ export class ClientBase extends EventEmitter { url: tweet.permanentUrl, source: "twitter", inReplyTo: tweet.inReplyToStatusId - ? stringToUuid( - `${tweet.inReplyToStatusId}-${this.runtime.agentId}` - ) + ? createUniqueUuid(this.runtime, tweet.inReplyToStatusId) : undefined, } as Content; @@ -508,7 +500,7 @@ export class ClientBase extends EventEmitter { // check if it already exists const memory = await this.runtime.messageManager.getMemoryById( - stringToUuid(`${tweet.id}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, tweet.id) ); if (memory) { @@ -517,7 +509,7 @@ export class ClientBase extends EventEmitter { } await this.runtime.messageManager.createMemory({ - id: stringToUuid(`${tweet.id}-${this.runtime.agentId}`), + id: createUniqueUuid(this.runtime, tweet.id), userId, content: content, agentId: this.runtime.agentId, @@ -556,7 +548,7 @@ export class ClientBase extends EventEmitter { for (const tweet of allTweets) { tweetIdsToCheck.add(tweet.id); roomIds.add( - stringToUuid(`${tweet.conversationId}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, tweet.conversationId) ); } @@ -576,7 +568,7 @@ export class ClientBase extends EventEmitter { (tweet) => tweet.userId !== this.profile.id && !existingMemoryIds.has( - stringToUuid(`${tweet.id}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, tweet.id) ) ); @@ -599,13 +591,12 @@ export class ClientBase extends EventEmitter { for (const tweet of tweetsToSave) { logger.log("Saving Tweet", tweet.id); - const roomId = stringToUuid( - `${tweet.conversationId}-${this.runtime.agentId}` - ); + const roomId = createUniqueUuid(this.runtime, tweet.conversationId); + const userId = tweet.userId === this.profile.id ? this.runtime.agentId - : stringToUuid(tweet.userId); + : createUniqueUuid(this.runtime, tweet.userId); if (tweet.userId === this.profile.id) { continue; @@ -625,12 +616,12 @@ export class ClientBase extends EventEmitter { url: tweet.permanentUrl, source: "twitter", inReplyTo: tweet.inReplyToStatusId - ? stringToUuid(tweet.inReplyToStatusId) + ? createUniqueUuid(this.runtime, tweet.inReplyToStatusId) : undefined, } as Content; await this.runtime.messageManager.createMemory({ - id: stringToUuid(`${tweet.id}-${this.runtime.agentId}`), + id: createUniqueUuid(this.runtime, tweet.id), userId, content: content, agentId: this.runtime.agentId, diff --git a/packages/plugin-twitter/src/index.ts b/packages/plugin-twitter/src/index.ts index a192eb8059f..60803562eff 100644 --- a/packages/plugin-twitter/src/index.ts +++ b/packages/plugin-twitter/src/index.ts @@ -1,14 +1,14 @@ -import { logger, stringToUuid, type UUID, type Client, type IAgentRuntime, type Plugin } from "@elizaos/core"; +import { logger, type Client, type IAgentRuntime, type Plugin, type UUID } from "@elizaos/core"; import reply from "./actions/reply.ts"; +import spaceJoin from "./actions/spaceJoin.ts"; import { ClientBase } from "./base.ts"; import { TWITTER_CLIENT_NAME } from "./constants.ts"; import type { TwitterConfig } from "./environment.ts"; import { TwitterInteractionClient } from "./interactions.ts"; import { TwitterPostClient } from "./post.ts"; import { TwitterSpaceClient } from "./spaces.ts"; -import type { ITwitterClient } from "./types.ts"; import { TwitterTestSuite } from "./tests.ts"; -import spaceJoin from "./actions/spaceJoin.ts"; +import type { ITwitterClient } from "./types.ts"; /** * A manager that orchestrates all specialized Twitter logic: @@ -167,7 +167,7 @@ const TwitterClientInterface: Client = { // config.TWITTER_ACCESS_TOKEN && config.TWITTER_ACCESS_TOKEN_SECRET) )) { logger.info("Creating default Twitter client from character settings"); - await manager.createClient(runtime, stringToUuid("default"), config); + await manager.createClient(runtime, runtime.agentId, config); } } catch (error) { logger.error("Failed to create default Twitter client:", error); diff --git a/packages/plugin-twitter/src/interactions.ts b/packages/plugin-twitter/src/interactions.ts index fed0d4b5912..28793bb0256 100644 --- a/packages/plugin-twitter/src/interactions.ts +++ b/packages/plugin-twitter/src/interactions.ts @@ -1,21 +1,21 @@ -import { SearchMode, type Tweet } from "./client/index.ts"; import { + ChannelType, composeContext, + type Content, + createUniqueUuid, generateMessageResponse, generateShouldRespond, - messageCompletionFooter, - shouldRespondFooter, - type Content, type HandlerCallback, type IAgentRuntime, + logger, type Memory, + messageCompletionFooter, ModelClass, - type State, - stringToUuid, - logger, - ChannelType, + shouldRespondFooter, + type State } from "@elizaos/core"; import type { ClientBase } from "./base.ts"; +import { SearchMode, type Tweet } from "./client/index.ts"; import { buildConversationThread, sendTweet, wait } from "./utils.ts"; export const twitterMessageHandlerTemplate = @@ -236,9 +236,7 @@ export class TwitterInteractionClient { BigInt(tweet.id) > this.client.lastCheckedTweetId ) { // Generate the tweetId UUID the same way it's done in handleTweet - const tweetId = stringToUuid( - `${tweet.id}-${this.runtime.agentId}` - ); + const tweetId = createUniqueUuid(this.runtime, tweet.id); // Check if we've already processed this tweet const existingResponse = @@ -254,14 +252,14 @@ export class TwitterInteractionClient { } logger.log("New Tweet found", tweet.permanentUrl); - const roomId = stringToUuid( - `${tweet.conversationId}-${this.runtime.agentId}` - ); + const roomId = createUniqueUuid(this.runtime, tweet.conversationId); - const userIdUUID = + const userIdUUID = createUniqueUuid( + this.runtime, tweet.userId === this.client.profile.id ? this.runtime.agentId - : stringToUuid(tweet.userId!); + : tweet.userId + ); await this.runtime.ensureConnection({ userId: userIdUUID, @@ -373,17 +371,15 @@ export class TwitterInteractionClient { }); // check if the tweet exists, save if it doesn't - const tweetId = stringToUuid(`${tweet.id}-${this.runtime.agentId}`); + const tweetId = createUniqueUuid(this.runtime, tweet.id); const tweetExists = await this.runtime.messageManager.getMemoryById(tweetId); if (!tweetExists) { logger.log("tweet does not exist, saving"); - const userIdUUID = stringToUuid(`${tweet.userId}-${this.runtime.agentId}`); + const userIdUUID = createUniqueUuid(this.runtime, tweet.userId); - const roomId = stringToUuid( - `${tweet.conversationId}-${this.runtime.agentId}` - ); + const roomId = createUniqueUuid(this.runtime, tweet.conversationId); await this.runtime.ensureConnection({ userId: userIdUUID, @@ -402,9 +398,7 @@ export class TwitterInteractionClient { url: tweet.permanentUrl, imageUrls: tweet.photos?.map(photo => photo.url) || [], inReplyTo: tweet.inReplyToStatusId - ? stringToUuid( - `${tweet.inReplyToStatusId}-${this.runtime.agentId}` - ) + ? createUniqueUuid(this.runtime, tweet.inReplyToStatusId) : undefined, }, userId: userIdUUID, @@ -479,9 +473,9 @@ export class TwitterInteractionClient { const removeQuotes = (str: string) => str.replace(/^['"](.*)['"]$/, "$1"); - const stringId = stringToUuid(`${tweet.id}-${this.runtime.agentId}`); + const replyToId = createUniqueUuid(this.runtime, tweet.id); - response.inReplyTo = stringId; + response.inReplyTo = replyToId; response.text = removeQuotes(response.text); @@ -507,7 +501,7 @@ export class TwitterInteractionClient { }; const responseMessages = [{ - id: stringToUuid(`${tweet.id}-${this.runtime.agentId}`), + id: createUniqueUuid(this.runtime, tweet.id), userId: this.runtime.agentId, agentId: this.runtime.agentId, content: response, @@ -586,13 +580,11 @@ export class TwitterInteractionClient { // Handle memory storage const memory = await this.runtime.messageManager.getMemoryById( - stringToUuid(`${currentTweet.id}-${this.runtime.agentId}`) + createUniqueUuid(this.runtime, currentTweet.id) ); if (!memory) { - const roomId = stringToUuid( - `${currentTweet.conversationId}-${this.runtime.agentId}` - ); - const userId = stringToUuid(currentTweet.userId); + const roomId = createUniqueUuid(this.runtime, tweet.conversationId); + const userId = createUniqueUuid(this.runtime, currentTweet.userId); await this.runtime.ensureConnection({ userId, @@ -604,9 +596,7 @@ export class TwitterInteractionClient { }); this.runtime.messageManager.createMemory({ - id: stringToUuid( - `${currentTweet.id}-${this.runtime.agentId}` - ), + id: createUniqueUuid(this.runtime, currentTweet.id), agentId: this.runtime.agentId, content: { text: currentTweet.text, @@ -614,9 +604,7 @@ export class TwitterInteractionClient { url: currentTweet.permanentUrl, imageUrls: currentTweet.photos?.map(photo => photo.url) || [], inReplyTo: currentTweet.inReplyToStatusId - ? stringToUuid( - `${currentTweet.inReplyToStatusId}-${this.runtime.agentId}` - ) + ? createUniqueUuid(this.runtime, currentTweet.inReplyToStatusId) : undefined, }, createdAt: currentTweet.timestamp * 1000, @@ -624,7 +612,7 @@ export class TwitterInteractionClient { userId: currentTweet.userId === this.twitterUserId ? this.runtime.agentId - : stringToUuid(currentTweet.userId), + : createUniqueUuid(this.runtime, currentTweet.userId), }); } diff --git a/packages/plugin-twitter/src/post.ts b/packages/plugin-twitter/src/post.ts index 39bb20f138e..5c7a1c86592 100644 --- a/packages/plugin-twitter/src/post.ts +++ b/packages/plugin-twitter/src/post.ts @@ -2,21 +2,21 @@ import { ChannelType, cleanJsonResponse, composeContext, + createUniqueUuid, extractAttributes, generateText, type IAgentRuntime, logger, ModelClass, parseJSONObjectFromText, - stringToUuid, truncateToCompleteSentence, type UUID } from "@elizaos/core"; import type { ClientBase } from "./base.ts"; import type { Tweet } from "./client/index.ts"; +import { twitterPostTemplate } from "./templates.ts"; import type { MediaData } from "./types.ts"; import { fetchMediaData } from "./utils.ts"; -import { twitterPostTemplate } from "./templates.ts"; export class TwitterPostClient { @@ -162,7 +162,7 @@ export class TwitterPostClient { // Create a memory for the tweet await runtime.messageManager.createMemory({ - id: stringToUuid(`${tweet.id}-${runtime.agentId}`), + id: createUniqueUuid(this.runtime, tweet.id), userId: runtime.agentId, agentId: runtime.agentId, content: { @@ -292,9 +292,7 @@ export class TwitterPostClient { logger.log("Generating new tweet"); try { - const roomId = stringToUuid( - `twitter_generate_room-${this.client.profile.username}` - ); + const roomId = createUniqueUuid(this.runtime, 'twitter_generate'); await this.runtime.getOrCreateUser( this.runtime.agentId, [this.client.profile.username], diff --git a/packages/plugin-twitter/src/sttTtsSpaces.ts b/packages/plugin-twitter/src/sttTtsSpaces.ts index e5dcccd66c0..fb98905913c 100644 --- a/packages/plugin-twitter/src/sttTtsSpaces.ts +++ b/packages/plugin-twitter/src/sttTtsSpaces.ts @@ -1,24 +1,24 @@ // src/plugins/SttTtsPlugin.ts import { + ChannelType, type Content, + HandlerCallback, type IAgentRuntime, type Memory, - type Plugin, - logger, ModelClass, - stringToUuid, - ChannelType, - HandlerCallback + type Plugin, + createUniqueUuid, + logger } from "@elizaos/core"; +import { spawn } from "node:child_process"; +import { Readable } from "node:stream"; +import type { ClientBase } from "./base"; import type { AudioDataWithUser, JanusClient, Space, } from "./client"; -import { spawn } from "node:child_process"; -import type { ClientBase } from "./base"; -import { Readable } from "node:stream"; interface PluginConfig { runtime: IAgentRuntime; @@ -345,10 +345,10 @@ export class SttTtsPlugin implements Plugin { // Extract the numeric ID part const numericId = userId.replace("tw-", ""); - const roomId = stringToUuid(`twitter_generate_room-${this.spaceId}`); + const roomId = createUniqueUuid(this.runtime, `twitter_generate_room-${this.spaceId}`); // Create consistent UUID for the user - const userUuid = stringToUuid(`twitter-user-${numericId}`); + const userUuid = createUniqueUuid(this.runtime, numericId); // Ensure the user exists in the accounts table await this.runtime.getOrCreateUser( @@ -367,7 +367,7 @@ export class SttTtsPlugin implements Plugin { await this.runtime.ensureParticipantInRoom(userUuid, roomId); const memory = { - id: stringToUuid(`${roomId}-voice-message-${Date.now()}`), + id: createUniqueUuid(this.runtime, `${roomId}-voice-message-${Date.now()}`), agentId: this.runtime.agentId, content: { text: userText, @@ -381,7 +381,7 @@ export class SttTtsPlugin implements Plugin { const callback: HandlerCallback = async (content: Content, _files: any[] = []) => { try { const responseMemory: Memory = { - id: stringToUuid(`${memory.id}-voice-response-${Date.now()}`), + id: createUniqueUuid(this.runtime, `${memory.id}-voice-response-${Date.now()}`), userId: this.runtime.agentId, agentId: this.runtime.agentId, content: { diff --git a/packages/plugin-twitter/src/tests.ts b/packages/plugin-twitter/src/tests.ts index 97db96a11b9..ada2c3ba74b 100644 --- a/packages/plugin-twitter/src/tests.ts +++ b/packages/plugin-twitter/src/tests.ts @@ -4,6 +4,7 @@ import { type IAgentRuntime, ModelClass, stringToUuid, + createUniqueUuid, } from "@elizaos/core"; import type { TwitterClient } from "./index.ts"; import { SearchMode } from "./client/index.ts"; @@ -131,9 +132,8 @@ export class TwitterTestSuite implements TestSuite { async testPostTweet(runtime: IAgentRuntime) { try { - const roomId = stringToUuid( - `twitter_mock_room-${this.twitterClient.client.profile.username}` - ); + const roomId = createUniqueUuid(runtime, 'twitter_mock_room'); + const postClient = this.twitterClient.post; const tweetText = await this.generateRandomTweetContent(runtime); await postClient.postTweet( @@ -152,9 +152,8 @@ export class TwitterTestSuite implements TestSuite { async testPostImageTweet(runtime: IAgentRuntime) { try { - const roomId = stringToUuid( - `twitter_mock_room-${this.twitterClient.client.profile.username}` - ); + const roomId = createUniqueUuid(runtime, 'twitter_mock_room'); + const postClient = this.twitterClient.post; const tweetText = await this.generateRandomTweetContent( runtime, @@ -209,8 +208,8 @@ export class TwitterTestSuite implements TestSuite { message: { content: { text: testTweet.text, source: "twitter" }, agentId: runtime.agentId, - userId: stringToUuid(testTweet.userId), - roomId: stringToUuid(testTweet.conversationId), + userId: createUniqueUuid(runtime, testTweet.userId), + roomId: createUniqueUuid(runtime, testTweet.conversationId), }, thread: [], }); diff --git a/packages/plugin-twitter/src/utils.ts b/packages/plugin-twitter/src/utils.ts index 6d257d5e837..533729ba987 100644 --- a/packages/plugin-twitter/src/utils.ts +++ b/packages/plugin-twitter/src/utils.ts @@ -1,13 +1,11 @@ -import type { Tweet } from "./client"; -import { Content, IAgentRuntime, Memory, ModelClass, UUID, composeContext } from "@elizaos/core"; -import { ChannelType, generateText, stringToUuid } from "@elizaos/core"; -import type { ClientBase } from "./base"; -import { logger } from "@elizaos/core"; import type { Media, State } from "@elizaos/core"; +import { ChannelType, Content, IAgentRuntime, Memory, ModelClass, UUID, composeContext, createUniqueUuid, generateText, logger } from "@elizaos/core"; import fs from "node:fs"; import path from "node:path"; -import type { ActionResponse, MediaData } from "./types"; +import type { ClientBase } from "./base"; +import type { Tweet } from "./client"; import { SttTtsPlugin } from "./sttTtsSpaces"; +import type { ActionResponse, MediaData } from "./types"; export const wait = (minTime = 1000, maxTime = 3000) => { const waitTime = @@ -58,13 +56,11 @@ export async function buildConversationThread( // Handle memory storage const memory = await client.runtime.messageManager.getMemoryById( - stringToUuid(`${currentTweet.id}-${client.runtime.agentId}`) + createUniqueUuid(this.runtime, currentTweet.id) ); if (!memory) { - const roomId = stringToUuid( - `${currentTweet.conversationId}-${client.runtime.agentId}` - ); - const userId = stringToUuid(currentTweet.userId); + const roomId = createUniqueUuid(this.runtime, currentTweet.conversationId); + const userId = createUniqueUuid(this.runtime, currentTweet.userId); await client.runtime.ensureConnection({ userId, @@ -76,9 +72,7 @@ export async function buildConversationThread( }); await client.runtime.messageManager.createMemory({ - id: stringToUuid( - `${currentTweet.id}-${client.runtime.agentId}` - ), + id: createUniqueUuid(this.runtime, currentTweet.id), agentId: client.runtime.agentId, content: { text: currentTweet.text, @@ -86,9 +80,7 @@ export async function buildConversationThread( url: currentTweet.permanentUrl, imageUrls: currentTweet.photos.map((p) => p.url) || [], inReplyTo: currentTweet.inReplyToStatusId - ? stringToUuid( - `${currentTweet.inReplyToStatusId}-${client.runtime.agentId}` - ) + ? createUniqueUuid(this.runtime, currentTweet.inReplyToStatusId) : undefined, }, createdAt: currentTweet.timestamp * 1000, @@ -96,7 +88,7 @@ export async function buildConversationThread( userId: currentTweet.userId === client.profile.id ? client.runtime.agentId - : stringToUuid(currentTweet.userId), + : createUniqueUuid(this.runtime, currentTweet.userId), }); } @@ -267,7 +259,7 @@ export async function sendTweet( } const memories: Memory[] = sentTweets.map((tweet) => ({ - id: stringToUuid(`${tweet.id}-${client.runtime.agentId}`), + id: createUniqueUuid(client.runtime, tweet.id), agentId: client.runtime.agentId, userId: client.runtime.agentId, content: { @@ -277,9 +269,7 @@ export async function sendTweet( url: tweet.permanentUrl, imageUrls: tweet.photos.map((p) => p.url) || [], inReplyTo: tweet.inReplyToStatusId - ? stringToUuid( - `${tweet.inReplyToStatusId}-${client.runtime.agentId}` - ) + ? createUniqueUuid(client.runtime, tweet.inReplyToStatusId) : undefined, }, roomId, From 85879a70c45c5934c17361e9f91fdfd434d3f20b Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 28 Feb 2025 04:22:17 -0800 Subject: [PATCH 10/13] relationship provider and actordetails are providing --- packages/core/src/evaluators/reflection.ts | 17 ++- packages/core/src/index.ts | 1 - packages/core/src/messages.ts | 12 +- packages/core/src/providers/relationships.ts | 56 ++++++--- packages/core/src/relationships.ts | 112 ------------------ .../migrations/20250227085330_init.sql | 3 +- .../meta/20250227085330_snapshot.json | 12 +- .../drizzle/migrations/meta/_journal.json | 7 ++ packages/plugin-sql/src/base.ts | 3 + .../plugin-sql/src/schema/relationship.ts | 2 + 10 files changed, 88 insertions(+), 137 deletions(-) delete mode 100644 packages/core/src/relationships.ts diff --git a/packages/core/src/evaluators/reflection.ts b/packages/core/src/evaluators/reflection.ts index d7a590e203a..8c2ce6931dc 100644 --- a/packages/core/src/evaluators/reflection.ts +++ b/packages/core/src/evaluators/reflection.ts @@ -35,6 +35,12 @@ const reflectionTemplate = `# Task: Generate Agent Reflection, Extract Facts and {{bio}} +# Entities in Room +{{entitiesInRoom}} + +# Existing Relationships +{{existingRelationships}} + # Current Context: Agent Name: {{agentName}} Room Type: {{roomType}} @@ -49,7 +55,7 @@ Message Sender: {{senderName}} (ID: {{senderId}}) # Instructions: 1. Generate a self-reflection monologue about recent interactions 2. Extract new facts from the conversation -3. Identify and describe relationships between entities +3. Identify and describe relationships between entities. The sourceEntityId is the UUID of the entity initiating the interaction. The targetEntityId is the UUID of the entity being interacted with. Relationships are one-direction, so a friendship would be two entity relationships where each entity is both the source and the target of the other. Generate a response in the following format: \`\`\`json @@ -83,9 +89,13 @@ async function handler(runtime: IAgentRuntime, message: Memory) { agentId }); + console.log("*** existingRelationships", existingRelationships) + // Get actors in the room for name resolution const actors = await getActorDetails({ runtime, roomId }); + const entitiesInRoom = await runtime.databaseAdapter.getEntitiesForRoom(roomId, agentId); + // Get known facts const factsManager = new MemoryManager({ runtime, @@ -102,9 +112,12 @@ async function handler(runtime: IAgentRuntime, message: Memory) { ...state, knownFacts: formatFacts(knownFacts), roomType: state.roomType || "group", // Can be "group", "voice", or "dm" + entitiesInRoom: JSON.stringify(entitiesInRoom), + existingRelationships: JSON.stringify(existingRelationships) }, template: runtime.character.templates?.reflectionTemplate || reflectionTemplate, }); + console.log("*** reflection context\n", context) const reflection = await generateObject({ runtime, @@ -154,6 +167,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { ); if (existingRelationship) { + console.log('**** existingRelationship is true') // Update existing relationship by creating a new one const updatedMetadata = { ...existingRelationship.metadata, @@ -175,6 +189,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { metadata: updatedMetadata, }); } else { + console.log('**** existingRelationship is false') // Create new relationship await runtime.databaseAdapter.createRelationship({ sourceEntityId: sourceId, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index db0ec42a4eb..54f41be6b90 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -19,7 +19,6 @@ export * from "./messages.ts"; export * from "./parsing.ts"; export * from "./posts.ts"; export * from "./providers.ts"; -export * from "./relationships.ts"; export * from "./roles.ts"; export * from "./runtime.ts"; export * from "./settings.ts"; diff --git a/packages/core/src/messages.ts b/packages/core/src/messages.ts index 3d2e89e1f3b..621d42f6ded 100644 --- a/packages/core/src/messages.ts +++ b/packages/core/src/messages.ts @@ -33,11 +33,15 @@ export async function getActorDetails({ return acc; }, {}); + console.log('*** entity', entity) + console.log('*** mergedData', mergedData) + console.log('*** entity.metadata', entity.metadata) + return { id: entity.id, name: entity.metadata[room.source]?.name || entity.names[0], names: entity.names, - data: JSON.stringify(mergedData) + data: JSON.stringify({...mergedData, ...entity.metadata}) }; }); @@ -60,11 +64,15 @@ export async function getActorDetails({ * @returns string */ export function formatActors({ actors }: { actors: Actor[] }) { + console.log('*** actors', actors) const actorStrings = actors.map((actor: Actor) => { - const header = `${actor.name} (${actor.names.join(" aka ")})` + (actor.data && Object.entries(actor.data).length > 0) ? `\nData: ${actor.data}` : ""; + console.log('*** actor', actor) + const header = `${actor.name} (${actor.names.join(" aka ")})` + `\nID: ${actor.id}` + ((actor.data && Object.entries(actor.data).length > 0) ? `\nData: ${actor.data}` : "\n"); + console.log('*** header', header) return header; }); const finalActorStrings = actorStrings.join("\n"); + console.log('*** finalActorStrings', finalActorStrings) return finalActorStrings; } diff --git a/packages/core/src/providers/relationships.ts b/packages/core/src/providers/relationships.ts index a02dd191944..8d0818d4dd6 100644 --- a/packages/core/src/providers/relationships.ts +++ b/packages/core/src/providers/relationships.ts @@ -1,7 +1,16 @@ -import { getRelationships } from "../relationships.ts"; -import { IAgentRuntime, Memory, Provider, State, Relationship } from "../types.ts"; +import { IAgentRuntime, Memory, Provider, State, Relationship, UUID } from "../types.ts"; -function formatRelationships(relationships: Relationship[]) { +async function getRelationships({ + runtime, + userId, +}: { + runtime: IAgentRuntime; + userId: UUID; +}) { + return runtime.databaseAdapter.getRelationships({ userId, agentId: runtime.agentId }); +} + +async function formatRelationships(runtime: IAgentRuntime, relationships: Relationship[]) { // Sort relationships by interaction strength (descending) const sortedRelationships = relationships .filter(rel => rel.metadata?.interactions) @@ -14,16 +23,31 @@ function formatRelationships(relationships: Relationship[]) { return ""; } - console.log("*** sortedRelationships", sortedRelationships) + const formattedRelationships = await Promise.all(sortedRelationships + .map(async (rel, index) => { + + const formatMetadata = (metadata: any) => { + return JSON.stringify(Object.entries(metadata) + .map(([key, value]) => `${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`) + .join("\n")); + } + + // get the targetEntityId + const targetEntityId = rel.targetEntityId; + + // get the entity + const entity = await runtime.getEntity(targetEntityId); + + if(!entity) { + return null; + } - return sortedRelationships - .map((rel, index) => { - const strength = rel.metadata?.interactions || 0; - const name = rel.metadata?.name || "Unknown"; - const description = rel.metadata?.description || ""; - return `${index + 1}. ${name} (Interaction Strength: ${strength})${description ? ` - ${description}` : ""}`; + const names = entity.names.join(" aka "); + return `${names}\n${rel.tags ? rel.tags.join(", ") : ""}\n${formatMetadata(entity.metadata)}\n`; }) - .join("\n"); + ); + + return formattedRelationships.filter(Boolean).join("\n"); } const relationshipsProvider: Provider = { @@ -34,23 +58,17 @@ const relationshipsProvider: Provider = { userId: message.userId, }); - console.log("*** relationships", relationships) - if (!relationships || relationships.length === 0) { return ""; } - const formattedRelationships = formatRelationships(relationships); - - console.log("*** formattedRelationships", formattedRelationships) + const formattedRelationships = await formatRelationships(runtime, relationships); if (!formattedRelationships) { return ""; } - console.log('******* formattedRelationships', formattedRelationships) - - return `Top relationships that ${runtime.character.name} has observed:\n${formattedRelationships}`; + return `${runtime.character.name} has observed ${state.senderName} interacting with these people:\n${formattedRelationships}`; }, }; diff --git a/packages/core/src/relationships.ts b/packages/core/src/relationships.ts deleted file mode 100644 index d7924ff11d6..00000000000 --- a/packages/core/src/relationships.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { IAgentRuntime, Relationship, UUID } from "./types.ts"; - -export async function createRelationship({ - runtime, - sourceEntityId, - targetEntityId, - metadata = {}, -}: { - runtime: IAgentRuntime; - sourceEntityId: UUID; - targetEntityId: UUID; - metadata?: { [key: string]: any }; -}): Promise { - return runtime.databaseAdapter.createRelationship({ - sourceEntityId, - targetEntityId, - agentId: runtime.agentId, - metadata, - }); -} - -export async function updateRelationshipInteractionStrength({ - runtime, - sourceEntityId, - targetEntityId, - increment = 1, -}: { - runtime: IAgentRuntime; - sourceEntityId: UUID; - targetEntityId: UUID; - increment?: number; -}): Promise { - // Get existing relationship - let relationship = await getRelationship({ - runtime, - sourceEntityId, - targetEntityId, - }); - - if (!relationship) { - // Create new relationship if it doesn't exist - await createRelationship({ - runtime, - sourceEntityId, - targetEntityId, - metadata: { - interactions: increment, - }, - }); - return; - } - - // Update interaction strength - const currentStrength = relationship.metadata?.interactions || 0; - relationship.metadata = { - ...relationship.metadata, - interactions: currentStrength + increment, - }; - - // Update the relationship in the database - await runtime.databaseAdapter.updateRelationship(relationship); -} - -export async function getRelationship({ - runtime, - sourceEntityId, - targetEntityId, -}: { - runtime: IAgentRuntime; - sourceEntityId: UUID; - targetEntityId: UUID; -}) { - return runtime.databaseAdapter.getRelationship({ - sourceEntityId, - targetEntityId, - agentId: runtime.agentId, - }); -} - -export async function getRelationships({ - runtime, - userId, -}: { - runtime: IAgentRuntime; - userId: UUID; -}) { - return runtime.databaseAdapter.getRelationships({ userId, agentId: runtime.agentId }); -} - -export async function formatRelationships({ - runtime, - userId, -}: { - runtime: IAgentRuntime; - userId: UUID; -}) { - const relationships = await getRelationships({ runtime, userId }); - - const formattedRelationships = relationships.map( - (relationship: Relationship) => { - const { sourceEntityId, targetEntityId } = relationship; - - if (sourceEntityId === userId) { - return targetEntityId; - } - - return sourceEntityId; - } - ); - - return formattedRelationships; -} diff --git a/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql b/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql index c6af80d9f43..de6d1ea7d51 100644 --- a/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql +++ b/packages/plugin-sql/drizzle/migrations/20250227085330_init.sql @@ -131,7 +131,8 @@ CREATE TABLE "relationships" ( "targetEntityId" uuid NOT NULL, "agentId" uuid NOT NULL, "tags" text[], - "metadata" jsonb + "metadata" jsonb, + CONSTRAINT "unique_relationship" UNIQUE("sourceEntityId","targetEntityId","agentId") ); --> statement-breakpoint CREATE TABLE "rooms" ( diff --git a/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json b/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json index 4185b7aaf03..4bbaaeb3bbd 100644 --- a/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json +++ b/packages/plugin-sql/drizzle/migrations/meta/20250227085330_snapshot.json @@ -1328,7 +1328,17 @@ } }, "compositePrimaryKeys": {}, - "uniqueConstraints": {}, + "uniqueConstraints": { + "unique_relationship": { + "name": "unique_relationship", + "nullsNotDistinct": false, + "columns": [ + "sourceEntityId", + "targetEntityId", + "agentId" + ] + } + }, "policies": {}, "checkConstraints": {}, "isRLSEnabled": false diff --git a/packages/plugin-sql/drizzle/migrations/meta/_journal.json b/packages/plugin-sql/drizzle/migrations/meta/_journal.json index 91cc08634e2..d6c124f8f44 100644 --- a/packages/plugin-sql/drizzle/migrations/meta/_journal.json +++ b/packages/plugin-sql/drizzle/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1740646410010, "tag": "20250227085330_init", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1740738663301, + "tag": "20250228103103_big_kitty_pryde", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index fa3951332e3..ebc302a3bb2 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -1350,6 +1350,8 @@ export abstract class BaseDrizzleAdapter metadata?: { [key: string]: any }; }): Promise { return this.withDatabase(async () => { + console.log('**** creating relationship', params) + console.trace() try { const id = v4(); await this.db.insert(relationshipTable).values({ @@ -1372,6 +1374,7 @@ export abstract class BaseDrizzleAdapter } async updateRelationship(relationship: Relationship): Promise { + console.log('**** updating relationship', relationship) return this.withDatabase(async () => { try { await this.db.update(relationshipTable) diff --git a/packages/plugin-sql/src/schema/relationship.ts b/packages/plugin-sql/src/schema/relationship.ts index 4e1ce63a420..dd0610317f8 100644 --- a/packages/plugin-sql/src/schema/relationship.ts +++ b/packages/plugin-sql/src/schema/relationship.ts @@ -5,6 +5,7 @@ import { index, foreignKey, jsonb, + unique, } from "drizzle-orm/pg-core"; import { sql } from "drizzle-orm"; import { numberTimestamp } from "./types"; @@ -32,6 +33,7 @@ export const relationshipTable = pgTable( }, (table) => [ index("idx_relationships_users").on(table.sourceEntityId, table.targetEntityId), + unique("unique_relationship").on(table.sourceEntityId, table.targetEntityId, table.agentId), foreignKey({ name: "fk_user_a", columns: [table.sourceEntityId], From 6346f7406ef543c54fb1f5b5fff1174bb314854b Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 28 Feb 2025 05:19:56 -0800 Subject: [PATCH 11/13] fix lint and some string formatting template things --- .devcontainer/devcontainer.json | 72 +++---- .github/workflows/ci.yaml | 90 ++++----- .github/workflows/codeql.yml | 100 +++++----- .github/workflows/generate-changelog.yml | 54 +++--- .../generate-readme-translations.yml | 178 +++++++++--------- .github/workflows/image.yaml | 114 +++++------ .github/workflows/pr.yaml | 44 ++--- .github/workflows/pre-release.yml | 114 +++++------ .github/workflows/release.yaml | 108 +++++------ biome.json | 10 +- package.json | 4 +- packages/agent/src/plugins.test.ts | 12 +- packages/agent/src/server/api/agent.ts | 2 +- packages/agent/src/swarm/scenario.ts | 10 +- packages/agent/src/swarm/settings.ts | 6 +- packages/cli/src/utils/install-plugin.ts | 2 +- packages/client/src/components/overview.tsx | 2 +- packages/core/__tests__/goals.test.ts | 78 ++++---- packages/core/__tests__/knowledge.test.ts | 4 +- packages/core/__tests__/memory.test.ts | 4 +- packages/core/src/actions/followRoom.ts | 2 +- packages/core/src/actions/ignore.ts | 2 +- packages/core/src/actions/muteRoom.ts | 2 +- packages/core/src/actions/none.ts | 2 +- packages/core/src/actions/options.ts | 12 +- packages/core/src/actions/roles.ts | 27 ++- packages/core/src/actions/sendMessage.ts | 16 +- packages/core/src/actions/settings.ts | 28 +-- packages/core/src/actions/unfollowRoom.ts | 2 +- packages/core/src/actions/unmuteRoom.ts | 2 +- packages/core/src/actions/updateEntity.ts | 16 +- packages/core/src/bootstrap.ts | 20 +- packages/core/src/database.ts | 2 +- packages/core/src/entities.ts | 2 +- packages/core/src/evaluators/goal.ts | 2 +- packages/core/src/evaluators/reflection.ts | 19 +- packages/core/src/generation.ts | 1 - packages/core/src/messages.ts | 25 +-- packages/core/src/parsing.ts | 6 +- packages/core/src/providers/facts.ts | 2 +- packages/core/src/providers/options.ts | 2 +- packages/core/src/providers/relationships.ts | 4 +- packages/core/src/providers/roles.ts | 21 ++- packages/core/src/providers/settings.ts | 36 ++-- packages/core/src/providers/time.ts | 2 +- packages/core/src/roles.ts | 2 +- packages/core/src/runtime.ts | 7 +- packages/core/src/settings.ts | 2 +- packages/core/src/types.ts | 4 +- .../plugin-discord/src/actions/voiceJoin.ts | 8 +- .../plugin-discord/src/actions/voiceLeave.ts | 4 +- packages/plugin-discord/src/index.ts | 8 +- packages/plugin-discord/src/messages.ts | 2 +- .../src/providers/voiceState.ts | 4 +- packages/plugin-sql/src/base.ts | 21 +-- packages/plugin-sql/src/types.ts | 2 +- .../src/providers/deriveKeyProvider.ts | 18 +- .../providers/remoteAttestationProvider.ts | 12 +- packages/plugin-tee/src/vendors/index.ts | 2 +- .../plugin-twitter/src/actions/spaceJoin.ts | 6 +- packages/plugin-twitter/src/base.ts | 6 +- packages/plugin-twitter/src/sttTtsSpaces.ts | 4 +- packages/plugin-twitter/src/utils.ts | 6 +- 63 files changed, 677 insertions(+), 704 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0bb3eadc539..949aa57ae6a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,38 +1,38 @@ // See https://aka.ms/vscode-remote/devcontainer.json for format details. { - "name": "elizaos-dev", - "dockerFile": "Dockerfile", - "build": { - "args": { - "NODE_VER": "23.5.0", - "BUN_VER": "1.2.2" - } - }, - "privileged": true, - "runArgs": [ - "-p=3000:3000", // Add port for server api - "-p=5173:5173", // Add port for client - //"--volume=/usr/lib/wsl:/usr/lib/wsl", // uncomment for WSL - //"--volume=/mnt/wslg:/mnt/wslg", // uncomment for WSL - "--gpus=all", // ! uncomment for vGPU - //"--device=/dev/dxg", // uncomment this for vGPU under WSL - "--device=/dev/dri" - ], - "containerEnv": { - //"MESA_D3D12_DEFAULT_ADAPTER_NAME": "NVIDIA", // uncomment for WSL - //"LD_LIBRARY_PATH": "/usr/lib/wsl/lib" // uncomment for WSL - }, - "customizations": { - "vscode": { - "extensions": [ - "vscode.json-language-features", - "vscode.css-language-features", - // "foxundermoon.shell-format", - // "dbaeumer.vscode-eslint", - // "esbenp.prettier-vscode" - "ms-python.python" - ] - } - }, - "features": {} -} \ No newline at end of file + "name": "elizaos-dev", + "dockerFile": "Dockerfile", + "build": { + "args": { + "NODE_VER": "23.5.0", + "BUN_VER": "1.2.2" + } + }, + "privileged": true, + "runArgs": [ + "-p=3000:3000", // Add port for server api + "-p=5173:5173", // Add port for client + //"--volume=/usr/lib/wsl:/usr/lib/wsl", // uncomment for WSL + //"--volume=/mnt/wslg:/mnt/wslg", // uncomment for WSL + "--gpus=all", // ! uncomment for vGPU + //"--device=/dev/dxg", // uncomment this for vGPU under WSL + "--device=/dev/dri" + ], + "containerEnv": { + //"MESA_D3D12_DEFAULT_ADAPTER_NAME": "NVIDIA", // uncomment for WSL + //"LD_LIBRARY_PATH": "/usr/lib/wsl/lib" // uncomment for WSL + }, + "customizations": { + "vscode": { + "extensions": [ + "vscode.json-language-features", + "vscode.css-language-features", + // "foxundermoon.shell-format", + // "dbaeumer.vscode-eslint", + // "esbenp.prettier-vscode" + "ms-python.python" + ] + } + }, + "features": {} +} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d39655b5274..8f491e12f8d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,48 +1,48 @@ name: ci on: - push: - branches: [main] - pull_request: - branches: [main] + push: + branches: [main] + pull_request: + branches: [main] jobs: - check: - runs-on: ubuntu-latest - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ${{ vars.TURBO_TEAM }} - TURBO_REMOTE_ONLY: true - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - - - uses: actions/setup-node@v4 - with: - node-version: "23" - - - name: Install dependencies - run: bun install - - - name: Setup Biome CLI - uses: biomejs/setup-biome@v2 - with: - version: latest - - - name: Run Biome - run: biome ci - - - name: Create test env file - run: | - echo "TEST_DATABASE_CLIENT=sqlite" > packages/core/.env.test - echo "NODE_ENV=test" >> packages/core/.env.test - - - name: Run tests - run: cd packages/core && bun test:coverage - - - name: Build packages - run: bun run build - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} + check: + runs-on: ubuntu-latest + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + TURBO_REMOTE_ONLY: true + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + + - uses: actions/setup-node@v4 + with: + node-version: "23" + + - name: Install dependencies + run: bun install + + - name: Setup Biome CLI + uses: biomejs/setup-biome@v2 + with: + version: latest + + - name: Run Biome + run: biome ci + + - name: Create test env file + run: | + echo "TEST_DATABASE_CLIENT=sqlite" > packages/core/.env.test + echo "NODE_ENV=test" >> packages/core/.env.test + + - name: Run tests + run: cd packages/core && bun test:coverage + + - name: Build packages + run: bun run build + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 359ff9d6780..6bfc55477ee 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,55 +1,55 @@ name: "CodeQL Advanced" on: - push: - branches: ["main", "develop"] - pull_request: - branches: ["main", "develop"] - schedule: - - cron: "29 8 * * 6" + push: + branches: ["main", "develop"] + pull_request: + branches: ["main", "develop"] + schedule: + - cron: "29 8 * * 6" jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - # required for all workflows - security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - # only required for workflows in private repositories - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: javascript-typescript - build-mode: none - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: javascript-typescript + build-mode: none + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml index 5b2cd3598c6..bddf628627b 100644 --- a/.github/workflows/generate-changelog.yml +++ b/.github/workflows/generate-changelog.yml @@ -1,30 +1,30 @@ name: Generate Changelog on: - push: - tags: - - "*" + push: + tags: + - "*" jobs: - changelog: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - ref: main - token: ${{ secrets.CHANGELOG_GITHUB_TOKEN }} - - name: Generate Changelog - run: | - export PATH="$PATH:/home/runner/.local/share/gem/ruby/3.0.0/bin" - gem install --user-install github_changelog_generator - github_changelog_generator \ - -u ${{ github.repository_owner }} \ - -p ${{ github.event.repository.name }} \ - --token ${{ secrets.CHANGELOG_GITHUB_TOKEN }} - - name: Commit Changelog - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore: update changelog" - branch: main - file_pattern: "CHANGELOG.md" - commit_author: "GitHub Action " + changelog: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.CHANGELOG_GITHUB_TOKEN }} + - name: Generate Changelog + run: | + export PATH="$PATH:/home/runner/.local/share/gem/ruby/3.0.0/bin" + gem install --user-install github_changelog_generator + github_changelog_generator \ + -u ${{ github.repository_owner }} \ + -p ${{ github.event.repository.name }} \ + --token ${{ secrets.CHANGELOG_GITHUB_TOKEN }} + - name: Commit Changelog + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update changelog" + branch: main + file_pattern: "CHANGELOG.md" + commit_author: "GitHub Action " diff --git a/.github/workflows/generate-readme-translations.yml b/.github/workflows/generate-readme-translations.yml index 5e142c2980d..66587a865c1 100644 --- a/.github/workflows/generate-readme-translations.yml +++ b/.github/workflows/generate-readme-translations.yml @@ -1,98 +1,98 @@ name: Generate Readme Translations on: - push: - branches: - - "1222--README-ci-auto-translation" + push: + branches: + - "1222--README-ci-auto-translation" jobs: - translation: - runs-on: ubuntu-latest - strategy: - matrix: - language: - [ - { code: "CN", name: "Chinese" }, - { code: "DE", name: "German" }, - { code: "ES", name: "Spanish" }, - { code: "FR", name: "French" }, - { code: "HE", name: "Hebrew" }, - { code: "IT", name: "Italian" }, - { code: "JA", name: "Japanese" }, - { code: "KOR", name: "Korean" }, - { code: "PTBR", name: "Portuguese (Brazil)" }, - { code: "RU", name: "Russian" }, - { code: "TH", name: "Thai" }, - { code: "TR", name: "Turkish" }, - { code: "VI", name: "Vietnamese" }, - { code: "AR", name: "Arabic" }, - { code: "RS", name: "Srpski" }, - { code: "TG", name: "Tagalog" }, - { code: "PL", name: "Polski" }, - { code: "HU", name: "Hungarian" }, - { code: "FA", name: "Persian" }, - { code: "RO", name: "Romanian" }, - { code: "GR", name: "Greek" }, - { code: "NL", name: "Dutch" }, - ] - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - with: - ref: main - token: ${{ secrets.GH_TOKEN }} + translation: + runs-on: ubuntu-latest + strategy: + matrix: + language: + [ + { code: "CN", name: "Chinese" }, + { code: "DE", name: "German" }, + { code: "ES", name: "Spanish" }, + { code: "FR", name: "French" }, + { code: "HE", name: "Hebrew" }, + { code: "IT", name: "Italian" }, + { code: "JA", name: "Japanese" }, + { code: "KOR", name: "Korean" }, + { code: "PTBR", name: "Portuguese (Brazil)" }, + { code: "RU", name: "Russian" }, + { code: "TH", name: "Thai" }, + { code: "TR", name: "Turkish" }, + { code: "VI", name: "Vietnamese" }, + { code: "AR", name: "Arabic" }, + { code: "RS", name: "Srpski" }, + { code: "TG", name: "Tagalog" }, + { code: "PL", name: "Polski" }, + { code: "HU", name: "Hungarian" }, + { code: "FA", name: "Persian" }, + { code: "RO", name: "Romanian" }, + { code: "GR", name: "Greek" }, + { code: "NL", name: "Dutch" }, + ] + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.GH_TOKEN }} - - name: Translate to ${{ matrix.language.name }} - uses: 0xjord4n/aixion@v1.2.1 - id: aixion - with: - config: > - { - "provider": "openai", - "provider_options": { - "api_key": "${{ secrets.OPENAI_API_KEY }}" - }, - "messages": [ - { - "role": "system", - "content": "You will be provided with a markdown file in English, and your task is to translate it into ${{ matrix.language.name }}." - }, - { - "role": "user", - "content_path": "README.md" - } - ], - save_path: "packages/docs/packages/docs/i18n/readme/README_${{ matrix.language.code }}.md", - "model": "gpt-4o" - } + - name: Translate to ${{ matrix.language.name }} + uses: 0xjord4n/aixion@v1.2.1 + id: aixion + with: + config: > + { + "provider": "openai", + "provider_options": { + "api_key": "${{ secrets.OPENAI_API_KEY }}" + }, + "messages": [ + { + "role": "system", + "content": "You will be provided with a markdown file in English, and your task is to translate it into ${{ matrix.language.name }}." + }, + { + "role": "user", + "content_path": "README.md" + } + ], + save_path: "packages/docs/packages/docs/i18n/readme/README_${{ matrix.language.code }}.md", + "model": "gpt-4o" + } - # Upload each translated file as an artifact - - name: Upload translation - uses: actions/upload-artifact@v4 - with: - name: readme-${{ matrix.language.code }} - path: README_${{ matrix.language.code }}.md + # Upload each translated file as an artifact + - name: Upload translation + uses: actions/upload-artifact@v4 + with: + name: readme-${{ matrix.language.code }} + path: README_${{ matrix.language.code }}.md - commit: - needs: translation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: main - token: ${{ secrets.GH_TOKEN }} + commit: + needs: translation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.GH_TOKEN }} - # Download all translation artifacts - - name: Download all translations - uses: actions/download-artifact@v4 - with: - pattern: readme-* - merge-multiple: true + # Download all translation artifacts + - name: Download all translations + uses: actions/download-artifact@v4 + with: + pattern: readme-* + merge-multiple: true - - name: Commit all translations - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore: update all README translations" - branch: main - file_pattern: "README_*.md" - commit_author: "GitHub Action " + - name: Commit all translations + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update all README translations" + branch: main + file_pattern: "README_*.md" + commit_author: "GitHub Action " diff --git a/.github/workflows/image.yaml b/.github/workflows/image.yaml index 2135ed59c83..c9a8cc518f2 100644 --- a/.github/workflows/image.yaml +++ b/.github/workflows/image.yaml @@ -3,68 +3,68 @@ name: Create and publish a Docker image # Configures this workflow to run every time a change is pushed to the branch called `release`. on: - release: - types: [created] - workflow_dispatch: + release: + types: [created] + workflow_dispatch: # Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. jobs: - build-and-push-image: - runs-on: ubuntu-latest - # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. - permissions: - contents: read - packages: write - attestations: write - id-token: write - # - steps: - - name: Checkout repository - uses: actions/checkout@v4 - # Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - - name: Log in to the Container registry - uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. - # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. - # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - - name: Build and push Docker image - id: push - uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + attestations: write + id-token: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + id: push + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} - # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." - - name: Generate artifact attestation - uses: actions/attest-build-provenance@v1 - with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} - subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: true + # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true - # This step makes the Docker image public, so users can pull it without authentication. - - name: Make Docker image public - run: | - curl \ - -X PATCH \ - -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/user/packages/container/${{ env.IMAGE_NAME }}/visibility \ - -d '{"visibility":"public"}' + # This step makes the Docker image public, so users can pull it without authentication. + - name: Make Docker image public + run: | + curl \ + -X PATCH \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/user/packages/container/${{ env.IMAGE_NAME }}/visibility \ + -d '{"visibility":"public"}' diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index fb67a97dbce..ff90535fcc9 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -1,30 +1,30 @@ name: PR Title Check on: - pull_request: - types: [opened, edited, synchronize] + pull_request: + types: [opened, edited, synchronize] jobs: - check-pr-title: - runs-on: ubuntu-latest + check-pr-title: + runs-on: ubuntu-latest - steps: - - name: Check out the repository - uses: actions/checkout@v4 + steps: + - name: Check out the repository + uses: actions/checkout@v4 - - name: Validate PR title - id: validate - run: | - PR_TITLE=$(jq -r .pull_request.title "$GITHUB_EVENT_PATH") - echo "PR Title: $PR_TITLE" - if [[ ! "$PR_TITLE" =~ ^(feat|fix|docs|style|refactor|test|chore)(\([a-zA-Z0-9-]+\))?:\ .+ ]]; then - echo "PR title does not match the required pattern." - exit 1 - fi + - name: Validate PR title + id: validate + run: | + PR_TITLE=$(jq -r .pull_request.title "$GITHUB_EVENT_PATH") + echo "PR Title: $PR_TITLE" + if [[ ! "$PR_TITLE" =~ ^(feat|fix|docs|style|refactor|test|chore)(\([a-zA-Z0-9-]+\))?:\ .+ ]]; then + echo "PR title does not match the required pattern." + exit 1 + fi - - name: Set status - if: failure() - run: | - gh pr comment ${{ github.event.pull_request.number }} --body "❌ PR title does not match the required pattern. Please use one of these formats: - - 'type: description' (e.g., 'feat: add new feature') - - 'type(scope): description' (e.g., 'chore(core): update dependencies')" + - name: Set status + if: failure() + run: | + gh pr comment ${{ github.event.pull_request.number }} --body "❌ PR title does not match the required pattern. Please use one of these formats: + - 'type: description' (e.g., 'feat: add new feature') + - 'type(scope): description' (e.g., 'chore(core): update dependencies')" diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 71c6b53dc7a..dcff059b229 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -1,73 +1,73 @@ name: Pre-Release on: - workflow_dispatch: - inputs: - release_type: - description: "Type of release (prerelease, prepatch, patch, minor, preminor, major)" - required: true - default: "prerelease" + workflow_dispatch: + inputs: + release_type: + description: "Type of release (prerelease, prepatch, patch, minor, preminor, major)" + required: true + default: "prerelease" jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: "23" + - uses: actions/setup-node@v4 + with: + node-version: "23" - - uses: oven-sh/setup-bun@v2 + - uses: oven-sh/setup-bun@v2 - - name: Configure Git - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" + - name: Configure Git + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" - - name: "Setup npm for npmjs" - run: | - npm config set registry https://registry.npmjs.org/ - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc + - name: "Setup npm for npmjs" + run: | + npm config set registry https://registry.npmjs.org/ + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - - name: Install Protobuf Compiler - run: sudo apt-get install -y protobuf-compiler + - name: Install Protobuf Compiler + run: sudo apt-get install -y protobuf-compiler - - name: Install dependencies - run: bun install + - name: Install dependencies + run: bun install - - name: Build packages - run: bun run build + - name: Build packages + run: bun run build - - name: Tag and Publish Packages - id: tag_publish - run: | - RELEASE_TYPE=${{ github.event_name == 'push' && 'prerelease' || github.event.inputs.release_type }} - npx lerna version $RELEASE_TYPE --conventional-commits --yes --no-private --force-publish - npx lerna publish from-git --yes --dist-tag next + - name: Tag and Publish Packages + id: tag_publish + run: | + RELEASE_TYPE=${{ github.event_name == 'push' && 'prerelease' || github.event.inputs.release_type }} + npx lerna version $RELEASE_TYPE --conventional-commits --yes --no-private --force-publish + npx lerna publish from-git --yes --dist-tag next - - name: Get Version Tag - id: get_tag - run: echo "TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT + - name: Get Version Tag + id: get_tag + run: echo "TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT - - name: Generate Release Body - id: release_body - run: | - if [ -f CHANGELOG.md ]; then - echo "body=$(cat CHANGELOG.md)" >> $GITHUB_OUTPUT - else - echo "body=No changelog provided for this release." >> $GITHUB_OUTPUT - fi + - name: Generate Release Body + id: release_body + run: | + if [ -f CHANGELOG.md ]; then + echo "body=$(cat CHANGELOG.md)" >> $GITHUB_OUTPUT + else + echo "body=No changelog provided for this release." >> $GITHUB_OUTPUT + fi - - name: Create GitHub Release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - with: - tag_name: ${{ steps.get_tag.outputs.TAG }} - release_name: Release - body_path: CHANGELOG.md - draft: false - prerelease: ${{ github.event_name == 'push' }} + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + with: + tag_name: ${{ steps.get_tag.outputs.TAG }} + release_name: Release + body_path: CHANGELOG.md + draft: false + prerelease: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6e7984e0a20..2d1a38af33d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,59 +1,59 @@ name: Release on: - release: - types: [created] - workflow_dispatch: + release: + types: [created] + workflow_dispatch: jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version: "23" - - - uses: oven-sh/setup-bun@v2 - - - name: Configure Git - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" - - - name: "Setup npm for npmjs" - run: | - npm config set registry https://registry.npmjs.org/ - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - - - name: Install Protobuf Compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Install dependencies - run: bun install - - - name: Build packages - run: bun run build - - - name: Publish Packages - id: publish - run: | - # Get the latest release tag - LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) - - # Force clean the working directory and reset any changes - echo "Cleaning working directory and resetting any changes" - git clean -fd - git reset --hard HEAD - - # Force checkout the latest tag - echo "Checking out latest tag: $LATEST_TAG" - git checkout -b temp-publish-branch $LATEST_TAG - - echo "Publishing version: $LATEST_TAG" - npx lerna publish from-package --yes --dist-tag latest - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: "23" + + - uses: oven-sh/setup-bun@v2 + + - name: Configure Git + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + + - name: "Setup npm for npmjs" + run: | + npm config set registry https://registry.npmjs.org/ + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc + + - name: Install Protobuf Compiler + run: sudo apt-get install -y protobuf-compiler + + - name: Install dependencies + run: bun install + + - name: Build packages + run: bun run build + + - name: Publish Packages + id: publish + run: | + # Get the latest release tag + LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) + + # Force clean the working directory and reset any changes + echo "Cleaning working directory and resetting any changes" + git clean -fd + git reset --hard HEAD + + # Force checkout the latest tag + echo "Checking out latest tag: $LATEST_TAG" + git checkout -b temp-publish-branch $LATEST_TAG + + echo "Publishing version: $LATEST_TAG" + npx lerna publish from-package --yes --dist-tag latest + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/biome.json b/biome.json index 125fb2ecf25..0f3a2a354ae 100644 --- a/biome.json +++ b/biome.json @@ -47,7 +47,15 @@ "**/dist/**", "**/node_modules/**", "**/coverage/**", - "**/*.json" + "**/*.md", + "**/.docusaurus/**", + "**/build/**", + "**/*.json", + "**/.turbo/**", + "**/.git/**", + "**/local_cache/**", + "**/cache/**", + "**/tmp/**" ] }, "formatter": { diff --git a/package.json b/package.json index 3ed3971afec..bc504c4c3b9 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "eliza", "scripts": { - "format": "biome format --write .", + "format": "biome format .", "cli": "bun --filter=@elizaos/cli cli", "lint": "turbo run lint:fix --filter=./packages/*", - "check": "biome check --write .", + "check": "biome check . --fix --unsafe --verbose", "preinstall": "npx only-allow bun", "swarm": "turbo run build --filter=./packages/core && concurrently \"turbo run start --filter=@elizaos/agent -- --swarm \" \"turbo run start --filter=!@elizaos/agent --filter=!@elizaos/docs --filter=!@elizaos/core\"", "swarm:scenario": "turbo run build --filter=./packages/core && concurrently \"turbo run start --filter=@elizaos/agent -- --swarm --scenario \" \"turbo run start --filter=!@elizaos/agent --filter=!@elizaos/docs --filter=!@elizaos/core\"", diff --git a/packages/agent/src/plugins.test.ts b/packages/agent/src/plugins.test.ts index 6d70726e06e..a713b97f122 100644 --- a/packages/agent/src/plugins.test.ts +++ b/packages/agent/src/plugins.test.ts @@ -192,7 +192,7 @@ class TestRunner { if (!this.testResults.has(file)) { this.testResults.set(file, []); } - this.testResults.get(file)!.push({ file, suite, name, status, error }); + this.testResults.get(file)?.push({ file, suite, name, status, error }); } private async runTestSuite(suite: TestSuite, file: string): Promise { @@ -275,7 +275,7 @@ class TestRunner { if (!groupedBySuite.has(t.suite)) { groupedBySuite.set(t.suite, []); } - groupedBySuite.get(t.suite)!.push(t); + groupedBySuite.get(t.suite)?.push(t); }); groupedBySuite.forEach((suiteTests, suite) => { @@ -302,8 +302,8 @@ class TestRunner { tests.forEach(test => { if (test.status === "failed") { console.log(` ${colorize("FAIL", "red")} ${test.file} > ${test.suite} > ${test.name}`); - console.log(` ${colorize("AssertionError: " + test.error!.message, "red")}`); - console.log("\n" + colorize("⎯".repeat(66), "red") + "\n"); + console.log(` ${colorize(`AssertionError: ${test.error?.message}`, "red")}`); + console.log(`\n${colorize("⎯".repeat(66), "red")}\n`); } }); }); @@ -312,8 +312,8 @@ class TestRunner { const printTestSummary = (failedTestSuites: number) => { printSectionHeader("Test Summary", "cyan"); - console.log(` ${colorize("Test Suites:", "gray")} ${failedTestSuites > 0 ? colorize(failedTestSuites + " failed | ", "red") : ""}${colorize((this.testResults.size - failedTestSuites) + " passed", "green")} (${this.testResults.size})`); - console.log(` ${colorize(" Tests:", "gray")} ${this.stats.failed > 0 ? colorize(this.stats.failed + " failed | ", "red") : ""}${colorize(this.stats.passed + " passed", "green")} (${this.stats.total})`); + console.log(` ${colorize("Test Suites:", "gray")} ${failedTestSuites > 0 ? colorize(`${failedTestSuites} failed | `, "red") : ""}${colorize(`${this.testResults.size - failedTestSuites} passed`, "green")} (${this.testResults.size})`); + console.log(` ${colorize(" Tests:", "gray")} ${this.stats.failed > 0 ? colorize(`${this.stats.failed} failed | `, "red") : ""}${colorize(`${this.stats.passed} passed`, "green")} (${this.stats.total})`); }; const failedTestSuites = printTestSuiteSummary(); diff --git a/packages/agent/src/server/api/agent.ts b/packages/agent/src/server/api/agent.ts index bc28611d9d3..0a52f0cd333 100644 --- a/packages/agent/src/server/api/agent.ts +++ b/packages/agent/src/server/api/agent.ts @@ -383,7 +383,7 @@ export function agentRouter( if (!character) { try { character = await directClient.loadCharacterTryPath(characterName); - } catch (e) { + } catch (_e) { const existingAgent = Array.from(agents.values()).find( (a) => a.character.name.toLowerCase() === characterName.toLowerCase() ); diff --git a/packages/agent/src/swarm/scenario.ts b/packages/agent/src/swarm/scenario.ts index 3da35053d35..eaf2bd009b9 100644 --- a/packages/agent/src/swarm/scenario.ts +++ b/packages/agent/src/swarm/scenario.ts @@ -1,10 +1,10 @@ import { ChannelType, - Client, - HandlerCallback, - IAgentRuntime, - Memory, - UUID, + type Client, + type HandlerCallback, + type IAgentRuntime, + type Memory, + type UUID, createUniqueUuid } from "@elizaos/core"; import { v4 as uuidv4 } from "uuid"; diff --git a/packages/agent/src/swarm/settings.ts b/packages/agent/src/swarm/settings.ts index 46a346d8737..670f0694ac8 100644 --- a/packages/agent/src/swarm/settings.ts +++ b/packages/agent/src/swarm/settings.ts @@ -1,13 +1,13 @@ import { - Action, + type Action, ChannelType, createUniqueUuid, - Evaluator, + type Evaluator, type IAgentRuntime, initializeOnboardingConfig, logger, type OnboardingConfig, - Provider, + type Provider, RoleName, type UUID } from "@elizaos/core"; diff --git a/packages/cli/src/utils/install-plugin.ts b/packages/cli/src/utils/install-plugin.ts index 6dfdbbe6a2a..0241d5f6501 100644 --- a/packages/cli/src/utils/install-plugin.ts +++ b/packages/cli/src/utils/install-plugin.ts @@ -2,7 +2,7 @@ import { execa } from "execa"; import path from "node:path"; import { logger } from "@/src/utils/logger"; import { runBunCommand } from "@/src/utils/run-bun"; -import { promises as fs } from "fs"; +import { promises as fs } from "node:fs"; export async function installPlugin( pluginName: string, diff --git a/packages/client/src/components/overview.tsx b/packages/client/src/components/overview.tsx index 473fe6717b3..70d148e7bf9 100644 --- a/packages/client/src/components/overview.tsx +++ b/packages/client/src/components/overview.tsx @@ -251,7 +251,7 @@ export default function Overview({ character }: { character: Character }) { title: "Success", description: "Character updated successfully", }); - } catch (error) { + } catch (_error) { toast({ title: "Error", description: "Failed to update character", diff --git a/packages/core/__tests__/goals.test.ts b/packages/core/__tests__/goals.test.ts index ea698f7e705..e4f12bfe4c5 100644 --- a/packages/core/__tests__/goals.test.ts +++ b/packages/core/__tests__/goals.test.ts @@ -7,25 +7,25 @@ import { updateGoal, } from "../src/goals.ts"; import { - Action, - ChannelType, + type Action, + type ChannelType, type Character, - Client, + type Client, type Goal, GoalStatus, - HandlerCallback, + type HandlerCallback, type IAgentRuntime, type IMemoryManager, type Memory, - ModelClass, - Provider, - RoomData, + type ModelClass, + type Provider, + type RoomData, type Service, type ServiceType, type State, - Task, + type Task, type UUID, - WorldData + type WorldData } from "../src/types.ts"; // Mock the database adapter @@ -35,7 +35,7 @@ export const mockDatabaseAdapter = { createGoal: mock(), }; -const services = new Map(); +const _services = new Map(); // Create memory managers first const messageManager: IMemoryManager = { @@ -102,91 +102,91 @@ export const mockRuntime: IAgentRuntime = { getMemoryManager: () => null, getModel: () => undefined, events: new Map(), - registerClientInterface: function (name: string, client: Client): void { + registerClientInterface: (_name: string, _client: Client): void => { throw new Error("Function not implemented."); }, - transformUserId: function (userId: UUID): UUID { + transformUserId: (_userId: UUID): UUID => { throw new Error("Function not implemented."); }, - registerService: function (service: Service): void { + registerService: (_service: Service): void => { throw new Error("Function not implemented."); }, - setSetting: function (key: string, value: string | boolean | null | any, secret: boolean): void { + setSetting: (_key: string, _value: string | boolean | null | any, _secret: boolean): void => { throw new Error("Function not implemented."); }, - getSetting: function (key: string) { + getSetting: (_key: string) => { throw new Error("Function not implemented."); }, - getConversationLength: function (): number { + getConversationLength: (): number => { throw new Error("Function not implemented."); }, - processActions: function (message: Memory, responses: Memory[], state?: State, callback?: HandlerCallback): Promise { + processActions: (_message: Memory, _responses: Memory[], _state?: State, _callback?: HandlerCallback): Promise => { throw new Error("Function not implemented."); }, - evaluate: function (message: Memory, state?: State, didRespond?: boolean, callback?: HandlerCallback): Promise { + evaluate: (_message: Memory, _state?: State, _didRespond?: boolean, _callback?: HandlerCallback): Promise => { throw new Error("Function not implemented."); }, - getOrCreateUser: function (userId: UUID, userName: string | null, name: string | null, source: string | null): Promise { + getOrCreateUser: (_userId: UUID, _userName: string | null, _name: string | null, _source: string | null): Promise => { throw new Error("Function not implemented."); }, - registerProvider: function (provider: Provider): void { + registerProvider: (_provider: Provider): void => { throw new Error("Function not implemented."); }, - registerAction: function (action: Action): void { + registerAction: (_action: Action): void => { throw new Error("Function not implemented."); }, - ensureConnection: function ({ userId, roomId, userName, userScreenName, source, channelId, serverId, type, }: { userId: UUID; roomId: UUID; userName?: string; userScreenName?: string; source?: string; channelId?: string; serverId?: string; type: ChannelType; }): Promise { + ensureConnection: ({ userId, roomId, userName, userScreenName, source, channelId, serverId, type, }: { userId: UUID; roomId: UUID; userName?: string; userScreenName?: string; source?: string; channelId?: string; serverId?: string; type: ChannelType; }): Promise => { throw new Error("Function not implemented."); }, - ensureParticipantInRoom: function (userId: UUID, roomId: UUID): Promise { + ensureParticipantInRoom: (_userId: UUID, _roomId: UUID): Promise => { throw new Error("Function not implemented."); }, - getWorld: function (worldId: UUID): Promise { + getWorld: (_worldId: UUID): Promise => { throw new Error("Function not implemented."); }, - ensureWorldExists: function ({ id, name, serverId, }: WorldData): Promise { + ensureWorldExists: ({ id, name, serverId, }: WorldData): Promise => { throw new Error("Function not implemented."); }, - getRoom: function (roomId: UUID): Promise { + getRoom: (_roomId: UUID): Promise => { throw new Error("Function not implemented."); }, - registerModel: function (modelClass: ModelClass, handler: (params: any) => Promise): void { + registerModel: (_modelClass: ModelClass, _handler: (params: any) => Promise): void => { throw new Error("Function not implemented."); }, - registerEvent: function (event: string, handler: (params: any) => void): void { + registerEvent: (_event: string, _handler: (params: any) => void): void => { throw new Error("Function not implemented."); }, - getEvent: function (event: string): ((params: any) => void)[] | undefined { + getEvent: (_event: string): ((params: any) => void)[] | undefined => { throw new Error("Function not implemented."); }, - emitEvent: function (event: string | string[], params: any): void { + emitEvent: (_event: string | string[], _params: any): void => { throw new Error("Function not implemented."); }, - registerTask: function (task: Task): UUID { + registerTask: (_task: Task): UUID => { throw new Error("Function not implemented."); }, - getTasks: function ({ roomId, tags, }: { roomId?: UUID; tags?: string[]; }): Task[] | undefined { + getTasks: ({ roomId, tags, }: { roomId?: UUID; tags?: string[]; }): Task[] | undefined => { throw new Error("Function not implemented."); }, - getTask: function (id: UUID): Task | undefined { + getTask: (_id: UUID): Task | undefined => { throw new Error("Function not implemented."); }, - updateTask: function (id: UUID, task: Task): void { + updateTask: (_id: UUID, _task: Task): void => { throw new Error("Function not implemented."); }, - deleteTask: function (id: UUID): void { + deleteTask: (_id: UUID): void => { throw new Error("Function not implemented."); }, - stop: function (): Promise { + stop: (): Promise => { throw new Error("Function not implemented."); }, - ensureAgentExists: function (): Promise { + ensureAgentExists: (): Promise => { throw new Error("Function not implemented."); }, - ensureEmbeddingDimension: function (): Promise { + ensureEmbeddingDimension: (): Promise => { throw new Error("Function not implemented."); }, - ensureCharacterExists: function (character: Character): Promise { + ensureCharacterExists: (_character: Character): Promise => { throw new Error("Function not implemented."); } }; diff --git a/packages/core/__tests__/knowledge.test.ts b/packages/core/__tests__/knowledge.test.ts index 5ba21456a05..1d181a2e5bb 100644 --- a/packages/core/__tests__/knowledge.test.ts +++ b/packages/core/__tests__/knowledge.test.ts @@ -228,7 +228,7 @@ describe("Knowledge Module", () => { const largeText = "test ".repeat(1000); // ~5000 chars // Mock splitChunks to return more chunks for large text - mockSplitChunks.mockImplementation(async (text: string) => { + mockSplitChunks.mockImplementation(async (_text: string) => { // Create ~7 chunks for the test return Array.from({ length: 7 }, (_, i) => `chunk${i + 1} of large text` @@ -279,7 +279,7 @@ describe("Knowledge Module", () => { ]; for (const config of configs) { - const result = await knowledge.set(mockRuntime, { + const _result = await knowledge.set(mockRuntime, { id: TEST_UUID_1, content: { text } }, config); diff --git a/packages/core/__tests__/memory.test.ts b/packages/core/__tests__/memory.test.ts index ea60c431f56..c8721da6523 100644 --- a/packages/core/__tests__/memory.test.ts +++ b/packages/core/__tests__/memory.test.ts @@ -376,12 +376,12 @@ describe("MemoryManager", () => { }); it("should preserve semantic boundaries when possible", async () => { - const text = "First paragraph.\n\nSecond paragraph.\n\nThird paragraph."; + const _text = "First paragraph.\n\nSecond paragraph.\n\nThird paragraph."; // Test that fragments break at paragraph boundaries }); it("should handle multilingual content properly", async () => { - const multilingualText = "English text. 中文文本. Русский текст."; + const _multilingualText = "English text. 中文文本. Русский текст."; // Test proper handling of different scripts }); }); diff --git a/packages/core/src/actions/followRoom.ts b/packages/core/src/actions/followRoom.ts index 9e57bb2380e..348dba166e7 100644 --- a/packages/core/src/actions/followRoom.ts +++ b/packages/core/src/actions/followRoom.ts @@ -1,7 +1,7 @@ import { composeContext } from "../context"; import { generateTrueOrFalse } from "../generation"; import { booleanFooter } from "../parsing"; -import { Action, ActionExample, HandlerCallback, IAgentRuntime, Memory, ModelClass, State } from "../types"; +import { type Action, type ActionExample, type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, type State } from "../types"; export const shouldFollowTemplate = `# Task: Decide if {{agentName}} should start following this room, i.e. eagerly participating without explicit mentions. diff --git a/packages/core/src/actions/ignore.ts b/packages/core/src/actions/ignore.ts index 9d5b1054339..cf0e027358c 100644 --- a/packages/core/src/actions/ignore.ts +++ b/packages/core/src/actions/ignore.ts @@ -1,4 +1,4 @@ -import { Action, ActionExample, IAgentRuntime, Memory } from "../types"; +import type { Action, ActionExample, IAgentRuntime, Memory } from "../types"; export const ignoreAction: Action = { name: "IGNORE", diff --git a/packages/core/src/actions/muteRoom.ts b/packages/core/src/actions/muteRoom.ts index 9ca386059a8..b20245b88ef 100644 --- a/packages/core/src/actions/muteRoom.ts +++ b/packages/core/src/actions/muteRoom.ts @@ -1,7 +1,7 @@ import { composeContext } from "../context"; import { generateTrueOrFalse } from "../generation"; import { booleanFooter } from "../parsing"; -import { Action, ActionExample, HandlerCallback, IAgentRuntime, Memory, ModelClass, State } from "../types"; +import { type Action, type ActionExample, type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, type State } from "../types"; export const shouldMuteTemplate = `# Task: Decide if {{agentName}} should mute this room and stop responding unless explicitly mentioned. diff --git a/packages/core/src/actions/none.ts b/packages/core/src/actions/none.ts index a71bad32332..af9179022ba 100644 --- a/packages/core/src/actions/none.ts +++ b/packages/core/src/actions/none.ts @@ -1,4 +1,4 @@ -import { Action, ActionExample, IAgentRuntime, Memory } from "../types"; +import type { Action, ActionExample, IAgentRuntime, Memory } from "../types"; export const noneAction: Action = { name: "NONE", diff --git a/packages/core/src/actions/options.ts b/packages/core/src/actions/options.ts index 8fa5b62102b..03c50edfa70 100644 --- a/packages/core/src/actions/options.ts +++ b/packages/core/src/actions/options.ts @@ -1,13 +1,13 @@ import { composeContext } from "../context"; import { logger } from "../logger"; import { - Action, - ActionExample, - HandlerCallback, - IAgentRuntime, - Memory, + type Action, + type ActionExample, + type HandlerCallback, + type IAgentRuntime, + type Memory, ModelClass, - State, + type State, } from "../types"; const optionExtractionTemplate = `# Task: Extract selected task and option from user message diff --git a/packages/core/src/actions/roles.ts b/packages/core/src/actions/roles.ts index ec36916dcee..d73f8b87c3d 100644 --- a/packages/core/src/actions/roles.ts +++ b/packages/core/src/actions/roles.ts @@ -1,7 +1,7 @@ import { createUniqueUuid, generateObjectArray } from ".."; import { composeContext } from "../context"; import { logger } from "../logger"; -import { Action, ActionExample, ChannelType, HandlerCallback, IAgentRuntime, Memory, ModelClass, RoleName, State, UUID } from "../types"; +import { type Action, type ActionExample, ChannelType, type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, RoleName, type State, type UUID } from "../types"; // Role modification validation helper const canModifyRole = ( @@ -69,7 +69,7 @@ const updateRoleAction: Action = { name: "UPDATE_ROLE", similes: ["CHANGE_ROLE", "SET_ROLE", "MODIFY_ROLE"], description: - "Updates the role for a user with respect to the agent, world being the server they are in. For example, if an admin tells the agent that a user is their boss, set their role to ADMIN.", + "Updates the role for a user with respect to the agent, world being the server they are in. For example, if an admin tells the agent that a user is their boss, set their role to ADMIN. Can only be used to set roles to ADMIN, OWNER or NONE. Can't be used to ban.", validate: async ( runtime: IAgentRuntime, @@ -145,7 +145,6 @@ const updateRoleAction: Action = { callback: HandlerCallback, responses: Memory[] ): Promise => { - console.log("**** UPDATE_ROLE handler") // Handle initial responses for (const response of responses) { await callback(response.content); @@ -182,9 +181,6 @@ const updateRoleAction: Action = { // Get all entities in the room const entities = await runtime.databaseAdapter.getEntitiesForRoom(room.id, runtime.agentId, true); - - console.log('***** entities are') - console.log(entities) // Build server members context from entities const serverMembersContext = entities @@ -206,8 +202,6 @@ const updateRoleAction: Action = { template: extractionTemplate, }); - console.log('****** extractionContext\n', extractionContext) - // Extract role assignments const result = (await generateObjectArray({ runtime, @@ -216,7 +210,6 @@ const updateRoleAction: Action = { })) as RoleAssignment[]; if (!result?.length) { - console.log("No valid role assignments found in the request."); await callback({ text: "No valid role assignments found in the request.", action: "UPDATE_ROLE", @@ -302,6 +295,22 @@ const updateRoleAction: Action = { }, }, ], + [ + { + user: "{{user1}}", + content: { + text: "Ban @troublemaker", + source: "discord", + } + }, + { + user: "{{user3}}", + content: { + text: "I cannot ban users.", + action: "REPLY", + } + } + ] ] as ActionExample[][], }; diff --git a/packages/core/src/actions/sendMessage.ts b/packages/core/src/actions/sendMessage.ts index 82772e910cc..173c9d73252 100644 --- a/packages/core/src/actions/sendMessage.ts +++ b/packages/core/src/actions/sendMessage.ts @@ -4,14 +4,14 @@ import { v4 as uuidv4 } from 'uuid'; import { logger } from "../logger"; import { - Action, - ActionExample, + type Action, + type ActionExample, Component, - HandlerCallback, - IAgentRuntime, - Memory, + type HandlerCallback, + type IAgentRuntime, + type Memory, ModelClass, - State, + type State, UUID, Entity, sendDirectMessage, @@ -69,7 +69,7 @@ export const sendMessageAction: Action = { validate: async ( runtime: IAgentRuntime, message: Memory, - state: State + _state: State ): Promise => { // Check if we have permission to send messages const worldId = message.roomId; @@ -104,7 +104,7 @@ export const sendMessageAction: Action = { const sourceEntityId = message.userId; const roomId = message.roomId; - const agentId = runtime.agentId; + const _agentId = runtime.agentId; const room = await runtime.getRoom(roomId); const worldId = room.worldId; diff --git a/packages/core/src/actions/settings.ts b/packages/core/src/actions/settings.ts index cd6ba33dd18..4718d82521c 100644 --- a/packages/core/src/actions/settings.ts +++ b/packages/core/src/actions/settings.ts @@ -5,16 +5,16 @@ import { logger } from "../logger"; import { messageCompletionFooter } from "../parsing"; import { findWorldForOwner } from "../roles"; import { - Action, - ActionExample, + type Action, + type ActionExample, ChannelType, - HandlerCallback, - IAgentRuntime, - Memory, + type HandlerCallback, + type IAgentRuntime, + type Memory, ModelClass, - OnboardingSetting, - State, - WorldSettings, + type OnboardingSetting, + type State, + type WorldSettings, } from "../types"; interface SettingUpdate { @@ -164,7 +164,7 @@ function categorizeSettings(worldSettings: WorldSettings): { */ async function extractSettingValues( runtime: IAgentRuntime, - message: Memory, + _message: Memory, state: State, worldSettings: WorldSettings ): Promise { @@ -328,7 +328,7 @@ const successTemplate = `# Task: Generate a response for successful setting upda Write a natural, conversational response that {{agentName}} would send about the successful update and next steps. Include the action "SETTING_UPDATED" in your response. -` + messageCompletionFooter; +${messageCompletionFooter}`; // Template for failure responses when settings couldn't be updated const failureTemplate = `# Task: Generate a response for failed setting updates @@ -357,7 +357,7 @@ const failureTemplate = `# Task: Generate a response for failed setting updates Write a natural, conversational response that {{agentName}} would send about the failed update and how to proceed. Include the action "SETTING_UPDATE_FAILED" in your response. -` + messageCompletionFooter; +${messageCompletionFooter}`; // Template for error responses when unexpected errors occur const errorTemplate = `# Task: Generate a response for an error during setting updates @@ -376,7 +376,7 @@ const errorTemplate = `# Task: Generate a response for an error during setting u Write a natural, conversational response that {{agentName}} would send about the error. Include the action "SETTING_UPDATE_ERROR" in your response. -` + messageCompletionFooter; +${messageCompletionFooter}`; // Template for completion responses when all required settings are configured const completionTemplate = `# Task: Generate a response for settings completion @@ -400,7 +400,7 @@ const completionTemplate = `# Task: Generate a response for settings completion Write a natural, conversational response that {{agentName}} would send about the successful completion of settings. Include the action "ONBOARDING_COMPLETE" in your response. -` + messageCompletionFooter; +${messageCompletionFooter}`; /** * Handles the completion of settings when all required settings are configured @@ -729,7 +729,7 @@ const updateSettingsAction: Action = { serverId ); if (!updatedWorldSettings) { - logger.error(`Failed to retrieve updated settings state`); + logger.error("Failed to retrieve updated settings state"); await generateErrorResponse(runtime, state, callback); return; } diff --git a/packages/core/src/actions/unfollowRoom.ts b/packages/core/src/actions/unfollowRoom.ts index 305c11ac5f5..bf76be988a8 100644 --- a/packages/core/src/actions/unfollowRoom.ts +++ b/packages/core/src/actions/unfollowRoom.ts @@ -1,7 +1,7 @@ import { generateTrueOrFalse } from ".."; import { composeContext } from "../context"; import { booleanFooter } from "../parsing"; -import { Action, ActionExample, HandlerCallback, IAgentRuntime, Memory, ModelClass, State } from "../types"; +import { type Action, type ActionExample, type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, type State } from "../types"; const shouldUnfollowTemplate = `# Task: Decide if {{agentName}} should stop closely following this previously followed room and only respond when mentioned. diff --git a/packages/core/src/actions/unmuteRoom.ts b/packages/core/src/actions/unmuteRoom.ts index 13dd31017c6..14d82dc5f74 100644 --- a/packages/core/src/actions/unmuteRoom.ts +++ b/packages/core/src/actions/unmuteRoom.ts @@ -1,7 +1,7 @@ import { generateTrueOrFalse } from "../generation"; import { composeContext } from "../context"; import { booleanFooter } from "../parsing"; -import { Action, ActionExample, HandlerCallback, IAgentRuntime, Memory, ModelClass, State } from "../types"; +import { type Action, type ActionExample, type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, type State } from "../types"; export const shouldUnmuteTemplate = `# Task: Decide if {{agentName}} should unmute this previously muted room and start considering it for responses again. diff --git a/packages/core/src/actions/updateEntity.ts b/packages/core/src/actions/updateEntity.ts index 2471c87e48c..05a6782de2d 100644 --- a/packages/core/src/actions/updateEntity.ts +++ b/packages/core/src/actions/updateEntity.ts @@ -9,15 +9,15 @@ import { v4 as uuidv4 } from 'uuid'; import { logger } from "../logger"; import { - Action, - ActionExample, + type Action, + type ActionExample, Component, - HandlerCallback, - IAgentRuntime, - Memory, + type HandlerCallback, + type IAgentRuntime, + type Memory, ModelClass, - State, - UUID, + type State, + type UUID, Entity } from "../types"; import { composeContext } from "../context"; @@ -99,7 +99,7 @@ export const updateEntityAction: Action = { validate: async ( runtime: IAgentRuntime, message: Memory, - state: State + _state: State ): Promise => { // Check if we have any registered sources or existing components that could be updated const worldId = message.roomId; diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index 3017f1719c8..730e435ab6e 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -1,4 +1,4 @@ -import { UUID } from "crypto"; +import type { UUID } from "node:crypto"; import { v4 } from "uuid"; import { followRoomAction } from "./actions/followRoom.ts"; import { ignoreAction } from "./actions/ignore.ts"; @@ -30,16 +30,16 @@ import { settingsProvider } from "./providers/settings.ts"; import { timeProvider } from "./providers/time.ts"; import { ChannelType, - Entity, - HandlerCallback, - IAgentRuntime, - Memory, + type Entity, + type HandlerCallback, + type IAgentRuntime, + type Memory, ModelClass, - Plugin, + type Plugin, RoleName, - RoomData, - State, - WorldData, + type RoomData, + type State, + type WorldData, } from "./types.ts"; import { createUniqueUuid } from "./entities.ts"; @@ -730,7 +730,7 @@ const syncMultipleUsers = async ( source: string ) => { if (!channelId) { - logger.warn(`Cannot sync users without a valid channelId`); + logger.warn("Cannot sync users without a valid channelId"); return; } diff --git a/packages/core/src/database.ts b/packages/core/src/database.ts index 9483cc61b8d..e0d806c628c 100644 --- a/packages/core/src/database.ts +++ b/packages/core/src/database.ts @@ -467,7 +467,7 @@ export abstract class DatabaseAdapter implements IDatabaseAdapter { * @param character The Character object to create. * @returns A Promise that resolves when the character creation is complete. */ - abstract createCharacter(character: Character): Promise; + abstract createCharacter(character: Character): Promise; /** * Retrieves all characters from the database. diff --git a/packages/core/src/entities.ts b/packages/core/src/entities.ts index 8f743adb89d..e6b12eaceb9 100644 --- a/packages/core/src/entities.ts +++ b/packages/core/src/entities.ts @@ -107,7 +107,7 @@ async function getRecentInteractions( }); for (const entity of candidateEntities) { - let interactions: Memory[] = []; + const interactions: Memory[] = []; let interactionScore = 0; // First get direct replies using inReplyTo diff --git a/packages/core/src/evaluators/goal.ts b/packages/core/src/evaluators/goal.ts index 6b16b7da38c..b695ed211db 100644 --- a/packages/core/src/evaluators/goal.ts +++ b/packages/core/src/evaluators/goal.ts @@ -2,7 +2,7 @@ import { composeContext } from "../context"; import { generateText } from "../generation"; import { getGoals } from "../goals"; import { parseJsonArrayFromText } from "../parsing"; -import { Evaluator, Goal, IAgentRuntime, Memory, ModelClass, State } from "../types"; +import { type Evaluator, type Goal, type IAgentRuntime, type Memory, ModelClass, type State } from "../types"; const goalsTemplate = `# TASK: Update Goal diff --git a/packages/core/src/evaluators/reflection.ts b/packages/core/src/evaluators/reflection.ts index 8c2ce6931dc..6605051bdac 100644 --- a/packages/core/src/evaluators/reflection.ts +++ b/packages/core/src/evaluators/reflection.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { composeContext } from "../context"; import { generateObject } from "../generation"; import { MemoryManager } from "../memory"; -import { Evaluator, IAgentRuntime, Memory, ModelClass, UUID } from "../types"; +import { type Evaluator, type IAgentRuntime, type Memory, ModelClass, type UUID } from "../types"; import { getActorDetails, resolveActorId } from "../messages"; // Schema definitions for the reflection output @@ -89,8 +89,6 @@ async function handler(runtime: IAgentRuntime, message: Memory) { agentId }); - console.log("*** existingRelationships", existingRelationships) - // Get actors in the room for name resolution const actors = await getActorDetails({ runtime, roomId }); @@ -104,7 +102,9 @@ async function handler(runtime: IAgentRuntime, message: Memory) { const knownFacts = await factsManager.getMemories({ roomId, - agentId + agentId, + count: 30, + unique: true, }); const context = composeContext({ @@ -113,11 +113,11 @@ async function handler(runtime: IAgentRuntime, message: Memory) { knownFacts: formatFacts(knownFacts), roomType: state.roomType || "group", // Can be "group", "voice", or "dm" entitiesInRoom: JSON.stringify(entitiesInRoom), - existingRelationships: JSON.stringify(existingRelationships) + existingRelationships: JSON.stringify(existingRelationships), + senderId: message.userId }, template: runtime.character.templates?.reflectionTemplate || reflectionTemplate, }); - console.log("*** reflection context\n", context) const reflection = await generateObject({ runtime, @@ -149,7 +149,6 @@ async function handler(runtime: IAgentRuntime, message: Memory) { // Update or create relationships for (const relationship of reflection.relationships) { - console.log("*** resolving relationship", relationship); let sourceId: UUID; let targetId: UUID; @@ -158,6 +157,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { targetId = resolveActorId(relationship.targetEntityId, actors); } catch (error) { console.warn('Failed to resolve relationship entities:', error); + console.warn('relationship:\n', relationship) continue; // Skip this relationship if we can't resolve the IDs } @@ -167,14 +167,11 @@ async function handler(runtime: IAgentRuntime, message: Memory) { ); if (existingRelationship) { - console.log('**** existingRelationship is true') - // Update existing relationship by creating a new one const updatedMetadata = { ...existingRelationship.metadata, interactions: (existingRelationship.metadata?.interactions || 0) + 1 }; - // Merge tags, removing duplicates const updatedTags = Array.from(new Set([ ...(existingRelationship.tags || []), ...relationship.tags @@ -189,8 +186,6 @@ async function handler(runtime: IAgentRuntime, message: Memory) { metadata: updatedMetadata, }); } else { - console.log('**** existingRelationship is false') - // Create new relationship await runtime.databaseAdapter.createRelationship({ sourceEntityId: sourceId, targetEntityId: targetId, diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index fba30ca20aa..ddc49016da6 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -396,7 +396,6 @@ export async function generateMessageResponse({ modelClass: ModelClass; stopSequences?: string[]; }): Promise { - console.log('*** generateMessageResponse', context) return await withRetry(async () => { const text = await runtime.useModel(modelClass, { runtime, diff --git a/packages/core/src/messages.ts b/packages/core/src/messages.ts index 621d42f6ded..b676bcc8ec0 100644 --- a/packages/core/src/messages.ts +++ b/packages/core/src/messages.ts @@ -1,4 +1,4 @@ -import { type Actor, type Content, type IAgentRuntime, type Memory, type UUID } from "./types.ts"; +import type { Actor, Content, IAgentRuntime, Memory, UUID } from "./types.ts"; export * as actions from "./actions"; export * as evaluators from "./evaluators"; export * as providers from "./providers"; @@ -33,10 +33,6 @@ export async function getActorDetails({ return acc; }, {}); - console.log('*** entity', entity) - console.log('*** mergedData', mergedData) - console.log('*** entity.metadata', entity.metadata) - return { id: entity.id, name: entity.metadata[room.source]?.name || entity.names[0], @@ -64,15 +60,11 @@ export async function getActorDetails({ * @returns string */ export function formatActors({ actors }: { actors: Actor[] }) { - console.log('*** actors', actors) const actorStrings = actors.map((actor: Actor) => { - console.log('*** actor', actor) - const header = `${actor.name} (${actor.names.join(" aka ")})` + `\nID: ${actor.id}` + ((actor.data && Object.entries(actor.data).length > 0) ? `\nData: ${actor.data}` : "\n"); - console.log('*** header', header) + const header = `${actor.name} (${actor.names.join(" aka ")})\nID: ${actor.id}${(actor.data && Object.keys(actor.data).length > 0) ? `\nData: ${JSON.stringify(actor.data)}\n` : "\n"}`; return header; }); const finalActorStrings = actorStrings.join("\n"); - console.log('*** finalActorStrings', finalActorStrings) return finalActorStrings; } @@ -132,12 +124,21 @@ export const formatMessages = ({ .join(", ")})` : ""; + const messageTime = new Date(message.createdAt); + const hours = messageTime.getHours().toString().padStart(2, '0'); + const minutes = messageTime.getMinutes().toString().padStart(2, '0'); + const timeString = `${hours}:${minutes}`; + const timestamp = formatTimestamp(message.createdAt); const shortId = message.userId.slice(-5); - return `(${timestamp}) [${shortId}] ${formattedName}: ${messageContent}${attachmentString}${ - messageAction && messageAction !== "null" ? ` (${messageAction})` : "" + if(messageAction === "REFLECTION") { + return `${timeString} (${timestamp}) [${shortId}] ${formattedName} (internal monologue) *${messageContent}*`; + } + + return `${timeString} (${timestamp}) [${shortId}] ${formattedName}: ${messageContent}${attachmentString}${ + messageAction && messageAction !== "null" ? ` (action: ${messageAction})` : "" }`; }) .join("\n"); diff --git a/packages/core/src/parsing.ts b/packages/core/src/parsing.ts index 17561e29b9e..4c1cb1ab484 100644 --- a/packages/core/src/parsing.ts +++ b/packages/core/src/parsing.ts @@ -5,10 +5,10 @@ const jsonBlockPattern = /```json\n([\s\S]*?)\n```/; export const messageCompletionFooter = `\nResponse format should be formatted in a valid JSON block like this: \`\`\`json -{ "user": "{{agentName}}", "text": "", "action": "" } +{ "thought": "", "user": "{{agentName}}", "text": "", "action": "" } \`\`\` -The "action" field should be one of the options in [Available Actions] and the "text" field should be the response you want to send. +The "action" field should be one of the options in [Available Actions] and the "text" field should be the response you want to send. Do not including any thinking or internal reflection in the "text" field. "thought" should be a short description of what the agent is thinking about before responding, inlcuding a brief justification for the response. `; export const shouldRespondFooter = "The available options are RESPOND, IGNORE, or STOP. Choose the most appropriate option."; @@ -336,7 +336,7 @@ export function truncateToCompleteSentence( // Assuming ~4 tokens per character on average const TOKENS_PER_CHAR = 4; const TARGET_TOKENS = 3000; -const TARGET_CHARS = Math.floor(TARGET_TOKENS / TOKENS_PER_CHAR); // ~750 chars +const _TARGET_CHARS = Math.floor(TARGET_TOKENS / TOKENS_PER_CHAR); // ~750 chars export async function splitChunks( content: string, diff --git a/packages/core/src/providers/facts.ts b/packages/core/src/providers/facts.ts index 828cc3ee15c..19e8b189214 100644 --- a/packages/core/src/providers/facts.ts +++ b/packages/core/src/providers/facts.ts @@ -1,7 +1,7 @@ import { MemoryManager } from "../memory.ts"; import { formatMessages } from "../messages.ts"; -import { IAgentRuntime, Memory, ModelClass, Provider, State } from "../types.ts"; +import { type IAgentRuntime, type Memory, ModelClass, type Provider, type State } from "../types.ts"; function formatFacts(facts: Memory[]) { return facts diff --git a/packages/core/src/providers/options.ts b/packages/core/src/providers/options.ts index 5f6295f3afe..fc267391aa4 100644 --- a/packages/core/src/providers/options.ts +++ b/packages/core/src/providers/options.ts @@ -1,5 +1,5 @@ import { logger } from "../logger"; -import { IAgentRuntime, Memory, Provider, State } from "../types"; +import type { IAgentRuntime, Memory, Provider, State } from "../types"; // Define an interface for option objects interface OptionObject { diff --git a/packages/core/src/providers/relationships.ts b/packages/core/src/providers/relationships.ts index 8d0818d4dd6..278b4b5f8bd 100644 --- a/packages/core/src/providers/relationships.ts +++ b/packages/core/src/providers/relationships.ts @@ -1,4 +1,4 @@ -import { IAgentRuntime, Memory, Provider, State, Relationship, UUID } from "../types.ts"; +import type { IAgentRuntime, Memory, Provider, State, Relationship, UUID } from "../types.ts"; async function getRelationships({ runtime, @@ -24,7 +24,7 @@ async function formatRelationships(runtime: IAgentRuntime, relationships: Relati } const formattedRelationships = await Promise.all(sortedRelationships - .map(async (rel, index) => { + .map(async (rel, _index) => { const formatMetadata = (metadata: any) => { return JSON.stringify(Object.entries(metadata) diff --git a/packages/core/src/providers/roles.ts b/packages/core/src/providers/roles.ts index 30d40de4ec1..c3c240d90a5 100644 --- a/packages/core/src/providers/roles.ts +++ b/packages/core/src/providers/roles.ts @@ -1,6 +1,6 @@ import { createUniqueUuid } from "../entities"; import { logger } from "../logger"; -import { ChannelType, IAgentRuntime, Memory, Provider, State, UUID } from "../types"; +import { ChannelType, type IAgentRuntime, type Memory, type Provider, type State, type UUID } from "../types"; export const roleProvider: Provider = { get: async ( @@ -46,9 +46,9 @@ export const roleProvider: Provider = { logger.info(`Found ${Object.keys(roles).length} roles`); // Group users by role - const owners: { name: string, username: string }[] = []; - const admins: { name: string, username: string }[] = []; - const members: { name: string, username: string }[] = []; + const owners: { name: string, username: string, names: string[] }[] = []; + const admins: { name: string, username: string, names: string[] }[] = []; + const members: { name: string, username: string, names: string[] }[] = []; // Process roles for (const userId in roles) { @@ -59,6 +59,7 @@ export const roleProvider: Provider = { const name = user.metadata[room.source]?.name; const username = user.metadata[room.source]?.username; + const names = user.names; // Skip duplicates (we store both UUID and original ID) if (owners.some(owner => owner.username === username) || admins.some(admin => admin.username === username) || members.some(member => member.username === username)) { @@ -68,13 +69,13 @@ export const roleProvider: Provider = { // Add to appropriate group switch (userRole) { case "OWNER": - owners.push({ name, username }); + owners.push({ name, username, names }); break; case "ADMIN": - admins.push({ name, username }); + admins.push({ name, username, names }); break; default: - members.push({ name, username }); + members.push({ name, username, names }); break; } } @@ -85,7 +86,7 @@ export const roleProvider: Provider = { if (owners.length > 0) { response += "## Owners\n"; owners.forEach(owner => { - response += `${owner.name} (${owner.username})\n`; + response += `${owner.name} (${owner.names.join(", ")})\n`; }); response += "\n"; } @@ -93,7 +94,7 @@ export const roleProvider: Provider = { if (admins.length > 0) { response += "## Administrators\n"; admins.forEach(admin => { - response += `${admin.name} (${admin.username})\n`; + response += `${admin.name} (${admin.names.join(", ")}) (${admin.username})\n`; }); response += "\n"; } @@ -101,7 +102,7 @@ export const roleProvider: Provider = { if (members.length > 0) { response += "## Members\n"; members.forEach(member => { - response += `${member.name} (${member.username})\n`; + response += `${member.name} (${member.names.join(", ")}) (${member.username})\n`; }); } diff --git a/packages/core/src/providers/settings.ts b/packages/core/src/providers/settings.ts index 8e71419aaa9..af45596379c 100644 --- a/packages/core/src/providers/settings.ts +++ b/packages/core/src/providers/settings.ts @@ -6,10 +6,10 @@ import { findWorldForOwner } from "../roles"; import { getWorldSettings } from "../settings"; import { ChannelType, - IAgentRuntime, - Memory, - Provider, - State, + type IAgentRuntime, + type Memory, + type Provider, + type State, type OnboardingSetting, type WorldSettings } from "../types"; @@ -30,7 +30,7 @@ const formatSettingValue = ( * Generates a status message based on the current settings state */ function generateStatusMessage( - runtime: IAgentRuntime, + _runtime: IAgentRuntime, worldSettings: WorldSettings, isOnboarding: boolean, state?: State @@ -38,7 +38,7 @@ function generateStatusMessage( try { // Format settings for display const formattedSettings = Object.entries(worldSettings) - .map(([key, setting]) => { + .map(([_key, setting]) => { if (typeof setting !== "object" || !setting.name) return null; const description = setting.description || ""; @@ -69,32 +69,24 @@ function generateStatusMessage( if (isOnboarding) { if (requiredUnconfigured > 0) { return ( - `# PRIORITY TASK: Onboarding with ${state.senderName}\n${state.agentName} still needs to configure ${requiredUnconfigured} required settings:\n\n` + - formattedSettings + `# PRIORITY TASK: Onboarding with ${state.senderName}\n${state.agentName} still needs to configure ${requiredUnconfigured} required settings:\n\n${formattedSettings .filter((s) => s.required && !s.configured) .map((s) => `${s.name}: ${s.usageDescription}\nValue: ${s.value}`) - .join("\n\n") + - "\n\n" + - `If the user gives any information related to the settings, ${state.agentName} should use the UPDATE_SETTINGS action to update the settings with this new information. ${state.agentName} can update any, some or all settings.` + .join("\n\n")}\n\nIf the user gives any information related to the settings, ${state.agentName} should use the UPDATE_SETTINGS action to update the settings with this new information. ${state.agentName} can update any, some or all settings.` ); - } else { + } return ( - "All required settings have been configured! Here's the current configuration:\n\n" + - formattedSettings.map((s) => `${s.name}: ${s.description}\nValue: ${s.value}`).join("\n") + `All required settings have been configured! Here's the current configuration:\n\n${formattedSettings.map((s) => `${s.name}: ${s.description}\nValue: ${s.value}`).join("\n")}` ); - } - } else { + } // Non-onboarding context - list all public settings with values and descriptions return ( - "## Current Configuration\n\n" + - (requiredUnconfigured > 0 + `## Current Configuration\n\n${requiredUnconfigured > 0 ? `**Note:** ${requiredUnconfigured} required settings still need configuration.\n\n` - : "All required settings are configured.\n\n") + - formattedSettings + : "All required settings are configured.\n\n"}${formattedSettings .map((s) => `### ${s.name}\n**Value:** ${s.value}\n**Description:** ${s.description}`) - .join("\n\n") + .join("\n\n")}` ); - } } catch (error) { logger.error(`Error generating status message: ${error}`); return "Error generating configuration status."; diff --git a/packages/core/src/providers/time.ts b/packages/core/src/providers/time.ts index c5c518990e8..61c5f9c1764 100644 --- a/packages/core/src/providers/time.ts +++ b/packages/core/src/providers/time.ts @@ -1,4 +1,4 @@ -import { IAgentRuntime, Memory, Provider, State } from "../types"; +import type { IAgentRuntime, Memory, Provider, State } from "../types"; const timeProvider: Provider = { get: async (_runtime: IAgentRuntime, _message: Memory, _state?: State) => { diff --git a/packages/core/src/roles.ts b/packages/core/src/roles.ts index 1782f74192f..b7f85477a7c 100644 --- a/packages/core/src/roles.ts +++ b/packages/core/src/roles.ts @@ -2,7 +2,7 @@ // Updated to use world metadata instead of cache import { logger } from "./logger"; -import { IAgentRuntime, WorldData } from "./types"; +import type { IAgentRuntime, WorldData } from "./types"; export interface ServerOwnershipState { servers: { diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 2dba60d7ba1..e81cf57bdae 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -63,7 +63,7 @@ function formatKnowledge(knowledge: KnowledgeItem[]): string { class KnowledgeManager { private runtime: AgentRuntime; - constructor(runtime: AgentRuntime, knowledgeRoot: string) { + constructor(runtime: AgentRuntime, _knowledgeRoot: string) { this.runtime = runtime; } @@ -1235,10 +1235,9 @@ export class AgentRuntime implements IAgentRuntime { return example .map((message) => { let messageString = - `${message.user}: ${message.content.text}` + - (message.content.action + `${message.user}: ${message.content.text}${message.content.action ? ` (action: ${message.content.action})` - : ""); + : ""}`; exampleNames.forEach((name, index) => { const placeholder = `{{user${index + 1}}}`; messageString = messageString.replaceAll(placeholder, name); diff --git a/packages/core/src/settings.ts b/packages/core/src/settings.ts index 8ae76282a06..2f37de00e8b 100644 --- a/packages/core/src/settings.ts +++ b/packages/core/src/settings.ts @@ -1,6 +1,6 @@ import { createUniqueUuid } from "./entities"; import { logger } from "./logger"; -import { IAgentRuntime, OnboardingConfig, OnboardingSetting, WorldData, WorldSettings } from "./types"; +import type { IAgentRuntime, OnboardingConfig, OnboardingSetting, WorldData, WorldSettings } from "./types"; function createSettingFromConfig( configSetting: Omit diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f1f2fc82e8b..4b1cc8efd5d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -991,7 +991,7 @@ export interface IDatabaseAdapter { tags?: string[]; }): Promise; - createCharacter(character: Character): Promise; + createCharacter(character: Character): Promise; listCharacters(): Promise; @@ -1085,7 +1085,7 @@ export abstract class Service { public static getInstance(): T { if (!Service.instance) { - Service.instance = new (this as any)(); + Service.instance = new (Service as any)(); } return Service.instance as T; } diff --git a/packages/plugin-discord/src/actions/voiceJoin.ts b/packages/plugin-discord/src/actions/voiceJoin.ts index 2f001d41159..ec84ba56d4d 100644 --- a/packages/plugin-discord/src/actions/voiceJoin.ts +++ b/packages/plugin-discord/src/actions/voiceJoin.ts @@ -9,19 +9,19 @@ import { composeContext, createUniqueUuid, generateText, - HandlerCallback, + type HandlerCallback, logger, ModelClass } from "@elizaos/core"; import { type Channel, type Guild, - BaseGuildVoiceChannel, + type BaseGuildVoiceChannel, ChannelType as DiscordChannelType } from "discord.js"; -import { DiscordClient } from "../index.ts"; -import { VoiceManager } from "../voice.ts"; +import type { DiscordClient } from "../index.ts"; +import type { VoiceManager } from "../voice.ts"; export default { name: "JOIN_VOICE", diff --git a/packages/plugin-discord/src/actions/voiceLeave.ts b/packages/plugin-discord/src/actions/voiceLeave.ts index a21cb7bb4f9..7764e704e20 100644 --- a/packages/plugin-discord/src/actions/voiceLeave.ts +++ b/packages/plugin-discord/src/actions/voiceLeave.ts @@ -13,8 +13,8 @@ import { BaseGuildVoiceChannel } from "discord.js"; -import { DiscordClient } from "../index.ts"; -import { VoiceManager } from "../voice.ts"; +import type { DiscordClient } from "../index.ts"; +import type { VoiceManager } from "../voice.ts"; export default { diff --git a/packages/plugin-discord/src/index.ts b/packages/plugin-discord/src/index.ts index d2640f2f59e..83cd094f076 100644 --- a/packages/plugin-discord/src/index.ts +++ b/packages/plugin-discord/src/index.ts @@ -9,8 +9,8 @@ import { type Memory, type Plugin, RoleName, - UUID, - WorldData + type UUID, + type WorldData } from "@elizaos/core"; import { Client, @@ -18,7 +18,7 @@ import { Events, GatewayIntentBits, type Guild, - GuildMember, + type GuildMember, type MessageReaction, type OAuth2Guild, Partials, @@ -534,7 +534,7 @@ export class DiscordClient extends EventEmitter implements IDiscordClient { */ private async buildStandardizedRooms( guild: Guild, - worldId: UUID + _worldId: UUID ): Promise { const rooms = []; diff --git a/packages/plugin-discord/src/messages.ts b/packages/plugin-discord/src/messages.ts index 14bfe9c1a6e..ba1cfea1b74 100644 --- a/packages/plugin-discord/src/messages.ts +++ b/packages/plugin-discord/src/messages.ts @@ -316,7 +316,7 @@ export class MessageManager { const discriminator = data.discriminator; return ( (data as { username: string }).username + - (discriminator ? "#" + discriminator : "") + (discriminator ? `#${discriminator}` : "") ); } } diff --git a/packages/plugin-discord/src/providers/voiceState.ts b/packages/plugin-discord/src/providers/voiceState.ts index 8355f86bd0a..545bab86e5a 100644 --- a/packages/plugin-discord/src/providers/voiceState.ts +++ b/packages/plugin-discord/src/providers/voiceState.ts @@ -35,9 +35,9 @@ const voiceStateProvider: Provider = { throw new Error("No world found"); } - const worldName = world.name; + const _worldName = world.name; - const roomType = room.type; + const _roomType = room.type; const channelId = room.channelId diff --git a/packages/plugin-sql/src/base.ts b/packages/plugin-sql/src/base.ts index ebc302a3bb2..30de73e408b 100644 --- a/packages/plugin-sql/src/base.ts +++ b/packages/plugin-sql/src/base.ts @@ -1,7 +1,7 @@ import { Actor, - Agent, - Component, + type Agent, + type Component, DatabaseAdapter, logger, type Character, @@ -43,11 +43,11 @@ import { import { v4 } from "uuid"; import { characterToInsert, - StoredTemplate, + type StoredTemplate, storedToTemplate, templateToStored, } from "./schema/character"; -import { DIMENSION_MAP, EmbeddingDimensionColumn } from "./schema/embedding"; +import { DIMENSION_MAP, type EmbeddingDimensionColumn } from "./schema/embedding"; import { cacheTable, characterTable, @@ -63,7 +63,7 @@ import { worldTable, componentTable, } from "./schema/index"; -import { DrizzleOperations } from "./types"; +import type { DrizzleOperations } from "./types"; export abstract class BaseDrizzleAdapter extends DatabaseAdapter @@ -1350,7 +1350,6 @@ export abstract class BaseDrizzleAdapter metadata?: { [key: string]: any }; }): Promise { return this.withDatabase(async () => { - console.log('**** creating relationship', params) console.trace() try { const id = v4(); @@ -1374,7 +1373,6 @@ export abstract class BaseDrizzleAdapter } async updateRelationship(relationship: Relationship): Promise { - console.log('**** updating relationship', relationship) return this.withDatabase(async () => { try { await this.db.update(relationshipTable) @@ -1441,7 +1439,6 @@ export abstract class BaseDrizzleAdapter tags?: string[]; }): Promise { return this.withDatabase(async () => { - console.log("*** Attempting to get relationships for ", params.userId) try { let query = this.db .select() @@ -1460,12 +1457,6 @@ export abstract class BaseDrizzleAdapter const results = await query; - console.log('****** relationship results ', results) - - if(results.length === 0) { - console.warn("Empty results") - console.trace() - } return results.map(result => ({ id: result.id, sourceEntityId: result.sourceEntityId, @@ -1577,7 +1568,7 @@ export abstract class BaseDrizzleAdapter } }); } - async createCharacter(character: Character): Promise { + async createCharacter(character: Character): Promise { return this.withDatabase(async () => { try { await this.db.transaction(async (tx) => { diff --git a/packages/plugin-sql/src/types.ts b/packages/plugin-sql/src/types.ts index acda9cfd8be..69280b0600b 100644 --- a/packages/plugin-sql/src/types.ts +++ b/packages/plugin-sql/src/types.ts @@ -18,7 +18,7 @@ export interface DrizzleOperations { update: (...args: any[]) => any; delete: (...args: any[]) => any; transaction: (cb: (tx: any) => Promise) => Promise; - execute>( + execute<_T = Record>( query: SQL ): Promise<{ rows: any[] } & Record>; } diff --git a/packages/plugin-tee/src/providers/deriveKeyProvider.ts b/packages/plugin-tee/src/providers/deriveKeyProvider.ts index d914b212811..b768ef5b299 100644 --- a/packages/plugin-tee/src/providers/deriveKeyProvider.ts +++ b/packages/plugin-tee/src/providers/deriveKeyProvider.ts @@ -209,11 +209,7 @@ const phalaDeriveKeyProvider: Provider = { * const provider = new MarlinDeriveKeyProvider(); * ``` */ -class MarlinDeriveKeyProvider extends DeriveKeyProvider { - constructor() { - super(); - } -} +class MarlinDeriveKeyProvider extends DeriveKeyProvider {} const marlinDeriveKeyProvider: Provider = { get: async (_runtime: IAgentRuntime, _message?: Memory, _state?: State) => { @@ -228,11 +224,7 @@ const marlinDeriveKeyProvider: Provider = { * const provider = new FleekDeriveKeyProvider(); * ``` */ -class FleekDeriveKeyProvider extends DeriveKeyProvider { - constructor() { - super(); - } -} +class FleekDeriveKeyProvider extends DeriveKeyProvider {} const fleekDeriveKeyProvider: Provider = { get: async (_runtime: IAgentRuntime, _message?: Memory, _state?: State) => { @@ -247,11 +239,7 @@ const fleekDeriveKeyProvider: Provider = { * const provider = new SgxGramineDeriveKeyProvider(); * ``` */ -class SgxGramineDeriveKeyProvider extends DeriveKeyProvider { - constructor() { - super(); - } -} +class SgxGramineDeriveKeyProvider extends DeriveKeyProvider {} const sgxGramineDeriveKeyProvider: Provider = { get: async (_runtime: IAgentRuntime, _message?: Memory, _state?: State) => { diff --git a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts index 5a43fe4b2d4..053282958a3 100644 --- a/packages/plugin-tee/src/providers/remoteAttestationProvider.ts +++ b/packages/plugin-tee/src/providers/remoteAttestationProvider.ts @@ -114,11 +114,7 @@ const phalaRemoteAttestationProvider: Provider = { * const provider = new MarlinRemoteAttestationProvider(); * ``` */ -class MarlinRemoteAttestationProvider extends RemoteAttestationProvider { - constructor() { - super(); - } -} +class MarlinRemoteAttestationProvider extends RemoteAttestationProvider {} const marlinRemoteAttestationProvider: Provider = { get: async (_runtime: IAgentRuntime, _message?: Memory, _state?: State) => { @@ -133,11 +129,7 @@ const marlinRemoteAttestationProvider: Provider = { * const provider = new FleekRemoteAttestationProvider(); * ``` */ -class FleekRemoteAttestationProvider extends RemoteAttestationProvider { - constructor() { - super(); - } -} +class FleekRemoteAttestationProvider extends RemoteAttestationProvider {} const fleekRemoteAttestationProvider: Provider = { get: async (_runtime: IAgentRuntime, _message?: Memory, _state?: State) => { diff --git a/packages/plugin-tee/src/vendors/index.ts b/packages/plugin-tee/src/vendors/index.ts index 9fc75832b4c..5394241fdd6 100644 --- a/packages/plugin-tee/src/vendors/index.ts +++ b/packages/plugin-tee/src/vendors/index.ts @@ -3,7 +3,7 @@ import { PhalaVendor } from './phala'; import { GramineVendor } from './gramine'; import { MarlinVendor } from './marlin'; import { FleekVendor } from './fleek'; -import { TeeVendorNames, TeeVendorName } from './types'; +import { TeeVendorNames, type TeeVendorName } from './types'; const vendors: Record = { [TeeVendorNames.PHALA]: new PhalaVendor(), diff --git a/packages/plugin-twitter/src/actions/spaceJoin.ts b/packages/plugin-twitter/src/actions/spaceJoin.ts index 9e24a0f72a9..173491b0a11 100644 --- a/packages/plugin-twitter/src/actions/spaceJoin.ts +++ b/packages/plugin-twitter/src/actions/spaceJoin.ts @@ -5,10 +5,10 @@ import { type Memory, type State, stringToUuid, - HandlerCallback, + type HandlerCallback, logger } from "@elizaos/core"; -import { Tweet } from "../client"; +import type { Tweet } from "../client"; import { SpaceActivity } from "../spaces"; export default { @@ -126,7 +126,7 @@ export default { // If the tweet author isn't hosting a Space, check if any mentioned users are currently hosting one - const agentName = client.state["TWITTER_USERNAME"]; + const agentName = client.state.TWITTER_USERNAME; for (const mention of tweet.mentions) { if (mention.username !== agentName) { const mentionJoined = await joinSpaceByUserName(mention.username); diff --git a/packages/plugin-twitter/src/base.ts b/packages/plugin-twitter/src/base.ts index fc9073bc940..82d0b9f1429 100644 --- a/packages/plugin-twitter/src/base.ts +++ b/packages/plugin-twitter/src/base.ts @@ -476,7 +476,7 @@ export class ClientBase extends EventEmitter { if (tweet.userId === this.profile.id) { continue; - } else { + } await this.runtime.ensureConnection({ userId, roomId, @@ -485,7 +485,6 @@ export class ClientBase extends EventEmitter { source: "twitter", type: ChannelType.FEED }); - } const content = { text: tweet.text, @@ -600,7 +599,7 @@ export class ClientBase extends EventEmitter { if (tweet.userId === this.profile.id) { continue; - } else { + } await this.runtime.ensureConnection({ userId, roomId, @@ -609,7 +608,6 @@ export class ClientBase extends EventEmitter { source: "twitter", type: ChannelType.FEED }); - } const content = { text: tweet.text, diff --git a/packages/plugin-twitter/src/sttTtsSpaces.ts b/packages/plugin-twitter/src/sttTtsSpaces.ts index fb98905913c..50dbb6e79f1 100644 --- a/packages/plugin-twitter/src/sttTtsSpaces.ts +++ b/packages/plugin-twitter/src/sttTtsSpaces.ts @@ -3,7 +3,7 @@ import { ChannelType, type Content, - HandlerCallback, + type HandlerCallback, type IAgentRuntime, type Memory, ModelClass, @@ -12,7 +12,7 @@ import { logger } from "@elizaos/core"; import { spawn } from "node:child_process"; -import { Readable } from "node:stream"; +import type { Readable } from "node:stream"; import type { ClientBase } from "./base"; import type { AudioDataWithUser, diff --git a/packages/plugin-twitter/src/utils.ts b/packages/plugin-twitter/src/utils.ts index 533729ba987..d0dc4ab552f 100644 --- a/packages/plugin-twitter/src/utils.ts +++ b/packages/plugin-twitter/src/utils.ts @@ -1,10 +1,10 @@ import type { Media, State } from "@elizaos/core"; -import { ChannelType, Content, IAgentRuntime, Memory, ModelClass, UUID, composeContext, createUniqueUuid, generateText, logger } from "@elizaos/core"; +import { ChannelType, type Content, type IAgentRuntime, type Memory, ModelClass, type UUID, composeContext, createUniqueUuid, generateText, logger } from "@elizaos/core"; import fs from "node:fs"; import path from "node:path"; import type { ClientBase } from "./base"; import type { Tweet } from "./client"; -import { SttTtsPlugin } from "./sttTtsSpaces"; +import type { SttTtsPlugin } from "./sttTtsSpaces"; import type { ActionResponse, MediaData } from "./types"; export const wait = (minTime = 1000, maxTime = 3000) => { @@ -617,7 +617,7 @@ Example: export async function isAgentInSpace(client: ClientBase, spaceId: string): Promise { const space = await client.twitterClient.getAudioSpaceById(spaceId); - const agentName = client.state["TWITTER_USERNAME"]; + const agentName = client.state.TWITTER_USERNAME; return space.participants.listeners.some( (participant) => participant.twitter_screen_name === agentName From 4ce78d1a6c11d2e588678505ef522f595b13d23f Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 28 Feb 2025 06:32:30 -0800 Subject: [PATCH 12/13] updating my handle now --- packages/core/src/actions/options.ts | 10 +- packages/core/src/actions/sendMessage.ts | 15 +- packages/core/src/actions/updateEntity.ts | 197 +++++++++------------ packages/core/src/bootstrap.ts | 2 + packages/core/src/entities.ts | 82 ++------- packages/core/src/evaluators/reflection.ts | 24 +-- packages/core/src/runtime.ts | 4 +- 7 files changed, 124 insertions(+), 210 deletions(-) diff --git a/packages/core/src/actions/options.ts b/packages/core/src/actions/options.ts index 03c50edfa70..bebec867bce 100644 --- a/packages/core/src/actions/options.ts +++ b/packages/core/src/actions/options.ts @@ -1,5 +1,6 @@ import { composeContext } from "../context"; import { logger } from "../logger"; +import { parseJSONObjectFromText } from "../parsing"; import { type Action, type ActionExample, @@ -33,11 +34,14 @@ Available options: 4. If no clear selection is made, return null for both fields Return in JSON format: +\`\`\`json { "taskId": number | null, "selectedOption": "OPTION_NAME" | null } -`; +\`\`\` + +Make sure to include the \`\`\`json\`\`\` tags around the JSON object.`; export const selectOptionAction: Action = { name: "SELECT_OPTION", @@ -125,10 +129,10 @@ export const selectOptionAction: Action = { const result = await runtime.useModel(ModelClass.TEXT_SMALL, { context, - stopSequences: ['}'] + stopSequences: [] }); - const parsed = JSON.parse(result); + const parsed = parseJSONObjectFromText(result); const { taskId, selectedOption } = parsed; if (taskId && selectedOption) { diff --git a/packages/core/src/actions/sendMessage.ts b/packages/core/src/actions/sendMessage.ts index 173c9d73252..1218164eb01 100644 --- a/packages/core/src/actions/sendMessage.ts +++ b/packages/core/src/actions/sendMessage.ts @@ -33,6 +33,7 @@ Analyze the conversation to identify: 3. Any identifying information about the target Return a JSON object with: +\`\`\`json { "targetType": "user|room", "source": "platform-name", @@ -41,9 +42,10 @@ Return a JSON object with: // e.g. username, roomName, etc. } } - +\`\`\` Example outputs: 1. For "send a message to @dev_guru on telegram": +\`\`\`json { "targetType": "user", "source": "telegram", @@ -51,15 +53,20 @@ Example outputs: "username": "dev_guru" } } +\`\`\` 2. For "post this in #announcements": +\`\`\`json { "targetType": "room", "source": "discord", "identifiers": { "roomName": "announcements" } -}`; +} +\`\`\` + +Make sure to include the \`\`\`json\`\`\` tags around the JSON object.`; export const sendMessageAction: Action = { name: "SEND_MESSAGE", @@ -116,7 +123,7 @@ export const sendMessageAction: Action = { const targetResult = await runtime.useModel(ModelClass.TEXT_LARGE, { context: targetContext, - stopSequences: ["}"] + stopSequences: [] }); const targetData = parseJSONObjectFromText(targetResult); @@ -133,7 +140,7 @@ export const sendMessageAction: Action = { if (targetData.targetType === "user") { // Try to find the target user entity - const targetEntity = await findEntityByName(runtime, JSON.stringify(targetData.identifiers), message, state); + const targetEntity = await findEntityByName(runtime, message, state); if (!targetEntity) { await callback({ diff --git a/packages/core/src/actions/updateEntity.ts b/packages/core/src/actions/updateEntity.ts index 05a6782de2d..6a52a387931 100644 --- a/packages/core/src/actions/updateEntity.ts +++ b/packages/core/src/actions/updateEntity.ts @@ -7,94 +7,81 @@ // sourceEntityId represents who is making the update, entityId is who they are talking about import { v4 as uuidv4 } from 'uuid'; -import { logger } from "../logger"; -import { - type Action, - type ActionExample, - Component, - type HandlerCallback, - type IAgentRuntime, - type Memory, - ModelClass, - type State, - type UUID, - Entity -} from "../types"; import { composeContext } from "../context"; import { findEntityByName } from "../entities"; +import { logger } from "../logger"; import { parseJSONObjectFromText } from "../parsing"; +import { + type Action, + type ActionExample, + type HandlerCallback, + type IAgentRuntime, + type Memory, + ModelClass, + type State, + type UUID +} from "../types"; -const sourceExtractionTemplate = `# Task: Extract Source and Entity Information +const componentTemplate = `# Task: Extract Source and Update Component Data -# Recent Messages: {{recentMessages}} +{{#if existingData}} +# Existing Component Data: +\`\`\`json +{{existingData}} +\`\`\` +{{/if}} + # Instructions: -Analyze the conversation to identify: -1. The source/platform being referenced (e.g. telegram, twitter, discord) -2. Any specific component data being shared (e.g. username, handle, display name) +1. Analyze the conversation to identify: + - The source/platform being referenced (e.g. telegram, twitter, discord) + - Any specific component data being shared + +2. Generate updated component data that: + - Is specific to the identified platform/source + - Preserves existing data when appropriate + - Includes the new information from the conversation + - Contains only valid data for this component type -Return a JSON object with: +Return a JSON object with the following structure: +\`\`\`json { "source": "platform-name", "data": { - // Relevant fields for that platform - // e.g. username, handle, displayName, etc. + // Component-specific fields + // e.g. username, username, displayName, etc. } } +\`\`\` Example outputs: 1. For "my telegram username is @dev_guru": +\`\`\`json { "source": "telegram", "data": { "username": "dev_guru" } } +\`\`\` 2. For "update my twitter handle to @tech_master": +\`\`\`json { "source": "twitter", "data": { - "handle": "tech_master" + "username": "tech_master" } -}`; - -const componentGenerationTemplate = `# Task: Update Entity Component Data - -Component Type: {{componentType}} - -{{#if existingData}} -# Existing Component Data: -\`\`\`json -{{existingData}} -\`\`\` -{{/if}} - -# New Information: -\`\`\`json -{{newData}} +} \`\`\` -# Instructions: -Generate updated component data for the {{componentType}} component. The data should: -1. Be specific to the {{componentType}} platform/source -2. Preserve existing data when appropriate -3. Merge new information with existing data -4. Return only valid data for this component type - -Return a valid JSON object with data relevant to {{componentType}}. -For example: -- telegram: username, display_name -- twitter: handle, display_name -- discord: username, display_name, discriminator - -Ensure the output is valid JSON and contains ONLY fields relevant to {{componentType}}.`; +Make sure to include the \`\`\`json\`\`\` tags around the JSON object.`; export const updateEntityAction: Action = { name: "UPDATE_ENTITY", - similes: ["CREATE_ENTITY", "EDIT_ENTITY", "UPDATE_COMPONENT", "CREATE_COMPONENT"], - description: "Creates or updates components for entities with data organized by source type (like twitter, discord, etc.)", + similes: ["CREATE_ENTITY", "UPDATE_USER", "EDIT_ENTITY", "UPDATE_COMPONENT", "CREATE_COMPONENT"], + description: "Add or edit contact details for a user entity (like twitter, discord, email address, etc.)", validate: async ( runtime: IAgentRuntime, @@ -102,20 +89,17 @@ export const updateEntityAction: Action = { _state: State ): Promise => { // Check if we have any registered sources or existing components that could be updated - const worldId = message.roomId; - const agentId = runtime.agentId; - - // Get all components for the current room to understand available sources - const roomComponents = await runtime.databaseAdapter.getComponents(message.roomId, worldId, agentId); + // const worldId = message.roomId; + // const agentId = runtime.agentId; - // Get source types from room components - const availableSources = new Set(roomComponents.map(c => c.type)); + // // Get all components for the current room to understand available sources + // const roomComponents = await runtime.databaseAdapter.getComponents(message.roomId, worldId, agentId); - // TODO: Add ability for plugins to register their sources - // const registeredSources = runtime.getRegisteredSources?.() || []; - // availableSources.add(...registeredSources); - - return availableSources.size > 0; + // // Get source types from room components + // const availableSources = new Set(roomComponents.map(c => c.type)); + + // console.log("*** updateEntityAction validate:", availableSources.size > 0) + return true; // availableSources.size > 0; }, handler: async ( @@ -126,6 +110,7 @@ export const updateEntityAction: Action = { callback: HandlerCallback, responses: Memory[] ): Promise => { + console.log('*** updateEntityAction handler') try { // Handle initial responses for (const response of responses) { @@ -139,7 +124,7 @@ export const updateEntityAction: Action = { const worldId = room.worldId; // First, find the entity being referenced - const entity = await findEntityByName(runtime, message.content.text, message, state); + const entity = await findEntityByName(runtime, message, state); if (!entity) { await callback({ @@ -150,71 +135,57 @@ export const updateEntityAction: Action = { return; } - // Extract source and component data from the message - const sourceContext = composeContext({ - state, - template: sourceExtractionTemplate, - }); + // Get existing component if it exists - we'll get this after the LLM identifies the source + let existingComponent = null; - const sourceResult = await runtime.useModel(ModelClass.TEXT_LARGE, { - context: sourceContext, - stopSequences: ["}"] + // Generate component data using the combined template + const context = composeContext({ + state, + template: componentTemplate, }); - const sourceData = parseJSONObjectFromText(sourceResult); - if (!sourceData?.source) { - await callback({ - text: "I couldn't determine what information you want to update. Could you please specify the platform (like telegram, twitter, etc.) and the information you want to update?", - action: "UPDATE_ENTITY_ERROR", - source: message.content.source, - }); - return; - } + console.log("*** updateEntityAction context", context); - const componentType = sourceData.source.toLowerCase(); - - // Get existing component if it exists - const existingComponent = await runtime.databaseAdapter.getComponent( - entity.id!, - componentType, - worldId, - sourceEntityId - ); - - // Generate updated component data - const context = composeContext({ - state: { - ...state, - componentType, - existingData: existingComponent ? JSON.stringify(existingComponent.data, null, 2) : null, - newData: JSON.stringify(sourceData.data, null, 2), - }, - template: componentGenerationTemplate, - }); - - const generatedDataText = await runtime.useModel(ModelClass.TEXT_LARGE, { + const result = await runtime.useModel(ModelClass.TEXT_LARGE, { context, - stopSequences: ["}"] + stopSequences: [] }); - + + console.log("*** updateEntityAction result", result); + // Parse the generated data - let componentData: any; + let parsedResult: any; try { - const jsonMatch = generatedDataText.match(/\{[\s\S]*\}/); + const jsonMatch = result.match(/\{[\s\S]*\}/); if (!jsonMatch) { throw new Error("No valid JSON found in the LLM response"); } - componentData = JSON.parse(jsonMatch[0]); + parsedResult = JSON.parse(jsonMatch[0]); + + if (!parsedResult.source || !parsedResult.data) { + throw new Error("Invalid response format - missing source or data"); + } } catch (error) { logger.error(`Failed to parse component data: ${error.message}`); await callback({ - text: "I couldn't properly generate the component data. Please try again with more specific information.", + text: "I couldn't properly understand the component information. Please try again with more specific information.", action: "UPDATE_ENTITY_ERROR", source: message.content.source, }); return; } + + const componentType = parsedResult.source.toLowerCase(); + const componentData = parsedResult.data; + + // Now that we know the component type, get the existing component if it exists + existingComponent = await runtime.databaseAdapter.getComponent( + entity.id!, + componentType, + worldId, + sourceEntityId + ); // Create or update the component if (existingComponent) { @@ -282,7 +253,7 @@ export const updateEntityAction: Action = { { user: "{{user1}}", content: { - text: "Set Jimmy's twitter handle to @jimmy_codes", + text: "Set Jimmy's twitter username to @jimmy_codes", }, }, { diff --git a/packages/core/src/bootstrap.ts b/packages/core/src/bootstrap.ts index 730e435ab6e..ed345ef8d7e 100644 --- a/packages/core/src/bootstrap.ts +++ b/packages/core/src/bootstrap.ts @@ -237,11 +237,13 @@ const messageReceivedHandler = async ({ runtime.character.templates?.messageHandlerTemplate || messageHandlerTemplate, }); + console.log('*** context', context) const responseContent = await generateMessageResponse({ runtime: runtime, context, modelClass: ModelClass.TEXT_LARGE, }); + console.log('*** responseContent', responseContent) // Check if this is still the latest response ID for this agent+room const currentResponseId = agentResponses.get(message.roomId); diff --git a/packages/core/src/entities.ts b/packages/core/src/entities.ts index e6b12eaceb9..5351c6644ed 100644 --- a/packages/core/src/entities.ts +++ b/packages/core/src/entities.ts @@ -12,48 +12,6 @@ import { } from "./types.ts"; const entityResolutionTemplate = `# Task: Resolve Entity Name - -# Examples: -1. Query: "me" - Result: { - "entityId": "user-123", - "type": "EXACT_MATCH", - "matches": [{ - "name": "Alice", - "reason": "Message sender referring to themselves" - }] - } - -2. Query: "you" - Result: { - "entityId": "agent-456", - "type": "EXACT_MATCH", - "matches": [{ - "name": "Assistant", - "reason": "Direct reference to the agent" - }] - } - -3. Query: "@username" - Result: { - "entityId": null, - "type": "USERNAME_MATCH", - "matches": [{ - "name": "username", - "reason": "Exact match with user's handle" - }] - } - -4. Query: "John" - Result: { - "entityId": null, - "type": "NAME_MATCH", - "matches": [{ - "name": "John Smith", - "reason": "Name matches entity's display name" - }] - } - Message Sender: {{senderName}} (ID: {{senderId}}) Agent: {{agentName}} (ID: {{agentId}}) @@ -62,26 +20,18 @@ Agent: {{agentName}} (ID: {{agentId}}) {{entitiesInRoom}} {{/if}} -# Recent Messages: {{recentMessages}} -# Recent Interactions: -{{#if recentInteractions}} -{{recentInteractions}} -{{/if}} - -# Query: -{{query}} - # Instructions: -1. Analyze the query and context to identify which entity is being referenced -2. Consider special references like "me" (message sender) or "you" (agent) +1. Analyze the context to identify which entity is being referenced +2. Consider special references like "me" (the message sender) or "you" (agent the message is directed to) 3. Look for usernames/handles in standard formats (e.g. @username, user#1234) 4. Consider context from recent messages for pronouns and references 5. If multiple matches exist, use context to disambiguate 6. Consider recent interactions and relationship strength when resolving ambiguity Return a JSON object with: +\`\`\`json { "entityId": "exact-id-if-known-otherwise-null", "type": "EXACT_MATCH | USERNAME_MATCH | NAME_MATCH | RELATIONSHIP_MATCH | AMBIGUOUS | UNKNOWN", @@ -89,7 +39,11 @@ Return a JSON object with: "name": "matched-name", "reason": "why this entity matches" }] -}`; +} +\`\`\` + +Make sure to include the \`\`\`json\`\`\` tags around the JSON object. +`; async function getRecentInteractions( runtime: IAgentRuntime, @@ -146,7 +100,6 @@ async function getRecentInteractions( export async function findEntityByName( runtime: IAgentRuntime, - query: string, message: Memory, state: State, ): Promise { @@ -210,16 +163,6 @@ export async function findEntityByName( // Get interaction strength data for relationship entities const interactionData = await getRecentInteractions(runtime, message.userId, allEntities, room.id, relationships); - // Format interaction data for LLM context - const recentInteractions = interactionData.map(data => ({ - entityName: data.entity.names[0], - interactions: data.count, - recentMessages: data.interactions.map(msg => ({ - from: msg.userId === message.userId ? "sender" : "entity", - text: msg.content.text - })) - })); - // Compose context for LLM const context = composeContext({ state: { @@ -227,19 +170,22 @@ export async function findEntityByName( roomName: room.name || room.id, worldName: world?.name || "Unknown", entitiesInRoom: JSON.stringify(filteredEntities, null, 2), - recentInteractions: JSON.stringify(recentInteractions, null, 2), userId: message.userId, - query + senderId: message.userId, }, template: entityResolutionTemplate }); + console.log("*** findEntityByName context", context) + // Use LLM to analyze and resolve the entity const result = await runtime.useModel(ModelClass.TEXT_LARGE, { context, - stopSequences: ["}"] + stopSequences: [] }); + console.log("*** findEntityByName result", result) + // Parse LLM response const resolution = parseJSONObjectFromText(result); if (!resolution) { diff --git a/packages/core/src/evaluators/reflection.ts b/packages/core/src/evaluators/reflection.ts index 6605051bdac..3b695b46f03 100644 --- a/packages/core/src/evaluators/reflection.ts +++ b/packages/core/src/evaluators/reflection.ts @@ -16,7 +16,7 @@ const relationshipSchema = z.object({ }); const reflectionSchema = z.object({ - reflection: z.string(), + // reflection: z.string(), facts: z.array(z.object({ claim: z.string(), type: z.string(), @@ -46,21 +46,18 @@ Agent Name: {{agentName}} Room Type: {{roomType}} Message Sender: {{senderName}} (ID: {{senderId}}) -# Recent Messages: {{recentMessages}} # Known Facts: {{knownFacts}} # Instructions: -1. Generate a self-reflection monologue about recent interactions -2. Extract new facts from the conversation -3. Identify and describe relationships between entities. The sourceEntityId is the UUID of the entity initiating the interaction. The targetEntityId is the UUID of the entity being interacted with. Relationships are one-direction, so a friendship would be two entity relationships where each entity is both the source and the target of the other. +1. Extract new facts from the conversation +2. Identify and describe relationships between entities. The sourceEntityId is the UUID of the entity initiating the interaction. The targetEntityId is the UUID of the entity being interacted with. Relationships are one-direction, so a friendship would be two entity relationships where each entity is both the source and the target of the other. Generate a response in the following format: \`\`\`json { - "reflection": "A thoughtful self-reflection monologue about how the interaction is going...", "facts": [ { "claim": "factual statement", @@ -199,20 +196,7 @@ async function handler(runtime: IAgentRuntime, message: Memory) { } } - // Store the reflection itself as a memory - const reflectionMemory = await runtime.messageManager.addEmbeddingToMemory({ - userId: agentId, - agentId, - content: { - text: `(Reflecting to self: ${reflection.reflection}`, - action: "REFLECTION" - }, - roomId, - createdAt: Date.now(), - }); - const memoryId = await runtime.messageManager.createMemory(reflectionMemory, true); - - await runtime.cacheManager.set(`${message.roomId}-reflection-last-processed`, memoryId); + await runtime.cacheManager.set(`${message.roomId}-reflection-last-processed`, message.id); return reflection; } diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index e81cf57bdae..5f67a631c52 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -896,13 +896,13 @@ export class AgentRuntime implements IAgentRuntime { if (!created) { logger.error( - `Failed to create user ${name} for agent ${this.agentId}.` + `Failed to create user ${names[0]} for agent ${this.agentId}.` ); return null; } logger.success( - `User ${name} created successfully for agent ${this.agentId}.` + `User ${names[0]} created successfully for agent ${this.agentId}.` ); } From 2253e0f35555c264437e73602b25437e9ec9de6f Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 28 Feb 2025 06:39:50 -0800 Subject: [PATCH 13/13] remove some continue relateed stuff --- packages/core/src/actions/ignore.ts | 2 +- packages/plugin-discord/src/messages.ts | 3 --- packages/plugin-telegram/src/messageManager.ts | 1 - packages/plugin-twitter/src/interactions.ts | 8 -------- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/core/src/actions/ignore.ts b/packages/core/src/actions/ignore.ts index cf0e027358c..3577bbc40a9 100644 --- a/packages/core/src/actions/ignore.ts +++ b/packages/core/src/actions/ignore.ts @@ -109,7 +109,7 @@ export const ignoreAction: Action = { }, { user: "{{user1}}", - content: { text: "Yeah", action: "CONTINUE" }, + content: { text: "Yeah" }, }, { user: "{{user1}}", diff --git a/packages/plugin-discord/src/messages.ts b/packages/plugin-discord/src/messages.ts index ba1cfea1b74..a2be593a801 100644 --- a/packages/plugin-discord/src/messages.ts +++ b/packages/plugin-discord/src/messages.ts @@ -161,9 +161,6 @@ export class MessageManager { const memories: Memory[] = []; for (const m of messages) { let action = content.action; - if (messages.length > 1 && m !== messages[messages.length - 1]) { - action = "CONTINUE"; - } const memory: Memory = { id: createUniqueUuid(this.runtime, m.id), diff --git a/packages/plugin-telegram/src/messageManager.ts b/packages/plugin-telegram/src/messageManager.ts index e7781ceea52..7f651a477cd 100644 --- a/packages/plugin-telegram/src/messageManager.ts +++ b/packages/plugin-telegram/src/messageManager.ts @@ -355,7 +355,6 @@ export class MessageManager { ...content, text: sentMessage.text, inReplyTo: messageId, - action: !isLastMessage ? "CONTINUE" : content.action }, createdAt: sentMessage.date * 1000 }; diff --git a/packages/plugin-twitter/src/interactions.ts b/packages/plugin-twitter/src/interactions.ts index 28793bb0256..cb84e666bb7 100644 --- a/packages/plugin-twitter/src/interactions.ts +++ b/packages/plugin-twitter/src/interactions.ts @@ -514,14 +514,6 @@ export class TwitterInteractionClient { )) as State; for (const responseMessage of responseMessages) { - if ( - responseMessage === - responseMessages[responseMessages.length - 1] - ) { - responseMessage.content.action = response.action; - } else { - responseMessage.content.action = "CONTINUE"; - } await this.runtime.messageManager.createMemory( responseMessage );