Merge pull request #4 from bendtherules/logging

Add basic logging
This commit is contained in:
2026-04-22 12:56:58 +05:30
committed by GitHub
17 changed files with 1385 additions and 238 deletions
+19
View File
@@ -39,3 +39,22 @@ ASK262_EMBEDDING_PROVIDER=ollama
# Required when running behind a reverse proxy or in production # Required when running behind a reverse proxy or in production
# Example: https://ask262.bendtherules.in or https://your-server.com # Example: https://ask262.bendtherules.in or https://your-server.com
# MCP_PUBLIC_URL=http://localhost:8081 # MCP_PUBLIC_URL=http://localhost:8081
# =============================================================================
# LOGGING CONFIGURATION
# =============================================================================
# Log level: trace, debug, info, warn, error
# HTTP server defaults to 'debug', stdio server defaults to 'info'
# ASK262_LOG_LEVEL=info
# Directory for log files (optional, defaults to ./logs)
# ASK262_LOG_DIR=./logs
# Max log file size in MB before rotation (optional, defaults to 100)
# System logrotate handles rotation, this is for reference
# ASK262_LOG_MAX_SIZE=100
# Days to keep rotated logs before deletion (optional, defaults to 30)
# Set to 0 to keep forever
# ASK262_LOG_RETENTION_DAYS=30
+4
View File
@@ -21,6 +21,10 @@ coverage/
# Test output # Test output
.idea/ .idea/
*.log *.log
# Logs directory
/logs/
*.jsonl
# Optional: package lock files if you prefer not to commit them # Optional: package lock files if you prefer not to commit them
# package-lock.json # package-lock.json
# yarn.lock # yarn.lock
+56 -3
View File
@@ -12,6 +12,7 @@
"@langchain/openai": "^0.1.0", "@langchain/openai": "^0.1.0",
"@mcp-use/inspector": "^2.1.0", "@mcp-use/inspector": "^2.1.0",
"@modelcontextprotocol/sdk": "^1.0.4", "@modelcontextprotocol/sdk": "^1.0.4",
"@opentelemetry/api": "^1.9.1",
"acorn": "^8.16.0", "acorn": "^8.16.0",
"cheerio": "^1.2.0", "cheerio": "^1.2.0",
"commander": "^14.0.3", "commander": "^14.0.3",
@@ -20,6 +21,8 @@
"hono": "^4.12.14", "hono": "^4.12.14",
"langchain": "^0.2.0", "langchain": "^0.2.0",
"ora": "^9.3.0", "ora": "^9.3.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.4.9", "@biomejs/biome": "^2.4.9",
@@ -229,6 +232,8 @@
"@paper-design/shaders-react": ["@paper-design/shaders-react@0.0.72", "", { "dependencies": { "@paper-design/shaders": "0.0.72" }, "peerDependencies": { "@types/react": "^18 || ^19", "react": "^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-q6KwquL93ZVNcuSM7pqzW0z/VLjnDVb/NSpYyGJBxf7MEHHCXx37E+zw/Px6RZLt3SGCUerIDDCGCMT385oW0w=="], "@paper-design/shaders-react": ["@paper-design/shaders-react@0.0.72", "", { "dependencies": { "@paper-design/shaders": "0.0.72" }, "peerDependencies": { "@types/react": "^18 || ^19", "react": "^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-q6KwquL93ZVNcuSM7pqzW0z/VLjnDVb/NSpYyGJBxf7MEHHCXx37E+zw/Px6RZLt3SGCUerIDDCGCMT385oW0w=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
"@posthog/core": ["@posthog/core@1.25.2", "", {}, "sha512-h2FO7ut/BbfwpAXWpwdDHTzQgUo9ibDFEs6ZO+3cI3KPWQt5XwczK1OLAuPprcjm8T/jl0SH8jSFo5XdU4RbTg=="], "@posthog/core": ["@posthog/core@1.25.2", "", {}, "sha512-h2FO7ut/BbfwpAXWpwdDHTzQgUo9ibDFEs6ZO+3cI3KPWQt5XwczK1OLAuPprcjm8T/jl0SH8jSFo5XdU4RbTg=="],
"@posthog/types": ["@posthog/types@1.369.3", "", {}, "sha512-Ywqvs4513PixR2TIA5O3GMEyK4F65uefwxPfsIUeHr9ruGylyXp00YJ4CEbp8U0DMzCkeF+LsMKVnHgN3pAXcA=="], "@posthog/types": ["@posthog/types@1.369.3", "", {}, "sha512-Ywqvs4513PixR2TIA5O3GMEyK4F65uefwxPfsIUeHr9ruGylyXp00YJ4CEbp8U0DMzCkeF+LsMKVnHgN3pAXcA=="],
@@ -475,6 +480,8 @@
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
@@ -539,6 +546,8 @@
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
@@ -553,7 +562,7 @@
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
@@ -569,6 +578,8 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
@@ -611,6 +622,8 @@
"encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -647,10 +660,14 @@
"express-rate-limit": ["express-rate-limit@8.3.2", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg=="], "express-rate-limit": ["express-rate-limit@8.3.2", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg=="],
"fast-copy": ["fast-copy@4.0.3", "", {}, "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
@@ -727,6 +744,8 @@
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
"help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
"highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
"highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="],
@@ -787,6 +806,8 @@
"jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
"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=="], "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -869,6 +890,8 @@
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], "minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
@@ -905,6 +928,8 @@
"ollama": ["ollama@0.5.18", "", { "dependencies": { "whatwg-fetch": "^3.6.20" } }, "sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg=="], "ollama": ["ollama@0.5.18", "", { "dependencies": { "whatwg-fetch": "^3.6.20" } }, "sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg=="],
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
@@ -947,6 +972,14 @@
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="],
"pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
"pino-pretty": ["pino-pretty@13.1.3", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", "strip-json-comments": "^5.0.2" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg=="],
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
"postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], "postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="],
@@ -961,18 +994,24 @@
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"protobufjs": ["protobufjs@7.5.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg=="], "protobufjs": ["protobufjs@7.5.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
"qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="],
"query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="], "query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
@@ -997,6 +1036,8 @@
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
"redis": ["redis@5.12.1", "", { "dependencies": { "@redis/bloom": "5.12.1", "@redis/client": "5.12.1", "@redis/json": "5.12.1", "@redis/search": "5.12.1", "@redis/time-series": "5.12.1" } }, "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g=="], "redis": ["redis@5.12.1", "", { "dependencies": { "@redis/bloom": "5.12.1", "@redis/client": "5.12.1", "@redis/json": "5.12.1", "@redis/search": "5.12.1", "@redis/time-series": "5.12.1" } }, "sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g=="],
"reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
@@ -1025,10 +1066,14 @@
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
@@ -1055,12 +1100,16 @@
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], "slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"stdin-discarder": ["stdin-discarder@0.3.1", "", {}, "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA=="], "stdin-discarder": ["stdin-discarder@0.3.1", "", {}, "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA=="],
@@ -1069,6 +1118,8 @@
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"table-layout": ["table-layout@4.1.1", "", { "dependencies": { "array-back": "^6.2.2", "wordwrapjs": "^5.1.0" } }, "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA=="], "table-layout": ["table-layout@4.1.1", "", { "dependencies": { "array-back": "^6.2.2", "wordwrapjs": "^5.1.0" } }, "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA=="],
@@ -1085,6 +1136,8 @@
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
"thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="],
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -1237,8 +1290,6 @@
"encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "encoding-sniffer/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
@@ -1269,6 +1320,8 @@
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"react-router/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"table-layout/array-back": ["array-back@6.2.3", "", {}, "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw=="], "table-layout/array-back": ["array-back@6.2.3", "", {}, "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw=="],
"whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+14
View File
@@ -0,0 +1,14 @@
# coolify.yaml - Coolify deployment configuration
version: 1
services:
- name: ask262
cronjobs:
- name: "log-rotation"
schedule: "*/5 * * * *" # Every 5 minutes
command: "logrotate -f /app/logrotate.conf 2>/dev/null || true"
- name: "log-cleanup"
schedule: "0 0 * * *" # Daily at midnight
command: "find ${ASK262_LOG_DIR:-/app/logs} -name 'ask262.jsonl.*' -mtime +${ASK262_LOG_RETENTION_DAYS:-30} -delete 2>/dev/null || true"
+12
View File
@@ -0,0 +1,12 @@
# Logrotate configuration for ask262 MCP server
# Rotates logs at 100MB, keeps 10 rotated files, compresses old ones
${ASK262_LOG_DIR}/ask262.jsonl {
size 100M
rotate 10
compress
delaycompress
copytruncate
notifempty
missingok
}
+4 -1
View File
@@ -58,6 +58,7 @@
"@langchain/openai": "^0.1.0", "@langchain/openai": "^0.1.0",
"@mcp-use/inspector": "^2.1.0", "@mcp-use/inspector": "^2.1.0",
"@modelcontextprotocol/sdk": "^1.0.4", "@modelcontextprotocol/sdk": "^1.0.4",
"@opentelemetry/api": "^1.9.1",
"acorn": "^8.16.0", "acorn": "^8.16.0",
"cheerio": "^1.2.0", "cheerio": "^1.2.0",
"commander": "^14.0.3", "commander": "^14.0.3",
@@ -65,7 +66,9 @@
"graphology": "^0.26.0", "graphology": "^0.26.0",
"hono": "^4.12.14", "hono": "^4.12.14",
"langchain": "^0.2.0", "langchain": "^0.2.0",
"ora": "^9.3.0" "ora": "^9.3.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.4.9", "@biomejs/biome": "^2.4.9",
+58 -4
View File
@@ -9,6 +9,8 @@ import { spawn } from "node:child_process";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { z } from "zod"; import { z } from "zod";
import { LogOperation, logger } from "../lib/logger.js";
import { withSpan } from "../lib/tracing.js";
// #region Zod schemas (not exported) // #region Zod schemas (not exported)
@@ -232,17 +234,69 @@ export function createEvaluateInEngine262Tool(
timeoutMs = DEFAULT_EXECUTION_TIMEOUT_MS, timeoutMs = DEFAULT_EXECUTION_TIMEOUT_MS,
) { ) {
return async ({ code }: EvaluateToolInput): Promise<EvaluateToolOutput> => { return async ({ code }: EvaluateToolInput): Promise<EvaluateToolOutput> => {
const log = await logger.forComponent("engine262-runner");
log.info(LogOperation.EVALUATE_IN_ENGINE262, { code_length: code.length });
// Truncate code for logging if too long (>500 chars)
const codeForLog =
code.length > 500
? `${code.substring(0, 500)}... (${code.length - 500} more chars)`
: code;
// Only the main evaluating_in_engine262 span logs the full code
return await withSpan(
LogOperation.EVALUATING_IN_ENGINE262,
{ code: codeForLog, code_length: code.length, timeout_ms: timeoutMs },
async () => {
const op = log.start(LogOperation.EVALUATING_IN_ENGINE262, {
code: codeForLog,
code_length: code.length,
timeout_ms: timeoutMs,
});
try { try {
// Child operations don't need to log the code - it's in the parent span context
log.debug(LogOperation.SPAWNING_CHILD_PROCESS, {
timeout_ms: timeoutMs,
});
// Execute code in isolated child process // Execute code in isolated child process
const resultJson = await executeInChildProcess(code, timeoutMs); const resultJson = await executeInChildProcess(code, timeoutMs);
// Parse the result // Parse the result
return JSON.parse(resultJson) as EvaluateToolOutput; const result = JSON.parse(resultJson) as EvaluateToolOutput;
} catch (error) {
// Return error result if ("error" in result && result.error) {
log.warn(LogOperation.ENGINE262_ABRUPT_COMPLETION, {
error: result.error,
});
op.end({ status: "error", error: result.error });
} else {
const successResult = result as EvaluateSuccessOutput;
// op.end logs the final completion with all metrics and duration
op.end({
status: "success",
important_sections: successResult.importantSections.length,
other_sections: successResult.otherSections.length,
console_entries: successResult.consoleOutput.length,
});
}
return result;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
log.error(
LogOperation.EVALUATING_IN_ENGINE262,
{ code_length: code.length },
error,
);
op.end({ status: "exception", error: error.message });
return { return {
error: error instanceof Error ? error.message : String(error), error: error.message,
}; };
} }
},
);
}; };
} }
+45 -3
View File
@@ -5,6 +5,8 @@
import type { Table } from "@lancedb/lancedb"; import type { Table } from "@lancedb/lancedb";
import { z } from "zod"; import { z } from "zod";
import { LogOperation, logger } from "../lib/logger.js";
import { withSpan } from "../lib/tracing.js";
// #region Zod schemas (not exported) // #region Zod schemas (not exported)
@@ -21,6 +23,8 @@ const sectionDataSchema = z.object({
error: z.string().optional(), error: z.string().optional(),
sectionTitle: z.string().optional(), sectionTitle: z.string().optional(),
childrensectionids: z.array(z.string()).optional(), childrensectionids: z.array(z.string()).optional(),
partIndex: z.number().optional(),
totalParts: z.number().optional(),
}); });
const getSectionContentOutputSchema = z.object({ const getSectionContentOutputSchema = z.object({
@@ -81,6 +85,22 @@ export function createGetSectionContentTool(table: Table) {
sectionIds, sectionIds,
recursive, recursive,
}: GetSectionContentInput): Promise<GetSectionContentOutput> => { }: GetSectionContentInput): Promise<GetSectionContentOutput> => {
const log = await logger.forComponent("get-section-tool");
log.info(LogOperation.GET_SECTION_CONTENT, {
section_count: sectionIds.length,
recursive,
});
return await withSpan(
LogOperation.FETCHING_SECTION_CONTENT,
{ section_count: sectionIds.length, recursive },
async () => {
const op = log.start(LogOperation.FETCHING_SECTION_CONTENT, {
section_ids: sectionIds,
recursive,
});
const sectionsData = new Map< const sectionsData = new Map<
string, string,
{ {
@@ -92,6 +112,7 @@ export function createGetSectionContentTool(table: Table) {
const queue: string[] = [...sectionIds]; const queue: string[] = [...sectionIds];
const visited = new Set<string>(); const visited = new Set<string>();
try {
while (queue.length > 0) { while (queue.length > 0) {
const currentId = queue.shift(); const currentId = queue.shift();
if (!currentId || visited.has(currentId)) continue; if (!currentId || visited.has(currentId)) continue;
@@ -168,8 +189,29 @@ export function createGetSectionContentTool(table: Table) {
}; };
}); });
return { const totalSectionsFetched = sectionsData.size;
sections, const totalContentLength = sections.reduce(
}; (sum, s) => sum + s.content.length,
0,
);
op.end({
total_sections: totalSectionsFetched,
total_content_length: totalContentLength,
recursive,
});
return { sections };
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
log.error(
LogOperation.FETCHING_SECTION_CONTENT,
{ section_ids: sectionIds },
error,
);
throw err;
}
},
);
}; };
} }
+37
View File
@@ -6,6 +6,8 @@
import { DynamicStructuredTool } from "@langchain/core/tools"; import { DynamicStructuredTool } from "@langchain/core/tools";
import type Graph from "graphology"; import type Graph from "graphology";
import { z } from "zod"; import { z } from "zod";
import { LogOperation, logger } from "../lib/logger.js";
import { withSpan } from "../lib/tracing.js";
const graphExplorerSchema = z.object({ const graphExplorerSchema = z.object({
query: z query: z
@@ -27,10 +29,24 @@ export function createGraphExplorerTool(graph: Graph) {
"Explores structural relationships between specification sections and implementation code (functions). Use this to find which spec section a function implements.", "Explores structural relationships between specification sections and implementation code (functions). Use this to find which spec section a function implements.",
schema: graphExplorerSchema, schema: graphExplorerSchema,
func: async ({ query }) => { func: async ({ query }) => {
const log = await logger.forComponent("graph-explorer");
log.info(LogOperation.EXPLORING_GRAPH, { query });
return await withSpan(
LogOperation.EXPLORING_GRAPH,
{ query },
async () => {
const op = log.start(LogOperation.EXPLORING_GRAPH, { query });
let nodeId = query; let nodeId = query;
if (!graph.hasNode(nodeId)) { if (!graph.hasNode(nodeId)) {
if (graph.hasNode(`func-${query}`)) { if (graph.hasNode(`func-${query}`)) {
nodeId = `func-${query}`; nodeId = `func-${query}`;
log.debug(LogOperation.RESOLVING_NODE_ID, {
original: query,
resolved: nodeId,
});
} }
} }
@@ -38,6 +54,12 @@ export function createGraphExplorerTool(graph: Graph) {
const neighbors = graph.neighbors(nodeId); const neighbors = graph.neighbors(nodeId);
const nodeAttr = graph.getNodeAttributes(nodeId); const nodeAttr = graph.getNodeAttributes(nodeId);
log.debug(LogOperation.NODE_FOUND, {
node_id: nodeId,
type: nodeAttr.type,
neighbor_count: neighbors.length,
});
let result = `Information for ${nodeId} (${nodeAttr.type}):\n`; let result = `Information for ${nodeId} (${nodeAttr.type}):\n`;
if (nodeAttr.title) result += `- Title: ${nodeAttr.title}\n`; if (nodeAttr.title) result += `- Title: ${nodeAttr.title}\n`;
if (nodeAttr.file) result += `- File: ${nodeAttr.file}\n`; if (nodeAttr.file) result += `- File: ${nodeAttr.file}\n`;
@@ -50,10 +72,25 @@ export function createGraphExplorerTool(graph: Graph) {
result += `- ${neighbor} (${attr.type}) via ${edgeAttr.type}${attr.title ? `: ${attr.title}` : ""}\n`; result += `- ${neighbor} (${attr.type}) via ${edgeAttr.type}${attr.title ? `: ${attr.title}` : ""}\n`;
}); });
op.end({
status: "found",
node_id: nodeId,
type: nodeAttr.type,
neighbor_count: neighbors.length,
});
return result; return result;
} }
log.warn(LogOperation.NODE_NOT_FOUND, {
query,
attempted_id: nodeId,
});
op.end({ status: "not_found", query });
return `No information found in graph for ${query}. Use ask262_search_spec_sections to search text.`; return `No information found in graph for ${query}. Use ask262_search_spec_sections to search text.`;
}, },
);
},
}); });
} }
+52 -5
View File
@@ -4,6 +4,8 @@
*/ */
import { RERANKER_MODEL } from "../constants.js"; import { RERANKER_MODEL } from "../constants.js";
import { LogOperation, logger } from "../lib/logger.js";
import { withSpan } from "../lib/tracing.js";
const OLLAMA_HOST = process.env.OLLAMA_HOST || "http://localhost:11434"; const OLLAMA_HOST = process.env.OLLAMA_HOST || "http://localhost:11434";
@@ -23,6 +25,21 @@ export async function rerankDocuments<T extends { pageContent: string }>(
query: string, query: string,
documents: T[], documents: T[],
): Promise<RerankResult<T>[]> { ): Promise<RerankResult<T>[]> {
const log = await logger.forComponent("reranker");
log.info(LogOperation.RERANKING_DOCUMENTS, {
document_count: documents.length,
});
return await withSpan(
LogOperation.RERANKING_DOCUMENTS,
{ document_count: documents.length },
async () => {
const op = log.start(LogOperation.RERANKING_DOCUMENTS, {
document_count: documents.length,
model: RERANKER_MODEL,
});
try { try {
const response = await fetch(`${OLLAMA_HOST}/api/rerank`, { const response = await fetch(`${OLLAMA_HOST}/api/rerank`, {
method: "POST", method: "POST",
@@ -35,9 +52,15 @@ export async function rerankDocuments<T extends { pageContent: string }>(
}); });
if (!response.ok) { if (!response.ok) {
console.warn( log.warn("reranker_api_failed", {
`Reranker API failed: ${response.statusText}. Returning all documents.`, status: response.status,
); statusText: response.statusText,
});
op.end({
status: "api_failed",
fallback: true,
document_count: documents.length,
});
return documents.map((doc, i) => ({ return documents.map((doc, i) => ({
document: doc, document: doc,
score: 1.0, score: 1.0,
@@ -47,6 +70,12 @@ export async function rerankDocuments<T extends { pageContent: string }>(
const data = await response.json(); const data = await response.json();
if (!data.results || !Array.isArray(data.results)) { if (!data.results || !Array.isArray(data.results)) {
log.warn("reranker_invalid_response", { response: data });
op.end({
status: "invalid_response",
fallback: true,
document_count: documents.length,
});
return documents.map((doc, i) => ({ return documents.map((doc, i) => ({
document: doc, document: doc,
score: 1.0, score: 1.0,
@@ -54,6 +83,12 @@ export async function rerankDocuments<T extends { pageContent: string }>(
})); }));
} }
// op.end logs the final success with all metrics
op.end({
status: "success",
results_count: data.results.length,
});
return data.results.map( return data.results.map(
(result: { index: number; relevance_score: number }) => ({ (result: { index: number; relevance_score: number }) => ({
document: documents[result.index], document: documents[result.index],
@@ -62,7 +97,19 @@ export async function rerankDocuments<T extends { pageContent: string }>(
}), }),
); );
} catch (error) { } catch (error) {
console.warn(`Reranker error: ${error}. Returning all documents.`); const err = error instanceof Error ? error : new Error(String(error));
return documents.map((doc, i) => ({ document: doc, score: 1.0, index: i })); log.warn("reranker_error", { error: err.message });
op.end({
status: "error",
error: err.message,
fallback: true,
});
return documents.map((doc, i) => ({
document: doc,
score: 1.0,
index: i,
}));
} }
},
);
} }
+43 -4
View File
@@ -6,6 +6,8 @@
import type { Table } from "@lancedb/lancedb"; import type { Table } from "@lancedb/lancedb";
import type { Embeddings } from "@langchain/core/embeddings"; import type { Embeddings } from "@langchain/core/embeddings";
import { z } from "zod"; import { z } from "zod";
import { LogOperation, logger } from "../lib/logger.js";
import { withSpan } from "../lib/tracing.js";
// #region Zod schemas (not exported) // #region Zod schemas (not exported)
@@ -72,11 +74,36 @@ export function createSearchSpecSectionsTool(
embeddings: Embeddings, embeddings: Embeddings,
) { ) {
return async ({ query }: SearchSpecInput): Promise<SearchSpecOutput> => { return async ({ query }: SearchSpecInput): Promise<SearchSpecOutput> => {
// Generate embedding for the query const log = await logger.forComponent("search-tool");
const queryVector = await embeddings.embedQuery(query);
// Search using LanceDB directly, limit to top 5 results log.info(LogOperation.SEARCH_SPEC_SECTIONS, { query });
const results = await table.search(queryVector).limit(5).toArray();
return await withSpan(
LogOperation.SEARCHING_SPEC_SECTIONS,
{ query },
async () => {
const op = log.start(LogOperation.SEARCHING_SPEC_SECTIONS, { query });
const SEARCH_LIMIT = 5;
try {
// Generate embedding for the query (timed operation)
const embedOp = log.start(LogOperation.GENERATING_EMBEDDING, {
query,
});
const queryVector = await embeddings.embedQuery(query);
embedOp.end();
// Search using LanceDB directly, limit to top results (timed operation)
const searchOp = log.start(LogOperation.QUERYING_LANCEDB, {
query,
limit: SEARCH_LIMIT,
});
const results = await table
.search(queryVector)
.limit(SEARCH_LIMIT)
.toArray();
searchOp.end({ results_found: results.length });
// Return documents with metadata as structured objects // Return documents with metadata as structured objects
const output: SearchSpecResult[] = results.map( const output: SearchSpecResult[] = results.map(
@@ -89,6 +116,18 @@ export function createSearchSpecSectionsTool(
}), }),
); );
op.end({
results: output.length,
section_ids: output.map((r) => r.sectionId),
});
return { results: output }; return { results: output };
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
log.error(LogOperation.SEARCHING_SPEC_SECTIONS, { query }, error);
throw err;
}
},
);
}; };
} }
+3
View File
@@ -66,6 +66,9 @@ export function createEmbeddings(provider?: EmbeddingProvider): Embeddings {
} }
} }
// Provider logging is done via console.error to stderr
// Detailed embedding operation logging is in the individual embedding classes
/** /**
* Get the currently configured embedding provider. * Get the currently configured embedding provider.
* *
+43 -3
View File
@@ -1,4 +1,5 @@
import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings";
import { LogOperation, logger } from "./logger.js";
/** /**
* Interface for FireworksEmbeddings parameters. * Interface for FireworksEmbeddings parameters.
@@ -97,15 +98,37 @@ export class FireworksEmbeddings extends Embeddings {
return []; return [];
} }
const log = await logger.forComponent("fireworks-embeddings");
const op = log.start(LogOperation.EMBEDDING_DOCUMENTS, {
total_documents: documents.length,
batch_size: this.batchSize,
model: this.modelName,
});
const allEmbeddings: number[][] = []; const allEmbeddings: number[][] = [];
const totalBatches = Math.ceil(documents.length / this.batchSize);
// Process in batches // Process in batches
for (let i = 0; i < documents.length; i += this.batchSize) { for (let i = 0; i < documents.length; i += this.batchSize) {
const batchNum = Math.floor(i / this.batchSize) + 1;
const batch = documents.slice(i, i + this.batchSize); const batch = documents.slice(i, i + this.batchSize);
log.debug(LogOperation.PROCESSING_EMBEDDING_BATCH, {
batch_num: batchNum,
total_batches: totalBatches,
batch_size: batch.length,
});
const batchEmbeddings = await this.embedBatchWithRetry(batch); const batchEmbeddings = await this.embedBatchWithRetry(batch);
allEmbeddings.push(...batchEmbeddings); allEmbeddings.push(...batchEmbeddings);
} }
op.end({
total_documents: documents.length,
batches: totalBatches,
embeddings_generated: allEmbeddings.length,
});
return allEmbeddings; return allEmbeddings;
} }
@@ -116,7 +139,15 @@ export class FireworksEmbeddings extends Embeddings {
documents: string[], documents: string[],
attempt = 1, attempt = 1,
): Promise<number[][]> { ): Promise<number[][]> {
const log = await logger.forComponent("fireworks-embeddings");
try { try {
log.debug(LogOperation.PROCESSING_EMBEDDING_BATCH, {
batch_size: documents.length,
attempt,
model: this.modelName,
});
return await this.embedBatch(documents); return await this.embedBatch(documents);
} catch (error) { } catch (error) {
// Check if it's a rate limit error (429) // Check if it's a rate limit error (429)
@@ -126,13 +157,22 @@ export class FireworksEmbeddings extends Embeddings {
if (isRateLimit && attempt < this.maxRetries) { if (isRateLimit && attempt < this.maxRetries) {
const delay = this.initialRetryDelayMs * 2 ** (attempt - 1); const delay = this.initialRetryDelayMs * 2 ** (attempt - 1);
console.error( log.warn(LogOperation.RETRYING_RATE_LIMIT, {
`[Fireworks] Rate limit hit. Waiting ${delay}ms before retry ${attempt}/${this.maxRetries}...`, attempt,
); max_retries: this.maxRetries,
delay_ms: delay,
batch_size: documents.length,
});
await sleep(delay); await sleep(delay);
return this.embedBatchWithRetry(documents, attempt + 1); return this.embedBatchWithRetry(documents, attempt + 1);
} }
log.error(
LogOperation.PROCESSING_EMBEDDING_BATCH,
{ batch_size: documents.length, attempt, is_rate_limit: isRateLimit },
error instanceof Error ? error : new Error(String(error)),
);
// Fail fast for other errors or if retries exhausted // Fail fast for other errors or if retries exhausted
throw error; throw error;
} }
+424
View File
@@ -0,0 +1,424 @@
/**
* Centralized logging module for Ask262 MCP server.
*
* Provides structured JSON logging with OpenTelemetry-style tracing support.
* Uses Pino for high-performance logging with dual output:
* - File: JSON Lines format for DuckDB querying
* - Console: Pretty-printed for development visibility
*
* @module lib/logger
*/
import { createWriteStream } from "node:fs";
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import pino from "pino";
import pretty from "pino-pretty";
import { getTraceContext } from "./tracing.js";
/**
* Log levels supported by the logger.
*/
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
/**
* Standardized operation names for logging.
* Using an enum ensures consistency across the codebase.
*
* Tense conventions:
* - Present continuous (-ing) for timed operations that have start/end
* - Past tense for lifecycle events and completion states
* - Tool names for MCP tool invocations (logged at info level)
*/
export enum LogOperation {
// Server lifecycle (past tense for events)
SERVER_STARTED = "server_started",
SERVER_STOPPED = "server_stopped",
// HTTP handling (present continuous for timed request handling)
HANDLING_MCP_HTTP_REQUEST = "handling_mcp_http_request",
// MCP Tool invocations (tool names, logged at info level)
SEARCH_SPEC_SECTIONS = "search_spec_sections",
GET_SECTION_CONTENT = "get_section_content",
EVALUATE_IN_ENGINE262 = "evaluate_in_engine262",
// MCP Tool spans (present continuous tense of tool names)
// Note: These are the same as the timed operation names below for consistency
// Vector search operations (present continuous)
SEARCHING_SPEC_SECTIONS = "searching_spec_sections",
GENERATING_EMBEDDING = "generating_embedding",
QUERYING_LANCEDB = "querying_lancedb",
// Section fetch operations (present continuous)
FETCHING_SECTION_CONTENT = "fetching_section_content",
QUERYING_TABLE = "querying_table",
// Code execution operations (present continuous for timed)
EVALUATING_IN_ENGINE262 = "evaluating_in_engine262",
SPAWNING_CHILD_PROCESS = "spawning_child_process",
// Completion states (engine262 spec terminology - normal/abrupt completion)
ENGINE262_NORMAL_COMPLETION = "engine262_normal_completion",
ENGINE262_ABRUPT_COMPLETION = "engine262_abrupt_completion",
// Reranking operations (present continuous)
RERANKING_DOCUMENTS = "reranking_documents",
// Graph exploration (present continuous for actions, past for results)
EXPLORING_GRAPH = "exploring_graph",
RESOLVING_NODE_ID = "resolving_node_id",
NODE_FOUND = "node_found",
NODE_NOT_FOUND = "node_not_found",
// Embedding batch operations (present continuous)
EMBEDDING_DOCUMENTS = "embedding_documents",
PROCESSING_EMBEDDING_BATCH = "processing_embedding_batch",
RETRYING_RATE_LIMIT = "retrying_rate_limit",
}
/**
* Numeric log level values (Pino convention).
*/
const LOG_LEVEL_VALUES: Record<LogLevel, number> = {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
};
/**
* Valid log level strings.
*/
const VALID_LOG_LEVELS: LogLevel[] = [
"trace",
"debug",
"info",
"warn",
"error",
];
/**
* Components that can log in the application.
*/
export type LogComponent =
| "http-server"
| "stdio-server"
| "search-tool"
| "get-section-tool"
| "engine262-runner"
| "reranker"
| "graph-explorer"
| "embeddings-factory"
| "fireworks-embeddings";
/**
* Get the file log level from environment.
* HTTP server defaults to 'debug', stdio defaults to 'info'.
*
* @returns The configured file log level
*/
function getFileLogLevel(): LogLevel {
const envLevel = process.env.ASK262_LOG_LEVEL?.toLowerCase();
if (envLevel && VALID_LOG_LEVELS.includes(envLevel as LogLevel)) {
return envLevel as LogLevel;
}
// Default: debug for HTTP, info for stdio
return "debug";
}
/**
* Get the console log level.
* Console shows max('info', file level) - never shows debug.
*
* @returns The calculated console log level
*/
function getConsoleLogLevel(): LogLevel {
const fileLevel = getFileLogLevel();
const fileLevelValue = LOG_LEVEL_VALUES[fileLevel];
const infoLevelValue = LOG_LEVEL_VALUES.info;
// Console level is max of (info, file level)
return fileLevelValue > infoLevelValue ? fileLevel : "info";
}
/**
* Get the log directory from environment.
*
* @returns The configured log directory path
*/
function getLogDir(): string {
return process.env.ASK262_LOG_DIR ?? "./logs";
}
/**
* Ensure the log directory exists.
* Creates the directory recursively if it doesn't exist.
*
* @throws Error if directory cannot be created
*/
async function ensureLogDir(): Promise<void> {
const logDir = getLogDir();
try {
await mkdir(logDir, { recursive: true });
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
throw new Error(
`Failed to create log directory '${logDir}': ${errorMsg}. ` +
"Check permissions or set ASK262_LOG_DIR to a writable location.",
);
}
}
/**
* Fields to redact from logs for security.
*/
const REDACT_FIELDS = [
"FIREWORKS_API_KEY",
"api_key",
"authorization",
"password",
"secret",
"token",
];
/**
* Create the root Pino logger instance.
*
* @returns Configured Pino logger with dual transport
*/
async function createRootLogger(): Promise<pino.Logger> {
await ensureLogDir();
const logDir = getLogDir();
const logFile = join(logDir, "ask262.jsonl");
const fileLevel = getFileLogLevel();
const consoleLevel = getConsoleLogLevel();
// File transport: JSON Lines format, synchronous writes
const fileStream = createWriteStream(logFile, { flags: "a" });
// Console transport: Pretty printed
const consoleStream = pretty({
colorize: true,
translateTime: "SYS:standard",
ignore: "pid,hostname",
});
return pino(
{
level: fileLevel,
redact: {
paths: REDACT_FIELDS,
remove: true,
censor: "[REDACTED]",
},
mixin() {
// Add trace context if available
const traceCtx = getTraceContext();
if (traceCtx) {
return {
trace_id: traceCtx.traceId,
span_id: traceCtx.spanId,
parent_span_id: traceCtx.parentSpanId,
};
}
return {};
},
formatters: {
level(label: string) {
return { level: label };
},
},
timestamp: () => `,"timestamp":"${new Date().toISOString()}"`,
},
pino.multistream([
{ stream: fileStream, level: fileLevel },
{ stream: consoleStream, level: consoleLevel },
]),
);
}
// Singleton root logger instance
let rootLogger: pino.Logger | null = null;
/**
* Get or create the root logger instance.
*
* @returns The root logger
*/
async function getRootLogger(): Promise<pino.Logger> {
if (!rootLogger) {
rootLogger = await createRootLogger();
}
return rootLogger;
}
/**
* Interface for timed operations.
*/
export interface TimedOperation {
/**
* End the timed operation and log the result.
*
* @param resultAttrs - Additional attributes to log with the result
*/
end(resultAttrs?: Record<string, unknown>): void;
}
/**
* Interface for component-bound loggers.
*/
export interface ComponentLogger {
/**
* Log at trace level.
*
* @param operation - The operation being performed (use LogOperation enum)
* @param attrs - Additional attributes
*/
trace(
operation: LogOperation | string,
attrs?: Record<string, unknown>,
): void;
/**
* Log at debug level.
*
* @param operation - The operation being performed (use LogOperation enum)
* @param attrs - Additional attributes
*/
debug(
operation: LogOperation | string,
attrs?: Record<string, unknown>,
): void;
/**
* Log at info level.
*
* @param operation - The operation being performed (use LogOperation enum)
* @param attrs - Additional attributes
*/
info(operation: LogOperation | string, attrs?: Record<string, unknown>): void;
/**
* Log at warn level.
*
* @param operation - The operation being performed (use LogOperation enum)
* @param attrs - Additional attributes
*/
warn(operation: LogOperation | string, attrs?: Record<string, unknown>): void;
/**
* Log at error level.
*
* @param operation - The operation being performed (use LogOperation enum)
* @param attrs - Additional attributes
* @param error - Optional error to include
*/
error(
operation: LogOperation | string,
attrs?: Record<string, unknown>,
error?: Error,
): void;
/**
* Start a timed operation.
*
* @param operation - The operation name (use LogOperation enum)
* @param attrs - Initial attributes
* @returns Timed operation handle
*/
start(
operation: LogOperation | string,
attrs?: Record<string, unknown>,
): TimedOperation;
}
/**
* Create a logger bound to a specific component.
*
* @param component - The component name (e.g., 'search-tool')
* @returns Component-bound logger
*
* @example
* ```typescript
* const log = logger.forComponent('search-tool');
*
* // Simple log
* log.info('vector_search_started', { query: 'how does array.map work' });
*
* // Timed operation
* const op = log.start('vector_search', { query: 'how does array.map work' });
* const results = await doSearch(query);
* op.end({ results: results.length });
* ```
*/
export async function forComponent(
component: LogComponent,
): Promise<ComponentLogger> {
const root = await getRootLogger();
return {
trace(operation: string, attrs?: Record<string, unknown>) {
root.trace({ component, operation, ...attrs });
},
debug(operation: string, attrs?: Record<string, unknown>) {
root.debug({ component, operation, ...attrs });
},
info(operation: string, attrs?: Record<string, unknown>) {
root.info({ component, operation, ...attrs });
},
warn(operation: string, attrs?: Record<string, unknown>) {
root.warn({ component, operation, ...attrs });
},
error(operation: string, attrs?: Record<string, unknown>, error?: Error) {
if (error) {
root.error({ component, operation, err: error, ...attrs });
} else {
root.error({ component, operation, ...attrs });
}
},
start(operation: string, attrs?: Record<string, unknown>): TimedOperation {
const startTime = performance.now();
// Log start
root.debug({ component, operation, status: "started", ...attrs });
return {
end(resultAttrs?: Record<string, unknown>) {
const durationMs = Math.round(performance.now() - startTime);
root.debug({
component,
operation,
status: "completed",
duration_ms: durationMs,
...attrs,
...resultAttrs,
});
},
};
},
};
}
/**
* Logger factory function.
*
* Use this to get component-bound loggers:
* ```typescript
* const log = await logger.forComponent('search-tool');
* ```
*/
export const logger = {
forComponent,
};
// Export for direct use in simple cases
export { getRootLogger };
+276
View File
@@ -0,0 +1,276 @@
/**
* OpenTelemetry trace context management for Ask262 MCP server.
*
* Provides AsyncLocalStorage-based context propagation for nested operations,
* enabling automatic parent-child span relationships without manual ID passing.
*
* @module lib/tracing
*/
import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
import { SpanStatusCode, trace } from "@opentelemetry/api";
/**
* Trace context stored in AsyncLocalStorage.
*/
interface TraceContext {
/** The root trace identifier */
traceId: string;
/** The current span identifier */
spanId: string;
/** Parent span identifier (null for root spans) */
parentSpanId: string | null;
/** Span depth level (0 = root) */
depth: number;
}
// AsyncLocalStorage for automatic context propagation
const traceStorage = new AsyncLocalStorage<TraceContext>();
/**
* Get the current trace context from AsyncLocalStorage.
*
* @returns Current trace context or undefined if not in a trace
*/
function getCurrentContext(): TraceContext | undefined {
return traceStorage.getStore();
}
/**
* Generate a unique span ID.
*
* @returns Short span ID (16 hex chars)
*/
function generateSpanId(): string {
return randomUUID().replace(/-/g, "").slice(0, 16);
}
/**
* Generate a unique trace ID.
*
* @returns Full trace ID (32 hex chars)
*/
function generateTraceId(): string {
return randomUUID().replace(/-/g, "");
}
/**
* Create a new trace context for a root operation.
*
* @param traceId - Optional existing trace ID (e.g., from request header)
* @returns New trace context
*
* @example
* ```typescript
* // Create new trace
* const traceCtx = createTraceContext();
*
* // Or use existing trace ID from request
* const traceCtx = createTraceContext(req.headers['x-request-id'] as string);
* ```
*/
export function createTraceContext(traceId?: string): TraceContext {
return {
traceId: traceId ?? generateTraceId(),
spanId: generateSpanId(),
parentSpanId: null,
depth: 0,
};
}
/**
* Execute a function within a trace context.
*
* This creates a new span and runs the function with that span as the active
* context. Any nested operations will automatically inherit this context.
*
* @param operation - The operation name for the span
* @param attributes - Initial span attributes
* @param fn - The function to execute within the span
* @param traceId - Optional trace ID to use (e.g., from request header)
* @returns Result of the function
*
* @example
* ```typescript
* const result = await withSpan(
* 'mcp_request',
* { tool: 'search-spec-sections' },
* async () => {
* // All code here has access to the span context
* return await handleRequest();
* }
* );
* ```
*/
export async function withSpan<T>(
operation: string,
attributes: Record<string, unknown> = {},
fn: () => Promise<T>,
traceId?: string,
): Promise<T> {
const parentContext = getCurrentContext();
const tracer = trace.getTracer("ask262");
// Build span context
const spanContext: TraceContext = parentContext
? {
traceId: parentContext.traceId,
spanId: generateSpanId(),
parentSpanId: parentContext.spanId,
depth: parentContext.depth + 1,
}
: createTraceContext(traceId);
// Create OTel span for context tracking
const span = tracer.startSpan(operation, {
attributes: {
...attributes,
"span.depth": spanContext.depth,
},
});
// Store in AsyncLocalStorage for nested calls
return traceStorage.run(spanContext, async () => {
try {
const result = await fn();
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: err instanceof Error ? err.message : String(err),
});
span.recordException(err instanceof Error ? err : new Error(String(err)));
throw err;
} finally {
span.end();
}
});
}
/**
* Get the current trace ID if in a trace context.
*
* @returns Trace ID or undefined
*/
export function getTraceId(): string | undefined {
return getCurrentContext()?.traceId;
}
/**
* Get the current span ID if in a trace context.
*
* @returns Span ID or undefined
*/
export function getSpanId(): string | undefined {
return getCurrentContext()?.spanId;
}
/**
* Get the parent span ID if in a trace context.
*
* @returns Parent span ID or undefined/null
*/
export function getParentSpanId(): string | null | undefined {
return getCurrentContext()?.parentSpanId;
}
/**
* Create a process-scoped trace context for stdio server.
*
* This creates a single trace ID that persists for the entire process lifetime,
* suitable for stdio transport where there's no natural request boundary.
*
* @returns Process-scoped trace context
*
* @example
* ```typescript
* // At server startup
* const sessionTraceId = createProcessScopedTrace();
*
* // For each message, use the same trace ID
* await withSpanContext(sessionTraceId, 'mcp_request', async () => {
* // handle message
* });
* ```
*/
export function createProcessScopedTrace(): string {
return generateTraceId();
}
/**
* Execute a function with a specific trace context.
*
* Similar to withSpan but uses an existing trace ID, useful for stdio
* where you want the same trace ID across multiple operations.
*
* @param traceId - The trace ID to use
* @param operation - The operation name
* @param attributes - Span attributes
* @param fn - The function to execute
* @returns Result of the function
*/
export async function withSpanContext<T>(
traceId: string,
operation: string,
attributes: Record<string, unknown> = {},
fn: () => Promise<T>,
): Promise<T> {
// Check if we're already in a context
const existingContext = getCurrentContext();
if (existingContext && existingContext.traceId === traceId) {
// Already in this trace, create child span
return withSpan(operation, attributes, fn);
}
// Create new root span with this trace ID
const spanContext: TraceContext = {
traceId,
spanId: generateSpanId(),
parentSpanId: null,
depth: 0,
};
const tracer = trace.getTracer("ask262");
const span = tracer.startSpan(operation, {
attributes: {
...attributes,
"span.depth": 0,
},
});
return traceStorage.run(spanContext, async () => {
try {
const result = await fn();
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: err instanceof Error ? err.message : String(err),
});
span.recordException(err instanceof Error ? err : new Error(String(err)));
throw err;
} finally {
span.end();
}
});
}
/**
* Trace context for HTTP server requests.
*
* Creates a new trace context for each request, optionally using
* an existing trace ID from request headers.
*
* @param requestId - Optional request ID from headers
* @returns New trace context for this request
*/
export function createHttpTraceContext(requestId?: string): TraceContext {
return createTraceContext(requestId);
}
// Re-export for convenience
export { getCurrentContext as getTraceContext };
+61 -27
View File
@@ -35,6 +35,8 @@ import {
} from "./agent-tools/index.js"; } from "./agent-tools/index.js";
import { DEFAULT_PORT, STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js"; import { DEFAULT_PORT, STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js";
import { createEmbeddings } from "./lib/embeddings-factory.js"; import { createEmbeddings } from "./lib/embeddings-factory.js";
import { LogOperation, logger } from "./lib/logger.js";
import { withSpan } from "./lib/tracing.js";
// Resolve storage path relative to this script's directory // Resolve storage path relative to this script's directory
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -88,11 +90,7 @@ async function createMcpServer() {
}, },
}, },
async ({ query }) => { async ({ query }) => {
console.log(`[TOOL] ${searchSpecToolName}: query="${query}"`);
const result = await searchSpecTool({ query }); const result = await searchSpecTool({ query });
console.log(
`[TOOL] ${searchSpecToolName}: ${result.results.length} results`,
);
return { return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }], content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
structuredContent: result, structuredContent: result,
@@ -115,17 +113,7 @@ async function createMcpServer() {
}, },
}, },
async ({ sectionIds, recursive }) => { async ({ sectionIds, recursive }) => {
console.log(
`[TOOL] ${sectionContentToolName}: sectionIds=[${sectionIds.map((id: string) => `"${id}"`).join(", ")}] recursive=${recursive}`,
);
const result = await getSectionContentTool({ sectionIds, recursive }); const result = await getSectionContentTool({ sectionIds, recursive });
const totalContentLength = result.sections.reduce(
(sum: number, s: { content: string }) => sum + s.content.length,
0,
);
console.log(
`[TOOL] ${sectionContentToolName}: ${totalContentLength} chars, ${result.sections.length} sections`,
);
return { return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }], content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
structuredContent: result, structuredContent: result,
@@ -148,16 +136,8 @@ async function createMcpServer() {
}, },
}, },
async ({ code }) => { async ({ code }) => {
console.log(`[TOOL] ${evaluateToolName}: code length=${code.length}`);
const result = await evaluateTool({ code }); const result = await evaluateTool({ code });
const isError = result.error !== undefined; const isError = result.error !== undefined;
if (isError) {
console.log(`[TOOL] ${evaluateToolName}: error - ${result.error}`);
} else {
console.log(
`[TOOL] ${evaluateToolName}: ${result.importantSections.length} important, ${result.otherSections.length} other sections`,
);
}
const text = isError ? result.error : JSON.stringify(result, null, 2); const text = isError ? result.error : JSON.stringify(result, null, 2);
return { return {
content: [{ type: "text", text }], content: [{ type: "text", text }],
@@ -230,6 +210,9 @@ Key principles:
} }
export async function main() { export async function main() {
// Initialize HTTP server logger
const log = await logger.forComponent("http-server");
// Create Hono app // Create Hono app
const app = new Hono(); const app = new Hono();
@@ -269,6 +252,13 @@ export async function main() {
// MCP endpoint - handles GET and POST (HEAD is handled by middleware above) // MCP endpoint - handles GET and POST (HEAD is handled by middleware above)
// Must be defined BEFORE inspector (which mounts at /) for proper route matching // Must be defined BEFORE inspector (which mounts at /) for proper route matching
app.on(["GET", "POST"], "/mcp", async (c) => { app.on(["GET", "POST"], "/mcp", async (c) => {
// Get client IP from headers or connection
const clientIp =
c.req.header("x-forwarded-for") || c.req.header("x-real-ip") || "unknown";
// Get trace ID from request header or create new
const traceId = c.req.header("x-request-id") || undefined;
// Get parsed body from Hono (automatic JSON parsing) // Get parsed body from Hono (automatic JSON parsing)
let parsedBody: unknown; let parsedBody: unknown;
if (c.req.method === "POST") { if (c.req.method === "POST") {
@@ -280,6 +270,17 @@ export async function main() {
} }
} }
// Handle request within trace context (passing trace ID from header if available)
return await withSpan(
LogOperation.HANDLING_MCP_HTTP_REQUEST,
{ method: c.req.method, client_ip: clientIp },
async () => {
const op = log.start(LogOperation.HANDLING_MCP_HTTP_REQUEST, {
method: c.req.method,
client_ip: clientIp,
});
try {
// Create fresh server and transport for each request (stateless mode) // Create fresh server and transport for each request (stateless mode)
const server = await createMcpServer(); const server = await createMcpServer();
const transport = new WebStandardStreamableHTTPServerTransport({ const transport = new WebStandardStreamableHTTPServerTransport({
@@ -290,10 +291,27 @@ export async function main() {
// Use Web Standard handleRequest method // Use Web Standard handleRequest method
// Hono's c.req.raw is a Web Standard Request // Hono's c.req.raw is a Web Standard Request
const response = await transport.handleRequest(c.req.raw, { parsedBody }); const response = await transport.handleRequest(c.req.raw, {
parsedBody,
});
op.end({ status: "success" });
// Return the Web Standard Response directly // Return the Web Standard Response directly
return response; return response;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
log.error(
LogOperation.HANDLING_MCP_HTTP_REQUEST,
{ status: "error" },
error,
);
op.end({ status: "error", error: error.message });
throw err;
}
},
traceId,
);
}); });
// MCP Inspector at root path - auto-connects to /mcp // MCP Inspector at root path - auto-connects to /mcp
@@ -308,16 +326,32 @@ export async function main() {
}); });
// Start the server // Start the server
console.log(`Ask262 MCP HTTP Server running on http://0.0.0.0:${PORT}`); log.info(LogOperation.SERVER_STARTED, {
console.log(`MCP endpoint: POST http://0.0.0.0:${PORT}/mcp`); port: PORT,
console.log(`Health check: GET http://0.0.0.0:${PORT}/health`); transport: "http",
console.log(`Mode: Stateless JSON (non-streaming)`); mode: "stateless-json",
endpoints: ["/mcp", "/health"],
});
// Minimal console output for startup visibility
console.error(`Ask262 MCP HTTP Server running on http://0.0.0.0:${PORT}`);
console.error(`MCP endpoint: POST http://0.0.0.0:${PORT}/mcp`);
console.error(`Mode: Stateless JSON (non-streaming)`);
serve({ serve({
fetch: app.fetch, fetch: app.fetch,
port: PORT, port: PORT,
hostname: "0.0.0.0", // Bind to all interfaces for container/Docker compatibility hostname: "0.0.0.0", // Bind to all interfaces for container/Docker compatibility
}); });
// Handle graceful shutdown
const shutdown = (signal: string) => {
log.info(LogOperation.SERVER_STOPPED, { signal });
process.exit(0);
};
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
} }
main().catch((error) => { main().catch((error) => {
+46
View File
@@ -36,6 +36,8 @@ import {
} from "./agent-tools/index.js"; } from "./agent-tools/index.js";
import { STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js"; import { STORAGE_DIR as STORAGE_DIR_REL } from "./constants.js";
import { createEmbeddings } from "./lib/embeddings-factory.js"; import { createEmbeddings } from "./lib/embeddings-factory.js";
import { LogOperation, logger } from "./lib/logger.js";
import { createProcessScopedTrace, withSpanContext } from "./lib/tracing.js";
// Resolve storage path relative to this script's directory // Resolve storage path relative to this script's directory
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -88,6 +90,17 @@ export interface SearchSpecMCPOutput extends McpToolOutputBase {
const embeddings = createEmbeddings(); const embeddings = createEmbeddings();
export async function main() { export async function main() {
// Initialize stdio server logger
const log = await logger.forComponent("stdio-server");
// Create process-scoped trace ID for this session
const sessionTraceId = createProcessScopedTrace();
log.info(LogOperation.SERVER_STARTED, {
transport: "stdio",
trace_id: sessionTraceId,
});
// Connect to LanceDB // Connect to LanceDB
const db = await lancedbSdk.connect(STORAGE_DIR); const db = await lancedbSdk.connect(STORAGE_DIR);
const table = await db.openTable("spec_vectors"); const table = await db.openTable("spec_vectors");
@@ -124,6 +137,11 @@ export async function main() {
}, },
}, },
async ({ query }: SearchSpecMCPInput): Promise<SearchSpecMCPOutput> => { async ({ query }: SearchSpecMCPInput): Promise<SearchSpecMCPOutput> => {
return await withSpanContext(
sessionTraceId,
"vector_search",
{ tool: searchSpecToolName, query },
async () => {
const result = await searchSpecTool({ query }); const result = await searchSpecTool({ query });
return { return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }], content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
@@ -132,6 +150,8 @@ export async function main() {
}; };
}, },
); );
},
);
// Register get section content tool // Register get section content tool
server.registerTool( server.registerTool(
@@ -150,6 +170,11 @@ export async function main() {
sectionIds, sectionIds,
recursive, recursive,
}: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => { }: GetSectionContentMCPInput): Promise<GetSectionContentMCPOutput> => {
return await withSpanContext(
sessionTraceId,
"section_fetch",
{ tool: sectionContentToolName, section_count: sectionIds.length },
async () => {
const result = await getSectionContentTool({ sectionIds, recursive }); const result = await getSectionContentTool({ sectionIds, recursive });
return { return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }], content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
@@ -158,6 +183,8 @@ export async function main() {
}; };
}, },
); );
},
);
// Register evaluate in engine262 tool // Register evaluate in engine262 tool
server.registerTool( server.registerTool(
@@ -173,6 +200,11 @@ export async function main() {
}, },
}, },
async ({ code }: EvaluateToolMCPInput): Promise<EvaluateToolMCPOutput> => { async ({ code }: EvaluateToolMCPInput): Promise<EvaluateToolMCPOutput> => {
return await withSpanContext(
sessionTraceId,
"code_execution",
{ tool: evaluateToolName, code_length: code.length },
async () => {
const result = await evaluateTool({ code }); const result = await evaluateTool({ code });
const isError = result.error !== undefined; const isError = result.error !== undefined;
const text = isError ? result.error : JSON.stringify(result, null, 2); const text = isError ? result.error : JSON.stringify(result, null, 2);
@@ -183,6 +215,8 @@ export async function main() {
}; };
}, },
); );
},
);
// Register prompt for tool orchestration guidance // Register prompt for tool orchestration guidance
server.registerPrompt( server.registerPrompt(
@@ -248,6 +282,18 @@ Key principles:
await server.connect(transport); await server.connect(transport);
console.error("Ask262 MCP Server running on stdio"); console.error("Ask262 MCP Server running on stdio");
// Handle graceful shutdown
const shutdown = (signal: string) => {
log.info(LogOperation.SERVER_STOPPED, {
signal,
trace_id: sessionTraceId,
});
process.exit(0);
};
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
} }
main().catch((error) => { main().catch((error) => {