@elizaos/core 2.0.0-alpha.63 → 2.0.0-alpha.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -332,10 +332,10 @@
332
332
  "// Test hook to clear env cache in logger tests (kept internal)\nexport const __loggerTestHooks = {\n\t__noop: () => {},\n};\n\nimport adze, {\n\ttype ConsoleStyle,\n\ttype LevelConfiguration,\n\ttype Method,\n\tsetup,\n\ttype UserConfiguration,\n} from \"adze\";\nimport type Log from \"adze/dist/log.js\";\nimport { getEnv as getEnvironmentVar } from \"./utils/environment\";\n\n/**\n * Interface for Adze sealed logger with known methods\n */\ninterface AdzeLogMethods {\n\talert(...args: unknown[]): void;\n\terror(...args: unknown[]): void;\n\twarn(...args: unknown[]): void;\n\tinfo(...args: unknown[]): void;\n\tfail(...args: unknown[]): void;\n\tsuccess(...args: unknown[]): void;\n\tlog(...args: unknown[]): void;\n\tdebug(...args: unknown[]): void;\n\tverbose(...args: unknown[]): void;\n}\n\nimport fastRedact from \"fast-redact\";\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\n/**\n * Log function signature matching Pino's API for compatibility\n */\ntype LogFn = (\n\tobj: Record<string, unknown> | string | Error,\n\tmsg?: string,\n\t...args: unknown[]\n) => void;\n\n/**\n * Logger interface - elizaOS standard logger API\n */\nexport interface Logger {\n\tlevel: string;\n\ttrace: LogFn;\n\tdebug: LogFn;\n\tinfo: LogFn;\n\twarn: LogFn;\n\terror: LogFn;\n\tfatal: LogFn;\n\tsuccess: LogFn;\n\tprogress: LogFn;\n\tlog: LogFn;\n\tclear: () => void;\n\tchild: (bindings: Record<string, unknown>) => Logger;\n}\n\n/**\n * Configuration for logger creation\n */\nexport interface LoggerBindings extends Record<string, unknown> {\n\tlevel?: string;\n\tnamespace?: string;\n\tnamespaces?: string[];\n\tmaxMemoryLogs?: number;\n\t__forceType?: \"browser\" | \"node\"; // For testing - forces specific environment behavior\n}\n\n/**\n * Log entry structure for in-memory storage and streaming\n */\nexport interface LogEntry {\n\ttime: number;\n\tlevel?: number;\n\tmsg: string;\n\tagentName?: string;\n\tagentId?: string;\n\t[key: string]: string | number | boolean | null | undefined;\n}\n\n/**\n * Log listener callback type for real-time log streaming\n */\nexport type LogListener = (entry: LogEntry) => void;\n\n// Global log listeners for streaming\nconst logListeners: Set<LogListener> = new Set();\n\n/**\n * Add a listener for real-time log entries (used for WebSocket streaming)\n * @param listener - Callback function to receive log entries\n * @returns Function to remove the listener\n */\nexport function addLogListener(listener: LogListener): () => void {\n\tlogListeners.add(listener);\n\treturn () => logListeners.delete(listener);\n}\n\n/**\n * Remove a log listener\n * @param listener - The listener to remove\n */\nexport function removeLogListener(listener: LogListener): void {\n\tlogListeners.delete(listener);\n}\n\n/**\n * In-memory destination for recent logs\n */\ninterface InMemoryDestination {\n\twrite: (entry: LogEntry) => void;\n\tclear: () => void;\n\trecentLogs: () => string;\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Log level priorities for filtering\n */\nconst LOG_LEVEL_PRIORITY: Record<string, number> = {\n\ttrace: 10,\n\tverbose: 10,\n\tdebug: 20,\n\tsuccess: 27,\n\tprogress: 28,\n\tlog: 29,\n\tinfo: 30,\n\twarn: 40,\n\terror: 50,\n\tfatal: 60,\n\talert: 60,\n};\n\n/**\n * Reverse mapping from numeric level to preferred level name\n * When multiple level names have the same numeric value, we prioritize the most semantic one\n */\nconst LEVEL_TO_NAME: Record<number, string> = {\n\t10: \"trace\", // prefer 'trace' over 'verbose'\n\t20: \"debug\",\n\t27: \"success\",\n\t28: \"progress\",\n\t29: \"log\",\n\t30: \"info\",\n\t40: \"warn\",\n\t50: \"error\",\n\t60: \"fatal\", // prefer 'fatal' over 'alert'\n};\n\n/**\n * Check if a message should be logged based on current level\n */\nfunction shouldLog(messageLevel: string, currentLevel: string): boolean {\n\tconst messagePriority = LOG_LEVEL_PRIORITY[messageLevel.toLowerCase()] || 30;\n\tconst currentPriority = LOG_LEVEL_PRIORITY[currentLevel.toLowerCase()] || 30;\n\treturn messagePriority >= currentPriority;\n}\n\n/**\n * Safe JSON stringify that handles circular references\n */\nfunction safeStringify(obj: unknown): string {\n\ttry {\n\t\tconst seen = new WeakSet();\n\t\treturn JSON.stringify(obj, (_, value) => {\n\t\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\t\tif (seen.has(value)) return \"[Circular]\";\n\t\t\t\tseen.add(value);\n\t\t\t}\n\t\t\treturn value;\n\t\t});\n\t} catch {\n\t\treturn String(obj);\n\t}\n}\n\n/**\n * Parse boolean from text string\n */\nfunction parseBooleanFromText(value: string | undefined | null): boolean {\n\tif (!value) return false;\n\tconst normalized = value.toLowerCase().trim();\n\treturn (\n\t\tnormalized === \"true\" ||\n\t\tnormalized === \"1\" ||\n\t\tnormalized === \"yes\" ||\n\t\tnormalized === \"on\"\n\t);\n}\n\n/**\n * Format a value for display in pretty log extras\n */\nfunction formatExtraValue(value: unknown): string {\n\tif (value === null) return \"null\";\n\tif (value === undefined) return \"undefined\";\n\tif (typeof value === \"string\") return value;\n\tif (typeof value === \"number\" || typeof value === \"boolean\")\n\t\treturn String(value);\n\tif (value instanceof Error) return value.message;\n\treturn safeStringify(value);\n}\n\n/**\n * Format a log entry in compact pretty format\n * Format: [src] message (key=val, key=val)\n *\n * agentId/agentName are NOT displayed in pretty mode because:\n * - Loggers with namespace already show #agentName prefix (via Adze)\n * - These fields ARE still included in JSON mode for filtering/monitoring\n */\nfunction formatPrettyLog(\n\tcontext: Record<string, unknown>,\n\tmessage: string,\n\tisJsonMode: boolean,\n): string {\n\t// In JSON mode, don't format - return message as-is\n\tif (isJsonMode) {\n\t\treturn message;\n\t}\n\n\tconst src = context.src as string | undefined;\n\n\t// Build prefix: [SRC] in uppercase\n\tconst srcPart = src ? `[${src.toUpperCase()}] ` : \"\";\n\n\t// Build extras: (key=val, key=val)\n\t// Exclude: src (already in prefix), agentId/agentName (shown via Adze namespace #agent)\n\tconst excludeKeys = [\"src\", \"agentId\", \"agentName\"];\n\tconst extraPairs: string[] = [];\n\n\tfor (const [key, value] of Object.entries(context)) {\n\t\tif (excludeKeys.includes(key)) continue;\n\t\tif (value === undefined) continue;\n\t\textraPairs.push(`${key}=${formatExtraValue(value)}`);\n\t}\n\n\tconst extrasPart = extraPairs.length > 0 ? ` (${extraPairs.join(\", \")})` : \"\";\n\n\treturn `${srcPart}${message}${extrasPart}`;\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n// Log level configuration\nconst DEFAULT_LOG_LEVEL = \"info\";\nconst effectiveLogLevel = getEnvironmentVar(\"LOG_LEVEL\") || DEFAULT_LOG_LEVEL;\n\n// Custom log levels mapping (elizaOS to Adze)\n// These are for our internal shouldLog function, not Adze's levels\nexport const customLevels: Record<string, number> = {\n\tfatal: 60,\n\terror: 50,\n\twarn: 40,\n\tinfo: 30,\n\tlog: 29,\n\tprogress: 28,\n\tsuccess: 27,\n\tdebug: 20,\n\ttrace: 10,\n};\n\n// Configuration flags\nconst raw = parseBooleanFromText(getEnvironmentVar(\"LOG_JSON_FORMAT\"));\nconst showTimestamps = parseBooleanFromText(\n\tgetEnvironmentVar(\"LOG_TIMESTAMPS\") ?? \"true\",\n);\n\n// Generate a unique server ID for this process instance\nconst serverId =\n\tgetEnvironmentVar(\"SERVER_ID\") ||\n\t(typeof crypto !== \"undefined\" && crypto.randomUUID\n\t\t? crypto.randomUUID().slice(0, 8)\n\t\t: Math.random().toString(36).slice(2, 10));\n\n// Configure sensitive data redaction\n// fast-redact requires bracket notation for top-level keys or wildcard paths for nested\n// Using wildcard paths that match both top-level and nested objects\nlet redact: ReturnType<typeof fastRedact>;\ntry {\n\tredact = fastRedact({\n\t\tpaths: [\n\t\t\t// Wildcard paths for nested objects (also catches some top-level in object context)\n\t\t\t\"*.password\",\n\t\t\t\"*.passwd\",\n\t\t\t\"*.secret\",\n\t\t\t\"*.token\",\n\t\t\t\"*.apiKey\",\n\t\t\t\"*.api_key\",\n\t\t\t\"*.apiSecret\",\n\t\t\t\"*.api_secret\",\n\t\t\t\"*.authorization\",\n\t\t\t\"*.auth\",\n\t\t\t\"*.credential\",\n\t\t\t\"*.credentials\",\n\t\t\t\"*.privateKey\",\n\t\t\t\"*.private_key\",\n\t\t\t\"*.accessToken\",\n\t\t\t\"*.access_token\",\n\t\t\t\"*.refreshToken\",\n\t\t\t\"*.refresh_token\",\n\t\t\t\"*.cookie\",\n\t\t\t\"*.session\",\n\t\t\t\"*.jwt\",\n\t\t\t\"*.bearer\",\n\t\t],\n\t\tserialize: false, // Don't stringify, just redact in place\n\t\tcensor: \"[REDACTED]\",\n\t});\n} catch {\n\t// Fallback for environments where fast-redact fails (e.g., browser extensions)\n\tredact = ((obj: unknown) => obj) as ReturnType<typeof fastRedact>;\n\t(redact as { restore?: (obj: unknown) => unknown }).restore = (\n\t\tobj: unknown,\n\t) => obj;\n}\n\n// ============================================================================\n// File Log Output\n// ============================================================================\n\n/**\n * File logging — lazy-initialized on first write to avoid module-init timing issues.\n * Enable with LOG_FILE=true/1 (writes output.log, prompts.log, and chat.log in cwd) or LOG_FILE=/path/to/file.log.\n * Disabled by default.\n */\nlet _fileLogState: \"pending\" | \"active\" | \"disabled\" = \"pending\";\nlet _fileLogFd: number | null = null;\nlet _promptLogFd: number | null = null;\nlet _chatLogFd: number | null = null;\nlet _promptLogCounter = 0;\n\nlet _fs: typeof import(\"node:fs\") | null = null;\nfunction getFs(): typeof import(\"node:fs\") | null {\n\tif (_fs) return _fs;\n\ttry {\n\t\t_fs = require(\"node:fs\");\n\t\treturn _fs;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Strip ANSI escape codes from a string for plain-text logging.\n * Uses RegExp constructor to avoid control-character-in-regex lint.\n */\nfunction stripAnsi(str: string): string {\n\tconst ESC = \"\\x1b\";\n\tconst BEL = \"\\x07\";\n\tconst re = new RegExp(\n\t\t`${ESC}(?:\\\\[[\\\\x20-\\\\x3F]*[\\\\x40-\\\\x7E]|\\\\].*?(?:${BEL}|${ESC}\\\\\\\\|\\\\\\\\(B))`,\n\t\t\"g\",\n\t);\n\treturn str.replace(re, \"\");\n}\n\n/**\n * Lazily open the log files on the first write attempt.\n * Returns true if the files are ready for writing.\n */\nfunction ensureFileLog(): boolean {\n\tif (_fileLogState === \"active\") return true;\n\tif (_fileLogState === \"disabled\") return false;\n\n\t_fileLogState = \"disabled\";\n\ttry {\n\t\tif (typeof process === \"undefined\" || !process.env || !process.versions)\n\t\t\treturn false;\n\t\tif (!process.versions.node && !process.versions.bun) return false;\n\n\t\tconst logFileEnv = process.env.LOG_FILE;\n\t\tif (\n\t\t\t!logFileEnv ||\n\t\t\tlogFileEnv.trim() === \"\" ||\n\t\t\tlogFileEnv.trim() === \"0\" ||\n\t\t\tlogFileEnv.trim().toLowerCase() === \"false\"\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst fs = getFs();\n\t\tif (!fs) return false;\n\t\tconst pathMod = require(\"node:path\");\n\t\tconst isBooleanFlag = [\"true\", \"1\", \"yes\", \"on\"].includes(\n\t\t\tlogFileEnv.trim().toLowerCase(),\n\t\t);\n\t\tconst logFilePath = isBooleanFlag\n\t\t\t? pathMod.join(process.cwd(), \"output.log\")\n\t\t\t: logFileEnv.trim();\n\t\tconst logDir = pathMod.dirname(\n\t\t\tisBooleanFlag ? pathMod.join(process.cwd(), \"output.log\") : logFilePath,\n\t\t);\n\n\t\t// Ensure log directory exists\n\t\tfs.mkdirSync(logDir, { recursive: true });\n\n\t\tconst promptLogPath = pathMod.join(logDir, \"prompts.log\");\n\t\tconst chatLogPath = pathMod.join(logDir, \"chat.log\");\n\n\t\t_fileLogFd = fs.openSync(logFilePath, \"a\");\n\t\t_promptLogFd = fs.openSync(promptLogPath, \"a\");\n\t\t_chatLogFd = fs.openSync(chatLogPath, \"a\");\n\t\t_fileLogState = \"active\";\n\n\t\tprocess.on(\"exit\", () => {\n\t\t\tconst fs2 = getFs();\n\t\t\tif (fs2 && _fileLogFd !== null) {\n\t\t\t\ttry {\n\t\t\t\t\tfs2.closeSync(_fileLogFd);\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t\t_fileLogFd = null;\n\t\t\t}\n\t\t\tif (fs2 && _promptLogFd !== null) {\n\t\t\t\ttry {\n\t\t\t\t\tfs2.closeSync(_promptLogFd);\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t\t_promptLogFd = null;\n\t\t\t}\n\t\t\tif (fs2 && _chatLogFd !== null) {\n\t\t\t\ttry {\n\t\t\t\t\tfs2.closeSync(_chatLogFd);\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t\t_chatLogFd = null;\n\t\t\t}\n\t\t});\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Write a formatted log entry to the output file.\n * No-op in browser environments, when LOG_FILE is unset, or if file open failed.\n */\nfunction writeLogEntryToFile(entry: LogEntry): void {\n\tif (!ensureFileLog()) return;\n\ttry {\n\t\tconst fs = getFs();\n\t\tif (!fs) return;\n\t\tconst fd = _fileLogFd;\n\t\tif (fd === null) return;\n\t\tconst timestamp = new Date(entry.time).toISOString();\n\t\tconst levelStr = LEVEL_TO_NAME[entry.level ?? 30] || \"info\";\n\t\tconst line = `${timestamp} [${levelStr.toUpperCase().padEnd(8)}] ${stripAnsi(entry.msg)}\\n`;\n\t\tfs.writeSync(fd, line);\n\t} catch {\n\t\t// Silent fail\n\t}\n}\n\n// ============================================================================\n// Prompts.log (companion file to output.log)\n// ============================================================================\n\nfunction promptSlug(\n\tcounter: number,\n\tagentName: string,\n\tmodelType: string,\n): string {\n\treturn `#${String(counter).padStart(4, \"0\")}/${agentName}/${modelType}`;\n}\n\nconst MAX_PROMPT_LOG_CHARS = 100_000;\n\nfunction writeToPromptLog(\n\tslug: string,\n\tkind: \"PROMPT\" | \"RESPONSE\",\n\tmodelType: string,\n\tbody: string,\n\tmetadata?: Record<string, unknown>,\n): void {\n\tif (!ensureFileLog() || !_promptLogFd) return;\n\ttry {\n\t\tconst fs = getFs();\n\t\tif (!fs) return;\n\t\tconst sep = \"═\".repeat(80);\n\t\tlet header = `${sep}\\n ${slug} ${kind}: ${modelType} (${body.length} chars)\\n`;\n\t\theader += ` ${new Date().toISOString()}\\n`;\n\t\tif (metadata) {\n\t\t\theader += ` ${JSON.stringify(metadata, null, 2)}\\n`;\n\t\t}\n\t\theader += `${sep}\\n`;\n\t\tfs.writeSync(_promptLogFd, header);\n\t\tif (body.length > MAX_PROMPT_LOG_CHARS) {\n\t\t\tfs.writeSync(_promptLogFd, body.substring(0, MAX_PROMPT_LOG_CHARS));\n\t\t\tfs.writeSync(\n\t\t\t\t_promptLogFd,\n\t\t\t\t`\\n... [TRUNCATED — ${body.length - MAX_PROMPT_LOG_CHARS} more chars]\\n`,\n\t\t\t);\n\t\t} else {\n\t\t\tfs.writeSync(_promptLogFd, body);\n\t\t}\n\t\tfs.writeSync(_promptLogFd, `\\n${sep}\\n\\n`);\n\t} catch {\n\t\t// Silent fail\n\t}\n}\n\n/**\n * Log a prompt to prompts.log. Returns a slug for output.log.\n */\nexport function logPrompt(\n\tmodelType: string,\n\tprompt: string,\n\tmetadata?: {\n\t\tagentName?: string;\n\t\tagentId?: string;\n\t\trunId?: string;\n\t\tprovider?: string;\n\t\tcaller?: string;\n\t\t[key: string]: unknown;\n\t},\n): string {\n\tif (!ensureFileLog()) return \"\";\n\t// Generate next sequential counter for this prompt\n\tconst counter = ++_promptLogCounter;\n\tconst agentName = metadata?.agentName ?? \"unknown\";\n\tconst slug = promptSlug(counter, agentName, modelType);\n\t// Store slug in metadata to be reused by response\n\tmetadata = { ...metadata, promptSlug: slug };\n\twriteToPromptLog(slug, \"PROMPT\", modelType, prompt, metadata);\n\treturn slug;\n}\n\n/**\n * Log a response to prompts.log. Returns a slug for output.log.\n */\nexport function logResponse(\n\tmodelType: string,\n\tresponse: string,\n\tmetadata?: {\n\t\tagentName?: string;\n\t\tagentId?: string;\n\t\trunId?: string;\n\t\tprovider?: string;\n\t\tduration?: number;\n\t\tpromptSlug?: string;\n\t\t[key: string]: unknown;\n\t},\n): string {\n\tif (!ensureFileLog()) return \"\";\n\tconst _agentName = metadata?.agentName ?? \"unknown\";\n\t// Use the same slug that was stored in the prompt's metadata for correlation\n\tconst slug = metadata?.promptSlug;\n\tif (!slug) {\n\t\tlogger.warn(\n\t\t\t{ src: \"core:logger\" },\n\t\t\t\"logResponse missing promptSlug - responses can't be correlated\",\n\t\t);\n\t\treturn \"\";\n\t}\n\twriteToPromptLog(slug, \"RESPONSE\", modelType, response, metadata);\n\treturn slug;\n}\n\n// ============================================================================\n// Chat instrumentation (chat.log)\n// ============================================================================\n\nconst CHAT_PREVIEW_IN_MAX = 200;\nconst CHAT_PREVIEW_OUT_MAX = 120;\n\nfunction escapeChatPreview(text: string): string {\n\tconst oneLine = text.replace(/\\s+/g, \" \").trim();\n\treturn oneLine.replace(/\"/g, '\\\\\"');\n}\n\nfunction writeChatLine(line: string): void {\n\tif (!ensureFileLog() || !_chatLogFd) return;\n\ttry {\n\t\tconst fs = getFs();\n\t\tif (!fs) return;\n\t\tconst timestamp = new Date().toISOString();\n\t\tfs.writeSync(_chatLogFd, `${timestamp} ${line}\\n`);\n\t} catch {\n\t\t// Silent fail\n\t}\n}\n\n/**\n * Log an incoming message to chat.log. Call when a message is received.\n */\nexport function logChatIn(params: {\n\tagentName: string;\n\tagentId: string;\n\troomId: string;\n\tmessageId: string;\n\ttext: string;\n\tsource?: string;\n}): string {\n\tconst preview = escapeChatPreview(\n\t\tparams.text.length > CHAT_PREVIEW_IN_MAX\n\t\t\t? `${params.text.slice(0, CHAT_PREVIEW_IN_MAX)}…`\n\t\t\t: params.text,\n\t);\n\tconst roomShort = params.roomId.slice(0, 8);\n\tconst msgShort = params.messageId.slice(0, 8);\n\tconst source = params.source ?? \"unknown\";\n\tconst line = `[CHAT:IN] #${params.agentName} room=${roomShort} msg=${msgShort} source=${source} \"${preview}\"`;\n\twriteChatLine(line);\n\treturn line;\n}\n\n/**\n * Log an outgoing response to chat.log. Call when the agent sends a reply (once per logical send).\n */\nexport function logChatOut(params: {\n\tagentName: string;\n\tagentId: string;\n\troomId: string;\n\taction: string;\n\ttext?: string;\n\temoji?: string;\n\tproviders?: string[];\n\treasoning?: string;\n\tactions?: string[];\n}): string {\n\tconst roomShort = params.roomId.slice(0, 8);\n\tlet part = `[CHAT:OUT] #${params.agentName} room=${roomShort} action=${params.action}`;\n\tif (params.actions && params.actions.length > 0) {\n\t\tpart += ` actions=${params.actions.join(\",\")}`;\n\t}\n\tif (params.emoji) {\n\t\tpart += ` emoji=${params.emoji}`;\n\t}\n\tif (params.text !== undefined && params.text !== \"\") {\n\t\tconst preview = escapeChatPreview(\n\t\t\tparams.text.length > CHAT_PREVIEW_OUT_MAX\n\t\t\t\t? `${params.text.slice(0, CHAT_PREVIEW_OUT_MAX)}…`\n\t\t\t\t: params.text,\n\t\t);\n\t\tpart += ` len=${params.text.length} \"${preview}\"`;\n\t} else if (params.emoji) {\n\t\tpart += ` len=0`;\n\t}\n\tif (params.providers && params.providers.length > 0) {\n\t\tpart += ` providers=${params.providers.join(\",\")}`;\n\t}\n\tif (params.reasoning !== undefined && params.reasoning !== \"\") {\n\t\tconst safe = escapeChatPreview(\n\t\t\tparams.reasoning.length > 80\n\t\t\t\t? `${params.reasoning.slice(0, 80)}…`\n\t\t\t\t: params.reasoning,\n\t\t);\n\t\tpart += ` reasoning=\"${safe}\"`;\n\t}\n\twriteChatLine(part);\n\treturn part;\n}\n\n// ============================================================================\n// In-Memory Log Storage\n// ============================================================================\n\n/**\n * Creates an in-memory destination for storing recent logs\n */\nfunction createInMemoryDestination(maxLogs = 100): InMemoryDestination {\n\tconst logs: LogEntry[] = [];\n\n\treturn {\n\t\twrite(entry: LogEntry): void {\n\t\t\tlogs.push(entry);\n\t\t\tif (logs.length > maxLogs) {\n\t\t\t\tlogs.shift();\n\t\t\t}\n\t\t\t// Notify all listeners for real-time streaming\n\t\t\tfor (const listener of logListeners) {\n\t\t\t\tlistener(entry);\n\t\t\t}\n\t\t},\n\t\tclear(): void {\n\t\t\tlogs.length = 0;\n\t\t},\n\t\trecentLogs(): string {\n\t\t\treturn logs\n\t\t\t\t.map((entry) => {\n\t\t\t\t\tconst timestamp = showTimestamps\n\t\t\t\t\t\t? new Date(entry.time).toISOString()\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\t// Convert numeric level back to string using the reverse mapping\n\t\t\t\t\tconst levelStr = LEVEL_TO_NAME[entry.level ?? 30] || \"info\";\n\t\t\t\t\treturn `${timestamp} ${levelStr} ${entry.msg}`.trim();\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t},\n\t};\n}\n\n// Global in-memory destination\nconst globalInMemoryDestination = createInMemoryDestination();\n\n// ============================================================================\n// Adze Configuration\n// ============================================================================\n\n// Configure Adze globally\n// Map elizaOS log levels to Adze log levels\nconst getAdzeActiveLevel = () => {\n\tconst level = effectiveLogLevel.toLowerCase();\n\tif (level === \"trace\") return \"verbose\";\n\tif (level === \"debug\") return \"debug\";\n\tif (level === \"log\") return \"log\";\n\tif (level === \"info\") return \"info\";\n\tif (level === \"warn\") return \"warn\";\n\tif (level === \"error\") return \"error\";\n\tif (level === \"fatal\") return \"alert\";\n\treturn \"info\"; // Default to info\n};\n\nconst adzeActiveLevel = getAdzeActiveLevel();\n\n// Reusable custom level configuration using Adze's types\nconst customLevelConfig: Record<string, LevelConfiguration> = {\n\talert: {\n\t\tlevelName: \"alert\",\n\t\tlevel: 0,\n\t\tstyle: \"font-size: 12px; color: #ff0000;\",\n\t\tterminalStyle: [\"bgRed\", \"white\", \"bold\"] satisfies ConsoleStyle[],\n\t\tmethod: \"error\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\terror: {\n\t\tlevelName: \"error\",\n\t\tlevel: 1,\n\t\tstyle: \"font-size: 12px; color: #ff0000;\",\n\t\tterminalStyle: [\"bgRed\", \"whiteBright\", \"bold\"] satisfies ConsoleStyle[],\n\t\tmethod: \"error\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\twarn: {\n\t\tlevelName: \"warn\",\n\t\tlevel: 2,\n\t\tstyle: \"font-size: 12px; color: #ffaa00;\",\n\t\tterminalStyle: [\"bgYellow\", \"black\", \"bold\"] satisfies ConsoleStyle[],\n\t\tmethod: \"warn\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\tinfo: {\n\t\tlevelName: \"info\",\n\t\tlevel: 3,\n\t\tstyle: \"font-size: 12px; color: #0099ff;\",\n\t\tterminalStyle: [\"cyan\"] satisfies ConsoleStyle[],\n\t\tmethod: \"info\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\tfail: {\n\t\tlevelName: \"fail\",\n\t\tlevel: 4,\n\t\tstyle: \"font-size: 12px; color: #ff6600;\",\n\t\tterminalStyle: [\"red\", \"underline\"] satisfies ConsoleStyle[],\n\t\tmethod: \"error\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\tsuccess: {\n\t\tlevelName: \"success\",\n\t\tlevel: 5,\n\t\tstyle: \"font-size: 12px; color: #00cc00;\",\n\t\tterminalStyle: [\"green\"] satisfies ConsoleStyle[],\n\t\tmethod: \"log\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\tlog: {\n\t\tlevelName: \"log\",\n\t\tlevel: 6,\n\t\tstyle: \"font-size: 12px; color: #888888;\",\n\t\tterminalStyle: [\"white\"] satisfies ConsoleStyle[],\n\t\tmethod: \"log\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\tdebug: {\n\t\tlevelName: \"debug\",\n\t\tlevel: 7,\n\t\tstyle: \"font-size: 12px; color: #9b59b6;\",\n\t\tterminalStyle: [\"gray\", \"dim\"] satisfies ConsoleStyle[],\n\t\tmethod: \"debug\" satisfies Method,\n\t\temoji: \"\",\n\t},\n\tverbose: {\n\t\tlevelName: \"verbose\",\n\t\tlevel: 8,\n\t\tstyle: \"font-size: 12px; color: #666666;\",\n\t\tterminalStyle: [\"gray\", \"dim\", \"italic\"] satisfies ConsoleStyle[],\n\t\tmethod: \"debug\" satisfies Method,\n\t\temoji: \"\",\n\t},\n};\n\nconst adzeStore = setup({\n\tactiveLevel: adzeActiveLevel,\n\tformat: raw ? \"json\" : \"pretty\",\n\ttimestampFormatter: showTimestamps ? undefined : () => \"\",\n\twithEmoji: false,\n\tlevels: customLevelConfig,\n});\n\n// Mirror Adze output to in-memory storage\nadzeStore.addListener(\n\t\"*\",\n\t(log: { data?: { message?: string | unknown[]; level?: number } }) => {\n\t\ttry {\n\t\t\tconst d = log.data;\n\t\t\tconst dMessage = d?.message;\n\t\t\tconst msg = Array.isArray(dMessage)\n\t\t\t\t? dMessage\n\t\t\t\t\t\t.map((m: unknown) => (typeof m === \"string\" ? m : safeStringify(m)))\n\t\t\t\t\t\t.join(\" \")\n\t\t\t\t: typeof dMessage === \"string\"\n\t\t\t\t\t? dMessage\n\t\t\t\t\t: \"\";\n\n\t\t\tconst entry: LogEntry = {\n\t\t\t\ttime: Date.now(),\n\t\t\t\tlevel: d && typeof d.level === \"number\" ? d.level : undefined,\n\t\t\t\tmsg,\n\t\t\t};\n\t\t\tglobalInMemoryDestination.write(entry);\n\t\t} catch {\n\t\t\t// Silent fail - don't break logging\n\t\t}\n\t},\n);\n\n// ============================================================================\n// Logger Factory\n// ============================================================================\n\n/**\n * Creates a sealed Adze logger instance with namespaces and metadata\n */\nfunction sealAdze(base: Record<string, unknown>): ReturnType<typeof adze.seal> {\n\tlet chain: ReturnType<typeof adze.ns> | typeof adze = adze as\n\t\t| ReturnType<typeof adze.ns>\n\t\t| typeof adze;\n\n\t// Add namespaces if provided\n\tconst namespaces: string[] = [];\n\tif (typeof base.namespace === \"string\") namespaces.push(base.namespace);\n\tif (Array.isArray(base.namespaces))\n\t\tnamespaces.push(...(base.namespaces as string[]));\n\tif (namespaces.length > 0) {\n\t\tchain = chain.ns(...namespaces);\n\t}\n\n\t// Add metadata (excluding namespace properties)\n\tconst metaBase: Record<string, unknown> = { ...base };\n\tdelete metaBase.namespace;\n\tdelete metaBase.namespaces;\n\n\t// Add server context metadata (always, for observability)\n\t// Only add defaults if user hasn't provided them\n\tif (!metaBase.name) {\n\t\tmetaBase.name = \"elizaos\";\n\t}\n\n\t// Add pid for process identification\n\tif (!metaBase.pid && typeof process !== \"undefined\" && process.pid) {\n\t\tmetaBase.pid = process.pid;\n\t}\n\n\t// Add environment (production, development, test)\n\tif (!metaBase.environment && typeof process !== \"undefined\" && process.env) {\n\t\tmetaBase.environment = process.env.NODE_ENV || \"development\";\n\t}\n\n\t// Add serverId for instance identification\n\tif (!metaBase.serverId) {\n\t\tmetaBase.serverId = serverId;\n\t}\n\n\t// Add hostname (for JSON format or when explicitly needed)\n\tif (raw && !metaBase.hostname) {\n\t\t// Get hostname in a way that works in both Node and browser\n\t\tlet hostname = \"unknown\";\n\t\tif (typeof process !== \"undefined\" && process.platform) {\n\t\t\t// Node.js environment\n\t\t\tconst os = require(\"node:os\");\n\t\t\thostname = os.hostname();\n\t\t} else if (typeof window !== \"undefined\" && window.location) {\n\t\t\t// Browser environment\n\t\t\thostname = window.location.hostname || \"browser\";\n\t\t}\n\t\tmetaBase.hostname = hostname;\n\t}\n\n\t// This ensures the sealed logger inherits the correct log level and styling\n\tconst globalConfig: UserConfiguration = {\n\t\tactiveLevel: getAdzeActiveLevel(),\n\t\tformat: raw ? \"json\" : \"pretty\",\n\t\ttimestampFormatter: showTimestamps ? undefined : () => \"\",\n\t\twithEmoji: false,\n\t\tlevels: customLevelConfig,\n\t};\n\n\treturn chain.meta(metaBase).seal(globalConfig);\n}\n\n/**\n * Extract configuration from bindings\n */\nfunction extractBindingsConfig(bindings: LoggerBindings | boolean): {\n\tlevel: string;\n\tbase: Record<string, unknown>;\n\tmaxMemoryLogs?: number;\n} {\n\tlet level = effectiveLogLevel;\n\tlet base: Record<string, unknown> = {};\n\tlet maxMemoryLogs: number | undefined;\n\n\tif (typeof bindings === \"object\" && bindings !== null) {\n\t\tif (\"level\" in bindings) {\n\t\t\tlevel = bindings.level as string;\n\t\t}\n\t\tif (\n\t\t\t\"maxMemoryLogs\" in bindings &&\n\t\t\ttypeof bindings.maxMemoryLogs === \"number\"\n\t\t) {\n\t\t\tmaxMemoryLogs = bindings.maxMemoryLogs;\n\t\t}\n\n\t\t// Extract base bindings (excluding special properties)\n\t\tconst { level: _, maxMemoryLogs: __, ...rest } = bindings;\n\t\tbase = rest;\n\t}\n\n\treturn { level, base, maxMemoryLogs };\n}\n\n/**\n * Creates a logger instance using Adze\n * @param bindings - Logger configuration or boolean flag\n * @returns Logger instance with elizaOS API\n */\nfunction createLogger(bindings: LoggerBindings | boolean = false): Logger {\n\tconst { level, base, maxMemoryLogs } = extractBindingsConfig(bindings);\n\n\t// Reset memory buffer if custom limit requested\n\tif (typeof maxMemoryLogs === \"number\" && maxMemoryLogs > 0) {\n\t\tglobalInMemoryDestination.clear();\n\t}\n\n\t// Check if we should force browser behavior (for testing)\n\tconst forceBrowser =\n\t\ttypeof bindings === \"object\" &&\n\t\tbindings &&\n\t\t\"__forceType\" in bindings &&\n\t\tbindings.__forceType === \"browser\";\n\n\t// If forcing browser mode, create a simple console-based logger\n\tif (forceBrowser) {\n\t\tconst levelStr =\n\t\t\ttypeof level === \"number\" ? \"info\" : level || effectiveLogLevel;\n\t\tconst currentLevel = levelStr.toLowerCase();\n\n\t\tconst formatArgs = (...args: unknown[]): string => {\n\t\t\treturn args\n\t\t\t\t.map((arg) => {\n\t\t\t\t\tif (typeof arg === \"string\") return arg;\n\t\t\t\t\tif (arg instanceof Error) return arg.message;\n\t\t\t\t\treturn safeStringify(arg);\n\t\t\t\t})\n\t\t\t\t.join(\" \");\n\t\t};\n\n\t\tconst logToConsole = (method: string, ...args: unknown[]): void => {\n\t\t\tif (!shouldLog(method, currentLevel)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst message = formatArgs(...args);\n\t\t\tconst consoleMethod: keyof Console =\n\t\t\t\tmethod === \"fatal\"\n\t\t\t\t\t? \"error\"\n\t\t\t\t\t: method === \"trace\" || method === \"verbose\"\n\t\t\t\t\t\t? \"debug\"\n\t\t\t\t\t\t: method === \"success\" || method === \"progress\"\n\t\t\t\t\t\t\t? \"info\"\n\t\t\t\t\t\t\t: method === \"log\"\n\t\t\t\t\t\t\t\t? \"log\"\n\t\t\t\t\t\t\t\t: method in console &&\n\t\t\t\t\t\t\t\t\t\ttypeof console[method as keyof Console] === \"function\"\n\t\t\t\t\t\t\t\t\t? (method as keyof Console)\n\t\t\t\t\t\t\t\t\t: \"log\";\n\n\t\t\tconst consoleFn = console[consoleMethod];\n\t\t\tif (consoleFn && typeof consoleFn === \"function\") {\n\t\t\t\t// TypeScript doesn't know that consoleMethod excludes non-function properties\n\t\t\t\t// but we've already checked typeof consoleFn === 'function', so it's safe\n\t\t\t\t(consoleFn as (...args: unknown[]) => void)(message);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Safely redact sensitive data from an object (browser version)\n\t\t */\n\t\tconst safeRedact = (\n\t\t\tobj: Record<string, unknown>,\n\t\t): Record<string, unknown> => {\n\t\t\ttry {\n\t\t\t\tconst copy = { ...obj };\n\t\t\t\tredact(copy);\n\t\t\t\treturn copy;\n\t\t\t} catch {\n\t\t\t\treturn obj;\n\t\t\t}\n\t\t};\n\n\t\tconst adaptArgs = (\n\t\t\tobj: Record<string, unknown> | string | Error,\n\t\t\tmsg?: string,\n\t\t\t...args: unknown[]\n\t\t): unknown[] => {\n\t\t\tif (typeof obj === \"string\") {\n\t\t\t\treturn msg !== undefined ? [obj, msg, ...args] : [obj, ...args];\n\t\t\t}\n\t\t\tif (obj instanceof Error) {\n\t\t\t\treturn msg !== undefined\n\t\t\t\t\t? [obj.message, msg, ...args]\n\t\t\t\t\t: [obj.message, ...args];\n\t\t\t}\n\t\t\t// Redact sensitive data from objects\n\t\t\tconst redactedObj = safeRedact(obj);\n\t\t\tif (msg !== undefined) {\n\t\t\t\t// Browser is always pretty mode - format as compact single line\n\t\t\t\tconst formatted = formatPrettyLog(redactedObj, msg, false);\n\t\t\t\treturn [formatted, ...args];\n\t\t\t}\n\t\t\t// No message - format context only\n\t\t\tconst formatted = formatPrettyLog(redactedObj, \"\", false);\n\t\t\treturn formatted ? [formatted, ...args] : [...args];\n\t\t};\n\n\t\treturn {\n\t\t\tlevel: currentLevel,\n\t\t\ttrace: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"trace\", ...adaptArgs(obj, msg, ...args)),\n\t\t\tdebug: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"debug\", ...adaptArgs(obj, msg, ...args)),\n\t\t\tinfo: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"info\", ...adaptArgs(obj, msg, ...args)),\n\t\t\twarn: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"warn\", ...adaptArgs(obj, msg, ...args)),\n\t\t\terror: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"error\", ...adaptArgs(obj, msg, ...args)),\n\t\t\tfatal: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"fatal\", ...adaptArgs(obj, msg, ...args)),\n\t\t\tsuccess: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"success\", ...adaptArgs(obj, msg, ...args)),\n\t\t\tprogress: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"progress\", ...adaptArgs(obj, msg, ...args)),\n\t\t\tlog: (obj, msg, ...args) =>\n\t\t\t\tlogToConsole(\"log\", ...adaptArgs(obj, msg, ...args)),\n\t\t\tclear: () => {\n\t\t\t\tif (typeof console.clear === \"function\") console.clear();\n\t\t\t},\n\t\t\tchild: (childBindings: Record<string, unknown>) =>\n\t\t\t\tcreateLogger({\n\t\t\t\t\tlevel: currentLevel,\n\t\t\t\t\t...base,\n\t\t\t\t\t...childBindings,\n\t\t\t\t\t__forceType: \"browser\",\n\t\t\t\t}),\n\t\t};\n\t}\n\n\t// Create sealed Adze instance with configuration\n\tconst sealed = sealAdze(base);\n\tconst levelStr =\n\t\ttypeof level === \"number\" ? \"info\" : level || effectiveLogLevel;\n\tconst currentLevel = levelStr.toLowerCase();\n\n\t/**\n\t * Invoke Adze method with error capture\n\t */\n\tconst invoke = (method: string, ...args: unknown[]): void => {\n\t\t// Check if this log level should be output\n\t\tif (!shouldLog(method, currentLevel)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Capture to in-memory destination for API access (even for namespaced loggers)\n\t\tlet msg = \"\";\n\t\tif (args.length > 0) {\n\t\t\tmsg = args\n\t\t\t\t.map((arg) => {\n\t\t\t\t\tif (typeof arg === \"string\") return arg;\n\t\t\t\t\tif (arg instanceof Error) return arg.message;\n\t\t\t\t\treturn safeStringify(arg);\n\t\t\t\t})\n\t\t\t\t.join(\" \");\n\t\t}\n\n\t\t// Include namespace in the message if present\n\t\tif (base.namespace) {\n\t\t\tmsg = `#${base.namespace} ${msg}`;\n\t\t}\n\n\t\tconst entry: LogEntry = {\n\t\t\ttime: Date.now(),\n\t\t\tlevel:\n\t\t\t\tLOG_LEVEL_PRIORITY[method.toLowerCase()] || LOG_LEVEL_PRIORITY.info,\n\t\t\tmsg,\n\t\t};\n\n\t\tglobalInMemoryDestination.write(entry);\n\t\twriteLogEntryToFile(entry);\n\n\t\t// Map Eliza methods to correct Adze invocations\n\t\tlet adzeMethod = method;\n\t\tlet adzeArgs = args;\n\n\t\t// Normalize special cases - map our custom levels to Adze levels\n\t\tif (method === \"fatal\") {\n\t\t\t// Adze uses 'alert' for fatal-level logging\n\t\t\tadzeMethod = \"alert\";\n\t\t} else if (method === \"progress\") {\n\t\t\t// Map progress to info level with a prefix\n\t\t\tadzeMethod = \"info\";\n\t\t\tadzeArgs = [\"[PROGRESS]\", ...args];\n\t\t} else if (method === \"success\") {\n\t\t\t// Map success to info level with a prefix\n\t\t\tadzeMethod = \"info\";\n\t\t\tadzeArgs = [\"[SUCCESS]\", ...args];\n\t\t} else if (method === \"trace\") {\n\t\t\t// Map trace to verbose\n\t\t\tadzeMethod = \"verbose\";\n\t\t}\n\n\t\t// Invoke the sealed logger method\n\t\ttry {\n\t\t\t// The sealed logger implements AdzeLogMethods\n\t\t\tconst loggerWithMethods = sealed as Log & AdzeLogMethods;\n\t\t\tconst logMethod = loggerWithMethods[adzeMethod as keyof AdzeLogMethods];\n\t\t\tif (typeof logMethod === \"function\") {\n\t\t\t\tlogMethod.call(loggerWithMethods, ...adzeArgs);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fallback to console if Adze fails\n\t\t\tconsole.log(`[${method.toUpperCase()}]`, ...args);\n\t\t}\n\t};\n\n\t/**\n\t * Safely redact sensitive data from an object\n\t * Creates a shallow copy to avoid mutating the original\n\t */\n\tconst safeRedact = (\n\t\tobj: Record<string, unknown>,\n\t): Record<string, unknown> => {\n\t\ttry {\n\t\t\t// Create a shallow copy to avoid mutating original\n\t\t\tconst copy = { ...obj };\n\t\t\t// fast-redact returns the redacted string when serialize:false\n\t\t\t// but mutates the object in place, so we use the copy\n\t\t\tredact(copy);\n\t\t\treturn copy;\n\t\t} catch {\n\t\t\t// If redaction fails, return original (don't break logging)\n\t\t\treturn obj;\n\t\t}\n\t};\n\n\t/**\n\t * Adapt elizaOS logger API arguments to Adze format\n\t * Also applies redaction to sensitive data in objects\n\t *\n\t * In pretty mode: formats as compact single line [src] agent — message (extras)\n\t * In JSON mode: keeps structured object for machine parsing\n\t */\n\tconst adaptArgs = (\n\t\tobj: Record<string, unknown> | string | Error,\n\t\tmsg?: string,\n\t\t...args: unknown[]\n\t): unknown[] => {\n\t\t// String first argument - no context object\n\t\tif (typeof obj === \"string\") {\n\t\t\treturn msg !== undefined ? [obj, msg, ...args] : [obj, ...args];\n\t\t}\n\t\t// Error object\n\t\tif (obj instanceof Error) {\n\t\t\treturn msg !== undefined\n\t\t\t\t? [obj.message, { error: obj }, msg, ...args]\n\t\t\t\t: [obj.message, { error: obj }, ...args];\n\t\t}\n\n\t\t// Object (context) - redact sensitive data\n\t\tconst redactedObj = safeRedact(obj);\n\n\t\tif (msg !== undefined) {\n\t\t\t// Pretty mode: format as compact single line\n\t\t\tif (!raw) {\n\t\t\t\tconst formatted = formatPrettyLog(redactedObj, msg, raw);\n\t\t\t\treturn [formatted, ...args];\n\t\t\t}\n\t\t\t// JSON mode: keep structured object for machine parsing\n\t\t\treturn [msg, redactedObj, ...args];\n\t\t}\n\n\t\t// No message provided - just context object\n\t\tif (!raw) {\n\t\t\t// Pretty mode: format the object as a simple string\n\t\t\tconst formatted = formatPrettyLog(redactedObj, \"\", raw);\n\t\t\treturn formatted ? [formatted, ...args] : [...args];\n\t\t}\n\t\treturn [redactedObj, ...args];\n\t};\n\n\t// Create log methods\n\tconst trace: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"verbose\", ...adaptArgs(obj, msg, ...args));\n\tconst debug: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"debug\", ...adaptArgs(obj, msg, ...args));\n\tconst info: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"info\", ...adaptArgs(obj, msg, ...args));\n\tconst warn: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"warn\", ...adaptArgs(obj, msg, ...args));\n\tconst error: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"error\", ...adaptArgs(obj, msg, ...args));\n\tconst fatal: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"fatal\", ...adaptArgs(obj, msg, ...args));\n\tconst success: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"success\", ...adaptArgs(obj, msg, ...args));\n\tconst progress: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"progress\", ...adaptArgs(obj, msg, ...args));\n\tconst logFn: LogFn = (obj, msg, ...args) =>\n\t\tinvoke(\"log\", ...adaptArgs(obj, msg, ...args));\n\n\t/**\n\t * Clear console and memory buffer\n\t */\n\tconst clear = (): void => {\n\t\tconst consoleClear = console?.clear;\n\t\tif (typeof consoleClear === \"function\") {\n\t\t\tconsoleClear();\n\t\t}\n\t\tglobalInMemoryDestination.clear();\n\t};\n\n\t/**\n\t * Create child logger with additional bindings\n\t */\n\tconst child = (childBindings: Record<string, unknown>): Logger => {\n\t\treturn createLogger({ level: currentLevel, ...base, ...childBindings });\n\t};\n\n\treturn {\n\t\tlevel: currentLevel,\n\t\ttrace,\n\t\tdebug,\n\t\tinfo,\n\t\twarn,\n\t\terror,\n\t\tfatal,\n\t\tsuccess,\n\t\tprogress,\n\t\tlog: logFn,\n\t\tclear,\n\t\tchild,\n\t};\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\n// Create default logger instance\nconst logger = createLogger();\n\n// Backward compatibility alias\nexport const elizaLogger = logger;\n\n// Export recent logs function\nexport const recentLogs = (): string => globalInMemoryDestination.recentLogs();\n\n// Export everything\nexport { createLogger, logger };\nexport default logger;\n",
333
333
  "import type { KnowledgeSourceItem } from \"./knowledge\";\nimport type { Content } from \"./primitives\";\nimport type {\n\tJsonValue,\n\tAgent as ProtoAgent,\n\tAgentStatus as ProtoAgentStatus,\n\tCharacter as ProtoCharacter,\n\tCharacterSettings as ProtoCharacterSettingsType,\n\tMessageExample as ProtoMessageExample,\n\tMessageExampleGroup as ProtoMessageExampleGroup,\n} from \"./proto.js\";\nimport type { State } from \"./state\";\n\nexport type TemplateType =\n\t| string\n\t| ((params: {\n\t\t\tstate:\n\t\t\t\t| State\n\t\t\t\t| Record<string, string | number | boolean | null | undefined>\n\t\t\t\t| object;\n\t }) => string);\n\n/**\n * Example message for demonstration\n */\nexport interface MessageExample\n\textends Omit<ProtoMessageExample, \"$typeName\" | \"$unknown\" | \"content\"> {\n\tcontent: Content;\n}\n\nexport interface MessageExampleGroup\n\textends Omit<\n\t\tProtoMessageExampleGroup,\n\t\t\"$typeName\" | \"$unknown\" | \"examples\"\n\t> {\n\texamples: MessageExample[];\n}\n\nexport type CharacterSettings = Omit<\n\tProtoCharacterSettingsType,\n\t\"$typeName\" | \"$unknown\" | \"secrets\"\n> & {\n\tENABLE_AUTONOMY?: boolean | string;\n\tDISABLE_BASIC_CAPABILITIES?: boolean | string;\n\tENABLE_EXTENDED_CAPABILITIES?: boolean | string;\n\tADVANCED_CAPABILITIES?: boolean | string;\n\tsecrets?: Record<string, string | boolean | number>;\n\t[key: string]: JsonValue | undefined;\n};\nexport type ProtoCharacterSettings = ProtoCharacterSettingsType;\nexport type Character = Partial<\n\tOmit<\n\t\tProtoCharacter,\n\t\t| \"$typeName\"\n\t\t| \"$unknown\"\n\t\t| \"settings\"\n\t\t| \"messageExamples\"\n\t\t| \"knowledge\"\n\t\t| \"secrets\"\n\t>\n> & {\n\tsettings?: CharacterSettings;\n\tsecrets?: Record<string, string | number | boolean>;\n\tmessageExamples?: MessageExampleGroup[];\n\tknowledge?: KnowledgeSourceItem[];\n\t/** Enable advanced planning capabilities for this character */\n\tadvancedPlanning?: boolean;\n\t/** Enable advanced memory capabilities for this character */\n\tadvancedMemory?: boolean;\n};\n\nexport enum AgentStatus {\n\tACTIVE = \"active\",\n\tINACTIVE = \"inactive\",\n}\n\n/**\n * Represents an operational agent, extending the `Character` definition with runtime status and timestamps.\n * While `Character` defines the blueprint, `Agent` represents an instantiated and potentially running version.\n * It includes:\n * - `enabled`: A boolean indicating if the agent is currently active or disabled.\n * - `status`: The current operational status, typically `AgentStatus.ACTIVE` or `AgentStatus.INACTIVE`.\n * - `createdAt`, `updatedAt`: Timestamps for when the agent record was created and last updated in the database.\n * This interface is primarily used by the `IDatabaseAdapter` for agent management.\n */\nexport interface Agent\n\textends Character,\n\t\tOmit<\n\t\t\tProtoAgent,\n\t\t\t| \"$typeName\"\n\t\t\t| \"$unknown\"\n\t\t\t| \"character\"\n\t\t\t| \"status\"\n\t\t\t| \"createdAt\"\n\t\t\t| \"updatedAt\"\n\t\t\t| \"secrets\"\n\t\t> {\n\tstatus?: AgentStatus | ProtoAgentStatus;\n\tcreatedAt: number | bigint;\n\tupdatedAt: number | bigint;\n}\n",
334
334
  "import type { Agent } from \"./agent\";\nimport type {\n\tComponent,\n\tEntity,\n\tParticipant,\n\tRelationship,\n\tRoom,\n\tWorld,\n} from \"./environment\";\nimport type { Memory, MemoryMetadata } from \"./memory\";\nimport type {\n\tPairingAllowlistEntry,\n\tPairingChannel,\n\tPairingRequest,\n} from \"./pairing\";\nimport type { Metadata, UUID } from \"./primitives\";\nimport type {\n\tJsonValue,\n\tActionLogBody as ProtoActionLogBody,\n\tActionLogPrompt as ProtoActionLogPrompt,\n\tActionLogResult as ProtoActionLogResult,\n\tAgentRunCounts as ProtoAgentRunCounts,\n\tAgentRunSummary as ProtoAgentRunSummary,\n\tAgentRunSummaryResult as ProtoAgentRunSummaryResult,\n\tBaseLogBody as ProtoBaseLogBody,\n\tDbRunStatus as ProtoDbRunStatus,\n\tEmbeddingLogBody as ProtoEmbeddingLogBody,\n\tEmbeddingSearchResult as ProtoEmbeddingSearchResult,\n\tEvaluatorLogBody as ProtoEvaluatorLogBody,\n\tLog as ProtoLog,\n\tModelActionContext as ProtoModelActionContext,\n\tModelLogBody as ProtoModelLogBody,\n} from \"./proto.js\";\nimport type { Task } from \"./task\";\n\n/**\n * Allowed value types for log body fields\n */\nexport type LogBodyValue =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| undefined\n\t| UUID\n\t| Error\n\t| LogBodyValue[]\n\t| { [key: string]: LogBodyValue };\n\n/**\n * Base log body type with common properties\n */\nexport interface BaseLogBody\n\textends Omit<ProtoBaseLogBody, \"$typeName\" | \"$unknown\" | \"metadata\"> {\n\trunId?: string | UUID;\n\tparentRunId?: string | UUID;\n\tmessageId?: UUID;\n\troomId?: UUID;\n\tentityId?: UUID;\n\tsource?: string;\n\tstartTime?: number | bigint;\n\tendTime?: number | bigint;\n\tduration?: number | bigint;\n\tmetadata?: Record<string, LogBodyValue>;\n}\n\n/**\n * Action log content structure\n */\nexport interface ActionLogContent {\n\tactions?: string[];\n\ttext?: string;\n\tthought?: string;\n}\n\n/**\n * Action result structure for logging\n */\nexport interface ActionLogResult\n\textends Omit<\n\t\tProtoActionLogResult,\n\t\t\"$typeName\" | \"$unknown\" | \"data\" | \"error\"\n\t> {\n\tdata?: Record<string, LogBodyValue>;\n\terror?: string | Error;\n}\n\n/**\n * Prompt tracking for action logs\n */\nexport interface ActionLogPrompt\n\textends Omit<ProtoActionLogPrompt, \"$typeName\" | \"$unknown\" | \"timestamp\"> {\n\ttimestamp: number | bigint;\n}\n\n/**\n * Log body for action logs\n */\nexport interface ActionLogBody\n\textends Omit<\n\t\t\tProtoActionLogBody,\n\t\t\t| \"$typeName\"\n\t\t\t| \"$unknown\"\n\t\t\t| \"base\"\n\t\t\t| \"state\"\n\t\t\t| \"responses\"\n\t\t\t| \"content\"\n\t\t\t| \"result\"\n\t\t\t| \"prompts\"\n\t\t>,\n\t\tBaseLogBody {\n\taction?: string;\n\tactionName?: string;\n\tactionId?: UUID | string;\n\tmessage?: string;\n\tmessageId?: UUID;\n\tstate?: Record<string, LogBodyValue>;\n\tresponses?: Array<Record<string, LogBodyValue>>;\n\tcontent?: ActionLogContent;\n\tresult?: ActionLogResult;\n\tisVoidReturn?: boolean;\n\tprompts?: ActionLogPrompt[];\n\tpromptCount?: number;\n\tplanStep?: string;\n\tplanThought?: string;\n}\n\n/**\n * Log body for evaluator logs\n */\nexport interface EvaluatorLogBody\n\textends Omit<\n\t\t\tProtoEvaluatorLogBody,\n\t\t\t\"$typeName\" | \"$unknown\" | \"base\" | \"state\"\n\t\t>,\n\t\tBaseLogBody {\n\tmessageId?: UUID;\n\tstate?: Record<string, LogBodyValue>;\n}\n\n/**\n * Action context for model logs\n */\nexport type ModelActionContext = Omit<\n\tProtoModelActionContext,\n\t\"$typeName\" | \"$unknown\"\n>;\n\n/**\n * Log body for model logs\n */\nexport interface ModelLogBody\n\textends Omit<\n\t\t\tProtoModelLogBody,\n\t\t\t| \"$typeName\"\n\t\t\t| \"$unknown\"\n\t\t\t| \"base\"\n\t\t\t| \"params\"\n\t\t\t| \"response\"\n\t\t\t| \"actionContext\"\n\t\t\t| \"timestamp\"\n\t\t\t| \"executionTime\"\n\t\t>,\n\t\tBaseLogBody {\n\tparams?: Record<string, LogBodyValue>;\n\tactionContext?: ModelActionContext;\n\ttimestamp?: number | bigint;\n\texecutionTime?: number | bigint;\n\tresponse?: JsonValue;\n}\n\n/**\n * Log body for embedding logs\n */\nexport interface EmbeddingLogBody\n\textends Omit<\n\t\t\tProtoEmbeddingLogBody,\n\t\t\t\"$typeName\" | \"$unknown\" | \"base\" | \"duration\"\n\t\t>,\n\t\tBaseLogBody {\n\tduration?: number | bigint;\n\terror?: string | Error;\n}\n\n/**\n * Union type for all possible log body types\n */\nexport type LogBody =\n\t| BaseLogBody\n\t| ActionLogBody\n\t| EvaluatorLogBody\n\t| ModelLogBody\n\t| EmbeddingLogBody;\n\n/**\n * Represents a log entry\n */\nexport interface Log\n\textends Omit<\n\t\tProtoLog,\n\t\t\"$typeName\" | \"$unknown\" | \"body\" | \"createdAt\" | \"entityId\" | \"roomId\"\n\t> {\n\tentityId: UUID;\n\troomId?: UUID;\n\tbody: LogBody;\n\tcreatedAt: Date;\n}\n\nexport type RunStatus = \"started\" | \"completed\" | \"timeout\" | \"error\";\n\n/**\n * JSON Patch operation for atomic component data updates.\n *\n * WHY: Enables race-free partial updates to component JSONB data without\n * read-modify-write cycles. All operations are applied in a single UPDATE\n * statement using dialect-specific JSONB functions.\n *\n * OPERATIONS:\n * - set: Set a value at path (creates path if missing)\n * - push: Append value to array at path (errors if not array)\n * - remove: Delete the key/index at path (idempotent if missing)\n * - increment: Add numeric value to number at path (errors if not number)\n *\n * PATH FORMAT:\n * - Dot-separated: \"wallet.balance\" or \"positions.0.open\"\n * - Only alphanumeric, underscore, and numeric array indices allowed\n * - Validated with regex to prevent SQL injection\n */\nexport interface PatchOp {\n\t/** Operation type */\n\top: \"set\" | \"push\" | \"remove\" | \"increment\";\n\t/**\n\t * Dot-separated path to the field (e.g., \"wallet.balance\", \"items.0.name\")\n\t * Validated against /^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*|\\.\\d+)*$/\n\t */\n\tpath: string;\n\t/**\n\t * Value for the operation\n\t * - Required for: set, push, increment\n\t * - Ignored for: remove\n\t */\n\tvalue?: unknown;\n}\n\n/** Participant room state for batch get/update (getParticipantUserStates, updateParticipantUserStates). */\nexport type ParticipantUserState = \"FOLLOWED\" | \"MUTED\" | null;\n\n/** Fields that can be updated on a participant (Participant + DB-only roomState/metadata). */\nexport type ParticipantUpdateFields = Partial<Participant> & {\n\troomState?: ParticipantUserState;\n\tmetadata?: Record<string, unknown>;\n};\n\n/** Result of getEntitiesForRooms: one entry per requested roomId, same order. */\nexport type EntitiesForRoomsResult = Array<{\n\troomId: UUID;\n\tentities: Entity[];\n}>;\n\n/** Result of getParticipantsForRooms: one entry per requested roomId, same order. */\nexport type ParticipantsForRoomsResult = Array<{\n\troomId: UUID;\n\tentityIds: UUID[];\n}>;\n\n/** Result of getPairingRequests batch: one entry per (channel, agentId) query, same order. */\nexport type PairingRequestsResult = Array<{\n\tchannel: PairingChannel;\n\tagentId: UUID;\n\trequests: PairingRequest[];\n}>;\n\n/** Result of getPairingAllowlists batch: one entry per (channel, agentId) query, same order. */\nexport type PairingAllowlistsResult = Array<{\n\tchannel: PairingChannel;\n\tagentId: UUID;\n\tentries: PairingAllowlistEntry[];\n}>;\n\nexport interface AgentRunCounts\n\textends Omit<ProtoAgentRunCounts, \"$typeName\" | \"$unknown\"> {}\n\nexport interface AgentRunSummary\n\textends Omit<\n\t\tProtoAgentRunSummary,\n\t\t| \"$typeName\"\n\t\t| \"$unknown\"\n\t\t| \"status\"\n\t\t| \"startedAt\"\n\t\t| \"endedAt\"\n\t\t| \"durationMs\"\n\t\t| \"metadata\"\n\t> {\n\tstatus: RunStatus | ProtoDbRunStatus;\n\tstartedAt: number | bigint | null;\n\tendedAt: number | bigint | null;\n\tdurationMs: number | bigint | null;\n\tmetadata?: Record<string, JsonValue>;\n}\n\nexport interface AgentRunSummaryResult\n\textends Omit<ProtoAgentRunSummaryResult, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Interface for database operations.\n *\n * **Design: Batch-First CRUD**\n *\n * All create/read-by-ID/update/delete methods accept and return arrays.\n * This is intentional and non-negotiable for adapter implementations.\n *\n * WHY: elizaOS agents process events that frequently touch multiple DB rows\n * in a single tick -- load entity + room, store memory + log, clean up tasks.\n * Under the old single-item API, each was a separate round-trip. At scale\n * (multiple agents, concurrent conversations), this saturated connection pools\n * and made network latency the bottleneck. Batch methods let SQL adapters use\n * `IN (...)` clauses, multi-row inserts, and transactions -- actual DB-level\n * batching instead of application-level loops.\n *\n * Single-item convenience wrappers (e.g. `getAgent(id)`) live on `AgentRuntime`\n * and `IAgentRuntime`, NOT here. They delegate to batch methods internally.\n * This keeps the adapter contract simple: implement batch, get single-item free.\n *\n * **Query methods** (complex filter params, not ID lookups) remain singular because\n * batching `searchMemories` would mean \"run N different searches\" -- a fundamentally\n * different operation than \"look up N items by their IDs.\"\n *\n * See DATABASE_BATCH_API.md for the full design rationale and migration guide.\n */\nexport interface IDatabaseAdapter<DB extends object = object> {\n\t/** Database instance */\n\tdb: DB;\n\n\t/**\n\t * Initialize database connection\n\t *\n\t * WHY: Async initialization allows:\n\t * - Connection pooling setup\n\t * - Schema validation and migrations\n\t * - SSL/TLS handshake completion\n\t * - Adapter-specific configuration (RLS policies, extensions, etc.)\n\t *\n\t * @param config Optional adapter-specific configuration\n\t */\n\tinitialize(\n\t\tconfig?: Record<string, string | number | boolean | null>,\n\t): Promise<void>;\n\n\t/**\n\t * Run plugin schema migrations for all registered plugins\n\t * @param plugins Array of plugins with their schemas\n\t * @param options Migration options (verbose, force, dryRun, etc.)\n\t */\n\trunPluginMigrations?(\n\t\tplugins: Array<{\n\t\t\tname: string;\n\t\t\tschema?: Record<string, JsonValue | object>;\n\t\t}>,\n\t\toptions?: {\n\t\t\tverbose?: boolean;\n\t\t\tforce?: boolean;\n\t\t\tdryRun?: boolean;\n\t\t},\n\t): Promise<void>;\n\n\t/**\n\t * Run database migrations from migration files\n\t * @param migrationsPaths Optional array of migration file paths\n\t */\n\trunMigrations?(migrationsPaths?: string[]): Promise<void>;\n\n\t/** Check if the database connection is ready */\n\tisReady(): Promise<boolean>;\n\n\t/** Close database connection */\n\tclose(): Promise<void>;\n\n\tgetConnection(): Promise<DB>;\n\n\t/**\n\t * Execute a callback with full isolation context (Server RLS + Entity RLS).\n\t *\n\t * WHY: PostgreSQL Row Level Security requires setting session variables before\n\t * queries. This method sets the entity (and optionally server) context and\n\t * executes the callback within that context.\n\t *\n\t * WHY unknown context parameter: Different backends provide different context\n\t * types (Drizzle transaction for SQL, nothing for in-memory). Callers that\n\t * need the context can cast `ctx` to the appropriate type.\n\t *\n\t * @param entityId - The entity ID to set as context (null clears context)\n\t * @param callback - Function to execute within isolation context\n\t * @returns The result of the callback\n\t */\n\twithIsolationContext?<T>(\n\t\tentityId: UUID | null,\n\t\tcallback: (ctx: unknown) => Promise<T>,\n\t): Promise<T>;\n\n\t/** Get all agents */\n\tgetAgents(): Promise<Partial<Agent>[]>;\n\n\t// ── Agent CRUD (batch-only) ──────────────────────────────────────────\n\t// WHY batch-only: agent lifecycle operations (create on boot, update\n\t// settings, bulk cleanup) benefit from single-query multi-row SQL.\n\t// Single-item wrappers live on AgentRuntime.\n\tgetAgentsByIds(agentIds: UUID[]): Promise<Agent[]>;\n\tcreateAgents(agents: Partial<Agent>[]): Promise<UUID[]>;\n\tupdateAgents(\n\t\tupdates: Array<{ agentId: UUID; agent: Partial<Agent> }>,\n\t): Promise<boolean>;\n\tdeleteAgents(agentIds: UUID[]): Promise<boolean>;\n\n\t/**\n\t * Upsert agents (insert or update by ID)\n\t *\n\t * WHY: Atomic insert-or-update eliminates the get-check-create race condition\n\t * in `ensureAgentExists`. Single SQL statement is safer and faster.\n\t *\n\t * WHY on adapter interface: PostgreSQL and MySQL can perform this atomically\n\t * (ON CONFLICT / ON DUPLICATE KEY), so it belongs on the adapter. InMemory\n\t * simulates with has()/set(), which is acceptable.\n\t *\n\t * WHY void return: Upserts don't create new IDs - the caller already has them.\n\t * Returning UUID[] suggests creation, which is misleading for updates.\n\t *\n\t * IMPLEMENTATION NOTES:\n\t * - PostgreSQL: INSERT ... ON CONFLICT (id) DO UPDATE SET ...\n\t * - MySQL: INSERT ... ON DUPLICATE KEY UPDATE ...\n\t * - InMemory: map.has(id) ? map.set(id, merged) : map.set(id, agent)\n\t *\n\t * @param agents Agents to upsert (ID is required for each)\n\t */\n\tupsertAgents(agents: Partial<Agent>[]): Promise<void>;\n\n\t/**\n\t * Count total number of agents in the database\n\t *\n\t * WHY: Useful for admin dashboards, monitoring, and quota checks.\n\t * Simple count query that doesn't fetch full agent records.\n\t *\n\t * @returns Total count of agents\n\t */\n\tcountAgents(): Promise<number>;\n\n\t/**\n\t * Remove agents that haven't been active recently\n\t *\n\t * WHY: Cleanup stale agents for multi-tenant systems or dev environments\n\t * where agents are created for testing and then abandoned. Prevents\n\t * database bloat from accumulating test/demo agents.\n\t *\n\t * IMPLEMENTATION NOTE: Deletion criteria varies by adapter. SQL adapters\n\t * typically use updatedAt < 30 days ago. InMemory adapters may do nothing.\n\t */\n\tcleanupAgents(): Promise<void>;\n\n\tensureEmbeddingDimension(dimension: number): Promise<void>;\n\n\t/**\n\t * Execute a callback within a database transaction.\n\t *\n\t * WHY: Enables cross-method atomicity. Each batch method (createEntities,\n\t * upsertComponents, etc.) is already internally atomic. transaction() is for\n\t * when you need multiple methods to succeed or fail together.\n\t *\n\t * EXAMPLE: Create entity + its components atomically:\n\t * ```\n\t * await adapter.transaction(async (tx) => {\n\t * await tx.createEntities([entity]);\n\t * await tx.createComponents(components);\n\t * });\n\t * ```\n\t *\n\t * IMPLEMENTATION:\n\t * - SQL adapters: Use Drizzle's transaction() with prototype proxy pattern\n\t * - InMemory: Executes callback directly (NOT atomic - see warning below)\n\t *\n\t * TRAP - InMemory non-atomicity: The InMemory adapter does NOT provide true\n\t * transaction semantics. If step 2 fails, step 1's changes are NOT rolled back.\n\t * This is acceptable for dev/test but NOT for production critical paths.\n\t *\n\t * @param callback Function that receives a transactional adapter proxy\n\t * @param options.entityContext When set (Postgres + ENABLE_DATA_ISOLATION), runs callback under RLS for this entity.\n\t * WHY optional: System paths (migrations, boot, admin) run without a user entity; required would break them.\n\t * @returns Promise resolving to callback's return value\n\t * @throws Error if any operation in the callback fails (SQL: rolls back, InMemory: does NOT)\n\t */\n\ttransaction<T>(\n\t\tcallback: (tx: IDatabaseAdapter<DB>) => Promise<T>,\n\t\toptions?: { entityContext?: UUID },\n\t): Promise<T>;\n\n\t/** Get entities for multiple rooms (one entry per roomId, same order). */\n\tgetEntitiesForRooms(\n\t\troomIds: UUID[],\n\t\tincludeComponents?: boolean,\n\t): Promise<EntitiesForRoomsResult>;\n\n\t/** Create new entities */\n\tcreateEntities(entities: Entity[]): Promise<UUID[]>;\n\n\t/**\n\t * Upsert entities (insert or update by ID)\n\t *\n\t * WHY: Atomic insert-or-update eliminates race conditions in `ensureEntityExists`.\n\t * Entities may be created concurrently from multiple sources (client plugins,\n\t * RPC handlers, background jobs). Atomic upsert prevents duplicates.\n\t *\n\t * WHY on adapter interface: All SQL dialects support atomic upserts, so this\n\t * belongs on the adapter, not as runtime-level get-then-create orchestration.\n\t *\n\t * WHY void return: Caller already has the entity IDs. Upserts don't generate\n\t * new IDs - they're idempotent operations where the ID is the lookup key.\n\t *\n\t * IMPLEMENTATION NOTES:\n\t * - PostgreSQL: INSERT ... ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, ...\n\t * - MySQL: INSERT ... ON DUPLICATE KEY UPDATE name = VALUES(name), ...\n\t * - InMemory: entities.set(id, merged)\n\t * - Conflict resolution: Last write wins (update all fields from input)\n\t *\n\t * @param entities Entities to upsert (ID, agentId, name required)\n\t */\n\tupsertEntities(entities: Entity[]): Promise<void>;\n\n\t/**\n\t * Search entities by name substring match\n\t *\n\t * WHY: Enables autocomplete/search UIs for entity mentions (e.g., \"@user\"\n\t * in Discord, entity picker in admin dashboards). Case-insensitive substring\n\t * search across all entity names.\n\t *\n\t * WHY on adapter interface: SQL adapters use ILIKE/LOWER() with GIN indexes\n\t * on the names array. InMemory adapters iterate and filter. This search\n\t * pattern is common enough to warrant a dedicated method.\n\t *\n\t * PERFORMANCE:\n\t * - PostgreSQL: Uses GIN index on names array + ILIKE for case-insensitive\n\t * - MySQL: Uses JSON_TABLE + LIKE (slower, no ideal index)\n\t * - InMemory: O(N) scan with substring match\n\t *\n\t * @param params.query Substring to search for (case-insensitive)\n\t * @param params.agentId Scope search to this agent's entities\n\t * @param params.limit Max results (default: 10)\n\t * @returns Matching entities, ordered by relevance (exact matches first)\n\t */\n\tsearchEntitiesByName(params: {\n\t\tquery: string;\n\t\tagentId: UUID;\n\t\tlimit?: number;\n\t}): Promise<Entity[]>;\n\n\t/**\n\t * Get entities by exact name match\n\t *\n\t * WHY: Batch lookup for entities by their display names. Used when importing\n\t * data from external systems where entities are identified by name, not UUID.\n\t *\n\t * WHY batch: When syncing a channel with 50 participants, we need to resolve\n\t * all their names to entity IDs in one query, not 50 separate queries.\n\t *\n\t * IMPLEMENTATION NOTE: Matches ANY name in the entity's names array (entities\n\t * can have aliases/nicknames). Case-sensitive exact match.\n\t *\n\t * @param params.names Array of names to look up\n\t * @param params.agentId Scope to this agent's entities\n\t * @returns Entities with matching names (may return fewer than names.length)\n\t */\n\tgetEntitiesByNames(params: {\n\t\tnames: string[];\n\t\tagentId: UUID;\n\t}): Promise<Entity[]>;\n\n\t/**\n\t * Query entities by component properties (type and/or data filter)\n\t *\n\t * WHY: Eliminates multi-hop fetch patterns like \"get all user IDs → getEntitiesByIds →\n\t * filter for ACCOUNT components → extract wallet data\". Collapses 3+ queries into one\n\t * database-optimized query with JSONB containment filtering.\n\t *\n\t * This is the highest-impact API addition, replacing patterns like:\n\t * - int_accounts.ts: \"get accounts by pubkey\" (240 lines → 10 lines)\n\t * - int_users.ts: \"get users by type\" (multi-step ID resolution)\n\t * - int_spartan.ts: master registry becomes unnecessary\n\t *\n\t * TWO-QUERY APPROACH (critical for correctness):\n\t * 1. Query 1: SELECT DISTINCT entity_id FROM components WHERE ... LIMIT N\n\t * 2. Query 2: SELECT entities.*, components.* WHERE entity_id IN (...)\n\t *\n\t * WHY two queries: A single SELECT DISTINCT ... JOIN ... LIMIT can return fewer\n\t * than LIMIT entities if entities have multiple components (DISTINCT dedupes AFTER\n\t * LIMIT). Two queries ensures LIMIT applies to entity count, not row count.\n\t *\n\t * JSONB CONTAINMENT (@> operator):\n\t * - {\"wallet\": {\"chain\": \"solana\"}} @> {\"wallet\": {\"chain\": \"solana\"}} ✓\n\t * - {\"tags\": [\"admin\",\"user\"]} @> {\"tags\": [\"admin\"]} ✓ (containment, not equality)\n\t * - Exploits GIN index on components.data for O(log N) performance\n\t *\n\t * FILTER VALIDATION:\n\t * - If no componentType, no componentDataFilter, no entityIds: MUST throw unless limit set\n\t * - Prevents accidental full table scans from queryEntities({})\n\t *\n\t * @param params.componentType Filter by component type (e.g., \"ACCOUNT\", \"WALLET\")\n\t * @param params.componentDataFilter JSONB containment filter on component.data\n\t * @param params.agentId Scope query to agent's entities\n\t * @param params.entityIds Explicit list of entity IDs to filter\n\t * @param params.worldId Filter by world context\n\t * @param params.limit Max entities to return (applies to distinct entities, not rows)\n\t * @param params.offset Skip first N entities for pagination\n\t * @param params.includeAllComponents If false (default): return only matched component type.\n\t * If true: return all components for matched entities.\n\t * @returns Entities with their components (filtered by includeAllComponents)\n\t */\n\tqueryEntities(params: {\n\t\tcomponentType?: string;\n\t\tcomponentDataFilter?: Record<string, unknown>;\n\t\tagentId?: UUID;\n\t\tentityIds?: UUID[];\n\t\tworldId?: UUID;\n\t\tlimit?: number;\n\t\toffset?: number;\n\t\tincludeAllComponents?: boolean; // default false\n\t\t/** RLS only: when set (Postgres + ENABLE_DATA_ISOLATION), query runs under this entity context. Not a filter (WHY: RLS is connection-level; stores do not take entityContext). */\n\t\tentityContext?: UUID;\n\t}): Promise<Entity[]>;\n\n\t/** Get components by natural keys (entityId, type, worldId?, sourceEntityId?). Same order as keys; null where not found. */\n\tgetComponentsByNaturalKeys(\n\t\tkeys: Array<{\n\t\t\tentityId: UUID;\n\t\t\ttype: string;\n\t\t\tworldId?: UUID;\n\t\t\tsourceEntityId?: UUID;\n\t\t}>,\n\t): Promise<(Component | null)[]>;\n\n\t/** Get all components for multiple entities. Flat list (components have entityId). */\n\tgetComponentsForEntities(\n\t\tentityIds: UUID[],\n\t\tworldId?: UUID,\n\t\tsourceEntityId?: UUID,\n\t): Promise<Component[]>;\n\n\t// ── Entity CRUD (batch-only) ─────────────────────────────────────────\n\t// WHY batch-only: event handlers routinely resolve multiple entities at\n\t// once (e.g. all participants in a room). IN-clause reads and\n\t// transactional updates eliminate per-entity round-trips.\n\tgetEntitiesByIds(entityIds: UUID[]): Promise<Entity[]>;\n\tupdateEntities(entities: Entity[]): Promise<void>;\n\tdeleteEntities(entityIds: UUID[]): Promise<void>;\n\n\t// ── Component CRUD (batch-only) ────────────────────────────────────\n\t// WHY batch-only: components are often loaded/saved in groups (all\n\t// components for an entity, or all components of a type across a world).\n\t// getComponent() and getComponents() are query methods (kept singular)\n\t// because they filter by entityId+type, not by component ID.\n\tcreateComponents(components: Component[]): Promise<UUID[]>;\n\tgetComponentsByIds(componentIds: UUID[]): Promise<Component[]>;\n\tupdateComponents(components: Component[]): Promise<void>;\n\tdeleteComponents(componentIds: UUID[]): Promise<void>;\n\n\t/**\n\t * Upsert components (insert or update by natural key)\n\t *\n\t * WHY: Completes the upsert pattern established by upsertAgents, upsertEntities,\n\t * upsertWorlds, upsertRooms. Components have a composite natural key of\n\t * (entityId, type, worldId, sourceEntityId). Atomic upsert eliminates race\n\t * conditions when multiple code paths try to ensure a component exists.\n\t *\n\t * WHY natural key, not ID: The caller knows the component's semantic identity\n\t * (which entity, which type, which world context) but may not know if a\n\t * component with those properties already exists. The database enforces\n\t * uniqueness via the unique_component_natural_key constraint.\n\t *\n\t * CONFLICT RESOLUTION:\n\t * - On conflict: UPDATE data, agentId, roomId (mutable state)\n\t * - Do NOT update: id, entityId, type, worldId, sourceEntityId (identity)\n\t * - Do NOT update: createdAt (preserve original timestamp)\n\t *\n\t * IMPLEMENTATION NOTES:\n\t * - PostgreSQL: INSERT ... ON CONFLICT (entity_id, type, world_id, source_entity_id)\n\t * DO UPDATE SET data = EXCLUDED.data, ...\n\t * Requires unique_component_natural_key constraint with NULLS NOT DISTINCT\n\t * - MySQL: INSERT ... ON DUPLICATE KEY UPDATE data = VALUES(data), ...\n\t * Requires UNIQUE KEY on (entity_id, type, world_id, source_entity_id)\n\t * - InMemory: Find by natural key, update if found, insert if not\n\t *\n\t * TRAP: If input contains duplicate natural keys, dedupe first (last-wins).\n\t * PostgreSQL will error: \"ON CONFLICT DO UPDATE command cannot affect row a second time\"\n\t *\n\t * @param components Components to upsert (id, entityId, type, data required)\n\t * @param options.entityContext When set (Postgres + ENABLE_DATA_ISOLATION), upsert runs under RLS for this entity.\n\t */\n\tupsertComponents(\n\t\tcomponents: Component[],\n\t\toptions?: { entityContext?: UUID },\n\t): Promise<void>;\n\n\t/**\n\t * Batch patch components (JSON Patch ops per component). Run in a transaction; all commit or all roll back.\n\t * @param updates Array of { componentId, ops }\n\t * @param options.entityContext When set (Postgres + ENABLE_DATA_ISOLATION), patch runs under RLS for this entity.\n\t */\n\tpatchComponents(\n\t\tupdates: Array<{ componentId: UUID; ops: PatchOp[] }>,\n\t\toptions?: { entityContext?: UUID },\n\t): Promise<void>;\n\n\t/**\n\t * Get memories matching criteria\n\t *\n\t * WHY metadata parameter: Eliminates the \"fetch 50K rows, filter in JS\" antipattern\n\t * seen in plugin-knowledge. Database-level JSON filtering is 50-100x faster:\n\t * - PostgreSQL: Uses GIN-indexed @> operator on jsonb columns\n\t * - MySQL: Uses JSON_CONTAINS() function\n\t * - InMemory: Deep equality check (less efficient but correct)\n\t *\n\t * WHY limit/offset: Standard pagination naming (limit = max results, offset = skip N).\n\t * The deprecated 'count' parameter is kept for backward compatibility.\n\t *\n\t * @param params.metadata Filter by metadata fields (partial object match)\n\t * @param params.limit Max results to return (replaces deprecated 'count')\n\t * @param params.offset Skip first N results for pagination\n\t * @param params.tableName Memory type/table (required)\n\t */\n\tgetMemories(params: {\n\t\tentityId?: UUID;\n\t\tagentId?: UUID;\n\t\t/** @deprecated use limit */\n\t\tcount?: number;\n\t\tlimit?: number;\n\t\toffset?: number;\n\t\tunique?: boolean;\n\t\ttableName: string;\n\t\tstart?: number;\n\t\tend?: number;\n\t\troomId?: UUID;\n\t\tworldId?: UUID;\n\t\tmetadata?: Record<string, unknown>;\n\t\t/**\n\t\t * Order by column (currently only 'createdAt' supported for security).\n\t\t * Whitelisted to prevent SQL injection. Default behavior: ORDER BY created_at DESC.\n\t\t */\n\t\torderBy?: \"createdAt\";\n\t\t/**\n\t\t * Order direction. Default: 'desc' (newest first, current hardcoded behavior).\n\t\t */\n\t\torderDirection?: \"asc\" | \"desc\";\n\t}): Promise<Memory[]>;\n\n\tgetMemoriesByIds(ids: UUID[], tableName?: string): Promise<Memory[]>;\n\n\tgetMemoriesByRoomIds(params: {\n\t\ttableName: string;\n\t\troomIds: UUID[];\n\t\tlimit?: number;\n\t}): Promise<Memory[]>;\n\n\tgetCachedEmbeddings(params: {\n\t\tquery_table_name: string;\n\t\tquery_threshold: number;\n\t\tquery_input: string;\n\t\tquery_field_name: string;\n\t\tquery_field_sub_name: string;\n\t\tquery_match_count: number;\n\t}): Promise<{ embedding: number[]; levenshtein_score: number }[]>;\n\n\tgetLogs(params: {\n\t\tentityId?: UUID;\n\t\troomId?: UUID;\n\t\ttype?: string;\n\t\t/** @deprecated use limit */\n\t\tcount?: number;\n\t\tlimit?: number;\n\t\toffset?: number;\n\t}): Promise<Log[]>;\n\n\t// ── Log CRUD (batch-only) ────────────────────────────────────────────\n\t// WHY batch-only: a single agent turn can produce multiple log entries\n\t// (model call, action execution, evaluator run). Batching avoids N\n\t// inserts for N log entries per turn. Named createLogs (not \"logBatch\")\n\t// for consistency with the create{Domain}s convention.\n\tcreateLogs(\n\t\tparams: Array<{\n\t\t\tbody: LogBody;\n\t\t\tentityId: UUID;\n\t\t\troomId: UUID;\n\t\t\ttype: string;\n\t\t}>,\n\t): Promise<void>;\n\n\t/**\n\t * Get logs by their IDs\n\t *\n\t * WHY: Batch lookup for specific log entries. Used when rendering agent\n\t * run history or debugging specific interactions (e.g., \"show me all logs\n\t * from this conversation turn\").\n\t *\n\t * @param logIds Array of log IDs to fetch\n\t * @returns Array of logs (only found logs returned, no nulls)\n\t */\n\tgetLogsByIds(logIds: UUID[]): Promise<Log[]>;\n\n\t/**\n\t * Update logs (batch)\n\t *\n\t * WHY: Agent run summaries update log status/metadata after completion.\n\t * Logs aren't truly immutable - their status field changes as runs progress\n\t * (pending → running → completed → failed).\n\t *\n\t * WHY batch: When an agent run completes, it updates status for all logs\n\t * in that run (model call log, action logs, evaluator logs).\n\t *\n\t * @param logs Array of {id, updates} where updates is a partial Log\n\t */\n\tupdateLogs(logs: Array<{ id: UUID; updates: Partial<Log> }>): Promise<void>;\n\n\tdeleteLogs(logIds: UUID[]): Promise<void>;\n\n\tgetAgentRunSummaries?(params: {\n\t\tlimit?: number;\n\t\troomId?: UUID;\n\t\tstatus?: RunStatus | \"all\";\n\t\tfrom?: number;\n\t\tto?: number;\n\t\tentityId?: UUID;\n\t}): Promise<AgentRunSummaryResult>;\n\n\tsearchMemories(params: {\n\t\tembedding: number[];\n\t\tmatch_threshold?: number;\n\t\t/** @deprecated use limit */\n\t\tcount?: number;\n\t\tlimit?: number;\n\t\tunique?: boolean;\n\t\ttableName: string;\n\t\tquery?: string;\n\t\troomId?: UUID;\n\t\tworldId?: UUID;\n\t\tentityId?: UUID;\n\t}): Promise<Memory[]>;\n\n\t// ── Memory CRUD (batch-only) ─────────────────────────────────────────\n\t// WHY batch-only: memory ingestion (e.g. bulk import of conversation\n\t// history, knowledge base seeding) creates many memories at once.\n\t// Even single-message flows benefit: the runtime's createMemory()\n\t// wrapper handles secret redaction then delegates here.\n\t/**\n\t * Batch create memories\n\t *\n\t * WHY UUID[] return: Returns the IDs of created memories, enabling immediate\n\t * follow-up operations (e.g., linking to external systems, creating relationships).\n\t * Changed from boolean return which was ambiguous (false = failed OR already exists?).\n\t *\n\t * @returns Array of created memory IDs (in same order as input)\n\t */\n\tcreateMemories(\n\t\tmemories: Array<{ memory: Memory; tableName: string; unique?: boolean }>,\n\t): Promise<UUID[]>;\n\t/**\n\t * Batch update memories\n\t *\n\t * WHY void return: Updates should throw on failure (fail-fast principle).\n\t * Changed from boolean[] which created ambiguity about whether to continue\n\t * processing after a failed update. Now failures are exceptional, not expected.\n\t *\n\t * WHY batch: SQL adapters use CASE expressions for single UPDATE statement:\n\t * UPDATE memories SET content = CASE\n\t * WHEN id = $1 THEN $2\n\t * WHEN id = $3 THEN $4\n\t * ...\n\t * WHERE id IN ($1, $3, ...)\n\t *\n\t * @throws Error if any update fails (transaction rolls back)\n\t */\n\tupdateMemories(\n\t\tmemories: Array<Partial<Memory> & { id: UUID; metadata?: MemoryMetadata }>,\n\t): Promise<void>;\n\tdeleteMemories(memoryIds: UUID[]): Promise<void>;\n\n\t/**\n\t * Upsert memories (insert or update by ID)\n\t *\n\t * WHY: Completes the upsert pattern. Unlike createMemories (which uses ON CONFLICT\n\t * DO NOTHING to skip duplicates), upsertMemories uses ON CONFLICT DO UPDATE to\n\t * overwrite existing memories. Used for bulk data refresh or re-import scenarios.\n\t *\n\t * CONFLICT RESOLUTION:\n\t * - Updates: content, metadata, unique (mutable data)\n\t * - Preserves: id, type, entityId, roomId, worldId, agentId, createdAt (identity)\n\t *\n\t * NO SIMILARITY CHECK: Unlike createMemories, this does NOT run embedding similarity\n\t * checks. The caller is asserting \"I know this memory's ID, insert or replace.\"\n\t * This is intentional - upsert is for known-identity updates, not duplicate detection.\n\t *\n\t * EMBEDDING HANDLING: If a memory includes an embedding, the embeddings table row\n\t * is also upserted (ON CONFLICT on memory_id). This keeps embeddings in sync.\n\t *\n\t * @param memories Array of {memory, tableName} to upsert (memory.id required)\n\t * @param options.entityContext When set (Postgres + ENABLE_DATA_ISOLATION), upsert runs under RLS for this entity.\n\t */\n\tupsertMemories(\n\t\tmemories: Array<{ memory: Memory; tableName: string }>,\n\t\toptions?: { entityContext?: UUID },\n\t): Promise<void>;\n\n\tdeleteAllMemories(roomIds: UUID[], tableName: string): Promise<void>;\n\n\t/**\n\t * Count memories matching criteria.\n\t * Use roomIds for room scope (pass [roomId] for a single room).\n\t */\n\tcountMemories(params: {\n\t\troomIds?: UUID[];\n\t\tunique?: boolean;\n\t\ttableName?: string;\n\t\tentityId?: UUID;\n\t\tagentId?: UUID;\n\t\tmetadata?: Record<string, unknown>;\n\t}): Promise<number>;\n\n\tgetAllWorlds(): Promise<World[]>;\n\n\t// ── World CRUD (batch-only) ──────────────────────────────────────────\n\t// WHY batch-only: world lifecycle is analogous to agents -- created on\n\t// boot, updated in bulk during sync, cleaned up together.\n\tgetWorldsByIds(worldIds: UUID[]): Promise<World[]>;\n\tcreateWorlds(worlds: World[]): Promise<UUID[]>;\n\tdeleteWorlds(worldIds: UUID[]): Promise<void>;\n\tupdateWorlds(worlds: World[]): Promise<void>;\n\n\t/**\n\t * Upsert worlds (insert or update by ID)\n\t *\n\t * WHY: Atomic insert-or-update for world initialization. Worlds are created\n\t * during agent bootstrap or plugin initialization. Concurrent initialization\n\t * attempts should be idempotent, not fail with \"already exists\" errors.\n\t *\n\t * WHY on adapter interface: SQL dialects support atomic upserts for worlds.\n\t * The world table has minimal fields (id, name, type, agentId), making upserts\n\t * straightforward across all dialects.\n\t *\n\t * WHY void return: World IDs are provided by the caller (often deterministic\n\t * UUIDs based on world name/type). No need to return IDs.\n\t *\n\t * IMPLEMENTATION NOTES:\n\t * - PostgreSQL: INSERT ... ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, ...\n\t * - MySQL: INSERT ... ON DUPLICATE KEY UPDATE name = VALUES(name), ...\n\t * - InMemory: worlds.set(id, world)\n\t *\n\t * @param worlds Worlds to upsert (ID required for each)\n\t */\n\tupsertWorlds(worlds: World[]): Promise<void>;\n\n\tgetRoomsByIds(roomIds: UUID[]): Promise<Room[]>;\n\n\tcreateRooms(rooms: Room[]): Promise<UUID[]>;\n\n\tdeleteRoomsByWorldIds(worldIds: UUID[]): Promise<void>;\n\n\t/**\n\t * Get room IDs where entities are participants.\n\t * @param entityIds Array of entity UUIDs\n\t * @returns Array of room IDs where any of the entities participate\n\t */\n\tgetRoomsForParticipants(entityIds: UUID[]): Promise<UUID[]>;\n\n\t/** Get rooms for multiple worlds. Limit/offset apply globally across all worlds. */\n\tgetRoomsByWorlds(\n\t\tworldIds: UUID[],\n\t\tlimit?: number,\n\t\toffset?: number,\n\t): Promise<Room[]>;\n\n\t// ── Room CRUD (batch-only) ───────────────────────────────────────────\n\t// WHY batch-only: room cleanup (e.g. deleteRoomsByWorldId) and bulk\n\t// sync from external platforms naturally produce batches.\n\t// getRoomsByIds returns Room[] (never null) -- an empty array means\n\t// \"none found\", which is the correct semantics for batch reads.\n\tupdateRooms(rooms: Room[]): Promise<void>;\n\tdeleteRooms(roomIds: UUID[]): Promise<void>;\n\n\t/**\n\t * Upsert rooms (insert or update by ID)\n\t *\n\t * WHY: Atomic insert-or-update for room management. Rooms are created during\n\t * `ensureConnection` or when syncing external platforms (Discord, Telegram).\n\t * Concurrent connection attempts should be idempotent.\n\t *\n\t * WHY on adapter interface: SQL dialects support atomic room upserts. Rooms\n\t * have more fields than worlds (name, type, worldId, metadata, etc.) but\n\t * upsert is still straightforward.\n\t *\n\t * WHY void return: Room IDs are provided by caller. For DM rooms, the ID\n\t * is often deterministic (hash of participant IDs). No need to return IDs.\n\t *\n\t * IMPLEMENTATION NOTES:\n\t * - PostgreSQL: INSERT ... ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, ...\n\t * - MySQL: INSERT ... ON DUPLICATE KEY UPDATE name = VALUES(name), ...\n\t * - InMemory: rooms.set(id, room)\n\t * - Partial updates: Full replacement (all fields updated)\n\t *\n\t * @param rooms Rooms to upsert (ID, worldId, agentId required for each)\n\t */\n\tupsertRooms(rooms: Room[]): Promise<void>;\n\n\tgetParticipantsForEntities(entityIds: UUID[]): Promise<Participant[]>;\n\n\t/** Get participants for multiple rooms (one entry per roomId, same order). */\n\tgetParticipantsForRooms(roomIds: UUID[]): Promise<ParticipantsForRoomsResult>;\n\n\tareRoomParticipants(\n\t\tpairs: Array<{ roomId: UUID; entityId: UUID }>,\n\t): Promise<boolean[]>;\n\n\t/**\n\t * Create room participants (add entities to a room)\n\t *\n\t * WHY renamed from addRoomParticipants: 'create' prefix aligns with CRUD\n\t * naming convention (create = insert, update = modify, delete = remove).\n\t *\n\t * WHY UUID[] return: Returns participant record IDs (changed from boolean).\n\t * Useful for tracking who joined when, managing invites, etc.\n\t *\n\t * WHY ON CONFLICT DO NOTHING: Idempotent - calling twice with same entities\n\t * doesn't fail, just skips duplicates. This is intentional for invite flows.\n\t *\n\t * @returns Array of created participant record IDs\n\t */\n\tcreateRoomParticipants(entityIds: UUID[], roomId: UUID): Promise<UUID[]>;\n\n\t// ── Participant CRUD (batch-only for mutations) ─────────────────────\n\t// WHY batch-only: createRoomParticipants accepts an array of entity IDs\n\t// (adding multiple users to a channel is common). deleteParticipants\n\t// accepts {entityId, roomId} pairs for flexibility -- you might remove\n\t// different entities from different rooms in one call.\n\tdeleteParticipants(\n\t\tparticipants: Array<{ entityId: UUID; roomId: UUID }>,\n\t): Promise<boolean>;\n\n\t/**\n\t * Update participants (batch)\n\t *\n\t * WHY: Participants have fields beyond roomState (e.g., lastSeenAt, metadata).\n\t * This method provides general-purpose participant updates, while\n\t * updateParticipantUserState is a specialized convenience for the common\n\t * case of updating notification preferences.\n\t *\n\t * WHY composite key: Participant table's primary key is (entityId, roomId, agentId).\n\t * Updates must specify all key fields, not just a single UUID.\n\t *\n\t * USAGE NOTE: For updating notification state (FOLLOWED/MUTED), prefer\n\t * updateParticipantUserState() which has simpler signature.\n\t *\n\t * @param participants Array of participant updates with composite keys\n\t */\n\tupdateParticipants(\n\t\tparticipants: Array<{\n\t\t\tentityId: UUID;\n\t\t\troomId: UUID;\n\t\t\tupdates: ParticipantUpdateFields;\n\t\t}>,\n\t): Promise<void>;\n\n\tgetParticipantUserStates(\n\t\tpairs: Array<{ roomId: UUID; entityId: UUID }>,\n\t): Promise<ParticipantUserState[]>;\n\n\tupdateParticipantUserStates(\n\t\tupdates: Array<{\n\t\t\troomId: UUID;\n\t\t\tentityId: UUID;\n\t\t\tstate: ParticipantUserState;\n\t\t}>,\n\t): Promise<void>;\n\n\t/** Get relationships by (source, target) pairs. Same order as pairs; null where not found. */\n\tgetRelationshipsByPairs(\n\t\tpairs: Array<{ sourceEntityId: UUID; targetEntityId: UUID }>,\n\t): Promise<(Relationship | null)[]>;\n\n\t/**\n\t * Retrieves all relationships for entities. Use entityIds (pass [entityId] for a single entity).\n\t */\n\tgetRelationships(params: {\n\t\tentityIds?: UUID[];\n\t\ttags?: string[];\n\t\tlimit?: number;\n\t\toffset?: number;\n\t}): Promise<Relationship[]>;\n\n\t// ── Relationship CRUD (batch-only) ──────────────────────────────────\n\t// WHY batch-only: relationship graphs can be synced in bulk (e.g.\n\t// importing a social graph from an external platform). The singular\n\t// getRelationship() and getRelationships() are query methods -- they\n\t// filter by (sourceEntityId, targetEntityId) or (entityId, tags),\n\t// not by relationship ID.\n\tcreateRelationships(\n\t\trelationships: Array<{\n\t\t\tsourceEntityId: UUID;\n\t\t\ttargetEntityId: UUID;\n\t\t\ttags?: string[];\n\t\t\tmetadata?: Metadata;\n\t\t}>,\n\t): Promise<UUID[]>;\n\tgetRelationshipsByIds(relationshipIds: UUID[]): Promise<Relationship[]>;\n\tupdateRelationships(relationships: Relationship[]): Promise<void>;\n\tdeleteRelationships(relationshipIds: UUID[]): Promise<void>;\n\n\t// ── Cache CRUD (batch-only) ──────────────────────────────────────────\n\t// WHY batch-only: providers often need multiple cache keys in a single\n\t// render cycle (e.g. checking rate limits, feature flags, user prefs).\n\t// getCaches returns a Map so callers can look up individual keys without\n\t// losing the batch benefit.\n\tgetCaches<T>(keys: string[]): Promise<Map<string, T>>;\n\tsetCaches<T>(entries: Array<{ key: string; value: T }>): Promise<boolean>;\n\tdeleteCaches(keys: string[]): Promise<boolean>;\n\n\t// Only task instance methods - definitions are in-memory\n\t/**\n\t * Get tasks matching criteria\n\t *\n\t * WHY limit/offset added: Previously returned ALL matching tasks, which could\n\t * be thousands of records. Task queues grow unbounded over time, causing:\n\t * - Memory exhaustion when loading full queue\n\t * - Slow queries without limits\n\t * - UI freeze when rendering thousands of tasks\n\t *\n\t * @param params.limit Max results (default: unlimited, use with caution)\n\t * @param params.offset Skip first N results for pagination\n\t */\n\tgetTasks(params: {\n\t\troomId?: UUID;\n\t\ttags?: string[];\n\t\tentityId?: UUID;\n\t\t/** Required. Only tasks with agentId in this array are returned. Single agent = [id]. WHY: multi-tenant safety; schema indexes by agent_id; daemon batches one getTasks(agentIds) for many agents. */\n\t\tagentIds: UUID[];\n\t\tlimit?: number;\n\t\toffset?: number;\n\t}): Promise<Task[]>;\n\tgetTasksByName(name: string): Promise<Task[]>;\n\n\t// ── Task CRUD (batch-only) ───────────────────────────────────────────\n\t// WHY batch-only: task scheduling creates/updates tasks in bursts\n\t// (e.g. all recurring tasks on agent boot). getTasks() and\n\t// getTasksByName() are query methods (filter by room, tags, name).\n\tcreateTasks(tasks: Task[]): Promise<UUID[]>;\n\tgetTasksByIds(taskIds: UUID[]): Promise<Task[]>;\n\tupdateTasks(updates: Array<{ id: UUID; task: Partial<Task> }>): Promise<void>;\n\tdeleteTasks(taskIds: UUID[]): Promise<void>;\n\n\tgetMemoriesByWorldId(params: {\n\t\tworldIds?: UUID[];\n\t\t/** @deprecated use limit */\n\t\tcount?: number;\n\t\tlimit?: number;\n\t\ttableName?: string;\n\t}): Promise<Memory[]>;\n\n\t// Pairing methods for secure DM access control\n\n\t/** Get pairing requests for multiple (channel, agentId) queries. One entry per query, same order. */\n\tgetPairingRequests(\n\t\tqueries: Array<{ channel: PairingChannel; agentId: UUID }>,\n\t): Promise<PairingRequestsResult>;\n\n\t// ── Pairing request CRUD (batch-only) ────────────────────────────────\n\tcreatePairingRequests(requests: PairingRequest[]): Promise<UUID[]>;\n\tupdatePairingRequests(requests: PairingRequest[]): Promise<void>;\n\tdeletePairingRequests(ids: UUID[]): Promise<void>;\n\n\t/** Get pairing allowlists for multiple (channel, agentId) queries. One entry per query, same order. */\n\tgetPairingAllowlists(\n\t\tqueries: Array<{ channel: PairingChannel; agentId: UUID }>,\n\t): Promise<PairingAllowlistsResult>;\n\n\t// ── Pairing allowlist CRUD (batch-only) ──────────────────────────────\n\t// WHY batch-only: allowlist management (admin adding/removing multiple\n\t// users) is naturally a batch operation.\n\tcreatePairingAllowlistEntries(\n\t\tentries: PairingAllowlistEntry[],\n\t): Promise<UUID[]>;\n\n\t/**\n\t * Update pairing allowlist entries (batch)\n\t *\n\t * WHY: Allowlist entries have metadata/config that changes over time\n\t * (e.g., expiration dates, permission levels, notes). Batch updates\n\t * are needed when admin adjusts settings for multiple users at once.\n\t *\n\t * @param entries Full PairingAllowlistEntry objects (ID required for each)\n\t */\n\tupdatePairingAllowlistEntries(\n\t\tentries: PairingAllowlistEntry[],\n\t): Promise<void>;\n\n\tdeletePairingAllowlistEntries(ids: UUID[]): Promise<void>;\n\n\t// ── Plugin Schema Registration ──────────────────────────────────────────\n\t// WHY: Plugins need custom tables (goals, todos) but shouldn't cast runtime.db\n\t// to Drizzle types. This provides a generic, adapter-agnostic way for plugins\n\t// to register schemas and access data.\n\n\t/**\n\t * Register a plugin's schema (tables, columns, indexes)\n\t *\n\t * WHY: Plugins like goals and todos need custom tables. Without this, they\n\t * must cast runtime.db to Drizzle, which only works with SQL adapters.\n\t *\n\t * IDEMPOTENT: Safe to call multiple times (e.g., on hot reload). The adapter\n\t * should check if tables exist and only create/migrate what's needed.\n\t *\n\t * MIGRATIONS: If a plugin updates its schema (adds columns, indexes), the\n\t * adapter should diff against the current schema and apply changes. For SQL\n\t * adapters, this uses ALTER TABLE. For in-memory, it's a no-op (just stores\n\t * the schema definition).\n\t *\n\t * @param schema Complete schema definition for the plugin\n\t * @throws Error if schema is invalid or migration fails\n\t */\n\tregisterPluginSchema?(\n\t\tschema: import(\"./plugin-store\").PluginSchema,\n\t): Promise<void>;\n\n\t/**\n\t * Get a plugin store for CRUD operations on plugin tables\n\t *\n\t * WHY: Provides a generic interface for plugins to access their data without\n\t * knowing whether they're running on SQL or in-memory adapters.\n\t *\n\t * NAMESPACING: The store automatically prefixes table names with the plugin\n\t * name to avoid conflicts (e.g., \"goals_goals\", \"goals_goal_tags\").\n\t *\n\t * @param pluginName Name of the plugin (must match registered schema)\n\t * @returns Plugin store interface, or null if adapter doesn't support plugins\n\t *\n\t * @example\n\t * ```typescript\n\t * // In plugin code:\n\t * const store = runtime.getPluginStore('goals');\n\t * if (!store) throw new Error('Plugin storage not available');\n\t *\n\t * const goals = await store.query<Goal>('goals', {\n\t * agentId: runtime.agentId,\n\t * isCompleted: false\n\t * });\n\t * ```\n\t */\n\tgetPluginStore?(\n\t\tpluginName: string,\n\t): import(\"./plugin-store\").IPluginStore | null;\n}\n\n/**\n * Result interface for embedding similarity searches\n */\nexport interface EmbeddingSearchResult\n\textends Omit<ProtoEmbeddingSearchResult, \"levenshteinScore\"> {\n\tlevenshtein_score?: number;\n}\n\n/** Base shape for memory retrieval options (string IDs before UUID substitution) */\ninterface ProtoMemoryRetrievalOptions {\n\troomId?: string;\n\tagentId?: string;\n\tstart?: number;\n\tend?: number;\n\tlimit?: number;\n\tunique?: boolean;\n\ttableName?: string;\n}\n\n/**\n * Options for memory retrieval operations\n */\nexport interface MemoryRetrievalOptions\n\textends Omit<\n\t\tProtoMemoryRetrievalOptions,\n\t\t\"roomId\" | \"agentId\" | \"start\" | \"end\"\n\t> {\n\troomId: UUID;\n\tagentId?: UUID;\n\tstart?: number | bigint;\n\tend?: number | bigint;\n}\n\n/** Base shape for memory search options */\ninterface ProtoMemorySearchOptions {\n\troomId?: string;\n\tagentId?: string;\n\tmetadata?: unknown;\n\tmatchThreshold?: number;\n\tlimit?: number;\n\ttableName?: string;\n}\n\n/**\n * Options for memory search operations\n */\nexport interface MemorySearchOptions\n\textends Omit<\n\t\tProtoMemorySearchOptions,\n\t\t\"roomId\" | \"agentId\" | \"metadata\" | \"matchThreshold\"\n\t> {\n\troomId: UUID;\n\tagentId?: UUID;\n\tmetadata?: Partial<MemoryMetadata>;\n\tmatch_threshold?: number;\n}\n\n/** Base shape for multi-room memory options */\ninterface ProtoMultiRoomMemoryOptions {\n\troomIds?: string[];\n\tagentId?: string;\n\tlimit?: number;\n\ttableName?: string;\n}\n\n/**\n * Options for multi-room memory retrieval\n */\nexport interface MultiRoomMemoryOptions\n\textends Omit<ProtoMultiRoomMemoryOptions, \"roomIds\" | \"agentId\"> {\n\troomIds: UUID[];\n\tagentId?: UUID;\n}\n\n/**\n * Standard options pattern for memory operations\n * Provides a simpler, more consistent interface\n */\nexport interface StandardMemoryOptions {\n\troomId: UUID;\n\tlimit?: number; // Standard naming (replacing 'count')\n\tagentId?: UUID; // Common optional parameter\n\tunique?: boolean; // Common flag for duplication control\n\tstart?: number; // Pagination start\n\tend?: number; // Pagination end\n}\n\n/**\n * Specialized memory search options\n */\nexport interface MemorySearchParams extends StandardMemoryOptions {\n\tembedding: number[];\n\tsimilarity?: number; // Clearer name than 'match_threshold'\n}\n\n/**\n * Base interface for database connection objects.\n * Specific adapters should extend this with their connection type.\n *\n * @example\n * ```typescript\n * // In a PostgreSQL adapter:\n * interface PgConnection extends DbConnection {\n * pool: Pool;\n * query: <T>(sql: string, params?: unknown[]) => Promise<T>;\n * }\n * ```\n */\nexport interface DbConnection {\n\t/** Whether the connection is currently active */\n\tisConnected?: boolean;\n\t/** Close the connection */\n\tclose?: () => Promise<void>;\n}\n\n// Allowable vector dimensions\nexport const VECTOR_DIMS = {\n\tSMALL: 384,\n\tMEDIUM: 512,\n\tLARGE: 768,\n\tXL: 1024,\n\tXXL: 1536,\n\tXXXL: 3072,\n} as const;\n",
335
- "import type { ChannelType, Metadata } from \"./primitives\";\nimport type {\n\tComponent as ProtoComponent,\n\tEntity as ProtoEntity,\n\tParticipant as ProtoParticipant,\n\tRelationship as ProtoRelationship,\n\tRoom as ProtoRoom,\n\tWorld as ProtoWorld,\n\tWorldMetadata as ProtoWorldMetadata,\n} from \"./proto.js\";\nimport type { WorldSettings } from \"./settings\";\n\nexport type TimestampValue = number;\n\nexport interface Component\n\textends Omit<\n\t\tProtoComponent,\n\t\t\"$typeName\" | \"$unknown\" | \"createdAt\" | \"data\"\n\t> {\n\tcreatedAt: TimestampValue;\n\tdata?: Metadata;\n}\n\n/**\n * Represents a user account\n */\nexport interface Entity\n\textends Omit<\n\t\tProtoEntity,\n\t\t\"$typeName\" | \"$unknown\" | \"metadata\" | \"components\"\n\t> {\n\tmetadata?: Metadata;\n\tcomponents?: Component[];\n}\n\n/**\n * Defines roles within a system, typically for access control or permissions, often within a `World`.\n * - `OWNER`: Represents the highest level of control, typically the creator or primary administrator.\n * - `ADMIN`: Represents administrative privileges, usually a subset of owner capabilities.\n * - `NONE`: Indicates no specific role or default, minimal permissions.\n * These roles are often used in `World.metadata.roles` to assign roles to entities.\n */\nexport const Role = {\n\tOWNER: \"OWNER\",\n\tADMIN: \"ADMIN\",\n\tNONE: \"NONE\",\n} as const;\n\nexport type Role = (typeof Role)[keyof typeof Role];\n\nexport interface WorldOwnership {\n\townerId: string;\n}\n\nexport interface WorldMetadata\n\textends Omit<\n\t\tProtoWorldMetadata,\n\t\t\"$typeName\" | \"$unknown\" | \"roles\" | \"extra\" | \"ownership\"\n\t> {\n\ttype?: string;\n\tdescription?: string;\n\townership?: WorldOwnership;\n\troles?: Record<string, Role>;\n\textra?: Metadata;\n\tsettings?: WorldSettings;\n}\n\nexport interface World\n\textends Omit<ProtoWorld, \"$typeName\" | \"$unknown\" | \"metadata\"> {\n\tmetadata?: WorldMetadata;\n}\n\nexport interface Room\n\textends Omit<ProtoRoom, \"$typeName\" | \"$unknown\" | \"type\" | \"metadata\"> {\n\ttype: ChannelType;\n\tmetadata?: Metadata;\n}\n\nexport type RoomMetadata = Metadata;\n\n/**\n * Room participant with account details\n */\nexport interface Participant\n\textends Omit<ProtoParticipant, \"$typeName\" | \"$unknown\" | \"entity\"> {\n\tentity: Entity;\n}\n\n/**\n * Represents a relationship between users\n */\nexport interface Relationship\n\textends Omit<ProtoRelationship, \"$typeName\" | \"$unknown\" | \"metadata\"> {\n\tmetadata?: Metadata;\n}\n\n// Re-export Metadata for convenience\nexport type { Metadata };\n",
335
+ "import type { ChannelType, Metadata } from \"./primitives\";\nimport type {\n\tComponent as ProtoComponent,\n\tEntity as ProtoEntity,\n\tParticipant as ProtoParticipant,\n\tRelationship as ProtoRelationship,\n\tRoom as ProtoRoom,\n\tWorld as ProtoWorld,\n\tWorldMetadata as ProtoWorldMetadata,\n} from \"./proto.js\";\nimport type { WorldSettings } from \"./settings\";\n\nexport type TimestampValue = number;\n\nexport interface Component\n\textends Omit<\n\t\tProtoComponent,\n\t\t\"$typeName\" | \"$unknown\" | \"createdAt\" | \"data\"\n\t> {\n\tcreatedAt: TimestampValue;\n\tdata?: Metadata;\n}\n\n/**\n * Represents a user account\n */\nexport interface Entity\n\textends Omit<\n\t\tProtoEntity,\n\t\t\"$typeName\" | \"$unknown\" | \"metadata\" | \"components\"\n\t> {\n\tmetadata?: Metadata;\n\tcomponents?: Component[];\n}\n\n/**\n * Defines roles within a system, typically for access control or permissions, often within a `World`.\n * - `OWNER`: Represents the highest level of control, typically the creator or primary administrator.\n * - `ADMIN`: Represents administrative privileges, usually a subset of owner capabilities.\n * - `NONE`: Indicates no specific role or default, minimal permissions.\n * These roles are often used in `World.metadata.roles` to assign roles to entities.\n */\nexport const Role = {\n\tOWNER: \"OWNER\",\n\tADMIN: \"ADMIN\",\n\tNONE: \"NONE\",\n} as const;\n\nexport type Role = (typeof Role)[keyof typeof Role];\n\nexport interface WorldOwnership {\n\townerId: string;\n}\n\nexport interface WorldMetadata\n\textends Omit<\n\t\tProtoWorldMetadata,\n\t\t\"$typeName\" | \"$unknown\" | \"roles\" | \"extra\" | \"ownership\"\n\t> {\n\ttype?: string;\n\tdescription?: string;\n\townership?: WorldOwnership;\n\troles?: Record<string, Role>;\n\textra?: Metadata;\n\tsettings?: WorldSettings;\n\t/** Platform-specific chat type (e.g., 'private', 'group', 'supergroup', 'channel') */\n\tchatType?: string;\n\t/** Whether Telegram forum mode is enabled for this world */\n\tisForumEnabled?: boolean;\n\t/** Allow platform-specific extensions */\n\t[key: string]: unknown;\n}\n\nexport interface World\n\textends Omit<ProtoWorld, \"$typeName\" | \"$unknown\" | \"metadata\"> {\n\tmetadata?: WorldMetadata;\n}\n\nexport interface Room\n\textends Omit<ProtoRoom, \"$typeName\" | \"$unknown\" | \"type\" | \"metadata\"> {\n\ttype: ChannelType;\n\tmetadata?: Metadata;\n\t/** Platform server/guild/chat ID that owns this room */\n\tserverId?: string;\n}\n\nexport type RoomMetadata = Metadata;\n\n/**\n * Room participant with account details\n */\nexport interface Participant\n\textends Omit<ProtoParticipant, \"$typeName\" | \"$unknown\" | \"entity\"> {\n\tentity: Entity;\n}\n\n/**\n * Represents a relationship between users\n */\nexport interface Relationship\n\textends Omit<ProtoRelationship, \"$typeName\" | \"$unknown\" | \"metadata\"> {\n\tmetadata?: Metadata;\n}\n\n// Re-export Metadata for convenience\nexport type { Metadata };\n",
336
336
  "import type { HandlerCallback } from \"./components\";\nimport type { Entity, Room, World } from \"./environment\";\nimport type { Memory } from \"./memory\";\nimport type { ControlMessage } from \"./messaging\";\nimport type { ModelTypeName } from \"./model\";\nimport type { Content, JsonValue, UUID } from \"./primitives\";\nimport type { IAgentRuntime } from \"./runtime\";\n\n/**\n * Standard event types across all platforms\n */\nexport enum EventType {\n\t// World events\n\tWORLD_JOINED = \"WORLD_JOINED\",\n\tWORLD_CONNECTED = \"WORLD_CONNECTED\",\n\tWORLD_LEFT = \"WORLD_LEFT\",\n\n\t// Entity events\n\tENTITY_JOINED = \"ENTITY_JOINED\",\n\tENTITY_LEFT = \"ENTITY_LEFT\",\n\tENTITY_UPDATED = \"ENTITY_UPDATED\",\n\n\t// Room events\n\tROOM_JOINED = \"ROOM_JOINED\",\n\tROOM_LEFT = \"ROOM_LEFT\",\n\n\t// Message events\n\tMESSAGE_RECEIVED = \"MESSAGE_RECEIVED\",\n\tMESSAGE_SENT = \"MESSAGE_SENT\",\n\tMESSAGE_DELETED = \"MESSAGE_DELETED\",\n\n\t// Channel events\n\tCHANNEL_CLEARED = \"CHANNEL_CLEARED\",\n\n\t// Voice events\n\tVOICE_MESSAGE_RECEIVED = \"VOICE_MESSAGE_RECEIVED\",\n\tVOICE_MESSAGE_SENT = \"VOICE_MESSAGE_SENT\",\n\n\t// Interaction events\n\tREACTION_RECEIVED = \"REACTION_RECEIVED\",\n\tPOST_GENERATED = \"POST_GENERATED\",\n\tINTERACTION_RECEIVED = \"INTERACTION_RECEIVED\",\n\n\t// Run events\n\tRUN_STARTED = \"RUN_STARTED\",\n\tRUN_ENDED = \"RUN_ENDED\",\n\tRUN_TIMEOUT = \"RUN_TIMEOUT\",\n\n\t// Action events\n\tACTION_STARTED = \"ACTION_STARTED\",\n\tACTION_COMPLETED = \"ACTION_COMPLETED\",\n\n\t// Evaluator events\n\tEVALUATOR_STARTED = \"EVALUATOR_STARTED\",\n\tEVALUATOR_COMPLETED = \"EVALUATOR_COMPLETED\",\n\n\t// Model events\n\tMODEL_USED = \"MODEL_USED\",\n\n\t// Embedding events\n\tEMBEDDING_GENERATION_REQUESTED = \"EMBEDDING_GENERATION_REQUESTED\",\n\tEMBEDDING_GENERATION_COMPLETED = \"EMBEDDING_GENERATION_COMPLETED\",\n\tEMBEDDING_GENERATION_FAILED = \"EMBEDDING_GENERATION_FAILED\",\n\n\t// Control events\n\tCONTROL_MESSAGE = \"CONTROL_MESSAGE\",\n\n\t// Form events\n\tFORM_FIELD_CONFIRMED = \"FORM_FIELD_CONFIRMED\",\n\tFORM_FIELD_CANCELLED = \"FORM_FIELD_CANCELLED\",\n\n\t// Hook system events - command lifecycle\n\tHOOK_COMMAND_NEW = \"HOOK_COMMAND_NEW\",\n\tHOOK_COMMAND_RESET = \"HOOK_COMMAND_RESET\",\n\tHOOK_COMMAND_STOP = \"HOOK_COMMAND_STOP\",\n\n\t// Hook system events - session lifecycle\n\tHOOK_SESSION_START = \"HOOK_SESSION_START\",\n\tHOOK_SESSION_END = \"HOOK_SESSION_END\",\n\n\t// Hook system events - agent lifecycle\n\tHOOK_AGENT_BOOTSTRAP = \"HOOK_AGENT_BOOTSTRAP\",\n\tHOOK_AGENT_START = \"HOOK_AGENT_START\",\n\tHOOK_AGENT_END = \"HOOK_AGENT_END\",\n\n\t// Hook system events - gateway lifecycle\n\tHOOK_GATEWAY_START = \"HOOK_GATEWAY_START\",\n\tHOOK_GATEWAY_STOP = \"HOOK_GATEWAY_STOP\",\n\n\t// Hook system events - compaction\n\tHOOK_COMPACTION_BEFORE = \"HOOK_COMPACTION_BEFORE\",\n\tHOOK_COMPACTION_AFTER = \"HOOK_COMPACTION_AFTER\",\n\n\t// Hook system events - tool execution\n\tHOOK_TOOL_BEFORE = \"HOOK_TOOL_BEFORE\",\n\tHOOK_TOOL_AFTER = \"HOOK_TOOL_AFTER\",\n\tHOOK_TOOL_PERSIST = \"HOOK_TOOL_PERSIST\",\n\n\t// Hook system events - message lifecycle (supplements MESSAGE_*)\n\tHOOK_MESSAGE_SENDING = \"HOOK_MESSAGE_SENDING\",\n}\n\n/**\n * Platform-specific event type prefix\n */\nexport enum PlatformPrefix {\n\tDISCORD = \"DISCORD\",\n\tTELEGRAM = \"TELEGRAM\",\n\tX = \"X\",\n}\n\n/**\n * Base payload interface for all events\n */\nexport interface EventPayload {\n\truntime: IAgentRuntime;\n\tsource?: string;\n\tonComplete?: () => void;\n}\n\n/**\n * Payload for world-related events\n */\nexport interface WorldPayload extends EventPayload {\n\tworld: World;\n\trooms: Room[];\n\tentities: Entity[];\n}\n\n/**\n * Payload for entity-related events\n */\nexport interface EntityPayload extends EventPayload {\n\tentityId: UUID;\n\tworldId?: UUID;\n\troomId?: UUID;\n\tmetadata?: {\n\t\toriginalId: string;\n\t\tusername: string;\n\t\tdisplayName?: string;\n\t\ttype?: string;\n\t};\n}\n\n/**\n * Payload for reaction-related events\n */\nexport interface MessagePayload extends EventPayload {\n\tmessage: Memory;\n\tcallback?: HandlerCallback;\n}\n\n/**\n * Payload for channel cleared events\n */\nexport interface ChannelClearedPayload extends EventPayload {\n\troomId: UUID;\n}\n\n/**\n * Payload for events that are invoked without a message\n */\nexport interface InvokePayload extends EventPayload {\n\tworldId: UUID;\n\troomId: UUID;\n\tuserId?: UUID;\n\tsource?: string;\n\tcallback?: HandlerCallback;\n}\n\n/**\n * Run event payload type\n */\nexport interface RunEventPayload extends EventPayload {\n\trunId: UUID;\n\tmessageId: UUID;\n\troomId: UUID;\n\tentityId: UUID;\n\tstartTime: number | bigint;\n\tstatus: \"started\" | \"completed\" | \"timeout\";\n\tendTime?: number | bigint;\n\tduration?: number | bigint;\n\terror?: string | Error;\n}\n\n/**\n * Action event payload type\n */\nexport interface ActionEventPayload extends EventPayload {\n\troomId: UUID;\n\tworld: UUID;\n\tcontent: Content;\n\tmessageId?: UUID;\n}\n\n/**\n * Evaluator event payload type\n */\nexport interface EvaluatorEventPayload extends EventPayload {\n\tevaluatorId: UUID;\n\tevaluatorName: string;\n\tstartTime?: number | bigint;\n\tcompleted?: boolean;\n\terror?: Error;\n}\n\n/**\n * Model event payload type\n */\nexport interface ModelEventPayload extends EventPayload {\n\ttype: ModelTypeName;\n\ttokens?: {\n\t\tprompt: number;\n\t\tcompletion: number;\n\t\ttotal: number;\n\t};\n}\n\n/**\n * Payload for embedding generation events\n */\nexport interface EmbeddingGenerationPayload extends EventPayload {\n\tmemory: Memory;\n\tpriority?: \"high\" | \"normal\" | \"low\";\n\tembedding?: number[];\n\terror?: Error | string;\n\trunId?: UUID;\n\tretryCount?: number;\n\tmaxRetries?: number;\n}\n\n/**\n * Payload for control message events\n */\nexport interface ControlMessagePayload extends EventPayload {\n\tmessage: ControlMessage;\n}\n\nexport interface FormFieldEventPayload extends EventPayload {\n\tsessionId: string;\n\tentityId: UUID;\n\tfield: string;\n\tvalue?: JsonValue;\n\texternalData?: JsonValue;\n\treason?: string;\n}\n\n// ============================================================================\n// Hook System Event Payloads\n// ============================================================================\n\n/**\n * Base payload for all hook events.\n * Hooks can push messages to the `messages` array to send responses back to users.\n */\nexport interface HookEventPayload extends EventPayload {\n\t/** Session key this hook event relates to */\n\tsessionKey: string;\n\t/** Messages to send back to the user (hooks can push to this array) */\n\tmessages: string[];\n\t/** Timestamp when the event occurred */\n\ttimestamp: Date;\n\t/** Additional context specific to the event */\n\tcontext: Record<string, unknown>;\n}\n\n/**\n * Payload for command hook events (HOOK_COMMAND_NEW, HOOK_COMMAND_RESET, HOOK_COMMAND_STOP)\n */\nexport interface HookCommandPayload extends HookEventPayload {\n\t/** The command action: \"new\", \"reset\", or \"stop\" */\n\tcommand: \"new\" | \"reset\" | \"stop\";\n\t/** ID of the sender who issued the command */\n\tsenderId?: string;\n\t/** Source surface of the command (e.g., \"telegram\", \"discord\") */\n\tcommandSource?: string;\n\t/** Session entry data */\n\tsessionEntry?: Record<string, unknown>;\n\t/** Previous session entry data (for reset) */\n\tpreviousSessionEntry?: Record<string, unknown>;\n\t/** Configuration at time of command */\n\tconfig?: Record<string, unknown>;\n}\n\n/**\n * Bootstrap file definition for agent bootstrap hooks\n */\nexport interface BootstrapFile {\n\t/** File path relative to workspace */\n\tpath: string;\n\t/** File content */\n\tcontent: string;\n\t/** File type (e.g., \"soul\", \"boot\", \"tools\") */\n\ttype?: string;\n\t/** Whether this file is required */\n\trequired?: boolean;\n}\n\n/**\n * Payload for agent bootstrap hook event (HOOK_AGENT_BOOTSTRAP)\n */\nexport interface HookAgentBootstrapPayload extends HookEventPayload {\n\t/** Workspace directory path */\n\tworkspaceDir: string;\n\t/** Bootstrap files that will be injected. Hooks can modify this array. */\n\tbootstrapFiles: BootstrapFile[];\n\t/** Agent ID */\n\tagentId?: string;\n\t/** Session ID */\n\tsessionId?: string;\n}\n\n/**\n * Payload for agent start/end hook events (HOOK_AGENT_START, HOOK_AGENT_END)\n */\nexport interface HookAgentLifecyclePayload extends HookEventPayload {\n\t/** The initial prompt or message */\n\tprompt?: string;\n\t/** Messages in the conversation */\n\tconversationMessages?: unknown[];\n\t/** Whether the agent run completed successfully */\n\tsuccess?: boolean;\n\t/** Error message if failed */\n\terror?: string;\n\t/** Duration of the agent run in milliseconds */\n\tdurationMs?: number;\n\t/** System prompt to inject (for HOOK_AGENT_START result) */\n\tsystemPrompt?: string;\n\t/** Context to prepend to conversation (for HOOK_AGENT_START result) */\n\tprependContext?: string;\n}\n\n/**\n * Payload for session hook events (HOOK_SESSION_START, HOOK_SESSION_END)\n */\nexport interface HookSessionPayload extends HookEventPayload {\n\t/** Channel ID for the session */\n\tchannelId?: string;\n\t/** Account ID associated with the session */\n\taccountId?: string;\n\t/** Conversation ID */\n\tconversationId?: string;\n}\n\n/**\n * Payload for gateway hook events (HOOK_GATEWAY_START, HOOK_GATEWAY_STOP)\n */\nexport interface HookGatewayPayload extends HookEventPayload {\n\t/** Gateway port number */\n\tport?: number;\n\t/** Gateway host/bind address */\n\thost?: string;\n\t/** List of channels that were started */\n\tchannels?: string[];\n}\n\n/**\n * Payload for compaction hook events (HOOK_COMPACTION_BEFORE, HOOK_COMPACTION_AFTER)\n */\nexport interface HookCompactionPayload extends HookEventPayload {\n\t/** Number of messages before compaction */\n\tmessageCount: number;\n\t/** Estimated token count */\n\ttokenCount?: number;\n\t/** Number of messages compacted (for HOOK_COMPACTION_AFTER) */\n\tcompactedCount?: number;\n}\n\n/**\n * Payload for tool hook events (HOOK_TOOL_BEFORE, HOOK_TOOL_AFTER, HOOK_TOOL_PERSIST)\n */\nexport interface HookToolPayload extends HookEventPayload {\n\t/** Name of the tool being invoked */\n\ttoolName: string;\n\t/** Tool input arguments */\n\ttoolArgs?: Record<string, unknown>;\n\t/** Tool execution result (for HOOK_TOOL_AFTER) */\n\tresult?: unknown;\n\t/** Whether to skip this tool invocation (for HOOK_TOOL_BEFORE) */\n\tskip?: boolean;\n\t/** Modified arguments (for HOOK_TOOL_BEFORE) */\n\tmodifiedArgs?: Record<string, unknown>;\n\t/** Modified result to persist (for HOOK_TOOL_PERSIST) */\n\tmodifiedResult?: unknown;\n}\n\n/**\n * Payload for message sending hook event (HOOK_MESSAGE_SENDING)\n */\nexport interface HookMessageSendingPayload extends HookEventPayload {\n\t/** Recipient identifier */\n\tto: string;\n\t/** Message content */\n\tcontent: string;\n\t/** Message metadata */\n\tmetadata?: Record<string, unknown>;\n\t/** Whether to cancel sending this message */\n\tcancel?: boolean;\n\t/** Modified content to send instead */\n\tmodifiedContent?: string;\n}\n\n/**\n * Maps event types to their corresponding payload types\n */\nexport interface EventPayloadMap {\n\t[EventType.WORLD_JOINED]: WorldPayload;\n\t[EventType.WORLD_CONNECTED]: WorldPayload;\n\t[EventType.WORLD_LEFT]: WorldPayload;\n\t[EventType.ENTITY_JOINED]: EntityPayload;\n\t[EventType.ENTITY_LEFT]: EntityPayload;\n\t[EventType.ENTITY_UPDATED]: EntityPayload;\n\t[EventType.MESSAGE_RECEIVED]: MessagePayload;\n\t[EventType.MESSAGE_SENT]: MessagePayload;\n\t[EventType.MESSAGE_DELETED]: MessagePayload;\n\t[EventType.VOICE_MESSAGE_RECEIVED]: MessagePayload;\n\t[EventType.VOICE_MESSAGE_SENT]: MessagePayload;\n\t[EventType.CHANNEL_CLEARED]: ChannelClearedPayload;\n\t[EventType.REACTION_RECEIVED]: MessagePayload;\n\t[EventType.POST_GENERATED]: InvokePayload;\n\t[EventType.INTERACTION_RECEIVED]: MessagePayload;\n\t[EventType.RUN_STARTED]: RunEventPayload;\n\t[EventType.RUN_ENDED]: RunEventPayload;\n\t[EventType.RUN_TIMEOUT]: RunEventPayload;\n\t[EventType.ACTION_STARTED]: ActionEventPayload;\n\t[EventType.ACTION_COMPLETED]: ActionEventPayload;\n\t[EventType.EVALUATOR_STARTED]: EvaluatorEventPayload;\n\t[EventType.EVALUATOR_COMPLETED]: EvaluatorEventPayload;\n\t[EventType.MODEL_USED]: ModelEventPayload;\n\t[EventType.EMBEDDING_GENERATION_REQUESTED]: EmbeddingGenerationPayload;\n\t[EventType.EMBEDDING_GENERATION_COMPLETED]: EmbeddingGenerationPayload;\n\t[EventType.EMBEDDING_GENERATION_FAILED]: EmbeddingGenerationPayload;\n\t[EventType.CONTROL_MESSAGE]: ControlMessagePayload;\n\t[EventType.FORM_FIELD_CONFIRMED]: FormFieldEventPayload;\n\t[EventType.FORM_FIELD_CANCELLED]: FormFieldEventPayload;\n\t// Hook system event payloads\n\t[EventType.HOOK_COMMAND_NEW]: HookCommandPayload;\n\t[EventType.HOOK_COMMAND_RESET]: HookCommandPayload;\n\t[EventType.HOOK_COMMAND_STOP]: HookCommandPayload;\n\t[EventType.HOOK_SESSION_START]: HookSessionPayload;\n\t[EventType.HOOK_SESSION_END]: HookSessionPayload;\n\t[EventType.HOOK_AGENT_BOOTSTRAP]: HookAgentBootstrapPayload;\n\t[EventType.HOOK_AGENT_START]: HookAgentLifecyclePayload;\n\t[EventType.HOOK_AGENT_END]: HookAgentLifecyclePayload;\n\t[EventType.HOOK_GATEWAY_START]: HookGatewayPayload;\n\t[EventType.HOOK_GATEWAY_STOP]: HookGatewayPayload;\n\t[EventType.HOOK_COMPACTION_BEFORE]: HookCompactionPayload;\n\t[EventType.HOOK_COMPACTION_AFTER]: HookCompactionPayload;\n\t[EventType.HOOK_TOOL_BEFORE]: HookToolPayload;\n\t[EventType.HOOK_TOOL_AFTER]: HookToolPayload;\n\t[EventType.HOOK_TOOL_PERSIST]: HookToolPayload;\n\t[EventType.HOOK_MESSAGE_SENDING]: HookMessageSendingPayload;\n}\n\n/**\n * Event handler function type\n */\nexport type EventHandler<T extends keyof EventPayloadMap> = (\n\tpayload: EventPayloadMap[T],\n) => Promise<void>;\n",
337
337
  "/**\n * Hook Service Type Definitions\n *\n * Provides the type system for the unified hook service that consolidates\n * event-driven hooks across the Eliza agent runtime.\n */\n\nimport { type EventPayload, EventType } from \"./events\";\nimport type { Service } from \"./service\";\n\n// ============================================================================\n// Hook Source and Priority Types\n// ============================================================================\n\n/**\n * Identifies the origin of a hook registration.\n */\nexport type HookSource =\n\t| \"bundled\" // Built-in hooks from @elizaos/core\n\t| \"managed\" // User-installed hooks (~/.elizaos/hooks/)\n\t| \"workspace\" // Project-local hooks (./hooks/)\n\t| \"plugin\" // Plugin-registered hooks\n\t| \"runtime\"; // Programmatic registration via API\n\n/**\n * Hook priority. Higher values run first. Default is 0.\n * Hooks with the same priority run in FIFO order (registration time).\n */\nexport type HookPriority = number;\n\n/**\n * Default hook priority (FIFO ordering)\n */\nexport const DEFAULT_HOOK_PRIORITY: HookPriority = 0;\n\n// ============================================================================\n// Hook Requirements (Eligibility)\n// ============================================================================\n\n/**\n * Specifies requirements that must be met for a hook to be eligible.\n * If any requirement is not met, the hook will not be triggered.\n */\nexport interface HookRequirements {\n\t/** Operating systems where this hook is valid (e.g., [\"darwin\", \"linux\"]) */\n\tos?: string[];\n\t/** Binary executables that must be available in PATH (all required) */\n\tbins?: string[];\n\t/** Binary executables where at least one must be available */\n\tanyBins?: string[];\n\t/** Environment variables that must be set */\n\tenv?: string[];\n\t/** Configuration paths that must be truthy (e.g., [\"workspace.dir\"]) */\n\tconfig?: string[];\n}\n\n/**\n * Result of an eligibility check for a hook.\n */\nexport interface HookEligibilityResult {\n\t/** Whether the hook is eligible to run */\n\teligible: boolean;\n\t/** Reasons why the hook is not eligible (if applicable) */\n\treasons?: string[];\n}\n\n// ============================================================================\n// Hook Metadata and Registration\n// ============================================================================\n\n/**\n * Metadata describing a registered hook.\n */\nexport interface HookMetadata {\n\t/** Display name of the hook */\n\tname: string;\n\t/** Human-readable description */\n\tdescription?: string;\n\t/** Source/origin of the hook */\n\tsource: HookSource;\n\t/** Plugin ID if registered by a plugin */\n\tpluginId?: string;\n\t/** Event types this hook listens to */\n\tevents: EventType[];\n\t/** Execution priority (higher runs first) */\n\tpriority: HookPriority;\n\t/** Whether the hook is currently enabled */\n\tenabled: boolean;\n\t/** If true, bypass eligibility checks (except explicit disable) */\n\talways?: boolean;\n\t/** Requirements for this hook to be eligible */\n\trequires?: HookRequirements;\n}\n\n/**\n * Handler function for hook events.\n * Receives the event payload and can modify it (for modifying hooks).\n */\nexport type HookHandler<T extends EventPayload = EventPayload> = (\n\tpayload: T,\n) => Promise<void> | void;\n\n/**\n * A registered hook with its handler and metadata.\n */\nexport interface HookRegistration {\n\t/** Unique identifier for this registration */\n\tid: string;\n\t/** Hook metadata */\n\tmetadata: HookMetadata;\n\t/** The handler function */\n\thandler: HookHandler;\n\t/** Unix timestamp when this hook was registered */\n\tregisteredAt: number;\n}\n\n/**\n * Options for registering a hook.\n */\nexport interface HookRegistrationOptions {\n\t/** Display name of the hook */\n\tname: string;\n\t/** Human-readable description */\n\tdescription?: string;\n\t/** Source/origin of the hook (defaults to \"runtime\") */\n\tsource?: HookSource;\n\t/** Plugin ID if registered by a plugin */\n\tpluginId?: string;\n\t/** Execution priority (defaults to 0, higher runs first) */\n\tpriority?: HookPriority;\n\t/** If true, bypass eligibility checks */\n\talways?: boolean;\n\t/** Requirements for this hook to be eligible */\n\trequires?: HookRequirements;\n}\n\n// ============================================================================\n// Hook Snapshot (Introspection)\n// ============================================================================\n\n/**\n * Summary of a registered hook for introspection.\n */\nexport interface HookSummary {\n\t/** Hook name */\n\tname: string;\n\t/** Event types this hook listens to */\n\tevents: EventType[];\n\t/** Source/origin of the hook */\n\tsource: HookSource;\n\t/** Whether the hook is enabled */\n\tenabled: boolean;\n\t/** Plugin ID if from a plugin */\n\tpluginId?: string;\n\t/** Priority */\n\tpriority: HookPriority;\n}\n\n/**\n * Snapshot of all registered hooks at a point in time.\n */\nexport interface HookSnapshot {\n\t/** List of hook summaries */\n\thooks: HookSummary[];\n\t/** Snapshot version (increments on each change) */\n\tversion: number;\n\t/** Timestamp when snapshot was taken */\n\ttimestamp: number;\n}\n\n// ============================================================================\n// Directory-based Hook Loading\n// ============================================================================\n\n/**\n * Parsed frontmatter from a HOOK.md file.\n */\nexport interface HookFrontmatter {\n\t/** Hook name */\n\tname?: string;\n\t/** Hook description */\n\tdescription?: string;\n\t/** Events this hook handles */\n\tevents?: string[];\n\t/** Export name in handler module (default: \"default\") */\n\texport?: string;\n\t/** Operating systems supported */\n\tos?: string[];\n\t/** If true, bypass eligibility checks */\n\talways?: boolean;\n\t/** Requirements */\n\trequires?: {\n\t\tbins?: string[];\n\t\tanyBins?: string[];\n\t\tenv?: string[];\n\t\tconfig?: string[];\n\t};\n}\n\n/**\n * A hook discovered from a directory.\n */\nexport interface DiscoveredHook {\n\t/** Hook name */\n\tname: string;\n\t/** Hook description */\n\tdescription: string;\n\t/** Source type */\n\tsource: HookSource;\n\t/** Plugin ID if from plugin */\n\tpluginId?: string;\n\t/** Path to HOOK.md */\n\tfilePath: string;\n\t/** Base directory containing the hook */\n\tbaseDir: string;\n\t/** Path to handler module (handler.ts/js) */\n\thandlerPath: string;\n\t/** Parsed frontmatter */\n\tfrontmatter: HookFrontmatter;\n}\n\n/**\n * Result of loading hooks from a directory.\n */\nexport interface HookLoadResult {\n\t/** Hooks that were successfully loaded and registered */\n\tloaded: string[];\n\t/** Hooks that were skipped (eligibility, disabled, etc.) */\n\tskipped: Array<{ name: string; reason: string }>;\n\t/** Errors encountered during loading */\n\terrors: Array<{ name: string; error: string }>;\n}\n\n// ============================================================================\n// Hook Service Interface\n// ============================================================================\n\n/**\n * The HookService provides a unified interface for registering, managing,\n * and executing hooks across the Eliza agent runtime.\n *\n * Hooks are event-driven handlers that can respond to various lifecycle\n * events in the agent system. The service integrates with the runtime's\n * event system to dispatch hook handlers when events are emitted.\n *\n * @example\n * ```typescript\n * // Get the hook service\n * const hookService = runtime.getService<IHookService>(ServiceType.HOOKS);\n *\n * // Register a hook programmatically\n * const hookId = hookService.register(\n * [EventType.HOOK_COMMAND_NEW],\n * async (payload) => {\n * payload.messages.push(\"Session started!\");\n * },\n * { name: \"welcome-hook\", description: \"Welcomes users on new session\" }\n * );\n *\n * // Load hooks from a directory\n * const result = await hookService.registerFromDirectory(\n * \"./hooks\",\n * \"workspace\"\n * );\n *\n * // Introspect registered hooks\n * const snapshot = hookService.getSnapshot();\n * console.log(`${snapshot.hooks.length} hooks registered`);\n * ```\n */\nexport interface IHookService extends Service {\n\t// ========================================================================\n\t// Registration\n\t// ========================================================================\n\n\t/**\n\t * Register a hook handler for one or more event types.\n\t *\n\t * @param events - Event type(s) to listen for\n\t * @param handler - Handler function to call when event fires\n\t * @param options - Registration options (name, priority, etc.)\n\t * @returns Unique hook registration ID\n\t */\n\tregister(\n\t\tevents: EventType | EventType[],\n\t\thandler: HookHandler,\n\t\toptions: HookRegistrationOptions,\n\t): string;\n\n\t/**\n\t * Unregister a previously registered hook.\n\t *\n\t * @param hookId - The hook registration ID returned from register()\n\t * @returns true if hook was found and removed, false otherwise\n\t */\n\tunregister(hookId: string): boolean;\n\n\t/**\n\t * Load and register hooks from a directory.\n\t *\n\t * Scans the directory for subdirectories containing HOOK.md files,\n\t * validates eligibility, and registers eligible hooks.\n\t *\n\t * @param dir - Directory path to scan for hooks\n\t * @param source - Source type for these hooks\n\t * @param options - Additional options (plugin ID, etc.)\n\t * @returns Result of the loading operation\n\t */\n\tregisterFromDirectory(\n\t\tdir: string,\n\t\tsource: HookSource,\n\t\toptions?: { pluginId?: string },\n\t): Promise<HookLoadResult>;\n\n\t// ========================================================================\n\t// Introspection\n\t// ========================================================================\n\n\t/**\n\t * Get a snapshot of all registered hooks.\n\t *\n\t * @returns Snapshot with hook summaries and metadata\n\t */\n\tgetSnapshot(): HookSnapshot;\n\n\t/**\n\t * Get all hooks registered for a specific event type.\n\t *\n\t * @param event - Event type to query\n\t * @returns Array of hook registrations for that event\n\t */\n\tgetHooksByEvent(event: EventType): HookRegistration[];\n\n\t/**\n\t * Get a specific hook registration by ID.\n\t *\n\t * @param hookId - Hook registration ID\n\t * @returns The registration or undefined if not found\n\t */\n\tgetHook(hookId: string): HookRegistration | undefined;\n\n\t/**\n\t * Get all registered hooks.\n\t *\n\t * @returns Array of all hook registrations\n\t */\n\tgetAllHooks(): HookRegistration[];\n\n\t// ========================================================================\n\t// Configuration\n\t// ========================================================================\n\n\t/**\n\t * Enable or disable a hook.\n\t *\n\t * @param hookId - Hook registration ID\n\t * @param enabled - Whether the hook should be enabled\n\t */\n\tsetEnabled(hookId: string, enabled: boolean): void;\n\n\t/**\n\t * Update a hook's priority.\n\t *\n\t * @param hookId - Hook registration ID\n\t * @param priority - New priority value\n\t */\n\tsetPriority(hookId: string, priority: HookPriority): void;\n\n\t// ========================================================================\n\t// Eligibility\n\t// ========================================================================\n\n\t/**\n\t * Check if a hook is eligible to run based on its requirements.\n\t *\n\t * @param hookId - Hook registration ID\n\t * @returns Eligibility result with reasons if not eligible\n\t */\n\tcheckEligibility(hookId: string): HookEligibilityResult;\n\n\t/**\n\t * Check if a hook meets specific requirements.\n\t *\n\t * @param requirements - Requirements to check\n\t * @param config - Optional configuration to check against\n\t * @returns Eligibility result\n\t */\n\tcheckRequirements(\n\t\trequirements: HookRequirements,\n\t\tconfig?: Record<string, unknown>,\n\t): HookEligibilityResult;\n}\n\n// ============================================================================\n// Legacy Compatibility Types (for Otto migration)\n// ============================================================================\n\n/**\n * Legacy hook event type mapping for Otto compatibility.\n * Maps old event strings to new EventType enum values.\n */\nexport const LEGACY_EVENT_MAP: Record<string, EventType> = {\n\t\"command:new\": EventType.HOOK_COMMAND_NEW,\n\t\"command:reset\": EventType.HOOK_COMMAND_RESET,\n\t\"command:stop\": EventType.HOOK_COMMAND_STOP,\n\t\"session:start\": EventType.HOOK_SESSION_START,\n\t\"session:end\": EventType.HOOK_SESSION_END,\n\t\"agent:bootstrap\": EventType.HOOK_AGENT_BOOTSTRAP,\n\t\"gateway:startup\": EventType.HOOK_GATEWAY_START,\n\t\"gateway:stop\": EventType.HOOK_GATEWAY_STOP,\n\t// Plugin lifecycle hooks\n\tbefore_agent_start: EventType.HOOK_AGENT_START,\n\tagent_end: EventType.HOOK_AGENT_END,\n\tbefore_compaction: EventType.HOOK_COMPACTION_BEFORE,\n\tafter_compaction: EventType.HOOK_COMPACTION_AFTER,\n\tmessage_sending: EventType.HOOK_MESSAGE_SENDING,\n\tbefore_tool_call: EventType.HOOK_TOOL_BEFORE,\n\tafter_tool_call: EventType.HOOK_TOOL_AFTER,\n\ttool_result_persist: EventType.HOOK_TOOL_PERSIST,\n\tsession_start: EventType.HOOK_SESSION_START,\n\tsession_end: EventType.HOOK_SESSION_END,\n\tgateway_start: EventType.HOOK_GATEWAY_START,\n\tgateway_stop: EventType.HOOK_GATEWAY_STOP,\n};\n\n/**\n * Maps legacy event type strings to the canonical type.\n * Handles both old format (\"command:new\") and new format (\"HOOK_COMMAND_NEW\").\n *\n * @param legacyEvent - Legacy event string\n * @returns The canonical EventType or undefined if not mapped\n */\nexport function mapLegacyEvent(legacyEvent: string): EventType | undefined {\n\t// Check if it's already a valid EventType\n\tif (Object.values(EventType).includes(legacyEvent as EventType)) {\n\t\treturn legacyEvent as EventType;\n\t}\n\t// Check legacy mapping\n\treturn LEGACY_EVENT_MAP[legacyEvent];\n}\n\n/**\n * Maps an array of legacy event strings to EventType values.\n * Filters out any events that cannot be mapped.\n *\n * @param legacyEvents - Array of legacy event strings\n * @returns Array of mapped EventType values\n */\nexport function mapLegacyEvents(legacyEvents: string[]): EventType[] {\n\tconst mapped: EventType[] = [];\n\tfor (const event of legacyEvents) {\n\t\tconst mappedEvent = mapLegacyEvent(event);\n\t\tif (mappedEvent) {\n\t\t\tmapped.push(mappedEvent);\n\t\t}\n\t}\n\treturn mapped;\n}\n",
338
- "import type { Content, MetadataValue, UUID } from \"./primitives\";\nimport type {\n\tBaseMetadata as ProtoBaseMetadata,\n\tCustomMetadata as ProtoCustomMetadata,\n\tDescriptionMetadata as ProtoDescriptionMetadata,\n\tDocumentMetadata as ProtoDocumentMetadata,\n\tFragmentMetadata as ProtoFragmentMetadata,\n\tMemory as ProtoMemory,\n\tMemoryMetadata as ProtoMemoryMetadataType,\n\tMessageMetadata as ProtoMessageMetadata,\n} from \"./proto.js\";\n\n/**\n * Memory type enumeration for built-in memory types\n */\nexport type MemoryTypeAlias = string;\n\n/**\n * Enumerates the built-in types of memories that can be stored and retrieved.\n * - `DOCUMENT`: Represents a whole document or a large piece of text.\n * - `FRAGMENT`: A chunk or segment of a `DOCUMENT`, often created for embedding and search.\n * - `MESSAGE`: A conversational message, typically from a user or the agent.\n * - `DESCRIPTION`: A descriptive piece of information, perhaps about an entity or concept.\n * - `CUSTOM`: For any other type of memory not covered by the built-in types.\n * This enum is used in `MemoryMetadata` to categorize memories and influences how they are processed or queried.\n */\nexport const MemoryType = {\n\tDOCUMENT: \"document\",\n\tFRAGMENT: \"fragment\",\n\tMESSAGE: \"message\",\n\tDESCRIPTION: \"description\",\n\tCUSTOM: \"custom\",\n} as const;\n\nexport type MemoryType = (typeof MemoryType)[keyof typeof MemoryType];\n/**\n * Defines the scope of a memory, indicating its visibility and accessibility.\n * - `shared`: The memory is accessible to multiple entities or across different contexts (e.g., a public fact).\n * - `private`: The memory is specific to a single entity or a private context (e.g., a user's personal preference).\n * - `room`: The memory is scoped to a specific room or channel.\n * This is used in `MemoryMetadata` to control how memories are stored and retrieved based on context.\n */\nexport type MemoryScope = \"shared\" | \"private\" | \"room\";\n\n/**\n * Base interface for all memory metadata types.\n * It includes common properties for all memories, such as:\n * - `type`: The kind of memory (e.g., `MemoryType.MESSAGE`, `MemoryType.DOCUMENT`).\n * - `source`: An optional string indicating the origin of the memory (e.g., 'discord', 'user_input').\n * - `sourceId`: An optional UUID linking to a source entity or object.\n * - `scope`: The visibility scope of the memory (`shared`, `private`, or `room`).\n * - `timestamp`: An optional numerical timestamp (e.g., milliseconds since epoch) of when the memory was created or relevant.\n * - `tags`: Optional array of strings for categorizing or filtering memories.\n * Specific metadata types like `DocumentMetadata` or `MessageMetadata` extend this base.\n */\nexport interface BaseMetadata\n\textends Omit<\n\t\tProtoBaseMetadata,\n\t\t\"$typeName\" | \"$unknown\" | \"type\" | \"scope\" | \"timestamp\"\n\t> {\n\ttype: MemoryTypeAlias;\n\tscope?: MemoryScope;\n\ttimestamp?: number;\n}\n\nexport interface DocumentMetadata\n\textends Omit<ProtoDocumentMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"document\";\n}\n\nexport interface FragmentMetadata\n\textends Omit<ProtoFragmentMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\tdocumentId: UUID;\n\tposition: number;\n\ttype?: \"fragment\";\n}\n\n/**\n * Chat type for message context.\n */\nexport type MessageChatType =\n\t| \"dm\"\n\t| \"private\"\n\t| \"direct\"\n\t| \"group\"\n\t| \"supergroup\"\n\t| \"channel\"\n\t| \"thread\"\n\t| \"forum\"\n\t| string;\n\n/**\n * Sender identity information.\n */\nexport interface SenderIdentity {\n\t/** Platform-specific sender ID */\n\tid?: string;\n\t/** Display name */\n\tname?: string;\n\t/** Username (without @ prefix) */\n\tusername?: string;\n\t/** User tag (e.g., user#1234 for Discord) */\n\ttag?: string;\n\t/** E.164 phone number */\n\te164?: string;\n}\n\n/**\n * Thread context for threaded conversations.\n */\nexport interface ThreadContext {\n\t/** Thread/topic ID */\n\tid?: string | number;\n\t/** Thread label/name */\n\tlabel?: string;\n\t/** Whether this is a forum topic */\n\tisForum?: boolean;\n\t/** Thread starter message body */\n\tstarterBody?: string;\n}\n\n/**\n * Group context for group chats.\n */\nexport interface GroupContext {\n\t/** Group ID */\n\tid?: string;\n\t/** Group name/subject */\n\tname?: string;\n\t/** Channel within the group (e.g., #general) */\n\tchannel?: string;\n\t/** Workspace/space name */\n\tspace?: string;\n\t/** Group members (comma-separated or count) */\n\tmembers?: string;\n\t/** Group-specific system prompt */\n\tsystemPrompt?: string;\n}\n\n/**\n * Reply context for reply messages.\n */\nexport interface ReplyContext {\n\t/** ID of message being replied to (also in Content.inReplyTo) */\n\tid?: string;\n\t/** Full platform-specific ID */\n\tidFull?: string;\n\t/** Body of message being replied to */\n\tbody?: string;\n\t/** Sender of message being replied to */\n\tsender?: string;\n\t/** Whether this is a quote reply */\n\tisQuote?: boolean;\n}\n\n/**\n * Forwarded message context.\n */\nexport interface ForwardedContext {\n\t/** Original sender name */\n\tfromName?: string;\n\t/** Original sender ID */\n\tfromId?: string;\n\t/** Original sender username */\n\tfromUsername?: string;\n\t/** Original sender type */\n\tfromType?: string;\n\t/** Original chat/channel title */\n\tfromTitle?: string;\n\t/** Original signature */\n\tfromSignature?: string;\n\t/** Original chat type */\n\tfromChatType?: string;\n\t/** Original message ID */\n\toriginalMessageId?: number;\n\t/** Forward date timestamp */\n\tdate?: number;\n}\n\n/**\n * Delivery context for message routing.\n */\nexport interface DeliveryContext {\n\t/** Channel/provider for delivery */\n\tchannel?: string;\n\t/** Destination address */\n\tto?: string;\n\t/** Account ID for multi-account channels */\n\taccountId?: string;\n\t/** Thread ID for threaded replies */\n\tthreadId?: string | number;\n}\n\n/**\n * Session origin information.\n */\nexport interface SessionOrigin {\n\t/** Human-readable label */\n\tlabel?: string;\n\t/** Provider name */\n\tprovider?: string;\n\t/** Surface type */\n\tsurface?: string;\n\t/** Chat type */\n\tchatType?: MessageChatType;\n\t/** Original sender */\n\tfrom?: string;\n\t/** Original recipient */\n\tto?: string;\n\t/** Account ID */\n\taccountId?: string;\n\t/** Thread ID */\n\tthreadId?: string | number;\n}\n\n// =========================================================================\n// Session Context - First-class session support for filtering and state\n// =========================================================================\n\n/**\n * Model override configuration for a session.\n */\nexport interface SessionModelOverride {\n\t/** Provider name override (e.g., \"anthropic\", \"openai\") */\n\tprovider?: string;\n\t/** Model name override (e.g., \"claude-3-opus\", \"gpt-5\") */\n\tmodel?: string;\n\t/** Authentication profile override */\n\tauthProfile?: string;\n\t/** Source of auth profile override */\n\tauthProfileSource?: \"auto\" | \"user\";\n}\n\n/**\n * Token usage tracking for a session.\n */\nexport interface SessionUsage {\n\t/** Total input tokens consumed */\n\tinputTokens: number;\n\t/** Total output tokens generated */\n\toutputTokens: number;\n\t/** Combined total tokens */\n\ttotalTokens: number;\n\t/** Number of context compactions performed */\n\tcompactionCount: number;\n}\n\n/**\n * Skill snapshot for a session.\n */\nexport interface SessionSkillEntry {\n\t/** Skill name */\n\tname: string;\n\t/** Primary environment for the skill */\n\tprimaryEnv?: string;\n}\n\n/**\n * Skills configuration snapshot for a session.\n */\nexport interface SessionSkillsSnapshot {\n\t/** Prompt text for skills */\n\tprompt: string;\n\t/** List of available skills */\n\tskills: SessionSkillEntry[];\n}\n\n/**\n * Session context providing first-class session state access.\n * This enables filtering memories by session and accessing session configuration\n * from within the Eliza runtime pipeline (providers, evaluators, actions).\n */\nexport interface SessionContext {\n\t/** Session ID (UUID) - used for transcript files and filtering */\n\tsessionId: string;\n\n\t/** Session key for conversation routing (e.g., \"agent:123:telegram:+1234567890\") */\n\tsessionKey: string;\n\n\t/** Parent session key if this session was spawned from another */\n\tparentSessionKey?: string;\n\n\t/** Whether this is a newly created session */\n\tisNewSession: boolean;\n\n\t/** Timestamp of last session activity */\n\tupdatedAt: number;\n\n\t/** Human-readable session label */\n\tlabel?: string;\n\n\t/** Model and provider overrides for this session */\n\tmodelOverride?: SessionModelOverride;\n\n\t/** Thinking level setting (\"low\", \"medium\", \"high\") */\n\tthinkingLevel?: string;\n\n\t/** Verbose level setting (\"on\", \"off\") */\n\tverboseLevel?: string;\n\n\t/** Reasoning level setting */\n\treasoningLevel?: string;\n\n\t/** Send policy for outbound messages */\n\tsendPolicy?: \"allow\" | \"deny\";\n\n\t/** Token usage tracking */\n\tusage?: SessionUsage;\n\n\t/** Skills snapshot for this session */\n\tskillsSnapshot?: SessionSkillsSnapshot;\n\n\t/** Chat type for the session */\n\tchatType?: MessageChatType;\n\n\t/** Channel identifier */\n\tchannel?: string;\n\n\t/** Group ID if in a group context */\n\tgroupId?: string;\n\n\t/** Group channel name */\n\tgroupChannel?: string;\n\n\t/** Workspace/space identifier */\n\tspace?: string;\n\n\t/** Session spawned by this key (for sandbox scoping) */\n\tspawnedBy?: string;\n\n\t/** Response usage display mode */\n\tresponseUsage?: \"on\" | \"off\" | \"tokens\" | \"full\";\n\n\t/** Execution host configuration */\n\texecHost?: string;\n\n\t/** Execution security mode */\n\texecSecurity?: string;\n\n\t/** Group activation mode */\n\tgroupActivation?: \"mention\" | \"always\";\n}\n\nexport interface MessageMetadata\n\textends Omit<ProtoMessageMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"message\";\n\ttrajectoryStepId?: string;\n\tbenchmarkContext?: string;\n\n\t// =========================================================================\n\t// Message Context - per-message routing and identity information\n\t// =========================================================================\n\n\t/** Session key for conversation routing */\n\tsessionKey?: string;\n\t/** Parent session key (for spawned/subagent sessions) */\n\tparentSessionKey?: string;\n\n\t/** Sender identity */\n\tsender?: SenderIdentity;\n\n\t/** Platform/provider name (e.g., \"telegram\", \"discord\", \"slack\") */\n\tprovider?: string;\n\t/** Chat type */\n\tchatType?: MessageChatType;\n\t/** Account ID for multi-account channels */\n\taccountId?: string;\n\n\t/** Thread context */\n\tthread?: ThreadContext;\n\n\t/** Group context */\n\tgroup?: GroupContext;\n\n\t/** Reply context (supplements Content.inReplyTo) */\n\treply?: ReplyContext;\n\n\t/** Forwarded message context */\n\tforwarded?: ForwardedContext;\n\n\t/** Delivery context for routing responses */\n\tdelivery?: DeliveryContext;\n\n\t/** Session origin (where the conversation started) */\n\torigin?: SessionOrigin;\n\n\t/** Full session context for session-aware processing */\n\tsession?: SessionContext;\n\n\t/** Whether the agent was mentioned */\n\twasMentioned?: boolean;\n\n\t// =========================================================================\n\t// Message IDs - for multi-message batches and platform tracking\n\t// =========================================================================\n\n\t/** Full platform-specific message ID */\n\tmessageIdFull?: string;\n\t/** Multiple message IDs (for batched messages) */\n\tmessageIds?: string[];\n\t/** First message ID in a batch */\n\tmessageIdFirst?: string;\n\t/** Last message ID in a batch */\n\tmessageIdLast?: string;\n\n\t// =========================================================================\n\t// Platform-specific nested metadata\n\t// =========================================================================\n\n\t/** Telegram-specific metadata */\n\ttelegram?: {\n\t\tchatId?: string | number;\n\t\tmessageId?: string;\n\t\tthreadId?: string | number;\n\t};\n\n\t/** Discord-specific metadata */\n\tdiscord?: {\n\t\tguildId?: string;\n\t\tchannelId?: string;\n\t\tmessageId?: string;\n\t};\n\n\t/** Slack-specific metadata */\n\tslack?: {\n\t\tteamId?: string;\n\t\tchannelId?: string;\n\t\tmessageTs?: string;\n\t\tthreadTs?: string;\n\t};\n\n\t/** WhatsApp-specific metadata */\n\twhatsapp?: {\n\t\tphoneNumberId?: string;\n\t\tcontactId?: string;\n\t\tmessageId?: string;\n\t};\n\n\t/** Signal-specific metadata */\n\tsignal?: {\n\t\tgroupId?: string;\n\t\tsenderId?: string;\n\t\ttimestamp?: number;\n\t};\n\n\t// =========================================================================\n\t// Media and content processing\n\t// =========================================================================\n\n\t/** Sticker metadata (Telegram) */\n\tsticker?: {\n\t\temoji?: string;\n\t\tsetName?: string;\n\t\tfileId?: string;\n\t\tfileUniqueId?: string;\n\t\tdescription?: string;\n\t};\n\n\t/** Media transcription result */\n\ttranscript?: string;\n\n\t// =========================================================================\n\t// Command and gateway context\n\t// =========================================================================\n\n\t/** Command source type */\n\tcommandSource?: string;\n\t/** Command target session key */\n\tcommandTargetSessionKey?: string;\n\t/** Gateway client scopes */\n\tgatewayClientScopes?: string[];\n\t/** Untrusted user-provided context */\n\tuntrustedContext?: string[];\n\t/** Hook-injected messages */\n\thookMessages?: string[];\n}\n\nexport interface DescriptionMetadata\n\textends Omit<ProtoDescriptionMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"description\";\n}\n\n// MetadataValue is imported from primitives.ts\n\n/**\n * Custom metadata with typed dynamic properties\n */\nexport interface CustomMetadata\n\textends Omit<ProtoCustomMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"custom\";\n\t/** Custom metadata values - must be JSON-serializable */\n\t[key: string]: MetadataValue | MemoryTypeAlias | BaseMetadata | undefined;\n}\n\ninterface MemoryMetadataBase {\n\ttype?: MemoryTypeAlias;\n\tsource?: string;\n\tscope?: MemoryScope;\n\ttimestamp?: number;\n}\n\nexport type MemoryMetadata = (\n\t| DocumentMetadata\n\t| FragmentMetadata\n\t| MessageMetadata\n\t| DescriptionMetadata\n\t| CustomMetadata\n) &\n\tMemoryMetadataBase;\n\nexport type ProtoMemoryMetadata = ProtoMemoryMetadataType;\n\n/**\n * Represents a stored memory/message\n */\nexport interface Memory\n\textends Omit<\n\t\tProtoMemory,\n\t\t| \"$typeName\"\n\t\t| \"$unknown\"\n\t\t| \"id\"\n\t\t| \"createdAt\"\n\t\t| \"embedding\"\n\t\t| \"metadata\"\n\t\t| \"content\"\n\t> {\n\tid?: UUID;\n\tcreatedAt?: number;\n\tembedding?: number[];\n\tmetadata?: MemoryMetadata;\n\tcontent: Content;\n\n\t/**\n\t * Session ID for filtering and grouping memories by conversation session.\n\t * This is a first-class field to enable efficient querying by session.\n\t * Optional for backwards compatibility - memories without sessionId\n\t * will continue to work as before.\n\t *\n\t * Format: UUID string (e.g., \"550e8400-e29b-41d4-a716-446655440000\")\n\t */\n\tsessionId?: string;\n\n\t/**\n\t * Session key for routing and identification.\n\t * This is the full session key used for conversation routing.\n\t * Optional for backwards compatibility.\n\t *\n\t * Format: \"agent:<agentId>:<channel>:<destination>\" or similar patterns\n\t * Examples:\n\t * - \"agent:123:telegram:+14155551234\"\n\t * - \"agent:123:discord:channel:987654321\"\n\t * - \"cron:daily-summary\"\n\t */\n\tsessionKey?: string;\n}\n\n/**\n * Specialized memory type for messages with enhanced type checking\n */\nexport type MessageMemory = Memory;\n",
338
+ "import type { Content, MetadataValue, UUID } from \"./primitives\";\nimport type {\n\tBaseMetadata as ProtoBaseMetadata,\n\tCustomMetadata as ProtoCustomMetadata,\n\tDescriptionMetadata as ProtoDescriptionMetadata,\n\tDocumentMetadata as ProtoDocumentMetadata,\n\tFragmentMetadata as ProtoFragmentMetadata,\n\tMemory as ProtoMemory,\n\tMemoryMetadata as ProtoMemoryMetadataType,\n\tMessageMetadata as ProtoMessageMetadata,\n} from \"./proto.js\";\n\n/**\n * Memory type enumeration for built-in memory types\n */\nexport type MemoryTypeAlias = string;\n\n/**\n * Enumerates the built-in types of memories that can be stored and retrieved.\n * - `DOCUMENT`: Represents a whole document or a large piece of text.\n * - `FRAGMENT`: A chunk or segment of a `DOCUMENT`, often created for embedding and search.\n * - `MESSAGE`: A conversational message, typically from a user or the agent.\n * - `DESCRIPTION`: A descriptive piece of information, perhaps about an entity or concept.\n * - `CUSTOM`: For any other type of memory not covered by the built-in types.\n * This enum is used in `MemoryMetadata` to categorize memories and influences how they are processed or queried.\n */\nexport const MemoryType = {\n\tDOCUMENT: \"document\",\n\tFRAGMENT: \"fragment\",\n\tMESSAGE: \"message\",\n\tDESCRIPTION: \"description\",\n\tCUSTOM: \"custom\",\n} as const;\n\nexport type MemoryType = (typeof MemoryType)[keyof typeof MemoryType];\n/**\n * Defines the scope of a memory, indicating its visibility and accessibility.\n * - `shared`: The memory is accessible to multiple entities or across different contexts (e.g., a public fact).\n * - `private`: The memory is specific to a single entity or a private context (e.g., a user's personal preference).\n * - `room`: The memory is scoped to a specific room or channel.\n * This is used in `MemoryMetadata` to control how memories are stored and retrieved based on context.\n */\nexport type MemoryScope = \"shared\" | \"private\" | \"room\";\n\n/**\n * Base interface for all memory metadata types.\n * It includes common properties for all memories, such as:\n * - `type`: The kind of memory (e.g., `MemoryType.MESSAGE`, `MemoryType.DOCUMENT`).\n * - `source`: An optional string indicating the origin of the memory (e.g., 'discord', 'user_input').\n * - `sourceId`: An optional UUID linking to a source entity or object.\n * - `scope`: The visibility scope of the memory (`shared`, `private`, or `room`).\n * - `timestamp`: An optional numerical timestamp (e.g., milliseconds since epoch) of when the memory was created or relevant.\n * - `tags`: Optional array of strings for categorizing or filtering memories.\n * Specific metadata types like `DocumentMetadata` or `MessageMetadata` extend this base.\n */\nexport interface BaseMetadata\n\textends Omit<\n\t\tProtoBaseMetadata,\n\t\t\"$typeName\" | \"$unknown\" | \"type\" | \"scope\" | \"timestamp\"\n\t> {\n\ttype: MemoryTypeAlias;\n\tscope?: MemoryScope;\n\ttimestamp?: number;\n}\n\nexport interface DocumentMetadata\n\textends Omit<ProtoDocumentMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"document\";\n}\n\nexport interface FragmentMetadata\n\textends Omit<ProtoFragmentMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\tdocumentId: UUID;\n\tposition: number;\n\ttype?: \"fragment\";\n}\n\n/**\n * Chat type for message context.\n */\nexport type MessageChatType =\n\t| \"dm\"\n\t| \"private\"\n\t| \"direct\"\n\t| \"group\"\n\t| \"supergroup\"\n\t| \"channel\"\n\t| \"thread\"\n\t| \"forum\"\n\t| string;\n\n/**\n * Sender identity information.\n */\nexport interface SenderIdentity {\n\t/** Platform-specific sender ID */\n\tid?: string;\n\t/** Display name */\n\tname?: string;\n\t/** Username (without @ prefix) */\n\tusername?: string;\n\t/** User tag (e.g., user#1234 for Discord) */\n\ttag?: string;\n\t/** E.164 phone number */\n\te164?: string;\n}\n\n/**\n * Thread context for threaded conversations.\n */\nexport interface ThreadContext {\n\t/** Thread/topic ID */\n\tid?: string | number;\n\t/** Thread label/name */\n\tlabel?: string;\n\t/** Whether this is a forum topic */\n\tisForum?: boolean;\n\t/** Thread starter message body */\n\tstarterBody?: string;\n}\n\n/**\n * Group context for group chats.\n */\nexport interface GroupContext {\n\t/** Group ID */\n\tid?: string;\n\t/** Group name/subject */\n\tname?: string;\n\t/** Channel within the group (e.g., #general) */\n\tchannel?: string;\n\t/** Workspace/space name */\n\tspace?: string;\n\t/** Group members (comma-separated or count) */\n\tmembers?: string;\n\t/** Group-specific system prompt */\n\tsystemPrompt?: string;\n}\n\n/**\n * Reply context for reply messages.\n */\nexport interface ReplyContext {\n\t/** ID of message being replied to (also in Content.inReplyTo) */\n\tid?: string;\n\t/** Full platform-specific ID */\n\tidFull?: string;\n\t/** Body of message being replied to */\n\tbody?: string;\n\t/** Sender of message being replied to */\n\tsender?: string;\n\t/** Whether this is a quote reply */\n\tisQuote?: boolean;\n}\n\n/**\n * Forwarded message context.\n */\nexport interface ForwardedContext {\n\t/** Original sender name */\n\tfromName?: string;\n\t/** Original sender ID */\n\tfromId?: string;\n\t/** Original sender username */\n\tfromUsername?: string;\n\t/** Original sender type */\n\tfromType?: string;\n\t/** Original chat/channel title */\n\tfromTitle?: string;\n\t/** Original signature */\n\tfromSignature?: string;\n\t/** Original chat type */\n\tfromChatType?: string;\n\t/** Original message ID */\n\toriginalMessageId?: number;\n\t/** Forward date timestamp */\n\tdate?: number;\n}\n\n/**\n * Delivery context for message routing.\n */\nexport interface DeliveryContext {\n\t/** Channel/provider for delivery */\n\tchannel?: string;\n\t/** Destination address */\n\tto?: string;\n\t/** Account ID for multi-account channels */\n\taccountId?: string;\n\t/** Thread ID for threaded replies */\n\tthreadId?: string | number;\n}\n\n/**\n * Session origin information.\n */\nexport interface SessionOrigin {\n\t/** Human-readable label */\n\tlabel?: string;\n\t/** Provider name */\n\tprovider?: string;\n\t/** Surface type */\n\tsurface?: string;\n\t/** Chat type */\n\tchatType?: MessageChatType;\n\t/** Original sender */\n\tfrom?: string;\n\t/** Original recipient */\n\tto?: string;\n\t/** Account ID */\n\taccountId?: string;\n\t/** Thread ID */\n\tthreadId?: string | number;\n}\n\n// =========================================================================\n// Session Context - First-class session support for filtering and state\n// =========================================================================\n\n/**\n * Model override configuration for a session.\n */\nexport interface SessionModelOverride {\n\t/** Provider name override (e.g., \"anthropic\", \"openai\") */\n\tprovider?: string;\n\t/** Model name override (e.g., \"claude-3-opus\", \"gpt-5\") */\n\tmodel?: string;\n\t/** Authentication profile override */\n\tauthProfile?: string;\n\t/** Source of auth profile override */\n\tauthProfileSource?: \"auto\" | \"user\";\n}\n\n/**\n * Token usage tracking for a session.\n */\nexport interface SessionUsage {\n\t/** Total input tokens consumed */\n\tinputTokens: number;\n\t/** Total output tokens generated */\n\toutputTokens: number;\n\t/** Combined total tokens */\n\ttotalTokens: number;\n\t/** Number of context compactions performed */\n\tcompactionCount: number;\n}\n\n/**\n * Skill snapshot for a session.\n */\nexport interface SessionSkillEntry {\n\t/** Skill name */\n\tname: string;\n\t/** Primary environment for the skill */\n\tprimaryEnv?: string;\n}\n\n/**\n * Skills configuration snapshot for a session.\n */\nexport interface SessionSkillsSnapshot {\n\t/** Prompt text for skills */\n\tprompt: string;\n\t/** List of available skills */\n\tskills: SessionSkillEntry[];\n}\n\n/**\n * Session context providing first-class session state access.\n * This enables filtering memories by session and accessing session configuration\n * from within the Eliza runtime pipeline (providers, evaluators, actions).\n */\nexport interface SessionContext {\n\t/** Session ID (UUID) - used for transcript files and filtering */\n\tsessionId: string;\n\n\t/** Session key for conversation routing (e.g., \"agent:123:telegram:+1234567890\") */\n\tsessionKey: string;\n\n\t/** Parent session key if this session was spawned from another */\n\tparentSessionKey?: string;\n\n\t/** Whether this is a newly created session */\n\tisNewSession: boolean;\n\n\t/** Timestamp of last session activity */\n\tupdatedAt: number;\n\n\t/** Human-readable session label */\n\tlabel?: string;\n\n\t/** Model and provider overrides for this session */\n\tmodelOverride?: SessionModelOverride;\n\n\t/** Thinking level setting (\"low\", \"medium\", \"high\") */\n\tthinkingLevel?: string;\n\n\t/** Verbose level setting (\"on\", \"off\") */\n\tverboseLevel?: string;\n\n\t/** Reasoning level setting */\n\treasoningLevel?: string;\n\n\t/** Send policy for outbound messages */\n\tsendPolicy?: \"allow\" | \"deny\";\n\n\t/** Token usage tracking */\n\tusage?: SessionUsage;\n\n\t/** Skills snapshot for this session */\n\tskillsSnapshot?: SessionSkillsSnapshot;\n\n\t/** Chat type for the session */\n\tchatType?: MessageChatType;\n\n\t/** Channel identifier */\n\tchannel?: string;\n\n\t/** Group ID if in a group context */\n\tgroupId?: string;\n\n\t/** Group channel name */\n\tgroupChannel?: string;\n\n\t/** Workspace/space identifier */\n\tspace?: string;\n\n\t/** Session spawned by this key (for sandbox scoping) */\n\tspawnedBy?: string;\n\n\t/** Response usage display mode */\n\tresponseUsage?: \"on\" | \"off\" | \"tokens\" | \"full\";\n\n\t/** Execution host configuration */\n\texecHost?: string;\n\n\t/** Execution security mode */\n\texecSecurity?: string;\n\n\t/** Group activation mode */\n\tgroupActivation?: \"mention\" | \"always\";\n}\n\nexport interface MessageMetadata\n\textends Omit<ProtoMessageMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"message\";\n\ttrajectoryStepId?: string;\n\tbenchmarkContext?: string;\n\n\t// =========================================================================\n\t// Message Context - per-message routing and identity information\n\t// =========================================================================\n\n\t/** Session key for conversation routing */\n\tsessionKey?: string;\n\t/** Parent session key (for spawned/subagent sessions) */\n\tparentSessionKey?: string;\n\n\t/** Sender identity */\n\tsender?: SenderIdentity;\n\n\t/** Platform/provider name (e.g., \"telegram\", \"discord\", \"slack\") */\n\tprovider?: string;\n\t/** Chat type */\n\tchatType?: MessageChatType;\n\t/** Account ID for multi-account channels */\n\taccountId?: string;\n\n\t/** Thread context */\n\tthread?: ThreadContext;\n\n\t/** Group context */\n\tgroup?: GroupContext;\n\n\t/** Reply context (supplements Content.inReplyTo) */\n\treply?: ReplyContext;\n\n\t/** Forwarded message context */\n\tforwarded?: ForwardedContext;\n\n\t/** Delivery context for routing responses */\n\tdelivery?: DeliveryContext;\n\n\t/** Session origin (where the conversation started) */\n\torigin?: SessionOrigin;\n\n\t/** Full session context for session-aware processing */\n\tsession?: SessionContext;\n\n\t/** Whether the agent was mentioned */\n\twasMentioned?: boolean;\n\n\t// =========================================================================\n\t// Message IDs - for multi-message batches and platform tracking\n\t// =========================================================================\n\n\t/** Full platform-specific message ID */\n\tmessageIdFull?: string;\n\t/** Multiple message IDs (for batched messages) */\n\tmessageIds?: string[];\n\t/** First message ID in a batch */\n\tmessageIdFirst?: string;\n\t/** Last message ID in a batch */\n\tmessageIdLast?: string;\n\n\t// =========================================================================\n\t// Platform-specific nested metadata\n\t// =========================================================================\n\n\t/** Telegram-specific metadata */\n\ttelegram?: {\n\t\tchatId?: string | number;\n\t\tmessageId?: string;\n\t\tthreadId?: string | number;\n\t};\n\n\t/** Discord-specific metadata */\n\tdiscord?: {\n\t\tguildId?: string;\n\t\tchannelId?: string;\n\t\tmessageId?: string;\n\t};\n\n\t/** Slack-specific metadata */\n\tslack?: {\n\t\tteamId?: string;\n\t\tchannelId?: string;\n\t\tmessageTs?: string;\n\t\tthreadTs?: string;\n\t};\n\n\t/** WhatsApp-specific metadata */\n\twhatsapp?: {\n\t\tphoneNumberId?: string;\n\t\tcontactId?: string;\n\t\tmessageId?: string;\n\t};\n\n\t/** Signal-specific metadata */\n\tsignal?: {\n\t\tgroupId?: string;\n\t\tsenderId?: string;\n\t\ttimestamp?: number;\n\t};\n\n\t// =========================================================================\n\t// Media and content processing\n\t// =========================================================================\n\n\t/** Sticker metadata (Telegram) */\n\tsticker?: {\n\t\temoji?: string;\n\t\tsetName?: string;\n\t\tfileId?: string;\n\t\tfileUniqueId?: string;\n\t\tdescription?: string;\n\t};\n\n\t/** Media transcription result */\n\ttranscript?: string;\n\n\t// =========================================================================\n\t// Command and gateway context\n\t// =========================================================================\n\n\t/** Command source type */\n\tcommandSource?: string;\n\t/** Command target session key */\n\tcommandTargetSessionKey?: string;\n\t/** Gateway client scopes */\n\tgatewayClientScopes?: string[];\n\t/** Untrusted user-provided context */\n\tuntrustedContext?: string[];\n\t/** Hook-injected messages */\n\thookMessages?: string[];\n\n\t// =========================================================================\n\t// Entity identity - flat fields commonly used by platform plugins\n\t// =========================================================================\n\n\t/** Display name of the message sender entity */\n\tentityName?: string;\n\t/** Username of the message sender entity */\n\tentityUserName?: string;\n\t/** Whether the sender is a bot */\n\tfromBot?: boolean;\n\t/** Platform-specific sender ID (e.g., Telegram chat.id) */\n\tfromId?: string | number;\n\t/** Source entity UUID */\n\tsourceId?: string;\n\n\t/** Allow platform-specific extensions */\n\t[key: string]: unknown;\n}\n\nexport interface DescriptionMetadata\n\textends Omit<ProtoDescriptionMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"description\";\n}\n\n// MetadataValue is imported from primitives.ts\n\n/**\n * Custom metadata with typed dynamic properties\n */\nexport interface CustomMetadata\n\textends Omit<ProtoCustomMetadata, \"$typeName\" | \"$unknown\" | \"base\"> {\n\tbase?: BaseMetadata;\n\ttype?: \"custom\";\n\t/** Custom metadata values - must be JSON-serializable */\n\t[key: string]: MetadataValue | MemoryTypeAlias | BaseMetadata | undefined;\n}\n\ninterface MemoryMetadataBase {\n\ttype?: MemoryTypeAlias;\n\tsource?: string;\n\tscope?: MemoryScope;\n\ttimestamp?: number;\n}\n\nexport type MemoryMetadata = (\n\t| DocumentMetadata\n\t| FragmentMetadata\n\t| MessageMetadata\n\t| DescriptionMetadata\n\t| CustomMetadata\n) &\n\tMemoryMetadataBase;\n\nexport type ProtoMemoryMetadata = ProtoMemoryMetadataType;\n\n/**\n * Represents a stored memory/message\n */\nexport interface Memory\n\textends Omit<\n\t\tProtoMemory,\n\t\t| \"$typeName\"\n\t\t| \"$unknown\"\n\t\t| \"id\"\n\t\t| \"createdAt\"\n\t\t| \"embedding\"\n\t\t| \"metadata\"\n\t\t| \"content\"\n\t> {\n\tid?: UUID;\n\tcreatedAt?: number;\n\tembedding?: number[];\n\tmetadata?: MemoryMetadata;\n\tcontent: Content;\n\n\t/**\n\t * Session ID for filtering and grouping memories by conversation session.\n\t * This is a first-class field to enable efficient querying by session.\n\t * Optional for backwards compatibility - memories without sessionId\n\t * will continue to work as before.\n\t *\n\t * Format: UUID string (e.g., \"550e8400-e29b-41d4-a716-446655440000\")\n\t */\n\tsessionId?: string;\n\n\t/**\n\t * Session key for routing and identification.\n\t * This is the full session key used for conversation routing.\n\t * Optional for backwards compatibility.\n\t *\n\t * Format: \"agent:<agentId>:<channel>:<destination>\" or similar patterns\n\t * Examples:\n\t * - \"agent:123:telegram:+14155551234\"\n\t * - \"agent:123:discord:channel:987654321\"\n\t * - \"cron:daily-summary\"\n\t */\n\tsessionKey?: string;\n}\n\n/**\n * Specialized memory type for messages with enhanced type checking\n */\nexport type MessageMemory = Memory;\n",
339
339
  "import type { Memory } from \"./memory\";\nimport type { Content, UUID } from \"./primitives\";\nimport type {\n\tMessageResult as ProtoMessageResult,\n\tMessageStreamChunkPayload as ProtoMessageStreamChunkPayload,\n\tMessageStreamErrorPayload as ProtoMessageStreamErrorPayload,\n\tTargetInfo as ProtoTargetInfo,\n} from \"./proto.js\";\nimport type { IAgentRuntime } from \"./runtime\";\n\n/**\n * Information describing the target of a message.\n */\nexport interface TargetInfo extends ProtoTargetInfo {\n\troomId?: UUID;\n\tentityId?: UUID;\n}\n\n/**\n * Function signature for handlers responsible for sending messages to specific platforms.\n */\nexport type SendHandlerFunction = (\n\truntime: IAgentRuntime,\n\ttarget: TargetInfo,\n\tcontent: Content,\n) => Promise<void>;\n\nexport enum SOCKET_MESSAGE_TYPE {\n\tROOM_JOINING = 1,\n\tSEND_MESSAGE = 2,\n\tMESSAGE = 3,\n\tACK = 4,\n\tTHINKING = 5,\n\tCONTROL = 6,\n}\n\n/**\n * WebSocket/SSE event names for message streaming.\n * Used for real-time streaming of agent responses to clients.\n *\n * Event flow:\n * 1. First `messageStreamChunk` indicates stream start\n * 2. Multiple `messageStreamChunk` events with text chunks\n * 3. `messageBroadcast` event with complete message (indicates stream end)\n * 4. `messageStreamError` if an error occurs during streaming\n */\nexport const MESSAGE_STREAM_EVENT = {\n\t/** Text chunk during streaming. First chunk indicates stream start. */\n\tmessageStreamChunk: \"messageStreamChunk\",\n\t/** Error occurred during streaming */\n\tmessageStreamError: \"messageStreamError\",\n\t/** Complete message broadcast (existing event, indicates stream end) */\n\tmessageBroadcast: \"messageBroadcast\",\n} as const;\n\nexport type MessageStreamEventType =\n\t(typeof MESSAGE_STREAM_EVENT)[keyof typeof MESSAGE_STREAM_EVENT];\n\n/**\n * Payload for messageStreamChunk event\n * Uses camelCase for client-facing WebSocket events (JS convention)\n */\nexport interface MessageStreamChunkPayload\n\textends Omit<ProtoMessageStreamChunkPayload, \"messageId\" | \"agentId\"> {\n\tmessageId: UUID;\n\tagentId: UUID;\n}\n\n/**\n * Payload for messageStreamError event\n * Uses camelCase for client-facing WebSocket events (JS convention)\n */\nexport interface MessageStreamErrorPayload\n\textends Omit<ProtoMessageStreamErrorPayload, \"messageId\" | \"agentId\"> {\n\tmessageId: UUID;\n\tagentId: UUID;\n}\n\n/**\n * Control message actions that can be sent to the frontend\n */\nexport type ControlMessageAction = \"disable_input\" | \"enable_input\";\n\n/**\n * Payload for UI control messages\n */\nexport interface UIControlPayload {\n\t/** Action to perform */\n\taction: ControlMessageAction;\n\t/** Optional target element identifier */\n\ttarget?: string;\n\t/** Optional reason for the action */\n\treason?: string;\n\t/** Optional duration in milliseconds */\n\tduration?: number;\n}\n\n/**\n * Interface for control messages sent from the backend to the frontend\n * to manage UI state and interaction capabilities\n */\nexport interface ControlMessage {\n\t/** Message type identifier */\n\ttype: \"control\";\n\t/** Control message payload */\n\tpayload: UIControlPayload;\n\t/** Room ID to ensure signal is directed to the correct chat window */\n\troomId: UUID;\n}\n\n/**\n * Handler options for async message processing (User → Agent)\n * Follows the core pattern: HandlerOptions, HandlerCallback, etc.\n */\nexport interface MessageHandlerOptions {\n\t/**\n\t * Called when the agent generates a response\n\t * If provided, method returns immediately (async mode)\n\t * If not provided, method waits for response (sync mode)\n\t */\n\tonResponse?: (content: Content) => Promise<void>;\n\n\t/**\n\t * Called if an error occurs during processing\n\t */\n\tonError?: (error: Error) => Promise<void>;\n\n\t/**\n\t * Called when processing is complete\n\t */\n\tonComplete?: () => Promise<void>;\n}\n\n/**\n * Result of sending a message to an agent (User → Agent)\n * Follows the core pattern: ActionResult, ProviderResult, GenerateTextResult, etc.\n */\nexport interface MessageResult\n\textends Omit<\n\t\tProtoMessageResult,\n\t\t\"messageId\" | \"userMessage\" | \"agentResponses\"\n\t> {\n\tmessageId: UUID;\n\tuserMessage?: Memory;\n\tagentResponses?: Content[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Database Messaging Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { Metadata } from \"./primitives\";\n\n/**\n * Message Server\n *\n * Represents a messaging platform (Discord, Telegram, etc.) where agents operate.\n * Multiple agents can be associated with a single server.\n */\nexport interface MessageServer {\n\tid: UUID;\n\tname: string;\n\tsourceType: string;\n\tsourceId?: string;\n\tmetadata?: Metadata;\n\tcreatedAt: Date;\n\tupdatedAt: Date;\n}\n\n/**\n * MessagingChannel\n *\n * Represents a conversation space within a message server.\n * Can be a text channel, voice channel, DM, group DM, etc.\n *\n * NOTE: Named \"MessagingChannel\" (not \"Channel\") to avoid naming conflicts\n * with ChannelType and other channel-related types.\n */\nexport interface MessagingChannel {\n\tid: UUID;\n\tmessageServerId: UUID;\n\tname: string;\n\ttype: string;\n\tsourceType?: string;\n\tsourceId?: string;\n\ttopic?: string;\n\tmetadata?: Metadata;\n\tcreatedAt: Date;\n\tupdatedAt: Date;\n}\n\n/**\n * MessagingMessage\n *\n * Represents a message sent in a channel (stored in database).\n *\n * NOTE: Named \"MessagingMessage\" (not \"Message\" or \"DatabaseMessage\") to avoid\n * naming conflicts with Memory (which represents agent context) and other\n * message-related types.\n */\nexport interface MessagingMessage {\n\tid: UUID;\n\tchannelId: UUID;\n\tauthorId: UUID;\n\tcontent: string;\n\trawMessage?: Record<string, unknown>;\n\tsourceType?: string;\n\tsourceId?: string;\n\tmetadata?: Metadata;\n\tinReplyToRootMessageId?: UUID;\n\tcreatedAt: Date;\n\tupdatedAt: Date;\n}\n\n/**\n * Messaging Adapter Interface\n *\n * WHY separate from IDatabaseAdapter: Messaging functionality is specific to\n * certain database backends (SQL adapters) and certain deployment contexts\n * (multi-platform agents). In-memory and local-only adapters don't implement\n * message servers/channels, so these methods cannot be universally provided.\n *\n * WHY this architecture: Following the Interface Segregation Principle - clients\n * should not depend on interfaces they don't use. Client platform plugins\n * (Discord, Telegram) explicitly declare their dependency on IMessagingAdapter,\n * while simple agents can use just IDatabaseAdapter.\n *\n * USAGE: Access via runtime.getMessagingAdapter() or by casting runtime.adapter\n * when you know you're using a SQL backend.\n *\n * @example\n * ```typescript\n * const messagingAdapter = runtime.getMessagingAdapter();\n * if (messagingAdapter) {\n * const server = await messagingAdapter.createMessageServer({\n * name: \"Discord Server\",\n * sourceType: \"discord\",\n * sourceId: \"1234567890\"\n * });\n * }\n * ```\n */\nexport interface IMessagingAdapter {\n\t// ── Message Server Methods ──────────────────────────────────────────\n\n\t/**\n\t * Create a new message server\n\t *\n\t * WHY: When an agent first connects to a platform (Discord, Telegram),\n\t * it needs to register that platform as a message server.\n\t */\n\tcreateMessageServer(data: {\n\t\tid?: UUID;\n\t\tname: string;\n\t\tsourceType: string;\n\t\tsourceId?: string;\n\t\tmetadata?: Metadata;\n\t}): Promise<MessageServer>;\n\n\t/**\n\t * Get all message servers\n\t */\n\tgetMessageServers(): Promise<MessageServer[]>;\n\n\t/**\n\t * Get a message server by ID\n\t */\n\tgetMessageServerById(serverId: UUID): Promise<MessageServer | null>;\n\n\t/**\n\t * Get a message server by RLS server ID\n\t *\n\t * WHY: For Row Level Security (RLS) contexts where server ID is stored\n\t * in session variables.\n\t */\n\tgetMessageServerByRlsServerId(\n\t\trlsServerId: UUID,\n\t): Promise<MessageServer | null>;\n\n\t/**\n\t * Add an agent to a message server\n\t *\n\t * WHY: A server can have multiple agents (e.g., a Discord server with\n\t * multiple bot accounts).\n\t */\n\taddAgentToMessageServer(messageServerId: UUID, agentId: UUID): Promise<void>;\n\n\t/**\n\t * Get all agent IDs for a message server\n\t */\n\tgetAgentsForMessageServer(messageServerId: UUID): Promise<UUID[]>;\n\n\t/**\n\t * Remove an agent from a message server\n\t */\n\tremoveAgentFromMessageServer(\n\t\tmessageServerId: UUID,\n\t\tagentId: UUID,\n\t): Promise<void>;\n\n\t// ── Channel Methods ─────────────────────────────────────────────────\n\n\t/**\n\t * Create a new channel\n\t *\n\t * WHY: When the agent joins/creates a channel on a platform, it needs to\n\t * store the channel metadata for future message routing.\n\t *\n\t * @param data Channel properties\n\t * @param participantIds Optional initial participant list\n\t */\n\tcreateChannel(\n\t\tdata: {\n\t\t\tid?: UUID;\n\t\t\tmessageServerId: UUID;\n\t\t\tname: string;\n\t\t\ttype: string;\n\t\t\tsourceType?: string;\n\t\t\tsourceId?: string;\n\t\t\ttopic?: string;\n\t\t\tmetadata?: Metadata;\n\t\t},\n\t\tparticipantIds?: UUID[],\n\t): Promise<MessagingChannel>;\n\n\t/**\n\t * Get all channels for a message server\n\t */\n\tgetChannelsForMessageServer(\n\t\tmessageServerId: UUID,\n\t): Promise<MessagingChannel[]>;\n\n\t/**\n\t * Get channel details by ID\n\t */\n\tgetChannelDetails(channelId: UUID): Promise<MessagingChannel | null>;\n\n\t/**\n\t * Update channel properties\n\t */\n\tupdateChannel(\n\t\tchannelId: UUID,\n\t\tupdates: {\n\t\t\tname?: string;\n\t\t\tparticipantCentralUserIds?: UUID[];\n\t\t\tmetadata?: Metadata;\n\t\t},\n\t): Promise<MessagingChannel>;\n\n\t/**\n\t * Delete a channel\n\t */\n\tdeleteChannel(channelId: UUID): Promise<void>;\n\n\t/**\n\t * Add participants to a channel\n\t *\n\t * WHY: When users join a channel, they need to be tracked as participants\n\t * for permission checks and message delivery.\n\t */\n\taddChannelParticipants(channelId: UUID, entityIds: UUID[]): Promise<void>;\n\n\t/**\n\t * Get all participant IDs for a channel\n\t */\n\tgetChannelParticipants(channelId: UUID): Promise<UUID[]>;\n\n\t/**\n\t * Check if an entity is a channel participant\n\t */\n\tisChannelParticipant(channelId: UUID, entityId: UUID): Promise<boolean>;\n\n\t// ── Message Methods ─────────────────────────────────────────────────\n\n\t/**\n\t * Create a new message\n\t *\n\t * WHY: When a message is received from a platform or sent by the agent,\n\t * it's stored for conversation history, context, and retrieval.\n\t */\n\tcreateMessage(data: {\n\t\tchannelId: UUID;\n\t\tauthorId: UUID;\n\t\tcontent: string;\n\t\trawMessage?: Record<string, unknown>;\n\t\tsourceType?: string;\n\t\tsourceId?: string;\n\t\tmetadata?: Metadata;\n\t\tinReplyToRootMessageId?: UUID;\n\t\tmessageId?: UUID;\n\t}): Promise<MessagingMessage>;\n\n\t/**\n\t * Get a message by ID\n\t */\n\tgetMessageById(id: UUID): Promise<MessagingMessage | null>;\n\n\t/**\n\t * Update a message\n\t *\n\t * WHY: Messages can be edited after being sent (e.g., Discord edit events).\n\t */\n\tupdateMessage(\n\t\tid: UUID,\n\t\tpatch: {\n\t\t\tcontent?: string;\n\t\t\trawMessage?: Record<string, unknown>;\n\t\t\tsourceType?: string;\n\t\t\tsourceId?: string;\n\t\t\tmetadata?: Metadata;\n\t\t\tinReplyToRootMessageId?: UUID;\n\t\t},\n\t): Promise<MessagingMessage | null>;\n\n\t/**\n\t * Get messages for a channel with pagination\n\t *\n\t * WHY: Loading conversation history for context or display.\n\t *\n\t * @param channelId The channel to fetch messages from\n\t * @param limit Max messages to return (default 50)\n\t * @param beforeTimestamp Get messages before this timestamp (for pagination)\n\t */\n\tgetMessagesForChannel(\n\t\tchannelId: UUID,\n\t\tlimit?: number,\n\t\tbeforeTimestamp?: Date,\n\t): Promise<MessagingMessage[]>;\n\n\t/**\n\t * Delete a message\n\t */\n\tdeleteMessage(messageId: UUID): Promise<void>;\n\n\t/**\n\t * Find or create a DM channel between two users\n\t *\n\t * WHY: Direct message channels are created on-demand when two users\n\t * start a conversation. Ensures we don't create duplicate DM channels.\n\t */\n\tfindOrCreateDmChannel(\n\t\tuser1Id: UUID,\n\t\tuser2Id: UUID,\n\t\tmessageServerId: UUID,\n\t): Promise<MessagingChannel>;\n}\n",
340
340
  "import type {\n\tJsonValue,\n\tAudioProcessingParams as ProtoAudioProcessingParams,\n\tDetokenizeTextParams as ProtoDetokenizeTextParams,\n\tGenerateTextOptions as ProtoGenerateTextOptions,\n\tGenerateTextParams as ProtoGenerateTextParams,\n\tGenerateTextResult as ProtoGenerateTextResult,\n\tImageDescriptionParams as ProtoImageDescriptionParams,\n\tImageDescriptionResult as ProtoImageDescriptionResult,\n\tImageGenerationParams as ProtoImageGenerationParams,\n\tImageGenerationResult as ProtoImageGenerationResult,\n\tJSONSchema as ProtoJSONSchema,\n\tObjectGenerationParams as ProtoObjectGenerationParams,\n\tTextEmbeddingParams as ProtoTextEmbeddingParams,\n\tTextStreamChunk as ProtoTextStreamChunk,\n\tTextToSpeechParams as ProtoTextToSpeechParams,\n\tTokenizeTextParams as ProtoTokenizeTextParams,\n\tTokenUsage as ProtoTokenUsage,\n\tTranscriptionParams as ProtoTranscriptionParams,\n\tVideoProcessingParams as ProtoVideoProcessingParams,\n} from \"./proto.js\";\nimport type { IAgentRuntime } from \"./runtime\";\n\nexport type ModelTypeName = (typeof ModelType)[keyof typeof ModelType] | string;\n\n/**\n * LLM Mode for overriding model selection.\n *\n * - `DEFAULT`: Use the model type specified in the useModel call (no override)\n * - `SMALL`: Override all text generation model calls to use TEXT_SMALL\n * - `LARGE`: Override all text generation model calls to use TEXT_LARGE\n *\n * This is useful for cost optimization (force SMALL) or quality (force LARGE).\n * While not recommended for production, it can be a fast way to make the agent run cheaper.\n *\n * @example\n * ```typescript\n * const runtime = new AgentRuntime({\n * character: myCharacter,\n * llmMode: LLMMode.SMALL, // All LLM calls will use TEXT_SMALL\n * });\n * ```\n */\nexport const LLMMode = {\n\t/** Use the model type as specified in the call (no override) */\n\tDEFAULT: \"DEFAULT\",\n\t/** Override all text generation model calls to use TEXT_SMALL */\n\tSMALL: \"SMALL\",\n\t/** Override all text generation model calls to use TEXT_LARGE */\n\tLARGE: \"LARGE\",\n} as const;\n\nexport type LLMModeType = (typeof LLMMode)[keyof typeof LLMMode];\n\n/**\n * Defines the recognized types of models that the agent runtime can use.\n * These include models for text generation (small, large, reasoning, completion),\n * text embedding, tokenization (encode/decode), image generation and description,\n * audio transcription, text-to-speech, and generic object generation.\n * This constant is used throughout the system, particularly in `AgentRuntime.useModel`,\n * `AgentRuntime.registerModel`, and in `ModelParamsMap` / `ModelResultMap` to ensure\n * type safety and clarity when working with different AI models.\n * String values are used for extensibility with custom model types.\n */\nexport const ModelType = {\n\tSMALL: \"TEXT_SMALL\", // kept for backwards compatibility\n\tMEDIUM: \"TEXT_LARGE\", // kept for backwards compatibility\n\tLARGE: \"TEXT_LARGE\", // kept for backwards compatibility\n\tTEXT_SMALL: \"TEXT_SMALL\",\n\tTEXT_LARGE: \"TEXT_LARGE\",\n\tTEXT_EMBEDDING: \"TEXT_EMBEDDING\",\n\tTEXT_TOKENIZER_ENCODE: \"TEXT_TOKENIZER_ENCODE\",\n\tTEXT_TOKENIZER_DECODE: \"TEXT_TOKENIZER_DECODE\",\n\tTEXT_REASONING_SMALL: \"REASONING_SMALL\",\n\tTEXT_REASONING_LARGE: \"REASONING_LARGE\",\n\tTEXT_COMPLETION: \"TEXT_COMPLETION\",\n\tIMAGE: \"IMAGE\",\n\tIMAGE_DESCRIPTION: \"IMAGE_DESCRIPTION\",\n\tTRANSCRIPTION: \"TRANSCRIPTION\",\n\tTEXT_TO_SPEECH: \"TEXT_TO_SPEECH\",\n\tAUDIO: \"AUDIO\",\n\tVIDEO: \"VIDEO\",\n\tOBJECT_SMALL: \"OBJECT_SMALL\",\n\tOBJECT_LARGE: \"OBJECT_LARGE\",\n\tRESEARCH: \"RESEARCH\",\n} as const;\n\n/**\n * Union type of all text generation model types.\n * These models accept GenerateTextParams\n */\nexport type TextGenerationModelType =\n\t| typeof ModelType.TEXT_SMALL\n\t| typeof ModelType.TEXT_LARGE\n\t| typeof ModelType.TEXT_REASONING_SMALL\n\t| typeof ModelType.TEXT_REASONING_LARGE\n\t| typeof ModelType.TEXT_COMPLETION;\n\n/**\n * Model configuration setting keys used in character settings.\n * These constants define the keys for accessing model parameters\n * from character configuration with support for per-model-type settings.\n *\n * Setting Precedence (highest to lowest):\n * 1. Parameters passed directly to useModel()\n * 2. Model-specific settings (e.g., TEXT_SMALL_TEMPERATURE)\n * 3. Default settings (e.g., DEFAULT_TEMPERATURE)\n *\n * Example character settings:\n * ```\n * settings: {\n * DEFAULT_TEMPERATURE: 0.7, // Applies to all models\n * TEXT_SMALL_TEMPERATURE: 0.5, // Overrides default for TEXT_SMALL\n * TEXT_LARGE_MAX_TOKENS: 4096, // Specific to TEXT_LARGE\n * OBJECT_SMALL_TEMPERATURE: 0.3, // Specific to OBJECT_SMALL\n * }\n * ```\n */\nexport const MODEL_SETTINGS = {\n\t// Default settings - apply to all model types unless overridden\n\tDEFAULT_MAX_TOKENS: \"DEFAULT_MAX_TOKENS\",\n\tDEFAULT_TEMPERATURE: \"DEFAULT_TEMPERATURE\",\n\tDEFAULT_TOP_P: \"DEFAULT_TOP_P\",\n\tDEFAULT_TOP_K: \"DEFAULT_TOP_K\",\n\tDEFAULT_MIN_P: \"DEFAULT_MIN_P\",\n\tDEFAULT_SEED: \"DEFAULT_SEED\",\n\tDEFAULT_REPETITION_PENALTY: \"DEFAULT_REPETITION_PENALTY\",\n\tDEFAULT_FREQUENCY_PENALTY: \"DEFAULT_FREQUENCY_PENALTY\",\n\tDEFAULT_PRESENCE_PENALTY: \"DEFAULT_PRESENCE_PENALTY\",\n\n\t// TEXT_SMALL specific settings\n\tTEXT_SMALL_MAX_TOKENS: \"TEXT_SMALL_MAX_TOKENS\",\n\tTEXT_SMALL_TEMPERATURE: \"TEXT_SMALL_TEMPERATURE\",\n\tTEXT_SMALL_TOP_P: \"TEXT_SMALL_TOP_P\",\n\tTEXT_SMALL_TOP_K: \"TEXT_SMALL_TOP_K\",\n\tTEXT_SMALL_MIN_P: \"TEXT_SMALL_MIN_P\",\n\tTEXT_SMALL_SEED: \"TEXT_SMALL_SEED\",\n\tTEXT_SMALL_REPETITION_PENALTY: \"TEXT_SMALL_REPETITION_PENALTY\",\n\tTEXT_SMALL_FREQUENCY_PENALTY: \"TEXT_SMALL_FREQUENCY_PENALTY\",\n\tTEXT_SMALL_PRESENCE_PENALTY: \"TEXT_SMALL_PRESENCE_PENALTY\",\n\n\t// TEXT_LARGE specific settings\n\tTEXT_LARGE_MAX_TOKENS: \"TEXT_LARGE_MAX_TOKENS\",\n\tTEXT_LARGE_TEMPERATURE: \"TEXT_LARGE_TEMPERATURE\",\n\tTEXT_LARGE_TOP_P: \"TEXT_LARGE_TOP_P\",\n\tTEXT_LARGE_TOP_K: \"TEXT_LARGE_TOP_K\",\n\tTEXT_LARGE_MIN_P: \"TEXT_LARGE_MIN_P\",\n\tTEXT_LARGE_SEED: \"TEXT_LARGE_SEED\",\n\tTEXT_LARGE_REPETITION_PENALTY: \"TEXT_LARGE_REPETITION_PENALTY\",\n\tTEXT_LARGE_FREQUENCY_PENALTY: \"TEXT_LARGE_FREQUENCY_PENALTY\",\n\tTEXT_LARGE_PRESENCE_PENALTY: \"TEXT_LARGE_PRESENCE_PENALTY\",\n\n\t// OBJECT_SMALL specific settings\n\tOBJECT_SMALL_MAX_TOKENS: \"OBJECT_SMALL_MAX_TOKENS\",\n\tOBJECT_SMALL_TEMPERATURE: \"OBJECT_SMALL_TEMPERATURE\",\n\tOBJECT_SMALL_TOP_P: \"OBJECT_SMALL_TOP_P\",\n\tOBJECT_SMALL_TOP_K: \"OBJECT_SMALL_TOP_K\",\n\tOBJECT_SMALL_MIN_P: \"OBJECT_SMALL_MIN_P\",\n\tOBJECT_SMALL_SEED: \"OBJECT_SMALL_SEED\",\n\tOBJECT_SMALL_REPETITION_PENALTY: \"OBJECT_SMALL_REPETITION_PENALTY\",\n\tOBJECT_SMALL_FREQUENCY_PENALTY: \"OBJECT_SMALL_FREQUENCY_PENALTY\",\n\tOBJECT_SMALL_PRESENCE_PENALTY: \"OBJECT_SMALL_PRESENCE_PENALTY\",\n\n\t// OBJECT_LARGE specific settings\n\tOBJECT_LARGE_MAX_TOKENS: \"OBJECT_LARGE_MAX_TOKENS\",\n\tOBJECT_LARGE_TEMPERATURE: \"OBJECT_LARGE_TEMPERATURE\",\n\tOBJECT_LARGE_TOP_P: \"OBJECT_LARGE_TOP_P\",\n\tOBJECT_LARGE_TOP_K: \"OBJECT_LARGE_TOP_K\",\n\tOBJECT_LARGE_MIN_P: \"OBJECT_LARGE_MIN_P\",\n\tOBJECT_LARGE_SEED: \"OBJECT_LARGE_SEED\",\n\tOBJECT_LARGE_REPETITION_PENALTY: \"OBJECT_LARGE_REPETITION_PENALTY\",\n\tOBJECT_LARGE_FREQUENCY_PENALTY: \"OBJECT_LARGE_FREQUENCY_PENALTY\",\n\tOBJECT_LARGE_PRESENCE_PENALTY: \"OBJECT_LARGE_PRESENCE_PENALTY\",\n\n\t// TEXT_REASONING_SMALL specific settings\n\tTEXT_REASONING_SMALL_MAX_TOKENS: \"TEXT_REASONING_SMALL_MAX_TOKENS\",\n\tTEXT_REASONING_SMALL_TEMPERATURE: \"TEXT_REASONING_SMALL_TEMPERATURE\",\n\tTEXT_REASONING_SMALL_TOP_P: \"TEXT_REASONING_SMALL_TOP_P\",\n\tTEXT_REASONING_SMALL_TOP_K: \"TEXT_REASONING_SMALL_TOP_K\",\n\tTEXT_REASONING_SMALL_MIN_P: \"TEXT_REASONING_SMALL_MIN_P\",\n\tTEXT_REASONING_SMALL_SEED: \"TEXT_REASONING_SMALL_SEED\",\n\tTEXT_REASONING_SMALL_REPETITION_PENALTY:\n\t\t\"TEXT_REASONING_SMALL_REPETITION_PENALTY\",\n\tTEXT_REASONING_SMALL_FREQUENCY_PENALTY:\n\t\t\"TEXT_REASONING_SMALL_FREQUENCY_PENALTY\",\n\tTEXT_REASONING_SMALL_PRESENCE_PENALTY:\n\t\t\"TEXT_REASONING_SMALL_PRESENCE_PENALTY\",\n\n\t// TEXT_REASONING_LARGE specific settings\n\tTEXT_REASONING_LARGE_MAX_TOKENS: \"TEXT_REASONING_LARGE_MAX_TOKENS\",\n\tTEXT_REASONING_LARGE_TEMPERATURE: \"TEXT_REASONING_LARGE_TEMPERATURE\",\n\tTEXT_REASONING_LARGE_TOP_P: \"TEXT_REASONING_LARGE_TOP_P\",\n\tTEXT_REASONING_LARGE_TOP_K: \"TEXT_REASONING_LARGE_TOP_K\",\n\tTEXT_REASONING_LARGE_MIN_P: \"TEXT_REASONING_LARGE_MIN_P\",\n\tTEXT_REASONING_LARGE_SEED: \"TEXT_REASONING_LARGE_SEED\",\n\tTEXT_REASONING_LARGE_REPETITION_PENALTY:\n\t\t\"TEXT_REASONING_LARGE_REPETITION_PENALTY\",\n\tTEXT_REASONING_LARGE_FREQUENCY_PENALTY:\n\t\t\"TEXT_REASONING_LARGE_FREQUENCY_PENALTY\",\n\tTEXT_REASONING_LARGE_PRESENCE_PENALTY:\n\t\t\"TEXT_REASONING_LARGE_PRESENCE_PENALTY\",\n\n\t// TEXT_COMPLETION specific settings\n\tTEXT_COMPLETION_MAX_TOKENS: \"TEXT_COMPLETION_MAX_TOKENS\",\n\tTEXT_COMPLETION_TEMPERATURE: \"TEXT_COMPLETION_TEMPERATURE\",\n\tTEXT_COMPLETION_TOP_P: \"TEXT_COMPLETION_TOP_P\",\n\tTEXT_COMPLETION_TOP_K: \"TEXT_COMPLETION_TOP_K\",\n\tTEXT_COMPLETION_MIN_P: \"TEXT_COMPLETION_MIN_P\",\n\tTEXT_COMPLETION_SEED: \"TEXT_COMPLETION_SEED\",\n\tTEXT_COMPLETION_REPETITION_PENALTY: \"TEXT_COMPLETION_REPETITION_PENALTY\",\n\tTEXT_COMPLETION_FREQUENCY_PENALTY: \"TEXT_COMPLETION_FREQUENCY_PENALTY\",\n\tTEXT_COMPLETION_PRESENCE_PENALTY: \"TEXT_COMPLETION_PRESENCE_PENALTY\",\n} as const;\n\n/**\n * A segment of prompt content with stability metadata for provider-level prompt caching.\n * Providers may use `stable: true` segments for caching (Anthropic cache_control,\n * OpenAI/Gemini prefix caching). Only mark content stable when it is identical across\n * calls for the same schema/character—e.g. instructions, format, examples. Per-call\n * content (state, validation UUIDs) must be unstable so caches can actually hit.\n */\nexport interface PromptSegment {\n\tcontent: string;\n\t/** true = same across calls for same schema/character; false = changes per call */\n\tstable: boolean;\n}\n\n/**\n * Parameters for generating text using a language model.\n * This structure is typically passed to `AgentRuntime.useModel` when the `modelType` is one of\n * `ModelType.TEXT_SMALL`, `ModelType.TEXT_LARGE`, `ModelType.TEXT_REASONING_SMALL`,\n * `ModelType.TEXT_REASONING_LARGE`, or `ModelType.TEXT_COMPLETION`.\n * It includes essential information like the prompt and various generation controls.\n *\n * **Note for Plugin Implementers**: Different LLM providers have varying support for these parameters.\n * Some providers may not support both `temperature` and `topP` simultaneously, or may have other restrictions.\n * Plugin implementations should filter out unsupported parameters before calling their provider's API.\n * Check your provider's documentation to determine which parameters are supported.\n */\nexport interface GenerateTextParams\n\textends Omit<\n\t\tProtoGenerateTextParams,\n\t\t\"$typeName\" | \"$unknown\" | \"responseFormat\" | \"stopSequences\"\n\t> {\n\tresponseFormat?: { type: \"json_object\" | \"text\" } | string;\n\tstopSequences?: string[];\n\tonStreamChunk?: (chunk: string, messageId?: string) => void | Promise<void>;\n\tuser?: string;\n\t/**\n\t * Optional ordered segments for prompt cache hints. When set, must satisfy:\n\t * prompt === promptSegments.map(s => s.content).join(\"\")\n\t * Why: providers that ignore segments still get correct behavior via prompt;\n\t * those that use segments must send the same total text so model behavior is unchanged.\n\t */\n\tpromptSegments?: PromptSegment[];\n}\n\n/**\n * Token usage information from a model response.\n * Provides metrics about token consumption for billing and monitoring.\n */\nexport interface TokenUsage\n\textends Omit<ProtoTokenUsage, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Represents a single chunk in a text stream.\n * Each chunk contains a piece of the generated text.\n */\nexport interface TextStreamChunk\n\textends Omit<ProtoTextStreamChunk, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Result of a streaming text generation request.\n * Provides an async iterable for consuming text chunks as they arrive.\n *\n * @example\n * ```typescript\n * const result = await runtime.useModel(ModelType.TEXT_LARGE, {\n * prompt: \"Hello\",\n * stream: true\n * }) as TextStreamResult;\n *\n * let fullText = '';\n * for await (const chunk of result.textStream) {\n * fullText += chunk;\n * console.log('Received:', chunk);\n * }\n *\n * // After stream completes\n * const usage = await result.usage;\n * console.log('Total tokens:', usage.totalTokens);\n * ```\n */\nexport interface TextStreamResult {\n\t/**\n\t * Async iterable that yields text chunks as they are generated.\n\t * Each iteration provides a string chunk of the response.\n\t */\n\ttextStream: AsyncIterable<string>;\n\n\t/**\n\t * Promise that resolves to the complete text after streaming finishes.\n\t * Useful when you need the full response after streaming.\n\t */\n\ttext: Promise<string>;\n\n\t/**\n\t * Promise that resolves to token usage information after streaming completes.\n\t * May be undefined if the provider doesn't report usage for streaming.\n\t */\n\tusage: Promise<TokenUsage | undefined>;\n\n\t/**\n\t * Promise that resolves to the finish reason after streaming completes.\n\t * Common values: 'stop', 'length', 'content-filter'\n\t */\n\tfinishReason: Promise<string | undefined>;\n}\n\n/**\n * Options for the simplified generateText API.\n * Extends GenerateTextParams with additional configuration for character context.\n */\nexport interface GenerateTextOptions\n\textends Omit<\n\t\tProtoGenerateTextOptions,\n\t\t\"$typeName\" | \"$unknown\" | \"modelType\"\n\t> {\n\tincludeCharacter?: boolean;\n\tmodelType?: TextGenerationModelType;\n\tminTokens?: number;\n\ttopP?: number;\n\ttopK?: number;\n\tminP?: number;\n\tseed?: number;\n\trepetitionPenalty?: number;\n\tuser?: string;\n\tresponseFormat?: { type: \"json_object\" | \"text\" } | string;\n}\n\n/**\n * Structured response from text generation.\n */\nexport interface GenerateTextResult\n\textends Omit<ProtoGenerateTextResult, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Parameters for text tokenization models\n */\nexport interface TokenizeTextParams\n\textends Omit<\n\t\tProtoTokenizeTextParams,\n\t\t\"$typeName\" | \"$unknown\" | \"modelType\"\n\t> {\n\tmodelType: ModelTypeName;\n}\n\n/**\n * Parameters for detokenizing text, i.e., converting a sequence of numerical tokens back into a string.\n * This is the reverse operation of tokenization.\n * This structure is used with `AgentRuntime.useModel` when the `modelType` is `ModelType.TEXT_TOKENIZER_DECODE`.\n */\nexport interface DetokenizeTextParams\n\textends Omit<\n\t\tProtoDetokenizeTextParams,\n\t\t\"$typeName\" | \"$unknown\" | \"modelType\"\n\t> {\n\tmodelType: ModelTypeName;\n}\n\n/**\n * Parameters for text embedding models\n */\nexport interface TextEmbeddingParams\n\textends Omit<ProtoTextEmbeddingParams, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Parameters for image generation models\n */\nexport interface ImageGenerationParams\n\textends Omit<ProtoImageGenerationParams, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Parameters for image description models\n */\nexport interface ImageDescriptionParams\n\textends Omit<ProtoImageDescriptionParams, \"$typeName\" | \"$unknown\"> {}\nexport interface ImageDescriptionResult\n\textends Omit<ProtoImageDescriptionResult, \"$typeName\" | \"$unknown\"> {}\nexport interface ImageGenerationResult\n\textends Omit<ProtoImageGenerationResult, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Parameters for transcription models\n */\nexport interface TranscriptionParams\n\textends Omit<ProtoTranscriptionParams, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Parameters for text-to-speech models\n */\nexport interface TextToSpeechParams\n\textends Omit<ProtoTextToSpeechParams, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Parameters for audio processing models\n */\nexport interface AudioProcessingParams\n\textends Omit<ProtoAudioProcessingParams, \"$typeName\" | \"$unknown\"> {}\n\n/**\n * Parameters for video processing models\n */\nexport interface VideoProcessingParams\n\textends Omit<ProtoVideoProcessingParams, \"$typeName\" | \"$unknown\"> {}\n\n// ============================================================================\n// Research Model Types (Deep Research)\n// ============================================================================\n\n/**\n * Research tool configuration for web search\n */\nexport interface ResearchWebSearchTool {\n\ttype: \"web_search_preview\";\n}\n\n/**\n * Research tool configuration for file search over vector stores\n */\nexport interface ResearchFileSearchTool {\n\ttype: \"file_search\";\n\t/** Array of vector store IDs to search (max 2) */\n\tvectorStoreIds: string[];\n}\n\n/**\n * Research tool configuration for code interpreter\n */\nexport interface ResearchCodeInterpreterTool {\n\ttype: \"code_interpreter\";\n\t/** Container configuration */\n\tcontainer?: { type: \"auto\" };\n}\n\n/**\n * Research tool configuration for remote MCP servers.\n * MCP servers must implement a search/fetch interface for deep research compatibility.\n */\nexport interface ResearchMcpTool {\n\ttype: \"mcp\";\n\t/** Label to identify the MCP server */\n\tserverLabel: string;\n\t/** URL of the remote MCP server */\n\tserverUrl: string;\n\t/** Approval mode - must be \"never\" for deep research */\n\trequireApproval?: \"never\";\n}\n\n/**\n * Union type for all supported research tools\n */\nexport type ResearchTool =\n\t| ResearchWebSearchTool\n\t| ResearchFileSearchTool\n\t| ResearchCodeInterpreterTool\n\t| ResearchMcpTool;\n\n/**\n * Parameters for deep research models (o3-deep-research, o4-mini-deep-research).\n *\n * Deep research models can find, analyze, and synthesize hundreds of sources\n * to create comprehensive reports. They support web search, file search over\n * vector stores, and remote MCP servers as data sources.\n *\n * @example\n * ```typescript\n * const result = await runtime.useModel(ModelType.RESEARCH, {\n * input: \"Research the economic impact of AI on global labor markets\",\n * tools: [\n * { type: \"web_search_preview\" },\n * { type: \"code_interpreter\", container: { type: \"auto\" } }\n * ],\n * background: true,\n * });\n * ```\n */\nexport interface ResearchParams {\n\t/**\n\t * The research input/question.\n\t * Should be a detailed, specific question for best results.\n\t */\n\tinput: string;\n\n\t/**\n\t * Optional instructions to guide the research process.\n\t * Can include formatting requirements, source preferences, etc.\n\t */\n\tinstructions?: string;\n\n\t/**\n\t * Whether to run the request in background mode.\n\t * Recommended for long-running research tasks (can take tens of minutes).\n\t * When true, the request returns immediately and results can be polled.\n\t * @default false\n\t */\n\tbackground?: boolean;\n\n\t/**\n\t * Array of tools/data sources for the research model.\n\t * Must include at least one data source: web_search_preview, file_search, or mcp.\n\t * Can also include code_interpreter for data analysis.\n\t */\n\ttools?: ResearchTool[];\n\n\t/**\n\t * Maximum number of tool calls the model can make.\n\t * Use this to control cost and latency.\n\t */\n\tmaxToolCalls?: number;\n\n\t/**\n\t * Whether to include reasoning summary in the response.\n\t * @default \"auto\"\n\t */\n\treasoningSummary?: \"auto\" | \"none\";\n\n\t/**\n\t * Model variant to use.\n\t * @default \"o3-deep-research\"\n\t */\n\tmodel?: \"o3-deep-research\" | \"o4-mini-deep-research\";\n}\n\n/**\n * Annotation in research results, linking text to sources\n */\nexport interface ResearchAnnotation {\n\t/** URL of the source */\n\turl: string;\n\t/** Title of the source */\n\ttitle: string;\n\t/** Start index in the text where this citation appears */\n\tstartIndex: number;\n\t/** End index in the text where this citation ends */\n\tendIndex: number;\n}\n\n/**\n * Web search action taken by the research model\n */\nexport interface ResearchWebSearchCall {\n\tid: string;\n\ttype: \"web_search_call\";\n\tstatus: \"completed\" | \"failed\";\n\taction: {\n\t\ttype: \"search\" | \"open_page\" | \"find_in_page\";\n\t\tquery?: string;\n\t\turl?: string;\n\t};\n}\n\n/**\n * File search action taken over vector stores\n */\nexport interface ResearchFileSearchCall {\n\tid: string;\n\ttype: \"file_search_call\";\n\tstatus: \"completed\" | \"failed\";\n\tquery: string;\n\tresults?: Array<{\n\t\tfileId: string;\n\t\tfileName: string;\n\t\tscore: number;\n\t}>;\n}\n\n/**\n * Code interpreter action for data analysis\n */\nexport interface ResearchCodeInterpreterCall {\n\tid: string;\n\ttype: \"code_interpreter_call\";\n\tstatus: \"completed\" | \"failed\";\n\tcode: string;\n\toutput?: string;\n}\n\n/**\n * MCP tool call made to a remote server\n */\nexport interface ResearchMcpToolCall {\n\tid: string;\n\ttype: \"mcp_tool_call\";\n\tstatus: \"completed\" | \"failed\";\n\tserverLabel: string;\n\ttoolName: string;\n\targuments: Record<string, JsonValue>;\n\tresult?: JsonValue;\n}\n\n/**\n * Final message output from research\n */\nexport interface ResearchMessageOutput {\n\ttype: \"message\";\n\tcontent: Array<{\n\t\ttype: \"output_text\";\n\t\ttext: string;\n\t\tannotations: ResearchAnnotation[];\n\t}>;\n}\n\n/**\n * Union type for all research output items\n */\nexport type ResearchOutputItem =\n\t| ResearchWebSearchCall\n\t| ResearchFileSearchCall\n\t| ResearchCodeInterpreterCall\n\t| ResearchMcpToolCall\n\t| ResearchMessageOutput;\n\n/**\n * Result from a deep research model request\n */\nexport interface ResearchResult {\n\t/** Unique identifier for the response */\n\tid: string;\n\n\t/** The final research report text with inline citations */\n\ttext: string;\n\n\t/** Annotations linking text to sources - should be displayed as clickable links */\n\tannotations: ResearchAnnotation[];\n\n\t/**\n\t * Output items showing the research process.\n\t * Includes web searches, file searches, code execution, and MCP calls.\n\t */\n\toutputItems: ResearchOutputItem[];\n\n\t/**\n\t * For background requests, the current status\n\t */\n\tstatus?: \"queued\" | \"in_progress\" | \"completed\" | \"failed\";\n}\n\n/**\n * Optional JSON schema for validating generated objects\n */\nexport interface JSONSchema\n\textends Omit<\n\t\tProtoJSONSchema,\n\t\t\"$typeName\" | \"$unknown\" | \"type\" | \"properties\" | \"items\" | \"required\"\n\t> {\n\ttype?: string | string[];\n\tproperties?: Record<string, JSONSchema>;\n\titems?: JSONSchema | JSONSchema[];\n\trequired?: string[];\n\t[key: string]: JsonValue | JSONSchema | JSONSchema[] | undefined;\n}\n\n/**\n * Parameters for object generation models\n * @template T - The expected return type, inferred from schema if provided\n */\nexport interface ObjectGenerationParams\n\textends Omit<\n\t\tProtoObjectGenerationParams,\n\t\t| \"$typeName\"\n\t\t| \"$unknown\"\n\t\t| \"modelType\"\n\t\t| \"schema\"\n\t\t| \"enumValues\"\n\t\t| \"stopSequences\"\n\t> {\n\tschema?: JSONSchema;\n\tmodelType?: ModelTypeName;\n\tenumValues?: string[];\n\tstopSequences?: string[];\n}\n\n/**\n * Map of model types to their parameter types\n */\nexport interface ModelParamsMap {\n\t[ModelType.TEXT_SMALL]: GenerateTextParams;\n\t[ModelType.TEXT_LARGE]: GenerateTextParams;\n\t[ModelType.TEXT_EMBEDDING]: TextEmbeddingParams | string | null;\n\t[ModelType.TEXT_TOKENIZER_ENCODE]: TokenizeTextParams;\n\t[ModelType.TEXT_TOKENIZER_DECODE]: DetokenizeTextParams;\n\t[ModelType.TEXT_REASONING_SMALL]: GenerateTextParams;\n\t[ModelType.TEXT_REASONING_LARGE]: GenerateTextParams;\n\t[ModelType.IMAGE]: ImageGenerationParams;\n\t[ModelType.IMAGE_DESCRIPTION]: ImageDescriptionParams | string;\n\t[ModelType.TRANSCRIPTION]: TranscriptionParams | Buffer | string;\n\t[ModelType.TEXT_TO_SPEECH]: TextToSpeechParams | string;\n\t[ModelType.AUDIO]: AudioProcessingParams;\n\t[ModelType.VIDEO]: VideoProcessingParams;\n\t[ModelType.OBJECT_SMALL]: ObjectGenerationParams;\n\t[ModelType.OBJECT_LARGE]: ObjectGenerationParams;\n\t[ModelType.TEXT_COMPLETION]: GenerateTextParams;\n\t[ModelType.RESEARCH]: ResearchParams;\n\t// Custom model types should be registered via runtime.registerModel() in plugin init()\n}\n\n/**\n * Map of model types to their DEFAULT return value types.\n *\n * For text generation models (TEXT_SMALL, TEXT_LARGE, TEXT_REASONING_*),\n * the actual return type depends on the parameters and is handled by overloads:\n * - `{ prompt }`: Returns `string` (this default)\n * - `{ prompt, stream: true }`: Returns `TextStreamResult` (via overload)\n *\n * The overloads in IAgentRuntime.useModel() provide the correct type inference.\n */\nexport interface ModelResultMap {\n\t[ModelType.TEXT_SMALL]: string;\n\t[ModelType.TEXT_LARGE]: string;\n\t[ModelType.TEXT_EMBEDDING]: number[];\n\t[ModelType.TEXT_TOKENIZER_ENCODE]: number[];\n\t[ModelType.TEXT_TOKENIZER_DECODE]: string;\n\t[ModelType.TEXT_REASONING_SMALL]: string;\n\t[ModelType.TEXT_REASONING_LARGE]: string;\n\t[ModelType.IMAGE]: ImageGenerationResult[];\n\t[ModelType.IMAGE_DESCRIPTION]: ImageDescriptionResult;\n\t[ModelType.TRANSCRIPTION]: string;\n\t[ModelType.TEXT_TO_SPEECH]: Buffer | ArrayBuffer | Uint8Array;\n\t[ModelType.AUDIO]:\n\t\t| Buffer\n\t\t| ArrayBuffer\n\t\t| Uint8Array\n\t\t| Record<string, JsonValue>;\n\t[ModelType.VIDEO]:\n\t\t| Buffer\n\t\t| ArrayBuffer\n\t\t| Uint8Array\n\t\t| Record<string, JsonValue>;\n\t[ModelType.OBJECT_SMALL]: Record<string, JsonValue>;\n\t[ModelType.OBJECT_LARGE]: Record<string, JsonValue>;\n\t[ModelType.TEXT_COMPLETION]: string;\n\t[ModelType.RESEARCH]: ResearchResult;\n\t// Custom model types should be registered via runtime.registerModel() in plugin init()\n}\n\n/**\n * Models that support streaming - their handlers can return either string or TextStreamResult\n */\nexport type StreamableModelType =\n\t| typeof ModelType.TEXT_SMALL\n\t| typeof ModelType.TEXT_LARGE\n\t| typeof ModelType.TEXT_REASONING_SMALL\n\t| typeof ModelType.TEXT_REASONING_LARGE\n\t| typeof ModelType.TEXT_COMPLETION;\n\n/**\n * Result type for plugin model handlers - includes TextStreamResult for streamable models\n */\nexport type PluginModelResult<K extends keyof ModelResultMap> =\n\tK extends StreamableModelType\n\t\t? ModelResultMap[K] | TextStreamResult\n\t\t: ModelResultMap[K];\n\n/**\n * Type guard to check if a model type supports streaming.\n */\nconst STREAMABLE_MODEL_TYPES: ReadonlySet<string> = new Set([\n\tModelType.TEXT_SMALL,\n\tModelType.TEXT_LARGE,\n\tModelType.TEXT_REASONING_SMALL,\n\tModelType.TEXT_REASONING_LARGE,\n\tModelType.TEXT_COMPLETION,\n]);\n\nexport function isStreamableModelType(\n\tmodelType: ModelTypeName,\n): modelType is StreamableModelType {\n\treturn STREAMABLE_MODEL_TYPES.has(modelType);\n}\n\n/**\n * Defines the structure for a model handler registration within the `AgentRuntime`.\n * Each model (e.g., for text generation, embedding) is associated with a handler function,\n * the name of the provider (plugin or system) that registered it, and an optional priority.\n * The `priority` (higher is more preferred) helps in selecting which handler to use if multiple\n * handlers are registered for the same model type. The `registrationOrder` (not in type, but used in runtime)\n * serves as a tie-breaker. See `AgentRuntime.registerModel` and `AgentRuntime.getModel`.\n */\nexport interface ModelHandler<\n\tTParams = Record<string, JsonValue | object>,\n\tTResult = JsonValue | object,\n> {\n\t/** The function that executes the model, taking runtime and parameters, and returning a Promise. */\n\thandler: (runtime: IAgentRuntime, params: TParams) => Promise<TResult>;\n\t/** The name of the provider (e.g., plugin name) that registered this model handler. */\n\tprovider: string;\n\t/**\n\t * Optional priority for this model handler. Higher numbers indicate higher priority.\n\t * This is used by `AgentRuntime.getModel` to select the most appropriate handler\n\t * when multiple are available for a given model type. Defaults to 0 if not specified.\n\t */\n\tpriority?: number; // Optional priority for selection order\n\n\tregistrationOrder?: number;\n}\n",
341
341
  "/**\n * Onboarding Types\n *\n * Type definitions for the unified onboarding state machine that supports\n * both CLI and conversational (DM) onboarding flows.\n */\n\nimport type { Metadata, UUID } from \"./primitives\";\n\n/**\n * Onboarding step identifiers.\n * These represent the discrete steps in the onboarding flow.\n */\nexport const OnboardingStep = {\n\t/** Initial welcome and introduction */\n\tWELCOME: \"WELCOME\",\n\t/** Risk acknowledgement - user must accept security warnings */\n\tRISK_ACK: \"RISK_ACK\",\n\t/** Authentication setup - API keys, OAuth, etc. */\n\tAUTH: \"AUTH\",\n\t/** Channel configuration - Discord, Telegram, etc. */\n\tCHANNELS: \"CHANNELS\",\n\t/** Skills setup - tools and capabilities */\n\tSKILLS: \"SKILLS\",\n\t/** Onboarding complete */\n\tCOMPLETE: \"COMPLETE\",\n} as const;\n\nexport type OnboardingStep =\n\t(typeof OnboardingStep)[keyof typeof OnboardingStep];\n\n/**\n * Ordered list of onboarding steps for progression.\n */\nexport const ONBOARDING_STEP_ORDER: OnboardingStep[] = [\n\tOnboardingStep.WELCOME,\n\tOnboardingStep.RISK_ACK,\n\tOnboardingStep.AUTH,\n\tOnboardingStep.CHANNELS,\n\tOnboardingStep.SKILLS,\n\tOnboardingStep.COMPLETE,\n];\n\n/**\n * Settings collected during the AUTH step.\n */\nexport interface AuthSettings {\n\t/** Primary model provider (anthropic, openai, google, etc.) */\n\tmodelProvider?: string;\n\t/** API key for the selected provider */\n\tapiKey?: string;\n\t/** OAuth tokens if using OAuth flow */\n\toauthTokens?: {\n\t\taccessToken: string;\n\t\trefreshToken?: string;\n\t\texpiresAt?: number;\n\t};\n\t/** Setup token (e.g., from `claude setup-token`) */\n\tsetupToken?: string;\n\t/** Authentication method used */\n\tauthMethod?: \"api_key\" | \"oauth\" | \"setup_token\";\n}\n\n/**\n * Settings collected during the CHANNELS step.\n */\nexport interface ChannelSettings {\n\t/** Enabled channel types */\n\tenabledChannels: string[];\n\t/** Channel-specific configurations */\n\tchannelConfigs: Record<string, ChannelConfig>;\n\t/** DM policy settings */\n\tdmPolicy?: {\n\t\tallowUnknownSenders?: boolean;\n\t\trequireApproval?: boolean;\n\t};\n}\n\n/**\n * Configuration for a specific channel.\n */\nexport interface ChannelConfig {\n\t/** Channel type (discord, telegram, etc.) */\n\ttype: string;\n\t/** Whether the channel is enabled */\n\tenabled: boolean;\n\t/** Channel-specific credentials */\n\tcredentials?: Record<string, string>;\n\t/** Additional channel settings */\n\tsettings?: Record<string, string | boolean | number>;\n}\n\n/**\n * Settings collected during the SKILLS step.\n */\nexport interface SkillsSettings {\n\t/** Enabled skills */\n\tenabledSkills: string[];\n\t/** Skills to install */\n\tskillsToInstall: string[];\n\t/** Homebrew installation preference */\n\tuseHomebrew?: boolean;\n\t/** Node package manager preference */\n\tnodeManager?: \"npm\" | \"pnpm\" | \"bun\";\n}\n\n/**\n * Complete settings collected during onboarding.\n */\nexport interface OnboardingSettings {\n\t/** Auth step settings */\n\tauth?: AuthSettings;\n\t/** Channels step settings */\n\tchannels?: ChannelSettings;\n\t/** Skills step settings */\n\tskills?: SkillsSettings;\n\t/** Risk acknowledgement timestamp */\n\triskAcknowledgedAt?: number;\n\t/** Whether user acknowledged risks */\n\triskAcknowledged?: boolean;\n\t/** Gateway configuration */\n\tgateway?: {\n\t\tmode: \"local\" | \"remote\";\n\t\tport?: number;\n\t\tbind?: string;\n\t};\n}\n\n/**\n * Error information for a specific step.\n */\nexport interface OnboardingStepError {\n\t/** Error code */\n\tcode: string;\n\t/** Human-readable error message */\n\tmessage: string;\n\t/** Step where the error occurred */\n\tstep: OnboardingStep;\n\t/** Additional error details */\n\tdetails?: Record<string, unknown>;\n\t/** Timestamp when error occurred */\n\ttimestamp: number;\n}\n\n/**\n * Context tracking the current state of onboarding.\n */\nexport interface OnboardingContext {\n\t/** Current onboarding step */\n\tcurrentStep: OnboardingStep;\n\t/** Steps that have been completed */\n\tcompletedSteps: OnboardingStep[];\n\t/** Collected settings */\n\tsettings: OnboardingSettings;\n\t/** Errors encountered during onboarding */\n\terrors: OnboardingStepError[];\n\t/** When onboarding started */\n\tstartedAt: number;\n\t/** Last activity timestamp */\n\tlastActivityAt: number;\n\t/** World ID (for DM onboarding) */\n\tworldId?: UUID;\n\t/** User ID being onboarded */\n\tuserId?: UUID;\n\t/** Platform (discord, telegram, cli, etc.) */\n\tplatform: string;\n\t/** Onboarding mode */\n\tmode: \"cli\" | \"conversational\" | \"wizard\";\n\t/** Session ID for tracking */\n\tsessionId: string;\n\t/** Whether onboarding was interrupted */\n\tinterrupted?: boolean;\n\t/** Metadata for custom extensions */\n\tmetadata?: Metadata;\n}\n\n/**\n * Input types for each onboarding step.\n */\nexport interface WelcomeInput {\n\t/** User's response to welcome message */\n\tacknowledged: boolean;\n\t/** User's name (optional) */\n\tuserName?: string;\n}\n\nexport interface RiskAckInput {\n\t/** Whether user accepted the risk warning */\n\taccepted: boolean;\n\t/** Text of the warning that was shown */\n\twarningText?: string;\n}\n\nexport interface AuthInput {\n\t/** Auth method being used */\n\tmethod: \"api_key\" | \"oauth\" | \"setup_token\";\n\t/** Provider name */\n\tprovider?: string;\n\t/** API key if using api_key method */\n\tapiKey?: string;\n\t/** OAuth callback data if using oauth method */\n\toauthCallback?: {\n\t\tcode: string;\n\t\tstate: string;\n\t};\n\t/** Setup token if using setup_token method */\n\tsetupToken?: string;\n\t/** Skip auth (use local models) */\n\tskip?: boolean;\n}\n\nexport interface ChannelsInput {\n\t/** Channels to enable */\n\tchannels: Array<{\n\t\ttype: string;\n\t\tenabled: boolean;\n\t\ttoken?: string;\n\t\tcredentials?: Record<string, string>;\n\t\tsettings?: Record<string, string | boolean | number>;\n\t}>;\n\t/** DM policy configuration */\n\tdmPolicy?: {\n\t\tallowUnknownSenders?: boolean;\n\t\trequireApproval?: boolean;\n\t};\n\t/** Skip channels configuration */\n\tskip?: boolean;\n}\n\nexport interface SkillsInput {\n\t/** Skills to enable */\n\tskills: string[];\n\t/** Skills to install */\n\tinstall: string[];\n\t/** Installation preferences */\n\tpreferences?: {\n\t\tuseHomebrew?: boolean;\n\t\tnodeManager?: \"npm\" | \"pnpm\" | \"bun\";\n\t};\n\t/** Skip skills configuration */\n\tskip?: boolean;\n}\n\n/**\n * Union type for step-specific inputs.\n */\nexport type OnboardingInput =\n\t| { step: typeof OnboardingStep.WELCOME; data: WelcomeInput }\n\t| { step: typeof OnboardingStep.RISK_ACK; data: RiskAckInput }\n\t| { step: typeof OnboardingStep.AUTH; data: AuthInput }\n\t| { step: typeof OnboardingStep.CHANNELS; data: ChannelsInput }\n\t| { step: typeof OnboardingStep.SKILLS; data: SkillsInput }\n\t| { step: typeof OnboardingStep.COMPLETE; data: Record<string, never> };\n\n/**\n * Result of advancing a step.\n */\nexport interface OnboardingResult {\n\t/** Whether the step was successfully processed */\n\tsuccess: boolean;\n\t/** The new current step after processing */\n\tnewStep: OnboardingStep;\n\t/** Whether onboarding is now complete */\n\tisComplete: boolean;\n\t/** Error if the step failed */\n\terror?: OnboardingStepError;\n\t/** Message to display to the user */\n\tmessage?: string;\n\t/** Updated context */\n\tcontext: OnboardingContext;\n\t/** Data returned from the step (e.g., validation results) */\n\tdata?: Record<string, unknown>;\n}\n\n/**\n * Progress information for display.\n */\nexport interface OnboardingProgress {\n\t/** Current step number (1-indexed) */\n\tcurrentStepNumber: number;\n\t/** Total number of steps */\n\ttotalSteps: number;\n\t/** Completion percentage (0-100) */\n\tpercentage: number;\n\t/** List of step statuses */\n\tsteps: Array<{\n\t\tstep: OnboardingStep;\n\t\tlabel: string;\n\t\tstatus: \"completed\" | \"current\" | \"pending\" | \"error\";\n\t\terrorMessage?: string;\n\t}>;\n\t/** Estimated time remaining (in seconds) */\n\testimatedTimeRemaining?: number;\n}\n\n/**\n * Serialized onboarding state for persistence.\n */\nexport interface SerializedOnboardingState {\n\t/** Version of the serialization format */\n\tversion: number;\n\t/** The onboarding context */\n\tcontext: OnboardingContext;\n\t/** Checksum for integrity verification */\n\tchecksum?: string;\n}\n\n/**\n * Labels for each onboarding step (for UI display).\n */\nexport const ONBOARDING_STEP_LABELS: Record<OnboardingStep, string> = {\n\t[OnboardingStep.WELCOME]: \"Welcome\",\n\t[OnboardingStep.RISK_ACK]: \"Risk Acknowledgement\",\n\t[OnboardingStep.AUTH]: \"Authentication\",\n\t[OnboardingStep.CHANNELS]: \"Channels\",\n\t[OnboardingStep.SKILLS]: \"Skills\",\n\t[OnboardingStep.COMPLETE]: \"Complete\",\n};\n\n/**\n * Descriptions for each onboarding step.\n */\nexport const ONBOARDING_STEP_DESCRIPTIONS: Record<OnboardingStep, string> = {\n\t[OnboardingStep.WELCOME]: \"Introduction to the onboarding process\",\n\t[OnboardingStep.RISK_ACK]:\n\t\t\"Review and acknowledge security risks and responsibilities\",\n\t[OnboardingStep.AUTH]: \"Configure authentication with AI model providers\",\n\t[OnboardingStep.CHANNELS]:\n\t\t\"Set up messaging channels (Discord, Telegram, etc.)\",\n\t[OnboardingStep.SKILLS]: \"Configure agent skills and capabilities\",\n\t[OnboardingStep.COMPLETE]: \"Onboarding complete - agent is ready to use\",\n};\n\n/**\n * Get the step index (0-indexed) for a given step.\n */\nexport function getStepIndex(step: OnboardingStep): number {\n\treturn ONBOARDING_STEP_ORDER.indexOf(step);\n}\n\n/**\n * Get the next step in the sequence, or null if at the end.\n */\nexport function getNextStep(\n\tcurrentStep: OnboardingStep,\n): OnboardingStep | null {\n\tconst currentIndex = getStepIndex(currentStep);\n\tif (currentIndex === -1 || currentIndex >= ONBOARDING_STEP_ORDER.length - 1) {\n\t\treturn null;\n\t}\n\treturn ONBOARDING_STEP_ORDER[currentIndex + 1];\n}\n\n/**\n * Get the previous step in the sequence, or null if at the beginning.\n */\nexport function getPreviousStep(\n\tcurrentStep: OnboardingStep,\n): OnboardingStep | null {\n\tconst currentIndex = getStepIndex(currentStep);\n\tif (currentIndex <= 0) {\n\t\treturn null;\n\t}\n\treturn ONBOARDING_STEP_ORDER[currentIndex - 1];\n}\n\n/**\n * Check if a step has been completed in the given context.\n */\nexport function isStepCompleted(\n\tcontext: OnboardingContext,\n\tstep: OnboardingStep,\n): boolean {\n\treturn context.completedSteps.includes(step);\n}\n\n/**\n * Calculate completion percentage from context.\n */\nexport function calculateProgress(context: OnboardingContext): number {\n\tconst totalSteps = ONBOARDING_STEP_ORDER.length - 1; // Exclude COMPLETE step\n\tconst completedCount = context.completedSteps.filter(\n\t\t(s) => s !== OnboardingStep.COMPLETE,\n\t).length;\n\treturn Math.round((completedCount / totalSteps) * 100);\n}\n",