diff --git a/.env.example b/.env.example index adfb15ab..87e9ad21 100644 --- a/.env.example +++ b/.env.example @@ -30,5 +30,9 @@ DATABASE_URL=postgresql://user:password@host:5432/database # Optional: force SSL for DB connections if needed by your host # DATABASE_SSL=true +# mem0 API key for user memory (optional — memory features disabled without key) +# Get your API key from https://app.mem0.ai — uses the hosted platform with graph memory +MEM0_API_KEY=your_mem0_api_key + # Logging level (optional: debug, info, warn, error) LOG_LEVEL=info diff --git a/config.json b/config.json index cc8b32d2..fd22570d 100644 --- a/config.json +++ b/config.json @@ -63,6 +63,11 @@ } } }, + "memory": { + "enabled": true, + "maxContextMemories": 5, + "autoExtract": true + }, "logging": { "level": "info", "fileOutput": true @@ -73,6 +78,7 @@ "usePermissions": true, "allowedCommands": { "ping": "everyone", + "memory": "everyone", "config": "admin", "warn": "admin", "kick": "admin", diff --git a/package.json b/package.json index 53a63827..83ca08d9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "discord.js": "^14.25.1", "dotenv": "^17.2.4", + "mem0ai": "^2.2.2", "pg": "^8.18.0", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4bd24130..5cd80304 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: dotenv: specifier: ^17.2.4 version: 17.2.4 + mem0ai: + specifier: ^2.2.2 + version: 2.2.2(@anthropic-ai/sdk@0.40.1(encoding@0.1.13))(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260214.0)(@google/genai@1.41.0)(@langchain/core@0.3.80(openai@4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.14.0)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.95.3)(@types/jest@29.5.14)(@types/pg@8.11.0)(@types/sqlite3@3.1.11)(cloudflare@4.5.0(encoding@0.1.13))(encoding@0.1.13)(groq-sdk@0.3.0(encoding@0.1.13))(neo4j-driver@5.28.3)(ollama@0.5.18)(pg@8.18.0)(redis@4.7.1)(sqlite3@5.1.7)(ws@8.19.0) pg: specifier: ^8.18.0 version: 8.18.0 @@ -39,6 +42,72 @@ importers: packages: + '@anthropic-ai/sdk@0.40.1': + resolution: {integrity: sha512-DJMWm8lTEM9Lk/MSFL+V+ugF7jKOn0M2Ujvb5fN8r2nY14aHbGPZ1k6sgjL+tpJ3VuOGJNG+4R83jEpOuYPv8w==} + + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.10.1': + resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==} + engines: {node: '>=20.0.0'} + + '@azure/core-client@1.10.1': + resolution: {integrity: sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==} + engines: {node: '>=20.0.0'} + + '@azure/core-http-compat@2.3.2': + resolution: {integrity: sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@azure/core-client': ^1.10.0 + '@azure/core-rest-pipeline': ^1.22.0 + + '@azure/core-paging@1.6.2': + resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.22.2': + resolution: {integrity: sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==} + engines: {node: '>=20.0.0'} + + '@azure/core-tracing@1.3.1': + resolution: {integrity: sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==} + engines: {node: '>=20.0.0'} + + '@azure/core-util@1.13.1': + resolution: {integrity: sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==} + engines: {node: '>=20.0.0'} + + '@azure/identity@4.13.0': + resolution: {integrity: sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw==} + engines: {node: '>=20.0.0'} + + '@azure/logger@1.3.0': + resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==} + engines: {node: '>=20.0.0'} + + '@azure/msal-browser@4.28.2': + resolution: {integrity: sha512-6vYUMvs6kJxJgxaCmHn/F8VxjLHNh7i9wzfwPGf8kyBJ8Gg2yvBXx175Uev8LdrD1F5C4o7qHa2CC4IrhGE1XQ==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@15.14.2': + resolution: {integrity: sha512-n8RBJEUmd5QotoqbZfd+eGBkzuFI1KX6jw2b3WcpSyGjwmzoeI/Jb99opIBPHpb8y312NB+B6+FGi2ZVSR8yfA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@3.8.7': + resolution: {integrity: sha512-a+Xnrae+uwLnlw68bplS1X4kuJ9F/7K6afuMFyRkNIskhjgDezl5Fhrx+1pmAlDmC0VaaAxjRQMp1OmcqVwkIg==} + engines: {node: '>=16'} + + '@azure/search-documents@12.2.0': + resolution: {integrity: sha512-4+Qw+qaGqnkdUCq/vEFzk/bkROogTvdbPb1fmI8poxNfDDN1q2WHxBmhI7CYwesrBj1yXC4i5E0aISBxZqZi0g==} + engines: {node: '>=20.0.0'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -117,6 +186,12 @@ packages: cpu: [x64] os: [win32] + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + + '@cloudflare/workers-types@4.20260214.0': + resolution: {integrity: sha512-qb8rgbAdJR4BAPXolXhFL/wuGtecHLh1veOyZ1mK6QqWuCdI3vK1biKC0i3lzmzdLR/DZvsN3mNtpUE8zpWGEg==} + '@colors/colors@1.6.0': resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} @@ -308,6 +383,34 @@ packages: cpu: [x64] os: [win32] + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + + '@google/genai@1.41.0': + resolution: {integrity: sha512-S4WGil+PG0NBQRAx+0yrQuM/TWOLn2gGEy5wn4IsoOI6ouHad0P61p3OWdhJ3aqr9kfj8o904i/jevfaGoGuIQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -318,6 +421,94 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@langchain/core@0.3.80': + resolution: {integrity: sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA==} + engines: {node: '>=18'} + + '@mistralai/mistralai@1.14.0': + resolution: {integrity: sha512-6zaj2f2LCd37cRpBvCgctkDbXtYBlAC85p+u4uU/726zjtsI+sdVH34qRzkm9iE3tRb8BoaiI0/P7TD+uMvLLQ==} + + '@npmcli/fs@1.1.1': + resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} + + '@npmcli/move-file@1.1.2': + resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} + engines: {node: '>=10'} + deprecated: This functionality has been moved to @npmcli/fs + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@qdrant/js-client-rest@1.13.0': + resolution: {integrity: sha512-bewMtnXlGvhhnfXsp0sLoLXOGvnrCM15z9lNlG0Snp021OedNAnRtKkerjk5vkOcbQWUmJHXYCuxDfcT93aSkA==} + engines: {node: '>=18.0.0', pnpm: '>=8'} + peerDependencies: + typescript: '>=4.7' + + '@qdrant/openapi-typescript-fetch@1.2.6': + resolution: {integrity: sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==} + engines: {node: '>=18.0.0', pnpm: '>=8'} + + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.6.1': + resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==} + engines: {node: '>=14'} + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.7': + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.2.0': + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.1.0': + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + '@rollup/rollup-android-arm-eabi@4.57.1': resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} cpu: [arm] @@ -468,12 +659,46 @@ packages: resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@sevinf/maybe@0.5.0': + resolution: {integrity: sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg==} + + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@so-ric/colorspace@1.1.6': resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@supabase/auth-js@2.95.3': + resolution: {integrity: sha512-vD2YoS8E2iKIX0F7EwXTmqhUpaNsmbU6X2R0/NdFcs02oEfnHyNP/3M716f3wVJ2E5XHGiTFXki6lRckhJ0Thg==} + engines: {node: '>=20.0.0'} + + '@supabase/functions-js@2.95.3': + resolution: {integrity: sha512-uTuOAKzs9R/IovW1krO0ZbUHSJnsnyJElTXIRhjJTqymIVGcHzkAYnBCJqd7468Fs/Foz1BQ7Dv6DCl05lr7ig==} + engines: {node: '>=20.0.0'} + + '@supabase/postgrest-js@2.95.3': + resolution: {integrity: sha512-LTrRBqU1gOovxRm1vRXPItSMPBmEFqrfTqdPTRtzOILV4jPSueFz6pES5hpb4LRlkFwCPRmv3nQJ5N625V2Xrg==} + engines: {node: '>=20.0.0'} + + '@supabase/realtime-js@2.95.3': + resolution: {integrity: sha512-D7EAtfU3w6BEUxDACjowWNJo/ZRo7sDIuhuOGKHIm9FHieGeoJV5R6GKTLtga/5l/6fDr2u+WcW/m8I9SYmaIw==} + engines: {node: '>=20.0.0'} + + '@supabase/storage-js@2.95.3': + resolution: {integrity: sha512-4GxkJiXI3HHWjxpC3sDx1BVrV87O0hfX+wvJdqGv67KeCu+g44SPnII8y0LL/Wr677jB7tpjAxKdtVWf+xhc9A==} + engines: {node: '>=20.0.0'} + + '@supabase/supabase-js@2.95.3': + resolution: {integrity: sha512-Fukw1cUTQ6xdLiHDJhKKPu6svEPaCEDvThqCne3OaQyZvuq2qjhJAd91kJu3PXLG18aooCgYBaB6qQz35hhABg==} + engines: {node: '>=20.0.0'} + + '@tootallnate/once@1.1.2': + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -483,15 +708,61 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@25.2.0': resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==} + '@types/pg@8.11.0': + resolution: {integrity: sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==} + + '@types/phoenix@1.6.7': + resolution: {integrity: sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==} + + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@types/sqlite3@3.1.11': + resolution: {integrity: sha512-KYF+QgxAnnAh7DWPdNDroxkDI3/MspH1NMx6m/N/6fT1G6+jvsw4/ZePt8R8cr7ta58aboeTfYFBDxTJ5yv15w==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@typespec/ts-http-runtime@0.3.3': + resolution: {integrity: sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==} + engines: {node: '>=20.0.0'} + '@vitest/coverage-v8@4.0.18': resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==} peerDependencies: @@ -534,6 +805,57 @@ packages: resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -544,14 +866,109 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base-64@0.1.0: + resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + cacache@15.3.0: + resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} + engines: {node: '>= 10'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cloudflare@4.5.0: + resolution: {integrity: sha512-fPcbPKx4zF45jBvQ0z7PCdgejVAPBBCZxwqk1k7krQNfpM07Cfj97/Q6wBzvYqlWXx/zt1S9+m8vnfCe06umbQ==} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + color-convert@3.1.3: resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} engines: {node: '>=14.6'} + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@2.1.0: resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} engines: {node: '>=12.20'} @@ -560,10 +977,89 @@ packages: resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} engines: {node: '>=18'} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + color@5.0.3: resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} engines: {node: '>=18'} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + console-table-printer@2.15.0: + resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + digest-fetch@1.3.0: + resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==} + discord-api-types@0.38.38: resolution: {integrity: sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q==} @@ -575,24 +1071,95 @@ packages: resolution: {integrity: sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.27.3: resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} hasBin: true + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -608,93 +1175,628 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-stream-rotator@0.6.1: resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + gaxios@7.1.3: + resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} + engines: {node: '>=18'} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} - js-tokens@10.0.0: - resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} - kuler@2.0.0: - resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - lodash.snakecase@4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - logform@2.7.0: - resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} - engines: {node: '>= 12.0.0'} + google-auth-library@10.5.0: + resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} + engines: {node: '>=18'} - magic-bytes.js@1.13.0: - resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} + groq-sdk@0.3.0: + resolution: {integrity: sha512-Cdgjh4YoSBE2X4S9sxPGXaAy1dlN4bRtAaDZ3cnq+XsxhhN9WSBeHF64l7LWwuD5ntmw7YC5Vf4Ff1oHCg1LOg==} - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + gtoken@8.0.0: + resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iceberg-js@0.8.1: + resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} + engines: {node: '>=20.0.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + + is-network-error@1.3.0: + resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + engines: {node: '>=16'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + js-tiktoken@1.0.21: + resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + langsmith@0.3.87: + resolution: {integrity: sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q==} + peerDependencies: + '@opentelemetry/api': '*' + '@opentelemetry/exporter-trace-otlp-proto': '*' + '@opentelemetry/sdk-trace-base': '*' + openai: '*' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/exporter-trace-otlp-proto': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + openai: + optional: true + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-bytes.js@1.13.0: + resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-fetch-happen@9.1.0: + resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} + engines: {node: '>= 10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + + mem0ai@2.2.2: + resolution: {integrity: sha512-gMvz80j+/UmpVaH73e2ohn6eVIuxq/56GYcI0li7N6JlVC2yf36SBNaCjsOzkMKFRgv2iOgLMEQWlxq2KokB8Q==} + engines: {node: '>=18'} + peerDependencies: + '@anthropic-ai/sdk': ^0.40.1 + '@azure/identity': ^4.0.0 + '@azure/search-documents': ^12.0.0 + '@cloudflare/workers-types': ^4.20250504.0 + '@google/genai': ^1.2.0 + '@langchain/core': ^0.3.44 + '@mistralai/mistralai': ^1.5.2 + '@qdrant/js-client-rest': 1.13.0 + '@supabase/supabase-js': ^2.49.1 + '@types/jest': 29.5.14 + '@types/pg': 8.11.0 + '@types/sqlite3': 3.1.11 + cloudflare: ^4.2.0 + groq-sdk: 0.3.0 + neo4j-driver: ^5.28.1 + ollama: ^0.5.14 + pg: 8.11.3 + redis: ^4.6.13 + sqlite3: 5.1.7 + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@1.4.1: + resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} + engines: {node: '>=8'} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + neo4j-driver-bolt-connection@5.28.3: + resolution: {integrity: sha512-wqHBYcU0FVRDmdsoZ+Fk0S/InYmu9/4BT6fPYh45Jimg/J7vQBUcdkiHGU7nop7HRb1ZgJmL305mJb6g5Bv35Q==} + + neo4j-driver-core@5.28.3: + resolution: {integrity: sha512-Jk+hAmjFmO5YzVH/U7FyKXigot9zmIfLz6SZQy0xfr4zfTE/S8fOYFOGqKQTHBE86HHOWH2RbTslbxIb+XtU2g==} + + neo4j-driver@5.28.3: + resolution: {integrity: sha512-k7c0wEh3HoONv1v5AyLp9/BDAbYHJhz2TZvzWstSEU3g3suQcXmKEaYBfrK2UMzxcy3bCT0DrnfRbzsOW5G/Ag==} + + node-abi@3.87.0: + resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} + engines: {node: '>=10'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-gyp@8.4.1: + resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} + engines: {node: '>= 10.12.0'} + hasBin: true + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ollama@0.5.18: + resolution: {integrity: sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + + openai@4.104.0: + resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + p-retry@7.1.1: + resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} + engines: {node: '>=20'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -708,6 +1810,10 @@ packages: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + pg-pool@3.11.0: resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==} peerDependencies: @@ -720,6 +1826,10 @@ packages: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} + pg-types@4.1.0: + resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==} + engines: {node: '>=10'} + pg@8.18.0: resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} engines: {node: '>= 16.0.0'} @@ -735,6 +1845,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -747,27 +1861,111 @@ packages: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} + postgres-array@3.0.4: + resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==} + engines: {node: '>=12'} + postgres-bytea@1.0.1: resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} engines: {node: '>=0.10.0'} + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + redis@4.7.1: + resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + rollup@4.57.1: resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -775,14 +1973,60 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-wcswidth@1.1.2: + resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@6.2.1: + resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} + engines: {node: '>= 10'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -791,22 +2035,65 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sqlite3@5.1.7: + resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} + + ssri@8.0.1: + resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} + engines: {node: '>= 8'} + stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -825,6 +2112,13 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} @@ -835,6 +2129,17 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -842,9 +2147,27 @@ packages: resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} + unique-filename@1.1.1: + resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} + + unique-slug@2.0.2: + resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -919,42 +2242,222 @@ packages: jsdom: optional: true + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + winston-daily-rotate-file@5.0.0: resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} engines: {node: '>=8'} peerDependencies: winston: ^3 - winston-transport@4.9.0: - resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} - engines: {node: '>= 12.0.0'} + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} + engines: {node: '>= 12.0.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@anthropic-ai/sdk@0.40.1(encoding@0.1.13)': + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-auth@1.10.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-client@1.10.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-http-compat@2.3.2(@azure/core-client@1.10.1)(@azure/core-rest-pipeline@1.22.2)': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-client': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + + '@azure/core-paging@1.6.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-rest-pipeline@1.22.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + '@typespec/ts-http-runtime': 0.3.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-tracing@1.3.1': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.13.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@typespec/ts-http-runtime': 0.3.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/identity@4.13.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.10.1 + '@azure/core-client': 1.10.1 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + '@azure/msal-browser': 4.28.2 + '@azure/msal-node': 3.8.7 + open: 10.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/logger@1.3.0': + dependencies: + '@typespec/ts-http-runtime': 0.3.3 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color - winston@3.19.0: - resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} - engines: {node: '>= 12.0.0'} + '@azure/msal-browser@4.28.2': + dependencies: + '@azure/msal-common': 15.14.2 - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true + '@azure/msal-common@15.14.2': {} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} + '@azure/msal-node@3.8.7': + dependencies: + '@azure/msal-common': 15.14.2 + jsonwebtoken: 9.0.3 + uuid: 8.3.2 -snapshots: + '@azure/search-documents@12.2.0': + dependencies: + '@azure/core-auth': 1.10.1 + '@azure/core-client': 1.10.1 + '@azure/core-http-compat': 2.3.2(@azure/core-client@1.10.1)(@azure/core-rest-pipeline@1.22.2) + '@azure/core-paging': 1.6.2 + '@azure/core-rest-pipeline': 1.22.2 + '@azure/core-tracing': 1.3.1 + '@azure/core-util': 1.13.1 + '@azure/logger': 1.3.0 + events: 3.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 '@babel/helper-string-parser@7.27.1': {} @@ -1006,6 +2509,10 @@ snapshots: '@biomejs/cli-win32-x64@2.3.14': optional: true + '@cfworker/json-schema@4.1.1': {} + + '@cloudflare/workers-types@4.20260214.0': {} + '@colors/colors@1.6.0': {} '@dabh/diagnostics@2.0.8': @@ -1141,6 +2648,46 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true + '@gar/promisify@1.1.3': + optional: true + + '@google/genai@1.41.0': + dependencies: + google-auth-library: 10.5.0 + p-retry: 7.1.1 + protobufjs: 7.5.4 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.2.0 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} @@ -1150,6 +2697,108 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@langchain/core@0.3.80(openai@4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76))': + dependencies: + '@cfworker/json-schema': 4.1.1 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.21 + langsmith: 0.3.87(openai@4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76)) + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + transitivePeerDependencies: + - '@opentelemetry/api' + - '@opentelemetry/exporter-trace-otlp-proto' + - '@opentelemetry/sdk-trace-base' + - openai + + '@mistralai/mistralai@1.14.0': + dependencies: + ws: 8.19.0 + zod: 4.3.6 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@npmcli/fs@1.1.1': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.7.4 + optional: true + + '@npmcli/move-file@1.1.2': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@qdrant/js-client-rest@1.13.0(typescript@5.9.3)': + dependencies: + '@qdrant/openapi-typescript-fetch': 1.2.6 + '@sevinf/maybe': 0.5.0 + typescript: 5.9.3 + undici: 6.23.0 + + '@qdrant/openapi-typescript-fetch@1.2.6': {} + + '@redis/bloom@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/client@1.6.1': + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + + '@redis/graph@1.1.1(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/json@1.0.7(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/search@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/time-series@1.1.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + '@rollup/rollup-android-arm-eabi@4.57.1': optional: true @@ -1234,6 +2883,10 @@ snapshots: '@sapphire/snowflake@3.5.3': {} + '@sevinf/maybe@0.5.0': {} + + '@sinclair/typebox@0.27.10': {} + '@so-ric/colorspace@1.1.6': dependencies: color: 5.0.3 @@ -1241,6 +2894,47 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@supabase/auth-js@2.95.3': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.95.3': + dependencies: + tslib: 2.8.1 + + '@supabase/postgrest-js@2.95.3': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.95.3': + dependencies: + '@types/phoenix': 1.6.7 + '@types/ws': 8.18.1 + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.95.3': + dependencies: + iceberg-js: 0.8.1 + tslib: 2.8.1 + + '@supabase/supabase-js@2.95.3': + dependencies: + '@supabase/auth-js': 2.95.3 + '@supabase/functions-js': 2.95.3 + '@supabase/postgrest-js': 2.95.3 + '@supabase/realtime-js': 2.95.3 + '@supabase/storage-js': 2.95.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@tootallnate/once@1.1.2': + optional: true + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1250,16 +2944,72 @@ snapshots: '@types/estree@1.0.8': {} + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 25.2.0 + form-data: 4.0.5 + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@25.2.0': dependencies: undici-types: 7.16.0 + '@types/pg@8.11.0': + dependencies: + '@types/node': 25.2.0 + pg-protocol: 1.11.0 + pg-types: 4.1.0 + + '@types/phoenix@1.6.7': {} + + '@types/retry@0.12.0': {} + + '@types/sqlite3@3.1.11': + dependencies: + '@types/node': 25.2.0 + + '@types/stack-utils@2.0.3': {} + '@types/triple-beam@1.3.5': {} + '@types/uuid@10.0.0': {} + '@types/ws@8.18.1': dependencies: '@types/node': 25.2.0 + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typespec/ts-http-runtime@0.3.3': + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.2.0))': dependencies: '@bcoe/v8-coverage': 1.0.2 @@ -1274,74 +3024,308 @@ snapshots: tinyrainbow: 3.0.3 vitest: 4.0.18(@types/node@25.2.0) - '@vitest/expect@4.0.18': + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.0))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.2.0) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + + '@vladfrangu/async_event_emitter@2.4.7': {} + + abbrev@1.1.1: + optional: true + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + agent-base@7.1.4: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + optional: true + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + aproba@2.1.0: + optional: true + + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + optional: true + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@0.3.11: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + async@3.2.6: {} + + asynckit@0.4.0: {} + + axios@1.7.7: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + base-64@0.1.0: {} + + base64-js@1.5.1: {} + + bignumber.js@9.3.1: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + optional: true + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-equal-constant-time@1.0.1: {} + + buffer@5.7.1: dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - chai: 6.2.2 - tinyrainbow: 3.0.3 + base64-js: 1.5.1 + ieee754: 1.2.1 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.0))': + buffer@6.0.3: dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.1(@types/node@25.2.0) + base64-js: 1.5.1 + ieee754: 1.2.1 - '@vitest/pretty-format@4.0.18': + bundle-name@4.1.0: dependencies: - tinyrainbow: 3.0.3 + run-applescript: 7.1.0 - '@vitest/runner@4.0.18': + cacache@15.3.0: dependencies: - '@vitest/utils': 4.0.18 - pathe: 2.0.3 + '@npmcli/fs': 1.1.1 + '@npmcli/move-file': 1.1.2 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 7.2.3 + infer-owner: 1.0.4 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 8.0.1 + tar: 6.2.1 + unique-filename: 1.1.1 + transitivePeerDependencies: + - bluebird + optional: true - '@vitest/snapshot@4.0.18': + call-bind-apply-helpers@1.0.2: dependencies: - '@vitest/pretty-format': 4.0.18 - magic-string: 0.30.21 - pathe: 2.0.3 + es-errors: 1.3.0 + function-bind: 1.1.2 - '@vitest/spy@4.0.18': {} + camelcase@6.3.0: {} - '@vitest/utils@4.0.18': + chai@6.2.2: {} + + chalk@4.1.2: dependencies: - '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.0.3 + ansi-styles: 4.3.0 + supports-color: 7.2.0 - '@vladfrangu/async_event_emitter@2.4.7': {} + charenc@0.0.2: {} - assertion-error@2.0.1: {} + chownr@1.1.4: {} - ast-v8-to-istanbul@0.3.11: + chownr@2.0.0: {} + + ci-info@3.9.0: {} + + clean-stack@2.2.0: + optional: true + + cloudflare@4.5.0(encoding@0.1.13): dependencies: - '@jridgewell/trace-mapping': 0.3.31 - estree-walker: 3.0.3 - js-tokens: 10.0.0 + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding - async@3.2.6: {} + cluster-key-slot@1.1.2: {} - chai@6.2.2: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 color-convert@3.1.3: dependencies: color-name: 2.1.0 + color-name@1.1.4: {} + color-name@2.1.0: {} color-string@2.1.4: dependencies: color-name: 2.1.0 + color-support@1.1.3: + optional: true + color@5.0.3: dependencies: color-convert: 3.1.3 color-string: 2.1.4 + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + concat-map@0.0.1: + optional: true + + console-control-strings@1.1.0: + optional: true + + console-table-printer@2.15.0: + dependencies: + simple-wcswidth: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypt@0.0.2: {} + + data-uri-to-buffer@4.0.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + delayed-stream@1.0.0: {} + + delegates@1.0.0: + optional: true + + detect-libc@2.1.2: {} + + diff-sequences@29.6.3: {} + + digest-fetch@1.3.0: + dependencies: + base-64: 0.1.0 + md5: 2.3.0 + discord-api-types@0.38.38: {} discord.js@14.25.1: @@ -1365,10 +3349,56 @@ snapshots: dotenv@17.2.4: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + enabled@2.0.0: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: + optional: true + + err-code@2.0.3: + optional: true + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.27.3: optionalDependencies: '@esbuild/aix-ppc64': 0.27.3 @@ -1398,12 +3428,32 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 + escape-string-regexp@2.0.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + event-target-shim@5.0.1: {} + + eventemitter3@4.0.7: {} + + events@3.3.0: {} + + expand-template@2.0.3: {} + expect-type@1.3.0: {} + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fdir@6.5.0(picomatch@4.0.3): @@ -1412,23 +3462,283 @@ snapshots: fecha@4.2.3: {} + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-stream-rotator@0.6.1: dependencies: moment: 2.30.1 + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + fn.name@1.1.0: {} + follow-redirects@1.15.11: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data-encoder@1.7.2: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + fs-constants@1.0.0: {} + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: + optional: true + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + + gauge@4.0.4: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + optional: true + + gaxios@7.1.3: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + rimraf: 5.0.10 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.3 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + + generic-pool@3.9.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + github-from-package@0.0.0: {} + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + optional: true + + google-auth-library@10.5.0: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.3 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + gtoken: 8.0.0 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + google-logging-utils@1.1.3: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + groq-sdk@0.3.0(encoding@0.1.13): + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + digest-fetch: 1.3.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0(encoding@0.1.13) + web-streams-polyfill: 3.3.3 + transitivePeerDependencies: + - encoding + + gtoken@8.0.0: + dependencies: + gaxios: 7.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: + optional: true + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + html-escaper@2.0.2: {} + http-cache-semantics@4.2.0: + optional: true + + http-proxy-agent@4.0.1: + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iceberg-js@0.8.1: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + ieee754@1.2.1: {} + + imurmurhash@0.1.4: + optional: true + + indent-string@4.0.0: + optional: true + + infer-owner@1.0.4: + optional: true + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + optional: true + inherits@2.0.4: {} + ini@1.3.8: {} + + ip-address@10.1.0: + optional: true + + is-buffer@1.1.6: {} + + is-docker@3.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-lambda@1.0.1: + optional: true + + is-network-error@1.3.0: {} + + is-number@7.0.0: {} + is-stream@2.0.1: {} + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -1437,14 +3747,116 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-reports@3.2.0: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.2.0 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + js-tiktoken@1.0.21: + dependencies: + base64-js: 1.5.1 + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.4 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + kuler@2.0.0: {} + + langsmith@0.3.87(openai@4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76)): + dependencies: + '@types/uuid': 10.0.0 + chalk: 4.1.2 + console-table-printer: 2.15.0 + p-queue: 6.6.2 + semver: 7.7.4 + uuid: 10.0.0 + optionalDependencies: + openai: 4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76) + + lodash.includes@4.3.0: {} - js-tokens@10.0.0: {} + lodash.isboolean@3.0.3: {} - kuler@2.0.0: {} + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} lodash.snakecase@4.1.1: {} @@ -1459,6 +3871,15 @@ snapshots: safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 + long@5.3.2: {} + + lru-cache@10.4.3: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + optional: true + magic-bytes.js@1.13.0: {} magic-string@0.30.21: @@ -1475,20 +3896,291 @@ snapshots: dependencies: semver: 7.7.4 + make-fetch-happen@9.1.0: + dependencies: + agentkeepalive: 4.6.0 + cacache: 15.3.0 + http-cache-semantics: 4.2.0 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 1.4.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.4 + promise-retry: 2.0.1 + socks-proxy-agent: 6.2.1 + ssri: 8.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true + + math-intrinsics@1.1.0: {} + + md5@2.3.0: + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + + mem0ai@2.2.2(@anthropic-ai/sdk@0.40.1(encoding@0.1.13))(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260214.0)(@google/genai@1.41.0)(@langchain/core@0.3.80(openai@4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.14.0)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.95.3)(@types/jest@29.5.14)(@types/pg@8.11.0)(@types/sqlite3@3.1.11)(cloudflare@4.5.0(encoding@0.1.13))(encoding@0.1.13)(groq-sdk@0.3.0(encoding@0.1.13))(neo4j-driver@5.28.3)(ollama@0.5.18)(pg@8.18.0)(redis@4.7.1)(sqlite3@5.1.7)(ws@8.19.0): + dependencies: + '@anthropic-ai/sdk': 0.40.1(encoding@0.1.13) + '@azure/identity': 4.13.0 + '@azure/search-documents': 12.2.0 + '@cloudflare/workers-types': 4.20260214.0 + '@google/genai': 1.41.0 + '@langchain/core': 0.3.80(openai@4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76)) + '@mistralai/mistralai': 1.14.0 + '@qdrant/js-client-rest': 1.13.0(typescript@5.9.3) + '@supabase/supabase-js': 2.95.3 + '@types/jest': 29.5.14 + '@types/pg': 8.11.0 + '@types/sqlite3': 3.1.11 + axios: 1.7.7 + cloudflare: 4.5.0(encoding@0.1.13) + groq-sdk: 0.3.0(encoding@0.1.13) + neo4j-driver: 5.28.3 + ollama: 0.5.18 + openai: 4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76) + pg: 8.18.0 + redis: 4.7.1 + sqlite3: 5.1.7 + uuid: 9.0.1 + zod: 3.25.76 + transitivePeerDependencies: + - debug + - encoding + - ws + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-response@3.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + optional: true + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-fetch@1.4.1: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + optional: true + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + optional: true + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp-classic@0.5.3: {} + + mkdirp@1.0.4: {} + moment@2.30.1: {} ms@2.1.3: {} + mustache@4.2.0: {} + nanoid@3.3.11: {} + napi-build-utils@2.0.0: {} + + negotiator@0.6.4: + optional: true + + neo4j-driver-bolt-connection@5.28.3: + dependencies: + buffer: 6.0.3 + neo4j-driver-core: 5.28.3 + string_decoder: 1.3.0 + + neo4j-driver-core@5.28.3: {} + + neo4j-driver@5.28.3: + dependencies: + neo4j-driver-bolt-connection: 5.28.3 + neo4j-driver-core: 5.28.3 + rxjs: 7.8.2 + + node-abi@3.87.0: + dependencies: + semver: 7.7.4 + + node-addon-api@7.1.1: {} + + node-domexception@1.0.0: {} + + node-fetch@2.7.0(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-gyp@8.4.1: + dependencies: + env-paths: 2.2.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 9.1.0 + nopt: 5.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.7.4 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + optional: true + + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + optional: true + object-hash@3.0.0: {} + obuf@1.1.2: {} + obug@2.1.1: {} + ollama@0.5.18: + dependencies: + whatwg-fetch: 3.6.20 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + one-time@1.0.0: dependencies: fn.name: 1.1.0 + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + openai@4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76): + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0(encoding@0.1.13) + optionalDependencies: + ws: 8.19.0 + zod: 3.25.76 + transitivePeerDependencies: + - encoding + + p-finally@1.0.0: {} + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + optional: true + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + p-retry@7.1.1: + dependencies: + is-network-error: 1.3.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + package-json-from-dist@1.0.1: {} + + path-is-absolute@1.0.1: + optional: true + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + pathe@2.0.3: {} pg-cloudflare@1.3.0: @@ -1498,6 +4190,8 @@ snapshots: pg-int8@1.0.1: {} + pg-numeric@1.0.2: {} + pg-pool@3.11.0(pg@8.18.0): dependencies: pg: 8.18.0 @@ -1512,6 +4206,16 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 + pg-types@4.1.0: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.4 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + pg@8.18.0: dependencies: pg-connection-string: 2.11.0 @@ -1528,6 +4232,8 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.3: {} postcss@8.5.6: @@ -1538,20 +4244,116 @@ snapshots: postgres-array@2.0.0: {} + postgres-array@3.0.4: {} + postgres-bytea@1.0.1: {} + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + postgres-date@1.0.7: {} + postgres-date@2.1.0: {} + postgres-interval@1.2.0: dependencies: xtend: 4.0.2 + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.87.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + promise-inflight@1.0.1: + optional: true + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + optional: true + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.2.0 + long: 5.3.2 + + proxy-from-env@1.1.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + react-is@18.3.1: {} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + redis@4.7.1: + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.1) + '@redis/client': 1.6.1 + '@redis/graph': 1.1.1(@redis/client@1.6.1) + '@redis/json': 1.0.7(@redis/client@1.6.1) + '@redis/search': 1.2.0(@redis/client@1.6.1) + '@redis/time-series': 1.1.0(@redis/client@1.6.1) + + retry@0.12.0: + optional: true + + retry@0.13.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + optional: true + + rimraf@5.0.10: + dependencies: + glob: 10.5.0 + rollup@4.57.1: dependencies: '@types/estree': 1.0.8 @@ -1583,32 +4385,152 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + run-applescript@7.1.0: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + safe-buffer@5.2.1: {} safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: + optional: true + semver@7.7.4: {} + set-blocking@2.0.0: + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@3.0.7: + optional: true + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-wcswidth@1.1.2: {} + + slash@3.0.0: {} + + smart-buffer@4.2.0: + optional: true + + socks-proxy-agent@6.2.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + optional: true + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + optional: true + source-map-js@1.2.1: {} split2@4.2.0: {} + sqlite3@5.1.7: + dependencies: + bindings: 1.5.0 + node-addon-api: 7.1.1 + prebuild-install: 7.1.3 + tar: 6.2.1 + optionalDependencies: + node-gyp: 8.4.1 + transitivePeerDependencies: + - bluebird + - supports-color + + ssri@8.0.1: + dependencies: + minipass: 3.3.6 + optional: true + stack-trace@0.0.10: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stackback@0.0.2: {} std-env@3.10.0: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@2.0.1: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + text-hex@1.0.0: {} tinybench@2.9.0: {} @@ -1622,18 +4544,48 @@ snapshots: tinyrainbow@3.0.3: {} + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + triple-beam@1.4.1: {} ts-mixer@6.0.4: {} tslib@2.8.1: {} + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + typescript@5.9.3: {} + + undici-types@5.26.5: {} + undici-types@7.16.0: {} undici@6.23.0: {} + unique-filename@1.1.1: + dependencies: + unique-slug: 2.0.2 + optional: true + + unique-slug@2.0.2: + dependencies: + imurmurhash: 0.1.4 + optional: true + util-deprecate@1.0.2: {} + uuid@10.0.0: {} + + uuid@8.3.2: {} + + uuid@9.0.1: {} + vite@7.3.1(@types/node@25.2.0): dependencies: esbuild: 0.27.3 @@ -1683,11 +4635,33 @@ snapshots: - tsx - yaml + web-streams-polyfill@3.3.3: {} + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-fetch@3.6.20: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + optional: true + winston-daily-rotate-file@5.0.0(winston@3.19.0): dependencies: file-stream-rotator: 0.6.1 @@ -1716,6 +4690,38 @@ snapshots: triple-beam: 1.4.1 winston-transport: 4.9.0 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + ws@8.19.0: {} + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + xtend@4.0.2: {} + + yallist@4.0.0: {} + + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-json-schema@3.25.1(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@3.25.76: {} + + zod@4.3.6: {} diff --git a/src/commands/memory.js b/src/commands/memory.js new file mode 100644 index 00000000..e3d1b5a2 --- /dev/null +++ b/src/commands/memory.js @@ -0,0 +1,428 @@ +/** + * Memory Command + * Allows users to view and manage what the bot remembers about them. + * + * Memories are stored externally on the mem0 platform (api.mem0.ai). + * Users can view their data with /memory view and delete it with + * /memory forget at any time. + * + * Subcommands: + * /memory view — Show all memories the bot has about you + * /memory forget — Clear all your memories (with confirmation) + * /memory forget — Clear memories matching a topic + * /memory optout — Toggle memory collection on/off + * /memory admin view @user — (Mod) View any user's memories + * /memory admin clear @user — (Mod) Clear any user's memories + */ + +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ComponentType, + PermissionFlagsBits, + SlashCommandBuilder, +} from 'discord.js'; +import { info, warn } from '../logger.js'; +import { + deleteAllMemories, + deleteMemory, + getMemories, + isMemoryAvailable, + searchMemories, +} from '../modules/memory.js'; +import { isOptedOut, toggleOptOut } from '../modules/optout.js'; +import { splitMessage } from '../utils/splitMessage.js'; + +/** + * Truncate a memory list to fit within Discord's 2000-char limit. + * @param {string} memoryList - Numbered memory lines joined by newlines + * @param {string} header - Header text prepended to the output + * @returns {string} Final message content, truncated if necessary + */ +function formatMemoryList(memoryList, header) { + const truncationNotice = '\n\n*(...and more)*'; + const maxBodyLength = 2000 - header.length - truncationNotice.length; + + const chunks = splitMessage(memoryList, maxBodyLength); + const isTruncated = chunks.length > 1; + return isTruncated ? `${header}${chunks[0]}${truncationNotice}` : `${header}${memoryList}`; +} + +export const data = new SlashCommandBuilder() + .setName('memory') + .setDescription('Manage what the bot remembers about you (stored externally)') + .addSubcommand((sub) => + sub.setName('view').setDescription('View what the bot remembers about you'), + ) + .addSubcommand((sub) => + sub + .setName('forget') + .setDescription('Forget your memories (all or by topic)') + .addStringOption((opt) => + opt + .setName('topic') + .setDescription('Specific topic to forget (omit to forget everything)') + .setRequired(false), + ), + ) + .addSubcommand((sub) => + sub.setName('optout').setDescription('Toggle memory collection on/off for your account'), + ) + .addSubcommandGroup((group) => + group + .setName('admin') + .setDescription('Admin memory management commands') + .addSubcommand((sub) => + sub + .setName('view') + .setDescription("View a user's memories") + .addUserOption((opt) => + opt.setName('user').setDescription('The user to view memories for').setRequired(true), + ), + ) + .addSubcommand((sub) => + sub + .setName('clear') + .setDescription("Clear a user's memories") + .addUserOption((opt) => + opt.setName('user').setDescription('The user to clear memories for').setRequired(true), + ), + ), + ); + +/** + * Execute the /memory command + * @param {import('discord.js').ChatInputCommandInteraction} interaction + */ +export async function execute(interaction) { + const subcommandGroup = interaction.options.getSubcommandGroup(false); + const subcommand = interaction.options.getSubcommand(); + const userId = interaction.user.id; + const username = interaction.user.username; + + // Handle admin subcommand group + if (subcommandGroup === 'admin') { + await handleAdmin(interaction, subcommand); + return; + } + + // Handle opt-out (doesn't require memory to be available) + if (subcommand === 'optout') { + await handleOptOut(interaction, userId); + return; + } + + if (!isMemoryAvailable()) { + await interaction.reply({ + content: + '🧠 Memory system is currently unavailable. The bot still works, just without long-term memory.', + ephemeral: true, + }); + return; + } + + if (subcommand === 'view') { + await handleView(interaction, userId, username); + } else if (subcommand === 'forget') { + const topic = interaction.options.getString('topic'); + if (topic) { + await handleForgetTopic(interaction, userId, username, topic); + } else { + await handleForgetAll(interaction, userId, username); + } + } +} + +/** + * Handle /memory optout — toggle memory collection for the user + * @param {import('discord.js').ChatInputCommandInteraction} interaction + * @param {string} userId + */ +async function handleOptOut(interaction, userId) { + const { optedOut } = await toggleOptOut(userId); + + if (optedOut) { + await interaction.reply({ + content: + '🚫 You have **opted out** of memory collection. The bot will no longer remember things about you. Your existing memories are unchanged — use `/memory forget` to delete them.', + ephemeral: true, + }); + } else { + await interaction.reply({ + content: + '✅ You have **opted back in** to memory collection. The bot will start remembering things about you again.', + ephemeral: true, + }); + } + + info('Memory opt-out toggled', { userId, optedOut }); +} + +/** + * Handle /memory view — show all memories for the user + * @param {import('discord.js').ChatInputCommandInteraction} interaction + * @param {string} userId + * @param {string} username + */ +async function handleView(interaction, userId, username) { + await interaction.deferReply({ ephemeral: true }); + + const memories = await getMemories(userId); + + if (memories.length === 0) { + await interaction.editReply({ + content: + "🧠 I don't have any memories about you yet. Chat with me and I'll start remembering!", + }); + return; + } + + const memoryList = memories.map((m, i) => `${i + 1}. ${m.memory}`).join('\n'); + const header = `🧠 **What I remember about ${username}:**\n\n`; + const content = formatMemoryList(memoryList, header); + + await interaction.editReply({ content }); + + info('Memory view command', { userId, username, count: memories.length }); +} + +/** + * Handle /memory forget (all) — delete all memories with confirmation + * @param {import('discord.js').ChatInputCommandInteraction} interaction + * @param {string} userId + * @param {string} username + */ +async function handleForgetAll(interaction, userId, username) { + const confirmButton = new ButtonBuilder() + .setCustomId('memory_forget_confirm') + .setLabel('Confirm') + .setStyle(ButtonStyle.Danger); + + const cancelButton = new ButtonBuilder() + .setCustomId('memory_forget_cancel') + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(confirmButton, cancelButton); + + const response = await interaction.reply({ + content: + '⚠️ **Are you sure?** This will delete **ALL** your memories permanently. This cannot be undone.', + components: [row], + ephemeral: true, + }); + + try { + const buttonInteraction = await response.awaitMessageComponent({ + componentType: ComponentType.Button, + filter: (i) => i.user.id === userId, + time: 30_000, + }); + + if (buttonInteraction.customId === 'memory_forget_confirm') { + const success = await deleteAllMemories(userId); + + if (success) { + await buttonInteraction.update({ + content: '🧹 Done! All your memories have been cleared. Fresh start!', + components: [], + }); + info('All memories cleared', { userId, username }); + } else { + await buttonInteraction.update({ + content: '❌ Failed to clear memories. Please try again later.', + components: [], + }); + warn('Failed to clear memories', { userId, username }); + } + } else { + await buttonInteraction.update({ + content: '↩️ Memory deletion cancelled.', + components: [], + }); + } + } catch { + // Timeout — no interaction received within 30 seconds + await interaction.editReply({ + content: '⏰ Confirmation timed out. No memories were deleted.', + components: [], + }); + } +} + +/** + * Handle /memory forget — delete memories matching a topic + * @param {import('discord.js').ChatInputCommandInteraction} interaction + * @param {string} userId + * @param {string} username + * @param {string} topic + */ +async function handleForgetTopic(interaction, userId, username, topic) { + await interaction.deferReply({ ephemeral: true }); + + // Search for memories matching the topic (results include IDs) + const { memories: matches } = await searchMemories(userId, topic, 10); + + if (matches.length === 0) { + await interaction.editReply({ + content: `🔍 No memories found matching "${topic}".`, + }); + return; + } + + // Use memory IDs directly from search results and delete in parallel + const matchesWithIds = matches.filter((m) => m.id); + const results = await Promise.allSettled(matchesWithIds.map((m) => deleteMemory(m.id))); + const deletedCount = results.filter((r) => r.status === 'fulfilled' && r.value === true).length; + + if (deletedCount > 0) { + await interaction.editReply({ + content: `🧹 Forgot ${deletedCount} memor${deletedCount === 1 ? 'y' : 'ies'} related to "${topic}".`, + }); + info('Topic memories cleared', { userId, username, topic, count: deletedCount }); + } else { + await interaction.editReply({ + content: `❌ Found memories about "${topic}" but couldn't delete them. Please try again.`, + }); + } +} + +/** + * Handle /memory admin commands + * @param {import('discord.js').ChatInputCommandInteraction} interaction + * @param {string} subcommand - 'view' or 'clear' + */ +async function handleAdmin(interaction, subcommand) { + // Permission check + const hasPermission = + interaction.memberPermissions && + (interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild) || + interaction.memberPermissions.has(PermissionFlagsBits.Administrator)); + + if (!hasPermission) { + await interaction.reply({ + content: + '❌ You need **Manage Server** or **Administrator** permission to use admin commands.', + ephemeral: true, + }); + return; + } + + if (!isMemoryAvailable()) { + await interaction.reply({ + content: + '🧠 Memory system is currently unavailable. The bot still works, just without long-term memory.', + ephemeral: true, + }); + return; + } + + const targetUser = interaction.options.getUser('user'); + const targetId = targetUser.id; + const targetUsername = targetUser.username; + + if (subcommand === 'view') { + await handleAdminView(interaction, targetId, targetUsername); + } else if (subcommand === 'clear') { + await handleAdminClear(interaction, targetId, targetUsername); + } +} + +/** + * Handle /memory admin view @user — view a user's memories (mod only) + * @param {import('discord.js').ChatInputCommandInteraction} interaction + * @param {string} targetId + * @param {string} targetUsername + */ +async function handleAdminView(interaction, targetId, targetUsername) { + await interaction.deferReply({ ephemeral: true }); + + const memories = await getMemories(targetId); + const optedOutStatus = isOptedOut(targetId) ? ' *(opted out)*' : ''; + + if (memories.length === 0) { + await interaction.editReply({ + content: `🧠 No memories found for **${targetUsername}**${optedOutStatus}.`, + }); + return; + } + + const memoryList = memories.map((m, i) => `${i + 1}. ${m.memory}`).join('\n'); + const header = `🧠 **Memories for ${targetUsername}${optedOutStatus}:**\n\n`; + const content = formatMemoryList(memoryList, header); + + await interaction.editReply({ content }); + + info('Admin memory view', { + adminId: interaction.user.id, + targetId, + targetUsername, + count: memories.length, + }); +} + +/** + * Handle /memory admin clear @user — clear a user's memories with confirmation (mod only) + * @param {import('discord.js').ChatInputCommandInteraction} interaction + * @param {string} targetId + * @param {string} targetUsername + */ +async function handleAdminClear(interaction, targetId, targetUsername) { + const adminId = interaction.user.id; + + const confirmButton = new ButtonBuilder() + .setCustomId('memory_admin_clear_confirm') + .setLabel('Confirm') + .setStyle(ButtonStyle.Danger); + + const cancelButton = new ButtonBuilder() + .setCustomId('memory_admin_clear_cancel') + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(confirmButton, cancelButton); + + const response = await interaction.reply({ + content: `⚠️ **Are you sure?** This will delete **ALL** memories for **${targetUsername}** permanently. This cannot be undone.`, + components: [row], + ephemeral: true, + }); + + try { + const buttonInteraction = await response.awaitMessageComponent({ + componentType: ComponentType.Button, + filter: (i) => i.user.id === adminId, + time: 30_000, + }); + + if (buttonInteraction.customId === 'memory_admin_clear_confirm') { + const success = await deleteAllMemories(targetId); + + if (success) { + await buttonInteraction.update({ + content: `🧹 Done! All memories for **${targetUsername}** have been cleared.`, + components: [], + }); + info('Admin cleared all memories', { adminId, targetId, targetUsername }); + } else { + await buttonInteraction.update({ + content: `❌ Failed to clear memories for **${targetUsername}**. Please try again later.`, + components: [], + }); + warn('Admin failed to clear memories', { adminId, targetId, targetUsername }); + } + } else { + await buttonInteraction.update({ + content: '↩️ Memory deletion cancelled.', + components: [], + }); + } + } catch { + // Timeout — no interaction received within 30 seconds + await interaction.editReply({ + content: '⏰ Confirmation timed out. No memories were deleted.', + components: [], + }); + } +} diff --git a/src/db.js b/src/db.js index 77cc7512..111ed732 100644 --- a/src/db.js +++ b/src/db.js @@ -159,6 +159,14 @@ export async function initDb() { ON mod_scheduled_actions (executed, execute_at) `); + // Memory opt-out table + await pool.query(` + CREATE TABLE IF NOT EXISTS memory_optouts ( + user_id TEXT PRIMARY KEY, + created_at TIMESTAMPTZ DEFAULT NOW() + ) + `); + info('Database schema initialized'); } catch (err) { // Clean up the pool so getPool() doesn't return an unusable instance diff --git a/src/index.js b/src/index.js index db6be201..d5a4cc77 100644 --- a/src/index.js +++ b/src/index.js @@ -28,7 +28,9 @@ import { } from './modules/ai.js'; import { loadConfig } from './modules/config.js'; import { registerEventHandlers } from './modules/events.js'; +import { checkMem0Health } from './modules/memory.js'; import { startTempbanScheduler, stopTempbanScheduler } from './modules/moderation.js'; +import { loadOptOuts } from './modules/optout.js'; import { HealthMonitor } from './utils/health.js'; import { loadCommandsFromDirectory } from './utils/loadCommands.js'; import { getPermissionError, hasPermission } from './utils/permissions.js'; @@ -288,6 +290,12 @@ async function startup() { // Start periodic conversation cleanup startConversationCleanup(); + // Load opt-out preferences from DB before enabling memory features + await loadOptOuts(); + + // Check mem0 availability for user memory features + await checkMem0Health(); + // Register event handlers with live config reference registerEventHandlers(client, config, healthMonitor); diff --git a/src/modules/ai.js b/src/modules/ai.js index 6d28e226..1d7a285d 100644 --- a/src/modules/ai.js +++ b/src/modules/ai.js @@ -6,6 +6,7 @@ import { info, error as logError, warn as logWarn } from '../logger.js'; import { getConfig } from './config.js'; +import { buildMemoryContext, extractAndStoreMemories } from './memory.js'; // Conversation history per channel (in-memory cache) let conversationHistory = new Map(); @@ -368,12 +369,18 @@ async function runCleanup() { } /** - * Generate AI response using OpenClaw's chat completions endpoint + * Generate AI response using OpenClaw's chat completions endpoint. + * + * Memory integration: + * - Pre-response: searches mem0 for relevant user memories and appends them to the system prompt. + * - Post-response: fires off memory extraction (non-blocking) so new facts get persisted. + * * @param {string} channelId - Channel ID * @param {string} userMessage - User's message * @param {string} username - Username * @param {Object} config - Bot configuration * @param {Object} healthMonitor - Health monitor instance (optional) + * @param {string} [userId] - Discord user ID for memory scoping * @returns {Promise} AI response */ export async function generateResponse( @@ -382,16 +389,30 @@ export async function generateResponse( username, config, healthMonitor = null, + userId = null, ) { const history = await getHistoryAsync(channelId); - const systemPrompt = + let systemPrompt = config.ai?.systemPrompt || `You are Volvox Bot, a helpful and friendly Discord bot for the Volvox developer community. You're witty, knowledgeable about programming and tech, and always eager to help. Keep responses concise and Discord-friendly (under 2000 chars). You can use Discord markdown formatting.`; + // Pre-response: inject user memory context into system prompt + if (userId) { + try { + const memoryContext = await buildMemoryContext(userId, username, userMessage); + if (memoryContext) { + systemPrompt += memoryContext; + } + } catch (err) { + // Memory lookup failed — continue without it + logWarn('Memory context lookup failed', { userId, error: err.message }); + } + } + // Build messages array for OpenAI-compatible API const messages = [ { role: 'system', content: systemPrompt }, @@ -439,6 +460,13 @@ You can use Discord markdown formatting.`; addToHistory(channelId, 'user', `${username}: ${userMessage}`, username); addToHistory(channelId, 'assistant', reply); + // Post-response: extract and store memorable facts (fire-and-forget) + if (userId) { + extractAndStoreMemories(userId, username, userMessage, reply).catch((err) => { + logWarn('Memory extraction failed', { userId, error: err.message }); + }); + } + return reply; } catch (err) { logError('OpenClaw API error', { error: err.message }); diff --git a/src/modules/events.js b/src/modules/events.js index 88fbb157..ca53ff3d 100644 --- a/src/modules/events.js +++ b/src/modules/events.js @@ -129,6 +129,7 @@ export function registerMessageCreateHandler(client, config, healthMonitor) { message.author.username, config, healthMonitor, + message.author.id, ); // Split long responses diff --git a/src/modules/memory.js b/src/modules/memory.js new file mode 100644 index 00000000..3e5f49bf --- /dev/null +++ b/src/modules/memory.js @@ -0,0 +1,517 @@ +/** + * Memory Module + * Integrates mem0 for persistent user memory across conversations. + * + * Uses the official mem0ai SDK with the hosted platform (api.mem0.ai) + * and graph memory enabled for entity relationship tracking. + * + * All operations are scoped per-user (Discord ID) and namespaced + * with app_id="bills-bot" to isolate from other consumers. + * + * Graceful fallback: if mem0 is unavailable, all operations return + * safe defaults (empty arrays / false) so the AI pipeline continues. + * + * **Privacy Notice:** + * This module sends user messages to the mem0 hosted platform + * (api.mem0.ai) for memory extraction and storage. By interacting + * with the bot, users' messages may be processed and stored externally. + * Users can view and delete their stored memories via the /memory command. + * The /memory forget command allows users to clear all their data. + */ + +import MemoryClient from 'mem0ai'; +import { debug, info, warn as logWarn } from '../logger.js'; +import { getConfig } from './config.js'; +import { isOptedOut } from './optout.js'; + +/** App namespace — isolates memories from other mem0 consumers */ +const APP_ID = 'bills-bot'; + +/** Default maximum memories to inject into context */ +const DEFAULT_MAX_CONTEXT_MEMORIES = 5; + +/** Cooldown period before retrying after a transient failure (ms) */ +const RECOVERY_COOLDOWN_MS = 60_000; + +/** HTTP status codes and error patterns for transient (retryable) errors */ +const TRANSIENT_ERROR_CODES = new Set([ + 'ECONNREFUSED', + 'ECONNRESET', + 'ETIMEDOUT', + 'ENOTFOUND', + 'EAI_AGAIN', +]); + +/** + * Determine whether an error is transient (temporary network/server issue) + * or permanent (auth failure, bad request, etc.). + * + * Transient errors should NOT disable the memory system — they are expected + * to resolve on their own (network blips, server restarts, 5xx errors). + * + * Permanent errors (401, 403, 422, other 4xx) indicate configuration issues + * that won't self-resolve, so the system should be marked unavailable. + * + * @param {Error} err - The caught error + * @returns {boolean} true if the error is transient and retryable + */ +function isTransientError(err) { + // Network-level errors (no HTTP response) + if (err.code && TRANSIENT_ERROR_CODES.has(err.code)) return true; + + // HTTP status-based classification + const status = err.status || err.statusCode || err.response?.status; + if (status) { + // 5xx = server error (transient), 429 = rate limited (transient) + if (status >= 500 || status === 429) return true; + // 4xx = client error (permanent) — auth failures, bad requests + if (status >= 400 && status < 500) return false; + } + + // Common transient error message patterns + const msg = (err.message || '').toLowerCase(); + if ( + msg.includes('timeout') || + msg.includes('econnrefused') || + msg.includes('econnreset') || + msg.includes('network') || + msg.includes('socket hang up') || + msg.includes('fetch failed') + ) { + return true; + } + + // Default: treat unknown errors as permanent (safer — triggers markUnavailable) + return false; +} + +/** Tracks whether mem0 is reachable (set by health check, cleared on errors) */ +let mem0Available = false; + +/** Timestamp (ms) when mem0 was last marked unavailable (0 = never) */ +let mem0UnavailableSince = 0; + +/** Singleton MemoryClient instance */ +let client = null; + +/** + * Mark mem0 as unavailable with a cooldown for auto-recovery. + * After RECOVERY_COOLDOWN_MS, the next request will be allowed through + * to check if the service has recovered. + */ +function markUnavailable() { + mem0Available = false; + mem0UnavailableSince = Date.now(); +} + +/** + * Mark mem0 as available and clear the recovery cooldown. + */ +function markAvailable() { + mem0Available = true; + mem0UnavailableSince = 0; +} + +/** + * Get or create the mem0 client instance. + * Returns null if the API key is not configured. + * @returns {MemoryClient|null} + */ +function getClient() { + if (client) return client; + + const apiKey = process.env.MEM0_API_KEY; + if (!apiKey) return null; + + try { + client = new MemoryClient({ apiKey }); + return client; + } catch (err) { + logWarn('Failed to create mem0 client', { error: err.message }); + return null; + } +} + +/** + * Get memory config from bot config + * @returns {Object} Memory configuration with defaults applied + */ +export function getMemoryConfig() { + try { + const config = getConfig(); + return { + enabled: config?.memory?.enabled ?? true, + maxContextMemories: config?.memory?.maxContextMemories ?? DEFAULT_MAX_CONTEXT_MEMORIES, + autoExtract: config?.memory?.autoExtract ?? true, + }; + } catch { + return { + enabled: false, + maxContextMemories: DEFAULT_MAX_CONTEXT_MEMORIES, + autoExtract: false, + }; + } +} + +/** + * Check if memory feature is enabled and mem0 is available. + * Supports auto-recovery: if mem0 was marked unavailable due to a transient + * error and the cooldown period has elapsed, it will be tentatively re-enabled + * so the next request can check if the service has recovered. + * @returns {boolean} + */ +export function isMemoryAvailable() { + const memConfig = getMemoryConfig(); + if (!memConfig.enabled) return false; + + if (mem0Available) return true; + + // Auto-recovery: if cooldown has elapsed, tentatively re-enable + if (mem0UnavailableSince > 0 && Date.now() - mem0UnavailableSince >= RECOVERY_COOLDOWN_MS) { + info('mem0 cooldown expired, attempting auto-recovery'); + markAvailable(); + return true; + } + + return false; +} + +/** + * Set the mem0 availability flag (for testing / health checks). + * + * **Asymmetric behavior by design:** + * - Setting `true` calls {@link markAvailable}, clearing any cooldown state. + * - Setting `false` performs a **hard disable** — sets mem0Available to false + * and resets the cooldown timestamp to 0 — but does NOT trigger the recovery + * cooldown (unlike {@link markUnavailable} which records a timestamp so + * auto-recovery can kick in after RECOVERY_COOLDOWN_MS). + * + * This is intentional: _setMem0Available is a test/health-check helper that + * needs to instantly toggle state without side effects from cooldown timers. + * Production error paths use markUnavailable() instead, which enables the + * timed auto-recovery flow. + * + * @param {boolean} available + */ +export function _setMem0Available(available) { + if (available) { + markAvailable(); + } else { + mem0Available = false; + mem0UnavailableSince = 0; + } +} + +/** + * Get the recovery cooldown duration in ms (exported for testing) + * @returns {number} + */ +export function _getRecoveryCooldownMs() { + return RECOVERY_COOLDOWN_MS; +} + +/** + * Expose isTransientError for testing (prefixed with _ to indicate internal) + * @param {Error} err + * @returns {boolean} + */ +export function _isTransientError(err) { + return isTransientError(err); +} + +/** + * Set the mem0 client instance (for testing) + * @param {object|null} newClient + */ +export function _setClient(newClient) { + client = newClient; +} + +/** + * Run a health check against the mem0 platform on startup. + * Verifies the API key is configured and the SDK client can actually + * communicate with the hosted platform by performing a lightweight search. + * @returns {Promise} true if mem0 is ready + */ +export async function checkMem0Health() { + const memConfig = getMemoryConfig(); + if (!memConfig.enabled) { + info('Memory module disabled via config'); + markUnavailable(); + return false; + } + + const apiKey = process.env.MEM0_API_KEY; + if (!apiKey) { + logWarn('MEM0_API_KEY not set — memory features disabled'); + markUnavailable(); + return false; + } + + try { + const c = getClient(); + if (!c) { + markUnavailable(); + return false; + } + + // Verify SDK connectivity with a lightweight search against the platform + await c.search('health-check', { + user_id: '__health_check__', + app_id: APP_ID, + limit: 1, + }); + + markAvailable(); + info('mem0 health check passed (SDK connectivity verified)'); + return true; + } catch (err) { + logWarn('mem0 health check failed', { error: err.message }); + markUnavailable(); + return false; + } +} + +/** + * Add a memory for a user. + * Graph memory is enabled to automatically build entity relationships. + * + * Part of the public API — used by extractAndStoreMemories internally and + * exported for direct use by other modules/plugins that need to store + * specific memories programmatically. + * + * @param {string} userId - Discord user ID + * @param {string} text - The memory text to store + * @param {Object} [metadata] - Optional metadata + * @returns {Promise} true if stored successfully + */ +export async function addMemory(userId, text, metadata = {}) { + if (!isMemoryAvailable()) return false; + + try { + const c = getClient(); + if (!c) return false; + + const messages = [{ role: 'user', content: text }]; + await c.add(messages, { + user_id: userId, + app_id: APP_ID, + metadata, + enable_graph: true, + }); + + debug('Memory added', { userId, textPreview: text.substring(0, 100) }); + return true; + } catch (err) { + logWarn('Failed to add memory', { userId, error: err.message }); + if (!isTransientError(err)) markUnavailable(); + return false; + } +} + +/** + * Search memories relevant to a query for a given user. + * Returns both regular memory results and graph relations. + * @param {string} userId - Discord user ID + * @param {string} query - Search query + * @param {number} [limit] - Max results (defaults to config maxContextMemories) + * @returns {Promise<{memories: Array<{memory: string, score?: number}>, relations: Array}>} + */ +export async function searchMemories(userId, query, limit) { + if (!isMemoryAvailable()) return { memories: [], relations: [] }; + + const memConfig = getMemoryConfig(); + const maxResults = limit ?? memConfig.maxContextMemories; + + try { + const c = getClient(); + if (!c) return { memories: [], relations: [] }; + + const result = await c.search(query, { + user_id: userId, + app_id: APP_ID, + limit: maxResults, + enable_graph: true, + }); + + // SDK returns { results: [...], relations: [...] } with graph enabled + const rawMemories = Array.isArray(result) ? result : result?.results || []; + const relations = result?.relations || []; + + const memories = rawMemories.map((m) => ({ + id: m.id || '', + memory: m.memory || m.text || m.content || '', + score: m.score ?? null, + })); + + return { memories, relations }; + } catch (err) { + logWarn('Failed to search memories', { userId, error: err.message }); + if (!isTransientError(err)) markUnavailable(); + return { memories: [], relations: [] }; + } +} + +/** + * Get all memories for a user. + * @param {string} userId - Discord user ID + * @returns {Promise>} All user memories + */ +export async function getMemories(userId) { + if (!isMemoryAvailable()) return []; + + try { + const c = getClient(); + if (!c) return []; + + const result = await c.getAll({ + user_id: userId, + app_id: APP_ID, + enable_graph: true, + }); + + const memories = Array.isArray(result) ? result : result?.results || []; + + return memories.map((m) => ({ + id: m.id || '', + memory: m.memory || m.text || m.content || '', + })); + } catch (err) { + logWarn('Failed to get memories', { userId, error: err.message }); + if (!isTransientError(err)) markUnavailable(); + return []; + } +} + +/** + * Delete all memories for a user. + * @param {string} userId - Discord user ID + * @returns {Promise} true if deleted successfully + */ +export async function deleteAllMemories(userId) { + if (!isMemoryAvailable()) return false; + + try { + const c = getClient(); + if (!c) return false; + + await c.deleteAll({ user_id: userId, app_id: APP_ID }); + info('All memories deleted for user', { userId }); + return true; + } catch (err) { + logWarn('Failed to delete all memories', { userId, error: err.message }); + if (!isTransientError(err)) markUnavailable(); + return false; + } +} + +/** + * Delete a specific memory by ID. + * @param {string} memoryId - Memory ID to delete + * @returns {Promise} true if deleted successfully + */ +export async function deleteMemory(memoryId) { + if (!isMemoryAvailable()) return false; + + try { + const c = getClient(); + if (!c) return false; + + await c.delete(memoryId); + debug('Memory deleted', { memoryId }); + return true; + } catch (err) { + logWarn('Failed to delete memory', { memoryId, error: err.message }); + if (!isTransientError(err)) markUnavailable(); + return false; + } +} + +/** + * Format graph relations into a readable context string. + * @param {Array<{source: string, source_type: string, relationship: string, target: string, target_type: string}>} relations + * @returns {string} Formatted relations string or empty string + */ +export function formatRelations(relations) { + if (!relations || relations.length === 0) return ''; + + const lines = relations.map((r) => `- ${r.source} → ${r.relationship} → ${r.target}`); + + return `\nRelationships:\n${lines.join('\n')}`; +} + +/** + * Build a context string from user memories to inject into the system prompt. + * Includes both regular memories and graph relations for richer context. + * @param {string} userId - Discord user ID + * @param {string} username - Display name + * @param {string} query - The user's current message (for relevance search) + * @returns {Promise} Context string or empty string + */ +export async function buildMemoryContext(userId, username, query) { + if (!isMemoryAvailable()) return ''; + if (isOptedOut(userId)) return ''; + + const { memories, relations } = await searchMemories(userId, query); + + if (memories.length === 0 && (!relations || relations.length === 0)) return ''; + + let context = ''; + + if (memories.length > 0) { + const memoryLines = memories.map((m) => `- ${m.memory}`).join('\n'); + context += `\n\nWhat you know about ${username}:\n${memoryLines}`; + } + + const relationsContext = formatRelations(relations); + if (relationsContext) { + context += relationsContext; + } + + return context; +} + +/** + * Analyze a conversation exchange and extract memorable facts to store. + * Uses mem0's AI to identify new personal info worth remembering. + * Graph memory is enabled to automatically build entity relationships. + * @param {string} userId - Discord user ID + * @param {string} username - Display name + * @param {string} userMessage - What the user said + * @param {string} assistantReply - What the bot replied + * @returns {Promise} true if any memories were stored + */ +export async function extractAndStoreMemories(userId, username, userMessage, assistantReply) { + if (!isMemoryAvailable()) return false; + if (isOptedOut(userId)) return false; + + const memConfig = getMemoryConfig(); + if (!memConfig.autoExtract) return false; + + try { + const c = getClient(); + if (!c) return false; + + const messages = [ + { role: 'user', content: `${username}: ${userMessage}` }, + { role: 'assistant', content: assistantReply }, + ]; + + await c.add(messages, { + user_id: userId, + app_id: APP_ID, + enable_graph: true, + }); + + debug('Memory extraction completed', { + userId, + username, + messagePreview: userMessage.substring(0, 80), + }); + return true; + } catch (err) { + logWarn('Memory extraction failed', { userId, error: err.message }); + if (!isTransientError(err)) markUnavailable(); + return false; + } +} diff --git a/src/modules/optout.js b/src/modules/optout.js new file mode 100644 index 00000000..cdc9ea5b --- /dev/null +++ b/src/modules/optout.js @@ -0,0 +1,124 @@ +/** + * Opt-Out Module + * Manages user opt-out state for memory collection. + * + * Users who opt out will not have their messages analyzed for memory + * extraction and will not have memories injected into AI context. + * The bot still works normally for opted-out users, just without + * long-term memory features. + * + * State is stored in an in-memory Set for fast lookups and persisted + * to PostgreSQL (memory_optouts table) for durability across restarts. + */ + +import { getPool } from '../db.js'; +import { info, warn as logWarn } from '../logger.js'; + +/** In-memory set of opted-out user IDs */ +let optedOutUsers = new Set(); + +/** Database pool — defaults to getPool(), can be overridden for testing */ +let pool = null; + +/** + * Get the active database pool. + * Uses injected pool if set, otherwise falls back to getPool(). + * @returns {import('pg').Pool | null} + */ +function resolvePool() { + if (pool) return pool; + try { + return getPool(); + } catch { + return null; + } +} + +/** + * Set the database pool (for testing). + * @param {import('pg').Pool | null} mockPool + */ +export function _setPool(mockPool) { + pool = mockPool; +} + +/** + * Reset the opt-out state (for testing). + */ +export function _resetOptouts() { + optedOutUsers = new Set(); + pool = null; +} + +/** + * Check if a user has opted out of memory collection. + * @param {string} userId - Discord user ID + * @returns {boolean} true if the user has opted out + */ +export function isOptedOut(userId) { + return optedOutUsers.has(userId); +} + +/** + * Toggle the opt-out state for a user. + * If opted out, opts them back in. If opted in, opts them out. + * Persists the change to the database (best-effort). + * @param {string} userId - Discord user ID + * @returns {Promise<{ optedOut: boolean }>} The new opt-out state + */ +export async function toggleOptOut(userId) { + const db = resolvePool(); + + if (optedOutUsers.has(userId)) { + optedOutUsers.delete(userId); + info('User opted back in to memory', { userId }); + + if (db) { + try { + await db.query('DELETE FROM memory_optouts WHERE user_id = $1', [userId]); + } catch (err) { + logWarn('Failed to delete opt-out from database', { userId, error: err.message }); + } + } + + return { optedOut: false }; + } + + optedOutUsers.add(userId); + info('User opted out of memory', { userId }); + + if (db) { + try { + await db.query( + 'INSERT INTO memory_optouts (user_id) VALUES ($1) ON CONFLICT (user_id) DO NOTHING', + [userId], + ); + } catch (err) { + logWarn('Failed to persist opt-out to database', { userId, error: err.message }); + } + } + + return { optedOut: true }; +} + +/** + * Load opt-out state from the database. + * Handles unavailable database gracefully. + */ +export async function loadOptOuts() { + const db = resolvePool(); + + if (!db) { + logWarn('Database not available, starting with empty opt-out set'); + return; + } + + try { + const result = await db.query('SELECT user_id FROM memory_optouts'); + optedOutUsers = new Set(result.rows.map((row) => row.user_id)); + info('Loaded opt-out list from database', { count: optedOutUsers.size }); + } catch (err) { + logWarn('Failed to load opt-outs from database', { error: err.message }); + optedOutUsers = new Set(); + } +} diff --git a/tests/commands/memory.test.js b/tests/commands/memory.test.js new file mode 100644 index 00000000..b4cf5e15 --- /dev/null +++ b/tests/commands/memory.test.js @@ -0,0 +1,887 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +// Mock discord.js +vi.mock('discord.js', () => { + class MockSlashCommandBuilder { + constructor() { + this.name = ''; + this.description = ''; + this._subcommands = []; + this._subcommandGroups = []; + } + setName(name) { + this.name = name; + return this; + } + setDescription(desc) { + this.description = desc; + return this; + } + addSubcommand(fn) { + const sub = new MockSubcommandBuilder(); + fn(sub); + this._subcommands.push(sub); + return this; + } + addSubcommandGroup(fn) { + const group = new MockSubcommandGroupBuilder(); + fn(group); + this._subcommandGroups.push(group); + return this; + } + toJSON() { + return { name: this.name, description: this.description }; + } + } + + class MockSubcommandGroupBuilder { + constructor() { + this.name = ''; + this.description = ''; + this._subcommands = []; + } + setName(name) { + this.name = name; + return this; + } + setDescription(desc) { + this.description = desc; + return this; + } + addSubcommand(fn) { + const sub = new MockSubcommandBuilder(); + fn(sub); + this._subcommands.push(sub); + return this; + } + } + + class MockSubcommandBuilder { + constructor() { + this.name = ''; + this.description = ''; + this._options = []; + } + setName(name) { + this.name = name; + return this; + } + setDescription(desc) { + this.description = desc; + return this; + } + addStringOption(fn) { + const opt = new MockStringOption(); + fn(opt); + this._options.push(opt); + return this; + } + addUserOption(fn) { + const opt = new MockUserOption(); + fn(opt); + this._options.push(opt); + return this; + } + } + + class MockStringOption { + constructor() { + this.name = ''; + this.description = ''; + this.required = false; + } + setName(name) { + this.name = name; + return this; + } + setDescription(desc) { + this.description = desc; + return this; + } + setRequired(req) { + this.required = req; + return this; + } + } + + class MockUserOption { + constructor() { + this.name = ''; + this.description = ''; + this.required = false; + } + setName(name) { + this.name = name; + return this; + } + setDescription(desc) { + this.description = desc; + return this; + } + setRequired(req) { + this.required = req; + return this; + } + } + + class MockButtonBuilder { + constructor() { + this._customId = ''; + this._label = ''; + this._style = null; + } + setCustomId(id) { + this._customId = id; + return this; + } + setLabel(label) { + this._label = label; + return this; + } + setStyle(style) { + this._style = style; + return this; + } + } + + class MockActionRowBuilder { + constructor() { + this._components = []; + } + addComponents(...components) { + this._components.push(...components); + return this; + } + } + + return { + SlashCommandBuilder: MockSlashCommandBuilder, + ButtonBuilder: MockButtonBuilder, + ActionRowBuilder: MockActionRowBuilder, + ButtonStyle: { Danger: 4, Secondary: 2 }, + ComponentType: { Button: 2 }, + PermissionFlagsBits: { + ManageGuild: 1n << 5n, + Administrator: 1n << 3n, + }, + }; +}); + +// Mock splitMessage utility +vi.mock('../../src/utils/splitMessage.js', () => ({ + splitMessage: vi.fn((text, maxLength) => { + if (!text || text.length <= (maxLength || 1990)) return text ? [text] : []; + return [text.slice(0, maxLength || 1990), text.slice(maxLength || 1990)]; + }), +})); + +// Mock memory module +vi.mock('../../src/modules/memory.js', () => ({ + isMemoryAvailable: vi.fn(() => true), + getMemories: vi.fn(() => Promise.resolve([])), + deleteAllMemories: vi.fn(() => Promise.resolve(true)), + searchMemories: vi.fn(() => Promise.resolve({ memories: [], relations: [] })), + deleteMemory: vi.fn(() => Promise.resolve(true)), +})); + +// Mock optout module +vi.mock('../../src/modules/optout.js', () => ({ + isOptedOut: vi.fn(() => false), + toggleOptOut: vi.fn(() => ({ optedOut: true })), +})); + +// Mock logger +vi.mock('../../src/logger.js', () => ({ + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), +})); + +import { PermissionFlagsBits } from 'discord.js'; +import { data, execute } from '../../src/commands/memory.js'; +import { + deleteAllMemories, + deleteMemory, + getMemories, + isMemoryAvailable, + searchMemories, +} from '../../src/modules/memory.js'; +import { isOptedOut, toggleOptOut } from '../../src/modules/optout.js'; + +/** + * Create a mock interaction for memory command tests. + * @param {Object} options - Override options + * @returns {Object} Mock interaction + */ +function createMockInteraction({ + subcommand = 'view', + subcommandGroup = null, + topic = null, + userId = '123456', + username = 'testuser', + targetUser = null, + hasManageGuild = false, + hasAdmin = false, +} = {}) { + const mockResponse = { + awaitMessageComponent: vi.fn(), + }; + + return { + options: { + getSubcommand: () => subcommand, + getSubcommandGroup: () => subcommandGroup, + getString: (name) => (name === 'topic' ? topic : null), + getUser: () => targetUser, + }, + user: { id: userId, username }, + memberPermissions: { + has: (perm) => { + if (perm === PermissionFlagsBits.ManageGuild) return hasManageGuild; + if (perm === PermissionFlagsBits.Administrator) return hasAdmin; + return false; + }, + }, + reply: vi.fn().mockResolvedValue(mockResponse), + deferReply: vi.fn(), + editReply: vi.fn(), + _mockResponse: mockResponse, + }; +} + +describe('memory command', () => { + beforeEach(() => { + vi.clearAllMocks(); + isMemoryAvailable.mockReturnValue(true); + getMemories.mockResolvedValue([]); + deleteAllMemories.mockResolvedValue(true); + searchMemories.mockResolvedValue({ memories: [], relations: [] }); + deleteMemory.mockResolvedValue(true); + toggleOptOut.mockReturnValue({ optedOut: true }); + isOptedOut.mockReturnValue(false); + }); + + describe('data export', () => { + it('should export command data with name "memory"', () => { + expect(data.name).toBe('memory'); + expect(data.description).toBeTruthy(); + }); + }); + + describe('unavailable state', () => { + it('should reply with unavailable message when memory is not available', async () => { + isMemoryAvailable.mockReturnValue(false); + const interaction = createMockInteraction(); + + await execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('unavailable'), + ephemeral: true, + }), + ); + }); + }); + + describe('/memory view', () => { + it('should show empty message when no memories exist', async () => { + getMemories.mockResolvedValue([]); + const interaction = createMockInteraction({ subcommand: 'view' }); + + await execute(interaction); + + expect(interaction.deferReply).toHaveBeenCalledWith({ ephemeral: true }); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining("don't have any memories"), + }), + ); + }); + + it('should display formatted memories', async () => { + getMemories.mockResolvedValue([ + { id: 'mem-1', memory: 'Loves Rust' }, + { id: 'mem-2', memory: 'Works at Google' }, + ]); + const interaction = createMockInteraction({ subcommand: 'view' }); + + await execute(interaction); + + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('Loves Rust'), + }), + ); + expect(interaction.editReply.mock.calls[0][0].content).toContain('Works at Google'); + expect(interaction.editReply.mock.calls[0][0].content).toContain( + 'What I remember about testuser', + ); + }); + + it('should truncate long memory lists', async () => { + // Create many long memories to exceed 2000 chars + const memories = Array.from({ length: 50 }, (_, i) => ({ + id: `mem-${i}`, + memory: `This is a long memory entry number ${i} with lots of detail about the user's preferences and interests that takes up space`, + })); + getMemories.mockResolvedValue(memories); + const interaction = createMockInteraction({ subcommand: 'view' }); + + await execute(interaction); + + const content = interaction.editReply.mock.calls[0][0].content; + expect(content.length).toBeLessThanOrEqual(2000); + expect(content).toContain('...and more'); + }); + }); + + describe('/memory optout', () => { + it('should toggle opt-out and reply with opted-out message', async () => { + toggleOptOut.mockReturnValue({ optedOut: true }); + const interaction = createMockInteraction({ subcommand: 'optout' }); + + await execute(interaction); + + expect(toggleOptOut).toHaveBeenCalledWith('123456'); + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('opted out'), + ephemeral: true, + }), + ); + }); + + it('should toggle opt-in and reply with opted-in message', async () => { + toggleOptOut.mockReturnValue({ optedOut: false }); + const interaction = createMockInteraction({ subcommand: 'optout' }); + + await execute(interaction); + + expect(toggleOptOut).toHaveBeenCalledWith('123456'); + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('opted back in'), + ephemeral: true, + }), + ); + }); + + it('should work even when memory system is unavailable', async () => { + isMemoryAvailable.mockReturnValue(false); + toggleOptOut.mockReturnValue({ optedOut: true }); + const interaction = createMockInteraction({ subcommand: 'optout' }); + + await execute(interaction); + + expect(toggleOptOut).toHaveBeenCalledWith('123456'); + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('opted out'), + ephemeral: true, + }), + ); + }); + }); + + describe('/memory forget (all) — confirmation flow', () => { + it('should show confirmation buttons when forgetting all', async () => { + const interaction = createMockInteraction({ subcommand: 'forget' }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_forget_confirm', + update: vi.fn(), + }); + + await execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('Are you sure'), + components: expect.any(Array), + ephemeral: true, + }), + ); + }); + + it('should delete memories on confirm', async () => { + deleteAllMemories.mockResolvedValue(true); + const mockUpdate = vi.fn(); + const interaction = createMockInteraction({ subcommand: 'forget' }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_forget_confirm', + update: mockUpdate, + }); + + await execute(interaction); + + expect(deleteAllMemories).toHaveBeenCalledWith('123456'); + expect(mockUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('cleared'), + components: [], + }), + ); + }); + + it('should show error when deletion fails on confirm', async () => { + deleteAllMemories.mockResolvedValue(false); + const mockUpdate = vi.fn(); + const interaction = createMockInteraction({ subcommand: 'forget' }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_forget_confirm', + update: mockUpdate, + }); + + await execute(interaction); + + expect(mockUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('Failed'), + components: [], + }), + ); + }); + + it('should cancel on cancel button', async () => { + const mockUpdate = vi.fn(); + const interaction = createMockInteraction({ subcommand: 'forget' }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_forget_cancel', + update: mockUpdate, + }); + + await execute(interaction); + + expect(deleteAllMemories).not.toHaveBeenCalled(); + expect(mockUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('cancelled'), + components: [], + }), + ); + }); + + it('should timeout after 30 seconds', async () => { + const interaction = createMockInteraction({ subcommand: 'forget' }); + interaction._mockResponse.awaitMessageComponent.mockRejectedValue( + new Error('Collector received no interactions before ending with reason: time'), + ); + + await execute(interaction); + + expect(deleteAllMemories).not.toHaveBeenCalled(); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('timed out'), + components: [], + }), + ); + }); + + it('should pass correct filter to awaitMessageComponent', async () => { + const interaction = createMockInteraction({ subcommand: 'forget', userId: 'user789' }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_forget_cancel', + update: vi.fn(), + }); + + await execute(interaction); + + const awaitCall = interaction._mockResponse.awaitMessageComponent.mock.calls[0][0]; + expect(awaitCall.time).toBe(30_000); + + // Test the filter function + expect(awaitCall.filter({ user: { id: 'user789' } })).toBe(true); + expect(awaitCall.filter({ user: { id: 'other_user' } })).toBe(false); + }); + }); + + describe('/memory forget ', () => { + it('should search and delete matching memories using IDs from search results', async () => { + searchMemories.mockResolvedValue({ + memories: [{ id: 'mem-1', memory: 'User is learning Rust', score: 0.95 }], + relations: [], + }); + deleteMemory.mockResolvedValue(true); + + const interaction = createMockInteraction({ + subcommand: 'forget', + topic: 'Rust', + }); + + await execute(interaction); + + expect(interaction.deferReply).toHaveBeenCalledWith({ ephemeral: true }); + expect(searchMemories).toHaveBeenCalledWith('123456', 'Rust', 10); + expect(deleteMemory).toHaveBeenCalledWith('mem-1'); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('1 memory'), + }), + ); + }); + + it('should handle no matching memories', async () => { + searchMemories.mockResolvedValue({ memories: [], relations: [] }); + const interaction = createMockInteraction({ + subcommand: 'forget', + topic: 'nonexistent', + }); + + await execute(interaction); + + expect(interaction.deferReply).toHaveBeenCalledWith({ ephemeral: true }); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('No memories found'), + }), + ); + }); + + it('should handle deletion failure for matched memories', async () => { + searchMemories.mockResolvedValue({ + memories: [{ id: 'mem-1', memory: 'Test memory', score: 0.9 }], + relations: [], + }); + deleteMemory.mockResolvedValue(false); + + const interaction = createMockInteraction({ + subcommand: 'forget', + topic: 'test', + }); + + await execute(interaction); + + expect(interaction.deferReply).toHaveBeenCalledWith({ ephemeral: true }); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining("couldn't delete"), + }), + ); + }); + + it('should report correct count for multiple parallel deletions', async () => { + searchMemories.mockResolvedValue({ + memories: [ + { id: 'mem-1', memory: 'Rust project A', score: 0.95 }, + { id: 'mem-2', memory: 'Rust project B', score: 0.9 }, + ], + relations: [], + }); + deleteMemory.mockResolvedValue(true); + + const interaction = createMockInteraction({ + subcommand: 'forget', + topic: 'Rust', + }); + + await execute(interaction); + + expect(interaction.deferReply).toHaveBeenCalledWith({ ephemeral: true }); + expect(deleteMemory).toHaveBeenCalledTimes(2); + expect(deleteMemory).toHaveBeenCalledWith('mem-1'); + expect(deleteMemory).toHaveBeenCalledWith('mem-2'); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('2 memories'), + }), + ); + }); + }); + + describe('/memory admin view', () => { + it('should reject without proper permissions', async () => { + const interaction = createMockInteraction({ + subcommand: 'view', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: false, + hasAdmin: false, + }); + + await execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('Manage Server'), + ephemeral: true, + }), + ); + }); + + it('should allow with ManageGuild permission', async () => { + getMemories.mockResolvedValue([{ id: 'mem-1', memory: 'Target loves coding' }]); + const interaction = createMockInteraction({ + subcommand: 'view', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + + await execute(interaction); + + expect(interaction.deferReply).toHaveBeenCalledWith({ ephemeral: true }); + expect(getMemories).toHaveBeenCalledWith('999'); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('targetuser'), + }), + ); + }); + + it('should allow with Administrator permission', async () => { + getMemories.mockResolvedValue([]); + const interaction = createMockInteraction({ + subcommand: 'view', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasAdmin: true, + }); + + await execute(interaction); + + expect(interaction.deferReply).toHaveBeenCalledWith({ ephemeral: true }); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('No memories found'), + }), + ); + }); + + it('should show opted-out status for target user', async () => { + isOptedOut.mockReturnValue(true); + getMemories.mockResolvedValue([]); + const interaction = createMockInteraction({ + subcommand: 'view', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + + await execute(interaction); + + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('opted out'), + }), + ); + }); + + it('should show memories for target user', async () => { + getMemories.mockResolvedValue([ + { id: 'mem-1', memory: 'Loves TypeScript' }, + { id: 'mem-2', memory: 'Works remotely' }, + ]); + const interaction = createMockInteraction({ + subcommand: 'view', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + + await execute(interaction); + + const content = interaction.editReply.mock.calls[0][0].content; + expect(content).toContain('Loves TypeScript'); + expect(content).toContain('Works remotely'); + expect(content).toContain('targetuser'); + }); + + it('should reply unavailable when memory system is down', async () => { + isMemoryAvailable.mockReturnValue(false); + const interaction = createMockInteraction({ + subcommand: 'view', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + + await execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('unavailable'), + ephemeral: true, + }), + ); + }); + }); + + describe('/memory admin clear', () => { + it('should reject without proper permissions', async () => { + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: false, + hasAdmin: false, + }); + + await execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('Manage Server'), + ephemeral: true, + }), + ); + }); + + it('should show confirmation with target username', async () => { + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_admin_clear_cancel', + update: vi.fn(), + }); + + await execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('targetuser'), + components: expect.any(Array), + ephemeral: true, + }), + ); + }); + + it('should delete target memories on confirm', async () => { + deleteAllMemories.mockResolvedValue(true); + const mockUpdate = vi.fn(); + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_admin_clear_confirm', + update: mockUpdate, + }); + + await execute(interaction); + + expect(deleteAllMemories).toHaveBeenCalledWith('999'); + expect(mockUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('targetuser'), + components: [], + }), + ); + }); + + it('should show error when admin clear fails', async () => { + deleteAllMemories.mockResolvedValue(false); + const mockUpdate = vi.fn(); + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_admin_clear_confirm', + update: mockUpdate, + }); + + await execute(interaction); + + expect(mockUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('Failed'), + components: [], + }), + ); + }); + + it('should cancel on cancel button', async () => { + const mockUpdate = vi.fn(); + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_admin_clear_cancel', + update: mockUpdate, + }); + + await execute(interaction); + + expect(deleteAllMemories).not.toHaveBeenCalled(); + expect(mockUpdate).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('cancelled'), + components: [], + }), + ); + }); + + it('should timeout after 30 seconds', async () => { + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + }); + interaction._mockResponse.awaitMessageComponent.mockRejectedValue( + new Error('Collector received no interactions before ending with reason: time'), + ); + + await execute(interaction); + + expect(deleteAllMemories).not.toHaveBeenCalled(); + expect(interaction.editReply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('timed out'), + components: [], + }), + ); + }); + + it('should only allow admin user to click buttons', async () => { + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + hasManageGuild: true, + userId: 'admin123', + }); + interaction._mockResponse.awaitMessageComponent.mockResolvedValue({ + customId: 'memory_admin_clear_cancel', + update: vi.fn(), + }); + + await execute(interaction); + + const awaitCall = interaction._mockResponse.awaitMessageComponent.mock.calls[0][0]; + expect(awaitCall.filter({ user: { id: 'admin123' } })).toBe(true); + expect(awaitCall.filter({ user: { id: 'other_user' } })).toBe(false); + }); + + it('should handle null memberPermissions', async () => { + const interaction = createMockInteraction({ + subcommand: 'clear', + subcommandGroup: 'admin', + targetUser: { id: '999', username: 'targetuser' }, + }); + interaction.memberPermissions = null; + + await execute(interaction); + + expect(interaction.reply).toHaveBeenCalledWith( + expect.objectContaining({ + content: expect.stringContaining('Manage Server'), + ephemeral: true, + }), + ); + }); + }); +}); diff --git a/tests/index.test.js b/tests/index.test.js index eb753db9..6d958658 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -142,6 +142,10 @@ vi.mock('../src/modules/events.js', () => ({ registerEventHandlers: mocks.events.registerEventHandlers, })); +vi.mock('../src/modules/memory.js', () => ({ + checkMem0Health: vi.fn().mockResolvedValue(false), +})); + vi.mock('../src/modules/moderation.js', () => ({ startTempbanScheduler: mocks.moderation.startTempbanScheduler, stopTempbanScheduler: mocks.moderation.stopTempbanScheduler, diff --git a/tests/modules/ai.test.js b/tests/modules/ai.test.js index ca430eb2..a30ecbb3 100644 --- a/tests/modules/ai.test.js +++ b/tests/modules/ai.test.js @@ -10,6 +10,12 @@ vi.mock('../../src/modules/config.js', () => ({ })), })); +// Mock memory module +vi.mock('../../src/modules/memory.js', () => ({ + buildMemoryContext: vi.fn(() => Promise.resolve('')), + extractAndStoreMemories: vi.fn(() => Promise.resolve(false)), +})); + import { _setPoolGetter, addToHistory, @@ -23,6 +29,7 @@ import { stopConversationCleanup, } from '../../src/modules/ai.js'; import { getConfig } from '../../src/modules/config.js'; +import { buildMemoryContext, extractAndStoreMemories } from '../../src/modules/memory.js'; // Mock logger vi.mock('../../src/logger.js', () => ({ @@ -224,6 +231,111 @@ describe('ai module', () => { const fetchCall = globalThis.fetch.mock.calls[0]; expect(fetchCall[1].headers['Content-Type']).toBe('application/json'); }); + + it('should inject memory context into system prompt when userId is provided', async () => { + buildMemoryContext.mockResolvedValue('\n\nWhat you know about testuser:\n- Loves Rust'); + + const mockResponse = { + ok: true, + json: vi.fn().mockResolvedValue({ + choices: [{ message: { content: 'I know you love Rust!' } }], + }), + }; + vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse); + + const config = { ai: { systemPrompt: 'You are a bot.' } }; + await generateResponse( + 'ch1', + 'What do you know about me?', + 'testuser', + config, + null, + 'user-123', + ); + + expect(buildMemoryContext).toHaveBeenCalledWith( + 'user-123', + 'testuser', + 'What do you know about me?', + ); + + // Verify the system prompt includes memory context + const fetchCall = globalThis.fetch.mock.calls[0]; + const body = JSON.parse(fetchCall[1].body); + expect(body.messages[0].content).toContain('What you know about testuser'); + expect(body.messages[0].content).toContain('Loves Rust'); + }); + + it('should not inject memory context when userId is null', async () => { + const mockResponse = { + ok: true, + json: vi.fn().mockResolvedValue({ + choices: [{ message: { content: 'OK' } }], + }), + }; + vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse); + + const config = { ai: { systemPrompt: 'You are a bot.' } }; + await generateResponse('ch1', 'Hi', 'user', config, null, null); + + expect(buildMemoryContext).not.toHaveBeenCalled(); + }); + + it('should fire memory extraction after response when userId is provided', async () => { + extractAndStoreMemories.mockResolvedValue(true); + const mockResponse = { + ok: true, + json: vi.fn().mockResolvedValue({ + choices: [{ message: { content: 'Nice!' } }], + }), + }; + vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse); + + const config = { ai: {} }; + await generateResponse('ch1', "I'm learning Rust", 'testuser', config, null, 'user-123'); + + // extractAndStoreMemories is fire-and-forget, wait for it + await vi.waitFor(() => { + expect(extractAndStoreMemories).toHaveBeenCalledWith( + 'user-123', + 'testuser', + "I'm learning Rust", + 'Nice!', + ); + }); + }); + + it('should continue working when memory context lookup fails', async () => { + buildMemoryContext.mockRejectedValue(new Error('mem0 down')); + + const mockResponse = { + ok: true, + json: vi.fn().mockResolvedValue({ + choices: [{ message: { content: 'Still working!' } }], + }), + }; + vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse); + + const config = { ai: { systemPrompt: 'You are a bot.' } }; + const reply = await generateResponse('ch1', 'Hi', 'user', config, null, 'user-123'); + + expect(reply).toBe('Still working!'); + }); + + it('should not call memory extraction when userId is not provided', async () => { + const mockResponse = { + ok: true, + json: vi.fn().mockResolvedValue({ + choices: [{ message: { content: 'OK' } }], + }), + }; + vi.spyOn(globalThis, 'fetch').mockResolvedValue(mockResponse); + + const config = { ai: {} }; + await generateResponse('ch1', 'Hi', 'user', config); + + expect(extractAndStoreMemories).not.toHaveBeenCalled(); + }); }); describe('cleanup scheduler', () => { diff --git a/tests/modules/events.test.js b/tests/modules/events.test.js index 3823c447..151e0355 100644 --- a/tests/modules/events.test.js +++ b/tests/modules/events.test.js @@ -398,7 +398,7 @@ describe('events module', () => { getOrCreateThread.mockResolvedValueOnce({ thread: mockThread, isNew: true }); const message = { - author: { bot: false, username: 'user' }, + author: { bot: false, id: 'author-123', username: 'user' }, guild: { id: 'g1' }, content: '<@bot-user-id> hello from channel', channel: { @@ -424,6 +424,7 @@ describe('events module', () => { 'user', config, null, + 'author-123', ); }); diff --git a/tests/modules/memory.test.js b/tests/modules/memory.test.js new file mode 100644 index 00000000..28b4b9c9 --- /dev/null +++ b/tests/modules/memory.test.js @@ -0,0 +1,890 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +// Mock mem0ai SDK +vi.mock('mem0ai', () => { + const MockMemoryClient = vi.fn(); + return { default: MockMemoryClient }; +}); + +// Mock config module +vi.mock('../../src/modules/config.js', () => ({ + getConfig: vi.fn(() => ({ + memory: { + enabled: true, + maxContextMemories: 5, + autoExtract: true, + }, + })), +})); + +// Mock optout module +vi.mock('../../src/modules/optout.js', () => ({ + isOptedOut: vi.fn(() => false), +})); + +// Mock logger +vi.mock('../../src/logger.js', () => ({ + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), +})); + +import { getConfig } from '../../src/modules/config.js'; +import { + _getRecoveryCooldownMs, + _isTransientError, + _setClient, + _setMem0Available, + addMemory, + buildMemoryContext, + checkMem0Health, + deleteAllMemories, + deleteMemory, + extractAndStoreMemories, + formatRelations, + getMemories, + getMemoryConfig, + isMemoryAvailable, + searchMemories, +} from '../../src/modules/memory.js'; +import { isOptedOut } from '../../src/modules/optout.js'; + +/** + * Create a mock mem0 client with all SDK methods stubbed. + * @param {Object} overrides - Method overrides + * @returns {Object} Mock client + */ +function createMockClient(overrides = {}) { + return { + add: vi.fn().mockResolvedValue({ results: [{ id: 'mem-1' }] }), + search: vi.fn().mockResolvedValue({ results: [], relations: [] }), + getAll: vi.fn().mockResolvedValue({ results: [] }), + delete: vi.fn().mockResolvedValue({ message: 'Memory deleted' }), + deleteAll: vi.fn().mockResolvedValue({ message: 'Memories deleted' }), + ...overrides, + }; +} + +describe('memory module', () => { + beforeEach(() => { + _setMem0Available(false); + _setClient(null); + vi.clearAllMocks(); + // Reset config mock to defaults + getConfig.mockReturnValue({ + memory: { + enabled: true, + maxContextMemories: 5, + autoExtract: true, + }, + }); + // Reset optout mock + isOptedOut.mockReturnValue(false); + // Set up env for tests + delete process.env.MEM0_API_KEY; + }); + + afterEach(() => { + _setClient(null); + delete process.env.MEM0_API_KEY; + }); + + describe('getMemoryConfig', () => { + it('should return config values from bot config', () => { + const config = getMemoryConfig(); + expect(config.enabled).toBe(true); + expect(config.maxContextMemories).toBe(5); + expect(config.autoExtract).toBe(true); + }); + + it('should return defaults when config is missing', () => { + getConfig.mockReturnValue({}); + const config = getMemoryConfig(); + expect(config.enabled).toBe(true); + expect(config.maxContextMemories).toBe(5); + expect(config.autoExtract).toBe(true); + }); + + it('should return safe disabled fallback when getConfig throws', () => { + getConfig.mockImplementation(() => { + throw new Error('not loaded'); + }); + const config = getMemoryConfig(); + expect(config.enabled).toBe(false); + expect(config.maxContextMemories).toBe(5); + expect(config.autoExtract).toBe(false); + }); + + it('should respect custom config values', () => { + getConfig.mockReturnValue({ + memory: { + enabled: false, + maxContextMemories: 10, + autoExtract: false, + }, + }); + const config = getMemoryConfig(); + expect(config.enabled).toBe(false); + expect(config.maxContextMemories).toBe(10); + expect(config.autoExtract).toBe(false); + }); + }); + + describe('isMemoryAvailable', () => { + afterEach(() => { + // Ensure fake timers never leak into other tests, even if a test fails mid-way + vi.useRealTimers(); + }); + + it('should return false when mem0 is not available', () => { + _setMem0Available(false); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should return true when enabled and available', () => { + _setMem0Available(true); + expect(isMemoryAvailable()).toBe(true); + }); + + it('should return false when disabled in config', () => { + _setMem0Available(true); + getConfig.mockReturnValue({ memory: { enabled: false } }); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should auto-recover after cooldown period expires', async () => { + vi.useFakeTimers(); + _setMem0Available(true); + + const failingClient = createMockClient({ + add: vi.fn().mockRejectedValue(new Error('API error')), + }); + _setClient(failingClient); + + // This will fail and call markUnavailable() + await addMemory('user123', 'test'); + expect(isMemoryAvailable()).toBe(false); + + // Advance time past the cooldown + vi.advanceTimersByTime(_getRecoveryCooldownMs()); + + // Should now auto-recover + expect(isMemoryAvailable()).toBe(true); + }); + + it('should not auto-recover before cooldown expires', async () => { + vi.useFakeTimers(); + _setMem0Available(true); + const failingClient = createMockClient({ + add: vi.fn().mockRejectedValue(new Error('API error')), + }); + _setClient(failingClient); + + // Trigger a failure to markUnavailable + await addMemory('user123', 'test'); + expect(isMemoryAvailable()).toBe(false); + + // Advance time but not enough + vi.advanceTimersByTime(_getRecoveryCooldownMs() - 1000); + expect(isMemoryAvailable()).toBe(false); + + // Now advance past the cooldown + vi.advanceTimersByTime(1000); + expect(isMemoryAvailable()).toBe(true); + }); + + it('should re-disable if recovery attempt also fails', async () => { + vi.useFakeTimers(); + _setMem0Available(true); + const failingClient = createMockClient({ + add: vi.fn().mockRejectedValue(new Error('API error')), + search: vi.fn().mockRejectedValue(new Error('Still down')), + }); + _setClient(failingClient); + + // Trigger initial failure + await addMemory('user123', 'test'); + expect(isMemoryAvailable()).toBe(false); + + // Advance past cooldown - auto-recovery kicks in + vi.advanceTimersByTime(_getRecoveryCooldownMs()); + expect(isMemoryAvailable()).toBe(true); + + // But the next operation also fails + await searchMemories('user123', 'test'); + expect(isMemoryAvailable()).toBe(false); + }); + }); + + describe('error classification', () => { + it('should treat network errors as transient', () => { + const econnrefused = new Error('connect ECONNREFUSED'); + econnrefused.code = 'ECONNREFUSED'; + expect(_isTransientError(econnrefused)).toBe(true); + + const etimedout = new Error('connect ETIMEDOUT'); + etimedout.code = 'ETIMEDOUT'; + expect(_isTransientError(etimedout)).toBe(true); + + const econnreset = new Error('socket hang up'); + econnreset.code = 'ECONNRESET'; + expect(_isTransientError(econnreset)).toBe(true); + }); + + it('should treat 5xx status codes as transient', () => { + const err500 = new Error('Internal Server Error'); + err500.status = 500; + expect(_isTransientError(err500)).toBe(true); + + const err503 = new Error('Service Unavailable'); + err503.status = 503; + expect(_isTransientError(err503)).toBe(true); + }); + + it('should treat 429 rate-limit errors as transient', () => { + const err429 = new Error('Too Many Requests'); + err429.status = 429; + expect(_isTransientError(err429)).toBe(true); + }); + + it('should treat 4xx auth/client errors as permanent', () => { + const err401 = new Error('Unauthorized'); + err401.status = 401; + expect(_isTransientError(err401)).toBe(false); + + const err403 = new Error('Forbidden'); + err403.status = 403; + expect(_isTransientError(err403)).toBe(false); + + const err422 = new Error('Unprocessable Entity'); + err422.status = 422; + expect(_isTransientError(err422)).toBe(false); + }); + + it('should treat timeout message patterns as transient', () => { + expect(_isTransientError(new Error('request timeout'))).toBe(true); + expect(_isTransientError(new Error('fetch failed'))).toBe(true); + expect(_isTransientError(new Error('network error'))).toBe(true); + }); + + it('should treat unknown errors as permanent', () => { + expect(_isTransientError(new Error('API error'))).toBe(false); + expect(_isTransientError(new Error('something unexpected'))).toBe(false); + }); + + it('should not mark unavailable on transient errors', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + add: vi.fn().mockRejectedValue( + (() => { + const e = new Error('Service Unavailable'); + e.status = 503; + return e; + })(), + ), + }); + _setClient(mockClient); + + const result = await addMemory('user123', 'test'); + expect(result).toBe(false); + // Should still be available — transient error + expect(isMemoryAvailable()).toBe(true); + }); + + it('should mark unavailable on permanent errors', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + add: vi.fn().mockRejectedValue( + (() => { + const e = new Error('Unauthorized'); + e.status = 401; + return e; + })(), + ), + }); + _setClient(mockClient); + + const result = await addMemory('user123', 'test'); + expect(result).toBe(false); + // Should be unavailable — auth error + expect(isMemoryAvailable()).toBe(false); + }); + }); + + describe('checkMem0Health', () => { + it('should mark as available when API key is set and SDK connectivity verified', async () => { + process.env.MEM0_API_KEY = 'test-api-key'; + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ results: [], relations: [] }), + }); + _setClient(mockClient); + + const result = await checkMem0Health(); + expect(result).toBe(true); + expect(isMemoryAvailable()).toBe(true); + + // Verify it performed a lightweight search to check connectivity + expect(mockClient.search).toHaveBeenCalledWith('health-check', { + user_id: '__health_check__', + app_id: 'bills-bot', + limit: 1, + }); + }); + + it('should mark as unavailable when API key is not set', async () => { + const result = await checkMem0Health(); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should return false when memory disabled in config', async () => { + getConfig.mockReturnValue({ memory: { enabled: false } }); + + const result = await checkMem0Health(); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should fail health check when SDK connectivity check throws', async () => { + process.env.MEM0_API_KEY = 'test-api-key'; + // Explicitly mock a client whose search method throws — simulates a client + // that was created successfully but cannot reach the mem0 platform + const brokenClient = createMockClient({ + search: vi.fn().mockRejectedValue(new Error('ECONNREFUSED: connect failed')), + }); + _setClient(brokenClient); + + const result = await checkMem0Health(); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + expect(brokenClient.search).toHaveBeenCalled(); + }); + + it('should mark as unavailable when SDK connectivity check fails', async () => { + process.env.MEM0_API_KEY = 'test-api-key'; + const mockClient = createMockClient({ + search: vi.fn().mockRejectedValue(new Error('Connection refused')), + }); + _setClient(mockClient); + + const result = await checkMem0Health(); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + }); + }); + + describe('addMemory', () => { + it('should return false when memory unavailable', async () => { + _setMem0Available(false); + const result = await addMemory('user123', 'I love Rust'); + expect(result).toBe(false); + }); + + it('should call client.add with correct params and return true', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + + const result = await addMemory('user123', 'I love Rust'); + expect(result).toBe(true); + + expect(mockClient.add).toHaveBeenCalledWith([{ role: 'user', content: 'I love Rust' }], { + user_id: 'user123', + app_id: 'bills-bot', + metadata: {}, + enable_graph: true, + }); + }); + + it('should return false on SDK error and mark unavailable', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + add: vi.fn().mockRejectedValue(new Error('API error')), + }); + _setClient(mockClient); + + const result = await addMemory('user123', 'test'); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should pass optional metadata', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + + await addMemory('user123', 'test', { source: 'chat' }); + + expect(mockClient.add).toHaveBeenCalledWith( + expect.any(Array), + expect.objectContaining({ metadata: { source: 'chat' } }), + ); + }); + + it('should return false when client is null', async () => { + _setMem0Available(true); + _setClient(null); + // No API key set, so getClient returns null + const result = await addMemory('user123', 'test'); + expect(result).toBe(false); + }); + }); + + describe('searchMemories', () => { + it('should return empty results when unavailable', async () => { + _setMem0Available(false); + const result = await searchMemories('user123', 'Rust'); + expect(result).toEqual({ memories: [], relations: [] }); + }); + + it('should search and return formatted memories with relations', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ + results: [ + { memory: 'User is learning Rust', score: 0.95 }, + { memory: 'User works at Google', score: 0.8 }, + ], + relations: [ + { + source: 'User', + source_type: 'person', + relationship: 'works at', + target: 'Google', + target_type: 'organization', + }, + ], + }), + }); + _setClient(mockClient); + + const result = await searchMemories('user123', 'What language?'); + expect(result.memories).toEqual([ + { id: '', memory: 'User is learning Rust', score: 0.95 }, + { id: '', memory: 'User works at Google', score: 0.8 }, + ]); + expect(result.relations).toHaveLength(1); + expect(result.relations[0].relationship).toBe('works at'); + + expect(mockClient.search).toHaveBeenCalledWith('What language?', { + user_id: 'user123', + app_id: 'bills-bot', + limit: 5, + enable_graph: true, + }); + }); + + it('should handle array response format', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue([{ memory: 'User loves TypeScript', score: 0.9 }]), + }); + _setClient(mockClient); + + const result = await searchMemories('user123', 'languages'); + expect(result.memories).toEqual([{ id: '', memory: 'User loves TypeScript', score: 0.9 }]); + expect(result.relations).toEqual([]); + }); + + it('should respect custom limit parameter', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ results: [], relations: [] }), + }); + _setClient(mockClient); + + await searchMemories('user123', 'test', 3); + + expect(mockClient.search).toHaveBeenCalledWith('test', expect.objectContaining({ limit: 3 })); + }); + + it('should return empty results on SDK error and mark unavailable', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockRejectedValue(new Error('API error')), + }); + _setClient(mockClient); + + const result = await searchMemories('user123', 'test'); + expect(result).toEqual({ memories: [], relations: [] }); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should handle text/content field variants', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ + results: [ + { text: 'via text field' }, + { content: 'via content field' }, + { memory: 'via memory field' }, + ], + }), + }); + _setClient(mockClient); + + const result = await searchMemories('user123', 'test'); + expect(result.memories[0]).toEqual({ id: '', memory: 'via text field', score: null }); + expect(result.memories[1]).toEqual({ id: '', memory: 'via content field', score: null }); + expect(result.memories[2]).toEqual({ id: '', memory: 'via memory field', score: null }); + }); + + it('should return empty results when client is null', async () => { + _setMem0Available(true); + _setClient(null); + const result = await searchMemories('user123', 'test'); + expect(result).toEqual({ memories: [], relations: [] }); + }); + }); + + describe('getMemories', () => { + it('should return empty array when unavailable', async () => { + _setMem0Available(false); + const result = await getMemories('user123'); + expect(result).toEqual([]); + }); + + it('should call client.getAll and return formatted memories', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + getAll: vi.fn().mockResolvedValue({ + results: [ + { id: 'mem-1', memory: 'Loves Rust' }, + { id: 'mem-2', memory: 'Works at Google' }, + ], + }), + }); + _setClient(mockClient); + + const result = await getMemories('user123'); + expect(result).toEqual([ + { id: 'mem-1', memory: 'Loves Rust' }, + { id: 'mem-2', memory: 'Works at Google' }, + ]); + + expect(mockClient.getAll).toHaveBeenCalledWith({ + user_id: 'user123', + app_id: 'bills-bot', + enable_graph: true, + }); + }); + + it('should handle array response format', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + getAll: vi.fn().mockResolvedValue([{ id: 'mem-1', memory: 'Test' }]), + }); + _setClient(mockClient); + + const result = await getMemories('user123'); + expect(result).toEqual([{ id: 'mem-1', memory: 'Test' }]); + }); + + it('should return empty array on SDK error and mark unavailable', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + getAll: vi.fn().mockRejectedValue(new Error('API error')), + }); + _setClient(mockClient); + + const result = await getMemories('user123'); + expect(result).toEqual([]); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should return empty array when client is null', async () => { + _setMem0Available(true); + _setClient(null); + const result = await getMemories('user123'); + expect(result).toEqual([]); + }); + }); + + describe('deleteAllMemories', () => { + it('should return false when unavailable', async () => { + _setMem0Available(false); + const result = await deleteAllMemories('user123'); + expect(result).toBe(false); + }); + + it('should call client.deleteAll and return true', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + + const result = await deleteAllMemories('user123'); + expect(result).toBe(true); + + expect(mockClient.deleteAll).toHaveBeenCalledWith({ + user_id: 'user123', + app_id: 'bills-bot', + }); + }); + + it('should return false on SDK error and mark unavailable', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + deleteAll: vi.fn().mockRejectedValue(new Error('API error')), + }); + _setClient(mockClient); + + const result = await deleteAllMemories('user123'); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should return false when client is null', async () => { + _setMem0Available(true); + _setClient(null); + const result = await deleteAllMemories('user123'); + expect(result).toBe(false); + }); + }); + + describe('deleteMemory', () => { + it('should return false when unavailable', async () => { + _setMem0Available(false); + const result = await deleteMemory('mem-1'); + expect(result).toBe(false); + }); + + it('should call client.delete with the memory ID', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + + const result = await deleteMemory('mem-42'); + expect(result).toBe(true); + + expect(mockClient.delete).toHaveBeenCalledWith('mem-42'); + }); + + it('should return false on SDK error and mark unavailable', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + delete: vi.fn().mockRejectedValue(new Error('Not found')), + }); + _setClient(mockClient); + + const result = await deleteMemory('nonexistent'); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should return false when client is null', async () => { + _setMem0Available(true); + _setClient(null); + const result = await deleteMemory('mem-1'); + expect(result).toBe(false); + }); + }); + + describe('formatRelations', () => { + it('should return empty string for null/undefined/empty relations', () => { + expect(formatRelations(null)).toBe(''); + expect(formatRelations(undefined)).toBe(''); + expect(formatRelations([])).toBe(''); + }); + + it('should format relations as readable lines', () => { + const relations = [ + { + source: 'Joseph', + source_type: 'person', + relationship: 'works as', + target: 'software engineer', + target_type: 'role', + }, + { + source: 'Joseph', + source_type: 'person', + relationship: 'lives in', + target: 'New York', + target_type: 'location', + }, + ]; + + const result = formatRelations(relations); + expect(result).toContain('Relationships:'); + expect(result).toContain('Joseph → works as → software engineer'); + expect(result).toContain('Joseph → lives in → New York'); + }); + }); + + describe('buildMemoryContext', () => { + it('should return empty string when unavailable', async () => { + _setMem0Available(false); + const result = await buildMemoryContext('user123', 'testuser', 'hello'); + expect(result).toBe(''); + }); + + it('should return empty string when user has opted out', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + isOptedOut.mockReturnValue(true); + + const result = await buildMemoryContext('user123', 'testuser', 'hello'); + expect(result).toBe(''); + expect(mockClient.search).not.toHaveBeenCalled(); + }); + + it('should return formatted context string with memories and relations', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ + results: [ + { memory: 'User is learning Rust', score: 0.95 }, + { memory: 'User works at Google', score: 0.8 }, + ], + relations: [ + { + source: 'testuser', + source_type: 'person', + relationship: 'works at', + target: 'Google', + target_type: 'organization', + }, + ], + }), + }); + _setClient(mockClient); + + const result = await buildMemoryContext('user123', 'testuser', 'tell me about Rust'); + expect(result).toContain('What you know about testuser'); + expect(result).toContain('- User is learning Rust'); + expect(result).toContain('- User works at Google'); + expect(result).toContain('Relationships:'); + expect(result).toContain('testuser → works at → Google'); + }); + + it('should return empty string when no memories or relations found', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ results: [], relations: [] }), + }); + _setClient(mockClient); + + const result = await buildMemoryContext('user123', 'testuser', 'random query'); + expect(result).toBe(''); + }); + + it('should return context with only relations when no memories found', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ + results: [], + relations: [ + { + source: 'testuser', + source_type: 'person', + relationship: 'likes', + target: 'programming', + target_type: 'interest', + }, + ], + }), + }); + _setClient(mockClient); + + const result = await buildMemoryContext('user123', 'testuser', 'hobbies'); + expect(result).toContain('Relationships:'); + expect(result).toContain('testuser → likes → programming'); + expect(result).not.toContain('What you know about'); + }); + + it('should return context with only memories when no relations found', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + search: vi.fn().mockResolvedValue({ + results: [{ memory: 'Likes cats', score: 0.9 }], + relations: [], + }), + }); + _setClient(mockClient); + + const result = await buildMemoryContext('user123', 'testuser', 'pets'); + expect(result).toContain('What you know about testuser'); + expect(result).toContain('- Likes cats'); + expect(result).not.toContain('Relationships:'); + }); + }); + + describe('extractAndStoreMemories', () => { + it('should return false when unavailable', async () => { + _setMem0Available(false); + const result = await extractAndStoreMemories('user123', 'testuser', 'hello', 'hi'); + expect(result).toBe(false); + }); + + it('should return false when user has opted out', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + isOptedOut.mockReturnValue(true); + + const result = await extractAndStoreMemories('user123', 'testuser', 'hello', 'hi'); + expect(result).toBe(false); + expect(mockClient.add).not.toHaveBeenCalled(); + }); + + it('should return false when autoExtract is disabled', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + getConfig.mockReturnValue({ + memory: { enabled: true, autoExtract: false }, + }); + + const result = await extractAndStoreMemories('user123', 'testuser', 'hello', 'hi'); + expect(result).toBe(false); + expect(mockClient.add).not.toHaveBeenCalled(); + }); + + it('should call client.add with conversation messages and graph enabled', async () => { + _setMem0Available(true); + const mockClient = createMockClient(); + _setClient(mockClient); + + const result = await extractAndStoreMemories( + 'user123', + 'testuser', + "I'm learning Rust", + 'Rust is awesome! What project are you working on?', + ); + expect(result).toBe(true); + + expect(mockClient.add).toHaveBeenCalledWith( + [ + { role: 'user', content: "testuser: I'm learning Rust" }, + { role: 'assistant', content: 'Rust is awesome! What project are you working on?' }, + ], + { + user_id: 'user123', + app_id: 'bills-bot', + enable_graph: true, + }, + ); + }); + + it('should return false on SDK failure and mark unavailable', async () => { + _setMem0Available(true); + const mockClient = createMockClient({ + add: vi.fn().mockRejectedValue(new Error('API error')), + }); + _setClient(mockClient); + + const result = await extractAndStoreMemories('user123', 'testuser', 'hi', 'hello'); + expect(result).toBe(false); + expect(isMemoryAvailable()).toBe(false); + }); + + it('should return false when client is null', async () => { + _setMem0Available(true); + _setClient(null); + const result = await extractAndStoreMemories('user123', 'testuser', 'hi', 'hello'); + expect(result).toBe(false); + }); + }); +}); diff --git a/tests/modules/optout.test.js b/tests/modules/optout.test.js new file mode 100644 index 00000000..2c94d526 --- /dev/null +++ b/tests/modules/optout.test.js @@ -0,0 +1,203 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +// Mock logger +vi.mock('../../src/logger.js', () => ({ + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), +})); + +// Mock db module +vi.mock('../../src/db.js', () => ({ + getPool: vi.fn(() => { + throw new Error('Database not initialized'); + }), +})); + +import { getPool } from '../../src/db.js'; +import { warn } from '../../src/logger.js'; +import { + _resetOptouts, + _setPool, + isOptedOut, + loadOptOuts, + toggleOptOut, +} from '../../src/modules/optout.js'; + +/** Helper to create a mock pool */ +function createMockPool() { + return { query: vi.fn() }; +} + +describe('optout module', () => { + let mockPool; + + beforeEach(() => { + vi.clearAllMocks(); + _resetOptouts(); + mockPool = createMockPool(); + _setPool(mockPool); + }); + + afterEach(() => { + _resetOptouts(); + }); + + describe('isOptedOut', () => { + it('should return false for users who have not opted out', () => { + expect(isOptedOut('user123')).toBe(false); + }); + + it('should return true for users who have opted out', async () => { + mockPool.query.mockResolvedValue({ rows: [] }); + await toggleOptOut('user123'); + expect(isOptedOut('user123')).toBe(true); + }); + + it('should return false for different users', async () => { + mockPool.query.mockResolvedValue({ rows: [] }); + await toggleOptOut('user123'); + expect(isOptedOut('user456')).toBe(false); + }); + }); + + describe('toggleOptOut', () => { + it('should opt out a user who is opted in', async () => { + mockPool.query.mockResolvedValue({ rows: [] }); + const result = await toggleOptOut('user123'); + expect(result).toEqual({ optedOut: true }); + expect(isOptedOut('user123')).toBe(true); + expect(mockPool.query).toHaveBeenCalledWith( + 'INSERT INTO memory_optouts (user_id) VALUES ($1) ON CONFLICT (user_id) DO NOTHING', + ['user123'], + ); + }); + + it('should opt in a user who is opted out', async () => { + mockPool.query.mockResolvedValue({ rows: [] }); + await toggleOptOut('user123'); // opt out + const result = await toggleOptOut('user123'); // opt back in + expect(result).toEqual({ optedOut: false }); + expect(isOptedOut('user123')).toBe(false); + expect(mockPool.query).toHaveBeenCalledWith('DELETE FROM memory_optouts WHERE user_id = $1', [ + 'user123', + ]); + }); + + it('should persist to database on each toggle', async () => { + mockPool.query.mockResolvedValue({ rows: [] }); + await toggleOptOut('user123'); + expect(mockPool.query).toHaveBeenCalledTimes(1); + + await toggleOptOut('user123'); + expect(mockPool.query).toHaveBeenCalledTimes(2); + }); + + it('should handle multiple users independently', async () => { + mockPool.query.mockResolvedValue({ rows: [] }); + await toggleOptOut('user1'); + await toggleOptOut('user2'); + + expect(isOptedOut('user1')).toBe(true); + expect(isOptedOut('user2')).toBe(true); + expect(isOptedOut('user3')).toBe(false); + + await toggleOptOut('user1'); // opt back in + expect(isOptedOut('user1')).toBe(false); + expect(isOptedOut('user2')).toBe(true); + }); + + it('should keep in-memory state when DB insert fails', async () => { + mockPool.query.mockRejectedValue(new Error('connection refused')); + const result = await toggleOptOut('user123'); + expect(result).toEqual({ optedOut: true }); + expect(isOptedOut('user123')).toBe(true); + expect(warn).toHaveBeenCalledWith( + 'Failed to persist opt-out to database', + expect.objectContaining({ userId: 'user123' }), + ); + }); + + it('should keep in-memory state when DB delete fails', async () => { + mockPool.query.mockResolvedValueOnce({ rows: [] }); // insert succeeds + await toggleOptOut('user123'); // opt out + + mockPool.query.mockRejectedValueOnce(new Error('connection refused')); + const result = await toggleOptOut('user123'); // opt back in + expect(result).toEqual({ optedOut: false }); + expect(isOptedOut('user123')).toBe(false); + expect(warn).toHaveBeenCalledWith( + 'Failed to delete opt-out from database', + expect.objectContaining({ userId: 'user123' }), + ); + }); + + it('should work without a pool (no DB available)', async () => { + _setPool(null); + // getPool already mocked to throw + const result = await toggleOptOut('user123'); + expect(result).toEqual({ optedOut: true }); + expect(isOptedOut('user123')).toBe(true); + }); + }); + + describe('loadOptOuts', () => { + it('should load opted-out users from database', async () => { + mockPool.query.mockResolvedValue({ + rows: [{ user_id: 'user1' }, { user_id: 'user2' }], + }); + + await loadOptOuts(); + + expect(isOptedOut('user1')).toBe(true); + expect(isOptedOut('user2')).toBe(true); + expect(isOptedOut('user3')).toBe(false); + expect(mockPool.query).toHaveBeenCalledWith('SELECT user_id FROM memory_optouts'); + }); + + it('should handle empty result set', async () => { + mockPool.query.mockResolvedValue({ rows: [] }); + + await loadOptOuts(); + + expect(isOptedOut('anyone')).toBe(false); + }); + + it('should handle database query failure gracefully', async () => { + mockPool.query.mockRejectedValue(new Error('relation does not exist')); + + await loadOptOuts(); + + expect(isOptedOut('anyone')).toBe(false); + expect(warn).toHaveBeenCalledWith( + 'Failed to load opt-outs from database', + expect.objectContaining({ error: 'relation does not exist' }), + ); + }); + + it('should handle no pool available gracefully', async () => { + _setPool(null); + // getPool already mocked to throw + + await loadOptOuts(); + + expect(isOptedOut('anyone')).toBe(false); + expect(warn).toHaveBeenCalledWith('Database not available, starting with empty opt-out set'); + }); + + it('should fall back to getPool when no injected pool', async () => { + _setPool(null); + const fallbackPool = createMockPool(); + fallbackPool.query.mockResolvedValue({ + rows: [{ user_id: 'user1' }], + }); + getPool.mockReturnValue(fallbackPool); + + await loadOptOuts(); + + expect(isOptedOut('user1')).toBe(true); + expect(getPool).toHaveBeenCalled(); + }); + }); +});