claude-flow-novice 2.15.3 → 2.15.4
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/.claude/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
- package/.claude/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
- package/.claude/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
- package/.claude/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
- package/.claude/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
- package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
- package/.claude/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
- package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
- package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
- package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
- package/.claude/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
- package/.claude/commands/cfn-loop-cli.md +16 -2
- package/.claude/commands/switch-api.md +31 -10
- package/.claude/hooks/cfn-lint-sql-injection.sh +61 -0
- package/.claude/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
- package/.claude/hooks/cfn-pre-edit-security-warning.sh +40 -0
- package/.claude/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
- package/.claude/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
- package/.claude/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
- package/.claude/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
- package/.claude/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
- package/.claude/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
- package/.claude/skills/cfn-loop-orchestration/security_utils.sh +24 -0
- package/.claude/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
- package/.claude/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
- package/.claude/skills/cfn-redis-coordination/agent-log.sh +4 -0
- package/.claude/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
- package/.claude/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
- package/.claude/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
- package/.claude/skills/cfn-redis-coordination/get-context.sh +33 -0
- package/.claude/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
- package/.claude/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
- package/.claude/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/.claude/skills/cfn-redis-coordination/redis-functions.sh +33 -0
- package/.claude/skills/cfn-redis-coordination/report-completion.sh +24 -31
- package/.claude/skills/cfn-redis-coordination/store-context.sh +4 -0
- package/.claude/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
- package/.claude/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
- package/.claude/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
- package/.claude/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
- package/.claude/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
- package/README.md +116 -475
- package/claude-assets/agents/cfn-dev-team/README.md +103 -0
- package/claude-assets/agents/cfn-dev-team/architecture/goal-planner.md +1 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +77 -15
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +355 -6
- package/claude-assets/agents/cfn-dev-team/coordinators/consensus-builder.md +82 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +82 -1
- package/claude-assets/agents/cfn-dev-team/coordinators/multi-sprint-coordinator.md +77 -15
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +99 -12
- package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +1 -1
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +97 -0
- package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +20 -1
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +97 -0
- package/claude-assets/agents/cfn-dev-team/developers/backend-developer.md +110 -13
- package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +106 -15
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +115 -11
- package/claude-assets/agents/cfn-dev-team/developers/frontend/mobile-dev.md +94 -7
- package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +87 -9
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +85 -7
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +160 -28
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +101 -19
- package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +108 -14
- package/claude-assets/agents/cfn-dev-team/reviewers/{reviewer.md → code-reviewer.md} +95 -8
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/code-quality-validator.md +107 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/perf-analyzer.md +98 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/performance-benchmarker.md +95 -7
- package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +136 -9
- package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +108 -1
- package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +107 -13
- package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +737 -0
- package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
- package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +828 -0
- package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +106 -7
- package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +77 -0
- package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +684 -0
- package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +110 -1
- package/claude-assets/agents/cfn-dev-team/testers/tester.md +94 -7
- package/claude-assets/agents/cfn-dev-team/utility/code-booster.md +1 -3
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +87 -13
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +103 -7
- package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -3
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +94 -7
- package/claude-assets/agents/docker-coordinators/cfn-docker-v3-coordinator.md +46 -0
- package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
- package/claude-assets/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
- package/claude-assets/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
- package/claude-assets/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
- package/claude-assets/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
- package/claude-assets/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
- package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
- package/claude-assets/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
- package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
- package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
- package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
- package/claude-assets/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
- package/claude-assets/commands/cfn-loop-cli.md +16 -2
- package/claude-assets/commands/switch-api.md +31 -10
- package/claude-assets/hooks/cfn-lint-sql-injection.sh +61 -0
- package/claude-assets/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
- package/claude-assets/hooks/cfn-pre-edit-security-warning.sh +40 -0
- package/claude-assets/hooks/detect-hardcoded-credentials.sh +212 -0
- package/claude-assets/skills/SKILL_TEMPLATE.md +774 -0
- package/claude-assets/skills/agent-lifecycle/execute-lifecycle-hook.sh +84 -113
- package/claude-assets/skills/agent-lifecycle/simple-audit.sh +33 -6
- package/claude-assets/skills/agent-template-generator/SKILL.md +440 -0
- package/claude-assets/skills/agent-template-generator/generate-agent.sh +405 -0
- package/claude-assets/skills/agent-validation-linter/SKILL.md +589 -0
- package/claude-assets/skills/agent-validation-linter/lint-agents.sh +271 -0
- package/claude-assets/skills/bootstrap/bash-fundamentals.md +786 -0
- package/claude-assets/skills/bootstrap/database-connection.md +464 -0
- package/claude-assets/skills/bootstrap/error-handling.md +580 -0
- package/claude-assets/skills/bootstrap/file-operations.md +699 -0
- package/claude-assets/skills/bootstrap/skill-loader.md +616 -0
- package/claude-assets/skills/bootstrap/sqlite-params.sh +287 -0
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
- package/claude-assets/skills/cfn-automatic-memory-persistence/test-memory-persistence.sh +17 -16
- package/claude-assets/skills/cfn-deployment/SKILL.md +293 -0
- package/claude-assets/skills/cfn-deployment/execute.sh +21 -0
- package/claude-assets/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
- package/claude-assets/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
- package/claude-assets/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
- package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +38 -0
- package/claude-assets/skills/cfn-error-batching-strategy/lib/core-functions.sh +47 -47
- package/claude-assets/skills/cfn-file-operations/SKILL.md +290 -0
- package/claude-assets/skills/cfn-file-operations/execute.sh +129 -0
- package/claude-assets/skills/cfn-file-operations/lib/atomic-write.sh +294 -0
- package/claude-assets/skills/cfn-file-operations/lib/lock.sh +361 -0
- package/claude-assets/skills/cfn-file-operations/test.sh +369 -0
- package/claude-assets/skills/cfn-log-operations/SKILL.md +308 -0
- package/claude-assets/skills/cfn-log-operations/execute.sh +420 -0
- package/claude-assets/skills/cfn-log-operations/lib/rotate.sh +406 -0
- package/claude-assets/skills/cfn-log-operations/lib/search.sh +448 -0
- package/claude-assets/skills/cfn-log-operations/test.sh +394 -0
- package/claude-assets/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
- package/claude-assets/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
- package/claude-assets/skills/cfn-loop-orchestration/security_utils.sh +24 -0
- package/claude-assets/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
- package/claude-assets/skills/cfn-parameterized-queries/SKILL.md +339 -0
- package/claude-assets/skills/cfn-playbook/query-playbook.sh +19 -15
- package/claude-assets/skills/cfn-playbook/update-playbook.sh +25 -14
- package/claude-assets/skills/cfn-process-instrumentation/instrument-process.sh +44 -0
- package/claude-assets/skills/cfn-promotion/SKILL.md +305 -0
- package/claude-assets/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-log.sh +4 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
- package/claude-assets/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
- package/claude-assets/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
- package/claude-assets/skills/cfn-redis-coordination/get-context.sh +33 -0
- package/claude-assets/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
- package/claude-assets/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
- package/claude-assets/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
- package/claude-assets/skills/cfn-redis-coordination/redis-functions.sh +33 -0
- package/claude-assets/skills/cfn-redis-coordination/report-completion.sh +24 -31
- package/claude-assets/skills/cfn-redis-coordination/store-context.sh +4 -0
- package/claude-assets/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
- package/claude-assets/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
- package/claude-assets/skills/cfn-skill-loader/SKILL.md +466 -0
- package/claude-assets/skills/cfn-skill-loader/execute.sh +344 -0
- package/claude-assets/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
- package/claude-assets/skills/cfn-task-audit/get-audit-data.sh +42 -21
- package/claude-assets/skills/cfn-task-audit/store-task-audit.sh +17 -10
- package/claude-assets/skills/cfn-test-runner/detect-regressions.sh +17 -14
- package/claude-assets/skills/cfn-test-runner/detect-regressions.sh.backup-1763392821 +55 -0
- package/claude-assets/skills/cfn-test-runner/store-benchmarks.sh +17 -19
- package/claude-assets/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
- package/claude-assets/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
- package/claude-assets/skills/cfn-utilities/SKILL.md +237 -0
- package/claude-assets/skills/cfn-utilities/execute.sh +32 -0
- package/claude-assets/skills/cfn-utilities/lib/errors.sh +56 -0
- package/claude-assets/skills/cfn-utilities/lib/file-ops.sh +164 -0
- package/claude-assets/skills/cfn-utilities/lib/logging.sh +77 -0
- package/claude-assets/skills/cfn-utilities/lib/retry.sh +127 -0
- package/claude-assets/skills/cfn-utilities/test.sh +317 -0
- package/claude-assets/skills/integration/agent-handoff.sh +62 -64
- package/claude-assets/skills/json-validation/SKILL.md +431 -0
- package/claude-assets/skills/json-validation/test-validate-success-criteria.sh +421 -0
- package/claude-assets/skills/json-validation/validate-success-criteria.sh +197 -0
- package/claude-assets/skills/redis-coordination/validate-parameters.sh +34 -0
- package/claude-assets/skills/workflow-codification/DEPLOY_QUICK_REFERENCE.md +106 -0
- package/claude-assets/skills/workflow-codification/PROPAGATE_UPDATE_QUICK_REFERENCE.md +366 -0
- package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh +481 -0
- package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh.backup-1763392820 +512 -0
- package/claude-assets/skills/workflow-codification/lib/security-utils.sh +204 -0
- package/claude-assets/skills/workflow-codification/propagate-skill-update.sh +648 -0
- package/claude-assets/skills/workflow-codification/propagate-skill-update.sh.backup-1763392820 +664 -0
- package/claude-assets/skills/workflow-codification/test-integration.sh +15 -0
- package/claude-assets/skills/workflow-codification/test-metadata-update.sh +350 -0
- package/claude-assets/skills/workflow-codification/track-cost-savings.sh +55 -14
- package/claude-assets/skills/workflow-codification/track-cost-savings.sh.backup-1763392821 +445 -0
- package/claude-assets/skills/workflow-codification/track-edge-case.sh +27 -60
- package/claude-assets/skills/workflow-codification/workflow-codification.db +0 -0
- package/dist/ace/ace-curator.js +10 -2
- package/dist/ace/ace-curator.js.map +1 -1
- package/dist/ace/ace-generator.js +4 -0
- package/dist/ace/ace-generator.js.map +1 -1
- package/dist/ace/ace-reflector.js +1 -1
- package/dist/ace/ace-reflector.js.map +1 -1
- package/dist/ace/context-injection.js +24 -2
- package/dist/ace/context-injection.js.map +1 -1
- package/dist/agents/agent-loader.js +146 -165
- package/dist/agents/agent-loader.js.map +1 -1
- package/dist/agents/task-agent-integration.js +1 -1
- package/dist/agents/task-agent-integration.js.map +1 -1
- package/dist/api/health-endpoints.js +390 -0
- package/dist/api/health-endpoints.js.map +1 -0
- package/dist/cli/agent-executor.js +4 -1
- package/dist/cli/agent-executor.js.map +1 -1
- package/dist/cli/agent-prompt-builder.js +89 -1
- package/dist/cli/agent-prompt-builder.js.map +1 -1
- package/dist/cli/agent-spawn.js +130 -37
- package/dist/cli/agent-spawn.js.map +1 -1
- package/dist/cli/skill-cache-validator.js +412 -0
- package/dist/cli/skill-cache-validator.js.map +1 -0
- package/dist/cli/skill-cli.js +991 -0
- package/dist/cli/skill-cli.js.map +1 -0
- package/dist/cli/skill-execution-logger.js +284 -0
- package/dist/cli/skill-execution-logger.js.map +1 -0
- package/dist/cli/skill-loader.js +457 -0
- package/dist/cli/skill-loader.js.map +1 -0
- package/dist/coordination/event-bus.js +2 -2
- package/dist/coordination/event-bus.js.map +1 -1
- package/dist/coordination/fleet-manager.js +1 -1
- package/dist/coordination/fleet-manager.js.map +1 -1
- package/dist/coordination/index.js +23 -9
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/types/fleet-manager.types.js.map +1 -1
- package/dist/db/migration-manager.js +483 -0
- package/dist/db/migration-manager.js.map +1 -0
- package/dist/db/skills-query.js +535 -0
- package/dist/db/skills-query.js.map +1 -0
- package/dist/integration/DatabaseHandoff.js +1 -1
- package/dist/integration/DatabaseHandoff.js.map +1 -1
- package/dist/jobs/edge-case-analyzer.js +367 -0
- package/dist/jobs/edge-case-analyzer.js.map +1 -0
- package/dist/jobs/promotion-sla-enforcer.js +288 -0
- package/dist/jobs/promotion-sla-enforcer.js.map +1 -0
- package/dist/lib/agent-output-parser.js.map +1 -1
- package/dist/lib/agent-output-validator.js.map +1 -1
- package/dist/lib/agent-workspace.js +281 -0
- package/dist/lib/agent-workspace.js.map +1 -0
- package/dist/lib/atomic-file-writer.js +377 -0
- package/dist/lib/atomic-file-writer.js.map +1 -0
- package/dist/lib/backup-manager.js +779 -0
- package/dist/lib/backup-manager.js.map +1 -0
- package/dist/lib/checkpoint-manager.js +837 -0
- package/dist/lib/checkpoint-manager.js.map +1 -0
- package/dist/lib/circuit-breaker.js +340 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/completion-signal-handler.js +243 -0
- package/dist/lib/completion-signal-handler.js.map +1 -0
- package/dist/lib/config-manager.js +312 -0
- package/dist/lib/config-manager.js.map +1 -0
- package/dist/lib/config-migrator.js +386 -0
- package/dist/lib/config-migrator.js.map +1 -0
- package/dist/lib/config-validator.js.map +1 -1
- package/dist/lib/correlation-cache.js +311 -0
- package/dist/lib/correlation-cache.js.map +1 -0
- package/dist/lib/correlation.js +263 -0
- package/dist/lib/correlation.js.map +1 -0
- package/dist/lib/database-service/connection-pool-manager.js +520 -0
- package/dist/lib/database-service/connection-pool-manager.js.map +1 -0
- package/dist/lib/database-service/correlation.js +329 -0
- package/dist/lib/database-service/correlation.js.map +1 -0
- package/dist/lib/database-service/errors.js +120 -0
- package/dist/lib/database-service/errors.js.map +1 -0
- package/dist/lib/database-service/index.js +168 -0
- package/dist/lib/database-service/index.js.map +1 -0
- package/dist/lib/database-service/postgres-adapter.js +526 -0
- package/dist/lib/database-service/postgres-adapter.js.map +1 -0
- package/dist/lib/database-service/redis-adapter.js +360 -0
- package/dist/lib/database-service/redis-adapter.js.map +1 -0
- package/dist/lib/database-service/sqlite-adapter.js +544 -0
- package/dist/lib/database-service/sqlite-adapter.js.map +1 -0
- package/dist/lib/database-service/transaction-manager.js +773 -0
- package/dist/lib/database-service/transaction-manager.js.map +1 -0
- package/dist/lib/database-service/types.js +23 -0
- package/dist/lib/database-service/types.js.map +1 -0
- package/dist/lib/deadlock-resolver.js +292 -0
- package/dist/lib/deadlock-resolver.js.map +1 -0
- package/dist/lib/distributed-lock.js +451 -0
- package/dist/lib/distributed-lock.js.map +1 -0
- package/dist/lib/edge-case-deduplicator.js +227 -0
- package/dist/lib/edge-case-deduplicator.js.map +1 -0
- package/dist/lib/encryption-manager.js +322 -0
- package/dist/lib/encryption-manager.js.map +1 -0
- package/dist/lib/error-aggregator.js +234 -0
- package/dist/lib/error-aggregator.js.map +1 -0
- package/dist/lib/errors.js +287 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/file-lock-manager.js +578 -0
- package/dist/lib/file-lock-manager.js.map +1 -0
- package/dist/lib/file-operations.js +367 -0
- package/dist/lib/file-operations.js.map +1 -0
- package/dist/lib/idempotent-write.js +237 -0
- package/dist/lib/idempotent-write.js.map +1 -0
- package/dist/lib/integration-schema-validator.js +522 -0
- package/dist/lib/integration-schema-validator.js.map +1 -0
- package/dist/lib/lock-health-monitor.js +298 -0
- package/dist/lib/lock-health-monitor.js.map +1 -0
- package/dist/lib/log-shipper.js +422 -0
- package/dist/lib/log-shipper.js.map +1 -0
- package/dist/lib/logging.js +146 -0
- package/dist/lib/logging.js.map +1 -0
- package/dist/lib/message-deduplicator.js +439 -0
- package/dist/lib/message-deduplicator.js.map +1 -0
- package/dist/lib/multi-system-query.js +604 -0
- package/dist/lib/multi-system-query.js.map +1 -0
- package/dist/lib/orphan-detector.js +332 -0
- package/dist/lib/orphan-detector.js.map +1 -0
- package/dist/lib/password-generator.js +166 -0
- package/dist/lib/password-generator.js.map +1 -0
- package/dist/lib/path-validator.js +429 -0
- package/dist/lib/path-validator.js.map +1 -0
- package/dist/lib/query-translator.js +905 -0
- package/dist/lib/query-translator.js.map +1 -0
- package/dist/lib/queue-recovery.js +469 -0
- package/dist/lib/queue-recovery.js.map +1 -0
- package/dist/lib/redis-queue-manager.js +512 -0
- package/dist/lib/redis-queue-manager.js.map +1 -0
- package/dist/lib/reflection-archiver.js +272 -0
- package/dist/lib/reflection-archiver.js.map +1 -0
- package/dist/lib/retry-manager.js +453 -0
- package/dist/lib/retry-manager.js.map +1 -0
- package/dist/lib/retry.js +262 -0
- package/dist/lib/retry.js.map +1 -0
- package/dist/lib/schema-transform.js +695 -0
- package/dist/lib/schema-transform.js.map +1 -0
- package/dist/lib/schema-validator.js +491 -0
- package/dist/lib/schema-validator.js.map +1 -0
- package/dist/lib/skill-cache.js +297 -0
- package/dist/lib/skill-cache.js.map +1 -0
- package/dist/lib/skill-content-manager.js +337 -0
- package/dist/lib/skill-content-manager.js.map +1 -0
- package/dist/lib/skill-frontmatter-parser.js +237 -0
- package/dist/lib/skill-frontmatter-parser.js.map +1 -0
- package/dist/lib/skill-git-integration.js +275 -0
- package/dist/lib/skill-git-integration.js.map +1 -0
- package/dist/lib/skill-markdown-validator.js +396 -0
- package/dist/lib/skill-markdown-validator.js.map +1 -0
- package/dist/lib/skill-output-parser.js +312 -0
- package/dist/lib/skill-output-parser.js.map +1 -0
- package/dist/lib/unified-query-api.js +467 -0
- package/dist/lib/unified-query-api.js.map +1 -0
- package/dist/middleware/auth-middleware.js +350 -0
- package/dist/middleware/auth-middleware.js.map +1 -0
- package/dist/middleware/schema-validation.js +347 -0
- package/dist/middleware/schema-validation.js.map +1 -0
- package/dist/providers/anthropic-provider.js +1 -1
- package/dist/providers/anthropic-provider.js.map +1 -1
- package/dist/providers/provider-factory.js +2 -2
- package/dist/providers/provider-factory.js.map +1 -1
- package/dist/services/edge-case-analyzer.js +321 -0
- package/dist/services/edge-case-analyzer.js.map +1 -0
- package/dist/services/edge-case-deduplicator.js +266 -0
- package/dist/services/edge-case-deduplicator.js.map +1 -0
- package/dist/services/edge-case-detector.js +337 -0
- package/dist/services/edge-case-detector.js.map +1 -0
- package/dist/services/edge-case-tracker.js +547 -0
- package/dist/services/edge-case-tracker.js.map +1 -0
- package/dist/services/health-check-system.js +586 -0
- package/dist/services/health-check-system.js.map +1 -0
- package/dist/services/metrics-logger.js +412 -0
- package/dist/services/metrics-logger.js.map +1 -0
- package/dist/services/patch-generator.js +378 -0
- package/dist/services/patch-generator.js.map +1 -0
- package/dist/services/patch-validator.js +337 -0
- package/dist/services/patch-validator.js.map +1 -0
- package/dist/services/performance-monitor.js +811 -0
- package/dist/services/performance-monitor.js.map +1 -0
- package/dist/services/promotion-pipeline.js +918 -0
- package/dist/services/promotion-pipeline.js.map +1 -0
- package/dist/services/promotion-validator.js +394 -0
- package/dist/services/promotion-validator.js.map +1 -0
- package/dist/services/reflection-logger.js +388 -0
- package/dist/services/reflection-logger.js.map +1 -0
- package/dist/services/skill-deployment.js +472 -0
- package/dist/services/skill-deployment.js.map +1 -0
- package/dist/services/skill-loader.js +427 -0
- package/dist/services/skill-loader.js.map +1 -0
- package/dist/services/skill-promotion.js +372 -0
- package/dist/services/skill-promotion.js.map +1 -0
- package/dist/services/skill-validator.js +454 -0
- package/dist/services/skill-validator.js.map +1 -0
- package/dist/services/skill-versioning.js +244 -0
- package/dist/services/skill-versioning.js.map +1 -0
- package/dist/services/workspace-supervisor.js +597 -0
- package/dist/services/workspace-supervisor.js.map +1 -0
- package/dist/types/edge-case.js +45 -0
- package/dist/types/edge-case.js.map +1 -0
- package/package.json +201 -177
- package/readme/README.md +19 -4
- package/scripts/backup-cleanup.sh +627 -0
- package/scripts/cleanup-workspaces.sh +412 -0
- package/scripts/cleanup-yaml-configs.sh +141 -0
- package/scripts/deploy-approved-skills.sh +263 -0
- package/scripts/health-check.sh +447 -0
- package/scripts/log-aggregator.sh +554 -0
- package/scripts/log-monitor.sh +629 -0
- package/scripts/manage-agent-workspaces.sh +434 -0
- package/scripts/migrate-schema.sh +533 -0
- package/scripts/promote-staged-skills.sh +423 -0
- package/scripts/verify-no-secrets.sh +88 -35
- package/.claude/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
- package/.claude/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
- package/.claude/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
- package/.claude/skills/agent-lifecycle/SKILL.md +0 -60
- package/.claude/skills/agent-lifecycle/execute-lifecycle-hook.sh +0 -573
- package/.claude/skills/agent-lifecycle/simple-audit.sh +0 -31
- package/.claude/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
- package/README.md.backup_before_replace +0 -781
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
- package/claude-assets/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
- package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/queue-recovery.ts"],"sourcesContent":["/**\r\n * Queue Recovery System\r\n *\r\n * Provides failure detection, dead letter queue management, and automatic recovery\r\n * for Redis queue operations. Handles coordinator crashes and message reprocessing.\r\n * Part of Task 3.4: Redis Queue Consistency & Recovery (Integration Standardization Sprint 3)\r\n *\r\n * Features:\r\n * - Dead letter queue for failed messages\r\n * - Automatic retry with exponential backoff\r\n * - Failure detection (tasks not processed within timeout)\r\n * - Coordinator crash recovery procedure\r\n * - Message reprocessing safeguards\r\n *\r\n * Usage:\r\n * const recovery = new QueueRecovery(queueManager);\r\n *\r\n * // Start monitoring for stuck messages\r\n * recovery.startMonitoring();\r\n *\r\n * // Recover from coordinator crash\r\n * await recovery.recoverFromCrash();\r\n *\r\n * // Process dead letter queue\r\n * const reprocessed = await recovery.reprocessDeadLetters();\r\n */\r\n\r\nimport { RedisClientType } from 'redis';\r\nimport { createLogger } from './logging.js';\r\nimport { createError, ErrorCode, isRetryableError } from './errors.js';\r\nimport { withRetry, sleep } from './retry.js';\r\nimport { RedisQueueManager, QueueMessage } from './redis-queue-manager.js';\r\n\r\nconst logger = createLogger('queue-recovery');\r\n\r\n/**\r\n * Recovery options\r\n */\r\nexport interface RecoveryOptions {\r\n /** Maximum number of retry attempts (default: 3) */\r\n maxRetries?: number;\r\n /** Base retry delay in milliseconds (default: 1000) */\r\n baseRetryDelayMs?: number;\r\n /** Maximum retry delay in milliseconds (default: 60000) */\r\n maxRetryDelayMs?: number;\r\n /** Message processing timeout in milliseconds (default: 300000 - 5 minutes) */\r\n processingTimeoutMs?: number;\r\n /** Monitoring interval in milliseconds (default: 60000 - 1 minute) */\r\n monitoringIntervalMs?: number;\r\n /** Dead letter queue name (default: 'dlq') */\r\n deadLetterQueue?: string;\r\n /** Enable automatic reprocessing of dead letters (default: false) */\r\n autoReprocess?: boolean;\r\n}\r\n\r\n/**\r\n * Recovery statistics\r\n */\r\nexport interface RecoveryStats {\r\n /** Total messages recovered */\r\n totalRecovered: number;\r\n /** Total messages sent to DLQ */\r\n totalDeadLettered: number;\r\n /** Total messages reprocessed from DLQ */\r\n totalReprocessed: number;\r\n /** Total stuck messages detected */\r\n totalStuckDetected: number;\r\n /** Last recovery timestamp */\r\n lastRecoveryAt?: Date;\r\n}\r\n\r\n/**\r\n * Dead letter metadata\r\n */\r\nexport interface DeadLetterMetadata {\r\n /** Original queue */\r\n originalQueue: string;\r\n /** Failure reason */\r\n failureReason: string;\r\n /** Number of retry attempts made */\r\n retryAttempts: number;\r\n /** Dead lettered timestamp */\r\n deadLetteredAt: Date;\r\n /** Original message metadata */\r\n originalMetadata?: Record<string, any>;\r\n}\r\n\r\n/**\r\n * Default recovery options\r\n */\r\nconst DEFAULT_RECOVERY_OPTIONS: Required<RecoveryOptions> = {\r\n maxRetries: 3,\r\n baseRetryDelayMs: 1000,\r\n maxRetryDelayMs: 60000,\r\n processingTimeoutMs: 300000, // 5 minutes\r\n monitoringIntervalMs: 60000, // 1 minute\r\n deadLetterQueue: 'dlq',\r\n autoReprocess: false,\r\n};\r\n\r\n/**\r\n * Queue Recovery System\r\n *\r\n * Handles failure detection, retry, and recovery for queue operations.\r\n */\r\nexport class QueueRecovery {\r\n private queueManager: RedisQueueManager;\r\n private redis: RedisClientType;\r\n private options: Required<RecoveryOptions>;\r\n private monitoringTimer: NodeJS.Timeout | null = null;\r\n private stats: RecoveryStats = {\r\n totalRecovered: 0,\r\n totalDeadLettered: 0,\r\n totalReprocessed: 0,\r\n totalStuckDetected: 0,\r\n };\r\n\r\n /**\r\n * Create a new QueueRecovery instance\r\n *\r\n * @param queueManager - Queue manager instance\r\n * @param redis - Redis client instance\r\n * @param options - Recovery options\r\n */\r\n constructor(\r\n queueManager: RedisQueueManager,\r\n redis: RedisClientType,\r\n options: RecoveryOptions = {}\r\n ) {\r\n this.queueManager = queueManager;\r\n this.redis = redis;\r\n this.options = { ...DEFAULT_RECOVERY_OPTIONS, ...options };\r\n\r\n logger.info('QueueRecovery initialized', {\r\n maxRetries: this.options.maxRetries,\r\n processingTimeoutMs: this.options.processingTimeoutMs,\r\n deadLetterQueue: this.options.deadLetterQueue,\r\n });\r\n }\r\n\r\n /**\r\n * Retry message processing with exponential backoff\r\n *\r\n * @param message - Message to retry\r\n * @param processFn - Function to process message\r\n * @returns Processing result\r\n */\r\n public async retryWithBackoff<T = any, R = any>(\r\n message: QueueMessage<T>,\r\n processFn: (payload: T) => Promise<R>\r\n ): Promise<R> {\r\n const maxAttempts = this.options.maxRetries;\r\n let attempt = message.deliveryAttempts;\r\n\r\n while (attempt <= maxAttempts) {\r\n try {\r\n logger.debug('Processing message with retry', {\r\n messageId: message.id,\r\n attempt,\r\n maxAttempts,\r\n });\r\n\r\n const result = await processFn(message.payload);\r\n\r\n if (attempt > 1) {\r\n this.stats.totalRecovered++;\r\n logger.info('Message processed successfully after retry', {\r\n messageId: message.id,\r\n attempt,\r\n });\r\n }\r\n\r\n return result;\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n\r\n logger.warn('Message processing failed', {\r\n messageId: message.id,\r\n attempt,\r\n error: err.message,\r\n });\r\n\r\n // Check if we should retry\r\n if (!isRetryableError(err) || attempt >= maxAttempts) {\r\n logger.error('Message processing failed permanently', err, {\r\n messageId: message.id,\r\n attempt,\r\n retryable: isRetryableError(err),\r\n });\r\n\r\n // Send to dead letter queue\r\n await this.sendToDeadLetter(message, err.message);\r\n\r\n throw err;\r\n }\r\n\r\n // Calculate backoff delay\r\n const delay = this.calculateBackoffDelay(attempt);\r\n\r\n logger.debug('Retrying message after delay', {\r\n messageId: message.id,\r\n attempt,\r\n delayMs: delay,\r\n });\r\n\r\n // Wait before retry\r\n await sleep(delay);\r\n\r\n attempt++;\r\n }\r\n }\r\n\r\n // Should never reach here, but TypeScript needs it\r\n throw createError(\r\n ErrorCode.RETRY_EXHAUSTED,\r\n 'Message processing retry exhausted',\r\n { messageId: message.id, attempts: attempt }\r\n );\r\n }\r\n\r\n /**\r\n * Send message to dead letter queue\r\n *\r\n * @param message - Message to dead letter\r\n * @param reason - Failure reason\r\n */\r\n public async sendToDeadLetter<T = any>(\r\n message: QueueMessage<T>,\r\n reason: string\r\n ): Promise<void> {\r\n try {\r\n const metadata: DeadLetterMetadata = {\r\n originalQueue: message.queue,\r\n failureReason: reason,\r\n retryAttempts: message.deliveryAttempts,\r\n deadLetteredAt: new Date(),\r\n originalMetadata: message.metadata,\r\n };\r\n\r\n // Enqueue to dead letter queue\r\n await this.queueManager.enqueue(\r\n this.options.deadLetterQueue,\r\n message.payload,\r\n {\r\n deduplicate: false, // Allow duplicate dead letters\r\n metadata,\r\n }\r\n );\r\n\r\n this.stats.totalDeadLettered++;\r\n\r\n logger.info('Message sent to dead letter queue', {\r\n messageId: message.id,\r\n originalQueue: message.queue,\r\n reason,\r\n retryAttempts: message.deliveryAttempts,\r\n });\r\n } catch (error) {\r\n logger.error('Failed to send message to DLQ', error instanceof Error ? error : new Error(String(error)), {\r\n messageId: message.id,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to send message to dead letter queue',\r\n { messageId: message.id },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Reprocess messages from dead letter queue\r\n *\r\n * @param processFn - Function to process dead letter messages\r\n * @param maxMessages - Maximum number of messages to reprocess (default: 100)\r\n * @returns Number of messages reprocessed\r\n */\r\n public async reprocessDeadLetters<T = any>(\r\n processFn: (payload: T, metadata: DeadLetterMetadata) => Promise<void>,\r\n maxMessages: number = 100\r\n ): Promise<number> {\r\n let reprocessedCount = 0;\r\n\r\n try {\r\n logger.info('Starting dead letter reprocessing', {\r\n maxMessages,\r\n });\r\n\r\n for (let i = 0; i < maxMessages; i++) {\r\n // Dequeue from dead letter queue\r\n const message = await this.queueManager.dequeue<T>(\r\n this.options.deadLetterQueue,\r\n { timeout: 0 }\r\n );\r\n\r\n if (!message) {\r\n // No more messages\r\n break;\r\n }\r\n\r\n try {\r\n const metadata = message.metadata as DeadLetterMetadata;\r\n\r\n // Process message\r\n await processFn(message.payload, metadata);\r\n\r\n // Acknowledge successful processing\r\n await this.queueManager.acknowledge(message.id);\r\n\r\n reprocessedCount++;\r\n this.stats.totalReprocessed++;\r\n\r\n logger.debug('Dead letter message reprocessed', {\r\n messageId: message.id,\r\n originalQueue: metadata.originalQueue,\r\n });\r\n } catch (error) {\r\n // Reject message (will stay in DLQ or be retried based on options)\r\n await this.queueManager.reject(message.id, {\r\n retry: false,\r\n error: error instanceof Error ? error.message : String(error),\r\n });\r\n\r\n logger.error('Failed to reprocess dead letter', error instanceof Error ? error : new Error(String(error)), {\r\n messageId: message.id,\r\n });\r\n }\r\n }\r\n\r\n logger.info('Dead letter reprocessing complete', {\r\n reprocessedCount,\r\n });\r\n\r\n return reprocessedCount;\r\n } catch (error) {\r\n logger.error('Dead letter reprocessing failed', error instanceof Error ? error : new Error(String(error)));\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to reprocess dead letters',\r\n { reprocessedCount },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Detect and recover stuck messages (messages in processing longer than timeout)\r\n *\r\n * @param queue - Queue name to check\r\n * @returns Number of stuck messages recovered\r\n */\r\n public async recoverStuckMessages(queue: string): Promise<number> {\r\n try {\r\n const processingKey = `queue:${queue}:processing`;\r\n const queueKey = `queue:${queue}`;\r\n\r\n // Get all messages in processing\r\n const processingMessages = await this.redis.lRange(processingKey, 0, -1);\r\n\r\n let recoveredCount = 0;\r\n const now = Date.now();\r\n\r\n for (const messageData of processingMessages) {\r\n const message = JSON.parse(messageData) as QueueMessage;\r\n\r\n // Check if message is stuck (processing longer than timeout)\r\n if (message.dequeuedAt) {\r\n const processingTime = now - new Date(message.dequeuedAt).getTime();\r\n\r\n if (processingTime > this.options.processingTimeoutMs) {\r\n logger.warn('Stuck message detected', {\r\n messageId: message.id,\r\n queue,\r\n processingTimeMs: processingTime,\r\n timeoutMs: this.options.processingTimeoutMs,\r\n });\r\n\r\n this.stats.totalStuckDetected++;\r\n\r\n // Check if message has exceeded max retries\r\n if (message.deliveryAttempts >= this.options.maxRetries) {\r\n // Send to dead letter queue\r\n await this.sendToDeadLetter(\r\n message,\r\n `Message stuck in processing for ${processingTime}ms (exceeded max retries)`\r\n );\r\n\r\n // Remove from processing\r\n await this.redis.lRem(processingKey, 1, messageData);\r\n\r\n logger.info('Stuck message sent to DLQ', {\r\n messageId: message.id,\r\n deliveryAttempts: message.deliveryAttempts,\r\n });\r\n } else {\r\n // Re-enqueue for retry\r\n message.deliveryAttempts++;\r\n message.metadata = {\r\n ...message.metadata,\r\n recoveredAt: new Date().toISOString(),\r\n recoveryReason: 'Stuck in processing',\r\n };\r\n\r\n // Remove from processing and add back to queue\r\n await this.redis.lRem(processingKey, 1, messageData);\r\n await this.redis.rPush(queueKey, JSON.stringify(message));\r\n\r\n recoveredCount++;\r\n this.stats.totalRecovered++;\r\n\r\n logger.info('Stuck message re-enqueued', {\r\n messageId: message.id,\r\n deliveryAttempts: message.deliveryAttempts,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (recoveredCount > 0) {\r\n this.stats.lastRecoveryAt = new Date();\r\n }\r\n\r\n logger.debug('Stuck message recovery complete', {\r\n queue,\r\n recoveredCount,\r\n totalStuckDetected: this.stats.totalStuckDetected,\r\n });\r\n\r\n return recoveredCount;\r\n } catch (error) {\r\n logger.error('Failed to recover stuck messages', error instanceof Error ? error : new Error(String(error)), {\r\n queue,\r\n });\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to recover stuck messages',\r\n { queue },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Recover from coordinator crash\r\n *\r\n * Scans all queues for stuck messages and recovers them.\r\n *\r\n * @returns Object with recovery results per queue\r\n */\r\n public async recoverFromCrash(): Promise<Record<string, number>> {\r\n try {\r\n logger.info('Starting coordinator crash recovery');\r\n\r\n // Get all queues\r\n const queues = await this.queueManager.getQueues();\r\n\r\n const results: Record<string, number> = {};\r\n\r\n // Recover stuck messages from each queue\r\n for (const queue of queues) {\r\n // Skip dead letter queue\r\n if (queue === this.options.deadLetterQueue) {\r\n continue;\r\n }\r\n\r\n const recovered = await this.recoverStuckMessages(queue);\r\n results[queue] = recovered;\r\n }\r\n\r\n const totalRecovered = Object.values(results).reduce((sum, count) => sum + count, 0);\r\n\r\n logger.info('Coordinator crash recovery complete', {\r\n queuesProcessed: queues.length,\r\n totalRecovered,\r\n results,\r\n });\r\n\r\n return results;\r\n } catch (error) {\r\n logger.error('Coordinator crash recovery failed', error instanceof Error ? error : new Error(String(error)));\r\n\r\n throw createError(\r\n ErrorCode.DB_QUERY_FAILED,\r\n 'Failed to recover from coordinator crash',\r\n {},\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Start automatic monitoring for stuck messages\r\n */\r\n public startMonitoring(): void {\r\n if (this.monitoringTimer) {\r\n logger.warn('Monitoring already started');\r\n return;\r\n }\r\n\r\n this.monitoringTimer = setInterval(\r\n async () => {\r\n try {\r\n await this.recoverFromCrash();\r\n\r\n // Auto-reprocess dead letters if enabled\r\n if (this.options.autoReprocess) {\r\n await this.reprocessDeadLetters(async (payload, metadata) => {\r\n logger.debug('Auto-reprocessing dead letter', {\r\n originalQueue: metadata.originalQueue,\r\n });\r\n\r\n // Re-enqueue to original queue\r\n await this.queueManager.enqueue(metadata.originalQueue, payload, {\r\n deduplicate: false,\r\n metadata: {\r\n ...metadata,\r\n reprocessedAt: new Date().toISOString(),\r\n },\r\n });\r\n });\r\n }\r\n } catch (error) {\r\n logger.error('Monitoring cycle failed', error instanceof Error ? error : new Error(String(error)));\r\n }\r\n },\r\n this.options.monitoringIntervalMs\r\n );\r\n\r\n logger.info('Monitoring started', {\r\n intervalMs: this.options.monitoringIntervalMs,\r\n autoReprocess: this.options.autoReprocess,\r\n });\r\n }\r\n\r\n /**\r\n * Stop automatic monitoring\r\n */\r\n public stopMonitoring(): void {\r\n if (this.monitoringTimer) {\r\n clearInterval(this.monitoringTimer);\r\n this.monitoringTimer = null;\r\n logger.info('Monitoring stopped');\r\n }\r\n }\r\n\r\n /**\r\n * Get recovery statistics\r\n *\r\n * @returns Current statistics\r\n */\r\n public getStats(): RecoveryStats {\r\n return { ...this.stats };\r\n }\r\n\r\n /**\r\n * Reset statistics\r\n */\r\n public resetStats(): void {\r\n this.stats = {\r\n totalRecovered: 0,\r\n totalDeadLettered: 0,\r\n totalReprocessed: 0,\r\n totalStuckDetected: 0,\r\n };\r\n\r\n logger.debug('Statistics reset');\r\n }\r\n\r\n /**\r\n * Shutdown recovery system (stop monitoring)\r\n */\r\n public shutdown(): void {\r\n this.stopMonitoring();\r\n logger.info('QueueRecovery shutdown');\r\n }\r\n\r\n /**\r\n * Calculate exponential backoff delay\r\n *\r\n * @param attempt - Current attempt number (1-based)\r\n * @returns Delay in milliseconds\r\n */\r\n private calculateBackoffDelay(attempt: number): number {\r\n // Exponential backoff: baseDelay * 2^(attempt - 1)\r\n const delay = this.options.baseRetryDelayMs * Math.pow(2, attempt - 1);\r\n\r\n // Cap at max delay\r\n const cappedDelay = Math.min(delay, this.options.maxRetryDelayMs);\r\n\r\n // Add jitter (+/- 10%) to prevent thundering herd\r\n const jitterFactor = 0.1;\r\n const jitterRange = cappedDelay * jitterFactor;\r\n const jitter = (Math.random() * 2 - 1) * jitterRange;\r\n\r\n return Math.max(0, Math.floor(cappedDelay + jitter));\r\n }\r\n}\r\n\r\n/**\r\n * Message reprocessing safeguards\r\n */\r\nexport class ReprocessingSafeguards {\r\n private processedMessages: Set<string> = new Set();\r\n private maxProcessedTracking: number;\r\n\r\n /**\r\n * Create a new ReprocessingSafeguards instance\r\n *\r\n * @param maxProcessedTracking - Maximum number of processed message IDs to track (default: 10000)\r\n */\r\n constructor(maxProcessedTracking: number = 10000) {\r\n this.maxProcessedTracking = maxProcessedTracking;\r\n\r\n logger.debug('ReprocessingSafeguards initialized', {\r\n maxProcessedTracking,\r\n });\r\n }\r\n\r\n /**\r\n * Check if message has already been processed\r\n *\r\n * @param messageId - Message ID to check\r\n * @returns True if already processed\r\n */\r\n public hasBeenProcessed(messageId: string): boolean {\r\n return this.processedMessages.has(messageId);\r\n }\r\n\r\n /**\r\n * Mark message as processed\r\n *\r\n * @param messageId - Message ID to mark\r\n */\r\n public markProcessed(messageId: string): void {\r\n // Implement simple LRU-like behavior\r\n if (this.processedMessages.size >= this.maxProcessedTracking) {\r\n // Remove oldest entry (first in Set)\r\n const firstId = this.processedMessages.values().next().value;\r\n this.processedMessages.delete(firstId);\r\n }\r\n\r\n this.processedMessages.add(messageId);\r\n }\r\n\r\n /**\r\n * Clear all processed message tracking\r\n */\r\n public clear(): void {\r\n this.processedMessages.clear();\r\n logger.debug('Processed messages cleared');\r\n }\r\n\r\n /**\r\n * Get number of tracked processed messages\r\n */\r\n public getTrackedCount(): number {\r\n return this.processedMessages.size;\r\n }\r\n}\r\n"],"names":["createLogger","createError","ErrorCode","isRetryableError","sleep","logger","DEFAULT_RECOVERY_OPTIONS","maxRetries","baseRetryDelayMs","maxRetryDelayMs","processingTimeoutMs","monitoringIntervalMs","deadLetterQueue","autoReprocess","QueueRecovery","queueManager","redis","options","monitoringTimer","stats","totalRecovered","totalDeadLettered","totalReprocessed","totalStuckDetected","info","retryWithBackoff","message","processFn","maxAttempts","attempt","deliveryAttempts","debug","messageId","id","result","payload","error","err","Error","String","warn","retryable","sendToDeadLetter","delay","calculateBackoffDelay","delayMs","RETRY_EXHAUSTED","attempts","reason","metadata","originalQueue","queue","failureReason","retryAttempts","deadLetteredAt","Date","originalMetadata","enqueue","deduplicate","DB_QUERY_FAILED","undefined","reprocessDeadLetters","maxMessages","reprocessedCount","i","dequeue","timeout","acknowledge","reject","retry","recoverStuckMessages","processingKey","queueKey","processingMessages","lRange","recoveredCount","now","messageData","JSON","parse","dequeuedAt","processingTime","getTime","processingTimeMs","timeoutMs","lRem","recoveredAt","toISOString","recoveryReason","rPush","stringify","lastRecoveryAt","recoverFromCrash","queues","getQueues","results","recovered","Object","values","reduce","sum","count","queuesProcessed","length","startMonitoring","setInterval","reprocessedAt","intervalMs","stopMonitoring","clearInterval","getStats","resetStats","shutdown","Math","pow","cappedDelay","min","jitterFactor","jitterRange","jitter","random","max","floor","ReprocessingSafeguards","processedMessages","Set","maxProcessedTracking","hasBeenProcessed","has","markProcessed","size","firstId","next","value","delete","add","clear","getTrackedCount"],"mappings":"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"}
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Queue Manager
|
|
3
|
+
*
|
|
4
|
+
* Provides reliable queue operations with idempotency, acknowledgment protocol,
|
|
5
|
+
* and message visibility timeout for Docker agent ↔ Redis communication.
|
|
6
|
+
* Part of Task 3.4: Redis Queue Consistency & Recovery (Integration Standardization Sprint 3)
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Enqueue with idempotency (prevents duplicate messages)
|
|
10
|
+
* - Dequeue with acknowledgment protocol
|
|
11
|
+
* - Message visibility timeout
|
|
12
|
+
* - Queue monitoring (depth, age, throughput)
|
|
13
|
+
* - Multiple queue support (task, result, coordination)
|
|
14
|
+
* - Performance: <100ms per operation
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const queueManager = new RedisQueueManager(redisClient);
|
|
18
|
+
*
|
|
19
|
+
* // Producer
|
|
20
|
+
* await queueManager.enqueue('task-queue', {
|
|
21
|
+
* taskId: 'task-001',
|
|
22
|
+
* agentType: 'backend-developer',
|
|
23
|
+
* payload: { ... }
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Consumer
|
|
27
|
+
* const message = await queueManager.dequeue('task-queue', { timeout: 30000 });
|
|
28
|
+
* try {
|
|
29
|
+
* await processTask(message.payload);
|
|
30
|
+
* await queueManager.acknowledge(message.id);
|
|
31
|
+
* } catch (error) {
|
|
32
|
+
* await queueManager.reject(message.id, { retry: true });
|
|
33
|
+
* }
|
|
34
|
+
*/ import { v4 as uuidv4 } from 'uuid';
|
|
35
|
+
import { createLogger } from './logging.js';
|
|
36
|
+
import { createError, ErrorCode, isRetryableError } from './errors.js';
|
|
37
|
+
import { withRetry } from './retry.js';
|
|
38
|
+
import { MessageDeduplicator } from './message-deduplicator.js';
|
|
39
|
+
const logger = createLogger('redis-queue-manager');
|
|
40
|
+
/**
|
|
41
|
+
* Default queue options
|
|
42
|
+
*/ const DEFAULT_ENQUEUE_OPTIONS = {
|
|
43
|
+
deduplicate: true,
|
|
44
|
+
metadata: {},
|
|
45
|
+
visibilityTimeout: 30000
|
|
46
|
+
};
|
|
47
|
+
const DEFAULT_DEQUEUE_OPTIONS = {
|
|
48
|
+
timeout: 0,
|
|
49
|
+
visibilityTimeout: 30000,
|
|
50
|
+
count: 1
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Redis Queue Manager
|
|
54
|
+
*
|
|
55
|
+
* Provides reliable queue operations with at-least-once delivery guarantees.
|
|
56
|
+
*/ export class RedisQueueManager {
|
|
57
|
+
redis;
|
|
58
|
+
deduplicator;
|
|
59
|
+
stats = new Map();
|
|
60
|
+
/**
|
|
61
|
+
* Create a new RedisQueueManager instance
|
|
62
|
+
*
|
|
63
|
+
* @param redis - Redis client instance
|
|
64
|
+
* @param deduplicator - Optional custom deduplicator instance
|
|
65
|
+
*/ constructor(redis, deduplicator){
|
|
66
|
+
this.redis = redis;
|
|
67
|
+
this.deduplicator = deduplicator || new MessageDeduplicator(redis);
|
|
68
|
+
logger.info('RedisQueueManager initialized');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Enqueue a message to a queue
|
|
72
|
+
*
|
|
73
|
+
* @param queue - Queue name
|
|
74
|
+
* @param payload - Message payload
|
|
75
|
+
* @param options - Enqueue options
|
|
76
|
+
* @returns Message ID
|
|
77
|
+
*/ async enqueue(queue, payload, options = {}) {
|
|
78
|
+
const opts = {
|
|
79
|
+
...DEFAULT_ENQUEUE_OPTIONS,
|
|
80
|
+
...options
|
|
81
|
+
};
|
|
82
|
+
const startTime = Date.now();
|
|
83
|
+
try {
|
|
84
|
+
// Check for duplicates if enabled
|
|
85
|
+
if (opts.deduplicate) {
|
|
86
|
+
const isDuplicate = await this.deduplicator.isDuplicate(payload);
|
|
87
|
+
if (isDuplicate) {
|
|
88
|
+
logger.warn('Duplicate message detected, skipping enqueue', {
|
|
89
|
+
queue,
|
|
90
|
+
payloadHash: this.deduplicator.createFingerprint(payload).substring(0, 16) + '...'
|
|
91
|
+
});
|
|
92
|
+
throw createError(ErrorCode.DB_DUPLICATE_KEY, 'Duplicate message detected', {
|
|
93
|
+
queue
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Create message
|
|
98
|
+
const message = {
|
|
99
|
+
id: uuidv4(),
|
|
100
|
+
queue,
|
|
101
|
+
payload,
|
|
102
|
+
createdAt: new Date(),
|
|
103
|
+
enqueuedAt: new Date(),
|
|
104
|
+
deliveryAttempts: 0,
|
|
105
|
+
visibilityTimeout: opts.visibilityTimeout,
|
|
106
|
+
metadata: opts.metadata
|
|
107
|
+
};
|
|
108
|
+
// Push to queue (RPUSH for FIFO)
|
|
109
|
+
await withRetry(async ()=>{
|
|
110
|
+
const queueKey = this.getQueueKey(queue);
|
|
111
|
+
await this.redis.rPush(queueKey, JSON.stringify(message));
|
|
112
|
+
}, {
|
|
113
|
+
maxAttempts: 3,
|
|
114
|
+
shouldRetry: isRetryableError
|
|
115
|
+
});
|
|
116
|
+
// Mark as processed in deduplicator if enabled
|
|
117
|
+
if (opts.deduplicate) {
|
|
118
|
+
await this.deduplicator.markProcessed(payload, {
|
|
119
|
+
messageId: message.id,
|
|
120
|
+
queue
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Update stats
|
|
124
|
+
this.updateStats(queue, 'enqueued');
|
|
125
|
+
const duration = Date.now() - startTime;
|
|
126
|
+
logger.debug('Message enqueued', {
|
|
127
|
+
queue,
|
|
128
|
+
messageId: message.id,
|
|
129
|
+
durationMs: duration
|
|
130
|
+
});
|
|
131
|
+
// Validate performance requirement (<100ms)
|
|
132
|
+
if (duration > 100) {
|
|
133
|
+
logger.warn('Enqueue operation exceeded 100ms target', {
|
|
134
|
+
queue,
|
|
135
|
+
durationMs: duration
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return message.id;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logger.error('Failed to enqueue message', error instanceof Error ? error : new Error(String(error)), {
|
|
141
|
+
queue
|
|
142
|
+
});
|
|
143
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to enqueue message', {
|
|
144
|
+
queue
|
|
145
|
+
}, error instanceof Error ? error : undefined);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Dequeue a message from a queue
|
|
150
|
+
*
|
|
151
|
+
* @param queue - Queue name
|
|
152
|
+
* @param options - Dequeue options
|
|
153
|
+
* @returns Message or null if queue is empty
|
|
154
|
+
*/ async dequeue(queue, options = {}) {
|
|
155
|
+
const opts = {
|
|
156
|
+
...DEFAULT_DEQUEUE_OPTIONS,
|
|
157
|
+
...options
|
|
158
|
+
};
|
|
159
|
+
const startTime = Date.now();
|
|
160
|
+
try {
|
|
161
|
+
const queueKey = this.getQueueKey(queue);
|
|
162
|
+
const processingKey = this.getProcessingKey(queue);
|
|
163
|
+
let messageData = null;
|
|
164
|
+
// Use blocking pop if timeout specified
|
|
165
|
+
if (opts.timeout > 0) {
|
|
166
|
+
const result = await withRetry(async ()=>{
|
|
167
|
+
// BLMOVE atomically moves from queue to processing set with timeout
|
|
168
|
+
return await this.redis.blMove(queueKey, processingKey, 'LEFT', 'RIGHT', opts.timeout / 1000 // Convert to seconds
|
|
169
|
+
);
|
|
170
|
+
}, {
|
|
171
|
+
maxAttempts: 1
|
|
172
|
+
} // Don't retry blocking operations
|
|
173
|
+
);
|
|
174
|
+
messageData = result;
|
|
175
|
+
} else {
|
|
176
|
+
// Non-blocking pop
|
|
177
|
+
messageData = await withRetry(async ()=>{
|
|
178
|
+
return await this.redis.lMove(queueKey, processingKey, 'LEFT', 'RIGHT');
|
|
179
|
+
}, {
|
|
180
|
+
maxAttempts: 3,
|
|
181
|
+
shouldRetry: isRetryableError
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (!messageData) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
// Parse message
|
|
188
|
+
const message = JSON.parse(messageData);
|
|
189
|
+
// Convert date strings back to Date objects
|
|
190
|
+
message.createdAt = new Date(message.createdAt);
|
|
191
|
+
message.enqueuedAt = new Date(message.enqueuedAt);
|
|
192
|
+
message.dequeuedAt = new Date();
|
|
193
|
+
message.deliveryAttempts++;
|
|
194
|
+
// Store message with visibility timeout
|
|
195
|
+
await this.storeInFlight(message, opts.visibilityTimeout);
|
|
196
|
+
// Update stats
|
|
197
|
+
this.updateStats(queue, 'dequeued');
|
|
198
|
+
const duration = Date.now() - startTime;
|
|
199
|
+
logger.debug('Message dequeued', {
|
|
200
|
+
queue,
|
|
201
|
+
messageId: message.id,
|
|
202
|
+
deliveryAttempts: message.deliveryAttempts,
|
|
203
|
+
durationMs: duration
|
|
204
|
+
});
|
|
205
|
+
// Validate performance requirement (<100ms)
|
|
206
|
+
if (duration > 100) {
|
|
207
|
+
logger.warn('Dequeue operation exceeded 100ms target', {
|
|
208
|
+
queue,
|
|
209
|
+
durationMs: duration
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return message;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.error('Failed to dequeue message', error instanceof Error ? error : new Error(String(error)), {
|
|
215
|
+
queue
|
|
216
|
+
});
|
|
217
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to dequeue message', {
|
|
218
|
+
queue
|
|
219
|
+
}, error instanceof Error ? error : undefined);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Acknowledge successful message processing
|
|
224
|
+
*
|
|
225
|
+
* @param messageId - Message ID to acknowledge
|
|
226
|
+
*/ async acknowledge(messageId) {
|
|
227
|
+
const startTime = Date.now();
|
|
228
|
+
try {
|
|
229
|
+
// Remove from in-flight storage
|
|
230
|
+
const message = await this.getInFlight(messageId);
|
|
231
|
+
if (!message) {
|
|
232
|
+
logger.warn('Message not found for acknowledgment', {
|
|
233
|
+
messageId
|
|
234
|
+
});
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Remove from processing set
|
|
238
|
+
const processingKey = this.getProcessingKey(message.queue);
|
|
239
|
+
await withRetry(async ()=>{
|
|
240
|
+
await this.redis.lRem(processingKey, 1, JSON.stringify(message));
|
|
241
|
+
}, {
|
|
242
|
+
maxAttempts: 3,
|
|
243
|
+
shouldRetry: isRetryableError
|
|
244
|
+
});
|
|
245
|
+
// Remove from in-flight storage
|
|
246
|
+
await this.removeInFlight(messageId);
|
|
247
|
+
// Update stats
|
|
248
|
+
this.updateStats(message.queue, 'acknowledged');
|
|
249
|
+
const duration = Date.now() - startTime;
|
|
250
|
+
logger.debug('Message acknowledged', {
|
|
251
|
+
queue: message.queue,
|
|
252
|
+
messageId,
|
|
253
|
+
durationMs: duration
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
logger.error('Failed to acknowledge message', error instanceof Error ? error : new Error(String(error)), {
|
|
257
|
+
messageId
|
|
258
|
+
});
|
|
259
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to acknowledge message', {
|
|
260
|
+
messageId
|
|
261
|
+
}, error instanceof Error ? error : undefined);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Reject message processing (with optional retry)
|
|
266
|
+
*
|
|
267
|
+
* @param messageId - Message ID to reject
|
|
268
|
+
* @param options - Reject options
|
|
269
|
+
*/ async reject(messageId, options = {}) {
|
|
270
|
+
const startTime = Date.now();
|
|
271
|
+
try {
|
|
272
|
+
// Get message from in-flight storage
|
|
273
|
+
const message = await this.getInFlight(messageId);
|
|
274
|
+
if (!message) {
|
|
275
|
+
logger.warn('Message not found for rejection', {
|
|
276
|
+
messageId
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// Remove from processing set
|
|
281
|
+
const processingKey = this.getProcessingKey(message.queue);
|
|
282
|
+
await withRetry(async ()=>{
|
|
283
|
+
await this.redis.lRem(processingKey, 1, JSON.stringify(message));
|
|
284
|
+
}, {
|
|
285
|
+
maxAttempts: 3,
|
|
286
|
+
shouldRetry: isRetryableError
|
|
287
|
+
});
|
|
288
|
+
// Remove from in-flight storage
|
|
289
|
+
await this.removeInFlight(messageId);
|
|
290
|
+
if (options.retry) {
|
|
291
|
+
// Re-enqueue message
|
|
292
|
+
message.metadata = {
|
|
293
|
+
...message.metadata,
|
|
294
|
+
...options.metadata,
|
|
295
|
+
rejectedAt: new Date().toISOString(),
|
|
296
|
+
rejectionReason: options.error
|
|
297
|
+
};
|
|
298
|
+
const queueKey = this.getQueueKey(message.queue);
|
|
299
|
+
await withRetry(async ()=>{
|
|
300
|
+
await this.redis.rPush(queueKey, JSON.stringify(message));
|
|
301
|
+
}, {
|
|
302
|
+
maxAttempts: 3,
|
|
303
|
+
shouldRetry: isRetryableError
|
|
304
|
+
});
|
|
305
|
+
logger.debug('Message rejected and re-enqueued', {
|
|
306
|
+
queue: message.queue,
|
|
307
|
+
messageId,
|
|
308
|
+
deliveryAttempts: message.deliveryAttempts
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
logger.debug('Message rejected without retry', {
|
|
312
|
+
queue: message.queue,
|
|
313
|
+
messageId
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
// Update stats
|
|
317
|
+
this.updateStats(message.queue, 'rejected');
|
|
318
|
+
const duration = Date.now() - startTime;
|
|
319
|
+
logger.debug('Message rejected', {
|
|
320
|
+
queue: message.queue,
|
|
321
|
+
messageId,
|
|
322
|
+
retry: options.retry,
|
|
323
|
+
durationMs: duration
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
logger.error('Failed to reject message', error instanceof Error ? error : new Error(String(error)), {
|
|
327
|
+
messageId
|
|
328
|
+
});
|
|
329
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to reject message', {
|
|
330
|
+
messageId
|
|
331
|
+
}, error instanceof Error ? error : undefined);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get queue statistics
|
|
336
|
+
*
|
|
337
|
+
* @param queue - Queue name
|
|
338
|
+
* @returns Queue statistics
|
|
339
|
+
*/ async getStats(queue) {
|
|
340
|
+
try {
|
|
341
|
+
const queueKey = this.getQueueKey(queue);
|
|
342
|
+
const processingKey = this.getProcessingKey(queue);
|
|
343
|
+
// Get queue depth
|
|
344
|
+
const depth = await this.redis.lLen(queueKey);
|
|
345
|
+
// Get in-flight count
|
|
346
|
+
const inFlight = await this.redis.lLen(processingKey);
|
|
347
|
+
// Get oldest message age
|
|
348
|
+
let oldestMessageAge = 0;
|
|
349
|
+
const oldestMessage = await this.redis.lIndex(queueKey, 0);
|
|
350
|
+
if (oldestMessage) {
|
|
351
|
+
const message = JSON.parse(oldestMessage);
|
|
352
|
+
const age = Date.now() - new Date(message.enqueuedAt).getTime();
|
|
353
|
+
oldestMessageAge = Math.floor(age / 1000); // Convert to seconds
|
|
354
|
+
}
|
|
355
|
+
// Get stats from tracking
|
|
356
|
+
const stats = this.stats.get(queue) || {
|
|
357
|
+
enqueued: 0,
|
|
358
|
+
dequeued: 0,
|
|
359
|
+
acknowledged: 0,
|
|
360
|
+
rejected: 0,
|
|
361
|
+
startTime: new Date()
|
|
362
|
+
};
|
|
363
|
+
// Calculate throughput (messages per second)
|
|
364
|
+
const elapsed = (Date.now() - stats.startTime.getTime()) / 1000;
|
|
365
|
+
const throughput = elapsed > 0 ? stats.dequeued / elapsed : 0;
|
|
366
|
+
return {
|
|
367
|
+
queue,
|
|
368
|
+
depth,
|
|
369
|
+
inFlight,
|
|
370
|
+
oldestMessageAge,
|
|
371
|
+
totalEnqueued: stats.enqueued,
|
|
372
|
+
totalDequeued: stats.dequeued,
|
|
373
|
+
totalAcknowledged: stats.acknowledged,
|
|
374
|
+
totalRejected: stats.rejected,
|
|
375
|
+
throughput
|
|
376
|
+
};
|
|
377
|
+
} catch (error) {
|
|
378
|
+
logger.error('Failed to get queue stats', error instanceof Error ? error : new Error(String(error)), {
|
|
379
|
+
queue
|
|
380
|
+
});
|
|
381
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to get queue stats', {
|
|
382
|
+
queue
|
|
383
|
+
}, error instanceof Error ? error : undefined);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Purge all messages from a queue
|
|
388
|
+
*
|
|
389
|
+
* @param queue - Queue name
|
|
390
|
+
* @returns Number of messages purged
|
|
391
|
+
*/ async purge(queue) {
|
|
392
|
+
try {
|
|
393
|
+
const queueKey = this.getQueueKey(queue);
|
|
394
|
+
const count = await withRetry(async ()=>{
|
|
395
|
+
const len = await this.redis.lLen(queueKey);
|
|
396
|
+
await this.redis.del(queueKey);
|
|
397
|
+
return len;
|
|
398
|
+
}, {
|
|
399
|
+
maxAttempts: 3,
|
|
400
|
+
shouldRetry: isRetryableError
|
|
401
|
+
});
|
|
402
|
+
logger.info('Queue purged', {
|
|
403
|
+
queue,
|
|
404
|
+
messagesPurged: count
|
|
405
|
+
});
|
|
406
|
+
return count;
|
|
407
|
+
} catch (error) {
|
|
408
|
+
logger.error('Failed to purge queue', error instanceof Error ? error : new Error(String(error)), {
|
|
409
|
+
queue
|
|
410
|
+
});
|
|
411
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to purge queue', {
|
|
412
|
+
queue
|
|
413
|
+
}, error instanceof Error ? error : undefined);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get all queue names
|
|
418
|
+
*
|
|
419
|
+
* @returns Array of queue names
|
|
420
|
+
*/ async getQueues() {
|
|
421
|
+
try {
|
|
422
|
+
const pattern = 'queue:*';
|
|
423
|
+
const keys = await this.redis.keys(pattern);
|
|
424
|
+
const queues = keys.filter((key)=>!key.includes(':processing')).map((key)=>key.replace('queue:', ''));
|
|
425
|
+
return queues;
|
|
426
|
+
} catch (error) {
|
|
427
|
+
logger.error('Failed to get queues', error instanceof Error ? error : new Error(String(error)));
|
|
428
|
+
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to get queues', {}, error instanceof Error ? error : undefined);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Shutdown queue manager (cleanup resources)
|
|
433
|
+
*/ shutdown() {
|
|
434
|
+
this.deduplicator.shutdown();
|
|
435
|
+
logger.info('RedisQueueManager shutdown');
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Get Redis key for queue
|
|
439
|
+
*/ getQueueKey(queue) {
|
|
440
|
+
return `queue:${queue}`;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Get Redis key for processing set
|
|
444
|
+
*/ getProcessingKey(queue) {
|
|
445
|
+
return `queue:${queue}:processing`;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Get Redis key for in-flight message storage
|
|
449
|
+
*/ getInFlightKey(messageId) {
|
|
450
|
+
return `inflight:${messageId}`;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Store message in in-flight storage with TTL
|
|
454
|
+
*/ async storeInFlight(message, visibilityTimeout) {
|
|
455
|
+
const key = this.getInFlightKey(message.id);
|
|
456
|
+
await withRetry(async ()=>{
|
|
457
|
+
await this.redis.set(key, JSON.stringify(message), {
|
|
458
|
+
PX: visibilityTimeout
|
|
459
|
+
});
|
|
460
|
+
}, {
|
|
461
|
+
maxAttempts: 3,
|
|
462
|
+
shouldRetry: isRetryableError
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Get message from in-flight storage
|
|
467
|
+
*/ async getInFlight(messageId) {
|
|
468
|
+
const key = this.getInFlightKey(messageId);
|
|
469
|
+
const data = await withRetry(async ()=>await this.redis.get(key), {
|
|
470
|
+
maxAttempts: 3,
|
|
471
|
+
shouldRetry: isRetryableError
|
|
472
|
+
});
|
|
473
|
+
if (!data) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
const message = JSON.parse(data);
|
|
477
|
+
// Convert date strings back to Date objects
|
|
478
|
+
message.createdAt = new Date(message.createdAt);
|
|
479
|
+
message.enqueuedAt = new Date(message.enqueuedAt);
|
|
480
|
+
if (message.dequeuedAt) {
|
|
481
|
+
message.dequeuedAt = new Date(message.dequeuedAt);
|
|
482
|
+
}
|
|
483
|
+
return message;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Remove message from in-flight storage
|
|
487
|
+
*/ async removeInFlight(messageId) {
|
|
488
|
+
const key = this.getInFlightKey(messageId);
|
|
489
|
+
await withRetry(async ()=>await this.redis.del(key), {
|
|
490
|
+
maxAttempts: 3,
|
|
491
|
+
shouldRetry: isRetryableError
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Update queue statistics
|
|
496
|
+
*/ updateStats(queue, operation) {
|
|
497
|
+
let stats = this.stats.get(queue);
|
|
498
|
+
if (!stats) {
|
|
499
|
+
stats = {
|
|
500
|
+
enqueued: 0,
|
|
501
|
+
dequeued: 0,
|
|
502
|
+
acknowledged: 0,
|
|
503
|
+
rejected: 0,
|
|
504
|
+
startTime: new Date()
|
|
505
|
+
};
|
|
506
|
+
this.stats.set(queue, stats);
|
|
507
|
+
}
|
|
508
|
+
stats[operation]++;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
//# sourceMappingURL=redis-queue-manager.js.map
|