@hmawla/co-assistant 1.0.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/config.ts","../src/storage/migrations/001-initial.ts","../src/storage/database.ts","../src/storage/repositories/plugin-health.ts","../src/core/app.ts","../src/core/logger.ts","../src/storage/repositories/conversation.ts","../src/storage/repositories/preferences.ts","../src/storage/repositories/plugin-state.ts","../src/ai/models.ts","../src/ai/client.ts","../src/core/errors.ts","../src/ai/session.ts","../src/plugins/registry.ts","../src/plugins/types.ts","../src/plugins/manager.ts","../src/plugins/sandbox.ts","../src/plugins/credentials.ts","../src/bot/bot.ts","../src/bot/middleware/logging.ts","../src/bot/middleware/auth.ts","../src/bot/handlers/message.ts","../src/core/heartbeat.ts","../src/core/gc.ts"],"sourcesContent":["/**\n * @module core/config\n * @description Configuration loading and validation using Zod schemas.\n *\n * Responsible for:\n * - Loading environment variables from `.env` via dotenv\n * - Loading and validating `config.json` application settings\n * - Defining Zod schemas for all configuration sections\n * - Providing a singleton accessor (`getConfig()`) with caching\n * - Creating a default config.json when none exists\n */\n\nimport { z } from \"zod\";\nimport dotenv from \"dotenv\";\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\n\n// ---------------------------------------------------------------------------\n// Error class — fallback until core/errors is fully implemented\n// ---------------------------------------------------------------------------\n\n/** Fallback error class used when `core/errors` is not yet implemented. */\nclass ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigError\";\n }\n}\n\nexport { ConfigError };\n\n// ---------------------------------------------------------------------------\n// Zod Schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Schema for environment-variable based configuration.\n *\n * Validated against `process.env` — values originate from `.env` or the\n * hosting environment.\n */\nexport const EnvConfigSchema = z.object({\n TELEGRAM_BOT_TOKEN: z.string().min(1, \"TELEGRAM_BOT_TOKEN is required\"),\n TELEGRAM_USER_ID: z.string().min(1, \"TELEGRAM_USER_ID is required\"),\n GITHUB_TOKEN: z.string().optional(),\n LOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).default(\"info\"),\n DEFAULT_MODEL: z.string().default(\"gpt-4.1\"),\n HEARTBEAT_INTERVAL_MINUTES: z.string().default(\"0\"),\n AI_SESSION_POOL_SIZE: z.string().default(\"3\"),\n});\n\n/** Inferred type for environment configuration. */\nexport type EnvConfig = z.infer<typeof EnvConfigSchema>;\n\n/**\n * Schema for a single credential entry in a plugin manifest.\n */\nexport const PluginCredentialEntrySchema = z.object({\n key: z.string(),\n description: z.string(),\n type: z.string().optional(),\n});\n\n/** Inferred type for a plugin credential entry. */\nexport type PluginCredentialEntry = z.infer<typeof PluginCredentialEntrySchema>;\n\n/**\n * Schema for an individual plugin's runtime configuration.\n */\nexport const PluginConfigSchema = z.object({\n enabled: z.boolean(),\n credentials: z.record(z.string(), z.string()),\n});\n\n/** Inferred type for a single plugin's configuration. */\nexport type PluginConfig = z.infer<typeof PluginConfigSchema>;\n\n/**\n * Schema for Telegram bot behaviour settings.\n */\nexport const BotConfigSchema = z.object({\n maxMessageLength: z.number().default(4096),\n typingIndicator: z.boolean().default(true),\n});\n\n/** Inferred type for bot configuration. */\nexport type BotConfig = z.infer<typeof BotConfigSchema>;\n\n/**\n * Schema for AI / LLM interaction settings.\n */\nexport const AIConfigSchema = z.object({\n maxRetries: z.number().default(3),\n sessionTimeout: z.number().default(3600000),\n});\n\n/** Inferred type for AI configuration. */\nexport type AIConfig = z.infer<typeof AIConfigSchema>;\n\n/**\n * Schema for plugin health-check settings.\n */\nexport const PluginHealthConfigSchema = z.object({\n maxFailures: z.number().default(5),\n checkInterval: z.number().default(60000),\n});\n\n/** Inferred type for plugin health configuration. */\nexport type PluginHealthConfig = z.infer<typeof PluginHealthConfigSchema>;\n\n/**\n * Top-level application configuration schema (loaded from `config.json`).\n */\nexport const AppConfigSchema = z.object({\n plugins: z.record(z.string(), PluginConfigSchema).default({}),\n bot: BotConfigSchema.default({ maxMessageLength: 4096, typingIndicator: true }),\n ai: AIConfigSchema.default({ maxRetries: 3, sessionTimeout: 3600000 }),\n pluginHealth: PluginHealthConfigSchema.default({ maxFailures: 5, checkInterval: 60000 }),\n});\n\n/** Inferred type for the full application configuration. */\nexport type AppConfig = z.infer<typeof AppConfigSchema>;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Formats Zod validation issues into a human-readable multi-line string that\n * lists **every** failing field, not just the first.\n */\nfunction formatZodErrors(error: z.ZodError): string {\n return error.issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\";\n return ` • ${path}: ${issue.message}`;\n })\n .join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Load and validate environment variables.\n *\n * Calls `dotenv.config()` first so that `.env` values are merged into\n * `process.env`. If the `.env` file is missing a warning is logged but\n * execution continues (variables may be set by the host environment).\n *\n * @returns Validated {@link EnvConfig} object.\n * @throws {ConfigError} When one or more required env vars are missing or\n * fail validation. The error message lists **all** failing fields.\n */\nexport function loadEnvConfig(): EnvConfig {\n const result = dotenv.config();\n if (result.error) {\n // .env file missing — not fatal; env vars may come from the host\n console.warn(\n \"[config] .env file not found or unreadable — continuing with existing environment variables\",\n );\n }\n\n const parsed = EnvConfigSchema.safeParse(process.env);\n\n if (!parsed.success) {\n const details = formatZodErrors(parsed.error);\n throw new ConfigError(\n `Environment variable validation failed:\\n${details}`,\n );\n }\n\n return parsed.data;\n}\n\n/**\n * Load and validate the application configuration from a JSON file.\n *\n * If the file does not exist a default `config.json` is created from the\n * schema defaults so the application can start with sensible values.\n *\n * @param configPath - Path to the JSON config file. Defaults to\n * `./config.json`.\n * @returns Validated {@link AppConfig} object.\n * @throws {ConfigError} When the file exists but contains invalid JSON or\n * fails schema validation.\n */\nexport function loadAppConfig(configPath: string = \"./config.json\"): AppConfig {\n let raw: unknown = {};\n\n if (existsSync(configPath)) {\n try {\n const content = readFileSync(configPath, \"utf-8\");\n raw = JSON.parse(content);\n } catch (err) {\n throw new ConfigError(\n `Failed to read or parse config file at \"${configPath}\": ${(err as Error).message}`,\n );\n }\n } else {\n // Create a default config.json from schema defaults\n const defaults = AppConfigSchema.parse({});\n try {\n writeFileSync(configPath, JSON.stringify(defaults, null, 2) + \"\\n\", \"utf-8\");\n } catch {\n console.warn(\n `[config] Could not write default config to \"${configPath}\" — using in-memory defaults`,\n );\n }\n raw = defaults;\n }\n\n const parsed = AppConfigSchema.safeParse(raw);\n\n if (!parsed.success) {\n const details = formatZodErrors(parsed.error);\n throw new ConfigError(\n `Application config validation failed (${configPath}):\\n${details}`,\n );\n }\n\n return parsed.data;\n}\n\n// ---------------------------------------------------------------------------\n// Singleton cache\n// ---------------------------------------------------------------------------\n\nlet cachedConfig: { env: EnvConfig; app: AppConfig } | null = null;\n\n/**\n * Return the combined configuration singleton.\n *\n * On first call, loads both the environment and application configs,\n * validates them, and caches the result. Subsequent calls return the\n * cached value.\n *\n * @returns An object containing both `env` and `app` configurations.\n * @throws {ConfigError} If either configuration source fails validation.\n */\nexport function getConfig(): { env: EnvConfig; app: AppConfig } {\n if (!cachedConfig) {\n cachedConfig = {\n env: loadEnvConfig(),\n app: loadAppConfig(),\n };\n }\n return cachedConfig;\n}\n\n/**\n * Clear the cached configuration singleton.\n *\n * Useful in tests to force a fresh reload of configuration on the next\n * `getConfig()` call.\n */\nexport function resetConfig(): void {\n cachedConfig = null;\n}\n","/**\n * @module storage/migrations/001-initial\n * @description Initial database migration: creates conversations, plugin_state, preferences,\n * and plugin_health tables with supporting indexes.\n */\n\nimport type { Migration } from \"../database.js\";\n\n/** Initial schema migration for the co-assistant database. */\nconst migration: Migration = {\n id: \"001-initial\",\n up: `\n CREATE TABLE IF NOT EXISTS conversations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),\n content TEXT NOT NULL,\n model TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n\n CREATE TABLE IF NOT EXISTS plugin_state (\n plugin_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (plugin_id, key)\n );\n\n CREATE TABLE IF NOT EXISTS preferences (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n\n CREATE TABLE IF NOT EXISTS plugin_health (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n plugin_id TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('ok', 'error', 'disabled')),\n error_message TEXT,\n checked_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n\n CREATE INDEX IF NOT EXISTS idx_conversations_created_at ON conversations(created_at);\n CREATE INDEX IF NOT EXISTS idx_plugin_health_plugin_id ON plugin_health(plugin_id);\n `,\n};\n\nexport default migration;\n","/**\n * @module storage/database\n * @description SQLite database initialization and connection management using better-sqlite3.\n * Provides a singleton database instance with WAL mode, automatic directory creation,\n * and a migration runner that tracks applied migrations in a `_migrations` table.\n */\n\nimport Database from \"better-sqlite3\";\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport initialMigration from \"./migrations/001-initial.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A database migration descriptor. */\nexport interface Migration {\n /** Unique identifier for this migration (e.g. \"001-initial\"). */\n id: string;\n /** SQL statements to execute when applying the migration. */\n up: string;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/** Default path for the SQLite database file. */\nconst DEFAULT_DB_PATH = \"./data/co-assistant.db\";\n\n/** Ordered list of all migrations to apply. */\nconst MIGRATIONS: Migration[] = [initialMigration];\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\nlet instance: Database.Database | null = null;\n\n/**\n * Ensure the `_migrations` tracking table exists.\n *\n * @param db - The open database connection.\n */\nfunction ensureMigrationsTable(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS _migrations (\n id TEXT PRIMARY KEY,\n applied_at DATETIME DEFAULT CURRENT_TIMESTAMP\n );\n `);\n}\n\n/**\n * Run all pending migrations inside individual transactions.\n *\n * Each migration is wrapped in its own transaction so that a failure in one\n * migration does not leave the database in a partially-migrated state for\n * previously-successful migrations.\n *\n * @param db - The open database connection.\n */\nfunction runMigrations(db: Database.Database): void {\n ensureMigrationsTable(db);\n\n const applied = new Set(\n (db.prepare(\"SELECT id FROM _migrations\").all() as { id: string }[]).map(\n (row) => row.id,\n ),\n );\n\n for (const migration of MIGRATIONS) {\n if (applied.has(migration.id)) continue;\n\n const applyMigration = db.transaction(() => {\n db.exec(migration.up);\n db.prepare(\"INSERT INTO _migrations (id) VALUES (?)\").run(migration.id);\n });\n\n try {\n applyMigration();\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error);\n throw new Error(\n `Migration \"${migration.id}\" failed: ${message}`,\n );\n }\n }\n}\n\n/**\n * Returns the singleton `better-sqlite3` database instance.\n *\n * On first call the function:\n * 1. Creates the parent directory for the database file if it doesn't exist.\n * 2. Opens (or creates) the SQLite database.\n * 3. Enables WAL journal mode for better concurrent read performance.\n * 4. Runs any pending migrations.\n *\n * @param dbPath - Optional path override; defaults to `./data/co-assistant.db`.\n * @returns The open `Database` instance.\n */\nexport function getDatabase(dbPath: string = DEFAULT_DB_PATH): Database.Database {\n if (instance) return instance;\n\n // Ensure the data directory exists\n mkdirSync(dirname(dbPath), { recursive: true });\n\n instance = new Database(dbPath);\n\n // Enable WAL mode for better concurrent read performance\n instance.pragma(\"journal_mode = WAL\");\n\n // Run pending migrations\n runMigrations(instance);\n\n return instance;\n}\n\n/**\n * Closes the singleton database connection for graceful shutdown.\n *\n * After calling this function, {@link getDatabase} will open a fresh\n * connection on its next invocation.\n */\nexport function closeDatabase(): void {\n if (instance) {\n instance.close();\n instance = null;\n }\n}\n","/**\n * @module storage/repositories/plugin-health\n * @description Repository for plugin health-check logging and querying.\n * Stores periodic health snapshots so the system can detect degraded plugins.\n */\n\nimport type Database from \"better-sqlite3\";\nimport { getDatabase } from \"../database.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single plugin health-check record. */\nexport interface PluginHealthEntry {\n /** Auto-incremented primary key. */\n id: number;\n /** The unique plugin identifier. */\n plugin_id: string;\n /** Health status (`ok`, `error`, or `disabled`). */\n status: string;\n /** Optional error message when status is `error`. */\n error_message: string | null;\n /** ISO-8601 timestamp of the health check. */\n checked_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Repository\n// ---------------------------------------------------------------------------\n\n/**\n * Repository for recording and querying plugin health status.\n */\nexport class PluginHealthRepository {\n private db: Database.Database;\n\n /** Create a new repository backed by the singleton database. */\n constructor(db?: Database.Database) {\n this.db = db ?? getDatabase();\n }\n\n /**\n * Record a health-check result for a plugin.\n *\n * @param pluginId - The unique plugin identifier.\n * @param status - Health status (`ok`, `error`, or `disabled`).\n * @param errorMessage - Optional error details.\n */\n logHealth(pluginId: string, status: string, errorMessage?: string): void {\n this.db\n .prepare(\n \"INSERT INTO plugin_health (plugin_id, status, error_message) VALUES (?, ?, ?)\",\n )\n .run(pluginId, status, errorMessage ?? null);\n }\n\n /**\n * Retrieve the most recent health-check entries for a plugin.\n *\n * @param pluginId - The unique plugin identifier.\n * @param limit - Maximum number of entries to return (default `10`).\n * @returns An array of {@link PluginHealthEntry} objects, newest first.\n */\n getRecentHealth(pluginId: string, limit: number = 10): PluginHealthEntry[] {\n return this.db\n .prepare(\n \"SELECT id, plugin_id, status, error_message, checked_at FROM plugin_health WHERE plugin_id = ? ORDER BY checked_at DESC LIMIT ?\",\n )\n .all(pluginId, limit) as PluginHealthEntry[];\n }\n\n /**\n * Count the number of `error` health entries for a plugin within a time window.\n *\n * @param pluginId - The unique plugin identifier.\n * @param sinceMinutes - Look-back window in minutes (default `60`).\n * @returns The number of error entries in the window.\n */\n getFailureCount(pluginId: string, sinceMinutes: number = 60): number {\n const row = this.db\n .prepare(\n `SELECT COUNT(*) AS cnt FROM plugin_health\n WHERE plugin_id = ? AND status = 'error'\n AND checked_at >= datetime('now', '-' || ? || ' minutes')`,\n )\n .get(pluginId, sinceMinutes) as { cnt: number };\n return row.cnt;\n }\n}\n","/**\n * @module core/app\n * @description Main application orchestrator — boots the database, plugin\n * system, AI session, and Telegram bot in the correct order, and tears\n * everything down gracefully on shutdown.\n *\n * Usage:\n * ```ts\n * const app = new App();\n * await app.start({ verbose: true });\n * ```\n */\n\nimport dns from \"node:dns\";\nimport type { Logger } from \"pino\";\nimport { createChildLogger, setLogLevel } from \"./logger.js\";\nimport { getConfig } from \"./config.js\";\n\n// Force IPv4-first DNS resolution. Many networks have broken IPv6 connectivity,\n// which causes Node.js (which defaults to IPv6-first) to hang when connecting\n// to services like api.telegram.org that publish both A and AAAA records.\ndns.setDefaultResultOrder(\"ipv4first\");\nimport { getDatabase, closeDatabase } from \"../storage/database.js\";\nimport { ConversationRepository } from \"../storage/repositories/conversation.js\";\nimport { PreferencesRepository } from \"../storage/repositories/preferences.js\";\nimport { PluginStateRepository } from \"../storage/repositories/plugin-state.js\";\nimport { createModelRegistry } from \"../ai/models.js\";\nimport { copilotClient } from \"../ai/client.js\";\nimport { sessionManager } from \"../ai/session.js\";\nimport { createPluginRegistry } from \"../plugins/registry.js\";\nimport { createPluginManager } from \"../plugins/manager.js\";\nimport { pluginSandbox } from \"../plugins/sandbox.js\";\nimport { credentialManager } from \"../plugins/credentials.js\";\nimport { createBot, type TelegramBot } from \"../bot/bot.js\";\nimport type { Context } from \"telegraf\";\nimport { createMessageHandler, splitMessage } from \"../bot/handlers/message.js\";\nimport { HeartbeatManager } from \"./heartbeat.js\";\nimport { GarbageCollector } from \"./gc.js\";\nimport type { PluginManager } from \"../plugins/manager.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Options accepted by {@link App.start}. */\nexport interface StartOptions {\n /** Enable debug-level logging. */\n verbose?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// App\n// ---------------------------------------------------------------------------\n\n/**\n * Top-level application orchestrator.\n *\n * Coordinates the full startup and shutdown sequence for all subsystems:\n * database → repositories → plugins → AI client/session → Telegram bot.\n */\nexport class App {\n private logger: Logger;\n private bot?: TelegramBot;\n private pluginManager?: PluginManager;\n private heartbeatManager?: HeartbeatManager;\n private gc?: GarbageCollector;\n private isShuttingDown: boolean = false;\n private verbose: boolean = false;\n\n constructor() {\n this.logger = createChildLogger(\"app\");\n }\n\n // -----------------------------------------------------------------------\n // Startup\n // -----------------------------------------------------------------------\n\n /**\n * Boot the entire application.\n *\n * Initialisation order:\n * 1. Configuration & logging\n * 2. Database & repositories\n * 3. Model registry\n * 4. Plugin system (registry → manager)\n * 5. Copilot AI client & session\n * 6. Telegram bot (handlers → launch)\n * 7. OS signal handlers for graceful shutdown\n *\n * @param options - Optional start-up flags.\n */\n async start(options?: StartOptions): Promise<void> {\n // 1. Load configuration\n console.log(\" ▸ Loading configuration…\");\n const config = getConfig();\n this.logger.info(\"Configuration loaded\");\n\n // 2. Verbose logging\n if (options?.verbose) {\n setLogLevel(\"debug\");\n this.verbose = true;\n this.logger.debug(\"Verbose logging enabled\");\n }\n\n // 3. Initialise database\n console.log(\" ▸ Initializing database…\");\n const db = getDatabase();\n this.logger.info(\"Database ready\");\n\n // 4. Create repositories\n const conversationRepo = new ConversationRepository(db);\n const preferencesRepo = new PreferencesRepository(db);\n const pluginStateRepo = new PluginStateRepository(db);\n this.logger.info(\"Repositories created\");\n\n // 4b. Start garbage collector for DB retention and memory monitoring\n const gc = new GarbageCollector({\n intervalMinutes: 30,\n conversationRetentionDays: 30,\n healthRetentionDays: 7,\n });\n gc.start(db);\n this.gc = gc;\n\n // 5. Model registry\n const modelRegistry = createModelRegistry(preferencesRepo);\n this.logger.info(\"Model registry initialised\");\n\n // 6. Plugin system\n console.log(\" ▸ Discovering plugins…\");\n const pluginRegistry = createPluginRegistry();\n await pluginRegistry.discoverPlugins();\n\n const pluginManager = createPluginManager(\n pluginRegistry,\n pluginSandbox,\n credentialManager,\n pluginStateRepo,\n );\n await pluginManager.initialize();\n this.pluginManager = pluginManager;\n this.logger.info(\"Plugin system ready\");\n\n // 7. Start Copilot client\n console.log(\" ▸ Starting Copilot SDK client…\");\n await copilotClient.start();\n\n // 8. Resolve model & aggregate tools from plugins\n const currentModel = modelRegistry.getCurrentModelId();\n const activePlugins = pluginManager.getActivePlugins();\n const pluginTools = pluginManager.getAllTools();\n const poolSize = Math.max(1, parseInt(config.env.AI_SESSION_POOL_SIZE || \"3\", 10));\n\n // 9. Create AI session pool with model and plugin tools\n console.log(` ▸ Creating AI session pool (model: ${currentModel}, sessions: ${poolSize})…`);\n await sessionManager.createSession(currentModel, pluginTools, poolSize);\n\n // 10. Create and launch Telegram bot\n console.log(\" ▸ Connecting to Telegram…\");\n const bot = createBot(config.env.TELEGRAM_BOT_TOKEN);\n\n const rawMessageHandler = createMessageHandler({\n sessionManager,\n conversationRepo,\n });\n\n // Wrap the message handler with debug console logging\n const messageHandler = async (ctx: Context, text: string): Promise<void> => {\n const ts = () => new Date().toLocaleTimeString();\n const preview = text.length > 60 ? text.slice(0, 60) + \"…\" : text;\n\n if (this.verbose) {\n console.log(` ⇣ [${ts()}] Message received: \"${preview}\"`);\n }\n\n const startTime = Date.now();\n await rawMessageHandler(ctx, text);\n\n if (this.verbose) {\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);\n console.log(` ⇡ [${ts()}] Message completed (${elapsed}s)`);\n }\n };\n\n // Heartbeat manager — created early so the command handler can reference it\n const heartbeatManager = new HeartbeatManager();\n this.heartbeatManager = heartbeatManager;\n\n /**\n * Runs one or all heartbeat events on demand and delivers results in-chat.\n * Replies are threaded to the user's original /heartbeat command message.\n */\n const runHeartbeatOnDemand = async (ctx: Context, eventName?: string) => {\n const replyToId = ctx.message?.message_id;\n const replyOpts = replyToId\n ? { reply_parameters: { message_id: replyToId } } as Record<string, unknown>\n : {};\n\n const allEvents = heartbeatManager.listEvents();\n\n if (allEvents.length === 0) {\n await ctx.reply(\"No heartbeat events configured.\\nAdd one with: co-assistant heartbeat add\", replyOpts);\n return;\n }\n\n let eventsToRun = allEvents;\n if (eventName) {\n const match = allEvents.find((e) => e.name === eventName);\n if (!match) {\n const names = allEvents.map((e) => e.name).join(\", \");\n await ctx.reply(`❌ Heartbeat \"${eventName}\" not found.\\nAvailable: ${names}`, replyOpts);\n return;\n }\n eventsToRun = [match];\n }\n\n await ctx.reply(`🚀 Running ${eventsToRun.length} heartbeat event(s)…`, replyOpts);\n\n // Keep typing indicator alive while processing\n const typingInterval = setInterval(() => {\n ctx.sendChatAction(\"typing\").catch(() => {});\n }, 4000);\n\n try {\n for (const event of eventsToRun) {\n try {\n // Inject dedup state\n const useDedup = event.prompt.includes(\"{{DEDUP_STATE}}\");\n const state = useDedup ? heartbeatManager.loadState(event.name) : null;\n let finalPrompt = event.prompt;\n if (state) {\n finalPrompt = event.prompt.replace(\n \"{{DEDUP_STATE}}\",\n state.processedIds.length > 0\n ? `Previously processed IDs (${state.processedIds.length} total) — SKIP these:\\n${state.processedIds.map((id) => `- ${id}`).join(\"\\n\")}`\n : \"No previously processed items — this is the first run.\",\n );\n }\n\n const response = await sessionManager.sendMessage(finalPrompt);\n\n if (!response) {\n await ctx.reply(`⚠️ Heartbeat \"${event.name}\" returned no response.`, replyOpts);\n continue;\n }\n\n // Persist dedup IDs — use a Set to avoid duplicates\n const processedRe = /<!--\\s*PROCESSED:\\s*(.*?)\\s*-->/gi;\n if (useDedup && state) {\n const existing = new Set(state.processedIds);\n let m: RegExpExecArray | null;\n while ((m = processedRe.exec(response)) !== null) {\n for (const id of (m[1] ?? \"\").split(\",\")) {\n const t = id.trim();\n if (t && !existing.has(t)) {\n state.processedIds.push(t);\n existing.add(t);\n }\n }\n }\n processedRe.lastIndex = 0;\n state.lastRun = new Date().toISOString();\n heartbeatManager.saveState(event.name, state);\n }\n\n // Clean marker and deliver — threaded to original command\n const clean = response.replace(/<!--\\s*PROCESSED:\\s*.*?\\s*-->/gi, \"\").trim();\n if (clean) {\n const header = `💓 *Heartbeat: ${event.name}*\\n\\n`;\n const chunks = splitMessage(header + clean);\n for (const chunk of chunks) {\n await ctx.reply(chunk, { parse_mode: \"Markdown\", ...replyOpts } as Record<string, unknown>);\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.logger.error({ err, event: event.name }, \"On-demand heartbeat failed\");\n await ctx.reply(`❌ Heartbeat \"${event.name}\" failed: ${msg}`, replyOpts);\n }\n }\n } finally {\n clearInterval(typingInterval);\n }\n };\n\n /** Handle bot commands: /heartbeat [name] */\n const commandHandler = async (ctx: Context, command: string, args: string) => {\n const ts = () => new Date().toLocaleTimeString();\n const replyToId = ctx.message?.message_id;\n const replyOpts = replyToId\n ? { reply_parameters: { message_id: replyToId } } as Record<string, unknown>\n : {};\n\n if (this.verbose) {\n console.log(` ⇣ [${ts()}] Command received: /${command}${args ? \" \" + args : \"\"}`);\n }\n\n const startTime = Date.now();\n\n switch (command) {\n case \"heartbeat\":\n case \"hb\":\n await runHeartbeatOnDemand(ctx, args.trim() || undefined);\n break;\n\n case \"help\":\n await ctx.reply(\n \"🤖 *Co-Assistant Commands*\\n\\n\" +\n \"/heartbeat \\\\[name\\\\] — Run heartbeat event\\\\(s\\\\)\\n\" +\n \"/hb \\\\[name\\\\] — Shorthand for /heartbeat\\n\" +\n \"/help — Show this message\\n\\n\" +\n \"Or just send a message to chat with the AI\\\\.\",\n { parse_mode: \"MarkdownV2\", ...replyOpts } as Record<string, unknown>,\n );\n break;\n\n default:\n // Unknown command — fall through to message handler\n await messageHandler(ctx, `/${command} ${args}`.trim());\n }\n\n if (this.verbose) {\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);\n console.log(` ⇡ [${ts()}] Command completed: /${command} (${elapsed}s)`);\n }\n };\n\n bot.initialize({\n allowedUserId: Number(config.env.TELEGRAM_USER_ID),\n onMessage: messageHandler,\n onCommand: commandHandler,\n });\n\n try {\n await bot.launch();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n this.logger.error({ err }, \"Telegram bot failed to start\");\n console.error(`\\n✗ Telegram bot failed to start:\\n ${msg}\\n`);\n await this.shutdown();\n return;\n }\n this.bot = bot;\n\n // 11. Startup banner — visible in the console\n const pluginCount = activePlugins.size;\n const botUsername = bot.getBot().botInfo?.username ?? \"unknown\";\n const pluginNames = [...activePlugins.keys()].join(\", \") || \"none\";\n\n this.logger.info(\n { model: currentModel, plugins: pluginCount, version: \"1.0.0\" },\n `Co-Assistant is running! Model: ${currentModel}, Plugins: ${pluginCount}`,\n );\n\n console.log(\"\");\n console.log(\" ╔══════════════════════════════════════════════╗\");\n console.log(\" ║ 🤖 Co-Assistant is running! ║\");\n console.log(\" ╚══════════════════════════════════════════════╝\");\n console.log(\"\");\n console.log(` Bot: @${botUsername}`);\n console.log(` Model: ${currentModel}`);\n console.log(` Sessions: ${poolSize} (parallel processing)`);\n console.log(` Plugins: ${pluginNames} (${pluginCount} active)`);\n\n // 12. Start heartbeat scheduler\n const heartbeatInterval = parseInt(config.env.HEARTBEAT_INTERVAL_MINUTES || \"0\", 10);\n const heartbeatEvents = heartbeatManager.listEvents();\n\n if (heartbeatInterval > 0 && heartbeatEvents.length > 0) {\n heartbeatManager.start(\n heartbeatInterval,\n // Send heartbeat prompt to the AI session\n async (prompt) => sessionManager.sendMessage(prompt),\n // Forward the AI's response to the user via Telegram\n async (eventName, response) => {\n try {\n const chatId = config.env.TELEGRAM_USER_ID;\n const header = `💓 *Heartbeat: ${eventName}*\\n\\n`;\n const fullMessage = header + response;\n\n // Split long responses to respect Telegram's 4096-char limit\n const chunks = splitMessage(fullMessage);\n for (const chunk of chunks) {\n await bot.getBot().telegram.sendMessage(chatId, chunk, { parse_mode: \"Markdown\" });\n }\n } catch (err) {\n this.logger.error({ err, eventName }, \"Failed to send heartbeat response via Telegram\");\n }\n },\n );\n console.log(` Heartbeat: every ${heartbeatInterval} min (${heartbeatEvents.length} events)`);\n } else if (heartbeatInterval > 0) {\n console.log(` Heartbeat: every ${heartbeatInterval} min (no events — add with: co-assistant heartbeat add)`);\n } else {\n console.log(\" Heartbeat: disabled (set HEARTBEAT_INTERVAL_MINUTES in .env)\");\n }\n\n console.log(\"\");\n console.log(\" Open Telegram and send a message to your bot to get started.\");\n console.log(\" Press Ctrl+C to stop.\\n\");\n\n // 12. Graceful shutdown on OS signals\n const shutdownHandler = async (signal: string) => {\n this.logger.info({ signal }, \"Received shutdown signal\");\n await this.shutdown();\n };\n process.once(\"SIGINT\", () => shutdownHandler(\"SIGINT\"));\n process.once(\"SIGTERM\", () => shutdownHandler(\"SIGTERM\"));\n }\n\n // -----------------------------------------------------------------------\n // Shutdown\n // -----------------------------------------------------------------------\n\n /**\n * Gracefully shut down all subsystems in reverse order.\n *\n * Each step is wrapped in its own try/catch so a failure in one subsystem\n * never prevents the others from cleaning up.\n */\n async shutdown(): Promise<void> {\n if (this.isShuttingDown) {\n this.logger.warn(\"Shutdown already in progress — skipping\");\n return;\n }\n this.isShuttingDown = true;\n this.logger.info(\"Initiating graceful shutdown…\");\n\n // 1. Stop heartbeat scheduler\n if (this.heartbeatManager) {\n this.heartbeatManager.stop();\n this.logger.info(\"Heartbeat scheduler stopped\");\n }\n\n // 1b. Stop garbage collector\n if (this.gc) {\n this.gc.stop();\n }\n\n // 2. Stop Telegram bot\n try {\n if (this.bot) {\n await this.bot.stop();\n this.logger.info(\"Telegram bot stopped\");\n }\n } catch (err) {\n this.logger.error({ err }, \"Error stopping Telegram bot\");\n }\n\n // 3. Close AI session\n try {\n await sessionManager.closeSession();\n this.logger.info(\"AI session closed\");\n } catch (err) {\n this.logger.error({ err }, \"Error closing AI session\");\n }\n\n // 4. Stop Copilot client\n try {\n await copilotClient.stop();\n this.logger.info(\"Copilot client stopped\");\n } catch (err) {\n this.logger.error({ err }, \"Error stopping Copilot client\");\n }\n\n // 5. Shutdown plugins\n try {\n if (this.pluginManager) {\n await this.pluginManager.shutdown();\n this.logger.info(\"Plugins shut down\");\n }\n } catch (err) {\n this.logger.error({ err }, \"Error shutting down plugins\");\n }\n\n // 6. Close database\n try {\n closeDatabase();\n this.logger.info(\"Database closed\");\n } catch (err) {\n this.logger.error({ err }, \"Error closing database\");\n }\n\n this.logger.info(\"👋 Goodbye!\");\n process.exit(0);\n }\n}\n","/**\n * @module core/logger\n * @description Structured logging with pino.\n *\n * Strategy:\n * - A single root logger is created at module load.\n * - Log level is read from the `LOG_LEVEL` env var (default: \"info\").\n * - In development (`NODE_ENV !== \"production\"`) the `pino-pretty` transport\n * is used for human-readable console output. If pino-pretty is not\n * installed the logger falls back silently to standard JSON output.\n * - In production, logs are emitted as newline-delimited JSON to stdout.\n * - Subsystems obtain namespaced child loggers via `createChildLogger` so\n * every log line carries a `component` field for easy filtering.\n */\n\nimport pino from \"pino\";\nimport type { Logger } from \"pino\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Resolve the initial log level from the environment. */\nfunction resolveLogLevel(): string {\n const envLevel = process.env.LOG_LEVEL?.toLowerCase();\n const valid = [\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\", \"silent\"];\n return envLevel && valid.includes(envLevel) ? envLevel : \"info\";\n}\n\n/**\n * Build pino options, optionally including the pino-pretty transport when\n * running outside of production. Uses a dynamic import check that works in\n * both CJS and ESM environments.\n */\nfunction buildLoggerOptions(): pino.LoggerOptions {\n const level = resolveLogLevel();\n const isProduction = process.env.NODE_ENV === \"production\";\n\n const opts: pino.LoggerOptions = { level };\n\n if (!isProduction) {\n // pino resolves the transport target via its own worker thread import,\n // so we just need to specify the module name — no require.resolve needed.\n opts.transport = {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"SYS:HH:MM:ss\",\n ignore: \"pid,hostname\",\n },\n };\n }\n\n return opts;\n}\n\n// ---------------------------------------------------------------------------\n// Root logger\n// ---------------------------------------------------------------------------\n\n/**\n * The application-wide root pino logger.\n *\n * @example\n * ```ts\n * import { logger } from \"./logger.js\";\n * logger.info(\"Application starting\");\n * ```\n */\nexport const logger: Logger = pino(buildLoggerOptions());\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a child logger scoped to a named component.\n *\n * Child loggers inherit the root logger's level and transport while\n * automatically including a `component` field (and any extra metadata) in\n * every log entry, making it easy to filter logs per subsystem.\n *\n * @param name - Logical component name, e.g. `\"plugin:gmail\"` or `\"bot\"`.\n * @param meta - Optional extra key-value pairs merged into every log line.\n * @returns A pino child logger instance.\n *\n * @example\n * ```ts\n * const pluginLog = createChildLogger(\"plugin:gmail\", { pluginId: \"gmail\" });\n * pluginLog.info(\"Plugin initialized\");\n * pluginLog.error({ err: error }, \"Plugin failed\");\n * ```\n */\nexport function createChildLogger(\n name: string,\n meta?: Record<string, unknown>,\n): Logger {\n return logger.child({ component: name, ...meta });\n}\n\n/**\n * Dynamically change the root logger's level at runtime.\n *\n * All existing child loggers inherit the new level because pino child loggers\n * delegate to the parent's level unless they were created with an explicit\n * override.\n *\n * @param level - A valid pino log level string (e.g. `\"debug\"`, `\"warn\"`).\n *\n * @example\n * ```ts\n * setLogLevel(\"debug\"); // enable verbose logging\n * ```\n */\nexport function setLogLevel(level: string): void {\n logger.level = level;\n}\n","/**\n * @module storage/repositories/conversation\n * @description Repository for conversation message persistence operations.\n * Provides methods to add, retrieve, count, and clear conversation messages.\n */\n\nimport type Database from \"better-sqlite3\";\nimport { getDatabase } from \"../database.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single conversation message row returned from the database. */\nexport interface ConversationMessage {\n /** Auto-incremented primary key. */\n id: number;\n /** The role of the message author (`user`, `assistant`, or `system`). */\n role: string;\n /** The text content of the message. */\n content: string;\n /** The AI model used to generate the message (if applicable). */\n model: string | null;\n /** ISO-8601 timestamp of when the message was created. */\n created_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Repository\n// ---------------------------------------------------------------------------\n\n/**\n * Repository for managing conversation messages in the database.\n */\nexport class ConversationRepository {\n private db: Database.Database;\n\n /** Create a new repository backed by the singleton database. */\n constructor(db?: Database.Database) {\n this.db = db ?? getDatabase();\n }\n\n /**\n * Insert a new message into the conversations table.\n *\n * @param role - The message role (`user`, `assistant`, or `system`).\n * @param content - The text content of the message.\n * @param model - Optional AI model identifier.\n */\n addMessage(role: string, content: string, model?: string): void {\n this.db\n .prepare(\n \"INSERT INTO conversations (role, content, model) VALUES (?, ?, ?)\",\n )\n .run(role, content, model ?? null);\n }\n\n /**\n * Retrieve conversation history ordered newest-first.\n *\n * @param limit - Maximum number of messages to return (default `50`).\n * @returns An array of {@link ConversationMessage} objects.\n */\n getHistory(limit: number = 50): ConversationMessage[] {\n return this.db\n .prepare(\n \"SELECT id, role, content, model, created_at FROM conversations ORDER BY created_at DESC LIMIT ?\",\n )\n .all(limit) as ConversationMessage[];\n }\n\n /**\n * Retrieve the most recent messages ordered oldest-first, suitable for\n * building an AI context window.\n *\n * @param limit - Maximum number of messages to return (default `20`).\n * @returns An array of {@link ConversationMessage} objects in chronological order.\n */\n getRecentContext(limit: number = 20): ConversationMessage[] {\n return this.db\n .prepare(\n `SELECT id, role, content, model, created_at\n FROM (\n SELECT id, role, content, model, created_at\n FROM conversations\n ORDER BY created_at DESC\n LIMIT ?\n ) sub\n ORDER BY created_at ASC`,\n )\n .all(limit) as ConversationMessage[];\n }\n\n /**\n * Delete all conversation messages.\n */\n clear(): void {\n this.db.prepare(\"DELETE FROM conversations\").run();\n }\n\n /**\n * Return the total number of stored conversation messages.\n */\n count(): number {\n const row = this.db\n .prepare(\"SELECT COUNT(*) AS cnt FROM conversations\")\n .get() as { cnt: number };\n return row.cnt;\n }\n}\n","/**\n * @module storage/repositories/preferences\n * @description Repository for user preferences and settings persistence.\n * Provides a simple key/value store for application-wide settings.\n */\n\nimport type Database from \"better-sqlite3\";\nimport { getDatabase } from \"../database.js\";\n\n// ---------------------------------------------------------------------------\n// Repository\n// ---------------------------------------------------------------------------\n\n/**\n * Repository for managing user preferences in the database.\n */\nexport class PreferencesRepository {\n private db: Database.Database;\n\n /** Create a new repository backed by the singleton database. */\n constructor(db?: Database.Database) {\n this.db = db ?? getDatabase();\n }\n\n /**\n * Retrieve a preference value by key.\n *\n * @param key - The preference key.\n * @returns The stored value, or `null` if not found.\n */\n get(key: string): string | null {\n const row = this.db\n .prepare(\"SELECT value FROM preferences WHERE key = ?\")\n .get(key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n /**\n * Insert or update a preference value.\n *\n * @param key - The preference key.\n * @param value - The value to store.\n */\n set(key: string, value: string): void {\n this.db\n .prepare(\n `INSERT INTO preferences (key, value, updated_at)\n VALUES (?, ?, CURRENT_TIMESTAMP)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP`,\n )\n .run(key, value);\n }\n\n /**\n * Retrieve all preferences as a plain object.\n *\n * @returns A record mapping each preference key to its value.\n */\n getAll(): Record<string, string> {\n const rows = this.db\n .prepare(\"SELECT key, value FROM preferences\")\n .all() as { key: string; value: string }[];\n\n const result: Record<string, string> = {};\n for (const row of rows) {\n result[row.key] = row.value;\n }\n return result;\n }\n\n /**\n * Delete a single preference by key.\n *\n * @param key - The preference key to remove.\n */\n delete(key: string): void {\n this.db.prepare(\"DELETE FROM preferences WHERE key = ?\").run(key);\n }\n}\n","/**\n * @module storage/repositories/plugin-state\n * @description Repository for plugin state persistence.\n * Allows plugins to store arbitrary key/value pairs scoped by plugin ID.\n */\n\nimport type Database from \"better-sqlite3\";\nimport { getDatabase } from \"../database.js\";\n\n// ---------------------------------------------------------------------------\n// Repository\n// ---------------------------------------------------------------------------\n\n/**\n * Repository for managing per-plugin key/value state in the database.\n */\nexport class PluginStateRepository {\n private db: Database.Database;\n\n /** Create a new repository backed by the singleton database. */\n constructor(db?: Database.Database) {\n this.db = db ?? getDatabase();\n }\n\n /**\n * Retrieve a single value for a given plugin and key.\n *\n * @param pluginId - The unique plugin identifier.\n * @param key - The state key.\n * @returns The stored value, or `null` if not found.\n */\n get(pluginId: string, key: string): string | null {\n const row = this.db\n .prepare(\"SELECT value FROM plugin_state WHERE plugin_id = ? AND key = ?\")\n .get(pluginId, key) as { value: string } | undefined;\n return row?.value ?? null;\n }\n\n /**\n * Insert or update a state value for a plugin.\n *\n * @param pluginId - The unique plugin identifier.\n * @param key - The state key.\n * @param value - The value to store.\n */\n set(pluginId: string, key: string, value: string): void {\n this.db\n .prepare(\n `INSERT INTO plugin_state (plugin_id, key, value, updated_at)\n VALUES (?, ?, ?, CURRENT_TIMESTAMP)\n ON CONFLICT(plugin_id, key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP`,\n )\n .run(pluginId, key, value);\n }\n\n /**\n * Retrieve all key/value pairs for a plugin.\n *\n * @param pluginId - The unique plugin identifier.\n * @returns A plain object mapping keys to their string values.\n */\n getAll(pluginId: string): Record<string, string> {\n const rows = this.db\n .prepare(\"SELECT key, value FROM plugin_state WHERE plugin_id = ?\")\n .all(pluginId) as { key: string; value: string }[];\n\n const result: Record<string, string> = {};\n for (const row of rows) {\n result[row.key] = row.value;\n }\n return result;\n }\n\n /**\n * Delete a single state key for a plugin.\n *\n * @param pluginId - The unique plugin identifier.\n * @param key - The state key to remove.\n */\n delete(pluginId: string, key: string): void {\n this.db\n .prepare(\"DELETE FROM plugin_state WHERE plugin_id = ? AND key = ?\")\n .run(pluginId, key);\n }\n\n /**\n * Remove all stored state for a specific plugin.\n *\n * @param pluginId - The unique plugin identifier.\n */\n clearPlugin(pluginId: string): void {\n this.db\n .prepare(\"DELETE FROM plugin_state WHERE plugin_id = ?\")\n .run(pluginId);\n }\n}\n","/**\n * @module ai/models\n * @description AI model registry, selection, and switching logic.\n *\n * Responsible for:\n * - Maintaining a list of known/supported AI models\n * - Resolving the currently active model (preferences → env default)\n * - Persisting model selection to the preferences store\n * - Allowing runtime registration of custom models (BYOK)\n */\n\nimport type { Logger } from \"pino\";\nimport { createChildLogger } from \"../core/logger.js\";\nimport { getConfig } from \"../core/config.js\";\nimport type { PreferencesRepository } from \"../storage/repositories/preferences.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Descriptor for a single AI model known to the registry.\n */\nexport interface ModelInfo {\n /** Model identifier (e.g., \"gpt-4.1\") */\n id: string;\n /** Human-readable name */\n name: string;\n /** Brief description */\n description: string;\n /** Provider (e.g., \"openai\", \"anthropic\", \"github\") */\n provider: string;\n}\n\n// ---------------------------------------------------------------------------\n// Default model catalogue\n// ---------------------------------------------------------------------------\n\n/** Built-in models shipped with the application. */\nconst DEFAULT_MODELS: ModelInfo[] = [\n // OpenAI — Premium (3x)\n { id: \"gpt-5\", name: \"GPT-5\", description: \"OpenAI GPT-5 (premium, 3x)\", provider: \"openai\" },\n { id: \"o3\", name: \"o3\", description: \"OpenAI o3 reasoning (premium, 3x)\", provider: \"openai\" },\n // OpenAI — Standard (1x)\n { id: \"gpt-4.1\", name: \"GPT-4.1\", description: \"OpenAI GPT-4.1 (1x)\", provider: \"openai\" },\n { id: \"gpt-4o\", name: \"GPT-4o\", description: \"OpenAI GPT-4o multimodal (1x)\", provider: \"openai\" },\n { id: \"o4-mini\", name: \"o4 Mini\", description: \"OpenAI o4-mini reasoning (1x)\", provider: \"openai\" },\n // OpenAI — Low (0.33x)\n { id: \"gpt-4o-mini\", name: \"GPT-4o Mini\", description: \"OpenAI GPT-4o Mini (0.33x)\", provider: \"openai\" },\n { id: \"gpt-4.1-mini\", name: \"GPT-4.1 Mini\", description: \"OpenAI GPT-4.1 Mini (0.33x)\", provider: \"openai\" },\n { id: \"gpt-5-mini\", name: \"GPT-5 Mini\", description: \"OpenAI GPT-5 Mini (0.33x)\", provider: \"openai\" },\n { id: \"o3-mini\", name: \"o3 Mini\", description: \"OpenAI o3-mini reasoning (0.33x)\", provider: \"openai\" },\n // OpenAI — Nano (0x)\n { id: \"gpt-4.1-nano\", name: \"GPT-4.1 Nano\", description: \"OpenAI GPT-4.1 Nano (0x)\", provider: \"openai\" },\n // Anthropic — Premium (3x)\n { id: \"claude-opus-4\", name: \"Claude Opus 4\", description: \"Anthropic Claude Opus 4 (premium, 3x)\", provider: \"anthropic\" },\n // Anthropic — Standard (1x)\n { id: \"claude-sonnet-4\", name: \"Claude Sonnet 4\", description: \"Anthropic Claude Sonnet 4 (1x)\", provider: \"anthropic\" },\n // Anthropic — Low (0.33x)\n { id: \"claude-haiku-4.5\", name: \"Claude Haiku 4.5\", description: \"Anthropic Claude Haiku 4.5 (0.33x)\", provider: \"anthropic\" },\n];\n\n/** Preferences key used to persist the selected model. */\nconst PREF_KEY = \"current_model\";\n\n// ---------------------------------------------------------------------------\n// ModelRegistry\n// ---------------------------------------------------------------------------\n\n/**\n * Central registry of available AI models with preference-backed selection.\n *\n * The registry is initialised with a set of built-in models and can be\n * extended at runtime via {@link addModel}. The currently active model is\n * resolved from the user's persisted preference, falling back to the\n * `DEFAULT_MODEL` environment variable when no preference exists.\n */\nexport class ModelRegistry {\n private models: ModelInfo[];\n private preferences: PreferencesRepository;\n private logger: Logger;\n\n /**\n * @param preferences - Repository used to persist the selected model.\n */\n constructor(preferences: PreferencesRepository) {\n this.models = [...DEFAULT_MODELS];\n this.preferences = preferences;\n this.logger = createChildLogger(\"ai:models\");\n this.logger.debug({ count: this.models.length }, \"Model registry initialised\");\n }\n\n /**\n * Return every model currently registered (built-in + custom).\n *\n * @returns A shallow copy of the model list.\n */\n getAvailableModels(): ModelInfo[] {\n return [...this.models];\n }\n\n /**\n * Look up a single model by its identifier.\n *\n * @param modelId - Case-sensitive model identifier.\n * @returns The matching {@link ModelInfo}, or `undefined` when not found.\n */\n getModel(modelId: string): ModelInfo | undefined {\n return this.models.find((m) => m.id === modelId);\n }\n\n /**\n * Resolve the currently active model identifier.\n *\n * Resolution order:\n * 1. Persisted preference (`current_model` key)\n * 2. `DEFAULT_MODEL` from the environment configuration\n *\n * @returns The model identifier string.\n */\n getCurrentModelId(): string {\n const persisted = this.preferences.get(PREF_KEY);\n if (persisted) {\n this.logger.debug({ modelId: persisted }, \"Current model resolved from preferences\");\n return persisted;\n }\n\n try {\n const defaultModel = getConfig().env.DEFAULT_MODEL;\n this.logger.debug({ modelId: defaultModel }, \"Current model resolved from env default\");\n return defaultModel;\n } catch {\n // Env config may not be available (e.g. CLI without .env) — use hardcoded default\n const fallback = \"gpt-4.1\";\n this.logger.debug({ modelId: fallback }, \"Current model resolved from hardcoded fallback\");\n return fallback;\n }\n }\n\n /**\n * Select a model as the active model and persist the choice.\n *\n * Unknown model IDs are **allowed** (the Copilot SDK may support models\n * not present in our registry) but a warning is logged so operators can\n * spot potential typos.\n *\n * @param modelId - The identifier of the model to activate.\n */\n setCurrentModel(modelId: string): void {\n if (!this.isValidModel(modelId)) {\n this.logger.warn(\n { modelId },\n \"Setting model that is not in the registry — it may still be valid for the provider\",\n );\n }\n\n this.preferences.set(PREF_KEY, modelId);\n this.logger.info({ modelId }, \"Current model updated\");\n }\n\n /**\n * Check whether a model identifier corresponds to a registered model.\n *\n * @param modelId - The identifier to validate.\n * @returns `true` when the model exists in the registry.\n */\n isValidModel(modelId: string): boolean {\n return this.models.some((m) => m.id === modelId);\n }\n\n /**\n * Register a custom model at runtime (e.g. for BYOK scenarios).\n *\n * If a model with the same `id` already exists it will be replaced.\n *\n * @param model - The {@link ModelInfo} to add.\n */\n addModel(model: ModelInfo): void {\n const idx = this.models.findIndex((m) => m.id === model.id);\n if (idx !== -1) {\n this.models[idx] = model;\n this.logger.info({ modelId: model.id }, \"Existing model replaced in registry\");\n } else {\n this.models.push(model);\n this.logger.info({ modelId: model.id }, \"Custom model added to registry\");\n }\n }\n\n /**\n * Remove a model from the registry.\n *\n * @param modelId - The identifier of the model to remove.\n * @returns `true` if the model was found and removed, `false` otherwise.\n */\n removeModel(modelId: string): boolean {\n const idx = this.models.findIndex((m) => m.id === modelId);\n if (idx === -1) {\n this.logger.warn({ modelId }, \"Attempted to remove unknown model\");\n return false;\n }\n\n this.models.splice(idx, 1);\n this.logger.info({ modelId }, \"Model removed from registry\");\n return true;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new {@link ModelRegistry} instance wired to the given preferences\n * repository.\n *\n * @param preferences - Repository used to persist model selection.\n * @returns A ready-to-use `ModelRegistry`.\n */\nexport function createModelRegistry(preferences: PreferencesRepository): ModelRegistry {\n return new ModelRegistry(preferences);\n}\n","/**\n * @module ai/client\n * @description AI engine interface — wraps the GitHub Copilot SDK\n * {@link CopilotClient} with lifecycle management, structured error handling,\n * and reconnection support. All other AI modules obtain the underlying client\n * through the singleton {@link copilotClient} exported from this module.\n */\n\nimport { CopilotClient } from \"@github/copilot-sdk\";\nimport { createChildLogger } from \"../core/logger.js\";\nimport { AIError } from \"../core/errors.js\";\n\nconst logger = createChildLogger(\"ai:client\");\n\n// ---------------------------------------------------------------------------\n// CopilotClientWrapper\n// ---------------------------------------------------------------------------\n\n/**\n * Manages the full lifecycle of a {@link CopilotClient} instance.\n *\n * Consumers should call {@link start} before requesting the underlying client\n * via {@link getClient}, and {@link stop} during graceful shutdown.\n */\nexport class CopilotClientWrapper {\n private client: CopilotClient | null = null;\n private isStarted: boolean = false;\n\n /** Start the Copilot client. Must be called before creating sessions. */\n async start(): Promise<void> {\n if (this.isStarted) {\n logger.warn(\"Client already started — skipping\");\n return;\n }\n\n try {\n logger.info(\"Starting Copilot client…\");\n this.client = new CopilotClient();\n await this.client.start();\n this.isStarted = true;\n logger.info(\"Copilot client started successfully\");\n } catch (error: unknown) {\n this.client = null;\n this.isStarted = false;\n const reason = error instanceof Error ? error.message : String(error);\n logger.error({ err: error }, \"Failed to start Copilot client\");\n throw AIError.clientStartFailed(reason);\n }\n }\n\n /** Stop the Copilot client gracefully. */\n async stop(): Promise<void> {\n if (!this.isStarted || !this.client) {\n logger.debug(\"Client not running — nothing to stop\");\n return;\n }\n\n try {\n logger.info(\"Stopping Copilot client…\");\n await this.client.stop();\n logger.info(\"Copilot client stopped\");\n } catch (error: unknown) {\n logger.error({ err: error }, \"Error while stopping Copilot client (ignored)\");\n } finally {\n this.client = null;\n this.isStarted = false;\n }\n }\n\n /**\n * Get the underlying {@link CopilotClient}.\n * @throws {AIError} If the client has not been started.\n */\n getClient(): CopilotClient {\n if (!this.isStarted || !this.client) {\n throw AIError.clientStartFailed(\"Client not started\");\n }\n return this.client;\n }\n\n /** Check if the client is running. */\n isRunning(): boolean {\n return this.isStarted;\n }\n\n /** Restart the client (stop then start). */\n async restart(): Promise<void> {\n logger.info(\"Restarting Copilot client…\");\n await this.stop();\n await this.start();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\n/** Singleton Copilot client wrapper */\nexport const copilotClient = new CopilotClientWrapper();\n","/**\n * @module core/errors\n * @description Custom error classes and error handling utilities for the\n * Co-Assistant application. Provides a hierarchy of typed errors with\n * machine-readable codes, optional context, and static factory methods\n * for common failure scenarios.\n */\n\n// ---------------------------------------------------------------------------\n// Base error\n// ---------------------------------------------------------------------------\n\n/**\n * Base error class for all Co-Assistant errors.\n *\n * Every error carries a machine-readable `code` (e.g. `\"CONFIG_MISSING_ENV\"`)\n * and an optional `context` bag of structured data that aids debugging.\n */\nexport class CoAssistantError extends Error {\n /** Machine-readable error code. */\n readonly code: string;\n\n /** Optional structured context for debugging. */\n readonly context?: Record<string, unknown>;\n\n constructor(message: string, code: string, context?: Record<string, unknown>) {\n super(message);\n this.name = this.constructor.name;\n this.code = code;\n this.context = context;\n\n // Ensure the prototype chain is correct for instanceof checks after\n // transpilation to ES5/ES2015 targets.\n Object.setPrototypeOf(this, new.target.prototype);\n\n // Capture a clean stack trace (V8 / Node.js specific).\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// ConfigError\n// ---------------------------------------------------------------------------\n\n/**\n * Errors related to application configuration — missing environment variables,\n * invalid values, or missing config files.\n */\nexport class ConfigError extends CoAssistantError {\n constructor(message: string, code: string, context?: Record<string, unknown>) {\n super(message, code, context);\n }\n\n /** A required environment variable is not set. */\n static missingEnvVar(varName: string): ConfigError {\n return new ConfigError(\n `Missing required environment variable: ${varName}`,\n 'CONFIG_MISSING_ENV',\n { varName },\n );\n }\n\n /** A configuration value failed validation. */\n static invalidValue(key: string, reason: string): ConfigError {\n return new ConfigError(\n `Invalid configuration value for \"${key}\": ${reason}`,\n 'CONFIG_INVALID_VALUE',\n { key, reason },\n );\n }\n\n /** The configuration file could not be found on disk. */\n static fileNotFound(path: string): ConfigError {\n return new ConfigError(\n `Configuration file not found: ${path}`,\n 'CONFIG_FILE_NOT_FOUND',\n { path },\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// PluginError\n// ---------------------------------------------------------------------------\n\n/**\n * Errors originating from the plugin subsystem — initialisation failures,\n * tool execution errors, missing credentials, health-check failures, etc.\n */\nexport class PluginError extends CoAssistantError {\n /** Identifier of the plugin that produced this error. */\n readonly pluginId: string;\n\n constructor(\n message: string,\n code: string,\n pluginId: string,\n context?: Record<string, unknown>,\n ) {\n super(message, code, { ...context, pluginId });\n this.pluginId = pluginId;\n }\n\n /** Plugin initialisation failed. */\n static initFailed(pluginId: string, reason: string): PluginError {\n return new PluginError(\n `Plugin \"${pluginId}\" failed to initialise: ${reason}`,\n 'PLUGIN_INIT_FAILED',\n pluginId,\n { reason },\n );\n }\n\n /** A tool exposed by the plugin failed during execution. */\n static toolFailed(pluginId: string, toolName: string, reason: string): PluginError {\n return new PluginError(\n `Plugin \"${pluginId}\" tool \"${toolName}\" failed: ${reason}`,\n 'PLUGIN_TOOL_FAILED',\n pluginId,\n { toolName, reason },\n );\n }\n\n /** Required credentials for the plugin are missing. */\n static credentialsMissing(pluginId: string, keys: string[]): PluginError {\n return new PluginError(\n `Plugin \"${pluginId}\" is missing required credentials: ${keys.join(', ')}`,\n 'PLUGIN_CREDENTIALS_MISSING',\n pluginId,\n { keys },\n );\n }\n\n /** Plugin health check did not pass. */\n static healthCheckFailed(pluginId: string, reason: string): PluginError {\n return new PluginError(\n `Plugin \"${pluginId}\" health check failed: ${reason}`,\n 'PLUGIN_HEALTH_CHECK_FAILED',\n pluginId,\n { reason },\n );\n }\n\n /** Plugin is disabled and cannot be used. */\n static disabled(pluginId: string, reason: string): PluginError {\n return new PluginError(\n `Plugin \"${pluginId}\" is disabled: ${reason}`,\n 'PLUGIN_DISABLED',\n pluginId,\n { reason },\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// AIError\n// ---------------------------------------------------------------------------\n\n/**\n * Errors related to AI / LLM interactions — client start-up, session\n * creation, model resolution, and message sending.\n */\nexport class AIError extends CoAssistantError {\n constructor(message: string, code: string, context?: Record<string, unknown>) {\n super(message, code, context);\n }\n\n /** The AI client failed to start. */\n static clientStartFailed(reason: string): AIError {\n return new AIError(\n `AI client failed to start: ${reason}`,\n 'AI_CLIENT_START_FAILED',\n { reason },\n );\n }\n\n /** A new AI session could not be created. */\n static sessionCreateFailed(reason: string): AIError {\n return new AIError(\n `Failed to create AI session: ${reason}`,\n 'AI_SESSION_CREATE_FAILED',\n { reason },\n );\n }\n\n /** The requested model was not found or is unavailable. */\n static modelNotFound(model: string): AIError {\n return new AIError(\n `AI model not found: ${model}`,\n 'AI_MODEL_NOT_FOUND',\n { model },\n );\n }\n\n /** Sending a message to the AI failed. */\n static sendFailed(reason: string): AIError {\n return new AIError(\n `Failed to send message to AI: ${reason}`,\n 'AI_SEND_FAILED',\n { reason },\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// BotError\n// ---------------------------------------------------------------------------\n\n/**\n * Errors related to the Telegram bot — authorisation, message delivery,\n * and command handling.\n */\nexport class BotError extends CoAssistantError {\n constructor(message: string, code: string, context?: Record<string, unknown>) {\n super(message, code, context);\n }\n\n /** The user is not authorised to interact with the bot. */\n static unauthorized(userId: number): BotError {\n return new BotError(\n `Unauthorized user: ${userId}`,\n 'BOT_UNAUTHORIZED',\n { userId },\n );\n }\n\n /** The bot failed to send a message. */\n static sendFailed(reason: string): BotError {\n return new BotError(\n `Bot failed to send message: ${reason}`,\n 'BOT_SEND_FAILED',\n { reason },\n );\n }\n\n /** A bot command could not be executed. */\n static commandFailed(command: string, reason: string): BotError {\n return new BotError(\n `Bot command \"/${command}\" failed: ${reason}`,\n 'BOT_COMMAND_FAILED',\n { command, reason },\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Utility functions\n// ---------------------------------------------------------------------------\n\n/**\n * Type-guard that checks whether an unknown value is a {@link CoAssistantError}.\n */\nexport function isCoAssistantError(error: unknown): error is CoAssistantError {\n return error instanceof CoAssistantError;\n}\n\n/**\n * Safely format any thrown value into a human-readable string.\n *\n * For {@link CoAssistantError} instances the output includes the error code\n * and any attached context. Non-Error values are coerced via `String()`.\n */\nexport function formatError(error: unknown): string {\n if (isCoAssistantError(error)) {\n let msg = `[${error.code}] ${error.message}`;\n if (error.context && Object.keys(error.context).length > 0) {\n msg += ` | context: ${JSON.stringify(error.context)}`;\n }\n return msg;\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n","/**\n * @module ai/session\n * @description AI session **pool** management — creates and maintains multiple\n * parallel Copilot SDK sessions so that user messages, heartbeats, and commands\n * can be processed concurrently without blocking each other.\n *\n * The Copilot SDK only supports one `sendAndWait` call per session at a time.\n * Instead of serializing all messages through a single session (which causes\n * queueing), we maintain a configurable pool of sessions. Each incoming\n * `sendMessage` call acquires a free session, sends its prompt, and releases\n * the session when done. If all sessions are busy, callers wait until one\n * frees up.\n *\n * All other modules that need to interact with the AI should go through the\n * singleton {@link sessionManager}.\n */\n\nimport { approveAll, defineTool } from \"@github/copilot-sdk\";\nimport type { Tool } from \"@github/copilot-sdk\";\nimport type { Logger } from \"pino\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { createChildLogger } from \"../core/logger.js\";\nimport { AIError } from \"../core/errors.js\";\nimport { CopilotClientWrapper, copilotClient } from \"./client.js\";\nimport type { ToolDefinition } from \"../plugins/types.js\";\n\nconst logger = createChildLogger(\"ai:session\");\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Opaque handle returned by `client.createSession()`.\n *\n * The Copilot SDK does not export a named session type, so we use a\n * structural type that covers the API surface we depend on.\n */\ninterface CopilotSession {\n send(options: { prompt: string }): Promise<void>;\n sendAndWait(options: { prompt: string }, timeout?: number): Promise<{ data: { content: string } } | null>;\n on(event: string, handler: (...args: unknown[]) => void): void;\n disconnect(): Promise<void>;\n}\n\n/**\n * A session within the pool, tracked alongside its busy state and index.\n * Index 0 is the \"primary\" session — preferred for sequential use so that\n * back-to-back user messages naturally share the same conversation context.\n */\ninterface PooledSession {\n session: CopilotSession;\n busy: boolean;\n index: number;\n}\n\n/** Pending caller waiting for a free session. */\ninterface SessionWaiter {\n resolve: (ps: PooledSession) => void;\n reject: (err: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert an array of {@link ToolDefinition} objects into the format\n * expected by the Copilot SDK's `createSession({ tools })` option.\n */\nfunction convertTools(tools: ToolDefinition[]): Tool<unknown>[] {\n return tools.map((t) =>\n defineTool(t.name, {\n description: t.description,\n parameters: t.parameters as Record<string, unknown>,\n handler: t.handler as (args: unknown) => Promise<unknown>,\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// SessionManager\n// ---------------------------------------------------------------------------\n\n/**\n * Manages a **pool** of Copilot SDK sessions for parallel message processing.\n *\n * Responsibilities:\n * - Creating a pool of sessions with a given model and set of tools.\n * - Acquiring / releasing sessions for each `sendMessage` call.\n * - Sending messages (blocking and streaming).\n * - Rebuilding the pool when the model or tool set changes.\n * - Graceful cleanup on close.\n */\nexport class SessionManager {\n /** Pool of parallel Copilot sessions. */\n private pool: PooledSession[] = [];\n\n /** Configured pool size — how many parallel sessions to maintain. */\n private poolSize: number = 3;\n\n private currentModel: string = \"\";\n private tools: ToolDefinition[] = [];\n private logger: Logger;\n\n /**\n * Cached personality prompt loaded from `personality.md`.\n * Reloaded from disk on each message so edits take effect immediately.\n */\n private personalityPath: string;\n\n /** Path to the user profile loaded from `user.md`. */\n private userProfilePath: string;\n\n /**\n * Callers blocked waiting for a free session.\n * Drained FIFO when a session is released.\n */\n private waiters: SessionWaiter[] = [];\n\n /** Maximum time (ms) a caller will wait in the queue before giving up. */\n private static readonly ACQUIRE_TIMEOUT_MS = 300_000; // 5 minutes\n\n constructor(private clientWrapper: CopilotClientWrapper) {\n this.logger = logger;\n this.personalityPath = path.join(process.cwd(), \"personality.md\");\n this.userProfilePath = path.join(process.cwd(), \"user.md\");\n }\n\n /**\n * Read a markdown context file from disk.\n *\n * Reads fresh on each call so edits take effect without restart.\n * Returns an empty string if the file is missing or unreadable.\n */\n private loadContextFile(filePath: string, label: string): string {\n try {\n if (!existsSync(filePath)) return \"\";\n return readFileSync(filePath, \"utf-8\").trim();\n } catch {\n this.logger.warn(`Could not read ${label} — skipping`);\n return \"\";\n }\n }\n\n /**\n * Wrap a user prompt with system-level context (personality + user profile).\n *\n * Both files are read fresh from disk so edits take effect immediately.\n * The personality defines *how* the assistant behaves; the user profile\n * tells it *who* it's talking to.\n */\n private applySystemContext(prompt: string): string {\n const personality = this.loadContextFile(this.personalityPath, \"personality.md\");\n const userProfile = this.loadContextFile(this.userProfilePath, \"user.md\");\n\n const parts: string[] = [];\n if (personality) parts.push(personality);\n if (userProfile) parts.push(userProfile);\n\n if (parts.length === 0) return prompt;\n return `<system>\\n${parts.join(\"\\n\\n---\\n\\n\")}\\n</system>\\n\\n${prompt}`;\n }\n\n // -----------------------------------------------------------------------\n // Pool management (private)\n // -----------------------------------------------------------------------\n\n /**\n * Acquire a free session from the pool.\n *\n * Prefers the lowest-index free session so that sequential interactions\n * (e.g. back-to-back user messages) naturally reuse the same session and\n * keep their conversation context. If all sessions are busy, the caller\n * blocks until one is released or the acquire timeout fires.\n */\n private acquire(): Promise<PooledSession> {\n const free = this.pool.find((s) => !s.busy);\n if (free) {\n free.busy = true;\n return Promise.resolve(free);\n }\n\n // All sessions occupied — queue the caller with a timeout guard\n return new Promise<PooledSession>((resolve, reject) => {\n const waiter: SessionWaiter = { resolve, reject };\n\n const timer = setTimeout(() => {\n const idx = this.waiters.indexOf(waiter);\n if (idx >= 0) this.waiters.splice(idx, 1);\n reject(AIError.sendFailed(\"Timed out waiting for a free AI session\"));\n }, SessionManager.ACQUIRE_TIMEOUT_MS);\n\n // Wrap resolve to clear the timeout when the waiter is fulfilled\n waiter.resolve = (ps: PooledSession) => {\n clearTimeout(timer);\n resolve(ps);\n };\n\n this.waiters.push(waiter);\n });\n }\n\n /**\n * Release a session back to the pool.\n *\n * If callers are queued waiting for a session, the session is handed\n * directly to the next waiter (stays marked as busy) instead of being\n * returned to the idle pool — this avoids an unnecessary acquire cycle.\n */\n private release(ps: PooledSession): void {\n if (this.waiters.length > 0) {\n const waiter = this.waiters.shift()!;\n // Session stays busy — hand it directly to the waiter\n waiter.resolve(ps);\n } else {\n ps.busy = false;\n }\n }\n\n // -----------------------------------------------------------------------\n // Session creation\n // -----------------------------------------------------------------------\n\n /**\n * Create a pool of Copilot sessions with the specified model and tools.\n *\n * Sessions are created in parallel for faster startup. If any session\n * fails to create, all successful ones are cleaned up and the error\n * propagates.\n *\n * @param model - Model identifier (e.g. `\"gpt-5\"`, `\"claude-sonnet-4.5\"`).\n * @param tools - Optional array of tool definitions to register.\n * @param poolSize - Number of parallel sessions (default: 3).\n * @throws {AIError} If session creation fails.\n */\n async createSession(model: string, tools?: ToolDefinition[], poolSize?: number): Promise<void> {\n if (this.pool.length > 0) {\n this.logger.warn(\"Session pool already exists — closing before re-creating\");\n await this.closeSession();\n }\n\n if (tools) this.tools = tools;\n if (poolSize !== undefined && poolSize > 0) this.poolSize = poolSize;\n\n try {\n const client = this.clientWrapper.getClient();\n const sdkTools = convertTools(this.tools);\n\n this.logger.info(\n { model, toolCount: sdkTools.length, poolSize: this.poolSize },\n \"Creating session pool\",\n );\n\n // Create all sessions in parallel for faster startup\n const results = await Promise.allSettled(\n Array.from({ length: this.poolSize }, async (_, i) => {\n const session = await (client as unknown as {\n createSession(opts: Record<string, unknown>): Promise<CopilotSession>;\n }).createSession({\n model,\n tools: sdkTools.length > 0 ? sdkTools : undefined,\n onPermissionRequest: approveAll,\n });\n this.logger.debug({ index: i }, \"Pool session created\");\n return { session, busy: false, index: i } as PooledSession;\n }),\n );\n\n // Separate successes from failures\n const created: PooledSession[] = [];\n const errors: unknown[] = [];\n\n for (const r of results) {\n if (r.status === \"fulfilled\") {\n created.push(r.value);\n } else {\n errors.push(r.reason);\n }\n }\n\n // If any session failed, clean up the ones that succeeded and bail\n if (errors.length > 0) {\n for (const ps of created) {\n try { await ps.session.disconnect(); } catch { /* best-effort cleanup */ }\n }\n const first = errors[0] instanceof Error ? (errors[0] as Error).message : String(errors[0]);\n throw new Error(`${errors.length}/${this.poolSize} sessions failed: ${first}`);\n }\n\n this.pool = created;\n this.currentModel = model;\n\n this.logger.info({ model, poolSize: this.poolSize }, \"Session pool ready\");\n } catch (error: unknown) {\n const reason = error instanceof Error ? error.message : String(error);\n this.logger.error({ err: error, model }, \"Failed to create session pool\");\n throw AIError.sessionCreateFailed(reason);\n }\n }\n\n // -----------------------------------------------------------------------\n // Messaging\n // -----------------------------------------------------------------------\n\n /**\n * Send a prompt and wait for the complete assistant response.\n *\n * Acquires a free session from the pool, sends the prompt via\n * `sendAndWait`, and releases the session when done. Multiple callers\n * can run in parallel (up to `poolSize`). If all sessions are busy,\n * the caller blocks until one frees up.\n *\n * @param prompt - The user message to send.\n * @param timeout - Timeout in ms (default: 300 000 = 5 min). Complex prompts\n * involving multiple tool calls (e.g. heartbeats) can take\n * longer than the SDK's default 60 s.\n * @returns The assistant's full response content.\n * @throws {AIError} If no session pool is active or the send fails.\n */\n async sendMessage(prompt: string, timeout: number = 300_000): Promise<string> {\n this.ensureSession();\n\n // Inject personality + user profile so the model has full context\n const fullPrompt = this.applySystemContext(prompt);\n\n const ps = await this.acquire();\n try {\n this.logger.debug(\n { promptLength: fullPrompt.length, timeout, session: ps.index },\n \"Sending message (blocking)\",\n );\n\n const response = await ps.session.sendAndWait({ prompt: fullPrompt }, timeout);\n\n const content = response?.data?.content ?? \"\";\n this.logger.debug(\n { responseLength: content.length, session: ps.index },\n \"Received response\",\n );\n return content;\n } catch (error: unknown) {\n const reason = error instanceof Error ? error.message : String(error);\n this.logger.error({ err: error, session: ps.index }, \"sendMessage failed\");\n throw AIError.sendFailed(reason);\n } finally {\n this.release(ps);\n }\n }\n\n /**\n * Send a prompt with streaming — invokes `onChunk` for each incremental\n * delta and returns the full accumulated response when the session goes idle.\n *\n * Acquires a pooled session for the duration of the streaming call.\n *\n * @param prompt - The user message to send.\n * @param onChunk - Callback invoked with each streaming text delta.\n * @returns The full accumulated response string.\n * @throws {AIError} If no session pool is active or the send fails.\n */\n async sendMessageStreaming(\n prompt: string,\n onChunk: (chunk: string) => void,\n ): Promise<string> {\n this.ensureSession();\n\n // Inject personality + user profile for streaming too\n const fullPrompt = this.applySystemContext(prompt);\n\n const ps = await this.acquire();\n try {\n this.logger.debug(\n { promptLength: fullPrompt.length, session: ps.index },\n \"Sending message (streaming)\",\n );\n\n let accumulated = \"\";\n\n const done = new Promise<string>((resolve, reject) => {\n ps.session.on(\"assistant.message_delta\", (event: unknown) => {\n const delta = (event as { data: { deltaContent: string } }).data.deltaContent ?? \"\";\n if (delta) {\n accumulated += delta;\n onChunk(delta);\n }\n });\n\n ps.session.on(\"assistant.message\", (event: unknown) => {\n const content = (event as { data: { content: string } }).data.content ?? \"\";\n if (content) {\n accumulated = content;\n }\n });\n\n ps.session.on(\"session.idle\", () => {\n resolve(accumulated);\n });\n\n ps.session.on(\"error\", (err: unknown) => {\n reject(err);\n });\n });\n\n await ps.session.send({ prompt: fullPrompt });\n const result = await done;\n\n this.logger.debug(\n { responseLength: result.length, session: ps.index },\n \"Streaming response complete\",\n );\n return result;\n } catch (error: unknown) {\n const reason = error instanceof Error ? error.message : String(error);\n this.logger.error({ err: error, session: ps.index }, \"sendMessageStreaming failed\");\n throw AIError.sendFailed(reason);\n } finally {\n this.release(ps);\n }\n }\n\n // -----------------------------------------------------------------------\n // Model / tool management\n // -----------------------------------------------------------------------\n\n /**\n * Switch to a different model. Closes the entire pool and creates a new\n * one with the same tool set and pool size.\n *\n * @param model - The new model identifier.\n * @throws {AIError} If pool recreation fails.\n */\n async switchModel(model: string): Promise<void> {\n this.logger.info({ from: this.currentModel, to: model }, \"Switching model\");\n await this.closeSession();\n await this.createSession(model, this.tools);\n }\n\n /**\n * Replace the registered tool set. The pool must be rebuilt because the\n * Copilot SDK binds tools at session creation time.\n *\n * @param tools - New array of tool definitions.\n * @throws {AIError} If pool recreation fails.\n */\n async updateTools(tools: ToolDefinition[]): Promise<void> {\n this.logger.info({ toolCount: tools.length }, \"Updating tools (pool rebuild required)\");\n this.tools = tools;\n\n if (this.pool.length > 0) {\n await this.closeSession();\n await this.createSession(this.currentModel, this.tools);\n }\n }\n\n // -----------------------------------------------------------------------\n // Session lifecycle\n // -----------------------------------------------------------------------\n\n /**\n * Close all sessions in the pool and release resources.\n *\n * Any callers blocked in {@link acquire} are rejected with an error.\n * Safe to call even when the pool is empty (no-op).\n */\n async closeSession(): Promise<void> {\n if (this.pool.length === 0) {\n this.logger.debug(\"No active sessions to close\");\n return;\n }\n\n // Reject any callers waiting for a session\n for (const waiter of this.waiters) {\n waiter.reject(AIError.sessionCreateFailed(\"Session pool is closing\"));\n }\n this.waiters = [];\n\n this.logger.info({ poolSize: this.pool.length }, \"Closing session pool\");\n\n // Disconnect all sessions in parallel (best-effort per session)\n await Promise.allSettled(\n this.pool.map(async (ps) => {\n try {\n await ps.session.disconnect();\n } catch (err) {\n this.logger.error({ err, index: ps.index }, \"Error closing pool session (ignored)\");\n }\n }),\n );\n\n this.pool = [];\n this.logger.info(\"Session pool closed\");\n }\n\n /**\n * Get the identifier of the model used by the current (or most recent) pool.\n */\n getCurrentModel(): string {\n return this.currentModel;\n }\n\n /**\n * Check whether the session pool has at least one session.\n */\n isActive(): boolean {\n return this.pool.length > 0;\n }\n\n /**\n * Number of sessions not currently processing a message.\n * Useful for the Telegram handler to decide whether to show a \"queued\" notice.\n */\n getAvailableCount(): number {\n return this.pool.filter((s) => !s.busy).length;\n }\n\n /**\n * Total number of sessions in the pool.\n */\n getPoolSize(): number {\n return this.poolSize;\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n /**\n * Guard that throws if the pool is empty.\n * @throws {AIError} When called without an active pool.\n */\n private ensureSession(): void {\n if (this.pool.length === 0) {\n throw AIError.sessionCreateFailed(\"No active sessions — call createSession() first\");\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\n/** Singleton session manager wired to the default Copilot client. */\nexport const sessionManager = new SessionManager(copilotClient);\n","/**\n * @module plugins/registry\n * @description Plugin registry for discovering and tracking available plugins.\n *\n * Responsible for:\n * - Scanning the plugins directory to discover available plugins\n * - Reading and validating each plugin's `plugin.json` manifest against\n * {@link PluginManifestSchema}\n * - Tracking which plugins are enabled / disabled\n * - Persisting enable/disable state to `config.json`\n *\n * Usage:\n * ```ts\n * const registry = createPluginRegistry();\n * await registry.discoverPlugins();\n * registry.enablePlugin(\"gmail\");\n * ```\n */\n\nimport path from \"node:path\";\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport type { Logger } from \"pino\";\nimport { createChildLogger } from \"../core/logger.js\";\nimport { type PluginManifest, PluginManifestSchema } from \"./types.js\";\nimport { loadAppConfig, resetConfig, AppConfigSchema } from \"../core/config.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Name of the manifest file expected inside each plugin subdirectory. */\nconst MANIFEST_FILENAME = \"plugin.json\";\n\n/** Default path to the config file used for persistence. */\nconst CONFIG_PATH = \"./config.json\";\n\n// ---------------------------------------------------------------------------\n// PluginRegistry\n// ---------------------------------------------------------------------------\n\n/**\n * Registry that discovers plugins on disk, validates their manifests, and\n * manages the enabled / disabled state for each plugin.\n */\nexport class PluginRegistry {\n /** Validated manifests keyed by plugin ID. */\n private manifests: Map<string, PluginManifest> = new Map();\n\n /** Set of currently-enabled plugin IDs. */\n private enabledPlugins: Set<string> = new Set();\n\n /** Namespaced logger for registry operations. */\n private logger: Logger;\n\n /**\n * @param pluginsDir - Absolute or relative path to the directory that\n * contains plugin subdirectories.\n */\n constructor(private pluginsDir: string) {\n this.logger = createChildLogger(\"plugins:registry\");\n }\n\n // -----------------------------------------------------------------------\n // Discovery\n // -----------------------------------------------------------------------\n\n /**\n * Scan the plugins directory and discover all available plugins.\n *\n * For every subdirectory that contains a valid `plugin.json` the manifest\n * is parsed, validated with {@link PluginManifestSchema}, and stored.\n * Invalid or missing manifests are logged as warnings and skipped — they\n * never crash the application.\n *\n * After discovery the enabled state for each plugin is loaded from the\n * application config (`config.json → app.plugins[id].enabled`).\n *\n * @returns Array of all successfully validated {@link PluginManifest}s.\n */\n async discoverPlugins(): Promise<PluginManifest[]> {\n this.manifests.clear();\n this.enabledPlugins.clear();\n\n // Ensure the plugins directory exists.\n if (!existsSync(this.pluginsDir)) {\n this.logger.info(\n { pluginsDir: this.pluginsDir },\n \"Plugins directory does not exist — creating it\",\n );\n mkdirSync(this.pluginsDir, { recursive: true });\n return [];\n }\n\n // Enumerate subdirectories.\n const entries = readdirSync(this.pluginsDir);\n\n for (const entry of entries) {\n const entryPath = path.join(this.pluginsDir, entry);\n\n // Only consider directories.\n if (!statSync(entryPath).isDirectory()) {\n continue;\n }\n\n const manifestPath = path.join(entryPath, MANIFEST_FILENAME);\n\n // Check that plugin.json exists.\n if (!existsSync(manifestPath)) {\n this.logger.warn(\n { dir: entry },\n `Skipping \"${entry}\": no ${MANIFEST_FILENAME} found`,\n );\n continue;\n }\n\n // Read and parse JSON.\n let raw: unknown;\n try {\n const content = readFileSync(manifestPath, \"utf-8\");\n raw = JSON.parse(content);\n } catch (err) {\n this.logger.warn(\n { dir: entry, error: (err as Error).message },\n `Skipping \"${entry}\": failed to read or parse ${MANIFEST_FILENAME}`,\n );\n continue;\n }\n\n // Validate against the Zod schema.\n const result = PluginManifestSchema.safeParse(raw);\n\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` • ${i.path.join(\".\")}: ${i.message}`)\n .join(\"\\n\");\n this.logger.warn(\n { dir: entry },\n `Skipping \"${entry}\": manifest validation failed:\\n${issues}`,\n );\n continue;\n }\n\n const manifest = result.data;\n this.manifests.set(manifest.id, manifest);\n this.logger.info(\n { pluginId: manifest.id, version: manifest.version },\n `Discovered plugin \"${manifest.name}\"`,\n );\n }\n\n // Load enabled state from config.json.\n this.loadEnabledState();\n\n return this.getManifests();\n }\n\n // -----------------------------------------------------------------------\n // Manifest accessors\n // -----------------------------------------------------------------------\n\n /**\n * Get all discovered plugin manifests.\n *\n * @returns A shallow copy of the manifests array.\n */\n getManifests(): PluginManifest[] {\n return Array.from(this.manifests.values());\n }\n\n /**\n * Get a specific plugin's manifest by ID.\n *\n * @param pluginId - The unique plugin identifier.\n * @returns The manifest if found, otherwise `undefined`.\n */\n getManifest(pluginId: string): PluginManifest | undefined {\n return this.manifests.get(pluginId);\n }\n\n // -----------------------------------------------------------------------\n // Enabled / disabled state\n // -----------------------------------------------------------------------\n\n /**\n * Check whether a plugin is currently enabled.\n *\n * @param pluginId - The unique plugin identifier.\n * @returns `true` if the plugin is enabled.\n */\n isEnabled(pluginId: string): boolean {\n return this.enabledPlugins.has(pluginId);\n }\n\n /**\n * Enable a plugin and persist the state to `config.json`.\n *\n * @param pluginId - The unique plugin identifier.\n */\n enablePlugin(pluginId: string): void {\n this.enabledPlugins.add(pluginId);\n this.logger.info({ pluginId }, `Plugin \"${pluginId}\" enabled`);\n this.persistPluginState(pluginId, true);\n }\n\n /**\n * Disable a plugin and persist the state to `config.json`.\n *\n * @param pluginId - The unique plugin identifier.\n */\n disablePlugin(pluginId: string): void {\n this.enabledPlugins.delete(pluginId);\n this.logger.info({ pluginId }, `Plugin \"${pluginId}\" disabled`);\n this.persistPluginState(pluginId, false);\n }\n\n /**\n * Get the list of currently-enabled plugin IDs.\n *\n * @returns Array of enabled plugin ID strings.\n */\n getEnabledPluginIds(): string[] {\n return Array.from(this.enabledPlugins);\n }\n\n /**\n * Get the list of all discovered plugin IDs.\n *\n * @returns Array of all known plugin ID strings.\n */\n getAllPluginIds(): string[] {\n return Array.from(this.manifests.keys());\n }\n\n // -----------------------------------------------------------------------\n // Private helpers\n // -----------------------------------------------------------------------\n\n /**\n * Load the enabled state for every discovered plugin from the application\n * config. Plugins whose config entry has `enabled: true` are added to the\n * enabled set.\n */\n private loadEnabledState(): void {\n let appConfig: Record<string, unknown>;\n\n try {\n // Read the raw config to avoid triggering env-var validation via\n // the singleton `getConfig()` (which also loads EnvConfig).\n if (existsSync(CONFIG_PATH)) {\n const raw = JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\"));\n appConfig = AppConfigSchema.parse(raw);\n } else {\n appConfig = AppConfigSchema.parse({}) as unknown as Record<string, unknown>;\n }\n } catch {\n this.logger.warn(\"Could not load config.json — defaulting all plugins to disabled\");\n return;\n }\n\n const plugins = (appConfig as { plugins?: Record<string, { enabled?: boolean }> }).plugins ?? {};\n\n for (const id of this.manifests.keys()) {\n if (plugins[id]?.enabled === true) {\n this.enabledPlugins.add(id);\n this.logger.debug({ pluginId: id }, `Plugin \"${id}\" is enabled via config`);\n }\n }\n }\n\n /**\n * Persist the enabled/disabled state for a single plugin to `config.json`.\n *\n * Reads the current file, updates the relevant entry, and writes it back.\n * If the file does not exist a minimal config is created.\n */\n private persistPluginState(pluginId: string, enabled: boolean): void {\n try {\n let config: Record<string, unknown> = {};\n\n if (existsSync(CONFIG_PATH)) {\n config = JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\")) as Record<string, unknown>;\n }\n\n // Ensure the plugins map exists.\n if (!config.plugins || typeof config.plugins !== \"object\") {\n config.plugins = {};\n }\n\n const plugins = config.plugins as Record<string, Record<string, unknown>>;\n\n if (!plugins[pluginId]) {\n plugins[pluginId] = { enabled, credentials: {} };\n } else {\n plugins[pluginId].enabled = enabled;\n }\n\n writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n\n // Bust the singleton cache so subsequent `getConfig()` calls pick up\n // the new state.\n resetConfig();\n\n this.logger.debug(\n { pluginId, enabled },\n \"Persisted plugin state to config.json\",\n );\n } catch (err) {\n this.logger.error(\n { pluginId, error: (err as Error).message },\n \"Failed to persist plugin state to config.json\",\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new {@link PluginRegistry} instance.\n *\n * @param pluginsDir - Path to the plugins directory. Defaults to\n * `<cwd>/plugins`.\n * @returns A ready-to-use `PluginRegistry`.\n */\nexport function createPluginRegistry(pluginsDir?: string): PluginRegistry {\n return new PluginRegistry(pluginsDir ?? path.join(process.cwd(), \"plugins\"));\n}\n","/**\n * @module plugins/types\n * @description Central type definitions for the Co-Assistant plugin system.\n *\n * This file defines every interface, type, and Zod schema that plugin authors\n * and internal subsystems rely on:\n *\n * - **PluginManifestSchema / PluginManifest** — validated shape of a plugin's\n * `manifest.json` (or inline manifest object).\n * - **PluginContext** — runtime context injected into each plugin on init,\n * providing credentials, namespaced state storage, and a child logger.\n * - **ToolDefinition** — descriptor for a single tool a plugin exposes to the\n * AI model (mirrors the shape accepted by `@github/copilot-sdk` `defineTool`).\n * - **CoAssistantPlugin** — the interface every plugin must implement.\n * - **PluginStatus / PluginInfo** — lifecycle status tracking for the registry.\n * - **PluginFactory** — the default export shape expected from plugin modules.\n *\n * All exports are public API — plugin developers should be able to build a\n * fully functional plugin by importing only from this module.\n */\n\nimport { z } from \"zod\";\nimport type { Logger } from \"pino\";\n\n// ---------------------------------------------------------------------------\n// Plugin Manifest — Zod Schema & Inferred Type\n// ---------------------------------------------------------------------------\n\n/**\n * Schema describing a plugin credential requirement.\n *\n * Each entry declares a key the plugin needs at runtime (e.g. an API token)\n * along with a human-readable description and a credential type hint.\n */\nexport const CredentialRequirementSchema = z.object({\n /** Credential key used to look up the value at runtime. */\n key: z.string(),\n /** Human-readable explanation of what this credential is for. */\n description: z.string(),\n /**\n * Type hint for the credential.\n * - `\"text\"` — plain text secret (default)\n * - `\"oauth\"` — OAuth token / flow\n * - `\"apikey\"` — API key\n */\n type: z.enum([\"text\", \"oauth\", \"apikey\"]).default(\"text\"),\n});\n\n/**\n * Zod schema for the plugin manifest — the declarative metadata that\n * describes a plugin's identity, version, credential needs, and\n * inter-plugin dependencies.\n *\n * Validated at load time by the plugin registry before a plugin is\n * initialised.\n */\nexport const PluginManifestSchema = z.object({\n /**\n * Unique plugin identifier.\n * Must be kebab-case (`a-z`, `0-9`, and `-` only).\n */\n id: z.string().regex(/^[a-z0-9-]+$/, \"Plugin ID must be kebab-case\"),\n\n /** Human-readable display name. */\n name: z.string().min(1),\n\n /**\n * Semantic version string (e.g. `\"1.2.3\"`).\n * Must follow strict `MAJOR.MINOR.PATCH` format.\n */\n version: z.string().regex(/^\\d+\\.\\d+\\.\\d+$/, \"Must be semver\"),\n\n /** Short description of what this plugin does. */\n description: z.string(),\n\n /** Optional author or organisation name. */\n author: z.string().optional(),\n\n /**\n * Credentials the plugin requires to operate.\n * The loader will verify all required credentials are present before\n * calling `initialize()`.\n */\n requiredCredentials: z.array(CredentialRequirementSchema).default([]),\n\n /**\n * IDs of other plugins this plugin depends on.\n * Dependencies are loaded and initialised first.\n */\n dependencies: z.array(z.string()).default([]),\n});\n\n/** Validated plugin manifest — inferred from {@link PluginManifestSchema}. */\nexport type PluginManifest = z.infer<typeof PluginManifestSchema>;\n\n// ---------------------------------------------------------------------------\n// Plugin Context\n// ---------------------------------------------------------------------------\n\n/**\n * Namespaced state storage interface.\n *\n * Each plugin receives its own isolated key-value store backed by the\n * application's persistent storage layer. Keys are automatically namespaced\n * to the owning plugin to prevent collisions.\n */\nexport interface PluginStateStore {\n /** Retrieve a value by key. Returns `null` when the key does not exist. */\n get(key: string): string | null;\n /** Set (or overwrite) a value for the given key. */\n set(key: string, value: string): void;\n /** Delete a key and its associated value. */\n delete(key: string): void;\n /** Return a snapshot of all key-value pairs owned by this plugin. */\n getAll(): Record<string, string>;\n}\n\n/**\n * Runtime context injected into a plugin's `initialize()` method.\n *\n * The context provides everything a plugin needs to operate:\n * credentials, persistent state, and a namespaced logger.\n */\nexport interface PluginContext {\n /** The plugin's unique identifier. */\n pluginId: string;\n\n /**\n * Pre-validated credentials for this plugin.\n * All keys declared in `requiredCredentials` are guaranteed to be present.\n */\n credentials: Record<string, string>;\n\n /**\n * Namespaced state storage.\n * Get, set, and delete plugin-specific persistent data.\n */\n state: PluginStateStore;\n\n /**\n * Child logger namespaced to this plugin.\n * All log entries are automatically tagged with the plugin ID.\n */\n logger: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// Tool Definition\n// ---------------------------------------------------------------------------\n\n/**\n * Describes a single tool that a plugin exposes to the AI model.\n *\n * This shape closely mirrors the `defineTool` options accepted by the\n * `@github/copilot-sdk`. The plugin manager will automatically prefix\n * the tool name with the plugin ID (e.g. `weather__get-forecast`) to\n * guarantee uniqueness across all loaded plugins.\n */\nexport interface ToolDefinition {\n /**\n * Tool name (without plugin prefix).\n * The final registered name will be `<pluginId>__<name>`.\n */\n name: string;\n\n /** Description shown to the AI model so it knows when to invoke this tool. */\n description: string;\n\n /**\n * Parameter schema for the tool.\n *\n * Accepts either a plain JSON Schema object (as used by\n * `@github/copilot-sdk` `defineTool`) or a Zod schema that can be\n * converted to JSON Schema at registration time.\n */\n parameters: Record<string, unknown> | z.ZodType;\n\n /**\n * Handler invoked when the AI model calls this tool.\n *\n * @param args - Parsed arguments matching the declared parameters schema.\n * @returns A string or structured object that is relayed back to the model.\n */\n handler: (\n args: Record<string, unknown>,\n ) => Promise<string | Record<string, unknown>>;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin Interface\n// ---------------------------------------------------------------------------\n\n/**\n * The main interface every Co-Assistant plugin must implement.\n *\n * A plugin is the unit of extensibility: it declares metadata, exposes one\n * or more {@link ToolDefinition | tools} to the AI session, and manages its\n * own lifecycle through `initialize` → `getTools` → `destroy`.\n *\n * @example\n * ```ts\n * import type { CoAssistantPlugin, PluginContext, ToolDefinition } from \"./types.js\";\n *\n * export default function createPlugin(): CoAssistantPlugin {\n * let ctx: PluginContext;\n * return {\n * id: \"hello-world\",\n * name: \"Hello World\",\n * version: \"1.0.0\",\n * description: \"A minimal example plugin\",\n * requiredCredentials: [],\n * async initialize(context) { ctx = context; },\n * getTools() {\n * return [{\n * name: \"greet\",\n * description: \"Say hello\",\n * parameters: { type: \"object\", properties: {} },\n * handler: async () => \"Hello from a plugin!\",\n * }];\n * },\n * async destroy() {},\n * async healthCheck() { return true; },\n * };\n * }\n * ```\n */\nexport interface CoAssistantPlugin {\n /** Unique plugin identifier (kebab-case). Must match the manifest `id`. */\n id: string;\n\n /** Human-readable display name. */\n name: string;\n\n /** Semantic version string (`MAJOR.MINOR.PATCH`). */\n version: string;\n\n /** Brief description of what this plugin does. */\n description: string;\n\n /**\n * List of credential keys this plugin requires.\n * These must all be present in `PluginContext.credentials` before\n * `initialize()` is called.\n */\n requiredCredentials: string[];\n\n /**\n * Initialise the plugin with its runtime context.\n * Called exactly once after the plugin is loaded and credentials are\n * verified. Use this to set up API clients, open connections, etc.\n */\n initialize(context: PluginContext): Promise<void>;\n\n /**\n * Return the tool definitions this plugin provides.\n * Called after `initialize()`. The returned tools are registered with\n * the AI session via the Copilot SDK.\n */\n getTools(): ToolDefinition[];\n\n /**\n * Graceful shutdown hook.\n * Clean up resources, close connections, flush buffers, etc.\n */\n destroy(): Promise<void>;\n\n /**\n * Health check probe.\n * @returns `true` if the plugin is fully operational, `false` otherwise.\n */\n healthCheck(): Promise<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin Lifecycle & Status\n// ---------------------------------------------------------------------------\n\n/**\n * Represents the current lifecycle state of a loaded plugin.\n *\n * - `\"loaded\"` — module evaluated, not yet initialised.\n * - `\"active\"` — `initialize()` succeeded; plugin is operational.\n * - `\"error\"` — an unrecoverable error occurred during init or runtime.\n * - `\"disabled\"` — manually disabled by the user / admin.\n * - `\"unloaded\"` — `destroy()` was called and the plugin is no longer active.\n */\nexport type PluginStatus = \"loaded\" | \"active\" | \"error\" | \"disabled\" | \"unloaded\";\n\n/**\n * Read-only snapshot of a plugin's current state.\n * Used by the CLI, admin API, and health endpoints.\n */\nexport interface PluginInfo {\n /** Plugin identifier. */\n id: string;\n /** Human-readable name. */\n name: string;\n /** Semantic version. */\n version: string;\n /** Short description. */\n description: string;\n /** Current lifecycle status. */\n status: PluginStatus;\n /** Whether the plugin is enabled in configuration. */\n enabled: boolean;\n /** If `status` is `\"error\"`, a human-readable error message. */\n errorMessage?: string;\n /**\n * Consecutive failure count.\n * Incremented on health-check failures; reset on success.\n */\n failureCount: number;\n /** Names of tools registered by this plugin (prefixed). */\n tools: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Plugin Factory\n// ---------------------------------------------------------------------------\n\n/**\n * The shape of the default export every plugin module must provide.\n *\n * A factory is a zero-argument function that returns a fresh\n * {@link CoAssistantPlugin} instance. This allows the plugin manager to\n * instantiate plugins lazily and in isolation.\n *\n * @example\n * ```ts\n * // plugins/weather/index.ts\n * import type { PluginFactory } from \"../types.js\";\n *\n * const createPlugin: PluginFactory = () => ({\n * id: \"weather\",\n * name: \"Weather\",\n * version: \"0.1.0\",\n * description: \"Provides current weather data\",\n * requiredCredentials: [\"OPENWEATHER_API_KEY\"],\n * async initialize(ctx) { … },\n * getTools() { return [ … ]; },\n * async destroy() { … },\n * async healthCheck() { return true; },\n * });\n *\n * export default createPlugin;\n * ```\n */\nexport type PluginFactory = () => CoAssistantPlugin;\n","/**\n * @module plugins/manager\n * @description Central plugin lifecycle manager — discovers, loads, initialises,\n * and shuts down plugins. Coordinates with the {@link PluginRegistry} for\n * discovery and enable/disable persistence, the {@link PluginSandbox} for\n * error-isolated execution, the {@link CredentialManager} for credential\n * validation, and {@link PluginStateRepository} for per-plugin state storage.\n *\n * Usage:\n * ```ts\n * const manager = createPluginManager(registry, sandbox, credentials, stateRepo);\n * await manager.initialize(); // discover + load enabled plugins\n * const tools = manager.getAllTools();\n * // … later …\n * await manager.shutdown();\n * ```\n */\n\nimport path from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { tsImport } from \"tsx/esm/api\";\nimport type { Logger } from \"pino\";\nimport { createChildLogger } from \"../core/logger.js\";\nimport type { PluginRegistry } from \"./registry.js\";\nimport type { PluginSandbox } from \"./sandbox.js\";\nimport type { CredentialManager } from \"./credentials.js\";\nimport type { PluginStateRepository } from \"../storage/repositories/plugin-state.js\";\nimport type {\n CoAssistantPlugin,\n PluginContext,\n PluginFactory,\n PluginInfo,\n PluginStateStore,\n PluginStatus,\n ToolDefinition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// PluginManager\n// ---------------------------------------------------------------------------\n\n/**\n * Central orchestrator that loads plugins, manages their lifecycle, and\n * coordinates with the registry, sandbox, credential manager, and state\n * repository.\n */\nexport class PluginManager {\n /** Active (loaded and initialised) plugin instances keyed by ID. */\n private plugins: Map<string, CoAssistantPlugin> = new Map();\n\n /** Current lifecycle status for every known plugin. */\n private pluginStatuses: Map<string, PluginStatus> = new Map();\n\n /** Namespaced logger for manager operations. */\n private logger: Logger;\n\n constructor(\n private registry: PluginRegistry,\n private sandbox: PluginSandbox,\n private credentials: CredentialManager,\n private stateRepo: PluginStateRepository,\n ) {\n this.logger = createChildLogger(\"plugins:manager\");\n }\n\n // -----------------------------------------------------------------------\n // Initialisation\n // -----------------------------------------------------------------------\n\n /**\n * Initialise the plugin system: discover plugins on disk, then load and\n * initialise every enabled plugin.\n *\n * Safe to call exactly once at application startup. Errors from individual\n * plugins are caught and logged — a single broken plugin never prevents the\n * rest from loading.\n */\n async initialize(): Promise<void> {\n this.logger.info(\"Initializing plugin system…\");\n\n const manifests = await this.registry.discoverPlugins();\n const discovered = manifests.length;\n let loaded = 0;\n let failed = 0;\n\n const enabledIds = this.registry.getEnabledPluginIds();\n\n for (const pluginId of enabledIds) {\n try {\n await this.loadPlugin(pluginId);\n loaded++;\n } catch (err) {\n failed++;\n const error = err instanceof Error ? err : new Error(String(err));\n this.logger.error(\n { pluginId, error: error.message },\n `Failed to load plugin \"${pluginId}\"`,\n );\n this.pluginStatuses.set(pluginId, \"error\");\n }\n }\n\n this.logger.info(\n { discovered, loaded, failed },\n `Plugin system ready — ${discovered} discovered, ${loaded} loaded, ${failed} failed`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Load / Unload\n // -----------------------------------------------------------------------\n\n /**\n * Load and initialise a specific plugin by ID.\n *\n * Steps:\n * 1. Resolve the plugin manifest from the registry.\n * 2. Validate credentials via the {@link CredentialManager}.\n * 3. Dynamically import the plugin module (preferring compiled `.js`).\n * 4. Invoke the factory to obtain a {@link CoAssistantPlugin} instance.\n * 5. Build a {@link PluginContext} and call `plugin.initialize()` inside\n * the sandbox.\n * 6. Store the active plugin and set its status to `\"active\"`.\n *\n * @param pluginId - Unique identifier of the plugin to load.\n * @throws If the manifest is not found, credentials are invalid, or the\n * module cannot be imported.\n */\n async loadPlugin(pluginId: string): Promise<void> {\n this.logger.info({ pluginId }, `Loading plugin \"${pluginId}\"…`);\n\n // 1. Manifest\n const manifest = this.registry.getManifest(pluginId);\n if (!manifest) {\n throw new Error(`No manifest found for plugin \"${pluginId}\"`);\n }\n\n this.pluginStatuses.set(pluginId, \"loaded\");\n\n // 2. Credentials\n let creds: Record<string, string> = {};\n try {\n creds = this.credentials.getValidatedCredentials(\n pluginId,\n manifest.requiredCredentials,\n );\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.logger.warn(\n { pluginId, error: error.message },\n `Credential validation failed for \"${pluginId}\" — loading with empty credentials`,\n );\n }\n\n // 3. Dynamic import\n const pluginPath = this.resolvePluginPath(pluginId);\n this.logger.debug({ pluginId, pluginPath }, \"Importing plugin module\");\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let mod: any;\n try {\n // Use tsx's tsImport for .ts files so sub-imports (e.g. ./auth.js → ./auth.ts)\n // are resolved correctly even when running from compiled dist/ output.\n if (pluginPath.endsWith(\".ts\")) {\n mod = await tsImport(pluginPath, import.meta.url);\n } else {\n mod = await import(pluginPath);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.pluginStatuses.set(pluginId, \"error\");\n throw new Error(\n `Failed to import plugin module \"${pluginId}\" from ${pluginPath}: ${error.message}`,\n );\n }\n\n // 4. Factory\n const factory: PluginFactory | undefined =\n typeof mod.default === \"function\"\n ? mod.default\n : typeof mod.createPlugin === \"function\"\n ? mod.createPlugin\n : undefined;\n\n if (!factory) {\n this.pluginStatuses.set(pluginId, \"error\");\n throw new Error(\n `Plugin \"${pluginId}\" does not export a default factory or createPlugin function`,\n );\n }\n\n const plugin = factory();\n\n // 5. Build context & initialise\n const context = this.buildPluginContext(pluginId, creds);\n\n const initResult = await this.sandbox.safeExecute(\n pluginId,\n \"initialize\",\n () => plugin.initialize(context),\n );\n\n // safeExecute returns undefined on failure\n if (initResult === undefined && manifest.requiredCredentials.length > 0) {\n this.logger.warn(\n { pluginId },\n `Plugin \"${pluginId}\" initialize() did not succeed — marking as error`,\n );\n }\n\n // 6. Store\n this.plugins.set(pluginId, plugin);\n this.pluginStatuses.set(pluginId, \"active\");\n this.logger.info(\n { pluginId, version: manifest.version },\n `Plugin \"${manifest.name}\" v${manifest.version} loaded successfully`,\n );\n }\n\n /**\n * Unload a plugin: invoke its `destroy()` hook inside the sandbox,\n * remove it from the active plugin map, and mark it as unloaded.\n *\n * @param pluginId - Unique identifier of the plugin to unload.\n */\n async unloadPlugin(pluginId: string): Promise<void> {\n const plugin = this.plugins.get(pluginId);\n if (!plugin) {\n this.logger.warn({ pluginId }, `Plugin \"${pluginId}\" is not loaded — nothing to unload`);\n return;\n }\n\n this.logger.info({ pluginId }, `Unloading plugin \"${pluginId}\"…`);\n\n await this.sandbox.safeExecute(pluginId, \"destroy\", () => plugin.destroy());\n\n this.plugins.delete(pluginId);\n this.pluginStatuses.set(pluginId, \"unloaded\");\n this.logger.info({ pluginId }, `Plugin \"${pluginId}\" unloaded`);\n }\n\n // -----------------------------------------------------------------------\n // Enable / Disable\n // -----------------------------------------------------------------------\n\n /**\n * Enable a plugin: persist the enabled state in the registry and load the\n * plugin if it is not already active.\n *\n * @param pluginId - Unique identifier of the plugin to enable.\n */\n async enablePlugin(pluginId: string): Promise<void> {\n this.logger.info({ pluginId }, `Enabling plugin \"${pluginId}\"`);\n this.registry.enablePlugin(pluginId);\n\n if (!this.plugins.has(pluginId)) {\n await this.loadPlugin(pluginId);\n }\n }\n\n /**\n * Disable a plugin: persist the disabled state in the registry and unload\n * the plugin if it is currently active.\n *\n * @param pluginId - Unique identifier of the plugin to disable.\n */\n async disablePlugin(pluginId: string): Promise<void> {\n this.logger.info({ pluginId }, `Disabling plugin \"${pluginId}\"`);\n this.registry.disablePlugin(pluginId);\n\n if (this.plugins.has(pluginId)) {\n await this.unloadPlugin(pluginId);\n }\n\n this.pluginStatuses.set(pluginId, \"disabled\");\n }\n\n // -----------------------------------------------------------------------\n // Accessors\n // -----------------------------------------------------------------------\n\n /**\n * Get all active (loaded and running) plugin instances.\n *\n * @returns A new Map containing only the currently active plugins.\n */\n getActivePlugins(): Map<string, CoAssistantPlugin> {\n return new Map(this.plugins);\n }\n\n /**\n * Collect all tool definitions from every active plugin.\n *\n * Tool names are prefixed with the owning plugin's ID\n * (`<pluginId>__<toolName>`) to guarantee uniqueness. Handlers are wrapped\n * by the sandbox so errors are isolated.\n *\n * @returns Flat array of all tools across all active plugins.\n */\n getAllTools(): ToolDefinition[] {\n const tools: ToolDefinition[] = [];\n\n for (const [pluginId, plugin] of this.plugins) {\n try {\n const pluginTools = plugin.getTools();\n for (const tool of pluginTools) {\n tools.push({\n name: `${pluginId}__${tool.name}`,\n description: tool.description,\n parameters: tool.parameters,\n handler: this.sandbox.wrapToolHandler(pluginId, tool.name, tool.handler),\n });\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.logger.error(\n { pluginId, error: error.message },\n `Failed to get tools from plugin \"${pluginId}\"`,\n );\n }\n }\n\n return tools;\n }\n\n /**\n * Build a read-only info snapshot for every discovered plugin.\n *\n * Combines manifest metadata, current lifecycle status, enabled state,\n * sandbox failure counts, and registered tool names.\n *\n * @returns Array of {@link PluginInfo} objects (for display / admin API).\n */\n getPluginInfoList(): PluginInfo[] {\n const manifests = this.registry.getManifests();\n\n return manifests.map((manifest) => {\n const pluginId = manifest.id;\n const status = this.pluginStatuses.get(pluginId) ?? \"unloaded\";\n const enabled = this.registry.isEnabled(pluginId);\n const failureCount = this.sandbox.getFailureCount(pluginId);\n\n // Collect tool names from the active instance (if any).\n let toolNames: string[] = [];\n const plugin = this.plugins.get(pluginId);\n if (plugin) {\n try {\n toolNames = plugin.getTools().map((t) => `${pluginId}__${t.name}`);\n } catch {\n // Plugin may be in a bad state — silently skip tool enumeration.\n }\n }\n\n const info: PluginInfo = {\n id: pluginId,\n name: manifest.name,\n version: manifest.version,\n description: manifest.description,\n status,\n enabled,\n failureCount,\n tools: toolNames,\n };\n\n if (status === \"error\") {\n info.errorMessage = this.sandbox.isDisabled(pluginId)\n ? \"Auto-disabled due to repeated failures\"\n : \"Plugin encountered an error during lifecycle\";\n }\n\n return info;\n });\n }\n\n /**\n * Get info about a specific discovered plugin.\n *\n * @param pluginId - The unique plugin identifier.\n * @returns The plugin info snapshot, or `undefined` if the plugin was not\n * discovered.\n */\n getPluginInfo(pluginId: string): PluginInfo | undefined {\n return this.getPluginInfoList().find((info) => info.id === pluginId);\n }\n\n // -----------------------------------------------------------------------\n // Shutdown\n // -----------------------------------------------------------------------\n\n /**\n * Gracefully shut down all active plugins.\n *\n * Calls `destroy()` on each plugin inside the sandbox so that a failing\n * plugin does not prevent the rest from cleaning up.\n */\n async shutdown(): Promise<void> {\n this.logger.info(\"Shutting down plugin system…\");\n\n const pluginIds = Array.from(this.plugins.keys());\n\n for (const pluginId of pluginIds) {\n await this.unloadPlugin(pluginId);\n }\n\n this.logger.info(\n { count: pluginIds.length },\n `Plugin system shut down — ${pluginIds.length} plugin(s) unloaded`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Private helpers\n // -----------------------------------------------------------------------\n\n /**\n * Resolve the file-system path for a plugin module.\n *\n * Prefers the compiled `.js` file; falls back to `.ts` for development.\n */\n private resolvePluginPath(pluginId: string): string {\n const baseDir = path.join(process.cwd(), \"plugins\", pluginId);\n\n const jsPath = path.join(baseDir, \"index.js\");\n if (existsSync(jsPath)) {\n return jsPath;\n }\n\n const tsPath = path.join(baseDir, \"index.ts\");\n if (existsSync(tsPath)) {\n return tsPath;\n }\n\n // Fall back to .js — the dynamic import will surface a clear error.\n return jsPath;\n }\n\n /**\n * Build a {@link PluginContext} for a specific plugin.\n *\n * The context provides credentials, a namespaced state store backed by the\n * {@link PluginStateRepository}, and a child logger.\n */\n private buildPluginContext(\n pluginId: string,\n credentials: Record<string, string>,\n ): PluginContext {\n const stateStore: PluginStateStore = {\n get: (key: string) => this.stateRepo.get(pluginId, key),\n set: (key: string, value: string) => this.stateRepo.set(pluginId, key, value),\n delete: (key: string) => this.stateRepo.delete(pluginId, key),\n getAll: () => this.stateRepo.getAll(pluginId),\n };\n\n return {\n pluginId,\n credentials,\n state: stateStore,\n logger: createChildLogger(`plugin:${pluginId}`, { pluginId }),\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new {@link PluginManager} instance.\n *\n * @param registry - Plugin registry for discovery and enable/disable state.\n * @param sandbox - Execution sandbox for error isolation.\n * @param credentials - Credential manager for validation.\n * @param stateRepo - Repository for per-plugin persistent state.\n * @returns A ready-to-use `PluginManager`.\n */\nexport function createPluginManager(\n registry: PluginRegistry,\n sandbox: PluginSandbox,\n credentials: CredentialManager,\n stateRepo: PluginStateRepository,\n): PluginManager {\n return new PluginManager(registry, sandbox, credentials, stateRepo);\n}\n","/**\n * @module plugins/sandbox\n * @description Plugin execution sandbox — error isolation & health monitoring.\n *\n * Every plugin method call is routed through `PluginSandbox` so that:\n * 1. Errors never propagate into the host process (try/catch boundary).\n * 2. Consecutive failures are counted per plugin.\n * 3. A plugin is auto-disabled once it exceeds `maxFailures` consecutive\n * errors, preventing a single broken plugin from degrading the whole\n * assistant.\n * 4. A successful call resets the failure counter, proving the plugin has\n * recovered.\n *\n * Tool handlers are wrapped via `wrapToolHandler()` so the AI model receives\n * a descriptive error string instead of an unhandled exception.\n */\n\nimport type { Logger } from \"pino\";\nimport { createChildLogger } from \"../core/logger.js\";\nimport { PluginError } from \"../core/errors.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Default maximum consecutive failures before a plugin is auto-disabled. */\nconst DEFAULT_MAX_FAILURES = 5;\n\n// ---------------------------------------------------------------------------\n// PluginSandbox\n// ---------------------------------------------------------------------------\n\n/**\n * Wraps plugin method calls in a try/catch boundary, tracks consecutive\n * failures per plugin, and auto-disables plugins that exceed the failure\n * threshold.\n */\nexport class PluginSandbox {\n /** Consecutive failure counts keyed by plugin ID. */\n private failureCounts: Map<string, number> = new Map();\n\n /** Set of plugin IDs that have been auto-disabled due to repeated failures. */\n private disabledPlugins: Set<string> = new Set();\n\n /** Maximum consecutive failures before a plugin is disabled. */\n private maxFailures: number;\n\n /** Namespaced logger for sandbox events. */\n private logger: Logger;\n\n /**\n * Create a new sandbox instance.\n *\n * @param maxFailures - Override for the failure threshold. When omitted the\n * value is read from the application config (`app.pluginHealth.maxFailures`)\n * with a hard-coded fallback of {@link DEFAULT_MAX_FAILURES}.\n */\n constructor(maxFailures?: number) {\n this.logger = createChildLogger(\"plugins:sandbox\");\n\n if (maxFailures !== undefined) {\n this.maxFailures = maxFailures;\n } else {\n try {\n // Dynamic import to avoid circular-dependency issues at module load.\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { getConfig } = require(\"../core/config.js\");\n this.maxFailures = getConfig().app.pluginHealth.maxFailures ?? DEFAULT_MAX_FAILURES;\n } catch {\n this.maxFailures = DEFAULT_MAX_FAILURES;\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Core execution wrapper\n // -----------------------------------------------------------------------\n\n /**\n * Safely execute a plugin method within a try/catch boundary.\n *\n * If the plugin is disabled the call is short-circuited and `undefined` is\n * returned. On success the failure counter is reset; on failure it is\n * incremented and the error is logged with full context.\n *\n * **Critical:** errors are _never_ allowed to propagate out of this method.\n *\n * @param pluginId - Unique identifier of the plugin.\n * @param methodName - Name of the method being invoked (for logging).\n * @param fn - The async function to execute.\n * @returns The result of `fn()` on success, or `undefined` on failure.\n */\n async safeExecute<T>(\n pluginId: string,\n methodName: string,\n fn: () => Promise<T>,\n ): Promise<T | undefined> {\n try {\n if (this.isDisabled(pluginId)) {\n this.logger.warn(\n { pluginId, methodName },\n `Plugin \"${pluginId}\" is disabled — skipping ${methodName}`,\n );\n return undefined;\n }\n\n const result = await fn();\n this.recordSuccess(pluginId);\n return result;\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.recordFailure(pluginId, error, methodName);\n this.logger.error(\n {\n pluginId,\n methodName,\n error: error.message,\n failureCount: this.getFailureCount(pluginId),\n maxFailures: this.maxFailures,\n },\n `Plugin \"${pluginId}\" method \"${methodName}\" threw: ${error.message}`,\n );\n return undefined;\n }\n }\n\n // -----------------------------------------------------------------------\n // Tool handler wrapper\n // -----------------------------------------------------------------------\n\n /**\n * Wrap a tool handler so that errors are caught and returned as a\n * descriptive string rather than crashing the process.\n *\n * The AI model therefore receives actionable feedback about the failure\n * instead of an unhandled exception.\n *\n * @param pluginId - Unique identifier of the owning plugin.\n * @param toolName - Display name of the tool.\n * @param handler - The original tool handler function.\n * @returns A wrapped handler with identical signature.\n */\n wrapToolHandler(\n pluginId: string,\n toolName: string,\n handler: (args: Record<string, unknown>) => Promise<string | Record<string, unknown>>,\n ): (args: Record<string, unknown>) => Promise<string | Record<string, unknown>> {\n return async (args: Record<string, unknown>): Promise<string | Record<string, unknown>> => {\n const result = await this.safeExecute<string | Record<string, unknown>>(\n pluginId,\n `tool:${toolName}`,\n () => handler(args),\n );\n\n if (result === undefined) {\n return `Error: Tool ${toolName} failed: ${\n this.isDisabled(pluginId)\n ? \"plugin has been disabled due to repeated failures\"\n : \"unexpected error during execution\"\n }`;\n }\n\n return result;\n };\n }\n\n // -----------------------------------------------------------------------\n // Failure / success tracking\n // -----------------------------------------------------------------------\n\n /**\n * Record a failure for a plugin and auto-disable it if the threshold is\n * reached.\n *\n * @param pluginId - Unique identifier of the plugin.\n * @param error - The error that occurred.\n * @param methodName - The method that failed (for logging).\n * @returns `true` if the plugin was auto-disabled as a result of this failure.\n */\n recordFailure(pluginId: string, error: Error, methodName: string): boolean {\n const current = (this.failureCounts.get(pluginId) ?? 0) + 1;\n this.failureCounts.set(pluginId, current);\n\n // Best-effort persistent health logging — never throw from here.\n try {\n const { PluginHealthRepository } = require(\"../storage/repositories/plugin-health.js\");\n const repo = new PluginHealthRepository();\n repo.logHealth(pluginId, \"error\", error.message);\n } catch {\n // Storage unavailable — the in-memory counter is still maintained.\n }\n\n if (current >= this.maxFailures && !this.disabledPlugins.has(pluginId)) {\n this.disabledPlugins.add(pluginId);\n this.logger.warn(\n {\n pluginId,\n methodName,\n failureCount: current,\n maxFailures: this.maxFailures,\n },\n `Plugin \"${pluginId}\" auto-disabled after ${current} consecutive failures (threshold: ${this.maxFailures})`,\n );\n return true;\n }\n\n return false;\n }\n\n /**\n * Record a successful execution for a plugin.\n *\n * Resets the consecutive failure counter to zero — a successful call proves\n * the plugin is healthy.\n *\n * @param pluginId - Unique identifier of the plugin.\n */\n recordSuccess(pluginId: string): void {\n this.failureCounts.set(pluginId, 0);\n }\n\n // -----------------------------------------------------------------------\n // Status queries\n // -----------------------------------------------------------------------\n\n /**\n * Check whether a plugin has been auto-disabled due to repeated failures.\n *\n * @param pluginId - Unique identifier of the plugin.\n * @returns `true` if the plugin is currently disabled.\n */\n isDisabled(pluginId: string): boolean {\n return this.disabledPlugins.has(pluginId);\n }\n\n /**\n * Get the current consecutive failure count for a plugin.\n *\n * @param pluginId - Unique identifier of the plugin.\n * @returns The number of consecutive failures (0 when not tracked).\n */\n getFailureCount(pluginId: string): number {\n return this.failureCounts.get(pluginId) ?? 0;\n }\n\n /**\n * Reset the failure counter and re-enable a previously disabled plugin.\n *\n * @param pluginId - Unique identifier of the plugin.\n */\n resetPlugin(pluginId: string): void {\n this.failureCounts.delete(pluginId);\n this.disabledPlugins.delete(pluginId);\n this.logger.info({ pluginId }, `Plugin \"${pluginId}\" has been reset and re-enabled`);\n }\n\n /**\n * Build a health summary for every plugin the sandbox has interacted with.\n *\n * @returns A map from plugin ID to its current failure count and disabled\n * status.\n */\n getHealthSummary(): Map<string, { failures: number; disabled: boolean }> {\n const summary = new Map<string, { failures: number; disabled: boolean }>();\n\n // Include all plugins that have a failure count entry.\n for (const [pluginId, failures] of this.failureCounts) {\n summary.set(pluginId, {\n failures,\n disabled: this.disabledPlugins.has(pluginId),\n });\n }\n\n // Include disabled plugins that may have had their counter cleared.\n for (const pluginId of this.disabledPlugins) {\n if (!summary.has(pluginId)) {\n summary.set(pluginId, {\n failures: 0,\n disabled: true,\n });\n }\n }\n\n return summary;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\n/** Pre-configured sandbox instance for application-wide use. */\nexport const pluginSandbox = new PluginSandbox();\n","/**\n * @module plugins/credentials\n * @description Per-plugin credential management — reads, validates, and\n * provides credentials declared by each plugin's manifest.\n *\n * Credentials are sourced from the application's `config.json` under\n * `plugins.<pluginId>.credentials`. The {@link CredentialManager} ensures\n * that every key a plugin declares as required is present and non-empty\n * before the plugin is allowed to initialise.\n *\n * **Security:** credential *values* are never written to logs.\n */\n\nimport type { Logger } from \"pino\";\nimport { getConfig } from \"../core/config.js\";\nimport { PluginError } from \"../core/errors.js\";\nimport { createChildLogger } from \"../core/logger.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Shape of a single credential requirement from a plugin manifest. */\ninterface CredentialRequirement {\n key: string;\n description: string;\n type?: string;\n}\n\n/** Result of a validation check — either valid or carrying the missing keys. */\ntype ValidationResult =\n | { valid: true }\n | { valid: false; missing: string[] };\n\n/** Per-plugin status entry returned by {@link CredentialManager.getCredentialStatus}. */\ninterface CredentialStatusEntry {\n pluginId: string;\n configured: string[];\n missing: string[];\n}\n\n// ---------------------------------------------------------------------------\n// CredentialManager\n// ---------------------------------------------------------------------------\n\n/**\n * Reads, validates, and provides credentials for each plugin based on the\n * application configuration (`config.json`).\n *\n * Typical usage inside the plugin loader:\n * ```ts\n * const creds = credentialManager.getValidatedCredentials(\n * manifest.id,\n * manifest.requiredCredentials,\n * );\n * ```\n */\nexport class CredentialManager {\n private logger: Logger;\n\n constructor() {\n this.logger = createChildLogger(\"plugins:credentials\");\n }\n\n /**\n * Get credentials for a plugin from config.json.\n * Returns the credentials `Record` or an empty object if not configured.\n */\n getPluginCredentials(pluginId: string): Record<string, string> {\n const { app } = getConfig();\n return app.plugins[pluginId]?.credentials ?? {};\n }\n\n /**\n * Validate that all required credentials are present and non-empty.\n *\n * @returns `{ valid: true }` when every required key is present with a\n * non-empty value, or `{ valid: false, missing }` listing the keys that\n * are absent or empty.\n */\n validateCredentials(\n pluginId: string,\n requiredCredentials: CredentialRequirement[],\n ): ValidationResult {\n const credentials = this.getPluginCredentials(pluginId);\n\n const missing = requiredCredentials\n .filter(({ key }) => {\n const value = credentials[key];\n return value === undefined || value === \"\";\n })\n .map(({ key }) => key);\n\n if (missing.length === 0) {\n this.logger.debug({ pluginId }, \"All required credentials present\");\n return { valid: true };\n }\n\n this.logger.warn(\n { pluginId, missing },\n \"Missing required credentials for plugin\",\n );\n return { valid: false, missing };\n }\n\n /**\n * Get credentials for a plugin, throwing {@link PluginError} if any\n * required ones are missing.\n *\n * This is the main method plugins use during initialisation.\n *\n * @throws {PluginError} with code `PLUGIN_CREDENTIALS_MISSING` when one or\n * more required keys are absent or empty.\n */\n getValidatedCredentials(\n pluginId: string,\n requiredCredentials: CredentialRequirement[],\n ): Record<string, string> {\n const result = this.validateCredentials(pluginId, requiredCredentials);\n\n if (!result.valid) {\n throw PluginError.credentialsMissing(pluginId, result.missing);\n }\n\n this.logger.info(\n { pluginId, count: requiredCredentials.length },\n \"Credentials validated successfully\",\n );\n\n return this.getPluginCredentials(pluginId);\n }\n\n /**\n * Check whether a plugin has all required credentials configured.\n *\n * Non-throwing convenience wrapper around {@link validateCredentials}.\n */\n hasRequiredCredentials(\n pluginId: string,\n requiredCredentials: CredentialRequirement[],\n ): boolean {\n return this.validateCredentials(pluginId, requiredCredentials).valid;\n }\n\n /**\n * Get a summary of credential status for all known plugins.\n * Useful for the CLI status command.\n *\n * @param plugins - Array of plugin descriptors containing their id and\n * required credential definitions.\n * @returns Per-plugin breakdown of which credentials are configured vs\n * missing.\n */\n getCredentialStatus(\n plugins: Array<{ id: string; requiredCredentials: CredentialRequirement[] }>,\n ): CredentialStatusEntry[] {\n return plugins.map(({ id, requiredCredentials }) => {\n const credentials = this.getPluginCredentials(id);\n\n const configured: string[] = [];\n const missing: string[] = [];\n\n for (const { key } of requiredCredentials) {\n const value = credentials[key];\n if (value !== undefined && value !== \"\") {\n configured.push(key);\n } else {\n missing.push(key);\n }\n }\n\n return { pluginId: id, configured, missing };\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\n/** Application-wide credential manager instance. */\nexport const credentialManager = new CredentialManager();\n","/**\n * @module bot/bot\n * @description Telegraf bot setup, lifecycle management, and polling configuration.\n *\n * Provides the {@link TelegramBot} class which wraps a Telegraf instance with:\n * - A configurable middleware stack (logging → auth → error handling)\n * - Graceful shutdown on SIGINT / SIGTERM\n * - Structured pino logging via a child logger scoped to `\"bot\"`\n *\n * The class intentionally does **not** depend on the global config singleton;\n * the bot token and allowed-user ID are passed in at construction /\n * initialisation time so the module stays easily testable.\n */\n\nimport { Telegraf, Context } from \"telegraf\";\nimport { message } from \"telegraf/filters\";\nimport type { Logger } from \"pino\";\nimport { createChildLogger } from \"../core/logger.js\";\nimport { createLoggingMiddleware } from \"./middleware/logging.js\";\nimport { createAuthMiddleware } from \"./middleware/auth.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Options accepted by {@link TelegramBot.initialize}.\n */\nexport interface BotInitOptions {\n /** Telegram user ID that is allowed to interact with the bot. */\n allowedUserId: number;\n\n /** Handler invoked for every incoming text message. */\n onMessage: (ctx: Context, text: string) => Promise<void>;\n\n /**\n * Optional handler invoked for bot commands.\n *\n * Receives the command name (without the leading `/`) and the remaining\n * argument string.\n */\n onCommand?: (ctx: Context, command: string, args: string) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// TelegramBot\n// ---------------------------------------------------------------------------\n\n/**\n * High-level wrapper around a Telegraf instance.\n *\n * Usage:\n * ```ts\n * const bot = createBot(token);\n * bot.initialize({ allowedUserId: 12345, onMessage: handler });\n * await bot.launch();\n * ```\n */\nexport class TelegramBot {\n private bot: Telegraf;\n private isRunning: boolean = false;\n private logger: Logger;\n\n /**\n * @param token - The Telegram Bot API token obtained from BotFather.\n */\n constructor(token: string) {\n this.bot = new Telegraf(token);\n this.logger = createChildLogger(\"bot\");\n }\n\n // -----------------------------------------------------------------------\n // Initialisation\n // -----------------------------------------------------------------------\n\n /**\n * Register the middleware stack and message / command handlers.\n *\n * **Must** be called before {@link launch}. Middleware is applied in the\n * following order:\n *\n * 1. Logging middleware — logs every incoming update\n * 2. Auth guard middleware — drops unauthorised updates\n * 3. Error-handling middleware — catches downstream errors\n * 4. Command handler (if `onCommand` provided)\n * 5. Text-message handler\n * 6. Catch-all for unhandled update types\n */\n initialize(options: BotInitOptions): void {\n const { allowedUserId, onMessage, onCommand } = options;\n\n // 1. Logging\n this.bot.use(createLoggingMiddleware());\n\n // 2. Auth guard\n this.bot.use(createAuthMiddleware(allowedUserId));\n\n // 3. Error handling — wraps every subsequent handler in a try/catch\n this.bot.use(async (ctx, next) => {\n try {\n await next();\n } catch (err: unknown) {\n const errorMessage =\n err instanceof Error ? err.message : String(err);\n this.logger.error(\n { err, updateId: ctx.update.update_id },\n \"Unhandled error in middleware chain\",\n );\n try {\n await ctx.reply(\n \"⚠️ An unexpected error occurred. Please try again later.\",\n );\n } catch (replyErr) {\n this.logger.error(\n { err: replyErr },\n \"Failed to send error reply to user\",\n );\n }\n }\n });\n\n // 4. Command handler (optional)\n //\n // Handlers are dispatched as fire-and-forget background tasks so that\n // Telegraf's update loop is NOT blocked. This allows parallel messages\n // to be dispatched to the session pool concurrently. Errors are caught\n // inside the handlers themselves (not the middleware error handler).\n if (onCommand) {\n this.bot.on(message(\"text\"), async (ctx, next) => {\n const text = ctx.message.text;\n if (!text.startsWith(\"/\")) {\n return next();\n }\n\n const parts = text.slice(1).split(/\\s+/);\n const command = parts[0] ?? \"\";\n const cleanCommand = command.split(\"@\")[0]!;\n const args = parts.slice(1).join(\" \");\n\n // Fire-and-forget — don't await so the next update can be dispatched\n onCommand(ctx, cleanCommand, args).catch((err: unknown) => {\n this.logger.error({ err, command: cleanCommand }, \"Unhandled error in command handler\");\n });\n });\n }\n\n // 5. Text message handler (fire-and-forget for parallel processing)\n this.bot.on(message(\"text\"), async (ctx) => {\n onMessage(ctx, ctx.message.text).catch((err: unknown) => {\n this.logger.error({ err }, \"Unhandled error in message handler\");\n });\n });\n\n // 6. Catch-all for unhandled update types\n this.bot.on(\"message\", (ctx) => {\n this.logger.debug(\n { updateType: ctx.updateType, messageType: \"non-text\" },\n \"Received non-text message — ignoring\",\n );\n });\n\n this.logger.info(\"Bot initialised — middleware and handlers registered\");\n }\n\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n\n /**\n * Start the bot in long-polling mode.\n *\n * Telegraf's `launch()` never resolves — it runs the polling loop forever.\n * We use the `onLaunch` callback to detect when the initial handshake\n * (getMe + deleteWebhook) succeeds, then let polling continue in the\n * background.\n *\n * @throws {Error} If the Telegram API is unreachable after all retry attempts.\n */\n async launch(): Promise<void> {\n if (this.isRunning) {\n this.logger.warn(\"launch() called but bot is already running\");\n return;\n }\n\n const maxRetries = 3;\n const retryDelayMs = 5_000;\n /** Timeout for the initial getMe + deleteWebhook handshake. */\n const connectTimeoutMs = 30_000;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n // launch() never resolves because it runs the polling loop.\n // The onLaunch callback fires right after getMe + deleteWebhook\n // succeed and before polling starts — that's our \"connected\" signal.\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(\n () => reject(Object.assign(\n new Error(\"Telegram connection timed out\"),\n { code: \"ETIMEDOUT\" },\n )),\n connectTimeoutMs,\n );\n\n // Fire-and-forget: launch runs the polling loop in the background.\n // Errors during the initial handshake are caught via .catch().\n this.bot.launch(() => {\n clearTimeout(timer);\n resolve();\n }).catch((err: unknown) => {\n clearTimeout(timer);\n reject(err);\n });\n });\n\n this.isRunning = true;\n\n const botInfo = this.bot.botInfo;\n if (botInfo) {\n this.logger.info(\n { username: botInfo.username },\n `Bot launched as @${botInfo.username}`,\n );\n } else {\n this.logger.info(\"Bot launched (username not yet available)\");\n }\n\n // NOTE: Signal handlers for graceful shutdown are registered by the\n // App orchestrator (app.ts), NOT here — avoids duplicate handlers.\n\n return; // success\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n const code = (err as NodeJS.ErrnoException).code;\n\n const isNetworkError = code === \"ETIMEDOUT\" || code === \"ECONNREFUSED\" ||\n code === \"ENOTFOUND\" || code === \"EAI_AGAIN\" || code === \"ENETUNREACH\";\n\n if (isNetworkError && attempt < maxRetries) {\n console.log(` ⚠ Telegram connection failed (${code}). Retrying in ${retryDelayMs / 1000}s… (${attempt}/${maxRetries})`);\n this.logger.warn(\n { attempt, maxRetries, code },\n `Telegram connection failed (${code}). Retrying in ${retryDelayMs / 1000}s…`,\n );\n await new Promise((r) => setTimeout(r, retryDelayMs));\n continue;\n }\n\n this.logger.error({ err, code }, `Failed to connect to Telegram API: ${msg}`);\n throw new Error(\n `Could not connect to Telegram (${code || \"UNKNOWN\"}): ${msg}\\n` +\n \" → Check your internet connection and bot token.\\n\" +\n \" → Verify the token with: https://api.telegram.org/bot<TOKEN>/getMe\",\n );\n }\n }\n }\n\n /**\n * Stop the bot gracefully.\n *\n * Safe to call multiple times — subsequent calls are no-ops.\n */\n async stop(): Promise<void> {\n if (!this.isRunning) {\n this.logger.debug(\"stop() called but bot is not running\");\n return;\n }\n\n this.bot.stop(\"graceful shutdown\");\n this.isRunning = false;\n this.logger.info(\"Bot stopped\");\n }\n\n // -----------------------------------------------------------------------\n // Accessors\n // -----------------------------------------------------------------------\n\n /**\n * Return the underlying Telegraf instance for advanced or low-level usage.\n */\n getBot(): Telegraf {\n return this.bot;\n }\n\n /**\n * Check whether the bot is currently running (polling).\n */\n isActive(): boolean {\n return this.isRunning;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Convenience factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new {@link TelegramBot} instance.\n *\n * @param token - The Telegram Bot API token.\n * @returns An uninitialised `TelegramBot` — call {@link TelegramBot.initialize}\n * then {@link TelegramBot.launch} to start it.\n */\nexport function createBot(token: string): TelegramBot {\n return new TelegramBot(token);\n}\n","/**\n * @module bot/middleware/logging\n * @description Request logging middleware for Telegram bot interactions.\n *\n * Logs every incoming Telegram update with structured metadata including\n * update type, user ID, a short message preview, and wall-clock processing\n * time. Uses a pino child logger scoped to `\"bot:middleware:logging\"`.\n */\n\nimport type { Context, MiddlewareFn } from \"telegraf\";\nimport { createChildLogger } from \"../../core/logger.js\";\n\nconst logger = createChildLogger(\"bot:middleware:logging\");\n\n/**\n * Create Telegraf middleware that logs every incoming update.\n *\n * For each update the middleware records:\n * - `updateType` — the Telegram update type (e.g. `\"message\"`, `\"callback_query\"`)\n * - `updateId` — the numeric update ID\n * - `userId` — the sender's Telegram user ID (if available)\n * - `preview` — the first 50 characters of the text payload (if any)\n * - `durationMs` — wall-clock processing time of downstream middleware\n *\n * @returns A Telegraf middleware function.\n */\nexport function createLoggingMiddleware(): MiddlewareFn<Context> {\n return async (ctx, next) => {\n const start = Date.now();\n const updateId = ctx.update.update_id;\n const updateType = ctx.updateType;\n const userId = ctx.from?.id;\n\n // Extract a short text preview from the update when available.\n const rawText =\n (ctx.message && \"text\" in ctx.message ? ctx.message.text : undefined) ??\n (ctx.callbackQuery && \"data\" in ctx.callbackQuery\n ? ctx.callbackQuery.data\n : undefined);\n const preview = rawText ? rawText.slice(0, 50) : undefined;\n\n logger.debug(\n { updateId, updateType, userId, preview },\n \"Incoming update\",\n );\n\n await next();\n\n const durationMs = Date.now() - start;\n logger.debug(\n { updateId, updateType, userId, durationMs },\n \"Update processed\",\n );\n };\n}\n","/**\n * @module bot/middleware/auth\n * @description Authentication middleware to restrict bot access to authorized users.\n *\n * Compares the sender's Telegram user ID (`ctx.from.id`) against a single\n * allowed ID. Unauthorized updates are silently dropped — no reply is sent\n * to the user, but the attempt is logged at `warn` level so it can be\n * audited.\n */\n\nimport type { Context, MiddlewareFn } from \"telegraf\";\nimport { createChildLogger } from \"../../core/logger.js\";\n\nconst logger = createChildLogger(\"bot:auth\");\n\n/**\n * Create Telegraf middleware that restricts access to a single authorized user.\n *\n * If the incoming update has no `ctx.from` (rare, but possible for channel\n * posts) or the user ID does not match `allowedUserId`, the update is\n * silently ignored — `next()` is never called.\n *\n * @param allowedUserId - The Telegram user ID that is permitted to interact\n * with the bot.\n * @returns A Telegraf middleware function.\n */\nexport function createAuthMiddleware(\n allowedUserId: number,\n): MiddlewareFn<Context> {\n return async (ctx, next) => {\n const senderId = ctx.from?.id;\n\n if (senderId === undefined) {\n logger.warn(\n { updateId: ctx.update.update_id },\n \"Update has no sender — dropping\",\n );\n return;\n }\n\n if (senderId !== allowedUserId) {\n const username = ctx.from?.username;\n logger.warn(\n { senderId, username, allowedUserId, updateId: ctx.update.update_id },\n \"Unauthorized access attempt — dropping update\",\n );\n return;\n }\n\n await next();\n };\n}\n","/**\n * @module bot/handlers/message\n * @description Message handler for processing incoming Telegram text messages.\n *\n * Receives user text from the bot layer, forwards it to the AI session,\n * persists both sides of the conversation, and replies via Telegram.\n * Long responses are automatically split to respect Telegram's 4 096-char limit.\n */\n\nimport type { Context } from \"telegraf\";\nimport type { SessionManager } from \"../../ai/session.js\";\nimport type { ConversationRepository } from \"../../storage/repositories/conversation.js\";\nimport { createChildLogger } from \"../../core/logger.js\";\n\nconst logger = createChildLogger(\"bot:handlers\");\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Dependencies injected into the message handler factory. */\nexport interface MessageHandlerDeps {\n /** AI session manager used to send prompts. */\n sessionManager: SessionManager;\n /** Repository for persisting conversation messages. */\n conversationRepo: ConversationRepository;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Split a message into chunks that fit within Telegram's per-message limit.\n *\n * The function first attempts to split on paragraph boundaries (`\\n\\n`).\n * If a single paragraph exceeds `maxLength` it falls back to splitting on\n * newline boundaries, and finally to a hard character cut.\n *\n * @param text - The text to split.\n * @param maxLength - Maximum characters per chunk (default `4096`).\n * @returns An array of non-empty string chunks.\n */\nexport function splitMessage(text: string, maxLength: number = 4096): string[] {\n if (text.length <= maxLength) {\n return [text];\n }\n\n const chunks: string[] = [];\n let remaining = text;\n\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n // Try to split on a paragraph boundary\n let splitIdx = remaining.lastIndexOf(\"\\n\\n\", maxLength);\n\n // Fall back to a single newline\n if (splitIdx <= 0) {\n splitIdx = remaining.lastIndexOf(\"\\n\", maxLength);\n }\n\n // Hard split as last resort\n if (splitIdx <= 0) {\n splitIdx = maxLength;\n }\n\n chunks.push(remaining.slice(0, splitIdx));\n remaining = remaining.slice(splitIdx).replace(/^\\n+/, \"\");\n }\n\n return chunks.filter((c) => c.length > 0);\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the message handler function wired to the provided dependencies.\n *\n * The returned handler follows this flow:\n * 1. Send a \"typing\" indicator so the user knows the bot is working.\n * 2. If all AI sessions are busy, notify the user their message is queued.\n * 3. Persist the user message in conversation history.\n * 4. Forward the prompt to the AI via `sessionManager.sendMessage` — this\n * acquires a session from the pool so multiple messages process in parallel.\n * 5. Persist the assistant response.\n * 6. Reply **to the user's original message** (splitting long responses as\n * needed), so each answer threads back to the question that triggered it.\n *\n * @param deps - Injected dependencies ({@link MessageHandlerDeps}).\n * @returns An async handler compatible with `TelegramBot.onMessage`.\n */\nexport function createMessageHandler(deps: MessageHandlerDeps) {\n const { sessionManager, conversationRepo } = deps;\n\n return async (ctx: Context, text: string): Promise<void> => {\n // The message ID we'll thread our reply to\n const replyToId = ctx.message?.message_id;\n const replyOpts = replyToId\n ? { reply_parameters: { message_id: replyToId } } as Record<string, unknown>\n : {};\n\n // 1. Typing indicator\n await ctx.sendChatAction(\"typing\");\n\n // 2. If all pool sessions are busy, let the user know they're queued\n if (sessionManager.getAvailableCount() === 0) {\n await ctx.reply(\"⏳ All AI sessions busy — your message is queued.\", replyOpts);\n }\n\n // 3. Store the user message\n conversationRepo.addMessage(\"user\", text);\n logger.debug({ textLength: text.length }, \"User message stored\");\n\n // 4. Ensure the AI session pool is active\n if (!sessionManager.isActive()) {\n logger.error(\"No active AI session — cannot process message\");\n await ctx.reply(\"⚠️ AI session is not active. Please restart the bot.\", replyOpts);\n return;\n }\n\n // Keep sending \"typing\" while waiting for a session and during AI processing\n const typingInterval = setInterval(() => {\n ctx.sendChatAction(\"typing\").catch(() => {});\n }, 4000);\n\n // 5. Send to AI — acquires a session from the pool (blocks if all busy)\n try {\n const response = await sessionManager.sendMessage(text);\n\n if (!response) {\n logger.warn(\"AI returned an empty response\");\n await ctx.reply(\"The AI returned an empty response. Please try again.\", replyOpts);\n return;\n }\n\n // 6. Persist assistant response\n const model = sessionManager.getCurrentModel();\n conversationRepo.addMessage(\"assistant\", response, model);\n logger.debug(\n { responseLength: response.length, model },\n \"Assistant response stored\",\n );\n\n // 7. Reply to the user's specific message — split if necessary\n const chunks = splitMessage(response);\n for (const chunk of chunks) {\n await ctx.reply(chunk, replyOpts);\n }\n } catch (error: unknown) {\n const reason = error instanceof Error ? error.message : String(error);\n logger.error({ err: error }, \"Failed to process message through AI\");\n await ctx.reply(\"Sorry, I couldn't process that. Please try again.\", replyOpts);\n } finally {\n clearInterval(typingInterval);\n }\n };\n}\n","/**\n * @module core/heartbeat\n * @description Scheduled heartbeat prompt system with deduplication support.\n *\n * Reads `.heartbeat.md` files from the `heartbeats/` directory and sends each\n * prompt to the AI session at a configurable interval. Responses are forwarded\n * to the user via the Telegram bot.\n *\n * **Deduplication**: Heartbeat prompts can include a `{{DEDUP_STATE}}` placeholder.\n * When present, the manager injects previously-processed item IDs into the prompt\n * so the AI skips them. The AI must output a `<!-- PROCESSED: id1, id2 -->` marker\n * in its response; those IDs are persisted in a companion `.state.json` file and\n * injected on subsequent runs.\n *\n * Example file: `heartbeats/morning-briefing.heartbeat.md`\n * ```\n * Summarize my unread emails and today's calendar events.\n * ```\n */\n\nimport { readdirSync, readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport type { Logger } from \"pino\";\nimport { createChildLogger } from \"./logger.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Directory containing heartbeat prompt files. */\nconst HEARTBEATS_DIR = \"./heartbeats\";\n\n/** File extension for heartbeat prompt files. */\nconst HEARTBEAT_EXT = \".heartbeat.md\";\n\n/** Extension for per-event deduplication state files. */\nconst STATE_EXT = \".state.json\";\n\n/** Placeholder token in prompts that gets replaced with dedup context. */\nconst DEDUP_PLACEHOLDER = \"{{DEDUP_STATE}}\";\n\n/** Maximum number of processed IDs to retain per event (prevents unbounded growth). */\nconst MAX_STATE_IDS = 200;\n\n/** Regex to extract processed IDs from the AI response. */\nconst PROCESSED_MARKER_RE = /<!--\\s*PROCESSED:\\s*(.*?)\\s*-->/gi;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single heartbeat event loaded from disk. */\nexport interface HeartbeatEvent {\n /** Event name derived from the filename (e.g. \"morning-briefing\"). */\n name: string;\n /** The prompt text sent to the AI agent. */\n prompt: string;\n /** Absolute path to the source file. */\n filePath: string;\n}\n\n/**\n * Callback invoked for each heartbeat prompt execution.\n *\n * @param prompt - The prompt text to send to the AI.\n * @returns The AI's response text, or null if the send failed.\n */\nexport type HeartbeatSendFn = (prompt: string) => Promise<string | null>;\n\n/**\n * Callback invoked to deliver the AI's response to the user.\n *\n * @param eventName - The heartbeat event name (for context).\n * @param response - The AI's response text.\n */\nexport type HeartbeatNotifyFn = (eventName: string, response: string) => Promise<void>;\n\n/**\n * Persisted deduplication state for a single heartbeat event.\n * Stored as a companion `.state.json` file alongside the `.heartbeat.md`.\n */\nexport interface HeartbeatState {\n /** IDs of items already processed (capped at {@link MAX_STATE_IDS}). */\n processedIds: string[];\n /** ISO timestamp of the last successful run. */\n lastRun: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// HeartbeatManager\n// ---------------------------------------------------------------------------\n\n/**\n * Manages scheduled heartbeat events — periodic AI prompts that run\n * automatically at a configured interval.\n *\n * Usage:\n * ```ts\n * const hb = new HeartbeatManager();\n * hb.start(intervalMinutes, sendFn, notifyFn);\n * // later…\n * hb.stop();\n * ```\n */\nexport class HeartbeatManager {\n private logger: Logger;\n private timer: ReturnType<typeof setInterval> | null = null;\n private isRunning = false;\n /** Prevents overlapping cycles when a run takes longer than the interval. */\n private cycleInProgress = false;\n\n constructor() {\n this.logger = createChildLogger(\"heartbeat\");\n ensureHeartbeatsDir();\n }\n\n // -----------------------------------------------------------------------\n // File operations\n // -----------------------------------------------------------------------\n\n /**\n * Discover all heartbeat event files in the heartbeats directory.\n *\n * @returns Array of {@link HeartbeatEvent} objects, one per file.\n */\n listEvents(): HeartbeatEvent[] {\n ensureHeartbeatsDir();\n\n const files = readdirSync(HEARTBEATS_DIR).filter((f) => f.endsWith(HEARTBEAT_EXT));\n\n return files.map((file) => {\n const filePath = join(HEARTBEATS_DIR, file);\n const name = basename(file, HEARTBEAT_EXT);\n const prompt = readFileSync(filePath, \"utf-8\").trim();\n return { name, prompt, filePath };\n });\n }\n\n /**\n * Add a new heartbeat event by creating a `.heartbeat.md` file.\n *\n * @param name - Event name (used as the filename, kebab-cased).\n * @param prompt - The prompt text to send to the AI on each heartbeat.\n * @throws {Error} If an event with the same name already exists.\n */\n addEvent(name: string, prompt: string): void {\n ensureHeartbeatsDir();\n\n const safeName = name.toLowerCase().replace(/\\s+/g, \"-\").replace(/[^a-z0-9-]/g, \"\");\n const filePath = join(HEARTBEATS_DIR, `${safeName}${HEARTBEAT_EXT}`);\n\n if (existsSync(filePath)) {\n throw new Error(`Heartbeat event \"${safeName}\" already exists at ${filePath}`);\n }\n\n writeFileSync(filePath, prompt.trim() + \"\\n\", \"utf-8\");\n this.logger.info({ name: safeName, filePath }, \"Heartbeat event created\");\n }\n\n /**\n * Remove a heartbeat event by name.\n *\n * @param name - The event name to remove.\n * @throws {Error} If the event does not exist.\n */\n removeEvent(name: string): void {\n const safeName = name.toLowerCase().replace(/\\s+/g, \"-\").replace(/[^a-z0-9-]/g, \"\");\n const filePath = join(HEARTBEATS_DIR, `${safeName}${HEARTBEAT_EXT}`);\n\n if (!existsSync(filePath)) {\n throw new Error(`Heartbeat event \"${safeName}\" not found`);\n }\n\n unlinkSync(filePath);\n\n // Also remove companion state file if it exists\n const statePath = join(HEARTBEATS_DIR, `${safeName}${STATE_EXT}`);\n if (existsSync(statePath)) {\n unlinkSync(statePath);\n }\n\n this.logger.info({ name: safeName }, \"Heartbeat event removed\");\n }\n\n // -----------------------------------------------------------------------\n // Deduplication state\n // -----------------------------------------------------------------------\n\n /**\n * Load the persisted deduplication state for an event.\n *\n * @param eventName - The event name (kebab-cased, no extension).\n * @returns The stored state, or a fresh empty state if none exists.\n */\n loadState(eventName: string): HeartbeatState {\n const statePath = join(HEARTBEATS_DIR, `${eventName}${STATE_EXT}`);\n\n if (!existsSync(statePath)) {\n return { processedIds: [], lastRun: null };\n }\n\n try {\n const raw = readFileSync(statePath, \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<HeartbeatState>;\n return {\n processedIds: Array.isArray(parsed.processedIds) ? parsed.processedIds : [],\n lastRun: parsed.lastRun ?? null,\n };\n } catch (err) {\n this.logger.warn({ err, eventName }, \"Failed to parse state file — resetting\");\n return { processedIds: [], lastRun: null };\n }\n }\n\n /**\n * Persist deduplication state for an event. Caps stored IDs at\n * {@link MAX_STATE_IDS} to prevent the file from growing unboundedly.\n *\n * @param eventName - The event name (kebab-cased, no extension).\n * @param state - The state to persist.\n */\n saveState(eventName: string, state: HeartbeatState): void {\n const statePath = join(HEARTBEATS_DIR, `${eventName}${STATE_EXT}`);\n\n // Keep only the most recent IDs\n const trimmed: HeartbeatState = {\n processedIds: state.processedIds.slice(-MAX_STATE_IDS),\n lastRun: state.lastRun,\n };\n\n writeFileSync(statePath, JSON.stringify(trimmed, null, 2) + \"\\n\", \"utf-8\");\n this.logger.debug({ eventName, idCount: trimmed.processedIds.length }, \"State saved\");\n }\n\n /**\n * Inject deduplication context into a prompt. If the prompt contains the\n * `{{DEDUP_STATE}}` placeholder, it is replaced with a block listing the\n * previously-processed IDs. If no placeholder is present, the prompt is\n * returned unchanged.\n *\n * @param prompt - The raw prompt text from the `.heartbeat.md` file.\n * @param state - The loaded deduplication state.\n * @returns The prompt with dedup context injected.\n */\n private injectDedupContext(prompt: string, state: HeartbeatState): string {\n if (!prompt.includes(DEDUP_PLACEHOLDER)) {\n return prompt;\n }\n\n if (state.processedIds.length === 0) {\n return prompt.replace(DEDUP_PLACEHOLDER, \"No previously processed items — this is the first run.\");\n }\n\n const idList = state.processedIds.map((id) => `- ${id}`).join(\"\\n\");\n const context = [\n `Previously processed IDs (${state.processedIds.length} total) — SKIP these:`,\n idList,\n ].join(\"\\n\");\n\n return prompt.replace(DEDUP_PLACEHOLDER, context);\n }\n\n /**\n * Extract newly-processed item IDs from the AI's response. Looks for\n * `<!-- PROCESSED: id1, id2, id3 -->` markers anywhere in the text.\n *\n * @param response - The full AI response text.\n * @returns Array of extracted IDs (may be empty if no marker found).\n */\n private extractProcessedIds(response: string): string[] {\n const ids: string[] = [];\n\n let match: RegExpExecArray | null;\n while ((match = PROCESSED_MARKER_RE.exec(response)) !== null) {\n const raw = match[1] ?? \"\";\n for (const id of raw.split(\",\")) {\n const trimmed = id.trim();\n if (trimmed) ids.push(trimmed);\n }\n }\n\n // Reset regex lastIndex for next call\n PROCESSED_MARKER_RE.lastIndex = 0;\n\n return ids;\n }\n\n // -----------------------------------------------------------------------\n // Scheduling\n // -----------------------------------------------------------------------\n\n /**\n * Start the heartbeat scheduler.\n *\n * Runs all heartbeat events immediately on first tick, then repeats at the\n * configured interval.\n *\n * @param intervalMinutes - Minutes between each heartbeat cycle.\n * @param sendFn - Function that sends a prompt to the AI and returns the response.\n * @param notifyFn - Function that delivers the AI response to the user.\n */\n start(\n intervalMinutes: number,\n sendFn: HeartbeatSendFn,\n notifyFn: HeartbeatNotifyFn,\n ): void {\n if (this.isRunning) {\n this.logger.warn(\"Heartbeat scheduler already running\");\n return;\n }\n\n if (intervalMinutes <= 0) {\n this.logger.info(\"Heartbeat disabled (interval is 0)\");\n return;\n }\n\n const events = this.listEvents();\n if (events.length === 0) {\n this.logger.info(\"No heartbeat events found — scheduler idle\");\n }\n\n const intervalMs = intervalMinutes * 60 * 1000;\n this.isRunning = true;\n\n this.logger.info(\n { intervalMinutes, eventCount: events.length },\n `Heartbeat scheduler started (every ${intervalMinutes} min, ${events.length} events)`,\n );\n\n // Execute the heartbeat cycle with deduplication support\n const runCycle = async () => {\n // Guard: skip if a previous cycle is still running\n if (this.cycleInProgress) {\n this.logger.debug(\"Skipping heartbeat cycle — previous cycle still in progress\");\n return;\n }\n this.cycleInProgress = true;\n\n try {\n const currentEvents = this.listEvents();\n if (currentEvents.length === 0) return;\n\n this.logger.debug({ count: currentEvents.length }, \"Running heartbeat cycle\");\n\n for (const event of currentEvents) {\n try {\n this.logger.debug({ event: event.name }, `Executing heartbeat: ${event.name}`);\n\n // Load dedup state and inject into prompt if placeholder present\n const useDedup = event.prompt.includes(DEDUP_PLACEHOLDER);\n const state = useDedup ? this.loadState(event.name) : null;\n const finalPrompt = state\n ? this.injectDedupContext(event.prompt, state)\n : event.prompt;\n\n const response = await sendFn(finalPrompt);\n\n if (response) {\n // Extract and persist newly processed IDs if dedup is active\n if (useDedup && state) {\n const newIds = this.extractProcessedIds(response);\n if (newIds.length > 0) {\n // Deduplicate against existing IDs before persisting\n const existing = new Set(state.processedIds);\n const uniqueNew = newIds.filter((id) => !existing.has(id));\n if (uniqueNew.length > 0) {\n state.processedIds.push(...uniqueNew);\n state.lastRun = new Date().toISOString();\n this.saveState(event.name, state);\n this.logger.info(\n { event: event.name, newIds: uniqueNew.length, totalIds: state.processedIds.length },\n `Dedup: recorded ${uniqueNew.length} new IDs for \"${event.name}\"`,\n );\n }\n }\n }\n\n // Strip the PROCESSED marker from the message sent to the user\n const cleanResponse = response.replace(PROCESSED_MARKER_RE, \"\").trim();\n PROCESSED_MARKER_RE.lastIndex = 0;\n\n if (cleanResponse) {\n await notifyFn(event.name, cleanResponse);\n }\n this.logger.info({ event: event.name }, `Heartbeat \"${event.name}\" completed`);\n } else {\n this.logger.warn({ event: event.name }, `Heartbeat \"${event.name}\" returned no response`);\n }\n } catch (err) {\n this.logger.error(\n { err, event: event.name },\n `Heartbeat \"${event.name}\" failed`,\n );\n }\n }\n } finally {\n this.cycleInProgress = false;\n }\n };\n\n // Schedule recurring execution (first run after one interval)\n this.timer = setInterval(() => {\n runCycle().catch((err) => {\n this.logger.error({ err }, \"Heartbeat cycle failed\");\n });\n }, intervalMs);\n }\n\n /**\n * Stop the heartbeat scheduler.\n */\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.isRunning = false;\n this.cycleInProgress = false;\n this.logger.info(\"Heartbeat scheduler stopped\");\n }\n\n /**\n * Check whether the scheduler is currently running.\n */\n isActive(): boolean {\n return this.isRunning;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Ensure the heartbeats directory exists. */\nfunction ensureHeartbeatsDir(): void {\n if (!existsSync(HEARTBEATS_DIR)) {\n mkdirSync(HEARTBEATS_DIR, { recursive: true });\n }\n}\n","/**\n * @module core/gc\n * @description Periodic garbage collection and memory management.\n *\n * The {@link GarbageCollector} runs on a configurable interval and performs:\n * - **Conversation pruning** — deletes messages older than a retention window.\n * - **Plugin health pruning** — deletes health-check records past retention.\n * - **Memory stats logging** — logs heap and RSS usage for monitoring.\n *\n * Wired into the app lifecycle via {@link App.start} and {@link App.shutdown}.\n */\n\nimport type { Logger } from \"pino\";\nimport type Database from \"better-sqlite3\";\nimport { createChildLogger } from \"./logger.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Default GC cycle interval in minutes. */\nconst DEFAULT_INTERVAL_MINUTES = 30;\n\n/** Default conversation retention in days. */\nconst DEFAULT_CONVERSATION_RETENTION_DAYS = 30;\n\n/** Default plugin health record retention in days. */\nconst DEFAULT_HEALTH_RETENTION_DAYS = 7;\n\n// ---------------------------------------------------------------------------\n// GarbageCollector\n// ---------------------------------------------------------------------------\n\n/** Configuration options for the garbage collector. */\nexport interface GCOptions {\n /** How often the GC runs, in minutes. 0 = disabled. */\n intervalMinutes?: number;\n /** How many days of conversation history to retain. */\n conversationRetentionDays?: number;\n /** How many days of plugin health records to retain. */\n healthRetentionDays?: number;\n}\n\n/**\n * Periodic garbage collector that prunes old database records and monitors\n * memory usage. Designed to prevent unbounded growth of SQLite tables and\n * surface memory pressure early.\n */\nexport class GarbageCollector {\n private logger: Logger;\n private timer: ReturnType<typeof setInterval> | null = null;\n private db: Database.Database | null = null;\n\n private intervalMinutes: number;\n private conversationRetentionDays: number;\n private healthRetentionDays: number;\n\n constructor(options?: GCOptions) {\n this.logger = createChildLogger(\"gc\");\n this.intervalMinutes = options?.intervalMinutes ?? DEFAULT_INTERVAL_MINUTES;\n this.conversationRetentionDays = options?.conversationRetentionDays ?? DEFAULT_CONVERSATION_RETENTION_DAYS;\n this.healthRetentionDays = options?.healthRetentionDays ?? DEFAULT_HEALTH_RETENTION_DAYS;\n }\n\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n\n /**\n * Start the periodic GC timer.\n *\n * @param db - The SQLite database handle to prune.\n */\n start(db: Database.Database): void {\n this.db = db;\n\n if (this.intervalMinutes <= 0) {\n this.logger.info(\"Garbage collector disabled (interval = 0)\");\n return;\n }\n\n // Run once immediately at startup to clean up any stale data\n this.runCycle();\n\n const intervalMs = this.intervalMinutes * 60_000;\n this.timer = setInterval(() => this.runCycle(), intervalMs);\n\n // Allow the process to exit even if the GC timer is still active\n if (this.timer.unref) this.timer.unref();\n\n this.logger.info(\n {\n intervalMinutes: this.intervalMinutes,\n conversationRetentionDays: this.conversationRetentionDays,\n healthRetentionDays: this.healthRetentionDays,\n },\n `GC started (every ${this.intervalMinutes} min)`,\n );\n }\n\n /**\n * Stop the periodic GC timer.\n */\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n this.logger.info(\"GC stopped\");\n }\n }\n\n // -----------------------------------------------------------------------\n // GC cycle\n // -----------------------------------------------------------------------\n\n /**\n * Execute a single GC cycle: prune old records and log memory stats.\n */\n runCycle(): void {\n const start = Date.now();\n\n try {\n const conversationsPruned = this.pruneConversations();\n const healthPruned = this.prunePluginHealth();\n const memStats = this.getMemoryStats();\n\n const elapsed = Date.now() - start;\n\n this.logger.info(\n {\n conversationsPruned,\n healthPruned,\n heapUsedMB: memStats.heapUsedMB,\n rssMB: memStats.rssMB,\n elapsed,\n },\n `GC cycle complete — pruned ${conversationsPruned} conversations, ${healthPruned} health records (${elapsed}ms)`,\n );\n } catch (err) {\n this.logger.error({ err }, \"GC cycle failed\");\n }\n }\n\n // -----------------------------------------------------------------------\n // Pruning operations\n // -----------------------------------------------------------------------\n\n /**\n * Delete conversation messages older than the retention window.\n * @returns Number of rows deleted.\n */\n private pruneConversations(): number {\n if (!this.db) return 0;\n\n try {\n const result = this.db.prepare(\n `DELETE FROM conversations\n WHERE created_at < datetime('now', '-' || ? || ' days')`,\n ).run(this.conversationRetentionDays);\n\n return result.changes;\n } catch (err) {\n this.logger.error({ err }, \"Failed to prune conversations\");\n return 0;\n }\n }\n\n /**\n * Delete plugin health records older than the retention window.\n * @returns Number of rows deleted.\n */\n private prunePluginHealth(): number {\n if (!this.db) return 0;\n\n try {\n const result = this.db.prepare(\n `DELETE FROM plugin_health\n WHERE checked_at < datetime('now', '-' || ? || ' days')`,\n ).run(this.healthRetentionDays);\n\n return result.changes;\n } catch (err) {\n this.logger.error({ err }, \"Failed to prune plugin health records\");\n return 0;\n }\n }\n\n // -----------------------------------------------------------------------\n // Memory monitoring\n // -----------------------------------------------------------------------\n\n /**\n * Capture current process memory usage.\n * @returns Object with heap and RSS in megabytes.\n */\n getMemoryStats(): { heapUsedMB: number; heapTotalMB: number; rssMB: number; externalMB: number } {\n const mem = process.memoryUsage();\n return {\n heapUsedMB: Math.round(mem.heapUsed / 1024 / 1024 * 10) / 10,\n heapTotalMB: Math.round(mem.heapTotal / 1024 / 1024 * 10) / 10,\n rssMB: Math.round(mem.rss / 1024 / 1024 * 10) / 10,\n externalMB: Math.round(mem.external / 1024 / 1024 * 10) / 10,\n };\n }\n}\n"],"mappings":"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","names":["path","message","migration","message","message","message","logger","readFileSync","existsSync","logger","existsSync","readFileSync","path","existsSync","mkdirSync","readFileSync","writeFileSync","z","existsSync","mkdirSync","path","readFileSync","writeFileSync","path","existsSync","path","existsSync","getConfig","PluginHealthRepository","logger","logger","logger","sessionManager","readdirSync","readFileSync","writeFileSync","existsSync","mkdirSync","readdirSync","readFileSync","existsSync","writeFileSync","mkdirSync"]}