@resolveio/server-lib 22.3.221 → 22.3.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/assistant-core-heuristics.d.ts +11 -0
- package/ai/assistant-core-heuristics.js +356 -0
- package/ai/assistant-core-heuristics.js.map +1 -0
- package/ai/resolveio-platform-intelligence-memory-corpus.d.ts +3 -0
- package/ai/resolveio-platform-intelligence-memory-corpus.js +214 -0
- package/ai/resolveio-platform-intelligence-memory-corpus.js.map +1 -0
- package/ai/resolveio-platform-intelligence-memory.d.ts +20 -0
- package/ai/resolveio-platform-intelligence-memory.js +341 -0
- package/ai/resolveio-platform-intelligence-memory.js.map +1 -0
- package/{src/ai/resolveio-platform-intelligence-types.ts → ai/resolveio-platform-intelligence-types.d.ts} +15 -20
- package/ai/resolveio-platform-intelligence-types.js +4 -0
- package/ai/resolveio-platform-intelligence-types.js.map +1 -0
- package/ai/resolveio-platform-intelligence.d.ts +6 -0
- package/ai/resolveio-platform-intelligence.js +463 -0
- package/ai/resolveio-platform-intelligence.js.map +1 -0
- package/client-server-app.d.ts +1 -0
- package/client-server-app.js +68 -0
- package/client-server-app.js.map +1 -0
- package/collections/ai-run.collection.d.ts +3 -0
- package/collections/ai-run.collection.js +170 -0
- package/collections/ai-run.collection.js.map +1 -0
- package/collections/ai-terminal-conversation.collection.d.ts +2 -0
- package/collections/ai-terminal-conversation.collection.js +140 -0
- package/collections/ai-terminal-conversation.collection.js.map +1 -0
- package/collections/ai-terminal-issue-report.collection.d.ts +2 -0
- package/collections/ai-terminal-issue-report.collection.js +148 -0
- package/collections/ai-terminal-issue-report.collection.js.map +1 -0
- package/collections/ai-terminal-message.collection.d.ts +2 -0
- package/collections/ai-terminal-message.collection.js +121 -0
- package/collections/ai-terminal-message.collection.js.map +1 -0
- package/collections/app-setting.collection.d.ts +3 -0
- package/collections/app-setting.collection.js +103 -0
- package/collections/app-setting.collection.js.map +1 -0
- package/collections/app-status.collection.d.ts +3 -0
- package/collections/app-status.collection.js +57 -0
- package/collections/app-status.collection.js.map +1 -0
- package/collections/communication-metric.collection.d.ts +2 -0
- package/collections/communication-metric.collection.js +133 -0
- package/collections/communication-metric.collection.js.map +1 -0
- package/collections/counter.collection.d.ts +3 -0
- package/collections/counter.collection.js +56 -0
- package/collections/counter.collection.js.map +1 -0
- package/collections/cron-job-history.collection.d.ts +3 -0
- package/collections/cron-job-history.collection.js +137 -0
- package/collections/cron-job-history.collection.js.map +1 -0
- package/collections/cron-job.collection.d.ts +3 -0
- package/collections/cron-job.collection.js +92 -0
- package/collections/cron-job.collection.js.map +1 -0
- package/collections/customer-notification.collection.d.ts +3 -0
- package/collections/customer-notification.collection.js +130 -0
- package/collections/customer-notification.collection.js.map +1 -0
- package/collections/customer-portal-password.collection.d.ts +3 -0
- package/collections/customer-portal-password.collection.js +75 -0
- package/collections/customer-portal-password.collection.js.map +1 -0
- package/collections/email-history.collection.d.ts +3 -0
- package/collections/email-history.collection.js +134 -0
- package/collections/email-history.collection.js.map +1 -0
- package/collections/email-verified.collection.d.ts +3 -0
- package/collections/email-verified.collection.js +62 -0
- package/collections/email-verified.collection.js.map +1 -0
- package/collections/file.collection.d.ts +3 -0
- package/collections/file.collection.js +74 -0
- package/collections/file.collection.js.map +1 -0
- package/collections/flag-update.collection.d.ts +3 -0
- package/collections/flag-update.collection.js +57 -0
- package/collections/flag-update.collection.js.map +1 -0
- package/collections/flag.collection.d.ts +3 -0
- package/collections/flag.collection.js +57 -0
- package/collections/flag.collection.js.map +1 -0
- package/collections/log-method-latency.collection.d.ts +3 -0
- package/collections/log-method-latency.collection.js +77 -0
- package/collections/log-method-latency.collection.js.map +1 -0
- package/collections/log-subscription.collection.d.ts +3 -0
- package/collections/log-subscription.collection.js +80 -0
- package/collections/log-subscription.collection.js.map +1 -0
- package/collections/log.collection.d.ts +3 -0
- package/collections/log.collection.js +93 -0
- package/collections/log.collection.js.map +1 -0
- package/collections/logged-in-users.collection.d.ts +3 -0
- package/collections/logged-in-users.collection.js +67 -0
- package/collections/logged-in-users.collection.js.map +1 -0
- package/collections/monitor-cpu.collection.d.ts +3 -0
- package/collections/monitor-cpu.collection.js +65 -0
- package/collections/monitor-cpu.collection.js.map +1 -0
- package/collections/monitor-function.collection.d.ts +3 -0
- package/collections/monitor-function.collection.js +74 -0
- package/collections/monitor-function.collection.js.map +1 -0
- package/collections/monitor-memory.collection.d.ts +3 -0
- package/collections/monitor-memory.collection.js +77 -0
- package/collections/monitor-memory.collection.js.map +1 -0
- package/collections/monitor-mongo.collection.d.ts +3 -0
- package/collections/monitor-mongo.collection.js +71 -0
- package/collections/monitor-mongo.collection.js.map +1 -0
- package/collections/notification.collection.d.ts +3 -0
- package/collections/notification.collection.js +57 -0
- package/collections/notification.collection.js.map +1 -0
- package/collections/openai-usage-ledger.collection.d.ts +2 -0
- package/collections/openai-usage-ledger.collection.js +188 -0
- package/collections/openai-usage-ledger.collection.js.map +1 -0
- package/collections/report-builder-dashboard-builder.collection.d.ts +3 -0
- package/collections/report-builder-dashboard-builder.collection.js +109 -0
- package/collections/report-builder-dashboard-builder.collection.js.map +1 -0
- package/collections/report-builder-library.collection.d.ts +3 -0
- package/collections/report-builder-library.collection.js +87 -0
- package/collections/report-builder-library.collection.js.map +1 -0
- package/collections/report-builder-report.collection.d.ts +4 -0
- package/collections/report-builder-report.collection.js +184 -0
- package/collections/report-builder-report.collection.js.map +1 -0
- package/collections/user-group.collection.d.ts +4 -0
- package/collections/user-group.collection.js +89 -0
- package/collections/user-group.collection.js.map +1 -0
- package/collections/user-guide.collection.d.ts +3 -0
- package/collections/user-guide.collection.js +57 -0
- package/collections/user-guide.collection.js.map +1 -0
- package/collections/user.collection.d.ts +4 -0
- package/collections/user.collection.js +180 -0
- package/collections/user.collection.js.map +1 -0
- package/cron/cron.d.ts +14 -0
- package/cron/cron.js +216 -0
- package/cron/cron.js.map +1 -0
- package/fixtures/cron-jobs.d.ts +1 -0
- package/fixtures/cron-jobs.js +150 -0
- package/fixtures/cron-jobs.js.map +1 -0
- package/fixtures/init.d.ts +1 -0
- package/fixtures/init.js +91 -0
- package/fixtures/init.js.map +1 -0
- package/http/auth.d.ts +2 -0
- package/http/auth.js +951 -0
- package/http/auth.js.map +1 -0
- package/http/health.d.ts +1 -0
- package/http/health.js +11 -0
- package/http/health.js.map +1 -0
- package/http/home.d.ts +1 -0
- package/http/home.js +134 -0
- package/http/home.js.map +1 -0
- package/http/slow-query-publication.d.ts +2 -0
- package/http/slow-query-publication.js +99 -0
- package/http/slow-query-publication.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +19 -0
- package/index.js.map +1 -0
- package/managers/ai-assistant-codex-manager.manager.d.ts +67 -0
- package/managers/ai-assistant-codex-manager.manager.js +1113 -0
- package/managers/ai-assistant-codex-manager.manager.js.map +1 -0
- package/managers/ai-run-evidence.manager.d.ts +36 -0
- package/managers/ai-run-evidence.manager.js +377 -0
- package/managers/ai-run-evidence.manager.js.map +1 -0
- package/managers/communication-metric.manager.d.ts +16 -0
- package/managers/communication-metric.manager.js +134 -0
- package/managers/communication-metric.manager.js.map +1 -0
- package/managers/cron.manager.d.ts +20 -0
- package/managers/cron.manager.js +534 -0
- package/managers/cron.manager.js.map +1 -0
- package/managers/customer-notification-content.manager.d.ts +55 -0
- package/managers/customer-notification-content.manager.js +158 -0
- package/managers/customer-notification-content.manager.js.map +1 -0
- package/managers/diagnostic-manager-bootstrap.d.ts +9 -0
- package/managers/diagnostic-manager-bootstrap.js +260 -0
- package/managers/diagnostic-manager-bootstrap.js.map +1 -0
- package/managers/error-auto-fix.manager.d.ts +149 -0
- package/managers/error-auto-fix.manager.js +3064 -0
- package/managers/error-auto-fix.manager.js.map +1 -0
- package/managers/local-log.manager.d.ts +18 -0
- package/managers/local-log.manager.js +88 -0
- package/managers/local-log.manager.js.map +1 -0
- package/managers/method.manager.d.ts +84 -0
- package/managers/method.manager.js +1964 -0
- package/managers/method.manager.js.map +1 -0
- package/managers/mongo.manager.d.ts +224 -0
- package/managers/mongo.manager.js +5000 -0
- package/managers/mongo.manager.js.map +1 -0
- package/managers/monitor.manager.d.ts +70 -0
- package/managers/monitor.manager.js +550 -0
- package/managers/monitor.manager.js.map +1 -0
- package/managers/openai-usage-ledger.manager.d.ts +30 -0
- package/managers/openai-usage-ledger.manager.js +142 -0
- package/managers/openai-usage-ledger.manager.js.map +1 -0
- package/managers/slow-query-verifier.manager.d.ts +144 -0
- package/managers/slow-query-verifier.manager.js +3857 -0
- package/managers/slow-query-verifier.manager.js.map +1 -0
- package/managers/slow-query.manager.d.ts +28 -0
- package/managers/slow-query.manager.js +468 -0
- package/managers/slow-query.manager.js.map +1 -0
- package/managers/subscription.manager.d.ts +169 -0
- package/managers/subscription.manager.js +3434 -0
- package/managers/subscription.manager.js.map +1 -0
- package/managers/websocket.manager.d.ts +73 -0
- package/managers/websocket.manager.js +673 -0
- package/managers/websocket.manager.js.map +1 -0
- package/managers/worker-dispatcher.manager.d.ts +120 -0
- package/managers/worker-dispatcher.manager.js +1266 -0
- package/managers/worker-dispatcher.manager.js.map +1 -0
- package/managers/worker-server.manager.d.ts +35 -0
- package/managers/worker-server.manager.js +582 -0
- package/managers/worker-server.manager.js.map +1 -0
- package/methods/accounts.d.ts +2 -0
- package/methods/accounts.js +624 -0
- package/methods/accounts.js.map +1 -0
- package/methods/ai-terminal.d.ts +458 -0
- package/methods/ai-terminal.js +27991 -0
- package/methods/ai-terminal.js.map +1 -0
- package/methods/app-settings.d.ts +2 -0
- package/methods/app-settings.js +169 -0
- package/methods/app-settings.js.map +1 -0
- package/methods/aws.d.ts +2 -0
- package/methods/aws.js +877 -0
- package/methods/aws.js.map +1 -0
- package/methods/collections.d.ts +2 -0
- package/methods/collections.js +719 -0
- package/methods/collections.js.map +1 -0
- package/methods/counters.d.ts +2 -0
- package/methods/counters.js +113 -0
- package/methods/counters.js.map +1 -0
- package/methods/cron-jobs.d.ts +2 -0
- package/methods/cron-jobs.js +2475 -0
- package/methods/cron-jobs.js.map +1 -0
- package/methods/customer-notifications.d.ts +2 -0
- package/methods/customer-notifications.js +528 -0
- package/methods/customer-notifications.js.map +1 -0
- package/methods/diagnostics.d.ts +2 -0
- package/methods/diagnostics.js +703 -0
- package/methods/diagnostics.js.map +1 -0
- package/methods/flag-updates.d.ts +2 -0
- package/methods/flag-updates.js +8 -0
- package/methods/flag-updates.js.map +1 -0
- package/methods/flags.d.ts +2 -0
- package/methods/flags.js +8 -0
- package/methods/flags.js.map +1 -0
- package/methods/logs.d.ts +2 -0
- package/methods/logs.js +751 -0
- package/methods/logs.js.map +1 -0
- package/methods/mongo-explorer.d.ts +2 -0
- package/methods/mongo-explorer.js +1808 -0
- package/methods/mongo-explorer.js.map +1 -0
- package/methods/monitor.d.ts +2 -0
- package/methods/monitor.js +543 -0
- package/methods/monitor.js.map +1 -0
- package/methods/pdf.d.ts +2 -0
- package/methods/pdf.js +1216 -0
- package/methods/pdf.js.map +1 -0
- package/methods/publications.d.ts +1 -0
- package/methods/publications.js +183 -0
- package/methods/publications.js.map +1 -0
- package/methods/report-builder.d.ts +2 -0
- package/methods/report-builder.js +3094 -0
- package/methods/report-builder.js.map +1 -0
- package/methods/support.d.ts +2 -0
- package/methods/support.js +430 -0
- package/methods/support.js.map +1 -0
- package/models/ai-run.model.d.ts +19 -0
- package/models/ai-run.model.js +4 -0
- package/models/ai-run.model.js.map +1 -0
- package/models/ai-terminal-conversation.model.d.ts +17 -0
- package/models/ai-terminal-conversation.model.js +4 -0
- package/models/ai-terminal-conversation.model.js.map +1 -0
- package/models/ai-terminal-issue-report.model.d.ts +19 -0
- package/models/ai-terminal-issue-report.model.js +4 -0
- package/models/ai-terminal-issue-report.model.js.map +1 -0
- package/models/ai-terminal-message.model.d.ts +22 -0
- package/models/ai-terminal-message.model.js +4 -0
- package/models/ai-terminal-message.model.js.map +1 -0
- package/models/app-setting.model.d.ts +16 -0
- package/models/app-setting.model.js +4 -0
- package/models/app-setting.model.js.map +1 -0
- package/{src/models/app-status.model.ts → models/app-status.model.d.ts} +2 -3
- package/models/app-status.model.js +4 -0
- package/models/app-status.model.js.map +1 -0
- package/{src/models/billing-logged-in-users.model.ts → models/billing-logged-in-users.model.d.ts} +4 -5
- package/models/billing-logged-in-users.model.js +4 -0
- package/models/billing-logged-in-users.model.js.map +1 -0
- package/models/collection-document.model.d.ts +21 -0
- package/models/collection-document.model.js +4 -0
- package/models/collection-document.model.js.map +1 -0
- package/models/communication-metric.model.d.ts +20 -0
- package/models/communication-metric.model.js +4 -0
- package/models/communication-metric.model.js.map +1 -0
- package/{src/models/counter.model.ts → models/counter.model.d.ts} +3 -4
- package/models/counter.model.js +4 -0
- package/models/counter.model.js.map +1 -0
- package/models/cron-job-history.model.d.ts +15 -0
- package/models/cron-job-history.model.js +4 -0
- package/models/cron-job-history.model.js.map +1 -0
- package/models/cron-job.model.d.ts +14 -0
- package/models/cron-job.model.js +4 -0
- package/models/cron-job.model.js.map +1 -0
- package/models/customer-notification.model.d.ts +26 -0
- package/models/customer-notification.model.js +4 -0
- package/models/customer-notification.model.js.map +1 -0
- package/models/customer-portal-password.model.d.ts +11 -0
- package/models/customer-portal-password.model.js +4 -0
- package/models/customer-portal-password.model.js.map +1 -0
- package/models/dialog.model.d.ts +23 -0
- package/models/dialog.model.js +4 -0
- package/models/dialog.model.js.map +1 -0
- package/models/email-history.model.d.ts +32 -0
- package/{src/models/email-history.model.ts → models/email-history.model.js} +4 -36
- package/models/email-history.model.js.map +1 -0
- package/{src/models/email-verified.model.ts → models/email-verified.model.d.ts} +5 -6
- package/models/email-verified.model.js +4 -0
- package/models/email-verified.model.js.map +1 -0
- package/{src/models/file.model.ts → models/file.model.d.ts} +7 -8
- package/models/file.model.js +4 -0
- package/models/file.model.js.map +1 -0
- package/{src/models/flag-update.model.ts → models/flag-update.model.d.ts} +3 -4
- package/models/flag-update.model.js +4 -0
- package/models/flag-update.model.js.map +1 -0
- package/{src/models/flag.model.ts → models/flag.model.d.ts} +3 -4
- package/models/flag.model.js +4 -0
- package/models/flag.model.js.map +1 -0
- package/models/log-method-latency.model.d.ts +10 -0
- package/models/log-method-latency.model.js +4 -0
- package/models/log-method-latency.model.js.map +1 -0
- package/{src/models/log-subscription.model.ts → models/log-subscription.model.d.ts} +9 -11
- package/models/log-subscription.model.js +4 -0
- package/models/log-subscription.model.js.map +1 -0
- package/models/log.model.d.ts +17 -0
- package/models/log.model.js +4 -0
- package/models/log.model.js.map +1 -0
- package/{src/models/logged-in-users.model.ts → models/logged-in-users.model.d.ts} +5 -6
- package/models/logged-in-users.model.js +4 -0
- package/models/logged-in-users.model.js.map +1 -0
- package/{src/models/method-response.model.ts → models/method-response.model.d.ts} +6 -7
- package/models/method-response.model.js +4 -0
- package/models/method-response.model.js.map +1 -0
- package/models/method.model.d.ts +26 -0
- package/models/method.model.js +4 -0
- package/models/method.model.js.map +1 -0
- package/{src/models/monitor-cpu.model.ts → models/monitor-cpu.model.d.ts} +7 -9
- package/models/monitor-cpu.model.js +4 -0
- package/models/monitor-cpu.model.js.map +1 -0
- package/models/monitor-function.model.d.ts +14 -0
- package/models/monitor-function.model.js +4 -0
- package/models/monitor-function.model.js.map +1 -0
- package/models/monitor-memory.model.d.ts +15 -0
- package/models/monitor-memory.model.js +4 -0
- package/models/monitor-memory.model.js.map +1 -0
- package/models/monitor-mongo.model.d.ts +13 -0
- package/models/monitor-mongo.model.js +4 -0
- package/models/monitor-mongo.model.js.map +1 -0
- package/{src/models/notification.model.ts → models/notification.model.d.ts} +4 -6
- package/models/notification.model.js +4 -0
- package/models/notification.model.js.map +1 -0
- package/models/openai-usage-ledger.model.d.ts +30 -0
- package/models/openai-usage-ledger.model.js +4 -0
- package/models/openai-usage-ledger.model.js.map +1 -0
- package/models/pagination.model.d.ts +11 -0
- package/models/pagination.model.js +28 -0
- package/models/pagination.model.js.map +1 -0
- package/models/permission.model.d.ts +12 -0
- package/models/permission.model.js +4 -0
- package/models/permission.model.js.map +1 -0
- package/models/report-builder-dashboard-builder.model.d.ts +25 -0
- package/models/report-builder-dashboard-builder.model.js +4 -0
- package/models/report-builder-dashboard-builder.model.js.map +1 -0
- package/models/report-builder-library.model.d.ts +17 -0
- package/models/report-builder-library.model.js +4 -0
- package/models/report-builder-library.model.js.map +1 -0
- package/models/report-builder-report.model.d.ts +121 -0
- package/models/report-builder-report.model.js +4 -0
- package/models/report-builder-report.model.js.map +1 -0
- package/models/report-builder.model.d.ts +61 -0
- package/models/report-builder.model.js +4 -0
- package/models/report-builder.model.js.map +1 -0
- package/models/select-data-label.model.d.ts +9 -0
- package/models/select-data-label.model.js +4 -0
- package/models/select-data-label.model.js.map +1 -0
- package/models/server-message.model.d.ts +32 -0
- package/models/server-message.model.js +4 -0
- package/models/server-message.model.js.map +1 -0
- package/models/slow-query-report.model.d.ts +23 -0
- package/models/slow-query-report.model.js +4 -0
- package/models/slow-query-report.model.js.map +1 -0
- package/models/subscription.model.d.ts +31 -0
- package/models/subscription.model.js +4 -0
- package/models/subscription.model.js.map +1 -0
- package/models/support-ticket.model.d.ts +87 -0
- package/models/support-ticket.model.js +4 -0
- package/models/support-ticket.model.js.map +1 -0
- package/models/user-group.model.d.ts +20 -0
- package/models/user-group.model.js +4 -0
- package/models/user-group.model.js.map +1 -0
- package/{src/models/user-guide.model.ts → models/user-guide.model.d.ts} +4 -5
- package/models/user-guide.model.js +4 -0
- package/models/user-guide.model.js.map +1 -0
- package/models/user.model.d.ts +84 -0
- package/models/user.model.js +4 -0
- package/models/user.model.js.map +1 -0
- package/package.json +1 -1
- package/private/images/ResolveIO.png +0 -0
- package/public_api.js +127 -0
- package/public_api.js.map +1 -0
- package/publications/ai-terminal.d.ts +1 -0
- package/publications/ai-terminal.js +122 -0
- package/publications/ai-terminal.js.map +1 -0
- package/publications/app-settings.d.ts +2 -0
- package/publications/app-settings.js +28 -0
- package/publications/app-settings.js.map +1 -0
- package/publications/app-status.d.ts +2 -0
- package/publications/app-status.js +16 -0
- package/publications/app-status.js.map +1 -0
- package/publications/cron-jobs.d.ts +2 -0
- package/publications/cron-jobs.js +88 -0
- package/publications/cron-jobs.js.map +1 -0
- package/publications/customer-notifications.d.ts +2 -0
- package/publications/customer-notifications.js +161 -0
- package/publications/customer-notifications.js.map +1 -0
- package/publications/files.d.ts +2 -0
- package/publications/files.js +36 -0
- package/publications/files.js.map +1 -0
- package/publications/flags-update.d.ts +2 -0
- package/publications/flags-update.js +22 -0
- package/publications/flags-update.js.map +1 -0
- package/publications/flags.d.ts +2 -0
- package/publications/flags.js +22 -0
- package/publications/flags.js.map +1 -0
- package/publications/logs.d.ts +2 -0
- package/publications/logs.js +164 -0
- package/publications/logs.js.map +1 -0
- package/publications/notifications.d.ts +2 -0
- package/publications/notifications.js +16 -0
- package/publications/notifications.js.map +1 -0
- package/publications/report-builder-dashboard-builders.d.ts +2 -0
- package/publications/report-builder-dashboard-builders.js +42 -0
- package/publications/report-builder-dashboard-builders.js.map +1 -0
- package/publications/report-builder-libraries.d.ts +2 -0
- package/publications/report-builder-libraries.js +90 -0
- package/publications/report-builder-libraries.js.map +1 -0
- package/publications/report-builder-reports.d.ts +2 -0
- package/publications/report-builder-reports.js +50 -0
- package/publications/report-builder-reports.js.map +1 -0
- package/publications/super-admin.d.ts +2 -0
- package/publications/super-admin.js +16 -0
- package/publications/super-admin.js.map +1 -0
- package/publications/user-groups.d.ts +1 -0
- package/publications/user-groups.js +16 -0
- package/publications/user-groups.js.map +1 -0
- package/publications/user-guides.d.ts +1 -0
- package/publications/user-guides.js +16 -0
- package/publications/user-guides.js.map +1 -0
- package/resolveio-server-app.d.ts +70 -0
- package/resolveio-server-app.js +801 -0
- package/resolveio-server-app.js.map +1 -0
- package/server-app.d.ts +228 -0
- package/server-app.js +3566 -0
- package/server-app.js.map +1 -0
- package/services/codex-client.d.ts +128 -0
- package/services/codex-client.js +1629 -0
- package/services/codex-client.js.map +1 -0
- package/services/openai-client.d.ts +46 -0
- package/services/openai-client.js +318 -0
- package/services/openai-client.js.map +1 -0
- package/types/error-report.d.ts +25 -0
- package/types/error-report.js +4 -0
- package/types/error-report.js.map +1 -0
- package/types/slow-query-report.d.ts +27 -0
- package/types/slow-query-report.js +6 -0
- package/types/slow-query-report.js.map +1 -0
- package/util/ai-qa-policy.d.ts +124 -0
- package/util/ai-qa-policy.js +736 -0
- package/util/ai-qa-policy.js.map +1 -0
- package/util/ai-run-evidence-adapters.d.ts +109 -0
- package/util/ai-run-evidence-adapters.js +7234 -0
- package/util/ai-run-evidence-adapters.js.map +1 -0
- package/util/ai-run-evidence-dashboard.d.ts +88 -0
- package/util/ai-run-evidence-dashboard.js +343 -0
- package/util/ai-run-evidence-dashboard.js.map +1 -0
- package/util/ai-run-evidence-eval.d.ts +86 -0
- package/util/ai-run-evidence-eval.js +1018 -0
- package/util/ai-run-evidence-eval.js.map +1 -0
- package/util/ai-run-evidence.d.ts +244 -0
- package/util/ai-run-evidence.js +1096 -0
- package/util/ai-run-evidence.js.map +1 -0
- package/util/ai-runner-artifacts.d.ts +82 -0
- package/util/ai-runner-artifacts.js +713 -0
- package/util/ai-runner-artifacts.js.map +1 -0
- package/util/ai-runner-manager-autopilot.d.ts +210 -0
- package/util/ai-runner-manager-autopilot.js +642 -0
- package/util/ai-runner-manager-autopilot.js.map +1 -0
- package/util/ai-runner-manager-policy.d.ts +807 -0
- package/util/ai-runner-manager-policy.js +3501 -0
- package/util/ai-runner-manager-policy.js.map +1 -0
- package/util/ai-runner-qa-auth.d.ts +5 -0
- package/util/ai-runner-qa-auth.js +839 -0
- package/util/ai-runner-qa-auth.js.map +1 -0
- package/util/ai-runner-qa-tools.d.ts +26 -0
- package/util/ai-runner-qa-tools.js +3520 -0
- package/util/ai-runner-qa-tools.js.map +1 -0
- package/util/aicoder-runner-v6.d.ts +426 -0
- package/util/aicoder-runner-v6.js +2464 -0
- package/util/aicoder-runner-v6.js.map +1 -0
- package/util/common.d.ts +31 -0
- package/util/common.js +683 -0
- package/util/common.js.map +1 -0
- package/util/customer-portal-password.d.ts +13 -0
- package/util/customer-portal-password.js +209 -0
- package/util/customer-portal-password.js.map +1 -0
- package/util/error-reporter.d.ts +52 -0
- package/util/error-reporter.js +326 -0
- package/util/error-reporter.js.map +1 -0
- package/util/error-tracking.d.ts +13 -0
- package/util/error-tracking.js +120 -0
- package/util/error-tracking.js.map +1 -0
- package/util/openai-usage-cost.d.ts +6 -0
- package/util/openai-usage-cost.js +103 -0
- package/util/openai-usage-cost.js.map +1 -0
- package/util/report-builder-unwinds.d.ts +15 -0
- package/util/report-builder-unwinds.js +156 -0
- package/util/report-builder-unwinds.js.map +1 -0
- package/util/runner-process-janitor.d.ts +27 -0
- package/util/runner-process-janitor.js +208 -0
- package/util/runner-process-janitor.js.map +1 -0
- package/util/schema-report-builder.d.ts +6 -0
- package/util/schema-report-builder.js +481 -0
- package/util/schema-report-builder.js.map +1 -0
- package/util/slow-query-reporter.d.ts +28 -0
- package/util/slow-query-reporter.js +226 -0
- package/util/slow-query-reporter.js.map +1 -0
- package/util/subscription-dependency-context.d.ts +34 -0
- package/util/subscription-dependency-context.js +1283 -0
- package/util/subscription-dependency-context.js.map +1 -0
- package/util/support-runner-v5.d.ts +1426 -0
- package/util/support-runner-v5.js +7643 -0
- package/util/support-runner-v5.js.map +1 -0
- package/util/tokenizer.d.ts +5 -0
- package/util/tokenizer.js +41 -0
- package/util/tokenizer.js.map +1 -0
- package/workers/codex-runner.worker.d.ts +1 -0
- package/workers/codex-runner.worker.js +192 -0
- package/workers/codex-runner.worker.js.map +1 -0
- package/.nodemon.json +0 -5
- package/.vscode/settings.json +0 -21
- package/AGENTS.md +0 -195
- package/README.md +0 -22
- package/build_package.sh +0 -5
- package/compileDTS.pl +0 -64
- package/docs/ai-assistant-nightly-eval.md +0 -65
- package/docs/ai-assistant-preflight-checklist.md +0 -23
- package/docs/ai-assistant-report-builder-bridge-playbook.md +0 -115
- package/eslint-plugin-custom/index.js +0 -7
- package/eslint-plugin-custom/rules/no-filter-zero-index.js +0 -44
- package/eslint.config.js +0 -103
- package/gulpfile.js +0 -216
- package/methodAndPublicationListGenerator.py +0 -375
- package/mongodbensurers.js +0 -2
- package/mongostop.js +0 -3
- package/scripts/cleanup-bypassed-callmethod-logs.js +0 -616
- package/settings.development.json +0 -25
- package/settings.development.redacted.json +0 -25
- package/src/.env +0 -12
- package/src/ai/assistant-core-heuristics.ts +0 -379
- package/src/ai/resolveio-platform-intelligence-memory-corpus.ts +0 -185
- package/src/ai/resolveio-platform-intelligence-memory.ts +0 -325
- package/src/ai/resolveio-platform-intelligence.ts +0 -462
- package/src/client-server-app.ts +0 -12
- package/src/collections/ai-run.collection.ts +0 -117
- package/src/collections/ai-terminal-conversation.collection.ts +0 -91
- package/src/collections/ai-terminal-issue-report.collection.ts +0 -99
- package/src/collections/ai-terminal-message.collection.ts +0 -77
- package/src/collections/app-setting.collection.ts +0 -104
- package/src/collections/app-status.collection.ts +0 -58
- package/src/collections/communication-metric.collection.ts +0 -84
- package/src/collections/counter.collection.ts +0 -56
- package/src/collections/cron-job-history.collection.ts +0 -94
- package/src/collections/cron-job.collection.ts +0 -92
- package/src/collections/customer-notification.collection.ts +0 -131
- package/src/collections/customer-portal-password.collection.ts +0 -76
- package/src/collections/email-history.collection.ts +0 -134
- package/src/collections/email-verified.collection.ts +0 -62
- package/src/collections/file.collection.ts +0 -74
- package/src/collections/flag-update.collection.ts +0 -57
- package/src/collections/flag.collection.ts +0 -57
- package/src/collections/log-method-latency.collection.ts +0 -77
- package/src/collections/log-subscription.collection.ts +0 -80
- package/src/collections/log.collection.ts +0 -93
- package/src/collections/logged-in-users.collection.ts +0 -67
- package/src/collections/monitor-cpu.collection.ts +0 -65
- package/src/collections/monitor-function.collection.ts +0 -74
- package/src/collections/monitor-memory.collection.ts +0 -77
- package/src/collections/monitor-mongo.collection.ts +0 -71
- package/src/collections/notification.collection.ts +0 -57
- package/src/collections/openai-usage-ledger.collection.ts +0 -131
- package/src/collections/report-builder-dashboard-builder.collection.ts +0 -109
- package/src/collections/report-builder-library.collection.ts +0 -89
- package/src/collections/report-builder-report.collection.ts +0 -184
- package/src/collections/user-group.collection.ts +0 -89
- package/src/collections/user-guide.collection.ts +0 -57
- package/src/collections/user.collection.ts +0 -181
- package/src/cron/cron.ts +0 -117
- package/src/fixtures/cron-jobs.ts +0 -95
- package/src/fixtures/init.ts +0 -35
- package/src/http/auth.ts +0 -818
- package/src/http/health.ts +0 -7
- package/src/http/home.ts +0 -90
- package/src/http/slow-query-publication.ts +0 -49
- package/src/index.ts +0 -1
- package/src/managers/ai-assistant-codex-manager.manager.ts +0 -1131
- package/src/managers/ai-run-evidence.manager.ts +0 -264
- package/src/managers/communication-metric.manager.ts +0 -82
- package/src/managers/cron.manager.ts +0 -333
- package/src/managers/customer-notification-content.manager.ts +0 -236
- package/src/managers/diagnostic-manager-bootstrap.ts +0 -165
- package/src/managers/error-auto-fix.manager.ts +0 -2767
- package/src/managers/local-log.manager.ts +0 -113
- package/src/managers/method.manager.ts +0 -1857
- package/src/managers/mongo.manager.ts +0 -4575
- package/src/managers/monitor.manager.ts +0 -507
- package/src/managers/openai-usage-ledger.manager.ts +0 -112
- package/src/managers/slow-query-verifier.manager.ts +0 -3590
- package/src/managers/slow-query.manager.ts +0 -519
- package/src/managers/subscription.manager.ts +0 -3128
- package/src/managers/websocket.manager.ts +0 -746
- package/src/managers/worker-dispatcher.manager.ts +0 -1360
- package/src/managers/worker-server.manager.ts +0 -536
- package/src/methods/accounts.ts +0 -532
- package/src/methods/ai-terminal.ts +0 -29070
- package/src/methods/app-settings.ts +0 -114
- package/src/methods/aws.ts +0 -649
- package/src/methods/collections.ts +0 -641
- package/src/methods/counters.ts +0 -69
- package/src/methods/cron-jobs.ts +0 -2614
- package/src/methods/customer-notifications.ts +0 -458
- package/src/methods/diagnostics.ts +0 -616
- package/src/methods/flag-updates.ts +0 -7
- package/src/methods/flags.ts +0 -7
- package/src/methods/logs.ts +0 -657
- package/src/methods/mongo-explorer.ts +0 -1880
- package/src/methods/monitor.ts +0 -540
- package/src/methods/pdf.ts +0 -1236
- package/src/methods/publications.ts +0 -129
- package/src/methods/report-builder.ts +0 -3300
- package/src/methods/support.ts +0 -335
- package/src/models/ai-run.model.ts +0 -27
- package/src/models/ai-terminal-conversation.model.ts +0 -19
- package/src/models/ai-terminal-issue-report.model.ts +0 -21
- package/src/models/ai-terminal-message.model.ts +0 -24
- package/src/models/app-setting.model.ts +0 -17
- package/src/models/collection-document.model.ts +0 -24
- package/src/models/communication-metric.model.ts +0 -23
- package/src/models/cron-job-history.model.ts +0 -16
- package/src/models/cron-job.model.ts +0 -15
- package/src/models/customer-notification.model.ts +0 -28
- package/src/models/customer-portal-password.model.ts +0 -12
- package/src/models/dialog.model.ts +0 -25
- package/src/models/log-method-latency.model.ts +0 -11
- package/src/models/log.model.ts +0 -19
- package/src/models/method.model.ts +0 -25
- package/src/models/monitor-function.model.ts +0 -16
- package/src/models/monitor-memory.model.ts +0 -17
- package/src/models/monitor-mongo.model.ts +0 -15
- package/src/models/openai-usage-ledger.model.ts +0 -56
- package/src/models/pagination.model.ts +0 -35
- package/src/models/permission.model.ts +0 -14
- package/src/models/report-builder-dashboard-builder.model.ts +0 -29
- package/src/models/report-builder-library.model.ts +0 -20
- package/src/models/report-builder-report.model.ts +0 -136
- package/src/models/report-builder.model.ts +0 -68
- package/src/models/select-data-label.model.ts +0 -9
- package/src/models/server-message.model.ts +0 -31
- package/src/models/slow-query-report.model.ts +0 -23
- package/src/models/subscription.model.ts +0 -73
- package/src/models/support-ticket.model.ts +0 -104
- package/src/models/user-group.model.ts +0 -24
- package/src/models/user.model.ts +0 -96
- package/src/private/images/ResolveIO.png +0 -0
- package/src/publications/ai-terminal.ts +0 -73
- package/src/publications/app-settings.ts +0 -25
- package/src/publications/app-status.ts +0 -13
- package/src/publications/cron-jobs.ts +0 -40
- package/src/publications/customer-notifications.ts +0 -101
- package/src/publications/files.ts +0 -33
- package/src/publications/flags-update.ts +0 -19
- package/src/publications/flags.ts +0 -19
- package/src/publications/logs.ts +0 -163
- package/src/publications/notifications.ts +0 -13
- package/src/publications/report-builder-dashboard-builders.ts +0 -39
- package/src/publications/report-builder-libraries.ts +0 -41
- package/src/publications/report-builder-reports.ts +0 -47
- package/src/publications/super-admin.ts +0 -13
- package/src/publications/user-groups.ts +0 -12
- package/src/publications/user-guides.ts +0 -12
- package/src/resolveio-server-app.ts +0 -617
- package/src/server-app.ts +0 -3354
- package/src/services/codex-client.ts +0 -1231
- package/src/services/openai-client.ts +0 -265
- package/src/types/error-report.ts +0 -26
- package/src/types/js-tiktoken.d.ts +0 -11
- package/src/types/slow-query-report.ts +0 -28
- package/src/util/ai-qa-policy.ts +0 -925
- package/src/util/ai-run-evidence-adapters.ts +0 -8347
- package/src/util/ai-run-evidence-dashboard.ts +0 -323
- package/src/util/ai-run-evidence-eval.ts +0 -1057
- package/src/util/ai-run-evidence.ts +0 -1430
- package/src/util/ai-runner-artifacts.ts +0 -586
- package/src/util/ai-runner-manager-autopilot.ts +0 -961
- package/src/util/ai-runner-manager-policy.ts +0 -5011
- package/src/util/ai-runner-qa-auth.ts +0 -838
- package/src/util/ai-runner-qa-tools.ts +0 -3536
- package/src/util/aicoder-runner-v6.ts +0 -3121
- package/src/util/common.ts +0 -649
- package/src/util/customer-portal-password.ts +0 -183
- package/src/util/error-reporter.ts +0 -332
- package/src/util/error-tracking.ts +0 -79
- package/src/util/openai-usage-cost.ts +0 -114
- package/src/util/report-builder-unwinds.ts +0 -180
- package/src/util/runner-process-janitor.ts +0 -219
- package/src/util/schema-report-builder.ts +0 -448
- package/src/util/slow-query-reporter.ts +0 -216
- package/src/util/subscription-dependency-context.ts +0 -1096
- package/src/util/support-runner-v5.ts +0 -10040
- package/src/util/tokenizer.ts +0 -38
- package/src/workers/codex-runner.worker.ts +0 -142
- package/start_server.sh +0 -5
- package/tests/ai-assistant-corpus-build.ts +0 -484
- package/tests/ai-assistant-corpus-replay-e2e.ts +0 -774
- package/tests/ai-assistant-data-parity-e2e.ts +0 -1989
- package/tests/ai-assistant-eval-triage.ts +0 -831
- package/tests/ai-assistant-openai-e2e.ts +0 -1061
- package/tests/ai-assistant-openai-git-e2e.ts +0 -155
- package/tests/ai-assistant-preflight-matrix.ts +0 -215
- package/tests/ai-assistant-routing-eval.test.ts +0 -585
- package/tests/ai-assistant-snf-live-eval.ts +0 -975
- package/tests/ai-assistant-utils.test.ts +0 -4834
- package/tests/ai-manager-autopilot-snapshot.test.ts +0 -193
- package/tests/ai-manager-recovery-checkpoint.test.ts +0 -1383
- package/tests/ai-run-eval.test.ts +0 -132
- package/tests/ai-run-evidence.test.ts +0 -3773
- package/tests/ai-runner-contract.test.ts +0 -515
- package/tests/aicoder-runner-v6.test.ts +0 -822
- package/tests/error-reporter.test.ts +0 -145
- package/tests/method-publication-generator.test.ts +0 -46
- package/tests/report-builder-linking.test.ts +0 -79
- package/tests/resolveio-platform-intelligence.test.ts +0 -352
- package/tests/server-app-cron-owner.test.ts +0 -127
- package/tests/subscription-connect-race.test.ts +0 -158
- package/tests/subscription-dependency-context.test.ts +0 -324
- package/tests/subscription-manager-collection-tracking.test.ts +0 -86
- package/tests/subscription-manager-invalidation.test.ts +0 -86
- package/tests/support-runner-v5.test.ts +0 -3201
- package/tsconfig.json +0 -34
- /package/{src/private → private}/email-templates/enrollment.html +0 -0
- /package/{src/private → private}/email-templates/forgot-password.html +0 -0
- /package/{src/private → private}/email-templates/support-ticket-deleted.html +0 -0
- /package/{src/private → private}/email-templates/support-ticket-modified.html +0 -0
- /package/{src/private → private}/email-templates/support-ticket.html +0 -0
- /package/{src/public_api.ts → public_api.d.ts} +0 -0
|
@@ -1,2767 +0,0 @@
|
|
|
1
|
-
import { ResolveIOServer } from '../resolveio-server-app';
|
|
2
|
-
import { objectIdHexString, pad } from '../util/common';
|
|
3
|
-
import axios, { AxiosRequestConfig } from 'axios';
|
|
4
|
-
import { createHash } from 'crypto';
|
|
5
|
-
import { URL } from 'url';
|
|
6
|
-
import { Users } from '../collections/user.collection';
|
|
7
|
-
import { AppSettings } from '../collections/app-setting.collection';
|
|
8
|
-
import { buildGeneratedAppAutoFixSummary, buildResolveIOProjectAutoFixDetails } from './customer-notification-content.manager';
|
|
9
|
-
import { ErrorReportAttachment, ErrorReportPayload } from '../types/error-report';
|
|
10
|
-
|
|
11
|
-
type AICoderAppModel = Record<string, any>;
|
|
12
|
-
type AIDashboardJob = Record<string, any>;
|
|
13
|
-
type ClientModel = Record<string, any>;
|
|
14
|
-
type ErrorAutoFixLogModel = Record<string, any>;
|
|
15
|
-
|
|
16
|
-
export interface ErrorAutoFixManagerDependencies {
|
|
17
|
-
Clients?: any;
|
|
18
|
-
ErrorAutoFixLogs: any;
|
|
19
|
-
AICoderApps?: any;
|
|
20
|
-
AIDashboardJobs?: any;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const OPTIONAL_COLLECTION = {
|
|
24
|
-
findOne: () => Promise.resolve(null),
|
|
25
|
-
findById: () => Promise.resolve(null),
|
|
26
|
-
find: () => Promise.resolve([])
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
let AICoderApps: any = OPTIONAL_COLLECTION;
|
|
30
|
-
let AIDashboardJobs: any = OPTIONAL_COLLECTION;
|
|
31
|
-
let Clients: any = OPTIONAL_COLLECTION;
|
|
32
|
-
let ErrorAutoFixLogs: any = null;
|
|
33
|
-
let configuredErrorAutoFixDependencies: ErrorAutoFixManagerDependencies | null = null;
|
|
34
|
-
|
|
35
|
-
function resolveErrorAutoFixManagerDependencies(
|
|
36
|
-
overrides?: Partial<ErrorAutoFixManagerDependencies>
|
|
37
|
-
): ErrorAutoFixManagerDependencies {
|
|
38
|
-
const resolved: ErrorAutoFixManagerDependencies = {
|
|
39
|
-
...(configuredErrorAutoFixDependencies || {} as ErrorAutoFixManagerDependencies),
|
|
40
|
-
...(overrides || {})
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
if (!resolved.ErrorAutoFixLogs) {
|
|
44
|
-
throw new Error('ErrorAutoFixManager dependencies are not configured.');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return resolved;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function applyErrorAutoFixDependencies(dependencies: ErrorAutoFixManagerDependencies): void {
|
|
51
|
-
configuredErrorAutoFixDependencies = dependencies;
|
|
52
|
-
Clients = dependencies.Clients || OPTIONAL_COLLECTION;
|
|
53
|
-
ErrorAutoFixLogs = dependencies.ErrorAutoFixLogs;
|
|
54
|
-
AICoderApps = dependencies.AICoderApps || OPTIONAL_COLLECTION;
|
|
55
|
-
AIDashboardJobs = dependencies.AIDashboardJobs || OPTIONAL_COLLECTION;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function registerErrorAutoFixManagerDependencies(dependencies: ErrorAutoFixManagerDependencies): void {
|
|
59
|
-
applyErrorAutoFixDependencies(dependencies);
|
|
60
|
-
ensureErrorAutoFixManager();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface AutoFixConfig {
|
|
64
|
-
enabled: boolean;
|
|
65
|
-
repoRoot: string;
|
|
66
|
-
baseBranch: string;
|
|
67
|
-
branchPrefix: string;
|
|
68
|
-
githubToken?: string;
|
|
69
|
-
githubOwner: string;
|
|
70
|
-
githubRepo: string;
|
|
71
|
-
commandTimeoutMs: number;
|
|
72
|
-
notifyEmails: string[];
|
|
73
|
-
debugLogging: boolean;
|
|
74
|
-
configSource: string;
|
|
75
|
-
openaiEnvironment?: string;
|
|
76
|
-
openaiProjectId?: string;
|
|
77
|
-
autoOptimizeEnabled: boolean;
|
|
78
|
-
ingestKeys: string[];
|
|
79
|
-
dashboardWorkflowEnabled: boolean;
|
|
80
|
-
dashboardFallbackToGithub: boolean;
|
|
81
|
-
dashboardWaitTimeoutMs: number;
|
|
82
|
-
dashboardPublishRequired: boolean;
|
|
83
|
-
maxAutoRunAttemptsPerIssue: number;
|
|
84
|
-
escalationEmails: string[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface ErrorEmail {
|
|
88
|
-
key: string;
|
|
89
|
-
subject: string;
|
|
90
|
-
body: string;
|
|
91
|
-
from?: string;
|
|
92
|
-
messageId?: string;
|
|
93
|
-
hash?: string;
|
|
94
|
-
rawHash?: string;
|
|
95
|
-
issueHash?: string;
|
|
96
|
-
logId?: string;
|
|
97
|
-
raw: string;
|
|
98
|
-
fromAddress?: string;
|
|
99
|
-
id_client?: string;
|
|
100
|
-
client_name?: string;
|
|
101
|
-
source_type?: 'app' | 'infrastructure' | 'noise';
|
|
102
|
-
classification_reason?: string;
|
|
103
|
-
openai_environment?: string;
|
|
104
|
-
sourceApp?: string;
|
|
105
|
-
sourceEnvironment?: string;
|
|
106
|
-
severity?: string;
|
|
107
|
-
reportedBy?: string;
|
|
108
|
-
reportMetadata?: Record<string, any>;
|
|
109
|
-
reportContext?: Record<string, any>;
|
|
110
|
-
attachments?: ErrorReportAttachment[];
|
|
111
|
-
reportedAt?: Date;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
interface AutoFixResult {
|
|
115
|
-
status: 'skipped' | 'failed' | 'success' | 'in_progress';
|
|
116
|
-
email: ErrorEmail;
|
|
117
|
-
error?: string;
|
|
118
|
-
branchName?: string;
|
|
119
|
-
prUrl?: string;
|
|
120
|
-
log?: ErrorAutoFixLogModel;
|
|
121
|
-
reason?: string;
|
|
122
|
-
summary?: string;
|
|
123
|
-
notes?: string;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
interface LogReservation {
|
|
127
|
-
proceed: boolean;
|
|
128
|
-
log?: ErrorAutoFixLogModel;
|
|
129
|
-
reason?: string;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
interface EmailClassification {
|
|
133
|
-
source: 'app' | 'infrastructure' | 'noise';
|
|
134
|
-
skip: boolean;
|
|
135
|
-
reason?: string;
|
|
136
|
-
notify?: boolean;
|
|
137
|
-
markIgnored?: boolean;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
interface LibraryIssueGateDecision {
|
|
141
|
-
blocked: boolean;
|
|
142
|
-
reason?: string;
|
|
143
|
-
evidence: string[];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const INFRA_SUBJECT_KEYWORDS = [
|
|
147
|
-
'failed snapshot',
|
|
148
|
-
'replica set',
|
|
149
|
-
'data size used',
|
|
150
|
-
'oplog',
|
|
151
|
-
'lock during backup',
|
|
152
|
-
'backup',
|
|
153
|
-
'no primary',
|
|
154
|
-
'no secondary',
|
|
155
|
-
'preferred primary',
|
|
156
|
-
'exceeding memory',
|
|
157
|
-
'exceeding storage',
|
|
158
|
-
'unhealthy status',
|
|
159
|
-
'disconnected status',
|
|
160
|
-
'replication lag',
|
|
161
|
-
'database storage size',
|
|
162
|
-
'compacting collections',
|
|
163
|
-
'failed connection',
|
|
164
|
-
'heartbeat',
|
|
165
|
-
'high cpu usage',
|
|
166
|
-
'high ram usage'
|
|
167
|
-
];
|
|
168
|
-
const MONGO_SOURCE_KEYWORDS = [
|
|
169
|
-
'mongo-explorer',
|
|
170
|
-
'mongo explorer',
|
|
171
|
-
'mongo-manager',
|
|
172
|
-
'mongo manager',
|
|
173
|
-
'mongomanager',
|
|
174
|
-
'mongodb'
|
|
175
|
-
];
|
|
176
|
-
const MONGO_TEXT_PATTERNS: RegExp[] = [
|
|
177
|
-
/\[mongo\]/i,
|
|
178
|
-
/\bmongo(?:db)?\s+(?:server\s+)?error\b/i,
|
|
179
|
-
/\bmongoservererror\b/i,
|
|
180
|
-
/\bmongonetwork(?:timeout)?error\b/i,
|
|
181
|
-
/\bmongonetworktimeouterror\b/i
|
|
182
|
-
];
|
|
183
|
-
const DEFAULT_ERROR_ALERT_EMAIL = 'dev@resolveio.com';
|
|
184
|
-
const MAX_LOCAL_NOTIFICATION_USERS = 5000;
|
|
185
|
-
const LIBRARY_MARKER_PATTERNS: Array<{label: string; pattern: RegExp}> = [
|
|
186
|
-
{label: 'node_modules stack', pattern: /(?:^|[\/\\])node_modules(?:[\/\\]|$)/i},
|
|
187
|
-
{label: '@resolveio/client-lib', pattern: /@resolveio\/client-lib(?:-[a-z0-9-]+)?/i},
|
|
188
|
-
{label: '@resolveio/server-lib', pattern: /@resolveio\/server-lib/i},
|
|
189
|
-
{label: 'resolveio-client-lib path', pattern: /(?:^|[\/\\])resolveio-client-lib(?:[\/\\]|$)/i},
|
|
190
|
-
{label: 'resolveio-server-lib path', pattern: /(?:^|[\/\\])resolveio-server-lib(?:[\/\\]|$)/i},
|
|
191
|
-
{label: 'base template path', pattern: /(?:^|[\/\\])aicoder[\/\\]templates[\/\\]base_(?:small|medium|large|tool)(?:[\/\\]|$)/i}
|
|
192
|
-
];
|
|
193
|
-
const APP_OWNED_PATH_PATTERN = /(?:^|[\/\\])(angular[\/\\]app|server[\/\\]src)(?:[\/\\]|$)/i;
|
|
194
|
-
const FILE_PATH_PATTERN = /(?:webpack:\/\/\/?|\/|\.\/|[A-Za-z]:\\)[^\s'"`]+?\.(?:ts|tsx|js|jsx|mjs|cjs|html|scss|css)/g;
|
|
195
|
-
|
|
196
|
-
interface RepoTarget {
|
|
197
|
-
owner: string;
|
|
198
|
-
repo: string;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
interface GithubPullRequest {
|
|
202
|
-
number: number;
|
|
203
|
-
html_url: string;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
interface ErrorReportIngestResult {
|
|
207
|
-
status: 'queued' | 'skipped' | 'duplicate' | 'ignored' | 'success' | 'failed' | 'in_progress';
|
|
208
|
-
reason?: string;
|
|
209
|
-
log?: ErrorAutoFixLogModel;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export interface DevErrorReportOptions {
|
|
213
|
-
subject: string;
|
|
214
|
-
body?: string;
|
|
215
|
-
sourceApp?: string;
|
|
216
|
-
environment?: string;
|
|
217
|
-
severity?: string;
|
|
218
|
-
fingerprint?: string;
|
|
219
|
-
idempotencyKey?: string;
|
|
220
|
-
metadata?: Record<string, any>;
|
|
221
|
-
clientId?: string;
|
|
222
|
-
clientName?: string;
|
|
223
|
-
clientSlug?: string;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export class ErrorAutoFixManager {
|
|
227
|
-
private config: AutoFixConfig;
|
|
228
|
-
private _serverConfig = null;
|
|
229
|
-
private readonly githubApiBase = 'https://api.github.com';
|
|
230
|
-
private readonly dashboardMonitors = new Map<string, Promise<void>>();
|
|
231
|
-
private static readonly APP_SETTINGS_CACHE_TTL_MS = 10000;
|
|
232
|
-
private appSettingsAutoOptimizeCacheExpiresAt = 0;
|
|
233
|
-
private appSettingsAutoOptimizeCacheValue: boolean | null = null;
|
|
234
|
-
private ready = false;
|
|
235
|
-
|
|
236
|
-
constructor(serverConfig, dependencies?: Partial<ErrorAutoFixManagerDependencies>) {
|
|
237
|
-
const resolvedDependencies = resolveErrorAutoFixManagerDependencies(dependencies);
|
|
238
|
-
applyErrorAutoFixDependencies(resolvedDependencies);
|
|
239
|
-
|
|
240
|
-
this._serverConfig = serverConfig || {};
|
|
241
|
-
this.config = this.resolveConfig();
|
|
242
|
-
|
|
243
|
-
if (!resolvedDependencies.AICoderApps || !resolvedDependencies.AIDashboardJobs) {
|
|
244
|
-
this.config.dashboardWorkflowEnabled = false;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (!this.config.enabled) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (!this.config.dashboardWorkflowEnabled && (!this.config.repoRoot || !this.config.githubOwner || !this.config.githubRepo)) {
|
|
252
|
-
console.warn('ErrorAutoFixManager auto-optimize unavailable - missing repository configuration. Ingest and manual runs remain available.');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
this.ready = true;
|
|
256
|
-
|
|
257
|
-
// console.log('ErrorAutoFixManager initialized (HTTP intake mode).');
|
|
258
|
-
// console.log('ErrorAutoFixManager config source:', this.config.configSource);
|
|
259
|
-
// if (this.config.debugLogging) {
|
|
260
|
-
// console.log('ErrorAutoFixManager debug logging enabled.');
|
|
261
|
-
// }
|
|
262
|
-
// if (!this.config.autoOptimizeEnabled) {
|
|
263
|
-
// console.log('ErrorAutoFixManager auto-run disabled. Manual OpenAI runs required.');
|
|
264
|
-
// }
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
public isReady(): boolean {
|
|
268
|
-
return !!(this.ready && this.config?.enabled);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
public validateIngestKey(candidate?: string): boolean {
|
|
272
|
-
const keys = Array.isArray(this.config?.ingestKeys) ? this.config.ingestKeys.filter(Boolean) : [];
|
|
273
|
-
|
|
274
|
-
if (!keys.length) {
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (!candidate) {
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return keys.includes(candidate.trim());
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private debugLog(message: string, details?: any): void {
|
|
286
|
-
if (!this.config?.debugLogging) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return;
|
|
291
|
-
|
|
292
|
-
if (typeof details === 'undefined') {
|
|
293
|
-
console.log(`[ErrorAutoFixManager][debug] ${message}`);
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
console.log(`[ErrorAutoFixManager][debug] ${message}`, details);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private resolveConfig(): AutoFixConfig {
|
|
301
|
-
const scAutofix = (this._serverConfig && (this._serverConfig.autofix || this._serverConfig.AUTOFIX)) || {};
|
|
302
|
-
let notifyEmails: string[] = [];
|
|
303
|
-
let escalationEmails: string[] = [];
|
|
304
|
-
|
|
305
|
-
if (process.env.AUTOFIX_NOTIFY_EMAILS) {
|
|
306
|
-
notifyEmails = process.env.AUTOFIX_NOTIFY_EMAILS.split(',').map(a => a.trim()).filter(Boolean);
|
|
307
|
-
}
|
|
308
|
-
else if (Array.isArray(scAutofix.notifyEmails)) {
|
|
309
|
-
notifyEmails = scAutofix.notifyEmails.map(a => (a || '').trim()).filter(Boolean);
|
|
310
|
-
}
|
|
311
|
-
else if (typeof scAutofix.notifyEmails === 'string') {
|
|
312
|
-
notifyEmails = scAutofix.notifyEmails.split(',').map(a => a.trim()).filter(Boolean);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (process.env.AUTOFIX_ESCALATION_EMAILS) {
|
|
316
|
-
escalationEmails = process.env.AUTOFIX_ESCALATION_EMAILS.split(',').map(a => a.trim()).filter(Boolean);
|
|
317
|
-
}
|
|
318
|
-
else if (Array.isArray(scAutofix.escalationEmails)) {
|
|
319
|
-
escalationEmails = scAutofix.escalationEmails.map(a => (a || '').trim()).filter(Boolean);
|
|
320
|
-
}
|
|
321
|
-
else if (typeof scAutofix.escalationEmails === 'string') {
|
|
322
|
-
escalationEmails = scAutofix.escalationEmails.split(',').map(a => a.trim()).filter(Boolean);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const getString = (envKey: string, scKey: string, fallback = ''): string => {
|
|
326
|
-
return process.env[envKey] || scAutofix[scKey] || fallback;
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
const getNumber = (envKey: string, scKey: string, fallback: number) => {
|
|
330
|
-
const envVal = Number(process.env[envKey]);
|
|
331
|
-
if (!Number.isNaN(envVal) && process.env[envKey] !== undefined) {
|
|
332
|
-
return envVal;
|
|
333
|
-
}
|
|
334
|
-
const scVal = Number(scAutofix[scKey]);
|
|
335
|
-
if (!Number.isNaN(scVal) && scAutofix[scKey] !== undefined) {
|
|
336
|
-
return scVal;
|
|
337
|
-
}
|
|
338
|
-
return fallback;
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const getBoolean = (envKey: string, scKey: string, fallback = false) => {
|
|
342
|
-
if (typeof process.env[envKey] !== 'undefined') {
|
|
343
|
-
return process.env[envKey] === 'true';
|
|
344
|
-
}
|
|
345
|
-
if (typeof scAutofix[scKey] !== 'undefined') {
|
|
346
|
-
return !!scAutofix[scKey];
|
|
347
|
-
}
|
|
348
|
-
return fallback;
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
const parseKeyList = (value: any): string[] => {
|
|
352
|
-
if (Array.isArray(value)) {
|
|
353
|
-
return value.map(val => `${val || ''}`.trim()).filter(Boolean);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (typeof value === 'string') {
|
|
357
|
-
return value.split(',').map(val => val.trim()).filter(Boolean);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return [];
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
const normalizePositiveInt = (value: number, fallback: number): number => {
|
|
364
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
365
|
-
return fallback;
|
|
366
|
-
}
|
|
367
|
-
return Math.floor(value);
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const openaiProjectId = getString('OPENAI_PROJECT_ID', 'openaiProjectId');
|
|
371
|
-
const openaiEnvironment = getString('OPENAI_ENVIRONMENT', 'openaiEnvironment', openaiProjectId);
|
|
372
|
-
const ingestSource = typeof process.env.AUTOFIX_INGEST_KEYS !== 'undefined'
|
|
373
|
-
? process.env.AUTOFIX_INGEST_KEYS
|
|
374
|
-
: scAutofix.ingestKeys;
|
|
375
|
-
const hasAutoOptimizeEnv = typeof process.env.AUTOFIX_AUTO_OPTIMIZE_ENABLED !== 'undefined';
|
|
376
|
-
const autoOptimizeEnabled = getBoolean('AUTOFIX_AUTO_OPTIMIZE_ENABLED', 'autoOptimizeEnabled', false);
|
|
377
|
-
|
|
378
|
-
return {
|
|
379
|
-
enabled: true,
|
|
380
|
-
repoRoot: getString('AUTOFIX_REPO_ROOT', 'repoRoot', ''),
|
|
381
|
-
baseBranch: getString('AUTOFIX_BASE_BRANCH', 'baseBranch', 'main'),
|
|
382
|
-
branchPrefix: getString('AUTOFIX_BRANCH_PREFIX', 'branchPrefix', 'openai/auto'),
|
|
383
|
-
githubToken: getString('GITHUB_TOKEN', 'githubToken', getString('AUTOFIX_GITHUB_TOKEN', 'githubToken')),
|
|
384
|
-
githubOwner: getString('AUTOFIX_GITHUB_OWNER', 'githubOwner', 'resolveio'),
|
|
385
|
-
githubRepo: getString('AUTOFIX_GITHUB_REPO', 'githubRepo', 'resolveio-all'),
|
|
386
|
-
commandTimeoutMs: getNumber('AUTOFIX_COMMAND_TIMEOUT_MS', 'commandTimeoutMs', 600000),
|
|
387
|
-
notifyEmails,
|
|
388
|
-
debugLogging: getBoolean('AUTOFIX_DEBUG_LOGS', 'debugLogging', false),
|
|
389
|
-
configSource: hasAutoOptimizeEnv ? 'environment' : (Object.keys(scAutofix).length ? 'serverConfig' : 'defaults'),
|
|
390
|
-
openaiEnvironment,
|
|
391
|
-
openaiProjectId,
|
|
392
|
-
autoOptimizeEnabled: autoOptimizeEnabled,
|
|
393
|
-
ingestKeys: parseKeyList(ingestSource),
|
|
394
|
-
dashboardWorkflowEnabled: getBoolean('AUTOFIX_DASHBOARD_WORKFLOW_ENABLED', 'dashboardWorkflowEnabled', true),
|
|
395
|
-
dashboardFallbackToGithub: getBoolean('AUTOFIX_DASHBOARD_FALLBACK_TO_GITHUB', 'dashboardFallbackToGithub', true),
|
|
396
|
-
dashboardWaitTimeoutMs: getNumber('AUTOFIX_DASHBOARD_WAIT_TIMEOUT_MS', 'dashboardWaitTimeoutMs', 45 * 60 * 1000),
|
|
397
|
-
dashboardPublishRequired: getBoolean('AUTOFIX_DASHBOARD_PUBLISH_REQUIRED', 'dashboardPublishRequired', true),
|
|
398
|
-
maxAutoRunAttemptsPerIssue: normalizePositiveInt(
|
|
399
|
-
getNumber('AUTOFIX_MAX_AUTORUN_ATTEMPTS_PER_ISSUE', 'maxAutoRunAttemptsPerIssue', 3),
|
|
400
|
-
3
|
|
401
|
-
),
|
|
402
|
-
escalationEmails
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
private extractEmailAddress(value: string): string {
|
|
407
|
-
if (!value) {
|
|
408
|
-
return '';
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
let email = value;
|
|
412
|
-
const angleMatch = value.match(/<([^>]+)>/);
|
|
413
|
-
|
|
414
|
-
if (angleMatch && angleMatch[1]) {
|
|
415
|
-
email = angleMatch[1];
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
email = email.split(';')[0].split(',')[0];
|
|
419
|
-
return email.trim().toLowerCase();
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
private escapeRegex(value: string): string {
|
|
423
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
private async resolveClientForEnvironment(environment?: string): Promise<ClientModel> {
|
|
427
|
-
const host = this.extractHostFromUrl(environment);
|
|
428
|
-
if (!host) {
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
try {
|
|
433
|
-
const escaped = this.escapeRegex(host);
|
|
434
|
-
const query = {
|
|
435
|
-
website: {
|
|
436
|
-
$regex: escaped,
|
|
437
|
-
$options: 'i'
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
return await Clients.findOne(query);
|
|
442
|
-
}
|
|
443
|
-
catch (err) {
|
|
444
|
-
console.error('ErrorAutoFixManager resolveClientForEnvironment error:', err);
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
private extractHostFromUrl(value?: string): string | null {
|
|
450
|
-
if (!value) {
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const trimmed = value.trim();
|
|
455
|
-
if (!trimmed) {
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
const parsed = new URL(trimmed);
|
|
461
|
-
return parsed.hostname.toLowerCase();
|
|
462
|
-
}
|
|
463
|
-
catch {
|
|
464
|
-
return trimmed.toLowerCase();
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
private async resolveClientForReport(report: ErrorReportPayload): Promise<ClientModel> {
|
|
469
|
-
if (!report) {
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
try {
|
|
474
|
-
if (report.clientId) {
|
|
475
|
-
const byId = await Clients.findById(report.clientId);
|
|
476
|
-
if (byId) {
|
|
477
|
-
return byId;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const tryField = async (field: string, value?: string): Promise<ClientModel> => {
|
|
482
|
-
if (!value) {
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const escaped = this.escapeRegex(value);
|
|
487
|
-
const query: {[key: string]: any} = {};
|
|
488
|
-
query[field] = {$regex: `^${escaped}$`, $options: 'i'};
|
|
489
|
-
return await Clients.findOne(query);
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
return (
|
|
493
|
-
(await tryField('name', report.clientName)) ||
|
|
494
|
-
(await tryField('demo_name', report.clientSlug)) ||
|
|
495
|
-
(await tryField('repo', report.clientRepo)) ||
|
|
496
|
-
(await this.resolveClientForEnvironment(report.environment))
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
catch (err) {
|
|
500
|
-
console.error('ErrorAutoFixManager resolveClientForReport error:', err);
|
|
501
|
-
return null;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
private determineOpenAIEnvironment(client?: ClientModel): string {
|
|
506
|
-
const repoCandidate = (client?.repo || '').trim();
|
|
507
|
-
|
|
508
|
-
if (repoCandidate) {
|
|
509
|
-
return repoCandidate;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const configEnv = (this.config.openaiEnvironment || '').trim();
|
|
513
|
-
|
|
514
|
-
if (configEnv) {
|
|
515
|
-
return configEnv;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if ((this.config.githubOwner || '').trim() && (this.config.githubRepo || '').trim()) {
|
|
519
|
-
return `${this.config.githubOwner}/${this.config.githubRepo}`.trim();
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
return '';
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
private resolveEnvironmentForTask(email: ErrorEmail, log: ErrorAutoFixLogModel): string {
|
|
526
|
-
if (email && email.openai_environment && email.openai_environment.trim()) {
|
|
527
|
-
return email.openai_environment.trim();
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (log && log.openai_environment && log.openai_environment.trim()) {
|
|
531
|
-
return log.openai_environment.trim();
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return this.determineOpenAIEnvironment();
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
private buildEmailFromReport(report: ErrorReportPayload): ErrorEmail {
|
|
539
|
-
const timestamp = report.reportedAt instanceof Date && !Number.isNaN(report.reportedAt.getTime())
|
|
540
|
-
? report.reportedAt
|
|
541
|
-
: new Date();
|
|
542
|
-
const safeMessage = (report.message || '').toString().trim() || 'Unknown error';
|
|
543
|
-
const subjectBase = `[AppError] ${report.sourceApp || 'application'}${report.environment ? ` (${report.environment})` : ''}`;
|
|
544
|
-
const subject = `${subjectBase} - ${safeMessage.slice(0, 120)}`;
|
|
545
|
-
const lines: string[] = [
|
|
546
|
-
`Source App:\t${report.sourceApp}`,
|
|
547
|
-
`Environment:\t${report.environment || 'n/a'}`,
|
|
548
|
-
`Severity:\t${report.severity || 'error'}`,
|
|
549
|
-
`Client Hint:\t${report.clientName || report.clientSlug || report.clientRepo || report.clientId || 'n/a'}`,
|
|
550
|
-
`Reported By:\t${report.reportedBy || report.clientEmail || 'n/a'}`,
|
|
551
|
-
`Reported At:\t${timestamp.toISOString()}`,
|
|
552
|
-
`Message:\t${safeMessage}`
|
|
553
|
-
];
|
|
554
|
-
|
|
555
|
-
if (report.stack) {
|
|
556
|
-
lines.push('', 'Stack Trace:', report.stack);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (report.context && typeof report.context === 'object' && Object.keys(report.context).length) {
|
|
560
|
-
lines.push('', 'Context JSON:', JSON.stringify(report.context, null, 2));
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
if (report.metadata && typeof report.metadata === 'object' && Object.keys(report.metadata).length) {
|
|
564
|
-
lines.push('', 'Metadata JSON:', JSON.stringify(report.metadata, null, 2));
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (Array.isArray(report.attachments) && report.attachments.length) {
|
|
568
|
-
lines.push('', 'Attachments:');
|
|
569
|
-
report.attachments.forEach((att: ErrorReportAttachment) => {
|
|
570
|
-
const desc: string[] = [];
|
|
571
|
-
if (att.name) {
|
|
572
|
-
desc.push(att.name);
|
|
573
|
-
}
|
|
574
|
-
if (att.contentType) {
|
|
575
|
-
desc.push(att.contentType);
|
|
576
|
-
}
|
|
577
|
-
if (typeof att.size === 'number') {
|
|
578
|
-
desc.push(`${att.size}b`);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const summary = desc.length ? desc.join(' | ') : 'attachment';
|
|
582
|
-
lines.push(`- ${summary}${att.url ? ` -> ${att.url}` : ''}`);
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (report.fingerprint) {
|
|
587
|
-
lines.push('', `Fingerprint:\t${report.fingerprint}`);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if (report.idempotencyKey) {
|
|
591
|
-
lines.push(`Idempotency Key:\t${report.idempotencyKey}`);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
const body = lines.join('\n');
|
|
595
|
-
|
|
596
|
-
return {
|
|
597
|
-
key: `report:${timestamp.getTime().toString(36)}:${Math.random().toString(36).slice(2, 10)}`,
|
|
598
|
-
subject,
|
|
599
|
-
body,
|
|
600
|
-
raw: body,
|
|
601
|
-
from: report.reportedBy || report.clientEmail || 'errors@resolveio.com',
|
|
602
|
-
sourceApp: report.sourceApp,
|
|
603
|
-
sourceEnvironment: report.environment,
|
|
604
|
-
severity: report.severity,
|
|
605
|
-
reportedBy: report.reportedBy,
|
|
606
|
-
reportMetadata: report.metadata,
|
|
607
|
-
reportContext: report.context,
|
|
608
|
-
attachments: Array.isArray(report.attachments) ? report.attachments : [],
|
|
609
|
-
openai_environment: report.clientRepo,
|
|
610
|
-
reportedAt: timestamp
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
private classifyEmail(email: ErrorEmail): EmailClassification {
|
|
615
|
-
const subject = (email.subject || '').toLowerCase();
|
|
616
|
-
const body = email.body || '';
|
|
617
|
-
const normalizedBody = body.toLowerCase();
|
|
618
|
-
|
|
619
|
-
if (
|
|
620
|
-
subject.includes('autofix') &&
|
|
621
|
-
(normalizedBody.includes('auto-fix workflow') || normalizedBody.includes('autofix workflow'))
|
|
622
|
-
) {
|
|
623
|
-
return {source: 'noise', skip: true, reason: 'AutoFix status notification'};
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
if (this.isMongoOriginError(email)) {
|
|
627
|
-
return {
|
|
628
|
-
source: 'infrastructure',
|
|
629
|
-
skip: true,
|
|
630
|
-
reason: 'Mongo-origin error ignored for auto-fix',
|
|
631
|
-
markIgnored: true
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (subject.includes('[database]') || subject.includes('[server]')) {
|
|
636
|
-
return {source: 'infrastructure', skip: true, reason: 'Infrastructure alert (subject tag)'};
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const infraKeyword = INFRA_SUBJECT_KEYWORDS.find(keyword => subject.includes(keyword));
|
|
640
|
-
if (infraKeyword) {
|
|
641
|
-
return {source: 'infrastructure', skip: true, reason: `Infrastructure alert (${infraKeyword})`};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
if (!body.trim()) {
|
|
645
|
-
return {source: 'noise', skip: true, reason: 'Empty error payload'};
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const emptyErrorsRegex = /errors\s*[\r\n]+(\{\s*\}|\[\s*\])/i;
|
|
649
|
-
if (emptyErrorsRegex.test(body)) {
|
|
650
|
-
return {source: 'noise', skip: true, reason: 'Empty error details'};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
if (subject.includes('database is down') || normalizedBody.includes('database is down')) {
|
|
654
|
-
return {source: 'infrastructure', skip: true, reason: 'Database outage alert'};
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
return {source: 'app', skip: false};
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
private isMongoOriginError(email: ErrorEmail): boolean {
|
|
661
|
-
const sourceApp = String(email?.sourceApp || '').toLowerCase();
|
|
662
|
-
const sourceEnvironment = String(email?.sourceEnvironment || '').toLowerCase();
|
|
663
|
-
const subject = String(email?.subject || '').toLowerCase();
|
|
664
|
-
const body = String(email?.body || '');
|
|
665
|
-
|
|
666
|
-
const sourceMatches = MONGO_SOURCE_KEYWORDS.some(keyword =>
|
|
667
|
-
sourceApp.includes(keyword) || sourceEnvironment.includes(keyword)
|
|
668
|
-
);
|
|
669
|
-
if (sourceMatches) {
|
|
670
|
-
return true;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const text = `${subject}\n${body}`;
|
|
674
|
-
return MONGO_TEXT_PATTERNS.some(pattern => pattern.test(text));
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
private generateRawEmailHash(email: ErrorEmail): string {
|
|
678
|
-
return createHash('sha256').update(`${email.subject || ''}\n${email.body || ''}`, 'utf8').digest('hex');
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
private generateIssueHash(email: ErrorEmail): string {
|
|
682
|
-
const subject = this.normalizeTextForHash(email.subject || '');
|
|
683
|
-
const body = this.normalizeBodyForHash(email.body || '');
|
|
684
|
-
return createHash('sha256').update(`${subject}\n${body}`, 'utf8').digest('hex');
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
private normalizeTextForHash(value: string): string {
|
|
688
|
-
if (!value) {
|
|
689
|
-
return '';
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
const normalized = value
|
|
693
|
-
.replace(/\r/g, '\n')
|
|
694
|
-
.toLowerCase()
|
|
695
|
-
.replace(/0x[0-9a-f]+/gi, '0x#')
|
|
696
|
-
.replace(/\b[0-9a-f]{7,64}\b/gi, '#')
|
|
697
|
-
.replace(/\b[0-9]{4}[-/][0-9]{2}[-/][0-9]{2}(?:[ t][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,6})?)?\b/g, '#datetime#')
|
|
698
|
-
.replace(/\b[0-9]{2}:[0-9]{2}:[0-9]{2}\b/g, '#time#')
|
|
699
|
-
.replace(/[0-9]+/g, '#')
|
|
700
|
-
.replace(/[^\S\n]+/g, ' ')
|
|
701
|
-
.split('\n')
|
|
702
|
-
.map(line => line.trim())
|
|
703
|
-
.filter(Boolean)
|
|
704
|
-
.slice(0, 60)
|
|
705
|
-
.join('\n');
|
|
706
|
-
|
|
707
|
-
return normalized.length > 4000 ? normalized.slice(0, 4000) : normalized;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
private normalizeBodyForHash(body: string): string {
|
|
711
|
-
if (!body) {
|
|
712
|
-
return '';
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const withoutStackNoise = body
|
|
716
|
-
.replace(/=+\s*stack trace\s*=+/gi, 'stack trace')
|
|
717
|
-
.replace(/-{3,}/g, '-')
|
|
718
|
-
.replace(/\[[^\]]*request id[^\]]*\]/gi, '[request id]')
|
|
719
|
-
.replace(/guid:[^\s]+/gi, 'guid:#')
|
|
720
|
-
.replace(/request id:[^\s]+/gi, 'request id:#')
|
|
721
|
-
.replace(/correlation id:[^\s]+/gi, 'correlation id:#');
|
|
722
|
-
|
|
723
|
-
return this.normalizeTextForHash(withoutStackNoise);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
private parseRepoTarget(env: string): RepoTarget {
|
|
727
|
-
const trimmed = (env || '').trim();
|
|
728
|
-
if (!trimmed) {
|
|
729
|
-
return null;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
const [owner, repo] = trimmed.split('/', 2);
|
|
733
|
-
if (!owner || !repo) {
|
|
734
|
-
return null;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
return {
|
|
738
|
-
owner: owner.trim(),
|
|
739
|
-
repo: repo.trim()
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
private buildWorkBranch(hash: string): string {
|
|
744
|
-
const shortHash = (hash || '').toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 12) || 'autofix';
|
|
745
|
-
const prefix = (this.config.branchPrefix || 'openai/auto').trim();
|
|
746
|
-
if (!prefix) {
|
|
747
|
-
return shortHash;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
const segments = prefix.split('/').map(segment => segment.trim()).filter(Boolean);
|
|
751
|
-
const sanitized = segments.map(segment => segment.replace(/[^a-zA-Z0-9._-]/g, '-'));
|
|
752
|
-
return `${sanitized.join('/')}/${shortHash}`;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
private normalizeEmailText(value: string, limit = 20000): string {
|
|
756
|
-
if (!value) {
|
|
757
|
-
return '';
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
const cleaned = value.replace(/\r/g, '');
|
|
761
|
-
if (cleaned.length <= limit) {
|
|
762
|
-
return cleaned;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
return `${cleaned.slice(0, limit)}\n... (truncated)`;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
private buildContextMarkdown(email: ErrorEmail, identifier: string, normalizedBody: string): string {
|
|
769
|
-
const lines: string[] = [
|
|
770
|
-
`# OpenAI Error Context: ${identifier}`,
|
|
771
|
-
'',
|
|
772
|
-
`**Subject**: ${email.subject || '(no subject)'}`,
|
|
773
|
-
email.fromAddress ? `**From**: ${email.fromAddress}` : undefined,
|
|
774
|
-
email.client_name ? `**Client**: ${email.client_name}` : undefined,
|
|
775
|
-
email.source_type ? `**Classification**: ${email.source_type}${email.classification_reason ? ` (${email.classification_reason})` : ''}` : undefined,
|
|
776
|
-
email.id_client ? `**Client Id**: ${email.id_client}` : undefined,
|
|
777
|
-
'',
|
|
778
|
-
'## Body',
|
|
779
|
-
'```',
|
|
780
|
-
normalizedBody || '(empty body)',
|
|
781
|
-
'```',
|
|
782
|
-
'',
|
|
783
|
-
'_Generated by ErrorAutoFixManager_'
|
|
784
|
-
];
|
|
785
|
-
|
|
786
|
-
return lines.filter(line => typeof line !== 'undefined').join('\n');
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
private buildOpenAIComment(email: ErrorEmail, contextPath: string, normalizedBody: string): string {
|
|
790
|
-
const lines: string[] = [
|
|
791
|
-
'@openai review',
|
|
792
|
-
'',
|
|
793
|
-
`**Email Subject**: ${email.subject || '(no subject)'}`,
|
|
794
|
-
email.fromAddress ? `**From**: ${email.fromAddress}` : undefined,
|
|
795
|
-
email.client_name ? `**Client**: ${email.client_name}` : undefined,
|
|
796
|
-
contextPath ? `Context file: \`${contextPath}\`` : undefined,
|
|
797
|
-
'',
|
|
798
|
-
'```',
|
|
799
|
-
normalizedBody || '(empty body)',
|
|
800
|
-
'```',
|
|
801
|
-
'',
|
|
802
|
-
'Please investigate this error and propose a minimal, safe fix.'
|
|
803
|
-
];
|
|
804
|
-
|
|
805
|
-
return lines.filter(line => typeof line !== 'undefined').join('\n');
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
private formatGithubError(error: any): string {
|
|
809
|
-
if (!error) {
|
|
810
|
-
return 'GitHub request failed.';
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
const status = error?.response?.status;
|
|
814
|
-
const message = error?.response?.data?.message || error?.message || 'GitHub request failed.';
|
|
815
|
-
|
|
816
|
-
if (status) {
|
|
817
|
-
return `GitHub API error (${status}): ${message}`;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
return `GitHub API error: ${message}`;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
private buildGithubAuthHeader(token?: string): string {
|
|
824
|
-
const trimmed = (token || '').trim();
|
|
825
|
-
if (!trimmed) {
|
|
826
|
-
return '';
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if (/^(token|Bearer)\s+/i.test(trimmed)) {
|
|
830
|
-
return trimmed;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
if (trimmed.includes('.') && !trimmed.startsWith('gh')) {
|
|
834
|
-
return `Bearer ${trimmed}`;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
return `token ${trimmed}`;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
private async githubRequest<T>(config: AxiosRequestConfig, allow404 = false): Promise<T> {
|
|
841
|
-
const authHeader = this.buildGithubAuthHeader(this.config.githubToken);
|
|
842
|
-
const headers = {
|
|
843
|
-
...(authHeader ? {Authorization: authHeader} : {}),
|
|
844
|
-
Accept: 'application/vnd.github+json',
|
|
845
|
-
'User-Agent': 'ResolveIO-AutoFix',
|
|
846
|
-
'X-GitHub-Api-Version': '2022-11-28'
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
const finalConfig: AxiosRequestConfig = {
|
|
850
|
-
...config,
|
|
851
|
-
baseURL: this.githubApiBase,
|
|
852
|
-
headers: {
|
|
853
|
-
...headers,
|
|
854
|
-
...(config.headers || {})
|
|
855
|
-
},
|
|
856
|
-
timeout: Math.min(Math.max(this.config.commandTimeoutMs || 600000, 1000), 120000)
|
|
857
|
-
};
|
|
858
|
-
|
|
859
|
-
try {
|
|
860
|
-
const response = await axios.request<T>(finalConfig);
|
|
861
|
-
return response.data;
|
|
862
|
-
}
|
|
863
|
-
catch (err) {
|
|
864
|
-
const status = err?.response?.status;
|
|
865
|
-
if (allow404 && status === 404) {
|
|
866
|
-
return null as unknown as T;
|
|
867
|
-
}
|
|
868
|
-
throw new Error(this.formatGithubError(err));
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
private async resolveBaseBranch(target: RepoTarget): Promise<string> {
|
|
873
|
-
const fallback = (this.config.baseBranch || 'main').trim();
|
|
874
|
-
try {
|
|
875
|
-
const repo = await this.githubRequest<{default_branch?: string}>(
|
|
876
|
-
{method: 'GET', url: `/repos/${target.owner}/${target.repo}`}
|
|
877
|
-
);
|
|
878
|
-
const defaultBranch = (repo?.default_branch || '').trim();
|
|
879
|
-
return defaultBranch || fallback;
|
|
880
|
-
}
|
|
881
|
-
catch (err) {
|
|
882
|
-
this.debugLog('Failed to fetch default branch. Falling back to configured branch.', {
|
|
883
|
-
target,
|
|
884
|
-
error: err?.message
|
|
885
|
-
});
|
|
886
|
-
return fallback;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
private async getBranchSha(target: RepoTarget, branch: string): Promise<string> {
|
|
891
|
-
const ref = await this.githubRequest<{object?: {sha?: string}}>(
|
|
892
|
-
{method: 'GET', url: `/repos/${target.owner}/${target.repo}/git/ref/heads/${branch}`}
|
|
893
|
-
);
|
|
894
|
-
const sha = ref?.object?.sha;
|
|
895
|
-
if (!sha) {
|
|
896
|
-
throw new Error(`Base branch ${branch} not found.`);
|
|
897
|
-
}
|
|
898
|
-
return sha;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
private async ensureBranch(target: RepoTarget, newBranch: string, baseBranch: string, baseSha: string): Promise<void> {
|
|
902
|
-
const existing = await this.githubRequest<any>(
|
|
903
|
-
{method: 'GET', url: `/repos/${target.owner}/${target.repo}/git/ref/heads/${newBranch}`},
|
|
904
|
-
true
|
|
905
|
-
);
|
|
906
|
-
if (existing) {
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
await this.githubRequest(
|
|
911
|
-
{
|
|
912
|
-
method: 'POST',
|
|
913
|
-
url: `/repos/${target.owner}/${target.repo}/git/refs`,
|
|
914
|
-
data: {
|
|
915
|
-
ref: `refs/heads/${newBranch}`,
|
|
916
|
-
sha: baseSha
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
private async upsertContextFile(target: RepoTarget, branch: string, contextPath: string, content: string): Promise<void> {
|
|
923
|
-
const encodedPath = encodeURIComponent(contextPath).replace(/%2F/g, '/');
|
|
924
|
-
const existing = await this.githubRequest<any>(
|
|
925
|
-
{
|
|
926
|
-
method: 'GET',
|
|
927
|
-
url: `/repos/${target.owner}/${target.repo}/contents/${encodedPath}`,
|
|
928
|
-
params: {ref: branch}
|
|
929
|
-
},
|
|
930
|
-
true
|
|
931
|
-
);
|
|
932
|
-
const sha = existing && existing.sha ? existing.sha : undefined;
|
|
933
|
-
const commitMessage = `chore(openai): update error context ${contextPath}`;
|
|
934
|
-
|
|
935
|
-
await this.githubRequest(
|
|
936
|
-
{
|
|
937
|
-
method: 'PUT',
|
|
938
|
-
url: `/repos/${target.owner}/${target.repo}/contents/${encodedPath}`,
|
|
939
|
-
data: {
|
|
940
|
-
message: commitMessage.slice(0, 200),
|
|
941
|
-
content: Buffer.from(content, 'utf8').toString('base64'),
|
|
942
|
-
branch,
|
|
943
|
-
sha
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
private async findOpenPullRequest(target: RepoTarget, branch: string, base: string): Promise<GithubPullRequest> {
|
|
950
|
-
const prs = await this.githubRequest<GithubPullRequest[]>(
|
|
951
|
-
{
|
|
952
|
-
method: 'GET',
|
|
953
|
-
url: `/repos/${target.owner}/${target.repo}/pulls`,
|
|
954
|
-
params: {
|
|
955
|
-
state: 'open',
|
|
956
|
-
head: `${target.owner}:${branch}`,
|
|
957
|
-
base,
|
|
958
|
-
per_page: 1
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
);
|
|
962
|
-
if (prs && prs.length) {
|
|
963
|
-
return prs[0];
|
|
964
|
-
}
|
|
965
|
-
return null;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
private async createPullRequest(target: RepoTarget, branch: string, base: string, title: string, body: string): Promise<GithubPullRequest> {
|
|
969
|
-
const pr = await this.githubRequest<GithubPullRequest>(
|
|
970
|
-
{
|
|
971
|
-
method: 'POST',
|
|
972
|
-
url: `/repos/${target.owner}/${target.repo}/pulls`,
|
|
973
|
-
data: {
|
|
974
|
-
title,
|
|
975
|
-
head: branch,
|
|
976
|
-
base,
|
|
977
|
-
body
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
);
|
|
981
|
-
return pr;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
private async commentOnPullRequest(target: RepoTarget, prNumber: number, body: string): Promise<void> {
|
|
985
|
-
await this.githubRequest(
|
|
986
|
-
{
|
|
987
|
-
method: 'POST',
|
|
988
|
-
url: `/repos/${target.owner}/${target.repo}/issues/${prNumber}/comments`,
|
|
989
|
-
data: {
|
|
990
|
-
body
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
private parseBooleanEnv(envKey: string): boolean | null {
|
|
997
|
-
if (typeof process.env[envKey] === 'undefined') {
|
|
998
|
-
return null;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
return process.env[envKey] === 'true';
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
private async resolveAutoOptimizeEnabled(): Promise<boolean> {
|
|
1005
|
-
const now = Date.now();
|
|
1006
|
-
if (this.appSettingsAutoOptimizeCacheValue !== null && now < this.appSettingsAutoOptimizeCacheExpiresAt) {
|
|
1007
|
-
return this.appSettingsAutoOptimizeCacheValue;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
const envValue = this.parseBooleanEnv('AUTOFIX_AUTO_OPTIMIZE_ENABLED');
|
|
1011
|
-
let enabled = envValue !== null ? envValue : !!this.config.autoOptimizeEnabled;
|
|
1012
|
-
try {
|
|
1013
|
-
if (AppSettings && typeof AppSettings.findOne === 'function') {
|
|
1014
|
-
const activeSetting = await AppSettings.findOne({
|
|
1015
|
-
is_active: {
|
|
1016
|
-
$ne: false
|
|
1017
|
-
}
|
|
1018
|
-
}, {
|
|
1019
|
-
sort: {
|
|
1020
|
-
updatedAt: -1,
|
|
1021
|
-
createdAt: -1
|
|
1022
|
-
}
|
|
1023
|
-
}) || await AppSettings.findOne({}, {
|
|
1024
|
-
sort: {
|
|
1025
|
-
updatedAt: -1,
|
|
1026
|
-
createdAt: -1
|
|
1027
|
-
}
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
if (activeSetting && typeof activeSetting.enable_auto_fix === 'boolean') {
|
|
1031
|
-
enabled = !!activeSetting.enable_auto_fix;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
catch (error) {
|
|
1036
|
-
if (this.config?.debugLogging) {
|
|
1037
|
-
console.warn('ErrorAutoFixManager failed to read app settings auto-fix toggle', error);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
this.appSettingsAutoOptimizeCacheValue = enabled;
|
|
1042
|
-
this.appSettingsAutoOptimizeCacheExpiresAt = now + ErrorAutoFixManager.APP_SETTINGS_CACHE_TTL_MS;
|
|
1043
|
-
return enabled;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
private async reserveLog(email: ErrorEmail, client?: ClientModel, autoOptimizeEnabled = false): Promise<LogReservation> {
|
|
1047
|
-
const hash = email.hash;
|
|
1048
|
-
const rawHash = email.rawHash || hash;
|
|
1049
|
-
const now = email.reportedAt || new Date();
|
|
1050
|
-
|
|
1051
|
-
let existing: ErrorAutoFixLogModel = null;
|
|
1052
|
-
|
|
1053
|
-
if (hash) {
|
|
1054
|
-
existing = await ErrorAutoFixLogs.findOne({issue_hash: hash});
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
if (!existing && rawHash) {
|
|
1058
|
-
existing = await ErrorAutoFixLogs.findOne({email_hash: rawHash});
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
if (!existing && hash && hash !== rawHash) {
|
|
1062
|
-
existing = await ErrorAutoFixLogs.findOne({email_hash: hash});
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
if (existing) {
|
|
1066
|
-
await this.markDuplicate(existing._id, now);
|
|
1067
|
-
const updatedExisting = await ErrorAutoFixLogs.findOne({_id: existing._id}) || existing;
|
|
1068
|
-
|
|
1069
|
-
const metaUpdate: Partial<ErrorAutoFixLogModel> = {};
|
|
1070
|
-
|
|
1071
|
-
if (hash && (!updatedExisting.issue_hash || updatedExisting.issue_hash !== hash)) {
|
|
1072
|
-
metaUpdate.issue_hash = hash;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
if (rawHash && (!updatedExisting.email_hash || updatedExisting.email_hash !== rawHash)) {
|
|
1076
|
-
metaUpdate.email_hash = rawHash;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
if (email.fromAddress && email.fromAddress !== updatedExisting.from_email) {
|
|
1080
|
-
metaUpdate.from_email = email.fromAddress;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
if (client) {
|
|
1084
|
-
if (!updatedExisting.id_client || updatedExisting.id_client !== client._id) {
|
|
1085
|
-
metaUpdate.id_client = client._id;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
if (!updatedExisting.client_name || updatedExisting.client_name !== client.name) {
|
|
1089
|
-
metaUpdate.client_name = client.name;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
if (email.openai_environment && email.openai_environment !== updatedExisting.openai_environment) {
|
|
1094
|
-
metaUpdate.openai_environment = email.openai_environment;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
if (email.sourceApp && email.sourceApp !== updatedExisting.source_app) {
|
|
1098
|
-
metaUpdate.source_app = email.sourceApp;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
if (email.sourceEnvironment && email.sourceEnvironment !== updatedExisting.source_environment) {
|
|
1102
|
-
metaUpdate.source_environment = email.sourceEnvironment;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
if (email.severity && email.severity !== updatedExisting.severity) {
|
|
1106
|
-
metaUpdate.severity = email.severity;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
if (email.reportedBy && email.reportedBy !== updatedExisting.reported_by) {
|
|
1110
|
-
metaUpdate.reported_by = email.reportedBy;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
if (typeof email.reportMetadata !== 'undefined') {
|
|
1114
|
-
metaUpdate.report_metadata = email.reportMetadata;
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
if (typeof email.reportContext !== 'undefined') {
|
|
1118
|
-
metaUpdate.report_context = email.reportContext;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
if (typeof email.attachments !== 'undefined') {
|
|
1122
|
-
metaUpdate.attachments = email.attachments;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
metaUpdate.last_reported_at = now;
|
|
1126
|
-
|
|
1127
|
-
if (Object.keys(metaUpdate).length) {
|
|
1128
|
-
await this.updateLog(updatedExisting._id, metaUpdate);
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
if (updatedExisting.status === 'completed') {
|
|
1132
|
-
const reactivated = (await ErrorAutoFixLogs.findOneAndUpdate({_id: updatedExisting._id}, {
|
|
1133
|
-
$set: {
|
|
1134
|
-
status: 'pending',
|
|
1135
|
-
ignored: false,
|
|
1136
|
-
last_error: '',
|
|
1137
|
-
openai_task_id: '',
|
|
1138
|
-
branch_name: '',
|
|
1139
|
-
pr_url: ''
|
|
1140
|
-
}
|
|
1141
|
-
}, {
|
|
1142
|
-
returnDocument: 'after'
|
|
1143
|
-
})) || updatedExisting;
|
|
1144
|
-
|
|
1145
|
-
return {
|
|
1146
|
-
proceed: true,
|
|
1147
|
-
log: reactivated
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
if (updatedExisting.ignored) {
|
|
1152
|
-
return {
|
|
1153
|
-
proceed: false,
|
|
1154
|
-
log: updatedExisting,
|
|
1155
|
-
reason: 'ignored'
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
if (autoOptimizeEnabled && (updatedExisting.status === 'failed' || updatedExisting.status === 'skipped') && !updatedExisting.ignored) {
|
|
1160
|
-
const attemptsUsed = Number.isFinite(Number(updatedExisting.attempt_count))
|
|
1161
|
-
? Number(updatedExisting.attempt_count)
|
|
1162
|
-
: 0;
|
|
1163
|
-
const maxAttempts = Number.isFinite(Number(this.config.maxAutoRunAttemptsPerIssue))
|
|
1164
|
-
? Number(this.config.maxAutoRunAttemptsPerIssue)
|
|
1165
|
-
: 0;
|
|
1166
|
-
if (maxAttempts > 0 && attemptsUsed >= maxAttempts) {
|
|
1167
|
-
const reason = `Auto-fix attempt budget reached (${attemptsUsed}/${maxAttempts}).`;
|
|
1168
|
-
const exhausted = (await ErrorAutoFixLogs.findOneAndUpdate({_id: updatedExisting._id}, {
|
|
1169
|
-
$set: {
|
|
1170
|
-
status: 'skipped',
|
|
1171
|
-
ignored: true,
|
|
1172
|
-
last_error: `${reason} Marked ignored pending manual intervention.`,
|
|
1173
|
-
last_result_at: now,
|
|
1174
|
-
updatedAt: now
|
|
1175
|
-
}
|
|
1176
|
-
}, {
|
|
1177
|
-
returnDocument: 'after'
|
|
1178
|
-
})) || updatedExisting;
|
|
1179
|
-
|
|
1180
|
-
await this.sendEscalationNotice(exhausted, reason);
|
|
1181
|
-
return {
|
|
1182
|
-
proceed: false,
|
|
1183
|
-
log: exhausted,
|
|
1184
|
-
reason: 'budget_exhausted'
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
const resumed = await ErrorAutoFixLogs.findOneAndUpdate({_id: updatedExisting._id}, {
|
|
1189
|
-
$inc: {
|
|
1190
|
-
attempt_count: 1
|
|
1191
|
-
},
|
|
1192
|
-
$set: {
|
|
1193
|
-
status: 'in_progress',
|
|
1194
|
-
last_attempt_at: now,
|
|
1195
|
-
openai_task_id: '',
|
|
1196
|
-
branch_name: '',
|
|
1197
|
-
pr_url: '',
|
|
1198
|
-
last_error: ''
|
|
1199
|
-
}
|
|
1200
|
-
}, {
|
|
1201
|
-
returnDocument: 'after'
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
return {
|
|
1205
|
-
proceed: true,
|
|
1206
|
-
log: resumed || updatedExisting
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
if (updatedExisting.status === 'in_progress') {
|
|
1211
|
-
return {
|
|
1212
|
-
proceed: false,
|
|
1213
|
-
log: updatedExisting,
|
|
1214
|
-
reason: 'in_progress'
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
if (updatedExisting.status === 'success') {
|
|
1219
|
-
return {
|
|
1220
|
-
proceed: false,
|
|
1221
|
-
log: updatedExisting,
|
|
1222
|
-
reason: 'success'
|
|
1223
|
-
};
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
return {
|
|
1227
|
-
proceed: false,
|
|
1228
|
-
log: updatedExisting,
|
|
1229
|
-
reason: 'duplicate'
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
const counterValue = await ResolveIOServer.getMainServer().getMethodManager().callMethod('incCounter', 'autofix_error');
|
|
1234
|
-
const counterString = pad(counterValue, 6);
|
|
1235
|
-
|
|
1236
|
-
const doc: ErrorAutoFixLogModel = {
|
|
1237
|
-
_id: objectIdHexString(),
|
|
1238
|
-
email_hash: rawHash,
|
|
1239
|
-
issue_hash: hash,
|
|
1240
|
-
subject: email.subject,
|
|
1241
|
-
message_id: email.messageId,
|
|
1242
|
-
body: email.body,
|
|
1243
|
-
status: autoOptimizeEnabled ? 'in_progress' : 'pending',
|
|
1244
|
-
ignored: false,
|
|
1245
|
-
attempt_count: autoOptimizeEnabled ? 1 : 0,
|
|
1246
|
-
duplicate_count: 0,
|
|
1247
|
-
autofix_error_count: counterValue,
|
|
1248
|
-
autofix_error_count_string: counterString,
|
|
1249
|
-
first_seen_at: now,
|
|
1250
|
-
last_attempt_at: autoOptimizeEnabled ? now : undefined,
|
|
1251
|
-
from_email: email.fromAddress || '',
|
|
1252
|
-
id_client: client?._id,
|
|
1253
|
-
client_name: client?.name,
|
|
1254
|
-
openai_environment: email.openai_environment || this.determineOpenAIEnvironment(client),
|
|
1255
|
-
source_app: email.sourceApp,
|
|
1256
|
-
source_environment: email.sourceEnvironment,
|
|
1257
|
-
severity: email.severity,
|
|
1258
|
-
reported_by: email.reportedBy || email.fromAddress || '',
|
|
1259
|
-
last_reported_at: now,
|
|
1260
|
-
report_metadata: email.reportMetadata,
|
|
1261
|
-
report_context: email.reportContext,
|
|
1262
|
-
attachments: email.attachments
|
|
1263
|
-
};
|
|
1264
|
-
|
|
1265
|
-
await ErrorAutoFixLogs.insertOne(doc as any);
|
|
1266
|
-
|
|
1267
|
-
return {
|
|
1268
|
-
proceed: true,
|
|
1269
|
-
log: doc
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
private async markDuplicate(logId: string, timestamp: Date): Promise<void> {
|
|
1274
|
-
await ErrorAutoFixLogs.updateOne({_id: logId}, {
|
|
1275
|
-
$inc: {
|
|
1276
|
-
duplicate_count: 1
|
|
1277
|
-
},
|
|
1278
|
-
$set: {
|
|
1279
|
-
last_duplicate_at: timestamp,
|
|
1280
|
-
last_reported_at: timestamp,
|
|
1281
|
-
updatedAt: timestamp
|
|
1282
|
-
}
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
private async updateLog(logId: string, set: Partial<ErrorAutoFixLogModel>, inc?: {[key: string]: number}): Promise<ErrorAutoFixLogModel> {
|
|
1287
|
-
const cleanedSet: {[key: string]: any} = {};
|
|
1288
|
-
const rawSet: {[key: string]: any} = set ? {...set} : {};
|
|
1289
|
-
|
|
1290
|
-
Object.keys(rawSet).forEach(key => {
|
|
1291
|
-
if (typeof rawSet[key] !== 'undefined') {
|
|
1292
|
-
cleanedSet[key] = rawSet[key];
|
|
1293
|
-
}
|
|
1294
|
-
});
|
|
1295
|
-
|
|
1296
|
-
cleanedSet.updatedAt = new Date();
|
|
1297
|
-
|
|
1298
|
-
const update: any = {
|
|
1299
|
-
$set: cleanedSet
|
|
1300
|
-
};
|
|
1301
|
-
|
|
1302
|
-
if (inc) {
|
|
1303
|
-
update.$inc = inc;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
const result = await ErrorAutoFixLogs.findOneAndUpdate({_id: logId}, update, {
|
|
1307
|
-
returnDocument: 'after'
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
return result;
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
private getEscalationRecipients(): string[] {
|
|
1314
|
-
const unique = new Set<string>();
|
|
1315
|
-
const add = (value: string) => {
|
|
1316
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
1317
|
-
if (!normalized) {
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
unique.add(normalized);
|
|
1321
|
-
};
|
|
1322
|
-
|
|
1323
|
-
(this.config.escalationEmails || []).forEach(add);
|
|
1324
|
-
(this.config.notifyEmails || []).forEach(add);
|
|
1325
|
-
if (!unique.size) {
|
|
1326
|
-
add(DEFAULT_ERROR_ALERT_EMAIL);
|
|
1327
|
-
}
|
|
1328
|
-
return Array.from(unique);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
private async sendEscalationNotice(log: ErrorAutoFixLogModel, reason: string): Promise<void> {
|
|
1332
|
-
const recipients = this.getEscalationRecipients();
|
|
1333
|
-
if (!recipients.length || !log?._id) {
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
const identifier = log.autofix_error_count_string || log._id;
|
|
1338
|
-
const subject = `ResolveIO AutoFix Escalation: ${identifier}`;
|
|
1339
|
-
const body = [
|
|
1340
|
-
'Auto-fix retry budget was exhausted for this recurring issue and further autonomous attempts were stopped.',
|
|
1341
|
-
'',
|
|
1342
|
-
`Reason: ${reason}`,
|
|
1343
|
-
`Issue Log ID: ${log._id}`,
|
|
1344
|
-
`Issue Counter: ${log.autofix_error_count_string || 'n/a'}`,
|
|
1345
|
-
`Client: ${log.client_name || log.id_client || 'unknown'}`,
|
|
1346
|
-
`Source App: ${log.source_app || 'unknown'}`,
|
|
1347
|
-
`Environment: ${log.source_environment || log.openai_environment || 'unknown'}`,
|
|
1348
|
-
`Attempts: ${log.attempt_count || 0}/${this.config.maxAutoRunAttemptsPerIssue}`,
|
|
1349
|
-
`Issue Hash: ${log.issue_hash || log.email_hash || 'n/a'}`,
|
|
1350
|
-
'',
|
|
1351
|
-
'The issue is now marked ignored for auto-fix to protect customer token usage. Manual intervention is required.'
|
|
1352
|
-
].join('\n');
|
|
1353
|
-
|
|
1354
|
-
const methodManager = ResolveIOServer.getMainServer().getMethodManager();
|
|
1355
|
-
for (const recipient of recipients) {
|
|
1356
|
-
try {
|
|
1357
|
-
await methodManager.sendEmail(recipient, subject, body);
|
|
1358
|
-
}
|
|
1359
|
-
catch (error) {
|
|
1360
|
-
console.error('Failed sending auto-fix escalation email', {recipient, logId: log._id, error});
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
private collectLibraryIssueSignalText(email: ErrorEmail, log: ErrorAutoFixLogModel): string {
|
|
1366
|
-
const attachments = Array.isArray(log?.attachments) ? log.attachments : [];
|
|
1367
|
-
const attachmentSignals = attachments.map((attachment) => {
|
|
1368
|
-
return [
|
|
1369
|
-
attachment?.name || '',
|
|
1370
|
-
attachment?.url || '',
|
|
1371
|
-
attachment?.contentType || ''
|
|
1372
|
-
].filter(Boolean).join(' ');
|
|
1373
|
-
});
|
|
1374
|
-
const metadata = log?.report_metadata && Object.keys(log.report_metadata).length
|
|
1375
|
-
? JSON.stringify(log.report_metadata)
|
|
1376
|
-
: '';
|
|
1377
|
-
const context = log?.report_context && Object.keys(log.report_context).length
|
|
1378
|
-
? JSON.stringify(log.report_context)
|
|
1379
|
-
: '';
|
|
1380
|
-
return [
|
|
1381
|
-
email?.subject || '',
|
|
1382
|
-
email?.body || '',
|
|
1383
|
-
log?.body || '',
|
|
1384
|
-
metadata,
|
|
1385
|
-
context,
|
|
1386
|
-
attachmentSignals.join('\n')
|
|
1387
|
-
].filter(Boolean).join('\n');
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
private evaluateLibraryIssueGate(email: ErrorEmail, log: ErrorAutoFixLogModel): LibraryIssueGateDecision {
|
|
1391
|
-
const signalText = this.collectLibraryIssueSignalText(email, log);
|
|
1392
|
-
if (!signalText) {
|
|
1393
|
-
return {blocked: false, evidence: []};
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
const evidence: string[] = [];
|
|
1397
|
-
let markerHits = 0;
|
|
1398
|
-
for (const marker of LIBRARY_MARKER_PATTERNS) {
|
|
1399
|
-
if (!marker.pattern.test(signalText)) {
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
markerHits += 1;
|
|
1403
|
-
evidence.push(marker.label);
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
const rawPaths = signalText.match(FILE_PATH_PATTERN) || [];
|
|
1407
|
-
const normalizedPaths = rawPaths
|
|
1408
|
-
.map((value) => String(value || '').trim())
|
|
1409
|
-
.filter(Boolean)
|
|
1410
|
-
.map((value) => value.replace(/\\/g, '/').toLowerCase());
|
|
1411
|
-
const appOwnedPathCount = normalizedPaths.filter((value) => APP_OWNED_PATH_PATTERN.test(value)).length;
|
|
1412
|
-
const libraryPathCount = normalizedPaths.filter((value) => {
|
|
1413
|
-
return value.includes('/node_modules/')
|
|
1414
|
-
|| value.includes('@resolveio/client-lib')
|
|
1415
|
-
|| value.includes('@resolveio/server-lib')
|
|
1416
|
-
|| value.includes('/resolveio-client-lib/')
|
|
1417
|
-
|| value.includes('/resolveio-server-lib/')
|
|
1418
|
-
|| value.includes('/aicoder/templates/base_');
|
|
1419
|
-
}).length;
|
|
1420
|
-
|
|
1421
|
-
const shouldBlock = (libraryPathCount > 0 && appOwnedPathCount === 0)
|
|
1422
|
-
|| (markerHits >= 2 && appOwnedPathCount === 0)
|
|
1423
|
-
|| (markerHits >= 1 && libraryPathCount >= 2);
|
|
1424
|
-
if (!shouldBlock) {
|
|
1425
|
-
return {blocked: false, evidence};
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
const compactEvidence = Array.from(new Set([
|
|
1429
|
-
...evidence,
|
|
1430
|
-
...(libraryPathCount ? [`library_paths=${libraryPathCount}`] : []),
|
|
1431
|
-
...(appOwnedPathCount ? [`app_paths=${appOwnedPathCount}`] : [])
|
|
1432
|
-
])).slice(0, 8);
|
|
1433
|
-
return {
|
|
1434
|
-
blocked: true,
|
|
1435
|
-
reason: 'Root cause appears to be in shared ResolveIO libraries/templates; app-level auto-fix was blocked.',
|
|
1436
|
-
evidence: compactEvidence
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
private async sendLibraryIssueNotice(log: ErrorAutoFixLogModel, reason: string, evidence: string[]): Promise<void> {
|
|
1441
|
-
const recipients = this.getEscalationRecipients();
|
|
1442
|
-
if (!recipients.length || !log?._id) {
|
|
1443
|
-
return;
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
const identifier = log.autofix_error_count_string || log._id;
|
|
1447
|
-
const subject = `ResolveIO AutoFix Blocked (Library): ${identifier}`;
|
|
1448
|
-
const body = [
|
|
1449
|
-
'Auto-fix was blocked because the issue appears library-owned, so no autonomous patch was attempted.',
|
|
1450
|
-
'',
|
|
1451
|
-
`Reason: ${reason}`,
|
|
1452
|
-
evidence.length ? `Evidence: ${evidence.join(', ')}` : '',
|
|
1453
|
-
`Issue Log ID: ${log._id}`,
|
|
1454
|
-
`Issue Counter: ${log.autofix_error_count_string || 'n/a'}`,
|
|
1455
|
-
`Client: ${log.client_name || log.id_client || 'unknown'}`,
|
|
1456
|
-
`Source App: ${log.source_app || 'unknown'}`,
|
|
1457
|
-
`Environment: ${log.source_environment || log.openai_environment || 'unknown'}`,
|
|
1458
|
-
`Issue Hash: ${log.issue_hash || log.email_hash || 'n/a'}`,
|
|
1459
|
-
'',
|
|
1460
|
-
'Manual library release/fix is required. App-level auto-fix remains blocked for this issue to protect credits.'
|
|
1461
|
-
].filter(Boolean).join('\n');
|
|
1462
|
-
|
|
1463
|
-
const methodManager = ResolveIOServer.getMainServer().getMethodManager();
|
|
1464
|
-
for (const recipient of recipients) {
|
|
1465
|
-
try {
|
|
1466
|
-
await methodManager.sendEmail(recipient, subject, body);
|
|
1467
|
-
}
|
|
1468
|
-
catch (error) {
|
|
1469
|
-
console.error('Failed sending library-issue auto-fix email', {recipient, logId: log._id, error});
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
private async blockLibraryOwnedIssue(email: ErrorEmail, log: ErrorAutoFixLogModel, gate: LibraryIssueGateDecision): Promise<AutoFixResult> {
|
|
1475
|
-
const reason = gate.reason || 'Library-owned issue detected.';
|
|
1476
|
-
const details = gate.evidence.length ? `Signals: ${gate.evidence.join(', ')}` : '';
|
|
1477
|
-
const message = [reason, details].filter(Boolean).join(' ');
|
|
1478
|
-
const updated = (await this.updateLog(log._id, {
|
|
1479
|
-
status: 'skipped',
|
|
1480
|
-
ignored: true,
|
|
1481
|
-
last_error: message,
|
|
1482
|
-
last_result_at: new Date()
|
|
1483
|
-
})) || log;
|
|
1484
|
-
await this.notifyCustomerWorkflowStatus('blocked_library_issue', email, updated, {
|
|
1485
|
-
error: reason,
|
|
1486
|
-
notes: details
|
|
1487
|
-
});
|
|
1488
|
-
await this.sendLibraryIssueNotice(updated, reason, gate.evidence);
|
|
1489
|
-
return {
|
|
1490
|
-
status: 'skipped',
|
|
1491
|
-
email,
|
|
1492
|
-
error: message,
|
|
1493
|
-
log: updated,
|
|
1494
|
-
reason: 'library_issue_blocked',
|
|
1495
|
-
summary: updated.plan_summary || email.subject,
|
|
1496
|
-
notes: details
|
|
1497
|
-
};
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
private shouldNotifySkip(reason: string): boolean {
|
|
1501
|
-
return ['in_progress', 'success', 'ignored', 'duplicate', 'pending'].indexOf(reason) === -1;
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
private async isGeneratedAICoderClient(idClient: string): Promise<boolean> {
|
|
1505
|
-
const normalizedClientId = String(idClient || '').trim();
|
|
1506
|
-
if (!normalizedClientId) {
|
|
1507
|
-
return false;
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
const match = await AICoderApps.findOne({
|
|
1511
|
-
client_id: normalizedClientId
|
|
1512
|
-
}, {
|
|
1513
|
-
projection: {
|
|
1514
|
-
_id: 1
|
|
1515
|
-
}
|
|
1516
|
-
});
|
|
1517
|
-
|
|
1518
|
-
return !!match?._id;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
private buildAICoderWorkflowSummary(
|
|
1522
|
-
stage: 'detected_autofix_enabled' | 'detected_autofix_disabled' | 'completed_success' | 'completed_failed' | 'blocked_library_issue',
|
|
1523
|
-
email: ErrorEmail,
|
|
1524
|
-
log: ErrorAutoFixLogModel,
|
|
1525
|
-
extra?: { error?: string; notes?: string }
|
|
1526
|
-
): string {
|
|
1527
|
-
return buildGeneratedAppAutoFixSummary(stage, {
|
|
1528
|
-
source_app: String(log?.source_app || email?.sourceApp || '').trim(),
|
|
1529
|
-
environment: String(log?.source_environment || email?.sourceEnvironment || '').trim(),
|
|
1530
|
-
severity: String(log?.severity || email?.severity || '').trim(),
|
|
1531
|
-
error: extra?.error
|
|
1532
|
-
}, extra);
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
private buildResolveIOProjectWorkflowDetails(
|
|
1536
|
-
email: ErrorEmail,
|
|
1537
|
-
log: ErrorAutoFixLogModel,
|
|
1538
|
-
extra?: { error?: string; notes?: string }
|
|
1539
|
-
): string {
|
|
1540
|
-
return buildResolveIOProjectAutoFixDetails({
|
|
1541
|
-
client_name: log?.client_name || email?.client_name || '',
|
|
1542
|
-
issue_counter: log?.autofix_error_count_string || '',
|
|
1543
|
-
source_app: log?.source_app || email?.sourceApp || '',
|
|
1544
|
-
environment: log?.source_environment || email?.sourceEnvironment || log?.openai_environment || '',
|
|
1545
|
-
severity: log?.severity || email?.severity || '',
|
|
1546
|
-
issue_hash: log?.issue_hash || email?.issueHash || log?.email_hash || '',
|
|
1547
|
-
log_id: log?._id || '',
|
|
1548
|
-
notes: extra?.notes,
|
|
1549
|
-
error: extra?.error
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
private buildLocalInternalUserCriteria(): Record<string, any> {
|
|
1554
|
-
return {
|
|
1555
|
-
$and: [
|
|
1556
|
-
{
|
|
1557
|
-
is_customer: {
|
|
1558
|
-
$ne: true
|
|
1559
|
-
}
|
|
1560
|
-
},
|
|
1561
|
-
{
|
|
1562
|
-
$nor: [
|
|
1563
|
-
{
|
|
1564
|
-
'other.id_customer': {
|
|
1565
|
-
$exists: true,
|
|
1566
|
-
$nin: ['', null]
|
|
1567
|
-
}
|
|
1568
|
-
},
|
|
1569
|
-
{
|
|
1570
|
-
id_customer: {
|
|
1571
|
-
$exists: true,
|
|
1572
|
-
$nin: ['', null]
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
]
|
|
1576
|
-
}
|
|
1577
|
-
]
|
|
1578
|
-
};
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
private buildLocalAdminUserCriteria(): Record<string, any> {
|
|
1582
|
-
return {
|
|
1583
|
-
$or: [
|
|
1584
|
-
{
|
|
1585
|
-
'roles.super_admin': true
|
|
1586
|
-
},
|
|
1587
|
-
{
|
|
1588
|
-
'roles.groups.name': {
|
|
1589
|
-
$regex: 'admin',
|
|
1590
|
-
$options: 'i'
|
|
1591
|
-
}
|
|
1592
|
-
},
|
|
1593
|
-
{
|
|
1594
|
-
'roles.groups.views': {
|
|
1595
|
-
$regex: '^/manage'
|
|
1596
|
-
}
|
|
1597
|
-
},
|
|
1598
|
-
{
|
|
1599
|
-
'roles.groups.views': '/manage'
|
|
1600
|
-
}
|
|
1601
|
-
]
|
|
1602
|
-
};
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
private async resolveLocalNotificationUserIds(isGeneratedApp: boolean): Promise<string[]> {
|
|
1606
|
-
const andClauses: Record<string, any>[] = [
|
|
1607
|
-
{
|
|
1608
|
-
active: true
|
|
1609
|
-
},
|
|
1610
|
-
{
|
|
1611
|
-
username: {
|
|
1612
|
-
$exists: true
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
];
|
|
1616
|
-
|
|
1617
|
-
if (isGeneratedApp) {
|
|
1618
|
-
andClauses.push(this.buildLocalInternalUserCriteria());
|
|
1619
|
-
}
|
|
1620
|
-
else {
|
|
1621
|
-
andClauses.push(this.buildLocalAdminUserCriteria());
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
const users = await Users.find({
|
|
1625
|
-
$and: andClauses
|
|
1626
|
-
}, {
|
|
1627
|
-
projection: {
|
|
1628
|
-
_id: 1
|
|
1629
|
-
},
|
|
1630
|
-
limit: MAX_LOCAL_NOTIFICATION_USERS
|
|
1631
|
-
});
|
|
1632
|
-
|
|
1633
|
-
if (!Array.isArray(users) || !users.length) {
|
|
1634
|
-
return [];
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
const unique = new Set<string>();
|
|
1638
|
-
for (const user of users) {
|
|
1639
|
-
const idUser = String(user?._id || '').trim();
|
|
1640
|
-
if (idUser) {
|
|
1641
|
-
unique.add(idUser);
|
|
1642
|
-
}
|
|
1643
|
-
if (unique.size >= MAX_LOCAL_NOTIFICATION_USERS) {
|
|
1644
|
-
break;
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
return Array.from(unique);
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
private async notifyCustomerWorkflowStatus(
|
|
1651
|
-
stage: 'detected_autofix_enabled' | 'detected_autofix_disabled' | 'completed_success' | 'completed_failed' | 'blocked_library_issue',
|
|
1652
|
-
email: ErrorEmail,
|
|
1653
|
-
log: ErrorAutoFixLogModel,
|
|
1654
|
-
extra?: { error?: string; notes?: string }
|
|
1655
|
-
): Promise<void> {
|
|
1656
|
-
const idClient = String(log?.id_client || email?.id_client || '').trim();
|
|
1657
|
-
let isGeneratedApp = false;
|
|
1658
|
-
if (idClient) {
|
|
1659
|
-
isGeneratedApp = await this.isGeneratedAICoderClient(idClient);
|
|
1660
|
-
}
|
|
1661
|
-
else {
|
|
1662
|
-
const resolvedEnvironment = this.resolveEnvironmentForTask(email, log);
|
|
1663
|
-
const app = await this.resolveAutoFixApp(email, log, resolvedEnvironment);
|
|
1664
|
-
isGeneratedApp = !!app;
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
if (!isGeneratedApp && stage !== 'completed_success') {
|
|
1668
|
-
return;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
const issueKey = String(log?.issue_hash || log?.email_hash || email?.issueHash || email?.hash || log?._id || '').trim();
|
|
1672
|
-
const clientName = String(log?.client_name || email?.client_name || '').trim();
|
|
1673
|
-
const targetPayload: Record<string, any> = {};
|
|
1674
|
-
if (idClient) {
|
|
1675
|
-
targetPayload.target_type = isGeneratedApp ? 'client_internal' : 'client_admins';
|
|
1676
|
-
targetPayload.id_client = idClient;
|
|
1677
|
-
targetPayload.client_name = clientName || undefined;
|
|
1678
|
-
}
|
|
1679
|
-
else {
|
|
1680
|
-
const idUsers = await this.resolveLocalNotificationUserIds(isGeneratedApp);
|
|
1681
|
-
if (!idUsers.length) {
|
|
1682
|
-
return;
|
|
1683
|
-
}
|
|
1684
|
-
targetPayload.target_type = 'users';
|
|
1685
|
-
targetPayload.id_users = idUsers;
|
|
1686
|
-
targetPayload.client_name = clientName || undefined;
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
const basePayload: Record<string, any> = {
|
|
1690
|
-
...targetPayload,
|
|
1691
|
-
category: 'autofix',
|
|
1692
|
-
source: 'error-auto-fix',
|
|
1693
|
-
source_id: String(log?._id || '').trim() || undefined,
|
|
1694
|
-
dedupe_key: issueKey ? `autofix:${issueKey}:${stage}` : `autofix:${String(log?._id || '').trim()}:${stage}`,
|
|
1695
|
-
metadata: {
|
|
1696
|
-
issue_hash: log?.issue_hash || email?.issueHash || '',
|
|
1697
|
-
log_id: log?._id || '',
|
|
1698
|
-
severity: log?.severity || email?.severity || '',
|
|
1699
|
-
source_app: log?.source_app || email?.sourceApp || '',
|
|
1700
|
-
source_environment: log?.source_environment || email?.sourceEnvironment || '',
|
|
1701
|
-
error: extra?.error || '',
|
|
1702
|
-
notes: extra?.notes || ''
|
|
1703
|
-
}
|
|
1704
|
-
};
|
|
1705
|
-
|
|
1706
|
-
let payload: Record<string, any> = {};
|
|
1707
|
-
if (isGeneratedApp) {
|
|
1708
|
-
if (stage === 'detected_autofix_enabled') {
|
|
1709
|
-
payload = {
|
|
1710
|
-
title: 'Issue detected. Auto-fix is in progress.',
|
|
1711
|
-
message: 'We detected an issue in your app and started an automatic fix workflow. We will notify you when it is complete.',
|
|
1712
|
-
severity: 'info',
|
|
1713
|
-
details: this.buildAICoderWorkflowSummary(stage, email, log, extra)
|
|
1714
|
-
};
|
|
1715
|
-
}
|
|
1716
|
-
else if (stage === 'detected_autofix_disabled') {
|
|
1717
|
-
payload = {
|
|
1718
|
-
title: 'Issue detected. Auto-fix is disabled.',
|
|
1719
|
-
message: 'We detected an issue, but automatic fixing is not enabled. Please ask the AI assistant to investigate this issue.',
|
|
1720
|
-
severity: 'warning',
|
|
1721
|
-
details: this.buildAICoderWorkflowSummary(stage, email, log, extra)
|
|
1722
|
-
};
|
|
1723
|
-
}
|
|
1724
|
-
else if (stage === 'completed_success') {
|
|
1725
|
-
payload = {
|
|
1726
|
-
title: 'Auto-fix completed successfully.',
|
|
1727
|
-
message: 'Your issue was fixed automatically and the update was published.',
|
|
1728
|
-
severity: 'success',
|
|
1729
|
-
details: this.buildAICoderWorkflowSummary(stage, email, log, extra)
|
|
1730
|
-
};
|
|
1731
|
-
}
|
|
1732
|
-
else if (stage === 'blocked_library_issue') {
|
|
1733
|
-
payload = {
|
|
1734
|
-
title: 'Issue requires a platform library update.',
|
|
1735
|
-
message: 'We detected this issue in shared platform libraries, so app auto-fix was stopped. Our engineering team has been notified to ship a library-level fix.',
|
|
1736
|
-
severity: 'warning',
|
|
1737
|
-
details: this.buildAICoderWorkflowSummary(stage, email, log, extra)
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
|
-
else {
|
|
1741
|
-
payload = {
|
|
1742
|
-
title: 'Auto-fix needs manual review.',
|
|
1743
|
-
message: 'We could not complete an automatic fix for this issue. Manual follow-up is required.',
|
|
1744
|
-
severity: 'warning',
|
|
1745
|
-
details: this.buildAICoderWorkflowSummary(stage, email, log, extra)
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
else {
|
|
1750
|
-
payload = {
|
|
1751
|
-
title: 'ResolveIO project auto-fix completed',
|
|
1752
|
-
message: `A fix for ${String(log?.source_app || email?.sourceApp || 'the project').trim()} completed successfully.`,
|
|
1753
|
-
severity: 'success',
|
|
1754
|
-
details: this.buildResolveIOProjectWorkflowDetails(email, log, extra)
|
|
1755
|
-
};
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
try {
|
|
1759
|
-
await ResolveIOServer.getMainServer().getMethodManager().callMethod('createCustomerNotification', {
|
|
1760
|
-
...basePayload,
|
|
1761
|
-
...payload
|
|
1762
|
-
});
|
|
1763
|
-
}
|
|
1764
|
-
catch (error) {
|
|
1765
|
-
this.debugLog('Failed to create customer auto-fix notification', {
|
|
1766
|
-
logId: log?._id,
|
|
1767
|
-
stage,
|
|
1768
|
-
error: error?.message || error
|
|
1769
|
-
});
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
private async delay(ms: number): Promise<void> {
|
|
1774
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
1775
|
-
await new Promise(resolve => setTimeout(resolve, ms));
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
public async reportDevError(options: DevErrorReportOptions): Promise<void> {
|
|
1779
|
-
const subject = (options?.subject || '').trim() || 'ResolveIO alert';
|
|
1780
|
-
const body = options?.body || '';
|
|
1781
|
-
const sourceApp = options?.sourceApp || ResolveIOServer.getClientName() || 'resolveio-server';
|
|
1782
|
-
const environment = options?.environment || process.env.NODE_ENV || '';
|
|
1783
|
-
const severity = options?.severity || 'infrastructure';
|
|
1784
|
-
const fingerprint = (options?.fingerprint || createHash('sha256').update(`${sourceApp}:${environment || 'env'}:${subject}`).digest('hex')).trim();
|
|
1785
|
-
const metadata = options?.metadata && Object.keys(options.metadata).length ? options.metadata : undefined;
|
|
1786
|
-
|
|
1787
|
-
if (!this.isReady()) {
|
|
1788
|
-
console.warn('AutoFix manager not ready for dev alert', {subject, environment});
|
|
1789
|
-
return;
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
try {
|
|
1793
|
-
await this.ingestErrorReport({
|
|
1794
|
-
sourceApp,
|
|
1795
|
-
environment,
|
|
1796
|
-
message: subject,
|
|
1797
|
-
stack: body,
|
|
1798
|
-
severity,
|
|
1799
|
-
metadata,
|
|
1800
|
-
fingerprint,
|
|
1801
|
-
idempotencyKey: options?.idempotencyKey,
|
|
1802
|
-
clientId: options?.clientId,
|
|
1803
|
-
clientName: options?.clientName,
|
|
1804
|
-
clientSlug: options?.clientSlug,
|
|
1805
|
-
reportedBy: 'resolveio-server'
|
|
1806
|
-
});
|
|
1807
|
-
}
|
|
1808
|
-
catch (err) {
|
|
1809
|
-
console.error('Failed to log developer alert', subject, err);
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
public async ingestErrorReport(report: ErrorReportPayload): Promise<ErrorReportIngestResult> {
|
|
1814
|
-
if (!this.isReady()) {
|
|
1815
|
-
throw new Error('ErrorAutoFixManager is not enabled.');
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
if (!report || !report.sourceApp || !report.message) {
|
|
1819
|
-
throw new Error('Invalid error report payload.');
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
const normalizedReport: ErrorReportPayload = {
|
|
1823
|
-
...report,
|
|
1824
|
-
message: (report.message || '').toString(),
|
|
1825
|
-
stack: report.stack ? report.stack.toString() : undefined,
|
|
1826
|
-
metadata: report.metadata || undefined,
|
|
1827
|
-
context: report.context || undefined,
|
|
1828
|
-
attachments: Array.isArray(report.attachments) ? report.attachments : undefined,
|
|
1829
|
-
reportedAt: report.reportedAt instanceof Date && !Number.isNaN(report.reportedAt.getTime()) ? report.reportedAt : new Date()
|
|
1830
|
-
};
|
|
1831
|
-
|
|
1832
|
-
const email = this.buildEmailFromReport(normalizedReport);
|
|
1833
|
-
email.reportedAt = normalizedReport.reportedAt;
|
|
1834
|
-
email.openai_environment = normalizedReport.clientRepo || email.openai_environment;
|
|
1835
|
-
email.fromAddress = this.extractEmailAddress(normalizedReport.clientEmail || normalizedReport.reportedBy || email.from || '');
|
|
1836
|
-
if (!email.fromAddress && normalizedReport.clientSlug) {
|
|
1837
|
-
email.fromAddress = `${normalizedReport.clientSlug}@errors.resolveio.com`;
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
const rawHash = normalizedReport.idempotencyKey ? normalizedReport.idempotencyKey.trim() : '';
|
|
1841
|
-
email.rawHash = rawHash || this.generateRawEmailHash(email);
|
|
1842
|
-
const fingerprint = normalizedReport.fingerprint ? normalizedReport.fingerprint.trim() : '';
|
|
1843
|
-
if (fingerprint) {
|
|
1844
|
-
email.issueHash = fingerprint;
|
|
1845
|
-
email.hash = fingerprint;
|
|
1846
|
-
}
|
|
1847
|
-
else {
|
|
1848
|
-
email.issueHash = this.generateIssueHash(email);
|
|
1849
|
-
email.hash = email.issueHash || email.rawHash;
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
const client = await this.resolveClientForReport(normalizedReport);
|
|
1853
|
-
const autoOptimizeEnabled = await this.resolveAutoOptimizeEnabled();
|
|
1854
|
-
const reservation = await this.reserveLog(email, client, autoOptimizeEnabled);
|
|
1855
|
-
|
|
1856
|
-
if (!reservation.proceed) {
|
|
1857
|
-
if (reservation.reason && this.shouldNotifySkip(reservation.reason)) {
|
|
1858
|
-
await this.notify({
|
|
1859
|
-
status: 'skipped',
|
|
1860
|
-
email,
|
|
1861
|
-
reason: reservation.reason,
|
|
1862
|
-
error: `Skipped auto-fix (${reservation.reason}).`,
|
|
1863
|
-
log: reservation.log
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
return {
|
|
1868
|
-
status: reservation.reason === 'duplicate' ? 'duplicate' : (reservation.reason as any) || 'skipped',
|
|
1869
|
-
reason: reservation.reason,
|
|
1870
|
-
log: reservation.log
|
|
1871
|
-
};
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
email.logId = reservation.log._id;
|
|
1875
|
-
const classification = this.classifyEmail(email);
|
|
1876
|
-
await this.updateLog(email.logId, {
|
|
1877
|
-
id_client: client?._id,
|
|
1878
|
-
client_name: client?.name,
|
|
1879
|
-
from_email: email.fromAddress,
|
|
1880
|
-
source_type: classification.source,
|
|
1881
|
-
classification_reason: classification.reason || '',
|
|
1882
|
-
openai_environment: email.openai_environment || reservation.log.openai_environment || this.determineOpenAIEnvironment(client),
|
|
1883
|
-
source_app: email.sourceApp,
|
|
1884
|
-
source_environment: email.sourceEnvironment,
|
|
1885
|
-
severity: email.severity,
|
|
1886
|
-
reported_by: email.reportedBy || email.fromAddress,
|
|
1887
|
-
report_metadata: email.reportMetadata,
|
|
1888
|
-
report_context: email.reportContext,
|
|
1889
|
-
attachments: email.attachments,
|
|
1890
|
-
last_reported_at: email.reportedAt || new Date()
|
|
1891
|
-
});
|
|
1892
|
-
|
|
1893
|
-
if (classification.skip) {
|
|
1894
|
-
const updated = await this.updateLog(email.logId, {
|
|
1895
|
-
status: 'skipped',
|
|
1896
|
-
ignored: !!classification.markIgnored,
|
|
1897
|
-
last_error: classification.reason || 'Ignored non-app alert',
|
|
1898
|
-
last_result_at: new Date()
|
|
1899
|
-
});
|
|
1900
|
-
|
|
1901
|
-
if (classification.notify) {
|
|
1902
|
-
await this.notify({
|
|
1903
|
-
status: 'skipped',
|
|
1904
|
-
email,
|
|
1905
|
-
error: classification.reason,
|
|
1906
|
-
log: updated,
|
|
1907
|
-
reason: 'classified'
|
|
1908
|
-
});
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
return {
|
|
1912
|
-
status: 'skipped',
|
|
1913
|
-
reason: classification.reason,
|
|
1914
|
-
log: updated
|
|
1915
|
-
};
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
if (!autoOptimizeEnabled) {
|
|
1919
|
-
const queued = await this.updateLog(email.logId, {
|
|
1920
|
-
status: 'pending'
|
|
1921
|
-
});
|
|
1922
|
-
await this.notifyCustomerWorkflowStatus('detected_autofix_disabled', email, queued || reservation.log);
|
|
1923
|
-
|
|
1924
|
-
return {
|
|
1925
|
-
status: 'queued',
|
|
1926
|
-
log: queued
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
await this.notifyCustomerWorkflowStatus('detected_autofix_enabled', email, reservation.log);
|
|
1931
|
-
const result = await this.processEmail(email, reservation.log);
|
|
1932
|
-
await this.notify(result);
|
|
1933
|
-
const finalLog = result.log || (await ErrorAutoFixLogs.findOne({_id: email.logId}));
|
|
1934
|
-
return {
|
|
1935
|
-
status: result.status,
|
|
1936
|
-
reason: result.reason,
|
|
1937
|
-
log: finalLog || result.log
|
|
1938
|
-
};
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
public async runLog(logId: string): Promise<AutoFixResult> {
|
|
1942
|
-
if (!this.config.enabled) {
|
|
1943
|
-
throw new Error('Auto-fix manager disabled.');
|
|
1944
|
-
}
|
|
1945
|
-
|
|
1946
|
-
const log = await ErrorAutoFixLogs.findOne({_id: logId});
|
|
1947
|
-
if (!log) {
|
|
1948
|
-
throw new Error('Auto-fix log not found.');
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
if (log.ignored) {
|
|
1952
|
-
throw new Error('Auto-fix log is ignored.');
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
if (log.status === 'in_progress') {
|
|
1956
|
-
throw new Error('Auto-fix workflow already running.');
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
if (!log.body) {
|
|
1960
|
-
throw new Error('Auto-fix log is missing email body.');
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
const now = new Date();
|
|
1964
|
-
const email = this.buildEmailFromLog(log);
|
|
1965
|
-
const classification = this.classifyEmail(email);
|
|
1966
|
-
if (classification.skip && classification.markIgnored) {
|
|
1967
|
-
const ignoredLog = await this.updateLog(logId, {
|
|
1968
|
-
status: 'skipped',
|
|
1969
|
-
ignored: true,
|
|
1970
|
-
last_error: classification.reason || 'Mongo-origin error ignored for auto-fix',
|
|
1971
|
-
last_result_at: now
|
|
1972
|
-
});
|
|
1973
|
-
return {
|
|
1974
|
-
status: 'skipped',
|
|
1975
|
-
email,
|
|
1976
|
-
reason: 'classified',
|
|
1977
|
-
error: classification.reason || 'Mongo-origin error ignored for auto-fix',
|
|
1978
|
-
log: ignoredLog
|
|
1979
|
-
};
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
const inProgressLog = (await ErrorAutoFixLogs.findOneAndUpdate({_id: logId}, {
|
|
1983
|
-
$inc: {
|
|
1984
|
-
attempt_count: 1
|
|
1985
|
-
},
|
|
1986
|
-
$set: {
|
|
1987
|
-
status: 'in_progress',
|
|
1988
|
-
last_attempt_at: now,
|
|
1989
|
-
last_error: '',
|
|
1990
|
-
ignored: false
|
|
1991
|
-
}
|
|
1992
|
-
}, {
|
|
1993
|
-
returnDocument: 'after'
|
|
1994
|
-
})) || log;
|
|
1995
|
-
|
|
1996
|
-
const result = await this.processEmail(email, inProgressLog);
|
|
1997
|
-
await this.notify(result);
|
|
1998
|
-
return result;
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
private buildEmailFromLog(log: ErrorAutoFixLogModel): ErrorEmail {
|
|
2002
|
-
return {
|
|
2003
|
-
key: log.message_id || log._id,
|
|
2004
|
-
subject: log.subject,
|
|
2005
|
-
body: log.body || '',
|
|
2006
|
-
messageId: log.message_id,
|
|
2007
|
-
hash: log.issue_hash || log.email_hash,
|
|
2008
|
-
rawHash: log.email_hash,
|
|
2009
|
-
issueHash: log.issue_hash,
|
|
2010
|
-
raw: log.body || '',
|
|
2011
|
-
id_client: log.id_client,
|
|
2012
|
-
client_name: log.client_name,
|
|
2013
|
-
fromAddress: log.from_email,
|
|
2014
|
-
source_type: log.source_type || 'app',
|
|
2015
|
-
classification_reason: log.classification_reason,
|
|
2016
|
-
openai_environment: log.openai_environment,
|
|
2017
|
-
sourceApp: log.source_app,
|
|
2018
|
-
sourceEnvironment: log.source_environment,
|
|
2019
|
-
severity: log.severity,
|
|
2020
|
-
reportedBy: log.reported_by,
|
|
2021
|
-
reportMetadata: log.report_metadata,
|
|
2022
|
-
reportContext: log.report_context,
|
|
2023
|
-
attachments: log.attachments,
|
|
2024
|
-
reportedAt: log.last_reported_at
|
|
2025
|
-
};
|
|
2026
|
-
}
|
|
2027
|
-
|
|
2028
|
-
private async processEmail(email: ErrorEmail, log: ErrorAutoFixLogModel): Promise<AutoFixResult> {
|
|
2029
|
-
const libraryIssueGate = this.evaluateLibraryIssueGate(email, log);
|
|
2030
|
-
if (libraryIssueGate.blocked) {
|
|
2031
|
-
return await this.blockLibraryOwnedIssue(email, log, libraryIssueGate);
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
if (this.config.dashboardWorkflowEnabled) {
|
|
2035
|
-
const dashboardResult = await this.dispatchDashboardWorkflow(email, log);
|
|
2036
|
-
if (dashboardResult.status === 'in_progress' || dashboardResult.status === 'success') {
|
|
2037
|
-
return dashboardResult;
|
|
2038
|
-
}
|
|
2039
|
-
if (!this.config.dashboardFallbackToGithub) {
|
|
2040
|
-
return dashboardResult;
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
this.debugLog('Dashboard workflow unavailable; falling back to GitHub workflow.', {
|
|
2044
|
-
logId: log?._id,
|
|
2045
|
-
reason: dashboardResult.reason,
|
|
2046
|
-
error: dashboardResult.error
|
|
2047
|
-
});
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
return await this.dispatchGithubWorkflow(email, log);
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
private shouldFallbackDashboardMethod(error: any): boolean {
|
|
2054
|
-
const message = `${error?.message || ''}`.toLowerCase();
|
|
2055
|
-
return message.includes('not found')
|
|
2056
|
-
|| message.includes('worker dispatch unavailable')
|
|
2057
|
-
|| message.includes('no worker')
|
|
2058
|
-
|| message.includes('unavailable for aidashboard');
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
private async createDashboardJob(payload: Record<string, any>): Promise<AIDashboardJob> {
|
|
2062
|
-
const methodManager = ResolveIOServer.getMainServer().getMethodManager();
|
|
2063
|
-
try {
|
|
2064
|
-
return await methodManager.callMethod('aiDashboardCreateJob', payload);
|
|
2065
|
-
}
|
|
2066
|
-
catch (error) {
|
|
2067
|
-
if (!this.shouldFallbackDashboardMethod(error)) {
|
|
2068
|
-
throw error;
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
const manager: any = ResolveIOServer['AIDashboardManager'];
|
|
2073
|
-
if (manager && manager.isEnabled && manager.isEnabled()) {
|
|
2074
|
-
return await manager.createJob(payload);
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
throw new Error('AI Dashboard manager is not available.');
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
private async waitForDashboardJobStop(jobId: string, timeoutMs: number): Promise<void> {
|
|
2081
|
-
const methodManager = ResolveIOServer.getMainServer().getMethodManager();
|
|
2082
|
-
try {
|
|
2083
|
-
await methodManager.callMethod('aiDashboardWaitForJobStop', jobId, timeoutMs);
|
|
2084
|
-
return;
|
|
2085
|
-
}
|
|
2086
|
-
catch (error) {
|
|
2087
|
-
if (!this.shouldFallbackDashboardMethod(error)) {
|
|
2088
|
-
throw error;
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
const manager: any = ResolveIOServer['AIDashboardManager'];
|
|
2093
|
-
if (manager && manager.isEnabled && manager.isEnabled()) {
|
|
2094
|
-
await manager.waitForJobStop(jobId, timeoutMs);
|
|
2095
|
-
return;
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
throw new Error('AI Dashboard manager is not available.');
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
private async isDashboardJobRunning(jobId: string): Promise<boolean> {
|
|
2102
|
-
const methodManager = ResolveIOServer.getMainServer().getMethodManager();
|
|
2103
|
-
try {
|
|
2104
|
-
const running = await methodManager.callMethod('aiDashboardIsJobRunning', jobId);
|
|
2105
|
-
return !!running;
|
|
2106
|
-
}
|
|
2107
|
-
catch (error) {
|
|
2108
|
-
if (!this.shouldFallbackDashboardMethod(error)) {
|
|
2109
|
-
throw error;
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
const manager: any = ResolveIOServer['AIDashboardManager'];
|
|
2114
|
-
if (manager && manager.isEnabled && manager.isEnabled()) {
|
|
2115
|
-
return !!manager.isJobRunning(jobId);
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
throw new Error('AI Dashboard manager is not available.');
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
private async resolveAutoFixApp(email: ErrorEmail, log: ErrorAutoFixLogModel, resolvedEnvironment: string): Promise<AICoderAppModel | null> {
|
|
2122
|
-
const clientId = (email.id_client || log.id_client || '').trim();
|
|
2123
|
-
if (clientId) {
|
|
2124
|
-
const byClientId = await AICoderApps.findOne({client_id: clientId}, {
|
|
2125
|
-
sort: {
|
|
2126
|
-
updatedAt: -1,
|
|
2127
|
-
createdAt: -1
|
|
2128
|
-
}
|
|
2129
|
-
});
|
|
2130
|
-
if (byClientId) {
|
|
2131
|
-
return byClientId;
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
|
|
2135
|
-
const lookupClientCandidates = [
|
|
2136
|
-
(email.client_name || '').trim(),
|
|
2137
|
-
(log.client_name || '').trim()
|
|
2138
|
-
].filter(Boolean);
|
|
2139
|
-
for (const candidate of lookupClientCandidates) {
|
|
2140
|
-
const escaped = this.escapeRegex(candidate);
|
|
2141
|
-
const clientDoc = await Clients.findOne({
|
|
2142
|
-
$or: [
|
|
2143
|
-
{name: {$regex: `^${escaped}$`, $options: 'i'}},
|
|
2144
|
-
{demo_name: {$regex: `^${escaped}$`, $options: 'i'}},
|
|
2145
|
-
{repo: {$regex: `^${escaped}$`, $options: 'i'}}
|
|
2146
|
-
]
|
|
2147
|
-
});
|
|
2148
|
-
if (!clientDoc?._id) {
|
|
2149
|
-
continue;
|
|
2150
|
-
}
|
|
2151
|
-
const byClient = await AICoderApps.findOne({client_id: clientDoc._id}, {
|
|
2152
|
-
sort: {
|
|
2153
|
-
updatedAt: -1,
|
|
2154
|
-
createdAt: -1
|
|
2155
|
-
}
|
|
2156
|
-
});
|
|
2157
|
-
if (byClient) {
|
|
2158
|
-
return byClient;
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
const environmentRepo = (resolvedEnvironment || '').trim();
|
|
2163
|
-
if (environmentRepo) {
|
|
2164
|
-
const escapedRepo = this.escapeRegex(environmentRepo);
|
|
2165
|
-
const byRepo = await AICoderApps.findOne({
|
|
2166
|
-
repo: {
|
|
2167
|
-
$regex: `^${escapedRepo}$`,
|
|
2168
|
-
$options: 'i'
|
|
2169
|
-
}
|
|
2170
|
-
}, {
|
|
2171
|
-
sort: {
|
|
2172
|
-
updatedAt: -1,
|
|
2173
|
-
createdAt: -1
|
|
2174
|
-
}
|
|
2175
|
-
});
|
|
2176
|
-
if (byRepo) {
|
|
2177
|
-
return byRepo;
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
const appNameCandidates = [
|
|
2182
|
-
(email.sourceApp || '').trim(),
|
|
2183
|
-
(log.source_app || '').trim()
|
|
2184
|
-
].filter(Boolean);
|
|
2185
|
-
for (const candidate of appNameCandidates) {
|
|
2186
|
-
const escaped = this.escapeRegex(candidate);
|
|
2187
|
-
const bySlugOrName = await AICoderApps.findOne({
|
|
2188
|
-
$or: [
|
|
2189
|
-
{slug: {$regex: `^${escaped}$`, $options: 'i'}},
|
|
2190
|
-
{name: {$regex: `^${escaped}$`, $options: 'i'}}
|
|
2191
|
-
]
|
|
2192
|
-
}, {
|
|
2193
|
-
sort: {
|
|
2194
|
-
updatedAt: -1,
|
|
2195
|
-
createdAt: -1
|
|
2196
|
-
}
|
|
2197
|
-
});
|
|
2198
|
-
if (bySlugOrName) {
|
|
2199
|
-
return bySlugOrName;
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
|
|
2203
|
-
return null;
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
private buildDashboardAutoFixTitle(email: ErrorEmail, log: ErrorAutoFixLogModel, app: AICoderAppModel): string {
|
|
2207
|
-
const identifier = log.autofix_error_count_string || (log._id || '').slice(-6) || 'error';
|
|
2208
|
-
const source = (email.sourceApp || log.source_app || app?.name || 'application').trim();
|
|
2209
|
-
return `Auto-fix ${identifier}: ${source}`;
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
private buildDashboardAutoFixDescription(email: ErrorEmail, log: ErrorAutoFixLogModel, app: AICoderAppModel): string {
|
|
2213
|
-
const normalizedBody = this.normalizeEmailText(email.body || log.body || '', 15000);
|
|
2214
|
-
const metadata = log.report_metadata && Object.keys(log.report_metadata).length
|
|
2215
|
-
? JSON.stringify(log.report_metadata, null, 2)
|
|
2216
|
-
: '';
|
|
2217
|
-
const context = log.report_context && Object.keys(log.report_context).length
|
|
2218
|
-
? JSON.stringify(log.report_context, null, 2)
|
|
2219
|
-
: '';
|
|
2220
|
-
const attachments = Array.isArray(log.attachments) ? log.attachments : [];
|
|
2221
|
-
const attachmentLines = attachments.length
|
|
2222
|
-
? attachments.map(att => `- ${(att?.name || 'attachment').trim()} ${att?.url ? `(${att.url})` : ''}`.trim()).join('\n')
|
|
2223
|
-
: '(none)';
|
|
2224
|
-
const lines: string[] = [
|
|
2225
|
-
'Autonomous production error remediation request.',
|
|
2226
|
-
'',
|
|
2227
|
-
'Hard requirements:',
|
|
2228
|
-
'1. Before changing code, query the project MongoDB diagnostics logs for the latest context.',
|
|
2229
|
-
'2. Query `error-autofix-logs` using `_id`, `issue_hash`, and `email_hash` from this task, then use the newest matching records.',
|
|
2230
|
-
'3. If a previous dashboard job id is provided, query `ai-development-jobs` by that `_id` and review recent failure logs before retrying.',
|
|
2231
|
-
'4. Treat `.dashboard-output/build-*.log` as primary build evidence, and `.build-output/build-*.log` as retained history when diagnosing failures.',
|
|
2232
|
-
'5. Reproduce and fix the reported issue with the smallest safe code change.',
|
|
2233
|
-
'6. Keep query/result behavior backward compatible unless explicitly required by the bug fix.',
|
|
2234
|
-
'7. Run project build checks and continue iterating until the build is green.',
|
|
2235
|
-
'8. Ensure there are no new lint/type/runtime regressions introduced by the fix.',
|
|
2236
|
-
'9. Finish by publishing the passing fix to the default branch and deploying artifacts.',
|
|
2237
|
-
'',
|
|
2238
|
-
`App: ${app?.name || app?._id || 'unknown'}`,
|
|
2239
|
-
`Repo: ${app?.repo || 'unknown'}`,
|
|
2240
|
-
`Environment: ${email.sourceEnvironment || log.source_environment || 'unknown'}`,
|
|
2241
|
-
`Severity: ${email.severity || log.severity || 'error'}`,
|
|
2242
|
-
`Fingerprint: ${email.issueHash || log.issue_hash || log.email_hash || 'n/a'}`,
|
|
2243
|
-
`Error Log Id: ${String(log?._id || '').trim() || 'n/a'}`,
|
|
2244
|
-
`Previous Dashboard Job Id: ${String(log?.openai_task_id || '').trim() || 'n/a'}`,
|
|
2245
|
-
'',
|
|
2246
|
-
'Reported error payload:',
|
|
2247
|
-
'```',
|
|
2248
|
-
normalizedBody || '(empty error body)',
|
|
2249
|
-
'```',
|
|
2250
|
-
'',
|
|
2251
|
-
'Attachments:',
|
|
2252
|
-
attachmentLines
|
|
2253
|
-
];
|
|
2254
|
-
if (metadata) {
|
|
2255
|
-
lines.push('', 'Metadata JSON:', '```json', metadata, '```');
|
|
2256
|
-
}
|
|
2257
|
-
if (context) {
|
|
2258
|
-
lines.push('', 'Context JSON:', '```json', context, '```');
|
|
2259
|
-
}
|
|
2260
|
-
return lines.join('\n');
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
private evaluateDashboardPublishOutcome(job: AIDashboardJob): { success: boolean; branchName?: string; message: string } {
|
|
2264
|
-
const logEntries = Array.isArray(job?.log) ? job.log : [];
|
|
2265
|
-
const lastMatch = (predicate): string => {
|
|
2266
|
-
for (let i = logEntries.length - 1; i >= 0; i -= 1) {
|
|
2267
|
-
if (predicate(logEntries[i] || '')) {
|
|
2268
|
-
return logEntries[i];
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
return '';
|
|
2272
|
-
};
|
|
2273
|
-
|
|
2274
|
-
const failureEntry = lastMatch(entry => /publish failed|artifact publish failed|deploy failed/i.test(entry || ''));
|
|
2275
|
-
if (failureEntry) {
|
|
2276
|
-
return {
|
|
2277
|
-
success: false,
|
|
2278
|
-
message: failureEntry
|
|
2279
|
-
};
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
const publishEntry = lastMatch(entry => /Published build to /i.test(entry || ''));
|
|
2283
|
-
if (publishEntry) {
|
|
2284
|
-
const branchMatch = publishEntry.match(/\(([^()]+)\)\.?$/);
|
|
2285
|
-
return {
|
|
2286
|
-
success: true,
|
|
2287
|
-
branchName: (branchMatch && branchMatch[1]) ? branchMatch[1].trim() : undefined,
|
|
2288
|
-
message: publishEntry
|
|
2289
|
-
};
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
const skippedEntry = lastMatch(entry => /Publish skipped/i.test(entry || ''));
|
|
2293
|
-
if (skippedEntry) {
|
|
2294
|
-
return {
|
|
2295
|
-
success: !this.config.dashboardPublishRequired,
|
|
2296
|
-
message: skippedEntry
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
return {
|
|
2301
|
-
success: !this.config.dashboardPublishRequired,
|
|
2302
|
-
message: 'Dashboard job completed without a publish confirmation log entry.'
|
|
2303
|
-
};
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
private queueDashboardMonitor(log: ErrorAutoFixLogModel, email: ErrorEmail, jobId: string, effectiveEnvironment: string): void {
|
|
2307
|
-
const logId = (log?._id || '').trim();
|
|
2308
|
-
const normalizedJobId = (jobId || '').trim();
|
|
2309
|
-
if (!logId || !normalizedJobId) {
|
|
2310
|
-
return;
|
|
2311
|
-
}
|
|
2312
|
-
if (this.dashboardMonitors.has(logId)) {
|
|
2313
|
-
return;
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
|
-
const monitor = (async () => {
|
|
2317
|
-
try {
|
|
2318
|
-
await this.monitorDashboardAutoFixJob(log, email, normalizedJobId, effectiveEnvironment);
|
|
2319
|
-
}
|
|
2320
|
-
catch (error) {
|
|
2321
|
-
console.error('Auto-fix dashboard monitor failed', {logId, jobId: normalizedJobId, error});
|
|
2322
|
-
}
|
|
2323
|
-
finally {
|
|
2324
|
-
this.dashboardMonitors.delete(logId);
|
|
2325
|
-
}
|
|
2326
|
-
})();
|
|
2327
|
-
|
|
2328
|
-
this.dashboardMonitors.set(logId, monitor);
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
private async monitorDashboardAutoFixJob(log: ErrorAutoFixLogModel, email: ErrorEmail, jobId: string, effectiveEnvironment: string): Promise<void> {
|
|
2332
|
-
const logId = (log?._id || '').trim();
|
|
2333
|
-
if (!logId) {
|
|
2334
|
-
return;
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
const fail = async (message: string): Promise<void> => {
|
|
2338
|
-
const failedLog = (await this.updateLog(logId, {
|
|
2339
|
-
status: 'failed',
|
|
2340
|
-
last_error: message,
|
|
2341
|
-
last_result_at: new Date(),
|
|
2342
|
-
openai_task_id: jobId,
|
|
2343
|
-
openai_environment: effectiveEnvironment
|
|
2344
|
-
})) || log;
|
|
2345
|
-
await this.notifyCustomerWorkflowStatus('completed_failed', email, failedLog, {error: message});
|
|
2346
|
-
await this.notify({
|
|
2347
|
-
status: 'failed',
|
|
2348
|
-
email,
|
|
2349
|
-
error: message,
|
|
2350
|
-
log: failedLog,
|
|
2351
|
-
summary: failedLog.plan_summary || email.subject
|
|
2352
|
-
});
|
|
2353
|
-
};
|
|
2354
|
-
|
|
2355
|
-
try {
|
|
2356
|
-
await this.waitForDashboardJobStop(jobId, this.config.dashboardWaitTimeoutMs);
|
|
2357
|
-
}
|
|
2358
|
-
catch (error) {
|
|
2359
|
-
const message = error?.message || 'Failed while waiting for dashboard job completion.';
|
|
2360
|
-
await fail(message);
|
|
2361
|
-
return;
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
|
-
let isRunning = false;
|
|
2365
|
-
try {
|
|
2366
|
-
isRunning = await this.isDashboardJobRunning(jobId);
|
|
2367
|
-
}
|
|
2368
|
-
catch (error) {
|
|
2369
|
-
const message = error?.message || 'Unable to confirm dashboard job completion state.';
|
|
2370
|
-
await fail(message);
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
if (isRunning) {
|
|
2375
|
-
await fail(`Dashboard job ${jobId} timed out before completion.`);
|
|
2376
|
-
return;
|
|
2377
|
-
}
|
|
2378
|
-
|
|
2379
|
-
const job = await AIDashboardJobs.findOne({_id: jobId}) as AIDashboardJob | null;
|
|
2380
|
-
if (!job) {
|
|
2381
|
-
await fail(`Dashboard job ${jobId} not found.`);
|
|
2382
|
-
return;
|
|
2383
|
-
}
|
|
2384
|
-
|
|
2385
|
-
if (job.phase !== 'COMPLETE' || job.paused) {
|
|
2386
|
-
await fail(`Dashboard job ${jobId} ended in phase ${job.phase || 'unknown'}${job.paused ? ' (paused)' : ''}.`);
|
|
2387
|
-
return;
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
const publishOutcome = this.evaluateDashboardPublishOutcome(job);
|
|
2391
|
-
if (!publishOutcome.success) {
|
|
2392
|
-
await fail(publishOutcome.message);
|
|
2393
|
-
return;
|
|
2394
|
-
}
|
|
2395
|
-
|
|
2396
|
-
const fallbackBranch = (String(this._serverConfig?.['GITHUB_DEFAULT_BRANCH'] || process.env.GITHUB_DEFAULT_BRANCH || 'main').trim() || 'main');
|
|
2397
|
-
const branchName = publishOutcome.branchName || fallbackBranch;
|
|
2398
|
-
const successLog = (await this.updateLog(logId, {
|
|
2399
|
-
status: 'success',
|
|
2400
|
-
branch_name: branchName,
|
|
2401
|
-
pr_url: '',
|
|
2402
|
-
last_error: '',
|
|
2403
|
-
last_result_at: new Date(),
|
|
2404
|
-
openai_task_id: jobId,
|
|
2405
|
-
openai_environment: effectiveEnvironment
|
|
2406
|
-
})) || log;
|
|
2407
|
-
await this.notifyCustomerWorkflowStatus('completed_success', email, successLog, {notes: publishOutcome.message});
|
|
2408
|
-
|
|
2409
|
-
await this.notify({
|
|
2410
|
-
status: 'success',
|
|
2411
|
-
email,
|
|
2412
|
-
branchName,
|
|
2413
|
-
log: successLog,
|
|
2414
|
-
summary: successLog.plan_summary || email.subject,
|
|
2415
|
-
notes: publishOutcome.message
|
|
2416
|
-
});
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
private async dispatchDashboardWorkflow(email: ErrorEmail, log: ErrorAutoFixLogModel): Promise<AutoFixResult> {
|
|
2420
|
-
const fail = async (
|
|
2421
|
-
status: 'failed' | 'skipped',
|
|
2422
|
-
reason: string,
|
|
2423
|
-
message: string
|
|
2424
|
-
): Promise<AutoFixResult> => {
|
|
2425
|
-
const updated = (await this.updateLog(log._id, {
|
|
2426
|
-
status,
|
|
2427
|
-
last_error: message,
|
|
2428
|
-
last_result_at: new Date()
|
|
2429
|
-
})) || log;
|
|
2430
|
-
return {
|
|
2431
|
-
status,
|
|
2432
|
-
email,
|
|
2433
|
-
error: message,
|
|
2434
|
-
log: updated,
|
|
2435
|
-
reason
|
|
2436
|
-
};
|
|
2437
|
-
};
|
|
2438
|
-
|
|
2439
|
-
const openaiEnvironment = this.resolveEnvironmentForTask(email, log);
|
|
2440
|
-
const app = await this.resolveAutoFixApp(email, log, openaiEnvironment);
|
|
2441
|
-
if (!app?._id) {
|
|
2442
|
-
return await fail('skipped', 'dashboard_app_not_found', 'No AI Coder app found for this error log.');
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
const repo = (app.repo || '').trim();
|
|
2446
|
-
if (!repo) {
|
|
2447
|
-
return await fail('skipped', 'dashboard_repo_missing', 'Resolved AI Coder app is missing repo configuration.');
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
const effectiveEnvironment = repo;
|
|
2451
|
-
const emailHash = email.issueHash || email.hash || this.generateIssueHash(email);
|
|
2452
|
-
const rawHash = email.rawHash || this.generateRawEmailHash(email);
|
|
2453
|
-
const title = this.buildDashboardAutoFixTitle(email, log, app);
|
|
2454
|
-
const description = this.buildDashboardAutoFixDescription(email, log, app);
|
|
2455
|
-
|
|
2456
|
-
let job: AIDashboardJob;
|
|
2457
|
-
try {
|
|
2458
|
-
job = await this.createDashboardJob({
|
|
2459
|
-
project: app._id,
|
|
2460
|
-
title,
|
|
2461
|
-
description,
|
|
2462
|
-
repo,
|
|
2463
|
-
path: app.git_local_path || undefined,
|
|
2464
|
-
projectRoot: app.project_root || undefined
|
|
2465
|
-
});
|
|
2466
|
-
}
|
|
2467
|
-
catch (error) {
|
|
2468
|
-
const message = error?.message || 'Unable to enqueue dashboard auto-fix job.';
|
|
2469
|
-
return await fail('failed', 'dashboard_job_create_failed', message);
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
|
-
const jobId = (job?._id || '').trim();
|
|
2473
|
-
if (!jobId) {
|
|
2474
|
-
return await fail('failed', 'dashboard_job_missing_id', 'Dashboard job created without an id.');
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
const queuedLog = (await this.updateLog(log._id, {
|
|
2478
|
-
status: 'in_progress',
|
|
2479
|
-
last_attempt_at: new Date(),
|
|
2480
|
-
branch_name: '',
|
|
2481
|
-
pr_url: '',
|
|
2482
|
-
last_error: '',
|
|
2483
|
-
openai_environment: effectiveEnvironment,
|
|
2484
|
-
issue_hash: emailHash,
|
|
2485
|
-
email_hash: rawHash,
|
|
2486
|
-
openai_task_id: jobId,
|
|
2487
|
-
plan_summary: title,
|
|
2488
|
-
plan_body: description
|
|
2489
|
-
})) || log;
|
|
2490
|
-
this.queueDashboardMonitor(queuedLog, email, jobId, effectiveEnvironment);
|
|
2491
|
-
|
|
2492
|
-
return {
|
|
2493
|
-
status: 'in_progress',
|
|
2494
|
-
email,
|
|
2495
|
-
log: queuedLog,
|
|
2496
|
-
reason: 'dashboard_job_queued',
|
|
2497
|
-
summary: title,
|
|
2498
|
-
notes: `Dashboard job ${jobId} queued for autonomous build/fix/publish/deploy.`
|
|
2499
|
-
};
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
private async dispatchGithubWorkflow(email: ErrorEmail, log: ErrorAutoFixLogModel): Promise<AutoFixResult> {
|
|
2503
|
-
const openaiEnvironment = this.resolveEnvironmentForTask(email, log);
|
|
2504
|
-
const fallbackEnv = this.config.githubOwner && this.config.githubRepo ? `${this.config.githubOwner}/${this.config.githubRepo}` : '';
|
|
2505
|
-
const resolvedEnvironment = (openaiEnvironment || fallbackEnv || '').trim();
|
|
2506
|
-
const repoTarget = this.parseRepoTarget(resolvedEnvironment) || this.parseRepoTarget(fallbackEnv);
|
|
2507
|
-
|
|
2508
|
-
this.debugLog('Starting GitHub PR workflow.', {
|
|
2509
|
-
emailKey: email.key,
|
|
2510
|
-
logId: log._id,
|
|
2511
|
-
openaiEnvironment: resolvedEnvironment,
|
|
2512
|
-
repoTarget
|
|
2513
|
-
});
|
|
2514
|
-
|
|
2515
|
-
if (!this.config.githubToken) {
|
|
2516
|
-
const updated = (await this.updateLog(log._id, {
|
|
2517
|
-
status: 'skipped',
|
|
2518
|
-
last_error: 'GitHub token not configured',
|
|
2519
|
-
last_result_at: new Date(),
|
|
2520
|
-
id_client: email.id_client,
|
|
2521
|
-
client_name: email.client_name,
|
|
2522
|
-
from_email: email.fromAddress,
|
|
2523
|
-
source_type: email.source_type,
|
|
2524
|
-
classification_reason: email.classification_reason || '',
|
|
2525
|
-
openai_environment: resolvedEnvironment
|
|
2526
|
-
})) || log;
|
|
2527
|
-
|
|
2528
|
-
return {
|
|
2529
|
-
status: 'skipped',
|
|
2530
|
-
email,
|
|
2531
|
-
error: 'GitHub token not configured',
|
|
2532
|
-
log: updated,
|
|
2533
|
-
reason: 'missing_github_token'
|
|
2534
|
-
};
|
|
2535
|
-
}
|
|
2536
|
-
|
|
2537
|
-
if (!repoTarget) {
|
|
2538
|
-
const updated = (await this.updateLog(log._id, {
|
|
2539
|
-
status: 'skipped',
|
|
2540
|
-
last_error: 'GitHub repository not configured',
|
|
2541
|
-
last_result_at: new Date(),
|
|
2542
|
-
id_client: email.id_client,
|
|
2543
|
-
client_name: email.client_name,
|
|
2544
|
-
from_email: email.fromAddress,
|
|
2545
|
-
source_type: email.source_type,
|
|
2546
|
-
classification_reason: email.classification_reason || '',
|
|
2547
|
-
openai_environment: resolvedEnvironment
|
|
2548
|
-
})) || log;
|
|
2549
|
-
|
|
2550
|
-
return {
|
|
2551
|
-
status: 'skipped',
|
|
2552
|
-
email,
|
|
2553
|
-
error: 'GitHub repository not configured',
|
|
2554
|
-
log: updated,
|
|
2555
|
-
reason: 'missing_github_repo'
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
const effectiveEnv = `${repoTarget.owner}/${repoTarget.repo}`;
|
|
2560
|
-
const emailHash = email.issueHash || email.hash || this.generateIssueHash(email);
|
|
2561
|
-
const rawHash = email.rawHash || this.generateRawEmailHash(email);
|
|
2562
|
-
const workBranch = this.buildWorkBranch(emailHash);
|
|
2563
|
-
const contextIdentifier = (emailHash || '').slice(0, 12) || 'autofix';
|
|
2564
|
-
const contextPath = `.openai/error-context/${contextIdentifier}.md`;
|
|
2565
|
-
const normalizedBody = this.normalizeEmailText(email.body || '');
|
|
2566
|
-
const contextContent = this.buildContextMarkdown(email, contextIdentifier, normalizedBody);
|
|
2567
|
-
const commentBody = this.buildOpenAIComment(email, contextPath, normalizedBody);
|
|
2568
|
-
|
|
2569
|
-
await this.updateLog(log._id, {
|
|
2570
|
-
status: 'in_progress',
|
|
2571
|
-
last_attempt_at: new Date(),
|
|
2572
|
-
branch_name: workBranch,
|
|
2573
|
-
pr_url: '',
|
|
2574
|
-
last_error: '',
|
|
2575
|
-
openai_environment: effectiveEnv,
|
|
2576
|
-
issue_hash: emailHash,
|
|
2577
|
-
email_hash: rawHash
|
|
2578
|
-
});
|
|
2579
|
-
|
|
2580
|
-
try {
|
|
2581
|
-
const baseBranch = await this.resolveBaseBranch(repoTarget);
|
|
2582
|
-
const baseSha = await this.getBranchSha(repoTarget, baseBranch);
|
|
2583
|
-
await this.ensureBranch(repoTarget, workBranch, baseBranch, baseSha);
|
|
2584
|
-
await this.upsertContextFile(repoTarget, workBranch, contextPath, contextContent);
|
|
2585
|
-
|
|
2586
|
-
let pr = await this.findOpenPullRequest(repoTarget, workBranch, baseBranch);
|
|
2587
|
-
if (!pr) {
|
|
2588
|
-
const title = `AI Autofix: ${(email.subject || 'ResolveIO Error').slice(0, 180)}`;
|
|
2589
|
-
const bodyLines = [
|
|
2590
|
-
'This PR was opened automatically to let AI investigate the reported failure.',
|
|
2591
|
-
'',
|
|
2592
|
-
`Context file: \`${contextPath}\``
|
|
2593
|
-
];
|
|
2594
|
-
pr = await this.createPullRequest(repoTarget, workBranch, baseBranch, title, bodyLines.join('\n'));
|
|
2595
|
-
}
|
|
2596
|
-
|
|
2597
|
-
await this.commentOnPullRequest(repoTarget, pr.number, commentBody);
|
|
2598
|
-
|
|
2599
|
-
const updated = (await this.updateLog(log._id, {
|
|
2600
|
-
status: 'success',
|
|
2601
|
-
branch_name: workBranch,
|
|
2602
|
-
pr_url: pr.html_url,
|
|
2603
|
-
plan_summary: email.subject,
|
|
2604
|
-
plan_body: commentBody,
|
|
2605
|
-
last_result_at: new Date(),
|
|
2606
|
-
id_client: email.id_client,
|
|
2607
|
-
client_name: email.client_name,
|
|
2608
|
-
from_email: email.fromAddress,
|
|
2609
|
-
source_type: email.source_type,
|
|
2610
|
-
classification_reason: email.classification_reason || '',
|
|
2611
|
-
openai_environment: effectiveEnv,
|
|
2612
|
-
issue_hash: emailHash,
|
|
2613
|
-
email_hash: rawHash
|
|
2614
|
-
})) || log;
|
|
2615
|
-
|
|
2616
|
-
return {
|
|
2617
|
-
status: 'success',
|
|
2618
|
-
email,
|
|
2619
|
-
branchName: workBranch,
|
|
2620
|
-
prUrl: pr.html_url,
|
|
2621
|
-
log: updated,
|
|
2622
|
-
summary: email.subject,
|
|
2623
|
-
notes: `Context file: ${contextPath}`
|
|
2624
|
-
};
|
|
2625
|
-
}
|
|
2626
|
-
catch (err) {
|
|
2627
|
-
const message = err?.message || 'GitHub workflow failed.';
|
|
2628
|
-
const updated = (await this.updateLog(log._id, {
|
|
2629
|
-
status: 'failed',
|
|
2630
|
-
last_error: message,
|
|
2631
|
-
last_result_at: new Date(),
|
|
2632
|
-
id_client: email.id_client,
|
|
2633
|
-
client_name: email.client_name,
|
|
2634
|
-
from_email: email.fromAddress,
|
|
2635
|
-
source_type: email.source_type,
|
|
2636
|
-
classification_reason: email.classification_reason || '',
|
|
2637
|
-
openai_environment: effectiveEnv,
|
|
2638
|
-
issue_hash: emailHash,
|
|
2639
|
-
email_hash: rawHash
|
|
2640
|
-
})) || log;
|
|
2641
|
-
|
|
2642
|
-
return {
|
|
2643
|
-
status: 'failed',
|
|
2644
|
-
email,
|
|
2645
|
-
error: message,
|
|
2646
|
-
log: updated
|
|
2647
|
-
};
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
private async notify(result: AutoFixResult): Promise<void> {
|
|
2652
|
-
if (result.status === 'in_progress') {
|
|
2653
|
-
return;
|
|
2654
|
-
}
|
|
2655
|
-
|
|
2656
|
-
if (result.status === 'skipped' && result.reason && !this.shouldNotifySkip(result.reason)) {
|
|
2657
|
-
return;
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
const recipients = this.config.notifyEmails.length
|
|
2661
|
-
? this.config.notifyEmails
|
|
2662
|
-
: (result.status === 'failed' ? [DEFAULT_ERROR_ALERT_EMAIL] : []);
|
|
2663
|
-
if (!recipients.length) {
|
|
2664
|
-
return;
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
let subject = '';
|
|
2668
|
-
let bodyLines: string[] = [];
|
|
2669
|
-
const logId = result.log?._id || '';
|
|
2670
|
-
const summary = result.summary || result.log?.plan_summary || result.email.subject;
|
|
2671
|
-
|
|
2672
|
-
if (result.status === 'success') {
|
|
2673
|
-
subject = `ResolveIO AutoFix ✔ ${summary}`;
|
|
2674
|
-
bodyLines = [
|
|
2675
|
-
'Auto-fix workflow completed successfully.',
|
|
2676
|
-
`Client: ${result.log?.client_name || result.email.client_name || 'Unknown'}`,
|
|
2677
|
-
`Source: ${result.log?.source_type || result.email.source_type || 'app'}`,
|
|
2678
|
-
'',
|
|
2679
|
-
`Branch: ${result.branchName || 'unknown'}`,
|
|
2680
|
-
`PR: ${result.prUrl || 'pending'}`,
|
|
2681
|
-
'',
|
|
2682
|
-
'Summary:',
|
|
2683
|
-
summary || '(none)',
|
|
2684
|
-
''
|
|
2685
|
-
];
|
|
2686
|
-
|
|
2687
|
-
if (result.notes) {
|
|
2688
|
-
bodyLines.push(`Notes: ${result.notes}`, '');
|
|
2689
|
-
}
|
|
2690
|
-
|
|
2691
|
-
if (logId) {
|
|
2692
|
-
bodyLines.push(`Log ID: ${logId}`);
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
else if (result.status === 'failed') {
|
|
2696
|
-
subject = `ResolveIO AutoFix ✖ ${summary}`;
|
|
2697
|
-
bodyLines = [
|
|
2698
|
-
'Auto-fix workflow failed.',
|
|
2699
|
-
`Reason: ${result.error || result.log?.last_error || 'Unknown error'}`,
|
|
2700
|
-
`Client: ${result.log?.client_name || result.email.client_name || 'Unknown'}`,
|
|
2701
|
-
`Source: ${result.log?.source_type || result.email.source_type || 'app'}`,
|
|
2702
|
-
'',
|
|
2703
|
-
'Email Subject:',
|
|
2704
|
-
result.email.subject
|
|
2705
|
-
];
|
|
2706
|
-
|
|
2707
|
-
if (logId) {
|
|
2708
|
-
bodyLines.push('', `Log ID: ${logId}`);
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
else {
|
|
2712
|
-
subject = `ResolveIO AutoFix ⚠ ${result.email.subject}`;
|
|
2713
|
-
const reason = result.reason || 'skipped';
|
|
2714
|
-
|
|
2715
|
-
bodyLines = [
|
|
2716
|
-
`Auto-fix skipped. Reason: ${reason}.`,
|
|
2717
|
-
result.error && result.error !== reason ? `Details: ${result.error}` : '',
|
|
2718
|
-
`Client: ${result.log?.client_name || result.email.client_name || 'Unknown'}`,
|
|
2719
|
-
`Source: ${result.log?.source_type || result.email.source_type || 'app'}`,
|
|
2720
|
-
''
|
|
2721
|
-
];
|
|
2722
|
-
|
|
2723
|
-
if (logId) {
|
|
2724
|
-
bodyLines.push(`Log ID: ${logId}`);
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
|
-
bodyLines.push('Update or remove the log entry to retry processing.');
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
const textBody = bodyLines.filter(line => line !== undefined && line !== null && line !== '').join('\n');
|
|
2731
|
-
|
|
2732
|
-
for (let email of recipients) {
|
|
2733
|
-
await ResolveIOServer.getMainServer().getMethodManager().sendEmail(email, subject, textBody);
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
export function ensureErrorAutoFixManager(serverConfig?: any): ErrorAutoFixManager | null {
|
|
2740
|
-
const existing = ResolveIOServer['AutoFixManager'] as ErrorAutoFixManager | null;
|
|
2741
|
-
if (existing) {
|
|
2742
|
-
return existing;
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
if (!configuredErrorAutoFixDependencies) {
|
|
2746
|
-
return null;
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
const resolvedServerConfig = serverConfig || ResolveIOServer.getServerConfig();
|
|
2750
|
-
if (!resolvedServerConfig) {
|
|
2751
|
-
return null;
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
const manager = new ErrorAutoFixManager(resolvedServerConfig);
|
|
2755
|
-
ResolveIOServer['AutoFixManager'] = manager;
|
|
2756
|
-
return manager;
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
export async function reportDevError(options: DevErrorReportOptions): Promise<void> {
|
|
2760
|
-
const manager = ensureErrorAutoFixManager();
|
|
2761
|
-
if (!manager) {
|
|
2762
|
-
console.warn('AutoFix manager not initialized for dev alert', options?.subject);
|
|
2763
|
-
return;
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
await manager.reportDevError(options);
|
|
2767
|
-
}
|