diff --git a/bun.lock b/bun.lock index 6c54ff2..86edecd 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,8 @@ "@electric-sql/pglite": "^0.3.11", "@elizaos/plugin-openai": "^1.5.16", "@elizaos/plugin-openrouter": "^1.5.14", + "@elizaos/plugin-solana": "^1.2.6", + "@elizaos/plugin-sql": "^1.6.3", "@types/bun": "^1.3.0", "@types/node": "^24.7.0", "prettier": "3.5.3", @@ -35,6 +37,8 @@ "@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="], + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + "@biomejs/biome": ["@biomejs/biome@2.3.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.0", "@biomejs/cli-darwin-x64": "2.3.0", "@biomejs/cli-linux-arm64": "2.3.0", "@biomejs/cli-linux-arm64-musl": "2.3.0", "@biomejs/cli-linux-x64": "2.3.0", "@biomejs/cli-linux-x64-musl": "2.3.0", "@biomejs/cli-win32-arm64": "2.3.0", "@biomejs/cli-win32-x64": "2.3.0" }, "bin": { "biome": "bin/biome" } }, "sha512-shdUY5H3S3tJVUWoVWo5ua+GdPW5lRHf+b0IwZ4OC1o2zOKQECZ6l2KbU6t89FNhtd3Qx5eg5N7/UsQWGQbAFw=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3cJVT0Z5pbTkoBmbjmDZTDFYxIkRcrs9sYVJbIBHU8E6qQxgXAaBfSVjjCreG56rfDuQBr43GzwzmaHPcu4vlw=="], @@ -55,6 +59,8 @@ "@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@electric-sql/pglite": ["@electric-sql/pglite@0.3.11", "", {}, "sha512-FJtjnEyez8XgmgyE5Ewmx89TGVN+75ZjykFoExApRIbJBMT4dsbsuZkF/YWLuymGDfGFHDACjvENPMEqg4FoWg=="], "@elizaos/core": ["@elizaos/core@1.6.3", "", { "dependencies": { "adze": "^2.2.5", "crypto-browserify": "^3.12.0", "dotenv": "17.2.3", "glob": "11.0.3", "handlebars": "^4.7.8", "langchain": "^0.3.35", "pdfjs-dist": "^5.2.133", "unique-names-generator": "4.7.1", "uuid": "13.0.0", "zod": "4.1.11" } }, "sha512-MB+hZuB/q3t4x3COQfZVxtjwjwJoXr5NBiqo2J/EM4O9igUiJO6N+ZDrdVZtBmS3qzKybzbmBz8ifzHZqNlP2A=="], @@ -63,6 +69,16 @@ "@elizaos/plugin-openrouter": ["@elizaos/plugin-openrouter@1.5.14", "", { "dependencies": { "@ai-sdk/openai": "^2.0.32", "@ai-sdk/ui-utils": "1.2.11", "@elizaos/core": "^1.6.1", "@openrouter/ai-sdk-provider": "^1.2.0", "ai": "^5.0.47", "undici": "^7.16.0" } }, "sha512-Tn5pLddJH6Mt8seAh5myYEOB9uihsFFD5SI8ZWBVMpbTWjp98mYxUSX24SMYmAveqP4CSNFCfUj1roNIJ9sGJQ=="], + "@elizaos/plugin-solana": ["@elizaos/plugin-solana@1.2.6", "", { "dependencies": { "@solana/spl-token": "0.4.14", "@solana/spl-token-metadata": "^0.1.6", "@solana/web3.js": "^1.98.0", "bignumber.js": "9.3.0", "bs58": "6.0.0", "tweetnacl": "^1.0.3" }, "peerDependencies": { "@elizaos/core": "latest", "@elizaos/service-interfaces": "latest", "form-data": "4.0.2", "whatwg-url": "7.1.0" } }, "sha512-VWp4u2jOkEOgqNNaVN8j3g2TWKwq56GZzIawFPsr6YsF4NuCiQOaWfmeq+edVxpze5LkkzVfC/KPeU19jjZDuA=="], + + "@elizaos/plugin-sql": ["@elizaos/plugin-sql@1.6.3", "", { "dependencies": { "@electric-sql/pglite": "^0.3.3", "@elizaos/core": "1.6.3", "dotenv": "^16.4.7", "drizzle-kit": "^0.31.1", "drizzle-orm": "^0.44.2", "pg": "^8.13.3", "uuid": "^11.0.5" } }, "sha512-e7iSieIEwRY6dkrg9KI6ts0AH9jKpJIFOgb5z5evTB6xHd2bkuR9hlz+VV7HiWzrRbCueU0BJyCejAdatxnXBA=="], + + "@elizaos/service-interfaces": ["@elizaos/service-interfaces@1.6.3", "", { "dependencies": { "@elizaos/core": "1.6.3" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-uuRzZcIwcjGe+TCTThMcMV3THA7MBOY3fGm02X88JCO3tfnOC2OmOjyNOTzy4mwbH/RcFHXTsf9WtiLeG4Ygow=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="], @@ -157,6 +173,10 @@ "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.80", "", { "os": "win32", "cpu": "x64" }, "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg=="], + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], + + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@1.2.0", "", { "peerDependencies": { "ai": "^5.0.0", "zod": "^3.24.1 || ^v4" } }, "sha512-stuIwq7Yb7DNmk3GuCtz+oS3nZOY4TXEV3V5KsknDGQN7Fpu3KRMQVWRc1J073xKdf0FC9EHOctSyzsACmp5Ag=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -207,10 +227,40 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg=="], + "@solana/buffer-layout": ["@solana/buffer-layout@4.0.1", "", { "dependencies": { "buffer": "~6.0.3" } }, "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA=="], + + "@solana/buffer-layout-utils": ["@solana/buffer-layout-utils@0.2.0", "", { "dependencies": { "@solana/buffer-layout": "^4.0.0", "@solana/web3.js": "^1.32.0", "bigint-buffer": "^1.1.5", "bignumber.js": "^9.0.1" } }, "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g=="], + + "@solana/codecs": ["@solana/codecs@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-data-structures": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/codecs-strings": "2.0.0-rc.1", "@solana/options": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ=="], + + "@solana/codecs-core": ["@solana/codecs-core@2.0.0-rc.1", "", { "dependencies": { "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ=="], + + "@solana/codecs-data-structures": ["@solana/codecs-data-structures@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog=="], + + "@solana/codecs-numbers": ["@solana/codecs-numbers@2.3.0", "", { "dependencies": { "@solana/codecs-core": "2.3.0", "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg=="], + + "@solana/codecs-strings": ["@solana/codecs-strings@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5" } }, "sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g=="], + + "@solana/errors": ["@solana/errors@2.3.0", "", { "dependencies": { "chalk": "^5.4.1", "commander": "^14.0.0" }, "peerDependencies": { "typescript": ">=5.3.3" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ=="], + + "@solana/options": ["@solana/options@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-data-structures": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/codecs-strings": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA=="], + + "@solana/spl-token": ["@solana/spl-token@0.4.14", "", { "dependencies": { "@solana/buffer-layout": "^4.0.0", "@solana/buffer-layout-utils": "^0.2.0", "@solana/spl-token-group": "^0.0.7", "@solana/spl-token-metadata": "^0.1.6", "buffer": "^6.0.3" }, "peerDependencies": { "@solana/web3.js": "^1.95.5" } }, "sha512-u09zr96UBpX4U685MnvQsNzlvw9TiY005hk1vJmJr7gMJldoPG1eYU5/wNEyOA5lkMLiR/gOi9SFD4MefOYEsA=="], + + "@solana/spl-token-group": ["@solana/spl-token-group@0.0.7", "", { "dependencies": { "@solana/codecs": "2.0.0-rc.1" }, "peerDependencies": { "@solana/web3.js": "^1.95.3" } }, "sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug=="], + + "@solana/spl-token-metadata": ["@solana/spl-token-metadata@0.1.6", "", { "dependencies": { "@solana/codecs": "2.0.0-rc.1" }, "peerDependencies": { "@solana/web3.js": "^1.95.3" } }, "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA=="], + + "@solana/web3.js": ["@solana/web3.js@1.98.4", "", { "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@solana/buffer-layout": "^4.0.1", "@solana/codecs-numbers": "^2.1.0", "agentkeepalive": "^4.5.0", "bn.js": "^5.2.1", "borsh": "^0.7.0", "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", "jayson": "^4.1.1", "node-fetch": "^2.7.0", "rpc-websockets": "^9.0.2", "superstruct": "^2.0.2" } }, "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], + "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="], @@ -221,7 +271,9 @@ "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], - "@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="], + "@types/uuid": ["@types/uuid@8.3.4", "", {}, "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="], + + "@types/ws": ["@types/ws@7.4.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.2.0", "", {}, "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="], @@ -231,6 +283,8 @@ "adze": ["adze@2.2.5", "", { "dependencies": { "@ungap/structured-clone": "1.2.0", "picocolors": "1.1.1" } }, "sha512-QK+1EdcehjO1IRR8Bd4L7jhpeav+Enrp/cRLOlpHMsc4pdFTAKI5RI3rHqCakIVzq1RVZXCIzykMcD31ipiHAQ=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + "ai": ["ai@5.0.80", "", { "dependencies": { "@ai-sdk/gateway": "2.0.1", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.12", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-g1o6pjxm1eTtyh295dRhsg0gvZaHFlSo2oruWrK2rIR7KafWEhNB2A2/aJ9hyPT9AMI8JnQJyto1Tl9DMqwc9w=="], "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -243,14 +297,26 @@ "asn1.js": ["asn1.js@4.10.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base-x": ["base-x@5.0.1", "", {}, "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "bigint-buffer": ["bigint-buffer@1.1.5", "", { "dependencies": { "bindings": "^1.3.0" } }, "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA=="], + + "bignumber.js": ["bignumber.js@9.3.0", "", {}, "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + "bn.js": ["bn.js@5.2.2", "", {}, "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="], + "borsh": ["borsh@0.7.0", "", { "dependencies": { "bn.js": "^5.2.0", "bs58": "^4.0.0", "text-encoding-utf-8": "^1.0.2" } }, "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA=="], + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="], @@ -265,8 +331,16 @@ "browserify-sign": ["browserify-sign@4.2.5", "", { "dependencies": { "bn.js": "^5.2.2", "browserify-rsa": "^4.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "elliptic": "^6.6.1", "inherits": "^2.0.4", "parse-asn1": "^5.1.9", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" } }, "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw=="], + "bs58": ["bs58@6.0.0", "", { "dependencies": { "base-x": "^5.0.0" } }, "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "buffer-xor": ["buffer-xor@1.0.3", "", {}, "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="], + "bufferutil": ["bufferutil@4.0.9", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw=="], + "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="], "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], @@ -291,6 +365,8 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], @@ -319,12 +395,18 @@ "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + "delay": ["delay@5.0.0", "", {}, "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "des.js": ["des.js@1.1.0", "", { "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg=="], "diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="], "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + "drizzle-kit": ["drizzle-kit@0.31.6", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-/B4e/4pwnx25QwD5xXgdpo1S+077a2VZdosXbItE/oNmUgQwZydGDz9qJYmnQl/b+5IX0rLfwRhrPnroGtrg8Q=="], + "drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -341,22 +423,40 @@ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "es6-promise": ["es6-promise@4.2.8", "", {}, "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="], + + "es6-promisify": ["es6-promisify@5.0.0", "", { "dependencies": { "es6-promise": "^4.0.3" } }, "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ=="], + "esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="], - "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], "evp_bytestokey": ["evp_bytestokey@1.0.3", "", { "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA=="], + "eyes": ["eyes@0.1.8", "", {}, "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="], + + "fast-stable-stringify": ["fast-stable-stringify@1.0.0", "", {}, "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag=="], + + "fastestsmallesttextencoderdecoder": ["fastestsmallesttextencoderdecoder@1.0.22", "", {}, "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -365,6 +465,8 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + "glob": ["glob@11.0.3", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -387,6 +489,10 @@ "hmac-drbg": ["hmac-drbg@1.0.1", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="], + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], @@ -399,8 +505,12 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isomorphic-ws": ["isomorphic-ws@4.0.1", "", { "peerDependencies": { "ws": "*" } }, "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="], + "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], + "jayson": ["jayson@4.2.0", "", { "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", "@types/ws": "^7.4.4", "commander": "^2.20.3", "delay": "^5.0.0", "es6-promisify": "^5.0.0", "eyes": "^0.1.8", "isomorphic-ws": "^4.0.1", "json-stringify-safe": "^5.0.1", "stream-json": "^1.9.1", "uuid": "^8.3.2", "ws": "^7.5.10" }, "bin": { "jayson": "bin/jayson.js" } }, "sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg=="], + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="], @@ -409,6 +519,8 @@ "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], "langchain": ["langchain@0.3.36", "", { "dependencies": { "@langchain/openai": ">=0.1.0 <0.7.0", "@langchain/textsplitters": ">=0.0.0 <0.2.0", "js-tiktoken": "^1.0.12", "js-yaml": "^4.1.0", "jsonpointer": "^5.0.1", "langsmith": "^0.3.67", "openapi-types": "^12.1.3", "p-retry": "4", "uuid": "^10.0.0", "yaml": "^2.2.1", "zod": "^3.25.32" }, "peerDependencies": { "@langchain/anthropic": "*", "@langchain/aws": "*", "@langchain/cerebras": "*", "@langchain/cohere": "*", "@langchain/core": ">=0.3.58 <0.4.0", "@langchain/deepseek": "*", "@langchain/google-genai": "*", "@langchain/google-vertexai": "*", "@langchain/google-vertexai-web": "*", "@langchain/groq": "*", "@langchain/mistralai": "*", "@langchain/ollama": "*", "@langchain/xai": "*", "axios": "*", "cheerio": "*", "handlebars": "^4.7.8", "peggy": "^3.0.2", "typeorm": "*" }, "optionalPeers": ["@langchain/anthropic", "@langchain/aws", "@langchain/cerebras", "@langchain/cohere", "@langchain/deepseek", "@langchain/google-genai", "@langchain/google-vertexai", "@langchain/google-vertexai-web", "@langchain/groq", "@langchain/mistralai", "@langchain/ollama", "@langchain/xai", "axios", "cheerio", "handlebars", "peggy", "typeorm"] }, "sha512-PqC19KChFF0QlTtYDFgfEbIg+SCnCXox29G8tY62QWfj9bOW7ew2kgWmPw5qoHLOTKOdQPvXET20/1Pdq8vAtQ=="], @@ -433,6 +545,10 @@ "miller-rabin": ["miller-rabin@4.0.1", "", { "dependencies": { "bn.js": "^4.0.0", "brorand": "^1.0.1" }, "bin": { "miller-rabin": "bin/miller-rabin" } }, "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA=="], + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], "minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="], @@ -455,6 +571,10 @@ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "openai": ["openai@5.12.2", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ=="], @@ -537,12 +657,16 @@ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], "ripemd160": ["ripemd160@2.0.3", "", { "dependencies": { "hash-base": "^3.1.2", "inherits": "^2.0.4" } }, "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA=="], "rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="], + "rpc-websockets": ["rpc-websockets@9.2.0", "", { "dependencies": { "@swc/helpers": "^0.5.11", "@types/uuid": "^8.3.4", "@types/ws": "^8.2.2", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "uuid": "^8.3.2", "ws": "^8.5.0" }, "optionalDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" } }, "sha512-DS/XHdPxplQTtNRKiBCRWGBJfjOk56W7fyFUpiYi9fSTWTzoEMbUkn3J4gB0IMniIEVeAGR1/rzFQogzD5MxvQ=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], @@ -563,8 +687,14 @@ "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], + + "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -577,8 +707,12 @@ "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], + "superstruct": ["superstruct@2.0.2", "", {}, "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "text-encoding-utf-8": ["text-encoding-utf-8@1.0.2", "", {}, "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="], + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], @@ -595,8 +729,12 @@ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="], + "tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="], + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -611,9 +749,11 @@ "unique-names-generator": ["unique-names-generator@4.7.1", "", {}, "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow=="], + "utf-8-validate": ["utf-8-validate@5.0.10", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], "webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], @@ -629,6 +769,8 @@ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], @@ -643,14 +785,46 @@ "@ai-sdk/ui-utils/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@elizaos/core/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], + + "@elizaos/plugin-sql/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + "@langchain/core/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "@langchain/core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@langchain/openai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ=="], + + "@solana/codecs-core/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/codecs-data-structures/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ=="], + + "@solana/codecs-data-structures/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/codecs-numbers/@solana/codecs-core": ["@solana/codecs-core@2.3.0", "", { "dependencies": { "@solana/errors": "2.3.0" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw=="], + + "@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ=="], + + "@solana/codecs-strings/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/errors/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], + + "@solana/options/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ=="], + + "@solana/options/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/web3.js/bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="], + "asn1.js/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], + "borsh/bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "create-ecdh/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], @@ -661,22 +835,42 @@ "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "jayson/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], + + "jayson/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "jayson/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "langchain/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "langchain/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "langsmith/@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="], + "langsmith/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "md5.js/hash-base": ["hash-base@3.1.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.1" } }, "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg=="], "miller-rabin/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], + "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "public-encrypt/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "ripemd160/hash-base": ["hash-base@3.1.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.1" } }, "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg=="], + "rpc-websockets/@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "rpc-websockets/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "rpc-websockets/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -699,6 +893,76 @@ "zod-to-json-schema/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "@solana/codecs-core/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/codecs-core/@solana/errors/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "@solana/codecs-data-structures/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/codecs-data-structures/@solana/errors/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "@solana/codecs-strings/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/codecs-strings/@solana/errors/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "@solana/codecs/@solana/codecs-numbers/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/options/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/options/@solana/errors/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "@solana/web3.js/bs58/base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], + + "borsh/bs58/base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], + + "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -711,6 +975,10 @@ "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@solana/codecs/@solana/codecs-numbers/@solana/errors/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "@solana/codecs/@solana/codecs-numbers/@solana/errors/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + "sucrase/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], } } diff --git a/package.json b/package.json index 3775378..d3a4dfb 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "@electric-sql/pglite": "^0.3.11", "@elizaos/plugin-openai": "^1.5.16", "@elizaos/plugin-openrouter": "^1.5.14", + "@elizaos/plugin-solana": "^1.2.6", + "@elizaos/plugin-sql": "^1.6.3", "@types/bun": "^1.3.0", "@types/node": "^24.7.0", "prettier": "3.5.3", @@ -54,7 +56,7 @@ "format:check": "prettier --check ./src", "check": "biome check --write .", "test": "elizaos test", - "test:unit": "bun test --bail src/__tests__/unit/categorize-actions.test.ts && bun test --bail src/__tests__/unit/collect-provider-data.test.ts && bun test --bail src/__tests__/unit/execute-analysis-actions.test.ts && bun test --bail src/__tests__/unit/select-relevant-data-actions.test.ts && bun test --bail src/__tests__/unit/generate-analysis.test.ts && bun test --bail src/__tests__/unit/generate-recommendations.test.ts && bun test --bail src/__tests__/unit/save-analysis.test.ts && bun test --bail src/__tests__/unit/process-decisions.test.ts && bun test --bail src/__tests__/unit/db-getters.test.ts", + "test:unit": "bun test --bail src/__tests__/unit/categorize-actions.test.ts && bun test --bail src/__tests__/unit/collect-provider-data.test.ts && bun test --bail src/__tests__/unit/execute-analysis-actions.test.ts && bun test --bail src/__tests__/unit/select-relevant-data-actions.test.ts && bun test --bail src/__tests__/unit/generate-analysis.test.ts && bun test --bail src/__tests__/unit/generate-recommendations.test.ts && bun test --bail src/__tests__/unit/save-analysis.test.ts && bun test --bail src/__tests__/unit/process-decisions.test.ts && bun test --bail src/__tests__/unit/db-getters.test.ts && bun test --bail src/__tests__/unit/wallet-manager.test.ts", "test:integration": "bun test --bail src/__tests__/integration/run-analysis.test.ts && bun test --bail src/__tests__/integration/api-routes.test.ts", "test:integration:llm": "USE_REAL_LLM=true bun run test:integration", "test:all": "bun run test:unit && bun run test:integration", diff --git a/src/__tests__/integration/api-routes.test.ts b/src/__tests__/integration/api-routes.test.ts index 35e8751..4090869 100644 --- a/src/__tests__/integration/api-routes.test.ts +++ b/src/__tests__/integration/api-routes.test.ts @@ -32,8 +32,8 @@ describe('API Routes - Integration Tests', () => { limitActions: useRealLLM ? 10 : undefined, }); - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + // Get service via service loading mechanism + service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService; // Setup LLM mock using fixture-based system setupLLMMock(runtime, { useFixtures: true }); @@ -194,6 +194,45 @@ describe('API Routes - Integration Tests', () => { }); }); + describe('POST /analysis - Wallet Balance Validation', () => { + it('should check wallet balance before running analysis', async () => { + // Import wallet utils + const { checkWalletBalance } = await import('../../utils/walletManager.js'); + + // Mock checkWalletBalance to verify it's called + // Note: In real scenario, wallet auto-creation happens in service.initialize() + // and balance check happens in route handler before calling runAnalysis + + // This test validates the integration flow: + // 1. Wallet should exist (created during initialize) + // 2. Balance check should pass (sufficient funds) + // 3. Analysis should run successfully + + const result = await service.runAnalysis(runtime.agentId); + expect(result).toBeDefined(); + expect(result.id).toBeDefined(); + }); + + it('should validate wallet utilities work with runtime', async () => { + // Import wallet utils + const { hasWallet, getWalletPublicKey } = await import('../../utils/walletManager.js'); + + // Check if wallet exists + const walletExists = hasWallet(runtime); + + if (walletExists) { + // If wallet exists, public key should be retrievable + const publicKey = getWalletPublicKey(runtime); + expect(publicKey).toBeDefined(); + expect(typeof publicKey).toBe('string'); + } else { + // If no wallet, public key should be null + const publicKey = getWalletPublicKey(runtime); + expect(publicKey).toBeNull(); + } + }); + }); + describe('GET /analysis/:analysisId/actions', () => { it('should return all actions for analysis', async () => { const actions = await service.getActionsByAnalysisId(testAnalysisId); diff --git a/src/__tests__/integration/run-analysis.test.ts b/src/__tests__/integration/run-analysis.test.ts index aaefb07..0dab9fe 100644 --- a/src/__tests__/integration/run-analysis.test.ts +++ b/src/__tests__/integration/run-analysis.test.ts @@ -29,9 +29,8 @@ describe('SendoWorkerService - runAnalysis (Integration)', () => { limitActions: useRealLLM ? 10 : undefined, }); - // Create service - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + // Get service via service loading mechanism + service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService; // Setup LLM mock using fixture-based system setupLLMMock(runtime, { useFixtures: true }); diff --git a/src/__tests__/unit/categorize-actions.test.ts b/src/__tests__/unit/categorize-actions.test.ts index d5422a3..4851bd6 100644 --- a/src/__tests__/unit/categorize-actions.test.ts +++ b/src/__tests__/unit/categorize-actions.test.ts @@ -1,18 +1,18 @@ /** - * Unit tests for SendoWorkerService.categorizeActions() + * Unit tests for ActionCategorizer * * Tests action categorization with REAL runtime and actions */ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'; -import { SendoWorkerService } from '../../services/sendoWorkerService'; +import { ActionCategorizer } from '../../services/analysis'; import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime'; import { setupLLMMock } from '../helpers/mock-llm'; -import type { IAgentRuntime, Action } from '@elizaos/core'; +import type { IAgentRuntime } from '@elizaos/core'; -describe('SendoWorkerService - categorizeActions', () => { +describe('ActionCategorizer - categorize', () => { let runtime: IAgentRuntime; - let service: SendoWorkerService; + let categorizer: ActionCategorizer; beforeAll(async () => { // Create REAL runtime with test actions @@ -22,9 +22,8 @@ describe('SendoWorkerService - categorizeActions', () => { withActionActions: true, // 2 ACTION actions }); - // Create service - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + // Create categorizer + categorizer = new ActionCategorizer(runtime); // Setup LLM mock using fixture-based system setupLLMMock(runtime, { @@ -38,7 +37,7 @@ describe('SendoWorkerService - categorizeActions', () => { }); it('should categorize all available actions', async () => { - const result = await service.categorizeActions(); + const result = await categorizer.categorize(); // Verify structure expect(result).toHaveProperty('dataActions'); @@ -58,12 +57,12 @@ describe('SendoWorkerService - categorizeActions', () => { }); it('should correctly categorize DATA actions', async () => { - const result = await service.categorizeActions(); + const result = await categorizer.categorize(); // Check DATA actions const dataActionNames = new Set(); for (const actions of result.dataActions.values()) { - actions.forEach((action) => dataActionNames.add(action.name)); + actions.forEach((action: any) => dataActionNames.add(action.name)); } expect(dataActionNames.has('GET_WALLET_BALANCE')).toBe(true); @@ -72,12 +71,12 @@ describe('SendoWorkerService - categorizeActions', () => { }); it('should correctly categorize ACTION actions', async () => { - const result = await service.categorizeActions(); + const result = await categorizer.categorize(); // Check ACTION actions const actionActionNames = new Set(); for (const actions of result.actionActions.values()) { - actions.forEach((action) => actionActionNames.add(action.name)); + actions.forEach((action: any) => actionActionNames.add(action.name)); } expect(actionActionNames.has('EXECUTE_SWAP')).toBe(true); @@ -85,7 +84,7 @@ describe('SendoWorkerService - categorizeActions', () => { }); it('should group actions by type', async () => { - const result = await service.categorizeActions(); + const result = await categorizer.categorize(); // DATA actions should be grouped expect(result.dataActions.size).toBeGreaterThan(0); @@ -106,7 +105,7 @@ describe('SendoWorkerService - categorizeActions', () => { }); it('should include classification metadata', async () => { - const result = await service.categorizeActions(); + const result = await categorizer.categorize(); // Check first classification has all required fields const classification = result.classifications[0]; @@ -126,13 +125,12 @@ describe('SendoWorkerService - categorizeActions', () => { it('should handle empty actions gracefully', async () => { // Create runtime with NO actions const emptyRuntime = await createTestRuntime({ testId: 'empty-test' }); - const emptyService = new SendoWorkerService(emptyRuntime); - await emptyService.initialize(emptyRuntime); + const emptyCategorizer = new ActionCategorizer(emptyRuntime); // Setup same LLM mock setupLLMMock(emptyRuntime, { useFixtures: true }); - const result = await emptyService.categorizeActions(); + const result = await emptyCategorizer.categorize(); expect(result.classifications).toHaveLength(0); expect(result.dataActions.size).toBe(0); diff --git a/src/__tests__/unit/collect-provider-data.test.ts b/src/__tests__/unit/collect-provider-data.test.ts index f508a09..d406b87 100644 --- a/src/__tests__/unit/collect-provider-data.test.ts +++ b/src/__tests__/unit/collect-provider-data.test.ts @@ -1,17 +1,17 @@ /** - * Unit tests for SendoWorkerService collectProviderData + * Unit tests for ProviderCollector * * Tests the collection of data from registered providers */ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'; -import { SendoWorkerService } from '../../services/sendoWorkerService'; +import { ProviderCollector } from '../../services/data'; import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime'; import type { IAgentRuntime } from '@elizaos/core'; -describe('SendoWorkerService - collectProviderData', () => { +describe('ProviderCollector - collect', () => { let runtime: IAgentRuntime; - let service: SendoWorkerService; + let collector: ProviderCollector; beforeAll(async () => { // Create runtime with providers @@ -20,8 +20,7 @@ describe('SendoWorkerService - collectProviderData', () => { withProviders: true, }); - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + collector = new ProviderCollector(runtime); }); afterAll(async () => { @@ -29,7 +28,7 @@ describe('SendoWorkerService - collectProviderData', () => { }); it('should collect data from all registered providers', async () => { - const results = await service.collectProviderData(); + const results = await collector.collect(); expect(results).toBeDefined(); expect(Array.isArray(results)).toBe(true); @@ -37,7 +36,7 @@ describe('SendoWorkerService - collectProviderData', () => { }); it('should include provider name, data, and timestamp', async () => { - const results = await service.collectProviderData(); + const results = await collector.collect(); for (const result of results) { expect(result).toHaveProperty('providerName'); @@ -49,7 +48,7 @@ describe('SendoWorkerService - collectProviderData', () => { }); it('should return valid ISO timestamp', async () => { - const results = await service.collectProviderData(); + const results = await collector.collect(); if (results.length > 0) { const timestamp = results[0].timestamp; @@ -65,10 +64,8 @@ describe('SendoWorkerService - collectProviderData', () => { withProviders: false, }); - const emptyService = new SendoWorkerService(emptyRuntime); - await emptyService.initialize(emptyRuntime); - - const results = await emptyService.collectProviderData(); + const emptyCollector = new ProviderCollector(emptyRuntime); + const results = await emptyCollector.collect(); expect(results).toBeDefined(); expect(Array.isArray(results)).toBe(true); @@ -78,28 +75,28 @@ describe('SendoWorkerService - collectProviderData', () => { }); it('should handle provider errors gracefully', async () => { - // This test ensures collectProviderData doesn't throw if a provider fails + // This test ensures collect doesn't throw if a provider fails // The runtime's composeState should handle errors internally - await expect(service.collectProviderData()).resolves.toBeDefined(); + await expect(collector.collect()).resolves.toBeDefined(); }); it('should collect data from multiple providers', async () => { - const results = await service.collectProviderData(); + const results = await collector.collect(); // We registered 2 providers in the test runtime expect(results.length).toBeGreaterThanOrEqual(2); // Check that provider names are unique - const providerNames = results.map(r => r.providerName); + const providerNames = results.map((r: any) => r.providerName); const uniqueNames = new Set(providerNames); expect(uniqueNames.size).toBe(providerNames.length); }); it('should include expected test providers', async () => { - const results = await service.collectProviderData(); + const results = await collector.collect(); - const providerNames = results.map(r => r.providerName); + const providerNames = results.map((r: any) => r.providerName); // Test runtime registers walletContext and timeContext providers expect(providerNames).toContain('walletContext'); @@ -107,15 +104,15 @@ describe('SendoWorkerService - collectProviderData', () => { }); it('should return consistent data structure on multiple calls', async () => { - const results1 = await service.collectProviderData(); - const results2 = await service.collectProviderData(); + const results1 = await collector.collect(); + const results2 = await collector.collect(); // Same number of providers expect(results1.length).toBe(results2.length); // Same provider names (order may differ) - const names1 = results1.map(r => r.providerName).sort(); - const names2 = results2.map(r => r.providerName).sort(); + const names1 = results1.map((r: any) => r.providerName).sort(); + const names2 = results2.map((r: any) => r.providerName).sort(); expect(names1).toEqual(names2); }); }); diff --git a/src/__tests__/unit/db-getters.test.ts b/src/__tests__/unit/db-getters.test.ts index a2c1582..4c5b99c 100644 --- a/src/__tests__/unit/db-getters.test.ts +++ b/src/__tests__/unit/db-getters.test.ts @@ -23,8 +23,7 @@ describe('SendoWorkerService - DB Getters', () => { testId: 'db-getters-test', }); - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService; // Create test data const db = (runtime as any).db; diff --git a/src/__tests__/unit/execute-analysis-actions.test.ts b/src/__tests__/unit/execute-analysis-actions.test.ts index 1ec32a2..274260a 100644 --- a/src/__tests__/unit/execute-analysis-actions.test.ts +++ b/src/__tests__/unit/execute-analysis-actions.test.ts @@ -1,18 +1,19 @@ /** - * Unit tests for SendoWorkerService.executeAnalysisActions() + * Unit tests for DataExecutor.execute() * * Tests parallel execution of DATA actions with dynamic triggers */ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'; -import { SendoWorkerService } from '../../services/sendoWorkerService'; +import { DataExecutor } from '../../services/data'; +import { ActionCategorizer } from '../../services/analysis'; import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime'; import { setupLLMMock } from '../helpers/mock-llm'; -import type { IAgentRuntime, Action } from '@elizaos/core'; +import type { IAgentRuntime, Action, UUID } from '@elizaos/core'; -describe('SendoWorkerService - executeAnalysisActions', () => { +describe('DataExecutor - execute', () => { let runtime: IAgentRuntime; - let service: SendoWorkerService; + let executor: DataExecutor; let dataActions: Action[]; beforeAll(async () => { @@ -23,9 +24,13 @@ describe('SendoWorkerService - executeAnalysisActions', () => { withActionActions: false, // No ACTION actions needed }); - // Create service - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + // Helper function to get agent world ID + const getAgentWorldId = (): UUID => { + return (runtime as any).agentId as UUID; + }; + + // Create executor + executor = new DataExecutor(runtime, getAgentWorldId); // Setup LLM mock using fixture-based system setupLLMMock(runtime, { @@ -34,7 +39,8 @@ describe('SendoWorkerService - executeAnalysisActions', () => { }); // First, categorize actions to populate dataActions map - const categorization = await service.categorizeActions(); + const categorizer = new ActionCategorizer(runtime); + const categorization = await categorizer.categorize(); // Extract all DATA actions from the map dataActions = []; @@ -48,18 +54,21 @@ describe('SendoWorkerService - executeAnalysisActions', () => { }); it('should execute all DATA actions in parallel', async () => { - const results = await service.executeAnalysisActions(dataActions); + const { results, roomIds } = await executor.execute(dataActions); // Should have executed all selected DATA actions (now we have many more) expect(results.length).toBeGreaterThan(3); // At least 3, now we have 18+ // All should be successful - const successCount = results.filter((r) => r.success).length; + const successCount = results.filter((r: any) => r.success).length; expect(successCount).toBe(results.length); // All should succeed + + // Should have created rooms for cleanup + expect(roomIds.length).toBe(results.length); }); it('should return AnalysisActionResult objects with correct structure', async () => { - const results = await service.executeAnalysisActions(dataActions); + const { results } = await executor.execute(dataActions); // Check first result const actionResult = results[0]; @@ -73,10 +82,10 @@ describe('SendoWorkerService - executeAnalysisActions', () => { }); it('should execute GET_WALLET_BALANCE action', async () => { - const results = await service.executeAnalysisActions(dataActions); + const { results } = await executor.execute(dataActions); // Find the wallet balance result - const walletResult = results.find((r) => r.actionType === 'GET_WALLET_BALANCE'); + const walletResult = results.find((r: any) => r.actionType === 'GET_WALLET_BALANCE'); expect(walletResult).toBeDefined(); expect(walletResult?.success).toBe(true); @@ -89,10 +98,10 @@ describe('SendoWorkerService - executeAnalysisActions', () => { }); it('should execute GET_MARKET_DATA action', async () => { - const results = await service.executeAnalysisActions(dataActions); + const { results } = await executor.execute(dataActions); // Find the market data result - const marketResult = results.find((r) => r.actionType === 'GET_MARKET_DATA'); + const marketResult = results.find((r: any) => r.actionType === 'GET_MARKET_DATA'); expect(marketResult).toBeDefined(); expect(marketResult?.success).toBe(true); @@ -104,10 +113,10 @@ describe('SendoWorkerService - executeAnalysisActions', () => { }); it('should execute ASSESS_PORTFOLIO_RISK action', async () => { - const results = await service.executeAnalysisActions(dataActions); + const { results } = await executor.execute(dataActions); // Find the risk assessment result - const riskResult = results.find((r) => r.actionType === 'ASSESS_PORTFOLIO_RISK'); + const riskResult = results.find((r: any) => r.actionType === 'ASSESS_PORTFOLIO_RISK'); expect(riskResult).toBeDefined(); expect(riskResult?.success).toBe(true); @@ -120,9 +129,9 @@ describe('SendoWorkerService - executeAnalysisActions', () => { }); it('should include all action types in results', async () => { - const results = await service.executeAnalysisActions(dataActions); + const { results } = await executor.execute(dataActions); - const actionTypes = results.map((r) => r.actionType); + const actionTypes = results.map((r: any) => r.actionType); expect(actionTypes).toContain('GET_WALLET_BALANCE'); expect(actionTypes).toContain('GET_MARKET_DATA'); @@ -131,14 +140,15 @@ describe('SendoWorkerService - executeAnalysisActions', () => { it('should handle empty DATA actions gracefully', async () => { // Pass empty array - const results = await service.executeAnalysisActions([]); + const { results, roomIds } = await executor.execute([]); expect(results).toHaveLength(0); + expect(roomIds).toHaveLength(0); }); it('should execute actions in parallel (performance check)', async () => { const startTime = Date.now(); - await service.executeAnalysisActions(dataActions); + await executor.execute(dataActions); const duration = Date.now() - startTime; // If executed sequentially, would take ~300ms (3 actions * 100ms each) diff --git a/src/__tests__/unit/generate-analysis.test.ts b/src/__tests__/unit/generate-analysis.test.ts index d94b0c1..9b7813e 100644 --- a/src/__tests__/unit/generate-analysis.test.ts +++ b/src/__tests__/unit/generate-analysis.test.ts @@ -1,19 +1,19 @@ /** - * Unit tests for SendoWorkerService.generateAnalysis() + * Unit tests for AnalysisGenerator.generate() * * Tests LLM-based analysis generation from action results and provider data */ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'; -import { SendoWorkerService } from '../../services/sendoWorkerService'; +import { AnalysisGenerator } from '../../services/analysis'; import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime'; import { setupLLMMock } from '../helpers/mock-llm'; import type { IAgentRuntime } from '@elizaos/core'; import type { AnalysisActionResult, ProviderDataResult } from '../../types/index'; -describe('SendoWorkerService - generateAnalysis', () => { +describe('AnalysisGenerator - generate', () => { let runtime: IAgentRuntime; - let service: SendoWorkerService; + let generator: AnalysisGenerator; beforeAll(async () => { // Create REAL runtime @@ -21,9 +21,8 @@ describe('SendoWorkerService - generateAnalysis', () => { testId: 'generate-analysis-test', }); - // Create service - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + // Create generator + generator = new AnalysisGenerator(runtime); // Setup LLM mock using fixtures setupLLMMock(runtime, { useFixtures: true }); @@ -44,7 +43,7 @@ describe('SendoWorkerService - generateAnalysis', () => { const providerData: ProviderDataResult[] = []; - const analysis = await service.generateAnalysis(analysisResults, providerData); + const analysis = await generator.generate(analysisResults, providerData); expect(analysis).toHaveProperty('walletOverview'); expect(analysis).toHaveProperty('marketConditions'); @@ -100,7 +99,7 @@ describe('SendoWorkerService - generateAnalysis', () => { const providerData: ProviderDataResult[] = []; - await service.generateAnalysis(analysisResults, providerData); + await generator.generate(analysisResults, providerData); // Prompt should include both action results expect(capturedPrompt).toContain('GET_WALLET_BALANCE'); @@ -136,7 +135,7 @@ describe('SendoWorkerService - generateAnalysis', () => { }, ]; - await service.generateAnalysis(analysisResults, providerData); + await generator.generate(analysisResults, providerData); // Prompt should include provider data expect(capturedPrompt).toContain('walletContext'); @@ -172,7 +171,7 @@ describe('SendoWorkerService - generateAnalysis', () => { const providerData: ProviderDataResult[] = []; - const analysis = await service.generateAnalysis(analysisResults, providerData); + const analysis = await generator.generate(analysisResults, providerData); // Should still generate analysis expect(analysis).toHaveProperty('walletOverview'); @@ -186,7 +185,7 @@ describe('SendoWorkerService - generateAnalysis', () => { const analysisResults: AnalysisActionResult[] = []; const providerData: ProviderDataResult[] = []; - const analysis = await service.generateAnalysis(analysisResults, providerData); + const analysis = await generator.generate(analysisResults, providerData); // Should still generate all sections expect(analysis).toHaveProperty('walletOverview'); diff --git a/src/__tests__/unit/generate-recommendations.test.ts b/src/__tests__/unit/generate-recommendations.test.ts index bc43da9..c4e31c8 100644 --- a/src/__tests__/unit/generate-recommendations.test.ts +++ b/src/__tests__/unit/generate-recommendations.test.ts @@ -1,19 +1,19 @@ /** - * Unit tests for SendoWorkerService.generateRecommendations() + * Unit tests for RecommendationGenerator.generate() * * Tests LLM-based recommendation generation for ACTION actions */ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'; -import { SendoWorkerService } from '../../services/sendoWorkerService'; +import { RecommendationGenerator } from '../../services/recommendation'; import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime'; import { setupLLMMock } from '../helpers/mock-llm'; import type { IAgentRuntime, Action } from '@elizaos/core'; import type { ActionActionType } from '../../types/index'; -describe('SendoWorkerService - generateRecommendations', () => { +describe('RecommendationGenerator - generate', () => { let runtime: IAgentRuntime; - let service: SendoWorkerService; + let generator: RecommendationGenerator; let actionActions: Map; beforeAll(async () => { @@ -23,9 +23,8 @@ describe('SendoWorkerService - generateRecommendations', () => { withActionActions: true, // 2 ACTION actions }); - // Create service - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + // Create generator + generator = new RecommendationGenerator(runtime); // Prepare actionActions map const actions = Array.from(runtime.actions.values()); @@ -55,7 +54,7 @@ describe('SendoWorkerService - generateRecommendations', () => { }; const analysisId = crypto.randomUUID(); - const recommendations = await service.generateRecommendations( + const recommendations = await generator.generate( analysisId, analysis, actionActions @@ -99,7 +98,7 @@ describe('SendoWorkerService - generateRecommendations', () => { opportunities: 'Test', }; - const recommendations = await service.generateRecommendations( + const recommendations = await generator.generate( crypto.randomUUID(), analysis, actionActions @@ -128,8 +127,8 @@ describe('SendoWorkerService - generateRecommendations', () => { opportunities: 'Test', }; - await service.generateRecommendations( - crypto.randomUUID(), + await generator.generate( + crypto.randomUUID() as any, analysis, actionActions ); @@ -172,7 +171,7 @@ describe('SendoWorkerService - generateRecommendations', () => { opportunities: 'Test', }; - const recommendations = await service.generateRecommendations( + const recommendations = await generator.generate( crypto.randomUUID(), analysis, actionActions @@ -180,7 +179,7 @@ describe('SendoWorkerService - generateRecommendations', () => { // Should filter out null (failed) recommendations expect(recommendations.length).toBeGreaterThan(0); - expect(recommendations.every((r) => r !== null)).toBe(true); + expect(recommendations.every((r: any) => r !== null)).toBe(true); }); it('should handle action type processing errors gracefully', async () => { @@ -201,7 +200,7 @@ describe('SendoWorkerService - generateRecommendations', () => { opportunities: 'Test', }; - const recommendations = await service.generateRecommendations( + const recommendations = await generator.generate( crypto.randomUUID(), analysis, actionActions @@ -223,7 +222,7 @@ describe('SendoWorkerService - generateRecommendations', () => { }; const analysisId = crypto.randomUUID(); - const recommendations = await service.generateRecommendations( + const recommendations = await generator.generate( analysisId, analysis, actionActions @@ -257,7 +256,7 @@ describe('SendoWorkerService - generateRecommendations', () => { opportunities: 'Test', }; - const recommendations = await service.generateRecommendations( + const recommendations = await generator.generate( crypto.randomUUID(), analysis, emptyMap diff --git a/src/__tests__/unit/process-decisions.test.ts b/src/__tests__/unit/process-decisions.test.ts index 62c8b0b..bfcc647 100644 --- a/src/__tests__/unit/process-decisions.test.ts +++ b/src/__tests__/unit/process-decisions.test.ts @@ -22,8 +22,7 @@ describe('SendoWorkerService - processDecisions', () => { testId: 'process-decisions-test', }); - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService; // Create test data directly in DB const db = (runtime as any).db; diff --git a/src/__tests__/unit/save-analysis.test.ts b/src/__tests__/unit/save-analysis.test.ts index 737abd6..8f2b688 100644 --- a/src/__tests__/unit/save-analysis.test.ts +++ b/src/__tests__/unit/save-analysis.test.ts @@ -23,8 +23,7 @@ describe('SendoWorkerService - saveAnalysis (DB persistence)', () => { }); // Create service - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + service = await runtime.getServiceLoadPromise(SendoWorkerService.serviceType as any) as SendoWorkerService; // Setup LLM mock using fixture-based system setupLLMMock(runtime, { useFixtures: true }); diff --git a/src/__tests__/unit/select-relevant-data-actions.test.ts b/src/__tests__/unit/select-relevant-data-actions.test.ts index 0de018c..2c280b0 100644 --- a/src/__tests__/unit/select-relevant-data-actions.test.ts +++ b/src/__tests__/unit/select-relevant-data-actions.test.ts @@ -1,19 +1,19 @@ /** - * Unit tests for SendoWorkerService.selectRelevantDataActions() + * Unit tests for DataSelector.select() * * Tests LLM-based selection of relevant DATA actions */ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'; -import { SendoWorkerService } from '../../services/sendoWorkerService'; +import { DataSelector } from '../../services/data'; import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime'; import { setupLLMMock } from '../helpers/mock-llm'; import type { IAgentRuntime, Action } from '@elizaos/core'; import type { ProviderDataResult } from '../../types/index'; -describe('SendoWorkerService - selectRelevantDataActions', () => { +describe('DataSelector - select', () => { let runtime: IAgentRuntime; - let service: SendoWorkerService; + let selector: DataSelector; let dataActions: Map; beforeAll(async () => { @@ -23,9 +23,8 @@ describe('SendoWorkerService - selectRelevantDataActions', () => { withDataActions: true, // 3 DATA actions }); - // Create service - service = new SendoWorkerService(runtime); - await service.initialize(runtime); + // Create selector + selector = new DataSelector(runtime); // Prepare dataActions map (grouped by type) dataActions = new Map(); @@ -59,11 +58,11 @@ describe('SendoWorkerService - selectRelevantDataActions', () => { }, ]; - const selected = await service.selectRelevantDataActions(dataActions, providerData); + const selected = await selector.select(dataActions, providerData); // Fixtures select all DATA actions by default expect(selected.length).toBeGreaterThan(0); - expect(selected.every((a) => a !== null)).toBe(true); + expect(selected.every((a: any) => a !== null)).toBe(true); }); it('should return empty array if no actions are relevant', async () => { @@ -76,7 +75,7 @@ describe('SendoWorkerService - selectRelevantDataActions', () => { }); const providerData: ProviderDataResult[] = []; - const selected = await service.selectRelevantDataActions(dataActions, providerData); + const selected = await selector.select(dataActions, providerData); expect(selected.length).toBe(0); }); @@ -93,7 +92,7 @@ describe('SendoWorkerService - selectRelevantDataActions', () => { }); const providerData: ProviderDataResult[] = []; - const selected = await service.selectRelevantDataActions(dataActions, providerData); + const selected = await selector.select(dataActions, providerData); // Should return empty array on error (not throw) expect(selected.length).toBe(0); @@ -113,7 +112,7 @@ describe('SendoWorkerService - selectRelevantDataActions', () => { }); const providerData: ProviderDataResult[] = []; - await service.selectRelevantDataActions(dataActions, providerData); + await selector.select(dataActions, providerData); // Should have called LLM for all 3 action types expect(callOrder.length).toBe(3); @@ -139,7 +138,7 @@ describe('SendoWorkerService - selectRelevantDataActions', () => { }, ]; - await service.selectRelevantDataActions(dataActions, providerData); + await selector.select(dataActions, providerData); // Prompt should include provider data expect(capturedPrompt).toContain('walletContext'); @@ -150,7 +149,7 @@ describe('SendoWorkerService - selectRelevantDataActions', () => { const emptyMap = new Map(); const providerData: ProviderDataResult[] = []; - const selected = await service.selectRelevantDataActions(emptyMap, providerData); + const selected = await selector.select(emptyMap, providerData); expect(selected.length).toBe(0); }); diff --git a/src/__tests__/unit/wallet-manager.test.ts b/src/__tests__/unit/wallet-manager.test.ts new file mode 100644 index 0000000..7a93bdc --- /dev/null +++ b/src/__tests__/unit/wallet-manager.test.ts @@ -0,0 +1,374 @@ +/** + * Unit tests for walletManager utilities + * + * Tests wallet creation, balance checking, and persistence + */ + +import { describe, it, expect, beforeAll, afterAll, mock } from 'bun:test'; +import { + ensureSolanaWallet, + checkWalletBalance, + getWalletPublicKey, + hasWallet, +} from '../../utils/walletManager'; +import { createTestRuntime, cleanupTestRuntime } from '../helpers/test-runtime'; +import type { IAgentRuntime } from '@elizaos/core'; + +describe('walletManager - Basic Utilities', () => { + let runtime: IAgentRuntime; + + beforeAll(async () => { + // Create runtime without wallet + runtime = await createTestRuntime({ + testId: 'wallet-manager-test', + }); + }); + + afterAll(async () => { + await cleanupTestRuntime(runtime); + }); + + describe('hasWallet', () => { + it('should return false when no wallet is configured', () => { + expect(hasWallet(runtime)).toBe(false); + }); + + it('should return true when wallet is configured', () => { + // Configure wallet + runtime.setSetting('SOLANA_PUBLIC_KEY', 'test-public-key', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private-key', true); + + expect(hasWallet(runtime)).toBe(true); + + // Cleanup + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should return false when only public key is configured', () => { + runtime.setSetting('SOLANA_PUBLIC_KEY', 'test-public-key', false); + expect(hasWallet(runtime)).toBe(false); + + // Cleanup + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + }); + + it('should return false when only private key is configured', () => { + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private-key', true); + expect(hasWallet(runtime)).toBe(false); + + // Cleanup + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + }); + + describe('getWalletPublicKey', () => { + it('should return null when no wallet is configured', () => { + expect(getWalletPublicKey(runtime)).toBeNull(); + }); + + it('should return public key when wallet is configured', () => { + const testPublicKey = 'TestPublicKey123456789'; + runtime.setSetting('SOLANA_PUBLIC_KEY', testPublicKey, false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private-key', true); + + const publicKey = getWalletPublicKey(runtime); + expect(publicKey).toBe(testPublicKey); + + // Cleanup + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + }); +}); + +describe('walletManager - ensureSolanaWallet', () => { + let runtime: IAgentRuntime; + + beforeAll(async () => { + runtime = await createTestRuntime({ + testId: 'ensure-wallet-test', + }); + }); + + afterAll(async () => { + await cleanupTestRuntime(runtime); + }); + + it('should return existing wallet if already configured', async () => { + const testPublicKey = 'ExistingPublicKey123'; + runtime.setSetting('SOLANA_PUBLIC_KEY', testPublicKey, false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'existing-private-key', true); + + const resultPublicKey = await ensureSolanaWallet(runtime); + + expect(resultPublicKey).toBe(testPublicKey); + + // Cleanup + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should throw error if SOLANA_SERVICE is not available', async () => { + // Ensure no wallet configured + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + + await expect(ensureSolanaWallet(runtime)).rejects.toThrow( + 'SOLANA_SERVICE required but not available' + ); + }); + + it('should create new wallet when none exists', async () => { + // Ensure no wallet configured + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + + // Mock wallet data + const mockPublicKey = 'NewMockPublicKey' + Date.now(); + const mockPrivateKey = 'NewMockPrivateKey' + Date.now(); + + // Mock createWallet + const mockCreateWallet = mock(async () => ({ + publicKey: mockPublicKey, + privateKey: mockPrivateKey, + })); + + const mockService = { createWallet: mockCreateWallet }; + + // Mock getService and updateAgent + const originalGetService = runtime.getService; + const originalUpdateAgent = runtime.updateAgent; + let updateAgentCalled = false; + let updateAgentData: any = null; + + runtime.getService = ((serviceName: string) => { + if (serviceName === 'SOLANA_SERVICE') return mockService; + return originalGetService.call(runtime, serviceName); + }) as any; + + runtime.updateAgent = mock(async (_agentId: any, data: any) => { + updateAgentCalled = true; + updateAgentData = data; + return true; + }) as any; + + // Create wallet + const resultPublicKey = await ensureSolanaWallet(runtime); + + // Verify wallet was created + expect(resultPublicKey).toBe(mockPublicKey); + expect(mockCreateWallet).toHaveBeenCalled(); + + // Verify settings were updated + expect(runtime.getSetting('SOLANA_PUBLIC_KEY')).toBe(mockPublicKey); + expect(runtime.getSetting('SOLANA_PRIVATE_KEY')).toBe(mockPrivateKey); + + // Verify updateAgent was called for persistence + expect(updateAgentCalled).toBe(true); + expect(updateAgentData).toBeDefined(); + expect(updateAgentData.settings).toBeDefined(); + expect(updateAgentData.updatedAt).toBeDefined(); + + // Cleanup + runtime.getService = originalGetService; + runtime.updateAgent = originalUpdateAgent; + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should be idempotent - calling twice returns same wallet', async () => { + const testPublicKey = 'IdempotentPublicKey123'; + runtime.setSetting('SOLANA_PUBLIC_KEY', testPublicKey, false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'idempotent-private-key', true); + + // Mock service (should NOT be called) + const mockCreateWallet = mock(async () => ({ + publicKey: 'ShouldNotBeCreated', + privateKey: 'ShouldNotBeCreated', + })); + + const mockService = { createWallet: mockCreateWallet }; + const originalGetService = runtime.getService; + runtime.getService = ((serviceName: string) => { + if (serviceName === 'SOLANA_SERVICE') return mockService; + return originalGetService.call(runtime, serviceName); + }) as any; + + // Call twice + const result1 = await ensureSolanaWallet(runtime); + const result2 = await ensureSolanaWallet(runtime); + + // Both should return same wallet + expect(result1).toBe(testPublicKey); + expect(result2).toBe(testPublicKey); + + // createWallet should NOT have been called + expect(mockCreateWallet).not.toHaveBeenCalled(); + + // Cleanup + runtime.getService = originalGetService; + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should handle createWallet failure gracefully', async () => { + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + + // Mock createWallet that throws error + const mockCreateWallet = mock(async () => { + throw new Error('Network error: Failed to connect to Solana RPC'); + }); + + const mockService = { createWallet: mockCreateWallet }; + const originalGetService = runtime.getService; + runtime.getService = ((serviceName: string) => { + if (serviceName === 'SOLANA_SERVICE') return mockService; + return originalGetService.call(runtime, serviceName); + }) as any; + + // Should propagate the error + expect(ensureSolanaWallet(runtime)).rejects.toThrow('Network error'); + + // Cleanup + runtime.getService = originalGetService; + }); +}); + +describe('walletManager - checkWalletBalance', () => { + let runtime: IAgentRuntime; + + beforeAll(async () => { + runtime = await createTestRuntime({ + testId: 'check-balance-test', + }); + }); + + afterAll(async () => { + await cleanupTestRuntime(runtime); + }); + + it('should throw error if no wallet configured', async () => { + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + + await expect(checkWalletBalance(runtime)).rejects.toThrow('No wallet configured'); + }); + + it('should throw error if SOLANA_SERVICE not available', async () => { + runtime.setSetting('SOLANA_PUBLIC_KEY', 'test-key', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private', true); + + await expect(checkWalletBalance(runtime)).rejects.toThrow( + 'SOLANA_SERVICE required but not available' + ); + + // Cleanup + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should return balance info when balance is sufficient', async () => { + const testPublicKey = 'TestPublicKeyForBalance'; + runtime.setSetting('SOLANA_PUBLIC_KEY', testPublicKey, false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private', true); + + // Create mock service with getBalance method + const mockGetBalance = mock(async () => 1.5); + const mockService = { getBalance: mockGetBalance }; + + // Mock getService to return our mock service + const originalGetService = runtime.getService; + runtime.getService = ((serviceName: string) => { + if (serviceName === 'SOLANA_SERVICE') return mockService; + return originalGetService.call(runtime, serviceName); + }) as any; + + const result = await checkWalletBalance(runtime); + + expect(result.hasBalance).toBe(true); + expect(result.balance).toBe(1.5); + expect(result.publicKey).toBe(testPublicKey); + + // Verify getBalance was called + expect(mockGetBalance).toHaveBeenCalledWith('SOL', testPublicKey); + + // Cleanup + runtime.getService = originalGetService; + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should return hasBalance false when balance < 0.01 SOL', async () => { + const mockGetBalance = mock(async () => 0.005); + const mockService = { getBalance: mockGetBalance }; + + runtime.setSetting('SOLANA_PUBLIC_KEY', 'test-key', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private', true); + + const originalGetService = runtime.getService; + runtime.getService = ((serviceName: string) => { + if (serviceName === 'SOLANA_SERVICE') return mockService; + return originalGetService.call(runtime, serviceName); + }) as any; + + const result = await checkWalletBalance(runtime); + + expect(result.hasBalance).toBe(false); + expect(result.balance).toBe(0.005); + + // Cleanup + runtime.getService = originalGetService; + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should return hasBalance true when balance = 0.01 SOL (edge case)', async () => { + const mockGetBalance = mock(async () => 0.01); + const mockService = { getBalance: mockGetBalance }; + + runtime.setSetting('SOLANA_PUBLIC_KEY', 'test-key', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private', true); + + const originalGetService = runtime.getService; + runtime.getService = ((serviceName: string) => { + if (serviceName === 'SOLANA_SERVICE') return mockService; + return originalGetService.call(runtime, serviceName); + }) as any; + + const result = await checkWalletBalance(runtime); + + expect(result.hasBalance).toBe(true); + expect(result.balance).toBe(0.01); + + // Cleanup + runtime.getService = originalGetService; + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); + + it('should return hasBalance false when balance is 0', async () => { + const mockGetBalance = mock(async () => 0); + const mockService = { getBalance: mockGetBalance }; + + runtime.setSetting('SOLANA_PUBLIC_KEY', 'test-key', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', 'test-private', true); + + const originalGetService = runtime.getService; + runtime.getService = ((serviceName: string) => { + if (serviceName === 'SOLANA_SERVICE') return mockService; + return originalGetService.call(runtime, serviceName); + }) as any; + + const result = await checkWalletBalance(runtime); + + expect(result.hasBalance).toBe(false); + expect(result.balance).toBe(0); + + // Cleanup + runtime.getService = originalGetService; + runtime.setSetting('SOLANA_PUBLIC_KEY', '', false); + runtime.setSetting('SOLANA_PRIVATE_KEY', '', true); + }); +}); \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts index 59154c4..7f5f2bc 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,6 +1,7 @@ import type { Route, IAgentRuntime } from '@elizaos/core'; import { logger } from '@elizaos/core'; import { SendoWorkerService } from '../services/sendoWorkerService.js'; +import { checkWalletBalance } from '../utils/walletManager.js'; // ============================================ // HELPER FUNCTIONS @@ -141,8 +142,10 @@ async function getActionHandler(req: any, res: any, runtime: IAgentRuntime): Pro /** * POST /worker/analysis * Run a complete analysis for the current agent + * Requires wallet with minimum 0.01 SOL balance + * Returns immediately with 201 - analysis runs in background */ -async function runAnalysisHandler(req: any, res: any, runtime: IAgentRuntime): Promise { +async function runAnalysisHandler(_req: any, res: any, runtime: IAgentRuntime): Promise { const agentId = runtime.agentId; const workerService = runtime.getService('sendo_worker'); @@ -150,18 +153,43 @@ async function runAnalysisHandler(req: any, res: any, runtime: IAgentRuntime): P return sendError(res, 500, 'SERVICE_NOT_FOUND', 'SendoWorkerService not found'); } + // Check wallet balance before running analysis try { - // Run complete analysis workflow - const result = await workerService.runAnalysis(agentId); + const walletStatus = await checkWalletBalance(runtime); + + if (!walletStatus.hasBalance) { + return sendError( + res, + 402, + 'INSUFFICIENT_BALANCE', + `Wallet has insufficient balance. Current: ${walletStatus.balance} SOL, Required: 0.01 SOL minimum`, + `Please fund wallet: ${walletStatus.publicKey}` + ); + } - sendSuccess(res, { - message: 'Analysis completed successfully', - analysis: result, - }, 201); - } catch (error: any) { - logger.error('[Route] Failed to run analysis:', error); - sendError(res, 500, 'ANALYSIS_ERROR', 'Failed to run analysis', error.message); + logger.info(`[Route] Wallet balance check passed: ${walletStatus.balance} SOL (${walletStatus.publicKey})`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error('[Route] Failed to check wallet balance:', errorMessage); + return sendError(res, 500, 'WALLET_CHECK_FAILED', 'Failed to verify wallet balance', errorMessage); } + + // Return immediately with 201 - analysis runs in background + sendSuccess(res, { + message: 'Analysis started successfully', + status: 'processing', + agentId, + }, 201); + + // Run analysis in background (don't await) + workerService.runAnalysis(agentId) + .then((result) => { + logger.info(`[Route] ✅ Background analysis completed for agent ${agentId}: ${result.id}`); + }) + .catch((error) => { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`[Route] ❌ Background analysis failed for agent ${agentId}:`, errorMessage); + }); } /** diff --git a/src/services/analysis/categorizer.ts b/src/services/analysis/categorizer.ts new file mode 100644 index 0000000..d75f5bc --- /dev/null +++ b/src/services/analysis/categorizer.ts @@ -0,0 +1,107 @@ +import { IAgentRuntime, logger, ModelType, type Action } from '@elizaos/core'; +import type { + ActionClassification, + ActionCategorizationResponse, + DataActionType, + ActionActionType, +} from '../../types/index'; +import { actionCategorizationSchema } from '../../types/index'; +import { actionCategorizationPrompt } from '../../templates/index'; + +/** + * ActionCategorizer + * + * Categorizes actions into DATA (retrieval) or ACTION (execution) types using LLM. + */ +export class ActionCategorizer { + constructor(private runtime: IAgentRuntime) {} + + /** + * Categorize all available actions + * @returns Grouped actions and classifications + */ + async categorize(): Promise<{ + dataActions: Map; + actionActions: Map; + classifications: ActionClassification[]; + }> { + logger.info('[ActionCategorizer] Categorizing all available actions...'); + + // Get all actions from runtime + const allActions = Array.from(this.runtime.actions.values()); + logger.info(`[ActionCategorizer] Found ${allActions.length} actions to categorize`); + + // Process each action in parallel + const classifications = await Promise.all( + allActions.map((action) => this.categorizeAction(action)) + ); + + // Group actions by category and type + const dataActions = new Map(); + const actionActions = new Map(); + + for (const classification of classifications) { + if (classification.category === 'DATA') { + const typeActions = dataActions.get(classification.actionType) || []; + typeActions.push(classification.action); + dataActions.set(classification.actionType, typeActions); + } else if (classification.category === 'ACTION') { + const actionType = classification.actionType as ActionActionType; + const typeActions = actionActions.get(actionType) || []; + typeActions.push(classification.action); + actionActions.set(actionType, typeActions); + } + } + + logger.info( + `[ActionCategorizer] Categorization complete: ${dataActions.size} DATA types, ${actionActions.size} ACTION types` + ); + + return { dataActions, actionActions, classifications }; + } + + /** + * Categorize a single action using LLM + * @param action - The action to categorize + * @returns Classification result + */ + private async categorizeAction(action: Action): Promise { + const response = (await this.runtime.useModel(ModelType.OBJECT_SMALL, { + prompt: actionCategorizationPrompt({ action }), + schema: actionCategorizationSchema, + temperature: 0.1, + })) as ActionCategorizationResponse; + + // Extract plugin name from action + const pluginName = this.extractPluginName(action); + + return { + action, + category: response.category, + actionType: response.actionType as DataActionType | ActionActionType, + confidence: response.confidence, + reasoning: response.reasoning, + pluginName, + }; + } + + /** + * Extract plugin name from action + * @param action - The action + * @returns Plugin name or 'unknown' + */ + private extractPluginName(action: Action): string { + // Try to extract from action metadata + if ((action as any).plugin) { + return (action as any).plugin; + } + + // Fallback: try to extract from action name if it has a prefix + const nameParts = action.name.split(':'); + if (nameParts.length > 1) { + return nameParts[0]; + } + + return 'unknown'; + } +} diff --git a/src/services/analysis/generator.ts b/src/services/analysis/generator.ts new file mode 100644 index 0000000..c3ff255 --- /dev/null +++ b/src/services/analysis/generator.ts @@ -0,0 +1,53 @@ +import { IAgentRuntime, logger, ModelType } from '@elizaos/core'; +import type { AnalysisActionResult, ProviderDataResult } from '../../types/index'; +import { generateAnalysisSchema } from '../../types/index'; +import { generateAnalysisPrompt } from '../../templates/index'; + +/** + * AnalysisGenerator + * + * Generates comprehensive analysis using LLM based on DATA action results and provider data. + * Produces four sections: wallet overview, market conditions, risk assessment, and opportunities. + */ +export class AnalysisGenerator { + constructor(private runtime: IAgentRuntime) {} + + /** + * Generate comprehensive analysis using LLM + * @param analysisResults - Results from executed DATA actions + * @param providerData - Data collected from providers + * @returns Analysis with four sections + */ + async generate( + analysisResults: AnalysisActionResult[], + providerData: ProviderDataResult[] + ): Promise<{ + walletOverview: string; + marketConditions: string; + riskAssessment: string; + opportunities: string; + }> { + logger.info('[AnalysisGenerator] Generating analysis with LLM...'); + + // Extract plugin names from successful results + const pluginsUsed = [ + ...new Set([ + ...analysisResults.filter((r) => r.success).map((r) => r.actionType.split(':')[0] || 'unknown'), + ...providerData.map((p) => p.providerName), + ]), + ]; + + const analysis = await this.runtime.useModel(ModelType.OBJECT_LARGE, { + prompt: generateAnalysisPrompt({ + analysisResults, + providerData, + pluginsUsed, + }), + schema: generateAnalysisSchema, + temperature: 0.7, + }); + + logger.info('[AnalysisGenerator] Analysis generated successfully'); + return analysis; + } +} diff --git a/src/services/analysis/index.ts b/src/services/analysis/index.ts new file mode 100644 index 0000000..65c1e7c --- /dev/null +++ b/src/services/analysis/index.ts @@ -0,0 +1,2 @@ +export { ActionCategorizer } from './categorizer'; +export { AnalysisGenerator } from './generator'; diff --git a/src/services/data/dataExecutor.ts b/src/services/data/dataExecutor.ts new file mode 100644 index 0000000..c9cdc8b --- /dev/null +++ b/src/services/data/dataExecutor.ts @@ -0,0 +1,143 @@ +import { + IAgentRuntime, + UUID, + logger, + ModelType, + ChannelType, + type Action, + type Memory, +} from '@elizaos/core'; +import type { AnalysisActionResult } from '../../types/index'; +import { generateDataActionTriggerPrompt } from '../../templates/index'; +import { getActionResultFromCache, extractErrorMessage } from '../../utils/actionResult'; + +/** + * DataExecutor + * + * Executes DATA actions with generated trigger messages. + * Uses the agent's permanent world and tracks room IDs for cleanup. + */ +export class DataExecutor { + constructor( + private runtime: IAgentRuntime, + private getWorldId: () => UUID + ) {} + + /** + * Execute DATA actions with generated trigger messages + * @param dataActions - Array of DATA actions to execute + * @returns Object with results and room IDs for cleanup + */ + async execute( + dataActions: Action[] + ): Promise<{ results: AnalysisActionResult[]; roomIds: UUID[] }> { + logger.info('[DataExecutor] Executing DATA actions...'); + + if (dataActions.length === 0) { + logger.warn('[DataExecutor] No DATA actions to execute'); + return { results: [], roomIds: [] }; + } + + // Use agent's permanent world + const worldId = this.getWorldId(); + const createdRoomIds: UUID[] = []; + + logger.debug(`[DataExecutor] Using agent world ${worldId} for DATA actions`); + + // Execute each action in parallel + const results = await Promise.all( + dataActions.map(async (action) => { + try { + // Generate trigger message for this action + const triggerMessage = (await this.runtime.useModel(ModelType.TEXT_SMALL, { + prompt: generateDataActionTriggerPrompt({ action }), + temperature: 0.2, + })) as string; + + logger.debug(`[DataExecutor] Trigger for ${action.name}: "${triggerMessage.trim()}"`); + + // Create message with unique ID for stateCache retrieval + const messageId = crypto.randomUUID() as UUID; + + // Ensure room exists in database (unique room per action, using agent's world) + const roomId = crypto.randomUUID() as UUID; + createdRoomIds.push(roomId); // Track for cleanup + + await this.runtime.ensureRoomExists({ + id: roomId, + source: 'sendo-worker', + type: ChannelType.API, + worldId, + }); + + const memory: Memory = { + id: messageId, + entityId: this.runtime.agentId, + agentId: this.runtime.agentId, + roomId, + content: { + text: triggerMessage.trim(), + }, + createdAt: Date.now(), + }; + + // Create responses array with the action to execute + const responses: Memory[] = [ + { + id: crypto.randomUUID() as UUID, + entityId: this.runtime.agentId, + agentId: this.runtime.agentId, + roomId: memory.roomId, + content: { + text: triggerMessage.trim(), + actions: [action.name], + }, + createdAt: Date.now(), + }, + ]; + + // Process the action - this awaits until action completes + await this.runtime.processActions(memory, responses); + + // Retrieve results from stateCache using helper + const actionResult = getActionResultFromCache(this.runtime, messageId); + + if (actionResult) { + logger.debug( + `[DataExecutor] Action ${action.name} completed: success=${actionResult.success}` + ); + + return { + actionType: action.name, + success: actionResult.success ?? false, + data: actionResult.data || actionResult.values || null, + error: extractErrorMessage(actionResult), + }; + } else { + logger.warn(`[DataExecutor] No result found in stateCache for ${action.name}`); + return { + actionType: action.name, + success: false, + data: null, + error: 'No result returned from action', + }; + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`[DataExecutor] Failed to execute ${action.name}: ${errorMessage}`); + return { + actionType: action.name, + success: false, + data: null, + error: errorMessage, + }; + } + }) + ); + + const successCount = results.filter((r) => r.success).length; + logger.info(`[DataExecutor] Executed ${results.length} DATA actions, ${successCount} successful`); + + return { results, roomIds: createdRoomIds }; + } +} diff --git a/src/services/data/dataSelector.ts b/src/services/data/dataSelector.ts new file mode 100644 index 0000000..05d4713 --- /dev/null +++ b/src/services/data/dataSelector.ts @@ -0,0 +1,65 @@ +import { IAgentRuntime, logger, ModelType, type Action } from '@elizaos/core'; +import type { ProviderDataResult } from '../../types/index'; +import { selectRelevantActionsSchema } from '../../types/index'; +import { selectRelevantDataActionsPrompt } from '../../templates/index'; + +/** + * DataSelector + * + * Selects relevant DATA actions based on provider data using LLM. + * Groups actions by type and processes them in parallel. + */ +export class DataSelector { + constructor(private runtime: IAgentRuntime) {} + + /** + * Select relevant DATA actions based on provider data + * Groups actions by type and selects relevant ones for each type in parallel + * @param dataActions - Map of DATA actions grouped by type + * @param providerData - Provider data results + * @returns Array of relevant DATA actions + */ + async select( + dataActions: Map, + providerData: ProviderDataResult[] + ): Promise { + logger.info('[DataSelector] Selecting relevant DATA actions...'); + + // Process each data action type in parallel + const selections = await Promise.all( + Array.from(dataActions.entries()).map(async ([type, actions]) => { + try { + const response = await this.runtime.useModel(ModelType.OBJECT_SMALL, { + prompt: selectRelevantDataActionsPrompt({ + providerData, + dataActions: actions, + }), + schema: selectRelevantActionsSchema, + temperature: 0.3, + }); + + // Filter selected actions + const selectedActions = actions.filter((action) => + response.relevantActions.includes(action.name) + ); + + logger.debug( + `[DataSelector] Selected ${selectedActions.length}/${actions.length} ${type} actions` + ); + + return selectedActions; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`[DataSelector] Failed to select ${type} actions: ${errorMessage}`); + return []; + } + }) + ); + + // Flatten all selected actions + const relevantActions = selections.flat(); + + logger.info(`[DataSelector] Selected ${relevantActions.length} relevant DATA actions`); + return relevantActions; + } +} diff --git a/src/services/data/index.ts b/src/services/data/index.ts new file mode 100644 index 0000000..b510211 --- /dev/null +++ b/src/services/data/index.ts @@ -0,0 +1,3 @@ +export * from './providerCollector'; +export * from './dataSelector'; +export * from './dataExecutor'; diff --git a/src/services/data/providerCollector.ts b/src/services/data/providerCollector.ts new file mode 100644 index 0000000..ae38e95 --- /dev/null +++ b/src/services/data/providerCollector.ts @@ -0,0 +1,48 @@ +import { IAgentRuntime, UUID, logger, type Memory } from '@elizaos/core'; +import type { ProviderDataResult } from '../../types/index'; + +/** + * ProviderCollector + * + * Collects data from all available providers via composeState. + * Providers include context information like wallet, time, market data, etc. + */ +export class ProviderCollector { + constructor(private runtime: IAgentRuntime) {} + + /** + * Collect data from all available providers via composeState + * @returns Array of provider data results + */ + async collect(): Promise { + logger.info('[ProviderCollector] Collecting data from providers...'); + + // Create a minimal memory for state composition + const memory: Memory = { + id: crypto.randomUUID() as UUID, + entityId: this.runtime.agentId, + agentId: this.runtime.agentId, + roomId: crypto.randomUUID() as UUID, + content: { text: 'Collecting provider data for analysis' }, + createdAt: Date.now(), + }; + + // Compose state - this automatically calls all non-private, non-dynamic providers + const state = await this.runtime.composeState(memory); + + // Extract provider data from state.data.providers + const providers = state.data?.providers || {}; + const timestamp = new Date().toISOString(); + + const results: ProviderDataResult[] = Object.entries(providers).map( + ([name, providerResult]) => ({ + providerName: name, + data: providerResult, + timestamp, + }) + ); + + logger.info(`[ProviderCollector] Collected data from ${results.length} providers`); + return results; + } +} diff --git a/src/services/persistence/index.ts b/src/services/persistence/index.ts new file mode 100644 index 0000000..6ca663b --- /dev/null +++ b/src/services/persistence/index.ts @@ -0,0 +1 @@ +export { AnalysisRepository } from './repository'; diff --git a/src/services/persistence/repository.ts b/src/services/persistence/repository.ts new file mode 100644 index 0000000..a3fc475 --- /dev/null +++ b/src/services/persistence/repository.ts @@ -0,0 +1,243 @@ +import { logger, UUID } from '@elizaos/core'; +import { eq, desc } from 'drizzle-orm'; +import { analysisResults, recommendedActions } from '../../schemas/index'; +import type { AnalysisResult, RecommendedAction } from '../../types/index'; + +// Priority mappings +const PRIORITY_VALUES = { + high: 3, + medium: 2, + low: 1, +} as const; + +const PRIORITY_NAMES = { + 3: 'high', + 2: 'medium', + 1: 'low', +} as const; + +/** + * AnalysisRepository + * + * Handles all database operations for analyses and recommended actions. + * Provides CRUD operations with proper type conversion between DB and application types. + */ +export class AnalysisRepository { + constructor(private getDb: () => any) {} + + /** + * Save analysis and recommendations to database + * @param analysis - The analysis result + * @param recommendations - Array of recommended actions + */ + async saveAnalysis( + analysis: AnalysisResult, + recommendations: RecommendedAction[] + ): Promise { + const db = this.getDb(); + + // 1. Insert analysis result + await db.insert(analysisResults).values({ + id: analysis.id, + agentId: analysis.agentId, + analysis: analysis.analysis, + pluginsUsed: analysis.pluginsUsed, + executionTimeMs: analysis.executionTimeMs, + createdAt: new Date(analysis.createdAt), + }); + + // 2. Insert recommended actions + if (recommendations.length > 0) { + // Filter out any null/undefined recommendations + const validRecs = recommendations.filter((rec) => rec != null && rec.confidence != null); + + if (validRecs.length > 0) { + await db.insert(recommendedActions).values( + validRecs.map((rec) => ({ + id: rec.id, + analysisId: analysis.id, + actionType: rec.actionType, + pluginName: rec.pluginName, + priority: PRIORITY_VALUES[rec.priority], + reasoning: rec.reasoning, + confidence: rec.confidence.toString(), + triggerMessage: rec.triggerMessage, + params: rec.params ?? null, + estimatedImpact: rec.estimatedImpact ?? null, + status: rec.status, + createdAt: new Date(rec.createdAt), + })) + ); + } + } + + logger.info( + `[AnalysisRepository] Saved analysis ${analysis.id} with ${recommendations.length} recommendations` + ); + } + + /** + * Get a single analysis by ID with its recommended actions + * @param analysisId - The analysis UUID + * @returns The analysis result with actions, or null if not found + */ + async getAnalysisResult( + analysisId: UUID + ): Promise<(AnalysisResult & { recommendedActions: RecommendedAction[] }) | null> { + logger.info(`[AnalysisRepository] Getting analysis ${analysisId}`); + + const db = this.getDb(); + const results = await db + .select() + .from(analysisResults) + .where(eq(analysisResults.id, analysisId)) + .limit(1); + + if (results.length === 0) { + return null; + } + + const row = results[0]; + + // Also fetch the recommended actions for this analysis + const actions = await db + .select() + .from(recommendedActions) + .where(eq(recommendedActions.analysisId, analysisId)); + + const recommendedActionsList: RecommendedAction[] = actions.map((action: any) => ({ + id: action.id as UUID, + analysisId: action.analysisId as UUID, + actionType: action.actionType, + pluginName: action.pluginName, + priority: PRIORITY_NAMES[action.priority as keyof typeof PRIORITY_NAMES] || 'medium', + reasoning: action.reasoning, + confidence: parseFloat(action.confidence), + triggerMessage: action.triggerMessage, + params: action.params as Record, + estimatedImpact: action.estimatedImpact, + status: action.status as 'pending' | 'rejected' | 'executing' | 'completed' | 'failed', + executedAt: action.executedAt?.toISOString() ?? undefined, + error: action.error ?? undefined, + errorType: action.errorType ?? undefined, + createdAt: action.createdAt?.toISOString() ?? new Date().toISOString(), + })); + + return { + id: row.id as UUID, + agentId: row.agentId as UUID, + timestamp: row.createdAt?.toISOString() ?? new Date().toISOString(), + analysis: row.analysis as any, + pluginsUsed: row.pluginsUsed ?? [], + executionTimeMs: row.executionTimeMs ?? 0, + createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), + recommendedActions: recommendedActionsList, + }; + } + + /** + * Get all analyses for an agent (limited to most recent) + * @param agentId - The agent UUID + * @param limit - Maximum number of results (default: 10) + * @returns Array of analysis results + */ + async getAnalysesByAgentId(agentId: UUID, limit: number = 10): Promise { + logger.info(`[AnalysisRepository] Getting analyses for agent ${agentId}`); + + const db = this.getDb(); + const results = await db + .select() + .from(analysisResults) + .where(eq(analysisResults.agentId, agentId)) + .orderBy(desc(analysisResults.createdAt)) + .limit(limit); + + return results.map((row: any) => ({ + id: row.id as UUID, + agentId: row.agentId as UUID, + timestamp: row.createdAt?.toISOString() ?? new Date().toISOString(), + analysis: row.analysis as any, + pluginsUsed: row.pluginsUsed ?? [], + executionTimeMs: row.executionTimeMs ?? 0, + createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), + })); + } + + /** + * Get all actions for an analysis, sorted by priority and confidence + * @param analysisId - The analysis UUID + * @returns Array of recommended actions + */ + async getActionsByAnalysisId(analysisId: UUID): Promise { + logger.info(`[AnalysisRepository] Getting actions for analysis ${analysisId}`); + + const db = this.getDb(); + const results = await db + .select() + .from(recommendedActions) + .where(eq(recommendedActions.analysisId, analysisId)) + .orderBy(desc(recommendedActions.priority), desc(recommendedActions.confidence)); + + return results.map((row: any) => ({ + id: row.id, + analysisId: row.analysisId as UUID, + actionType: row.actionType, + pluginName: row.pluginName, + priority: PRIORITY_NAMES[row.priority as keyof typeof PRIORITY_NAMES] || 'medium', + reasoning: row.reasoning, + confidence: parseFloat(row.confidence ?? '0'), + triggerMessage: row.triggerMessage, + params: row.params as any, + estimatedImpact: row.estimatedImpact ?? undefined, + status: row.status as any, + decidedAt: row.decidedAt?.toISOString(), + executedAt: row.executedAt?.toISOString(), + result: row.result as any, + error: row.error ?? undefined, + errorType: row.errorType ?? undefined, + createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), + })); + } + + /** + * Get a specific action by ID + * @param actionId - The action ID + * @returns The recommended action + * @throws {Error} If action not found + */ + async getActionById(actionId: string): Promise { + logger.info(`[AnalysisRepository] Getting action ${actionId}`); + + const db = this.getDb(); + const results = await db + .select() + .from(recommendedActions) + .where(eq(recommendedActions.id, actionId)) + .limit(1); + + if (results.length === 0) { + throw new Error('Action not found'); + } + + const row = results[0]; + return { + id: row.id, + analysisId: row.analysisId as UUID, + actionType: row.actionType, + pluginName: row.pluginName, + priority: PRIORITY_NAMES[row.priority as keyof typeof PRIORITY_NAMES] || 'medium', + reasoning: row.reasoning, + confidence: parseFloat(row.confidence ?? '0'), + triggerMessage: row.triggerMessage, + params: row.params as any, + estimatedImpact: row.estimatedImpact ?? undefined, + status: row.status as any, + decidedAt: row.decidedAt?.toISOString(), + executedAt: row.executedAt?.toISOString(), + result: row.result as any, + error: row.error ?? undefined, + errorType: row.errorType ?? undefined, + createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), + }; + } +} diff --git a/src/services/recommendation/decisionProcessor.ts b/src/services/recommendation/decisionProcessor.ts new file mode 100644 index 0000000..07e5d06 --- /dev/null +++ b/src/services/recommendation/decisionProcessor.ts @@ -0,0 +1,282 @@ +import { IAgentRuntime, logger, UUID, ChannelType, type Memory } from '@elizaos/core'; +import type { RecommendedAction } from '../../types/index'; +import { recommendedActions } from '../../schemas/index'; +import { eq } from 'drizzle-orm'; +import { getActionResultFromCache, extractErrorMessage } from '../../utils/index'; + +/** + * DecisionProcessor + * + * Processes user decisions (accept/reject) for recommended actions. + * Handles execution of accepted actions asynchronously. + */ +export class DecisionProcessor { + constructor( + private runtime: IAgentRuntime, + private getDb: () => any, + private getAgentWorldId: () => UUID, + private getActionById: (actionId: string) => Promise + ) {} + + /** + * Process user decisions on recommended actions + * @param decisions - Array of actionId + decision (accept/reject) + * @returns Accepted and rejected actions + */ + async processDecisions( + decisions: Array<{ actionId: string; decision: 'accept' | 'reject' }> + ): Promise<{ + accepted: RecommendedAction[]; + rejected: Array<{ actionId: string; status: string }>; + }> { + logger.info(`[DecisionProcessor] Processing ${decisions.length} decisions...`); + + const accepted: RecommendedAction[] = []; + const rejected: Array<{ actionId: string; status: string }> = []; + const db = this.getDb(); + + for (const { actionId, decision } of decisions) { + try { + if (decision === 'accept') { + // Accept → Execute the action + const executingActions = await this.executeActions([actionId]); + if (executingActions.length > 0) { + accepted.push(executingActions[0]); + logger.info(`[DecisionProcessor] Action ${actionId} accepted and executing`); + } + } else if (decision === 'reject') { + // Reject → Just update status + await db + .update(recommendedActions) + .set({ + status: 'rejected', + decidedAt: new Date(), + }) + .where(eq(recommendedActions.id, actionId)); + + rejected.push({ actionId, status: 'rejected' }); + logger.info(`[DecisionProcessor] Action ${actionId} rejected`); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`[DecisionProcessor] Failed to process decision for ${actionId}: ${errorMessage}`); + // Continue processing other decisions + } + } + + logger.info( + `[DecisionProcessor] Decisions processed: ${accepted.length} accepted, ${rejected.length} rejected` + ); + + return { accepted, rejected }; + } + + /** + * Execute one or more recommended actions asynchronously + * Changes status to "executing" immediately and processes actions in background + * @param actionIds - Array of action IDs to execute + * @returns Array of actions with status updated to "executing" + */ + private async executeActions(actionIds: string[]): Promise { + logger.info(`[DecisionProcessor] Starting execution of ${actionIds.length} actions...`); + + if (actionIds.length === 0) { + logger.warn('[DecisionProcessor] No actions to execute'); + return []; + } + + const db = this.getDb(); + const executingActions: RecommendedAction[] = []; + + // 1. Update all actions to "executing" status immediately + for (const actionId of actionIds) { + try { + const action = await this.getActionById(actionId); + + await db + .update(recommendedActions) + .set({ + status: 'executing', + decidedAt: new Date(), + }) + .where(eq(recommendedActions.id, actionId)); + + executingActions.push({ + ...action, + status: 'executing', + decidedAt: new Date().toISOString(), + }); + + logger.debug(`[DecisionProcessor] Action ${actionId} status → executing`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`[DecisionProcessor] Failed to update ${actionId}: ${errorMessage}`); + } + } + + // 2. Launch execution asynchronously (don't await) + this.processExecutingActions(actionIds).catch((error) => { + logger.error('[DecisionProcessor] Action execution error:', error); + }); + + logger.info(`[DecisionProcessor] ${executingActions.length} actions marked as executing`); + return executingActions; + } + + /** + * Process executing actions and update database with results + * Called by executeActions() - runs asynchronously + * @param actionIds - Array of action IDs to process + */ + private async processExecutingActions(actionIds: string[]): Promise { + logger.info(`[DecisionProcessor] Processing ${actionIds.length} executing actions...`); + + // Process each action in parallel + await Promise.all( + actionIds.map(async (actionId) => { + try { + // 1. Get action from database + const recommendedAction = await this.getActionById(actionId); + + logger.debug( + `[DecisionProcessor] Processing action ${actionId}: ${recommendedAction.actionType}` + ); + + // 2. Find the actual Action from runtime + const action = Array.from(this.runtime.actions.values()).find( + (a) => a.name === recommendedAction.actionType + ); + + if (!action) { + throw new Error(`Action ${recommendedAction.actionType} not found in runtime`); + } + + // 3. Create memory with unique ID to trigger the action + const messageId = crypto.randomUUID() as UUID; + const roomId = crypto.randomUUID() as UUID; + + // Use agent's permanent world + const actionWorldId = this.getAgentWorldId(); + + // Ensure room exists in database (required for memory storage) + await this.runtime.ensureRoomExists({ + id: roomId, + source: 'sendo-worker', + type: ChannelType.API, + worldId: actionWorldId, + }); + + const memory: Memory = { + id: messageId, + entityId: this.runtime.agentId, + agentId: this.runtime.agentId, + roomId, + content: { + text: recommendedAction.triggerMessage, + }, + createdAt: Date.now(), + }; + + // Create responses array with the action to execute + const responses: Memory[] = [ + { + id: crypto.randomUUID() as UUID, + entityId: this.runtime.agentId, + agentId: this.runtime.agentId, + roomId: memory.roomId, + content: { + text: recommendedAction.triggerMessage, + actions: [action.name], + }, + createdAt: Date.now(), + }, + ]; + + // 4. Process the action - this awaits until action completes + await this.runtime.processActions(memory, responses); + + // 5. Retrieve results from stateCache using helper + const actionResult = getActionResultFromCache(this.runtime, messageId); + + const executedAt = new Date(); + const db = this.getDb(); + + // 6. Update database with result + if (actionResult) { + if (actionResult.success) { + // Success - extract result from ActionResult + const resultData = { + text: actionResult.text || JSON.stringify(actionResult.data || actionResult.values), + data: actionResult.data || actionResult.values || undefined, + timestamp: executedAt.toISOString(), + }; + + await db + .update(recommendedActions) + .set({ + status: 'completed', + result: resultData, + error: null, + executedAt, + }) + .where(eq(recommendedActions.id, actionId)); + + logger.info(`[DecisionProcessor] ✅ Action ${actionId} completed successfully`); + } else { + // Action executed but failed (e.g., insufficient funds, swap failed) + const errorMessage = extractErrorMessage(actionResult, 'Action execution failed'); + await db + .update(recommendedActions) + .set({ + status: 'failed', + error: errorMessage, + errorType: 'execution', + executedAt, + }) + .where(eq(recommendedActions.id, actionId)); + + logger.warn(`[DecisionProcessor] ⚠️ Action ${actionId} failed: ${errorMessage}`); + } + } else { + // No result - mark as failed (action was executed but returned nothing) + await db + .update(recommendedActions) + .set({ + status: 'failed', + error: 'No result returned from action', + errorType: 'execution', + executedAt, + }) + .where(eq(recommendedActions.id, actionId)); + + logger.warn(`[DecisionProcessor] ⚠️ Action ${actionId} failed: no result`); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`[DecisionProcessor] Action ${actionId} execution error: ${errorMessage}`); + + try { + // Determine error type: 'initialization' if action not found, otherwise 'technical' + const errorType = errorMessage.includes('not found') ? 'initialization' : 'technical'; + + // Mark as failed + const db = this.getDb(); + await db + .update(recommendedActions) + .set({ + status: 'failed', + error: errorMessage, + errorType, + executedAt: new Date(), + }) + .where(eq(recommendedActions.id, actionId)); + } catch (dbError) { + logger.error(`[DecisionProcessor] Failed to update error status: ${dbError}`); + } + } + }) + ); + + logger.info(`[DecisionProcessor] Finished processing ${actionIds.length} actions`); + } +} diff --git a/src/services/recommendation/generator.ts b/src/services/recommendation/generator.ts new file mode 100644 index 0000000..d3ef2bc --- /dev/null +++ b/src/services/recommendation/generator.ts @@ -0,0 +1,167 @@ +import { IAgentRuntime, logger, ModelType, UUID, type Action } from '@elizaos/core'; +import type { ActionActionType, RecommendedAction } from '../../types/index'; +import { + selectRelevantActionsSchema, + generateRecommendationSchema, +} from '../../types/index'; +import { + selectRelevantActionsPrompt, + generateRecommendationPrompt, +} from '../../templates/index'; + +/** + * RecommendationGenerator + * + * Generates action recommendations based on analysis results. + * Uses double parallel processing: by action type, then by individual action. + */ +export class RecommendationGenerator { + constructor(private runtime: IAgentRuntime) {} + + /** + * Generate recommendations based on analysis and available ACTION actions + * @param analysisId - ID of the analysis these recommendations belong to + * @param analysis - Generated analysis + * @param actionActions - Map of ACTION category actions grouped by type + * @returns Array of recommended actions ready to save + */ + async generate( + analysisId: UUID, + analysis: { + walletOverview: string; + marketConditions: string; + riskAssessment: string; + opportunities: string; + }, + actionActions: Map + ): Promise { + logger.info('[RecommendationGenerator] Generating recommendations...'); + + // Process each action type in parallel + const allRecommendations = await Promise.all( + Array.from(actionActions.entries()).map(async ([actionType, actions]) => { + try { + // 1. Select relevant actions for this type + const selection = await this.runtime.useModel(ModelType.OBJECT_SMALL, { + prompt: selectRelevantActionsPrompt({ + analysis, + actionType, + actions, + }), + schema: selectRelevantActionsSchema, + temperature: 0.3, + }); + + // 2. Filter selected actions + const selectedActions = actions.filter((action) => + selection.relevantActions.includes(action.name) + ); + + logger.debug( + `[RecommendationGenerator] Selected ${selectedActions.length}/${actions.length} ${actionType} actions` + ); + + if (selectedActions.length === 0) { + return []; + } + + // 3. Generate recommendations for each selected action in parallel + const recommendations = await Promise.all( + selectedActions.map(async (action) => { + try { + const pluginName = this.extractPluginName(action); + + const recommendation = await this.runtime.useModel(ModelType.OBJECT_SMALL, { + prompt: generateRecommendationPrompt({ + analysis, + action, + pluginName, + }), + schema: generateRecommendationSchema, + temperature: 0.2, + }); + + // Transform params from key-value array to object for frontend + let parsedParams: Record | undefined; + if ( + recommendation.params && + Array.isArray(recommendation.params) && + recommendation.params.length > 0 + ) { + parsedParams = recommendation.params.reduce( + (acc: Record, { key, value }: { key: string; value: any }) => { + acc[key] = value; + return acc; + }, + {} as Record + ); + } + + // Build complete RecommendedAction + return { + id: `rec-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + analysisId, + actionType: recommendation.actionType, + pluginName: recommendation.pluginName, + priority: recommendation.priority as 'high' | 'medium' | 'low', + reasoning: recommendation.reasoning, + confidence: recommendation.confidence, + triggerMessage: recommendation.triggerMessage, + params: parsedParams, + estimatedImpact: recommendation.estimatedImpact, + status: 'pending' as const, + createdAt: new Date().toISOString(), + } as RecommendedAction; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error( + `[RecommendationGenerator] Failed to generate recommendation for ${action.name}: ${errorMessage}` + ); + // Log full error for debugging + if (error instanceof Error && error.stack) { + logger.error(`[RecommendationGenerator] Stack trace: ${error.stack}`); + } + logger.error(`[RecommendationGenerator] Full error object: ${JSON.stringify(error, null, 2)}`); + return null; + } + }) + ); + + return recommendations.filter((r): r is RecommendedAction => r !== null); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error( + `[RecommendationGenerator] Failed to process ${actionType} actions: ${errorMessage}` + ); + return []; + } + }) + ); + + // Flatten all recommendations + const finalRecommendations = allRecommendations.flat(); + + logger.info(`[RecommendationGenerator] Generated ${finalRecommendations.length} recommendations`); + return finalRecommendations; + } + + /** + * Extract plugin name from action + * @param action - The action + * @returns Plugin name or 'unknown' + */ + private extractPluginName(action: Action): string { + // Try to extract from action metadata + if ((action as any).plugin) { + return (action as any).plugin; + } + + // Fallback: try to extract from action name if it has a prefix + const nameParts = action.name.split(':'); + if (nameParts.length > 1) { + return nameParts[0]; + } + + return 'unknown'; + } +} diff --git a/src/services/recommendation/index.ts b/src/services/recommendation/index.ts new file mode 100644 index 0000000..bcd2088 --- /dev/null +++ b/src/services/recommendation/index.ts @@ -0,0 +1,2 @@ +export { RecommendationGenerator } from './generator'; +export { DecisionProcessor } from './decisionProcessor'; diff --git a/src/services/sendoWorkerService.ts b/src/services/sendoWorkerService.ts index 59a7279..19472a5 100644 --- a/src/services/sendoWorkerService.ts +++ b/src/services/sendoWorkerService.ts @@ -3,43 +3,40 @@ import { IAgentRuntime, type UUID, logger, - ModelType, - type Action, - type Memory, - ChannelType, createUniqueUuid, } from '@elizaos/core'; -import { eq, desc } from 'drizzle-orm'; import type { AnalysisResult, RecommendedAction, - ActionClassification, - ActionCategorizationResponse, ActionActionType, - DataActionType, - AnalysisActionResult, - ProviderDataResult, } from '../types/index'; -import { - actionCategorizationSchema, - selectRelevantActionsSchema, - generateAnalysisSchema, - generateRecommendationSchema, -} from '../types/index'; -import { analysisResults, recommendedActions, PRIORITY_VALUES, PRIORITY_NAMES } from '../schemas/index'; -import { - actionCategorizationPrompt, - generateDataActionTriggerPrompt, - selectRelevantDataActionsPrompt, - generateAnalysisPrompt, - selectRelevantActionsPrompt, - generateRecommendationPrompt, -} from '../templates/index'; -import { getActionResultFromCache, extractErrorMessage } from '../utils/actionResult'; - +import { WorldManager } from '../utils/worldManager'; +import { ensureSolanaWallet } from '../utils/walletManager.js'; +import { ProviderCollector, DataSelector, DataExecutor } from './data'; +import { ActionCategorizer, AnalysisGenerator } from './analysis'; +import { RecommendationGenerator, DecisionProcessor } from './recommendation'; +import { AnalysisRepository } from './persistence'; + +/** + * SendoWorkerService + * + * Main orchestrator service for the Sendo Worker plugin. + * Manages the complete analysis workflow from data collection to action recommendations. + */ export class SendoWorkerService extends Service { static serviceType = 'sendo_worker'; + // Module instances + private worldManager!: WorldManager; + private providerCollector!: ProviderCollector; + private dataSelector!: DataSelector; + private dataExecutor!: DataExecutor; + private actionCategorizer!: ActionCategorizer; + private analysisGenerator!: AnalysisGenerator; + private recommendationGenerator!: RecommendationGenerator; + private decisionProcessor!: DecisionProcessor; + private repository!: AnalysisRepository; + get capabilityDescription(): string { return 'Sendo Worker service that manages analysis results and recommended actions'; } @@ -47,6 +44,34 @@ export class SendoWorkerService extends Service { async initialize(runtime: IAgentRuntime): Promise { logger.info('[SendoWorkerService] Initializing...'); this.runtime = runtime; + + // Initialize all module instances + this.worldManager = new WorldManager(runtime); + this.providerCollector = new ProviderCollector(runtime); + this.dataSelector = new DataSelector(runtime); + this.dataExecutor = new DataExecutor(runtime, this.getAgentWorldId.bind(this)); + this.actionCategorizer = new ActionCategorizer(runtime); + this.analysisGenerator = new AnalysisGenerator(runtime); + this.recommendationGenerator = new RecommendationGenerator(runtime); + this.decisionProcessor = new DecisionProcessor( + runtime, + this.getDb.bind(this), + this.getAgentWorldId.bind(this), + this.getActionById.bind(this) + ); + this.repository = new AnalysisRepository(this.getDb.bind(this)); + + // Ensure agent's permanent world exists + await this.worldManager.ensureAgentWorldExists(); + + // Ensure Solana wallet exists (required for analysis) + const solanaService = runtime.getService('chain_solana'); + if (solanaService) { + await ensureSolanaWallet(runtime); + } else { + logger.warn('[SendoWorkerService] Skipping wallet creation: Solana service not available'); + } + logger.info('[SendoWorkerService] Initialized successfully'); } @@ -71,475 +96,19 @@ export class SendoWorkerService extends Service { return db; } - // ============================================ - // DYNAMIC ANALYSIS METHODS - // ============================================ - - /** - * Categorize all available actions into DATA and ACTION categories - * Each action is processed in parallel with LLM - * @returns Object with categorized actions grouped by type - */ - async categorizeActions(): Promise<{ - dataActions: Map; - actionActions: Map; - classifications: ActionClassification[]; - }> { - logger.info('[SendoWorkerService] Categorizing all available actions...'); - - // Get all actions from runtime - const allActions = Array.from(this.runtime.actions.values()); - logger.info(`[SendoWorkerService] Found ${allActions.length} actions to categorize`); - - // Process each action in parallel - const classifications = await Promise.all( - allActions.map((action) => this.categorizeAction(action)) - ); - - // Group actions by category and type - const dataActions = new Map(); - const actionActions = new Map(); - - for (const classification of classifications) { - if (classification.category === 'DATA') { - const typeActions = dataActions.get(classification.actionType) || []; - typeActions.push(classification.action); - dataActions.set(classification.actionType, typeActions); - } else if (classification.category === 'ACTION') { - const actionType = classification.actionType as ActionActionType; - const typeActions = actionActions.get(actionType) || []; - typeActions.push(classification.action); - actionActions.set(actionType, typeActions); - } - } - - logger.info( - `[SendoWorkerService] Categorization complete: ${dataActions.size} DATA types, ${actionActions.size} ACTION types` - ); - - return { dataActions, actionActions, classifications }; - } - - /** - * Categorize a single action using LLM - * @param action - The action to categorize - * @returns Classification result - */ - private async categorizeAction(action: Action): Promise { - const response = (await this.runtime.useModel(ModelType.OBJECT_SMALL, { - prompt: actionCategorizationPrompt({ action }), - schema: actionCategorizationSchema, - temperature: 0.1, - })) as ActionCategorizationResponse; - - // Extract plugin name from action - const pluginName = this.extractPluginName(action); - - return { - action, - category: response.category, - actionType: response.actionType as DataActionType | ActionActionType, - confidence: response.confidence, - reasoning: response.reasoning, - pluginName, - }; - } - - /** - * Extract plugin name from action - * @param action - The action - * @returns Plugin name or 'unknown' - */ - private extractPluginName(action: Action): string { - // Try to extract from action metadata - if ((action as any).plugin) { - return (action as any).plugin; - } - - // Fallback: try to extract from action name if it has a prefix - const nameParts = action.name.split(':'); - if (nameParts.length > 1) { - return nameParts[0]; - } - - return 'unknown'; - } - - /** - * Collect data from all available providers via composeState - * @returns Array of provider data results - */ - async collectProviderData(): Promise { - logger.info('[SendoWorkerService] Collecting data from providers...'); - - // Create a minimal memory for state composition - const memory: Memory = { - id: crypto.randomUUID() as UUID, - entityId: this.runtime.agentId, - agentId: this.runtime.agentId, - roomId: crypto.randomUUID() as UUID, - content: { text: 'Collecting provider data for analysis' }, - createdAt: Date.now(), - }; - - // Compose state - this automatically calls all non-private, non-dynamic providers - const state = await this.runtime.composeState(memory); - - // Extract provider data from state.data.providers - const providers = state.data?.providers || {}; - const timestamp = new Date().toISOString(); - - const results: ProviderDataResult[] = Object.entries(providers).map( - ([name, providerResult]) => ({ - providerName: name, - data: providerResult, - timestamp, - }) - ); - - logger.info(`[SendoWorkerService] Collected data from ${results.length} providers`); - return results; - } - /** - * Select relevant DATA actions based on provider data - * Groups actions by type and selects relevant ones for each type in parallel - * @param dataActions - Map of DATA actions grouped by type - * @param providerData - Provider data results - * @returns Array of relevant DATA actions + * Get the permanent worldId for this agent */ - async selectRelevantDataActions( - dataActions: Map, - providerData: ProviderDataResult[] - ): Promise { - logger.info('[SendoWorkerService] Selecting relevant DATA actions...'); - - // Process each data action type in parallel - const selections = await Promise.all( - Array.from(dataActions.entries()).map(async ([type, actions]) => { - try { - const response = await this.runtime.useModel(ModelType.OBJECT_SMALL, { - prompt: selectRelevantDataActionsPrompt({ - providerData, - dataActions: actions, - }), - schema: selectRelevantActionsSchema, - temperature: 0.3, - }); - - // Filter selected actions - const selectedActions = actions.filter((action) => - response.relevantActions.includes(action.name) - ); - - logger.debug( - `[SendoWorkerService] Selected ${selectedActions.length}/${actions.length} ${type} actions` - ); - - return selectedActions; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error( - `[SendoWorkerService] Failed to select ${type} actions: ${errorMessage}` - ); - return []; - } - }) - ); - - // Flatten all selected actions - const relevantActions = selections.flat(); - - logger.info( - `[SendoWorkerService] Selected ${relevantActions.length} relevant DATA actions` - ); - return relevantActions; - } - - /** - * Execute DATA actions with generated trigger messages - * @param dataActions - Array of DATA actions to execute - * @returns Array of action execution results - */ - async executeAnalysisActions(dataActions: Action[], analysisSessionId?: string): Promise { - logger.info('[SendoWorkerService] Executing DATA actions...'); - - if (dataActions.length === 0) { - logger.warn('[SendoWorkerService] No DATA actions to execute'); - return []; - } - - // Create a worldId unique per analysis session - // Each analysis session gets its own world to avoid conflicts between different users/sessions - const sessionId = analysisSessionId || crypto.randomUUID(); - const worldId = createUniqueUuid( + private getAgentWorldId(): UUID { + return createUniqueUuid( this.runtime, - `sendo-analysis-${sessionId}` - ); - - // Execute each action in parallel - const results = await Promise.all( - dataActions.map(async (action) => { - try { - // Generate trigger message for this action - const triggerMessage = (await this.runtime.useModel(ModelType.TEXT_SMALL, { - prompt: generateDataActionTriggerPrompt({ action }), - temperature: 0.2, - })) as string; - - logger.debug(`[SendoWorkerService] Trigger for ${action.name}: "${triggerMessage.trim()}"`); - - // Create message with unique ID for stateCache retrieval - const messageId = crypto.randomUUID() as UUID; - - // Ensure room exists in database (unique room per action, shared worldId) - const roomId = crypto.randomUUID() as UUID; - await this.runtime.ensureRoomExists({ - id: roomId, - source: 'sendo-worker', - type: ChannelType.API, - worldId, - }); - - const memory: Memory = { - id: messageId, - entityId: this.runtime.agentId, - agentId: this.runtime.agentId, - roomId, - content: { - text: triggerMessage.trim(), - }, - createdAt: Date.now(), - }; - - // Create responses array with the action to execute - const responses: Memory[] = [ - { - id: crypto.randomUUID() as UUID, - entityId: this.runtime.agentId, - agentId: this.runtime.agentId, - roomId: memory.roomId, - content: { - text: triggerMessage.trim(), - actions: [action.name], - }, - createdAt: Date.now(), - }, - ]; - - // Process the action - this awaits until action completes - await this.runtime.processActions(memory, responses); - - // Retrieve results from stateCache using helper - const actionResult = getActionResultFromCache(this.runtime, messageId); - - if (actionResult) { - logger.debug( - `[SendoWorkerService] Action ${action.name} completed: success=${actionResult.success}` - ); - - return { - actionType: action.name, - success: actionResult.success ?? false, - data: actionResult.data || actionResult.values || null, - error: extractErrorMessage(actionResult), - }; - } else { - logger.warn(`[SendoWorkerService] No result found in stateCache for ${action.name}`); - return { - actionType: action.name, - success: false, - data: null, - error: 'No result returned from action', - }; - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error(`[SendoWorkerService] Failed to execute ${action.name}: ${errorMessage}`); - return { - actionType: action.name, - success: false, - data: null, - error: errorMessage, - }; - } - }) - ); - - const successCount = results.filter((r) => r.success).length; - logger.info( - `[SendoWorkerService] Executed ${results.length} DATA actions, ${successCount} successful` - ); - - return results; - } - - /** - * Generate comprehensive analysis using LLM - * @param analysisResults - Results from executed DATA actions - * @param providerData - Data collected from providers - * @returns Analysis with four sections - */ - async generateAnalysis( - analysisResults: AnalysisActionResult[], - providerData: ProviderDataResult[] - ): Promise<{ - walletOverview: string; - marketConditions: string; - riskAssessment: string; - opportunities: string; - }> { - logger.info('[SendoWorkerService] Generating analysis with LLM...'); - - // Extract plugin names from successful results - const pluginsUsed = [ - ...new Set([ - ...analysisResults.filter((r) => r.success).map((r) => r.actionType.split(':')[0] || 'unknown'), - ...providerData.map((p) => p.providerName), - ]), - ]; - - const analysis = await this.runtime.useModel(ModelType.OBJECT_LARGE, { - prompt: generateAnalysisPrompt({ - analysisResults, - providerData, - pluginsUsed, - }), - schema: generateAnalysisSchema, - temperature: 0.7, - }); - - logger.info('[SendoWorkerService] Analysis generated successfully'); - return analysis; + `sendo-agent-${this.runtime.agentId}` + ) as UUID; } - /** - * Generate recommendations based on analysis and available ACTION actions - * Uses double parallel processing: by action type, then by individual action - * @param analysisId - ID of the analysis these recommendations belong to - * @param analysis - Generated analysis - * @param actionActions - Map of ACTION category actions grouped by type - * @returns Array of recommended actions ready to save - */ - async generateRecommendations( - analysisId: UUID, - analysis: { - walletOverview: string; - marketConditions: string; - riskAssessment: string; - opportunities: string; - }, - actionActions: Map - ): Promise { - logger.info('[SendoWorkerService] Generating recommendations...'); - - // Process each action type in parallel - const allRecommendations = await Promise.all( - Array.from(actionActions.entries()).map(async ([actionType, actions]) => { - try { - // 1. Select relevant actions for this type - const selection = await this.runtime.useModel(ModelType.OBJECT_SMALL, { - prompt: selectRelevantActionsPrompt({ - analysis, - actionType, - actions, - }), - schema: selectRelevantActionsSchema, - temperature: 0.3, - }); - - // 2. Filter selected actions - const selectedActions = actions.filter((action) => - selection.relevantActions.includes(action.name) - ); - - logger.debug( - `[SendoWorkerService] Selected ${selectedActions.length}/${actions.length} ${actionType} actions` - ); - - if (selectedActions.length === 0) { - return []; - } - - // 3. Generate recommendations for each selected action in parallel - const recommendations = await Promise.all( - selectedActions.map(async (action) => { - try { - const pluginName = this.extractPluginName(action); - - const recommendation = await this.runtime.useModel(ModelType.OBJECT_SMALL, { - prompt: generateRecommendationPrompt({ - analysis, - action, - pluginName, - }), - schema: generateRecommendationSchema, - temperature: 0.2, - }); - - // Transform params from key-value array to object for frontend - let parsedParams: Record | undefined; - if ( - recommendation.params && - Array.isArray(recommendation.params) && - recommendation.params.length > 0 - ) { - parsedParams = recommendation.params.reduce( - (acc: Record, { key, value }: { key: string; value: any }) => { - acc[key] = value; - return acc; - }, - {} as Record - ); - } - - // Build complete RecommendedAction - return { - id: `rec-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, - analysisId, - actionType: recommendation.actionType, - pluginName: recommendation.pluginName, - priority: recommendation.priority as 'high' | 'medium' | 'low', - reasoning: recommendation.reasoning, - confidence: recommendation.confidence, - triggerMessage: recommendation.triggerMessage, - params: parsedParams, - estimatedImpact: recommendation.estimatedImpact, - status: 'pending' as const, - createdAt: new Date().toISOString(), - } as RecommendedAction; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error( - `[SendoWorkerService] Failed to generate recommendation for ${action.name}: ${errorMessage}` - ); - // Log full error for debugging - if (error instanceof Error && error.stack) { - logger.error(`[SendoWorkerService] Stack trace: ${error.stack}`); - } - logger.error(`[SendoWorkerService] Full error object: ${JSON.stringify(error, null, 2)}`); - return null; - } - }) - ); - - return recommendations.filter((r): r is RecommendedAction => r !== null); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error( - `[SendoWorkerService] Failed to process ${actionType} actions: ${errorMessage}` - ); - return []; - } - }) - ); - - // Flatten all recommendations - const finalRecommendations = allRecommendations.flat(); - - logger.info(`[SendoWorkerService] Generated ${finalRecommendations.length} recommendations`); - return finalRecommendations; - } + // ============================================ + // MAIN WORKFLOW METHOD + // ============================================ /** * Main orchestrator method that runs the complete analysis workflow @@ -554,57 +123,45 @@ export class SendoWorkerService extends Service { try { // 1. Categorize all available actions - logger.info('[SendoWorkerService] Step 1/6: Categorizing actions...'); - const { dataActions, actionActions, classifications } = await this.categorizeActions(); - logger.info( - `[SendoWorkerService] Categorized ${classifications.length} actions: ${dataActions.size} DATA types, ${actionActions.size} ACTION types` - ); + logger.info('[SendoWorkerService] Step 1: Categorizing actions...'); + const { dataActions, actionActions } = await this.actionCategorizer.categorize(); // 2. Collect provider data - logger.info('[SendoWorkerService] Step 2/6: Collecting provider data...'); - const providerData = await this.collectProviderData(); - logger.info(`[SendoWorkerService] Collected data from ${providerData.length} providers`); + logger.info('[SendoWorkerService] Step 2: Collecting provider data...'); + const providerData = await this.providerCollector.collect(); // 3. Select relevant DATA actions - logger.info('[SendoWorkerService] Step 3/6: Selecting relevant DATA actions...'); - const relevantDataActions = await this.selectRelevantDataActions(dataActions, providerData); - logger.info(`[SendoWorkerService] Selected ${relevantDataActions.length} relevant DATA actions`); + logger.info('[SendoWorkerService] Step 3: Selecting DATA actions...'); + const selectedDataActions = await this.dataSelector.select(dataActions, providerData); // 4. Execute DATA actions - logger.info('[SendoWorkerService] Step 4/6: Executing DATA actions...'); - const analysisResults = await this.executeAnalysisActions(relevantDataActions); - const successfulResults = analysisResults.filter((r) => r.success); - logger.info( - `[SendoWorkerService] Executed ${analysisResults.length} actions, ${successfulResults.length} successful` + logger.info('[SendoWorkerService] Step 4: Executing DATA actions...'); + const { results: dataResults, roomIds: createdRoomIds } = await this.dataExecutor.execute( + selectedDataActions ); - // 5. Generate analysis - logger.info('[SendoWorkerService] Step 5/6: Generating analysis...'); - const analysis = await this.generateAnalysis(analysisResults, providerData); - logger.info('[SendoWorkerService] Analysis generated successfully'); - - // 6. Generate recommendations - logger.info('[SendoWorkerService] Step 6/6: Generating recommendations...'); + // 5. Generate analysis from DATA results + logger.info('[SendoWorkerService] Step 5: Generating analysis...'); + const analysis = await this.analysisGenerator.generate(dataResults, providerData); - // Create analysis ID for recommendations + // 6. Generate recommendations from ACTION actions + logger.info('[SendoWorkerService] Step 6: Generating recommendations...'); const analysisId = crypto.randomUUID() as UUID; - const recommendations = await this.generateRecommendations( + const recommendations = await this.recommendationGenerator.generate( analysisId, analysis, actionActions ); - logger.info(`[SendoWorkerService] Generated ${recommendations.length} recommendations`); - // Build final result + // 7. Build result object const pluginsUsed = [ ...new Set([ - ...analysisResults.filter((r) => r.success).map((r) => r.actionType.split(':')[0] || 'unknown'), + ...dataResults.filter((r) => r.success).map((r) => r.actionType.split(':')[0] || 'unknown'), ...providerData.map((p) => p.providerName), ]), ]; const executionTimeMs = Date.now() - startTime; - const result: AnalysisResult = { id: analysisId, agentId, @@ -615,15 +172,23 @@ export class SendoWorkerService extends Service { createdAt: new Date().toISOString(), }; - // 7. Save to database + // 8. Save to database logger.info('[SendoWorkerService] Saving analysis to database...'); - await this.saveAnalysis(result, recommendations); + await this.repository.saveAnalysis(result, recommendations); logger.info('[SendoWorkerService] Analysis saved successfully'); logger.info( `[SendoWorkerService] ✅ Analysis completed in ${executionTimeMs}ms with ${recommendations.length} recommendations` ); + // 9. Cleanup temporary rooms + try { + await this.worldManager.cleanupAnalysisRooms(createdRoomIds); + } catch (cleanupError: any) { + // Non-critical error, just log it + logger.warn('[SendoWorkerService] Failed to cleanup rooms:', cleanupError.message); + } + return { ...result, recommendedActions: recommendations, @@ -635,65 +200,12 @@ export class SendoWorkerService extends Service { } } - /** - * Save analysis and recommendations to database - * @param analysis - The analysis result - * @param recommendations - Array of recommended actions - */ - private async saveAnalysis( - analysis: AnalysisResult, - recommendations: RecommendedAction[] - ): Promise { - const db = this.getDb(); - - // 1. Insert analysis result - await db.insert(analysisResults).values({ - id: analysis.id, - agentId: analysis.agentId, - analysis: analysis.analysis, - pluginsUsed: analysis.pluginsUsed, - executionTimeMs: analysis.executionTimeMs, - createdAt: new Date(analysis.createdAt), - }); - - // 2. Insert recommended actions - if (recommendations.length > 0) { - // Filter out any null/undefined recommendations - const validRecs = recommendations.filter((rec) => rec != null && rec.confidence != null); - - if (validRecs.length > 0) { - await db.insert(recommendedActions).values( - validRecs.map((rec) => ({ - id: rec.id, - analysisId: analysis.id, - actionType: rec.actionType, - pluginName: rec.pluginName, - priority: PRIORITY_VALUES[rec.priority], - reasoning: rec.reasoning, - confidence: rec.confidence.toString(), - triggerMessage: rec.triggerMessage, - params: rec.params ?? null, - estimatedImpact: rec.estimatedImpact ?? null, - status: rec.status, - createdAt: new Date(rec.createdAt), - })) - ); - } - } - - logger.info( - `[SendoWorkerService] Saved analysis ${analysis.id} with ${recommendations.length} recommendations` - ); - } - // ============================================ // DECISION & EXECUTION METHODS // ============================================ /** * Process decision for one or more actions (accept/reject) - * - Accept: Executes the action immediately (async) - * - Reject: Updates status to "rejected" without execution * @param decisions - Array of { actionId, decision: 'accept' | 'reject' } * @returns Object with accepted and rejected actions */ @@ -703,259 +215,11 @@ export class SendoWorkerService extends Service { accepted: RecommendedAction[]; rejected: Array<{ actionId: string; status: string }>; }> { - logger.info(`[SendoWorkerService] Processing ${decisions.length} decisions...`); - - const accepted: RecommendedAction[] = []; - const rejected: Array<{ actionId: string; status: string }> = []; - const db = this.getDb(); - - for (const { actionId, decision } of decisions) { - try { - if (decision === 'accept') { - // Accept → Execute the action - const executingActions = await this.executeActions([actionId]); - if (executingActions.length > 0) { - accepted.push(executingActions[0]); - logger.info(`[SendoWorkerService] Action ${actionId} accepted and executing`); - } - } else if (decision === 'reject') { - // Reject → Just update status - await db - .update(recommendedActions) - .set({ - status: 'rejected', - decidedAt: new Date(), - }) - .where(eq(recommendedActions.id, actionId)); - - rejected.push({ actionId, status: 'rejected' }); - logger.info(`[SendoWorkerService] Action ${actionId} rejected`); - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error(`[SendoWorkerService] Failed to process decision for ${actionId}: ${errorMessage}`); - // Continue processing other decisions - } - } - - logger.info( - `[SendoWorkerService] Decisions processed: ${accepted.length} accepted, ${rejected.length} rejected` - ); - - return { accepted, rejected }; - } - - /** - * Execute one or more recommended actions asynchronously - * Changes status to "executing" immediately and processes actions in background - * Called internally by processDecisions() when decision is "accept" - * @param actionIds - Array of action IDs to execute - * @returns Array of actions with status updated to "executing" - */ - private async executeActions(actionIds: string[]): Promise { - logger.info(`[SendoWorkerService] Starting execution of ${actionIds.length} actions...`); - - if (actionIds.length === 0) { - logger.warn('[SendoWorkerService] No actions to execute'); - return []; - } - - const db = this.getDb(); - const executingActions: RecommendedAction[] = []; - - // 1. Update all actions to "executing" status immediately - for (const actionId of actionIds) { - try { - const action = await this.getActionById(actionId); - - await db - .update(recommendedActions) - .set({ - status: 'executing', - decidedAt: new Date(), - }) - .where(eq(recommendedActions.id, actionId)); - - executingActions.push({ - ...action, - status: 'executing', - decidedAt: new Date().toISOString(), - }); - - logger.debug(`[SendoWorkerService] Action ${actionId} status → executing`); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error(`[SendoWorkerService] Failed to update ${actionId}: ${errorMessage}`); - } - } - - // 2. Launch execution asynchronously (don't await) - this.processExecutingActions(actionIds).catch((error) => { - logger.error('[SendoWorkerService] Action execution error:', error); - }); - - logger.info(`[SendoWorkerService] ${executingActions.length} actions marked as executing`); - return executingActions; - } - - /** - * Process executing actions and update database with results - * Called by executeActions() - runs asynchronously - * @param actionIds - Array of action IDs to process - */ - private async processExecutingActions(actionIds: string[]): Promise { - logger.info(`[SendoWorkerService] Processing ${actionIds.length} executing actions...`); - - // Process each action in parallel - await Promise.all( - actionIds.map(async (actionId) => { - try { - // 1. Get action from database - const recommendedAction = await this.getActionById(actionId); - - logger.debug( - `[SendoWorkerService] Processing action ${actionId}: ${recommendedAction.actionType}` - ); - - // 2. Find the actual Action from runtime - const action = Array.from(this.runtime.actions.values()).find( - (a) => a.name === recommendedAction.actionType - ); - - if (!action) { - throw new Error(`Action ${recommendedAction.actionType} not found in runtime`); - } - - // 3. Create memory with unique ID to trigger the action - const messageId = crypto.randomUUID() as UUID; - const roomId = crypto.randomUUID() as UUID; - - // Create a worldId unique per analysis (each analysis represents a user session) - const actionWorldId = createUniqueUuid( - this.runtime, - `sendo-analysis-${recommendedAction.analysisId}` - ); - - // Ensure room exists in database (required for memory storage) - await this.runtime.ensureRoomExists({ - id: roomId, - source: 'sendo-worker', - type: ChannelType.API, - worldId: actionWorldId, - }); - - const memory: Memory = { - id: messageId, - entityId: this.runtime.agentId, - agentId: this.runtime.agentId, - roomId, - content: { - text: recommendedAction.triggerMessage, - }, - createdAt: Date.now(), - }; - - // Create responses array with the action to execute - const responses: Memory[] = [ - { - id: crypto.randomUUID() as UUID, - entityId: this.runtime.agentId, - agentId: this.runtime.agentId, - roomId: memory.roomId, - content: { - text: recommendedAction.triggerMessage, - actions: [action.name], - }, - createdAt: Date.now(), - }, - ]; - - // 4. Process the action - this awaits until action completes - await this.runtime.processActions(memory, responses); - - // 5. Retrieve results from stateCache using helper - const actionResult = getActionResultFromCache(this.runtime, messageId); - - const executedAt = new Date(); - const db = this.getDb(); - - // 6. Update database with result - if (actionResult) { - if (actionResult.success) { - // Success - extract result from ActionResult - const resultData = { - text: actionResult.text || JSON.stringify(actionResult.data || actionResult.values), - data: actionResult.data || actionResult.values || undefined, - timestamp: executedAt.toISOString(), - }; - - await db - .update(recommendedActions) - .set({ - status: 'completed', - result: resultData, - error: null, - executedAt, - }) - .where(eq(recommendedActions.id, actionId)); - - logger.info(`[SendoWorkerService] ✅ Action ${actionId} completed successfully`); - } else { - // Action executed but failed (e.g., insufficient funds, swap failed) - const errorMessage = extractErrorMessage(actionResult, 'Action execution failed'); - await db - .update(recommendedActions) - .set({ - status: 'failed', - error: errorMessage, - errorType: 'execution', - executedAt, - }) - .where(eq(recommendedActions.id, actionId)); - - logger.warn(`[SendoWorkerService] ⚠️ Action ${actionId} failed: ${errorMessage}`); - } - } else { - // No result - mark as failed (action was executed but returned nothing) - await db - .update(recommendedActions) - .set({ - status: 'failed', - error: 'No result returned from action', - errorType: 'execution', - executedAt, - }) - .where(eq(recommendedActions.id, actionId)); - - logger.warn(`[SendoWorkerService] ⚠️ Action ${actionId} failed: no result`); - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - - logger.error(`[SendoWorkerService] ❌ Action ${actionId} failed: ${errorMessage}`); - - // Update database with error and errorType - // Exceptions here are initialization errors (action not found, room creation failed, etc.) - // These are infrastructure/setup problems, not execution failures - const db = this.getDb(); - await db - .update(recommendedActions) - .set({ - status: 'failed', - error: errorMessage, - errorType: 'initialization', - executedAt: new Date(), - }) - .where(eq(recommendedActions.id, actionId)); - } - }) - ); - - logger.info('[SendoWorkerService] Action processing completed'); + return this.decisionProcessor.processDecisions(decisions); } // ============================================ - // READ METHODS + // READ METHODS (Delegated to Repository) // ============================================ /** @@ -966,82 +230,17 @@ export class SendoWorkerService extends Service { async getAnalysisResult( analysisId: UUID ): Promise<(AnalysisResult & { recommendedActions: RecommendedAction[] }) | null> { - logger.info(`[SendoWorkerService] Getting analysis ${analysisId}`); - - const db = this.getDb(); - const results = await db - .select() - .from(analysisResults) - .where(eq(analysisResults.id, analysisId)) - .limit(1); - - if (results.length === 0) { - return null; - } - - const row = results[0]; - - // Also fetch the recommended actions for this analysis - const actions = await db - .select() - .from(recommendedActions) - .where(eq(recommendedActions.analysisId, analysisId)); - - const recommendedActionsList: RecommendedAction[] = actions.map((action: any) => ({ - id: action.id as UUID, - analysisId: action.analysisId as UUID, - actionType: action.actionType, - pluginName: action.pluginName, - priority: PRIORITY_NAMES[action.priority as keyof typeof PRIORITY_NAMES] || 'medium', - reasoning: action.reasoning, - confidence: parseFloat(action.confidence), - triggerMessage: action.triggerMessage, - params: action.params as Record, - estimatedImpact: action.estimatedImpact, - status: action.status as 'pending' | 'rejected' | 'executing' | 'completed' | 'failed', - executedAt: action.executedAt?.toISOString() ?? undefined, - error: action.error ?? undefined, - errorType: action.errorType ?? undefined, - createdAt: action.createdAt?.toISOString() ?? new Date().toISOString(), - })); - - return { - id: row.id as UUID, - agentId: row.agentId as UUID, - timestamp: row.createdAt?.toISOString() ?? new Date().toISOString(), - analysis: row.analysis as any, - pluginsUsed: row.pluginsUsed ?? [], - executionTimeMs: row.executionTimeMs ?? 0, - createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), - recommendedActions: recommendedActionsList, - }; + return this.repository.getAnalysisResult(analysisId); } /** * Get all analyses for an agent (limited to 10 most recent) * @param agentId - The agent UUID + * @param limit - Maximum number of results (default: 10) * @returns Array of analysis results */ async getAnalysesByAgentId(agentId: UUID, limit: number = 10): Promise { - logger.info(`[SendoWorkerService] Getting analyses for agent ${agentId}`); - - const db = this.getDb(); - const results = await db - .select() - .from(analysisResults) - .where(eq(analysisResults.agentId, agentId)) - .orderBy(desc(analysisResults.createdAt)) - .limit(limit); - - return results.map((row: any) => ({ - id: row.id as UUID, - agentId: row.agentId as UUID, - timestamp: row.createdAt?.toISOString() ?? new Date().toISOString(), - analysis: row.analysis as any, - pluginsUsed: row.pluginsUsed ?? [], - executionTimeMs: row.executionTimeMs ?? 0, - createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), - })); + return this.repository.getAnalysesByAgentId(agentId, limit); } /** @@ -1050,34 +249,7 @@ export class SendoWorkerService extends Service { * @returns Array of recommended actions */ async getActionsByAnalysisId(analysisId: UUID): Promise { - logger.info(`[SendoWorkerService] Getting actions for analysis ${analysisId}`); - - const db = this.getDb(); - const results = await db - .select() - .from(recommendedActions) - .where(eq(recommendedActions.analysisId, analysisId)) - .orderBy(desc(recommendedActions.priority), desc(recommendedActions.confidence)); - - return results.map((row: any) => ({ - id: row.id, - analysisId: row.analysisId as UUID, - actionType: row.actionType, - pluginName: row.pluginName, - priority: PRIORITY_NAMES[row.priority as keyof typeof PRIORITY_NAMES] || 'medium', - reasoning: row.reasoning, - confidence: parseFloat(row.confidence ?? '0'), - triggerMessage: row.triggerMessage, - params: row.params as any, - estimatedImpact: row.estimatedImpact ?? undefined, - status: row.status as any, - decidedAt: row.decidedAt?.toISOString(), - executedAt: row.executedAt?.toISOString(), - result: row.result as any, - error: row.error ?? undefined, - errorType: row.errorType ?? undefined, - createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), - })); + return this.repository.getActionsByAnalysisId(analysisId); } /** @@ -1087,41 +259,8 @@ export class SendoWorkerService extends Service { * @throws {Error} If action not found */ async getActionById(actionId: string): Promise { - logger.info(`[SendoWorkerService] Getting action ${actionId}`); - - const db = this.getDb(); - const results = await db - .select() - .from(recommendedActions) - .where(eq(recommendedActions.id, actionId)) - .limit(1); - - if (results.length === 0) { - throw new Error('Action not found'); - } - - const row = results[0]; - return { - id: row.id, - analysisId: row.analysisId as UUID, - actionType: row.actionType, - pluginName: row.pluginName, - priority: PRIORITY_NAMES[row.priority as keyof typeof PRIORITY_NAMES] || 'medium', - reasoning: row.reasoning, - confidence: parseFloat(row.confidence ?? '0'), - triggerMessage: row.triggerMessage, - params: row.params as any, - estimatedImpact: row.estimatedImpact ?? undefined, - status: row.status as any, - decidedAt: row.decidedAt?.toISOString(), - executedAt: row.executedAt?.toISOString(), - result: row.result as any, - error: row.error ?? undefined, - errorType: row.errorType ?? undefined, - createdAt: row.createdAt?.toISOString() ?? new Date().toISOString(), - }; + return this.repository.getActionById(actionId); } - } export default SendoWorkerService; diff --git a/src/utils/index.ts b/src/utils/index.ts index ff892a7..c6cccaa 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,3 @@ -// Export utility functions here -// Example: export { helperFunction } from './helpers.js'; +export * from './actionResult'; +export * from './worldManager'; +export * from './walletManager'; diff --git a/src/utils/walletManager.ts b/src/utils/walletManager.ts new file mode 100644 index 0000000..97f37d4 --- /dev/null +++ b/src/utils/walletManager.ts @@ -0,0 +1,129 @@ +import { logger, type IAgentRuntime, type Service } from '@elizaos/core'; + +/** + * Interface for Solana service with wallet creation and balance checking + */ +interface ISolanaService extends Service { + createWallet(): Promise<{ publicKey: string; privateKey: string }>; + getBalance(assetAddress: string, owner?: string): Promise; +} + +/** + * Ensure Solana wallet exists for the agent + * Creates a new wallet if none exists + * + * @param runtime - The agent runtime + * @returns Public key of the wallet (existing or newly created) + * @throws Error if SOLANA_SERVICE is not available + */ +export async function ensureSolanaWallet(runtime: IAgentRuntime): Promise { + // Check if wallet already configured + const publicKey = runtime.getSetting('SOLANA_PUBLIC_KEY'); + const privateKey = runtime.getSetting('SOLANA_PRIVATE_KEY'); + + if (publicKey && privateKey) { + logger.debug(`[WalletUtils] ✅ Solana wallet already configured: ${publicKey}`); + return publicKey; + } + + // No wallet - need to create one + logger.warn('[WalletUtils] ⚠️ No Solana wallet found, creating one...'); + + // Get Solana service + const solanaService = runtime.getService('chain_solana'); + if (!solanaService) { + logger.error('[WalletUtils] ❌ Solana service not found!'); + logger.error('[WalletUtils] Make sure @elizaos/plugin-solana is loaded'); + throw new Error('Solana service required but not available'); + } + + // Create wallet using Solana service + try { + const wallet = await solanaService.createWallet(); + logger.info(`[WalletUtils] 🎉 Created Solana wallet: ${wallet.publicKey}`); + + // Set in runtime (memory) + runtime.setSetting('SOLANA_PUBLIC_KEY', wallet.publicKey, false); + runtime.setSetting('SOLANA_PRIVATE_KEY', wallet.privateKey, true); // encrypted + + // Persist to database (CRUCIAL!) + // IAgentRuntime extends IDatabaseAdapter, so updateAgent is available directly + await runtime.updateAgent(runtime.agentId, { + settings: { + secrets: runtime.character.secrets || {}, + ...(runtime.character.settings || {}), + }, + updatedAt: Date.now(), + }); + + logger.info('[WalletUtils] ✅ Wallet persisted to database'); + logger.info('[WalletUtils] 💰 Fund this wallet to enable operations:'); + logger.info(`[WalletUtils] ${wallet.publicKey}`); + + return wallet.publicKey; + } catch (error) { + logger.error('[WalletUtils] ❌ Failed to create wallet:', error instanceof Error ? error.message : String(error)); + throw error; + } +} + +/** + * Get wallet public key if exists + * + * @param runtime - The agent runtime + * @returns Public key or null if no wallet configured + */ +export function getWalletPublicKey(runtime: IAgentRuntime): string | null { + return runtime.getSetting('SOLANA_PUBLIC_KEY'); +} + +/** + * Check if agent has a wallet configured + * + * @param runtime - The agent runtime + * @returns True if wallet is configured + */ +export function hasWallet(runtime: IAgentRuntime): boolean { + const publicKey = runtime.getSetting('SOLANA_PUBLIC_KEY'); + const privateKey = runtime.getSetting('SOLANA_PRIVATE_KEY'); + return !!(publicKey && privateKey); +} + +/** + * Check if wallet has sufficient balance for operations + * Minimum balance: 0.01 SOL (for transaction fees) + * + * @param runtime - The agent runtime + * @returns Object with hasBalance boolean and current balance in SOL + */ +export async function checkWalletBalance( + runtime: IAgentRuntime +): Promise<{ hasBalance: boolean; balance: number; publicKey: string }> { + const publicKey = getWalletPublicKey(runtime); + + if (!publicKey) { + throw new Error('No wallet configured'); + } + + // Get Solana service to check balance + const solanaService = runtime.getService('chain_solana'); + if (!solanaService) { + throw new Error('Solana service required but not available'); + } + + try { + // Get SOL balance using Solana service + // 'SOL' is the native token identifier + const balance = await solanaService.getBalance('SOL', publicKey); + const minimumBalance = 0.01; // 0.01 SOL minimum for operations + + return { + hasBalance: balance >= minimumBalance, + balance, + publicKey, + }; + } catch (error) { + logger.error('[WalletUtils] Failed to check wallet balance:', error instanceof Error ? error.message : String(error)); + throw error; + } +} \ No newline at end of file diff --git a/src/utils/worldManager.ts b/src/utils/worldManager.ts new file mode 100644 index 0000000..a9db44b --- /dev/null +++ b/src/utils/worldManager.ts @@ -0,0 +1,91 @@ +import { IAgentRuntime, UUID, logger, createUniqueUuid } from '@elizaos/core'; + +/** + * WorldManager + * + * Manages the permanent world for the agent and cleanup of temporary rooms. + * Each agent has one permanent world that persists across analyses. + */ +export class WorldManager { + constructor(private runtime: IAgentRuntime) {} + + /** + * Ensure a permanent world exists for this agent + * This world is used for all analyses and persists across restarts + * + * World ID format: sendo-agent-{agentId} + */ + async ensureAgentWorldExists(): Promise { + try { + const worldId = this.getAgentWorldId(); + const agentName = this.runtime.character?.name || 'Unknown Agent'; + + // Check if world already exists to avoid duplicate key error + const existingWorld = await this.runtime.getWorld(worldId); + + if (existingWorld) { + logger.debug(`[WorldManager] Agent world already exists: ${worldId}`); + return; + } + + // Create the world + await this.runtime.ensureWorldExists({ + id: worldId, + name: `Sendo Agent World - ${agentName}`, + agentId: this.runtime.agentId, + serverId: 'sendo-worker', + metadata: { + type: 'sendo-agent', + agentName, + createdAt: Date.now(), + permanent: true, + }, + }); + + logger.info(`[WorldManager] ✅ Agent world created: ${worldId}`); + } catch (error: any) { + logger.error('[WorldManager] Failed to ensure agent world:', error); + throw error; + } + } + + /** + * Get the permanent worldId for this agent + * Used for all analysis operations + */ + getAgentWorldId(): UUID { + return createUniqueUuid(this.runtime, `sendo-agent-${this.runtime.agentId}`) as UUID; + } + + /** + * Cleanup temporary rooms created during analysis + * The agent's world is kept permanent + * + * @param roomIds - Array of room IDs to cleanup + */ + async cleanupAnalysisRooms(roomIds: UUID[]): Promise { + if (roomIds.length === 0) { + return; + } + + try { + logger.debug(`[WorldManager] 🧹 Cleaning up ${roomIds.length} temporary rooms...`); + + // Delete each room + for (const roomId of roomIds) { + try { + await this.runtime.deleteRoom(roomId); + } catch (error: any) { + // Individual room deletion failure is non-critical + const errorMsg = error instanceof Error ? error.message : String(error); + logger.warn(`[WorldManager] Failed to delete room ${roomId}: ${errorMsg}`); + } + } + + logger.info(`[WorldManager] ✅ Cleaned up ${roomIds.length} temporary rooms`); + } catch (error: any) { + logger.error('[WorldManager] Cleanup failed:', error); + // Non-critical, don't throw + } + } +}