claude-flow-novice 1.6.4 → 1.6.5
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/commands/parse-epic.js +180 -0
- package/.claude/settings.local.json +2 -1
- package/.claude-flow-novice/dist/mcp/mcp-server-novice.js +37 -2
- package/.claude-flow-novice/dist/mcp/transports/base.js +5 -0
- package/.claude-flow-novice/dist/mcp/transports/base.js.map +1 -0
- package/.claude-flow-novice/dist/mcp/transports/http.js +414 -0
- package/.claude-flow-novice/dist/mcp/transports/http.js.map +1 -0
- package/.claude-flow-novice/dist/mcp/transports/stdio.js +217 -0
- package/.claude-flow-novice/dist/mcp/transports/stdio.js.map +1 -0
- package/.claude-flow-novice/dist/src/cli/commands/parse-epic.js +129 -0
- package/.claude-flow-novice/dist/src/cli/commands/parse-epic.js.map +1 -0
- package/.claude-flow-novice/dist/src/cli/index.js +3 -0
- package/.claude-flow-novice/dist/src/cli/index.js.map +1 -1
- package/.claude-flow-novice/dist/src/cli/utils/epic-parser.js +266 -0
- package/.claude-flow-novice/dist/src/cli/utils/epic-parser.js.map +1 -0
- package/.claude-flow-novice/dist/src/communication/message-bus.js +105 -2
- package/.claude-flow-novice/dist/src/communication/message-bus.js.map +1 -1
- package/.claude-flow-novice/dist/src/coordination/adapters/v1-coordinator-adapter.js +1 -1
- package/.claude-flow-novice/dist/src/coordination/adapters/v1-coordinator-adapter.js.map +1 -1
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/cache/artifact-cache-optimizer.js +632 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/cache/artifact-cache-optimizer.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/cache/index.js +11 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/cache/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/checkpoints/checkpoint-compressor.js +318 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/checkpoints/checkpoint-compressor.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/completion-detector.js +234 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/completion-detector.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/hierarchical-detector.js +347 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/hierarchical-detector.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/index.js +13 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/lamport-clock.js +173 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/lamport-clock.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/mesh-detector.js +526 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/mesh-detector.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/sdk-completion-detector.js +443 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/sdk-completion-detector.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/swarm-shutdown.js +366 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/completion/swarm-shutdown.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinator-factory.js +287 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinator-factory.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/cascading-shutdown.example.js +364 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/cascading-shutdown.example.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/cascading-shutdown.js +492 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/cascading-shutdown.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/hierarchical-coordinator.js +786 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/hierarchical-coordinator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/index.js +16 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/parent-child-manager.js +342 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/parent-child-manager.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/swarm-coordinator-v2.js +601 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/coordinators/swarm-coordinator-v2.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/agent-state.js +9 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/agent-state.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dead-letter-queue.js +413 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dead-letter-queue.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dependency-graph.js +471 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dependency-graph.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dependency-node.js +379 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dependency-node.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dependency-resolver.js +335 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/dependency-resolver.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/help-request-metrics.js +211 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/help-request-metrics.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/index.js +33 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/message-broker.js +920 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/message-broker.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/message-router.js +385 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/message-router.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/message.js +138 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/message.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/resource-manager-safe.js +478 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/resource-manager-safe.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/state-machine-config.js +358 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/state-machine-config.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/state-machine.js +588 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/state-machine.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/state-transition.js +153 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/state-transition.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/task-scheduler.js +360 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/task-scheduler.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/topic-manager.js +337 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/core/topic-manager.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/deadlock/deadlock-detector.js +424 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/deadlock/deadlock-detector.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/deadlock/index.js +9 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/deadlock/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/deadlock/resource-manager.js +669 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/deadlock/resource-manager.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/artifact-storage.js +451 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/artifact-storage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/cycle-detector.js +271 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/cycle-detector.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/dependency-graph.js +335 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/dependency-graph.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/dependency-manager.js +439 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/dependency-manager.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/dependency-request.js +92 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/dependency-request.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/index.js +21 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/topological-sort.js +223 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/dependency/topological-sort.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-coordinator.js +436 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-coordinator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-matcher.js +278 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-matcher.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-request-handler.js +317 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-request-handler.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-request.js +273 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/help-request.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/index.js +15 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/waiting-agent-pool.js +512 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/help-system/waiting-agent-pool.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/index.js +67 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/help-deadlock-integration.js +557 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/help-deadlock-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/index.js +14 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/message-bus-completion-integration.example.js +212 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/message-bus-completion-integration.example.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/message-bus-completion-integration.js +552 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/message-bus-completion-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/state-machine-integration.js +635 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/integration/state-machine-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/IArtifactStorage.js +28 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/IArtifactStorage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/ICoordinator.js +9 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/ICoordinator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/ISessionStore.js +25 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/ISessionStore.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/index.js +14 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/interfaces/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/artifact-adapter.js +308 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/artifact-adapter.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/completion-storage.js +439 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/completion-storage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/dependency-graph-storage.js +540 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/dependency-graph-storage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/dependency-storage.js +367 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/dependency-storage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/index.js +14 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/message-storage.js +518 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/message-storage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/state-storage.js +377 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/memory/state-storage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channel.js +371 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channel.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/dependency-channel.js +355 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/dependency-channel.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/help-channel.js +424 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/help-channel.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/index.js +16 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/state-channel.js +295 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/state-channel.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/task-channel.js +411 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/channels/task-channel.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/index.js +14 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/message-bus.js +387 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/message-bus.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/message-persistence.js +589 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/message-persistence.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/message-router.js +444 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/messaging/message-router.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/artifact-storage.js +560 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/artifact-storage.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/background-orchestrator.js +335 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/background-orchestrator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/bash-output-monitor.js +104 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/bash-output-monitor.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/checkpoint-manager.js +847 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/checkpoint-manager.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/help-coordinator.js +470 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/help-coordinator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/hierarchical-background-integration.js +450 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/hierarchical-background-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/index.js +13 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/message-bus-integration.js +625 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/message-bus-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/multi-level-control.js +545 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/multi-level-control.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/query-controller.js +740 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/query-controller.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/query-message-integration.js +415 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/query-message-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/session-pool-optimizer.js +615 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/session-pool-optimizer.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/state-machine-integration.js +547 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/state-machine-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/state-sdk-integration.js +342 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/sdk/state-sdk-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/security/payload-validator.js +259 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/security/payload-validator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/framework-registry.js +273 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/framework-registry.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/index.js +8 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/truth-config-manager.js +310 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/truth-config-manager.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/truth-validator.js +218 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/truth/truth-validator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/types/sdk.js +9 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/types/sdk.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/utils/index.js +6 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/utils/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/utils/priority-queue.js +145 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/utils/priority-queue.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/utils/sdk-helpers.js +122 -0
- package/.claude-flow-novice/dist/src/coordination/archives/v2-sdk-typescript/v2/utils/sdk-helpers.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/config-translator.js +17 -43
- package/.claude-flow-novice/dist/src/coordination/config-translator.js.map +1 -1
- package/.claude-flow-novice/dist/src/coordination/coordination-toggle.js +34 -76
- package/.claude-flow-novice/dist/src/coordination/coordination-toggle.js.map +1 -1
- package/.claude-flow-novice/dist/src/coordination/shared/core/agent-state.js +172 -0
- package/.claude-flow-novice/dist/src/coordination/shared/core/agent-state.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/core/index.js +7 -0
- package/.claude-flow-novice/dist/src/coordination/shared/core/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/index.js +19 -0
- package/.claude-flow-novice/dist/src/coordination/shared/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/interfaces/ICoordinator.js +24 -0
- package/.claude-flow-novice/dist/src/coordination/shared/interfaces/ICoordinator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/interfaces/index.js +7 -0
- package/.claude-flow-novice/dist/src/coordination/shared/interfaces/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/message-broker.js +920 -0
- package/.claude-flow-novice/dist/src/coordination/shared/message-broker.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/message.js +138 -0
- package/.claude-flow-novice/dist/src/coordination/shared/message.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/security/payload-validator.js +259 -0
- package/.claude-flow-novice/dist/src/coordination/shared/security/payload-validator.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/index.js +17 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/interfaces/transparency-system.js +19 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/interfaces/transparency-system.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/transparency-integration.js +357 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/transparency-integration.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/transparency-system.js +679 -0
- package/.claude-flow-novice/dist/src/coordination/shared/transparency/transparency-system.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/types/index.js +7 -0
- package/.claude-flow-novice/dist/src/coordination/shared/types/index.js.map +1 -0
- package/.claude-flow-novice/dist/src/coordination/shared/types/sdk.js +10 -0
- package/.claude-flow-novice/dist/src/coordination/shared/types/sdk.js.map +1 -0
- package/.claude-flow-novice/dist/src/feature-flags/core/FeatureFlagManager.js +52 -2
- package/.claude-flow-novice/dist/src/feature-flags/core/FeatureFlagManager.js.map +1 -1
- package/.claude-flow-novice/dist/src/mcp/mcp-server-novice.js +37 -2
- package/.claude-flow-novice/dist/src/mcp/session-manager.js +3 -1
- package/.claude-flow-novice/dist/src/mcp/session-manager.js.map +1 -1
- package/.claude-flow-novice/dist/src/providers/provider-manager.js +36 -3
- package/.claude-flow-novice/dist/src/providers/provider-manager.js.map +1 -1
- package/.claude-flow-novice/dist/src/utils/markdown-sanitizer.js +65 -41
- package/.claude-flow-novice/dist/src/utils/markdown-sanitizer.js.map +1 -1
- package/.claude-flow-novice/dist/src/web/api/server.js +1 -1
- package/.claude-flow-novice/dist/src/web/api/server.js.map +1 -1
- package/.claude-flow-novice/dist/src/workflows/progressive-rollout-manager.js +30 -0
- package/.claude-flow-novice/dist/src/workflows/progressive-rollout-manager.js.map +1 -1
- package/.claude-flow-novice/metrics.db-shm +0 -0
- package/.claude-flow-novice/metrics.db-wal +0 -0
- package/package.json +2 -2
- package/scripts/monitoring/alert-monitor.sh +220 -0
- package/scripts/monitoring/view-alerts.sh +307 -0
- package/scripts/test-zai-api.cjs +2 -2
- package/src/slash-commands/parse-epic.js +1 -1
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Coordination V2 - Message Broker Implementation
|
|
3
|
+
*
|
|
4
|
+
* EventEmitter-based message broker supporting:
|
|
5
|
+
* - Pub/sub pattern with topic-based routing
|
|
6
|
+
* - Request/reply pattern with timeout handling
|
|
7
|
+
* - Broadcast pattern to all subscribers
|
|
8
|
+
* - Priority queue ordering
|
|
9
|
+
* - At-least-once delivery semantics
|
|
10
|
+
*
|
|
11
|
+
* @module coordination/v2/core/message-broker
|
|
12
|
+
*/ import { EventEmitter } from 'events';
|
|
13
|
+
import { RateLimiter } from 'limiter';
|
|
14
|
+
import { MessagePriority, MessageUtils, DeliverySemantics } from './message.js';
|
|
15
|
+
import { PayloadValidator } from '../security/payload-validator.js';
|
|
16
|
+
/**
|
|
17
|
+
* Message broker error.
|
|
18
|
+
*/ export class MessageBrokerError extends Error {
|
|
19
|
+
constructor(message){
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'MessageBrokerError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Priority queue for message ordering.
|
|
26
|
+
*/ let PriorityQueue = class PriorityQueue {
|
|
27
|
+
items = [];
|
|
28
|
+
enqueue(item) {
|
|
29
|
+
this.items.push(item);
|
|
30
|
+
this.items.sort((a, b)=>b.priority - a.priority); // Higher priority first
|
|
31
|
+
}
|
|
32
|
+
dequeue() {
|
|
33
|
+
return this.items.shift();
|
|
34
|
+
}
|
|
35
|
+
peek() {
|
|
36
|
+
return this.items[0];
|
|
37
|
+
}
|
|
38
|
+
get length() {
|
|
39
|
+
return this.items.length;
|
|
40
|
+
}
|
|
41
|
+
size() {
|
|
42
|
+
return this.items.length;
|
|
43
|
+
}
|
|
44
|
+
clear() {
|
|
45
|
+
this.items = [];
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Message Broker Implementation.
|
|
50
|
+
*
|
|
51
|
+
* Core message bus for inter-agent communication with support for:
|
|
52
|
+
* - Topic-based pub/sub pattern
|
|
53
|
+
* - Request/reply pattern with correlation
|
|
54
|
+
* - Broadcast to all subscribers
|
|
55
|
+
* - Priority-based message ordering
|
|
56
|
+
* - At-least-once delivery semantics
|
|
57
|
+
* - Subscription management
|
|
58
|
+
* - Message statistics and monitoring
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const broker = new MessageBroker();
|
|
63
|
+
*
|
|
64
|
+
* // Subscribe to topic
|
|
65
|
+
* const sub = await broker.subscribe({
|
|
66
|
+
* topic: 'task.completed',
|
|
67
|
+
* handler: async (msg) => {
|
|
68
|
+
* console.log('Task completed:', msg.payload);
|
|
69
|
+
* }
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Publish message
|
|
73
|
+
* await broker.publish({
|
|
74
|
+
* topic: 'task.completed',
|
|
75
|
+
* payload: { taskId: 'task-1', result: 'success' },
|
|
76
|
+
* priority: MessagePriority.HIGH
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* // Request/reply pattern
|
|
80
|
+
* const reply = await broker.request('task.execute', { taskId: 'task-2' });
|
|
81
|
+
* console.log('Reply:', reply.payload);
|
|
82
|
+
*
|
|
83
|
+
* // Unsubscribe
|
|
84
|
+
* sub.unsubscribe();
|
|
85
|
+
* ```
|
|
86
|
+
*/ export class MessageBroker {
|
|
87
|
+
emitter;
|
|
88
|
+
subscriptions;
|
|
89
|
+
pendingRequests;
|
|
90
|
+
messageQueue;
|
|
91
|
+
config;
|
|
92
|
+
stats;
|
|
93
|
+
processing;
|
|
94
|
+
subscriptionCounter;
|
|
95
|
+
// SEC-DOS-001: Per-sender queue tracking and dead letter queue
|
|
96
|
+
senderQueueSizes;
|
|
97
|
+
deadLetterQueue;
|
|
98
|
+
// SEC-DOS-002: Rate limiting
|
|
99
|
+
rateLimiters;
|
|
100
|
+
rateLimiterCleanupInterval;
|
|
101
|
+
// SEC-INJ-001: Payload validation
|
|
102
|
+
payloadValidator;
|
|
103
|
+
// SEC-MEM-001: Subscription tracking and limits
|
|
104
|
+
agentSubscriptions;
|
|
105
|
+
subscriptionCleanupInterval;
|
|
106
|
+
/**
|
|
107
|
+
* Creates a new message broker instance.
|
|
108
|
+
*
|
|
109
|
+
* @param config - Broker configuration options
|
|
110
|
+
*/ constructor(config){
|
|
111
|
+
this.emitter = new EventEmitter();
|
|
112
|
+
this.emitter.setMaxListeners(0); // Unlimited listeners
|
|
113
|
+
this.subscriptions = new Map();
|
|
114
|
+
this.pendingRequests = new Map();
|
|
115
|
+
this.messageQueue = new PriorityQueue();
|
|
116
|
+
this.subscriptionCounter = 0;
|
|
117
|
+
this.processing = false;
|
|
118
|
+
// SEC-DOS-001: Initialize per-sender queue tracking
|
|
119
|
+
this.senderQueueSizes = new Map();
|
|
120
|
+
this.deadLetterQueue = new PriorityQueue();
|
|
121
|
+
// SEC-DOS-002: Initialize rate limiters
|
|
122
|
+
this.rateLimiters = new Map();
|
|
123
|
+
// SEC-MEM-001: Initialize subscription tracking
|
|
124
|
+
this.agentSubscriptions = new Map();
|
|
125
|
+
this.config = {
|
|
126
|
+
maxQueueSize: config?.maxQueueSize ?? 10000,
|
|
127
|
+
maxQueueSizePerSender: config?.maxQueueSizePerSender ?? 100,
|
|
128
|
+
enableDeadLetterQueue: config?.enableDeadLetterQueue ?? true,
|
|
129
|
+
priorityEvictionThreshold: config?.priorityEvictionThreshold ?? 0.9,
|
|
130
|
+
defaultRequestTimeout: config?.defaultRequestTimeout ?? 30000,
|
|
131
|
+
enableStats: config?.enableStats ?? true,
|
|
132
|
+
maxConcurrency: config?.maxConcurrency ?? 100,
|
|
133
|
+
maxSubscriptions: config?.maxSubscriptions ?? 10000,
|
|
134
|
+
maxSubscriptionsPerAgent: config?.maxSubscriptionsPerAgent ?? 100,
|
|
135
|
+
subscriptionTTL: config?.subscriptionTTL ?? 3600000,
|
|
136
|
+
cleanupIntervalMs: config?.cleanupIntervalMs ?? 60000,
|
|
137
|
+
authorizationProvider: config?.authorizationProvider,
|
|
138
|
+
rateLimit: config?.rateLimit ? {
|
|
139
|
+
maxMessagesPerSecond: config.rateLimit.maxMessagesPerSecond ?? 100,
|
|
140
|
+
maxBurstSize: config.rateLimit.maxBurstSize ?? 10,
|
|
141
|
+
strategy: config.rateLimit.strategy ?? 'token-bucket'
|
|
142
|
+
} : undefined,
|
|
143
|
+
payloadValidation: config?.payloadValidation
|
|
144
|
+
};
|
|
145
|
+
// SEC-INJ-001: Initialize payload validator
|
|
146
|
+
this.payloadValidator = new PayloadValidator(this.config.payloadValidation);
|
|
147
|
+
this.stats = {
|
|
148
|
+
totalPublished: 0,
|
|
149
|
+
totalDelivered: 0,
|
|
150
|
+
totalFailed: 0,
|
|
151
|
+
totalSubscriptions: 0,
|
|
152
|
+
messagesByTopic: {},
|
|
153
|
+
messagesByPriority: {},
|
|
154
|
+
avgDeliveryTime: 0
|
|
155
|
+
};
|
|
156
|
+
// SEC-DOS-002: Start rate limiter cleanup if rate limiting is enabled
|
|
157
|
+
if (this.config.rateLimit) {
|
|
158
|
+
this.startRateLimiterCleanup();
|
|
159
|
+
}
|
|
160
|
+
// SEC-MEM-001: Start subscription cleanup for TTL expiration
|
|
161
|
+
this.startSubscriptionCleanup();
|
|
162
|
+
}
|
|
163
|
+
// ============================================
|
|
164
|
+
// Pub/Sub Pattern
|
|
165
|
+
// ============================================
|
|
166
|
+
/**
|
|
167
|
+
* Subscribes to a topic with a message handler.
|
|
168
|
+
*
|
|
169
|
+
* @param config - Subscription configuration
|
|
170
|
+
* @returns Subscription handle for management
|
|
171
|
+
* @throws {MessageBrokerError} If configuration is invalid
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const sub = await broker.subscribe({
|
|
176
|
+
* topic: 'task.*',
|
|
177
|
+
* handler: async (msg) => {
|
|
178
|
+
* console.log('Received:', msg.payload);
|
|
179
|
+
* },
|
|
180
|
+
* priority: 10
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*/ async subscribe(config) {
|
|
184
|
+
if (!config.topic || typeof config.topic !== 'string') {
|
|
185
|
+
throw new MessageBrokerError('Subscription topic must be a non-empty string');
|
|
186
|
+
}
|
|
187
|
+
// SEC-007: Validate topic name to prevent injection
|
|
188
|
+
this.validateTopicName(config.topic);
|
|
189
|
+
if (typeof config.handler !== 'function') {
|
|
190
|
+
throw new MessageBrokerError('Subscription handler must be a function');
|
|
191
|
+
}
|
|
192
|
+
// SEC-MEM-001: Check global subscription limit
|
|
193
|
+
if (this.stats.totalSubscriptions >= this.config.maxSubscriptions) {
|
|
194
|
+
throw new MessageBrokerError(`Maximum subscriptions reached (max: ${this.config.maxSubscriptions})`);
|
|
195
|
+
}
|
|
196
|
+
// SEC-MEM-001: Check per-agent subscription limit
|
|
197
|
+
const subscriberId = config.subscriberId || 'anonymous';
|
|
198
|
+
const agentSubs = this.agentSubscriptions.get(subscriberId) || new Set();
|
|
199
|
+
if (agentSubs.size >= this.config.maxSubscriptionsPerAgent) {
|
|
200
|
+
throw new MessageBrokerError(`Subscriber ${subscriberId} exceeded subscription limit (max: ${this.config.maxSubscriptionsPerAgent})`);
|
|
201
|
+
}
|
|
202
|
+
// SEC-012: Check subscription authorization
|
|
203
|
+
if (this.config.authorizationProvider && config.subscriberId) {
|
|
204
|
+
const authorized = await this.config.authorizationProvider.canSubscribe(config.subscriberId, config.topic);
|
|
205
|
+
if (!authorized) {
|
|
206
|
+
throw new MessageBrokerError(`Subscriber ${config.subscriberId} not authorized to subscribe to topic ${config.topic}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const subscriptionId = `sub-${++this.subscriptionCounter}-${Date.now()}`;
|
|
210
|
+
// SEC-MEM-001: Create subscription with TTL expiration
|
|
211
|
+
const internalSub = {
|
|
212
|
+
id: subscriptionId,
|
|
213
|
+
topic: config.topic,
|
|
214
|
+
handler: config.handler,
|
|
215
|
+
priority: config.priority ?? 0,
|
|
216
|
+
createdAt: Date.now(),
|
|
217
|
+
filter: config.filter,
|
|
218
|
+
expiresAt: Date.now() + this.config.subscriptionTTL,
|
|
219
|
+
subscriberId
|
|
220
|
+
};
|
|
221
|
+
// Add to subscriptions map
|
|
222
|
+
if (!this.subscriptions.has(config.topic)) {
|
|
223
|
+
this.subscriptions.set(config.topic, []);
|
|
224
|
+
}
|
|
225
|
+
const topicSubs = this.subscriptions.get(config.topic);
|
|
226
|
+
topicSubs.push(internalSub);
|
|
227
|
+
// Sort by priority (higher priority first)
|
|
228
|
+
topicSubs.sort((a, b)=>b.priority - a.priority);
|
|
229
|
+
// SEC-MEM-001: Track subscription by agent
|
|
230
|
+
agentSubs.add(subscriptionId);
|
|
231
|
+
this.agentSubscriptions.set(subscriberId, agentSubs);
|
|
232
|
+
this.stats.totalSubscriptions++;
|
|
233
|
+
return {
|
|
234
|
+
id: subscriptionId,
|
|
235
|
+
topic: config.topic,
|
|
236
|
+
createdAt: internalSub.createdAt,
|
|
237
|
+
unsubscribe: ()=>this.unsubscribe(subscriptionId)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Unsubscribes a subscription by ID.
|
|
242
|
+
*
|
|
243
|
+
* @param subscriptionId - Subscription ID to remove
|
|
244
|
+
* @returns True if subscription was removed, false if not found
|
|
245
|
+
*/ unsubscribe(subscriptionId) {
|
|
246
|
+
for (const topic of Array.from(this.subscriptions.keys())){
|
|
247
|
+
const subs = this.subscriptions.get(topic);
|
|
248
|
+
const index = subs.findIndex((sub)=>sub.id === subscriptionId);
|
|
249
|
+
if (index !== -1) {
|
|
250
|
+
const sub = subs[index];
|
|
251
|
+
subs.splice(index, 1);
|
|
252
|
+
this.stats.totalSubscriptions--;
|
|
253
|
+
// SEC-MEM-001: Clean up agent subscription tracking
|
|
254
|
+
if (sub.subscriberId) {
|
|
255
|
+
const agentSubs = this.agentSubscriptions.get(sub.subscriberId);
|
|
256
|
+
if (agentSubs) {
|
|
257
|
+
agentSubs.delete(subscriptionId);
|
|
258
|
+
if (agentSubs.size === 0) {
|
|
259
|
+
this.agentSubscriptions.delete(sub.subscriberId);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Clean up empty topic entries
|
|
264
|
+
if (subs.length === 0) {
|
|
265
|
+
this.subscriptions.delete(topic);
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Publishes a message to a topic.
|
|
274
|
+
*
|
|
275
|
+
* @param config - Message configuration
|
|
276
|
+
* @returns Promise resolving to the published message
|
|
277
|
+
* @throws {MessageBrokerError} If message validation fails or queue is full
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* await broker.publish({
|
|
282
|
+
* topic: 'task.completed',
|
|
283
|
+
* payload: { taskId: 'task-1' },
|
|
284
|
+
* priority: MessagePriority.HIGH
|
|
285
|
+
* });
|
|
286
|
+
* ```
|
|
287
|
+
*/ async publish(config) {
|
|
288
|
+
MessageUtils.validateConfig(config);
|
|
289
|
+
// SEC-007: Validate topic name to prevent injection
|
|
290
|
+
this.validateTopicName(config.topic);
|
|
291
|
+
// SEC-INJ-001: Validate and sanitize payload BEFORE processing
|
|
292
|
+
const validationResult = this.payloadValidator.validate(config.payload);
|
|
293
|
+
if (!validationResult.valid) {
|
|
294
|
+
this.emitter.emit('validation:failed', {
|
|
295
|
+
errors: validationResult.errors,
|
|
296
|
+
senderId: config.senderId,
|
|
297
|
+
topic: config.topic
|
|
298
|
+
});
|
|
299
|
+
throw new MessageBrokerError(`Payload validation failed: ${validationResult.errors.join(', ')}`);
|
|
300
|
+
}
|
|
301
|
+
// SEC-INJ-001: Sanitize payload to prevent injection attacks
|
|
302
|
+
const sanitizedPayload = this.payloadValidator.sanitize(config.payload);
|
|
303
|
+
const senderId = config.senderId || 'anonymous';
|
|
304
|
+
// SEC-DOS-002: Rate limiting - Check FIRST before any processing
|
|
305
|
+
if (this.config.rateLimit) {
|
|
306
|
+
const allowed = await this.checkRateLimit(senderId);
|
|
307
|
+
if (!allowed) {
|
|
308
|
+
this.emitter.emit('rate-limit:exceeded', {
|
|
309
|
+
senderId,
|
|
310
|
+
topic: config.topic
|
|
311
|
+
});
|
|
312
|
+
throw new MessageBrokerError(`Rate limit exceeded for sender ${senderId}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// SEC-DOS-001: Per-sender queue limit - Check BEFORE global limit
|
|
316
|
+
// CRITICAL: Read and increment atomically to prevent race condition
|
|
317
|
+
const currentSenderSize = this.senderQueueSizes.get(senderId) || 0;
|
|
318
|
+
// Debug logging for per-sender queue tracking
|
|
319
|
+
if (process.env.DEBUG_MESSAGE_BROKER) {
|
|
320
|
+
console.log(`[MessageBroker PUBLISH] Sender ${senderId}: ${currentSenderSize} messages (limit: ${this.config.maxQueueSizePerSender}), queue processing: ${this.processing}`);
|
|
321
|
+
}
|
|
322
|
+
if (currentSenderSize >= this.config.maxQueueSizePerSender) {
|
|
323
|
+
throw new MessageBrokerError(`Sender ${senderId} exceeded queue limit (${currentSenderSize}/${this.config.maxQueueSizePerSender})`);
|
|
324
|
+
}
|
|
325
|
+
// SEC-DOS-001: Increment IMMEDIATELY after check to reserve slot (before async processing)
|
|
326
|
+
this.senderQueueSizes.set(senderId, currentSenderSize + 1);
|
|
327
|
+
if (process.env.DEBUG_MESSAGE_BROKER) {
|
|
328
|
+
console.log(`[MessageBroker PUBLISH] After increment: ${this.senderQueueSizes.get(senderId)}`);
|
|
329
|
+
}
|
|
330
|
+
// SEC-DOS-001: Global queue limit with priority eviction
|
|
331
|
+
if (this.messageQueue.length >= this.config.maxQueueSize) {
|
|
332
|
+
// If queue is at priority eviction threshold, evict low-priority messages
|
|
333
|
+
const evictionThreshold = Math.floor(this.config.maxQueueSize * this.config.priorityEvictionThreshold);
|
|
334
|
+
if (this.messageQueue.length >= evictionThreshold) {
|
|
335
|
+
const messagePriority = MessageUtils.normalizePriority(config.priority);
|
|
336
|
+
const evicted = this.evictLowPriorityMessages(messagePriority);
|
|
337
|
+
if (evicted.length === 0) {
|
|
338
|
+
throw new MessageBrokerError(`Message queue full (max: ${this.config.maxQueueSize}, no evictable messages)`);
|
|
339
|
+
}
|
|
340
|
+
this.emitter.emit('queue:high-pressure', {
|
|
341
|
+
queueSize: this.messageQueue.length,
|
|
342
|
+
evicted: evicted.length,
|
|
343
|
+
evictedMessages: evicted,
|
|
344
|
+
threshold: evictionThreshold
|
|
345
|
+
});
|
|
346
|
+
} else {
|
|
347
|
+
throw new MessageBrokerError(`Message queue full (max: ${this.config.maxQueueSize})`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// SEC-INJ-001: Use sanitized payload in message
|
|
351
|
+
const message = {
|
|
352
|
+
id: MessageUtils.generateMessageId(),
|
|
353
|
+
topic: config.topic,
|
|
354
|
+
payload: sanitizedPayload,
|
|
355
|
+
priority: MessageUtils.normalizePriority(config.priority),
|
|
356
|
+
timestamp: Date.now(),
|
|
357
|
+
replyTo: config.replyTo,
|
|
358
|
+
correlationId: config.correlationId,
|
|
359
|
+
senderId: config.senderId,
|
|
360
|
+
recipientId: config.recipientId,
|
|
361
|
+
deliverySemantics: config.deliverySemantics ?? DeliverySemantics.AT_LEAST_ONCE,
|
|
362
|
+
metadata: config.metadata
|
|
363
|
+
};
|
|
364
|
+
this.messageQueue.enqueue(message);
|
|
365
|
+
// SEC-DOS-001: Sender queue size already incremented at line 517 (atomic check-and-increment)
|
|
366
|
+
if (this.config.enableStats) {
|
|
367
|
+
this.stats.totalPublished++;
|
|
368
|
+
this.stats.messagesByTopic[message.topic] = (this.stats.messagesByTopic[message.topic] || 0) + 1;
|
|
369
|
+
this.stats.messagesByPriority[message.priority] = (this.stats.messagesByPriority[message.priority] || 0) + 1;
|
|
370
|
+
}
|
|
371
|
+
// Start processing if not already processing
|
|
372
|
+
this.processQueue().catch((error)=>{
|
|
373
|
+
console.error('Message queue processing error:', error);
|
|
374
|
+
});
|
|
375
|
+
return message;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Broadcasts a message to all subscribers matching the topic pattern.
|
|
379
|
+
*
|
|
380
|
+
* @param config - Message configuration
|
|
381
|
+
* @returns Promise resolving to array of delivery results
|
|
382
|
+
*/ async broadcast(config) {
|
|
383
|
+
await this.publish({
|
|
384
|
+
...config,
|
|
385
|
+
metadata: {
|
|
386
|
+
...config.metadata,
|
|
387
|
+
broadcast: true
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
// ============================================
|
|
392
|
+
// Request/Reply Pattern
|
|
393
|
+
// ============================================
|
|
394
|
+
/**
|
|
395
|
+
* Sends a request message and waits for a reply.
|
|
396
|
+
*
|
|
397
|
+
* @param topic - Request topic
|
|
398
|
+
* @param payload - Request payload
|
|
399
|
+
* @param options - Request options (timeout, correlation ID)
|
|
400
|
+
* @returns Promise resolving to reply message
|
|
401
|
+
* @throws {MessageBrokerError} If request times out or fails
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```typescript
|
|
405
|
+
* const reply = await broker.request('task.execute', { taskId: 'task-1' }, {
|
|
406
|
+
* timeout: 5000
|
|
407
|
+
* });
|
|
408
|
+
* console.log('Reply:', reply.payload);
|
|
409
|
+
* ```
|
|
410
|
+
*/ async request(topic, payload, options) {
|
|
411
|
+
const correlationId = options?.correlationId ?? MessageUtils.generateCorrelationId();
|
|
412
|
+
const timeout = options?.timeout ?? this.config.defaultRequestTimeout;
|
|
413
|
+
const priority = options?.priority ?? MessagePriority.NORMAL;
|
|
414
|
+
const replyTopic = `${topic}.reply.${correlationId}`;
|
|
415
|
+
return new Promise((resolve, reject)=>{
|
|
416
|
+
// Set timeout
|
|
417
|
+
const timeoutId = setTimeout(()=>{
|
|
418
|
+
this.pendingRequests.delete(correlationId);
|
|
419
|
+
reject(new MessageBrokerError(`Request timeout after ${timeout}ms (correlationId: ${correlationId})`));
|
|
420
|
+
}, timeout);
|
|
421
|
+
// Store pending request
|
|
422
|
+
this.pendingRequests.set(correlationId, {
|
|
423
|
+
correlationId,
|
|
424
|
+
resolve: resolve,
|
|
425
|
+
reject,
|
|
426
|
+
timeoutId,
|
|
427
|
+
createdAt: Date.now(),
|
|
428
|
+
expectedSender: options?.senderId,
|
|
429
|
+
resolved: false
|
|
430
|
+
});
|
|
431
|
+
// Publish request message
|
|
432
|
+
this.publish({
|
|
433
|
+
topic,
|
|
434
|
+
payload,
|
|
435
|
+
priority,
|
|
436
|
+
replyTo: replyTopic,
|
|
437
|
+
correlationId
|
|
438
|
+
}).catch((error)=>{
|
|
439
|
+
clearTimeout(timeoutId);
|
|
440
|
+
this.pendingRequests.delete(correlationId);
|
|
441
|
+
reject(error);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Sends a reply to a request message.
|
|
447
|
+
*
|
|
448
|
+
* @param request - Original request message
|
|
449
|
+
* @param payload - Reply payload
|
|
450
|
+
* @param success - Success status (default: true)
|
|
451
|
+
* @param error - Error message if failed
|
|
452
|
+
* @returns Promise resolving when reply is sent
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```typescript
|
|
456
|
+
* await broker.subscribe({
|
|
457
|
+
* topic: 'task.execute',
|
|
458
|
+
* handler: async (msg) => {
|
|
459
|
+
* const result = await executeTask(msg.payload);
|
|
460
|
+
* await broker.reply(msg, result);
|
|
461
|
+
* }
|
|
462
|
+
* });
|
|
463
|
+
* ```
|
|
464
|
+
*/ async reply(request, payload, success = true, error) {
|
|
465
|
+
if (!request.replyTo) {
|
|
466
|
+
throw new MessageBrokerError('Cannot reply: request has no replyTo topic');
|
|
467
|
+
}
|
|
468
|
+
if (!request.correlationId) {
|
|
469
|
+
throw new MessageBrokerError('Cannot reply: request has no correlationId');
|
|
470
|
+
}
|
|
471
|
+
const reply = MessageUtils.createReply(request, payload, success, error);
|
|
472
|
+
await this.publish({
|
|
473
|
+
topic: request.replyTo,
|
|
474
|
+
payload: reply,
|
|
475
|
+
priority: request.priority,
|
|
476
|
+
correlationId: request.correlationId,
|
|
477
|
+
senderId: request.recipientId // SEC-006: Propagate responder ID for validation
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
// ============================================
|
|
481
|
+
// Message Processing
|
|
482
|
+
// ============================================
|
|
483
|
+
/**
|
|
484
|
+
* Processes the message queue (internal).
|
|
485
|
+
* Handles priority ordering and at-least-once delivery.
|
|
486
|
+
*/ async processQueue() {
|
|
487
|
+
if (this.processing) {
|
|
488
|
+
return; // Already processing
|
|
489
|
+
}
|
|
490
|
+
this.processing = true;
|
|
491
|
+
try {
|
|
492
|
+
while(this.messageQueue.length > 0){
|
|
493
|
+
// SEC-DOS-001-RACE: Peek at message first to check for matching handlers
|
|
494
|
+
const message = this.messageQueue.peek();
|
|
495
|
+
if (!message) break;
|
|
496
|
+
// Check if there are any matching handlers BEFORE dequeuing
|
|
497
|
+
const hasHandlers = this.hasMatchingHandlers(message);
|
|
498
|
+
// SEC-DOS-001-RACE: Only dequeue if handlers exist to prevent race condition
|
|
499
|
+
if (!hasHandlers) {
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
// Now dequeue the message
|
|
503
|
+
this.messageQueue.dequeue();
|
|
504
|
+
// SEC-DOS-001: Deliver first, then decrement (counter represents "in-flight" messages)
|
|
505
|
+
await this.deliverMessage(message);
|
|
506
|
+
// SEC-DOS-001: Decrement AFTER successful delivery (message no longer in-flight)
|
|
507
|
+
const senderId = message.senderId || 'anonymous';
|
|
508
|
+
const senderSize = this.senderQueueSizes.get(senderId) || 0;
|
|
509
|
+
if (senderSize > 0) {
|
|
510
|
+
this.senderQueueSizes.set(senderId, senderSize - 1);
|
|
511
|
+
if (process.env.DEBUG_MESSAGE_BROKER) {
|
|
512
|
+
console.log(`[MessageBroker DELIVER] Decremented ${senderId}: ${senderSize} -> ${this.senderQueueSizes.get(senderId)}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} finally{
|
|
517
|
+
this.processing = false;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Checks if there are any matching handlers for a message (without delivering).
|
|
522
|
+
* Used to prevent dequeuing messages when no subscribers exist.
|
|
523
|
+
*/ hasMatchingHandlers(message) {
|
|
524
|
+
// Check for pending request/reply handlers
|
|
525
|
+
if (message.correlationId && this.pendingRequests.has(message.correlationId)) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
// Check for topic subscribers
|
|
529
|
+
for (const topicPattern of Array.from(this.subscriptions.keys())){
|
|
530
|
+
if (MessageUtils.matchesTopic(message.topic, topicPattern)) {
|
|
531
|
+
const subs = this.subscriptions.get(topicPattern);
|
|
532
|
+
if (subs.length > 0) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Delivers a message to all matching subscribers.
|
|
541
|
+
* Returns true if message was delivered to any handler, false otherwise.
|
|
542
|
+
*/ async deliverMessage(message) {
|
|
543
|
+
const startTime = Date.now();
|
|
544
|
+
const matchingHandlers = [];
|
|
545
|
+
// SEC-MEM-001: Clean up expired subscriptions before delivery
|
|
546
|
+
this.cleanupExpiredSubscriptions();
|
|
547
|
+
// Find matching subscriptions
|
|
548
|
+
for (const topicPattern of Array.from(this.subscriptions.keys())){
|
|
549
|
+
const subs = this.subscriptions.get(topicPattern);
|
|
550
|
+
if (MessageUtils.matchesTopic(message.topic, topicPattern)) {
|
|
551
|
+
matchingHandlers.push(...subs);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// Handle reply messages (special case)
|
|
555
|
+
if (message.correlationId && this.pendingRequests.has(message.correlationId)) {
|
|
556
|
+
const pendingRequest = this.pendingRequests.get(message.correlationId);
|
|
557
|
+
// SEC-006: Validate sender authorization for reply
|
|
558
|
+
if (pendingRequest.expectedSender && message.senderId !== pendingRequest.expectedSender) {
|
|
559
|
+
console.warn(`[MessageBroker] Unauthorized reply sender: expected ${pendingRequest.expectedSender}, got ${message.senderId}`);
|
|
560
|
+
return; // Drop unauthorized reply
|
|
561
|
+
}
|
|
562
|
+
// SEC-006: Check duplicate reply detection
|
|
563
|
+
if (pendingRequest.resolved) {
|
|
564
|
+
console.warn(`[MessageBroker] Duplicate reply detected for correlationId ${message.correlationId}`);
|
|
565
|
+
return; // Drop duplicate reply
|
|
566
|
+
}
|
|
567
|
+
// Mark resolved and cleanup
|
|
568
|
+
pendingRequest.resolved = true;
|
|
569
|
+
clearTimeout(pendingRequest.timeoutId);
|
|
570
|
+
this.pendingRequests.delete(message.correlationId);
|
|
571
|
+
pendingRequest.resolve(message.payload);
|
|
572
|
+
}
|
|
573
|
+
// Sort handlers by priority
|
|
574
|
+
matchingHandlers.sort((a, b)=>b.priority - a.priority);
|
|
575
|
+
// Deliver to handlers
|
|
576
|
+
for (const sub of matchingHandlers){
|
|
577
|
+
try {
|
|
578
|
+
// Apply filter if present
|
|
579
|
+
if (sub.filter && !sub.filter(message)) {
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
await sub.handler(message);
|
|
583
|
+
if (this.config.enableStats) {
|
|
584
|
+
this.stats.totalDelivered++;
|
|
585
|
+
}
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (this.config.enableStats) {
|
|
588
|
+
this.stats.totalFailed++;
|
|
589
|
+
}
|
|
590
|
+
// Emit error event but don't throw (at-least-once delivery)
|
|
591
|
+
this.emitter.emit('error', {
|
|
592
|
+
error,
|
|
593
|
+
message,
|
|
594
|
+
subscription: sub
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// SEC-DOS-001: Sender queue size already decremented in processQueue() after dequeue (line 729-734)
|
|
599
|
+
// Update delivery time statistics
|
|
600
|
+
if (this.config.enableStats && matchingHandlers.length > 0) {
|
|
601
|
+
const deliveryTime = Date.now() - startTime;
|
|
602
|
+
this.stats.avgDeliveryTime = (this.stats.avgDeliveryTime * (this.stats.totalDelivered - 1) + deliveryTime) / this.stats.totalDelivered;
|
|
603
|
+
}
|
|
604
|
+
// Return true if message was delivered to any handler
|
|
605
|
+
return matchingHandlers.length > 0;
|
|
606
|
+
}
|
|
607
|
+
// ============================================
|
|
608
|
+
// Statistics and Monitoring
|
|
609
|
+
// ============================================
|
|
610
|
+
/**
|
|
611
|
+
* Gets current message broker statistics.
|
|
612
|
+
*
|
|
613
|
+
* @returns Statistics object
|
|
614
|
+
*/ getStats() {
|
|
615
|
+
return {
|
|
616
|
+
...this.stats,
|
|
617
|
+
messagesByTopic: {
|
|
618
|
+
...this.stats.messagesByTopic
|
|
619
|
+
},
|
|
620
|
+
messagesByPriority: {
|
|
621
|
+
...this.stats.messagesByPriority
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Resets all statistics counters.
|
|
627
|
+
*/ resetStats() {
|
|
628
|
+
this.stats = {
|
|
629
|
+
totalPublished: 0,
|
|
630
|
+
totalDelivered: 0,
|
|
631
|
+
totalFailed: 0,
|
|
632
|
+
totalSubscriptions: this.stats.totalSubscriptions,
|
|
633
|
+
messagesByTopic: {},
|
|
634
|
+
messagesByPriority: {},
|
|
635
|
+
avgDeliveryTime: 0
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Gets all active subscriptions.
|
|
640
|
+
*
|
|
641
|
+
* @returns Array of subscription information
|
|
642
|
+
*/ getSubscriptions() {
|
|
643
|
+
const result = [];
|
|
644
|
+
for (const subs of Array.from(this.subscriptions.values())){
|
|
645
|
+
for (const sub of subs){
|
|
646
|
+
result.push({
|
|
647
|
+
id: sub.id,
|
|
648
|
+
topic: sub.topic,
|
|
649
|
+
priority: sub.priority,
|
|
650
|
+
createdAt: sub.createdAt
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Gets the current queue size.
|
|
658
|
+
*
|
|
659
|
+
* @returns Number of messages in queue
|
|
660
|
+
*/ getQueueSize() {
|
|
661
|
+
return this.messageQueue.length;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Gets the number of pending requests.
|
|
665
|
+
*
|
|
666
|
+
* @returns Number of pending request/reply operations
|
|
667
|
+
*/ getPendingRequestCount() {
|
|
668
|
+
return this.pendingRequests.size;
|
|
669
|
+
}
|
|
670
|
+
// ============================================
|
|
671
|
+
// Security Validation
|
|
672
|
+
// ============================================
|
|
673
|
+
/**
|
|
674
|
+
* Validates topic name to prevent injection attacks (SEC-007).
|
|
675
|
+
*
|
|
676
|
+
* @param topic - Topic name to validate
|
|
677
|
+
* @throws {MessageBrokerError} If topic name is invalid
|
|
678
|
+
*/ validateTopicName(topic) {
|
|
679
|
+
// SEC-007: Prevent path traversal FIRST (before pattern check)
|
|
680
|
+
if (topic.includes('..') || topic.includes('//')) {
|
|
681
|
+
throw new MessageBrokerError('Path traversal detected in topic name');
|
|
682
|
+
}
|
|
683
|
+
// SEC-007: Prevent excessively long topics
|
|
684
|
+
if (topic.length > 256) {
|
|
685
|
+
throw new MessageBrokerError('Topic name exceeds 256 characters');
|
|
686
|
+
}
|
|
687
|
+
// SEC-007: Validate topic pattern (alphanumeric, dots, dashes, underscores, wildcards)
|
|
688
|
+
const validTopicPattern = /^[a-zA-Z0-9._-]+(\.[a-zA-Z0-9._*-]+)*$/;
|
|
689
|
+
if (!validTopicPattern.test(topic)) {
|
|
690
|
+
throw new MessageBrokerError(`Invalid topic name: ${topic}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
// ============================================
|
|
694
|
+
// SEC-DOS-001 & SEC-DOS-002: DoS Protection Methods
|
|
695
|
+
// ============================================
|
|
696
|
+
/**
|
|
697
|
+
* SEC-DOS-002: Checks rate limit for a sender using token bucket algorithm.
|
|
698
|
+
*
|
|
699
|
+
* @param senderId - Sender identifier
|
|
700
|
+
* @returns True if request is allowed, false if rate limit exceeded
|
|
701
|
+
*/ async checkRateLimit(senderId) {
|
|
702
|
+
if (!this.config.rateLimit) {
|
|
703
|
+
return true; // Rate limiting disabled
|
|
704
|
+
}
|
|
705
|
+
let limiter = this.rateLimiters.get(senderId);
|
|
706
|
+
if (!limiter) {
|
|
707
|
+
// Create new rate limiter for this sender
|
|
708
|
+
limiter = new RateLimiter({
|
|
709
|
+
tokensPerInterval: this.config.rateLimit.maxMessagesPerSecond,
|
|
710
|
+
interval: 'second',
|
|
711
|
+
fireImmediately: true
|
|
712
|
+
});
|
|
713
|
+
this.rateLimiters.set(senderId, limiter);
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
const remainingTokens = await limiter.removeTokens(1);
|
|
717
|
+
return remainingTokens >= 0;
|
|
718
|
+
} catch (error) {
|
|
719
|
+
// Rate limit exceeded
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* SEC-DOS-002: Periodically cleanup idle rate limiters to prevent memory leaks.
|
|
725
|
+
*/ startRateLimiterCleanup() {
|
|
726
|
+
this.rateLimiterCleanupInterval = setInterval(()=>{
|
|
727
|
+
// Remove rate limiters that haven't been used recently
|
|
728
|
+
// In production, you would track last access time
|
|
729
|
+
// For now, we clear limiters periodically to prevent unbounded growth
|
|
730
|
+
if (this.rateLimiters.size > 1000) {
|
|
731
|
+
// Keep only the most recent 500 limiters (simple cleanup strategy)
|
|
732
|
+
const entries = Array.from(this.rateLimiters.entries());
|
|
733
|
+
const toKeep = entries.slice(-500);
|
|
734
|
+
this.rateLimiters.clear();
|
|
735
|
+
toKeep.forEach(([key, value])=>this.rateLimiters.set(key, value));
|
|
736
|
+
}
|
|
737
|
+
}, 60000); // Cleanup every 60 seconds
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* SEC-DOS-001: Evicts low-priority messages when queue is full.
|
|
741
|
+
* Only evicts enough messages to make room for 1 new message.
|
|
742
|
+
*
|
|
743
|
+
* @param incomingPriority - Priority of the incoming message
|
|
744
|
+
* @returns Array of evicted messages
|
|
745
|
+
*/ evictLowPriorityMessages(incomingPriority) {
|
|
746
|
+
const evicted = [];
|
|
747
|
+
// SEC-DOS-001-RACE: Only evict 1 message to make room for the incoming message
|
|
748
|
+
// Don't evict ALL lower-priority messages
|
|
749
|
+
const items = this.messageQueue.items;
|
|
750
|
+
if (!items || items.length === 0) {
|
|
751
|
+
return evicted;
|
|
752
|
+
}
|
|
753
|
+
// Find lowest priority message
|
|
754
|
+
let lowestIndex = 0;
|
|
755
|
+
let lowestPriority = items[0].priority;
|
|
756
|
+
for(let i = 1; i < items.length; i++){
|
|
757
|
+
if (items[i].priority < lowestPriority) {
|
|
758
|
+
lowestPriority = items[i].priority;
|
|
759
|
+
lowestIndex = i;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// Only evict if priority is lower than incoming
|
|
763
|
+
if (lowestPriority < incomingPriority) {
|
|
764
|
+
const candidate = items[lowestIndex];
|
|
765
|
+
items.splice(lowestIndex, 1);
|
|
766
|
+
// Move to dead letter queue if enabled
|
|
767
|
+
if (this.config.enableDeadLetterQueue) {
|
|
768
|
+
this.deadLetterQueue.enqueue(candidate);
|
|
769
|
+
}
|
|
770
|
+
// Decrement sender queue size
|
|
771
|
+
const senderId = candidate.senderId || 'anonymous';
|
|
772
|
+
const senderSize = this.senderQueueSizes.get(senderId) || 0;
|
|
773
|
+
if (senderSize > 0) {
|
|
774
|
+
this.senderQueueSizes.set(senderId, senderSize - 1);
|
|
775
|
+
}
|
|
776
|
+
evicted.push(candidate);
|
|
777
|
+
}
|
|
778
|
+
return evicted;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Gets the current size of the dead letter queue.
|
|
782
|
+
*
|
|
783
|
+
* @returns Number of messages in dead letter queue
|
|
784
|
+
*/ getDeadLetterQueueSize() {
|
|
785
|
+
return this.deadLetterQueue.length;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* SEC-DOS-002: Returns the number of active rate limiters.
|
|
789
|
+
*/ getRateLimiterCount() {
|
|
790
|
+
return this.rateLimiters.size;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Retrieves messages from the dead letter queue.
|
|
794
|
+
*
|
|
795
|
+
* @param count - Number of messages to retrieve (default: all)
|
|
796
|
+
* @returns Array of dead letter messages
|
|
797
|
+
*/ getDeadLetterMessages(count) {
|
|
798
|
+
const items = this.deadLetterQueue.items;
|
|
799
|
+
if (!items) return [];
|
|
800
|
+
if (count === undefined) {
|
|
801
|
+
return [
|
|
802
|
+
...items
|
|
803
|
+
];
|
|
804
|
+
}
|
|
805
|
+
return items.slice(0, count);
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Clears the dead letter queue.
|
|
809
|
+
*/ clearDeadLetterQueue() {
|
|
810
|
+
this.deadLetterQueue.clear();
|
|
811
|
+
}
|
|
812
|
+
// ============================================
|
|
813
|
+
// SEC-MEM-001: Subscription Cleanup
|
|
814
|
+
// ============================================
|
|
815
|
+
/**
|
|
816
|
+
* Starts periodic subscription cleanup to expire old subscriptions (SEC-MEM-001).
|
|
817
|
+
* Runs periodically to remove expired subscriptions based on TTL.
|
|
818
|
+
*/ startSubscriptionCleanup() {
|
|
819
|
+
const cleanupInterval = this.config.cleanupIntervalMs ?? 60000;
|
|
820
|
+
this.subscriptionCleanupInterval = setInterval(()=>{
|
|
821
|
+
this.cleanupExpiredSubscriptions();
|
|
822
|
+
}, cleanupInterval);
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Synchronously removes expired subscriptions (SEC-MEM-001).
|
|
826
|
+
* Called by background timer and immediately before publishing.
|
|
827
|
+
*
|
|
828
|
+
* @returns Number of subscriptions removed
|
|
829
|
+
*/ cleanupExpiredSubscriptions() {
|
|
830
|
+
const now = Date.now();
|
|
831
|
+
let expiredCount = 0;
|
|
832
|
+
for (const topic of Array.from(this.subscriptions.keys())){
|
|
833
|
+
const subs = this.subscriptions.get(topic);
|
|
834
|
+
const validSubs = subs.filter((sub)=>{
|
|
835
|
+
if (sub.expiresAt && now >= sub.expiresAt) {
|
|
836
|
+
// Expired subscription
|
|
837
|
+
expiredCount++;
|
|
838
|
+
// Clean up agent tracking
|
|
839
|
+
if (sub.subscriberId) {
|
|
840
|
+
const agentSubs = this.agentSubscriptions.get(sub.subscriberId);
|
|
841
|
+
if (agentSubs) {
|
|
842
|
+
agentSubs.delete(sub.id);
|
|
843
|
+
if (agentSubs.size === 0) {
|
|
844
|
+
this.agentSubscriptions.delete(sub.subscriberId);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
this.stats.totalSubscriptions--;
|
|
849
|
+
return false; // Remove expired subscription
|
|
850
|
+
}
|
|
851
|
+
return true; // Keep valid subscription
|
|
852
|
+
});
|
|
853
|
+
if (validSubs.length === 0) {
|
|
854
|
+
this.subscriptions.delete(topic);
|
|
855
|
+
} else if (validSubs.length !== subs.length) {
|
|
856
|
+
this.subscriptions.set(topic, validSubs);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (expiredCount > 0) {
|
|
860
|
+
this.emitter.emit('subscriptions:expired', {
|
|
861
|
+
count: expiredCount
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return expiredCount;
|
|
865
|
+
}
|
|
866
|
+
// ============================================
|
|
867
|
+
// Lifecycle Management
|
|
868
|
+
// ============================================
|
|
869
|
+
/**
|
|
870
|
+
* Clears all subscriptions and pending messages.
|
|
871
|
+
*/ clear() {
|
|
872
|
+
this.subscriptions.clear();
|
|
873
|
+
this.messageQueue.clear();
|
|
874
|
+
// Cancel all pending requests
|
|
875
|
+
for (const correlationId of Array.from(this.pendingRequests.keys())){
|
|
876
|
+
const pending = this.pendingRequests.get(correlationId);
|
|
877
|
+
clearTimeout(pending.timeoutId);
|
|
878
|
+
pending.reject(new MessageBrokerError('Broker cleared'));
|
|
879
|
+
}
|
|
880
|
+
this.pendingRequests.clear();
|
|
881
|
+
// SEC-MEM-001: Clear subscription tracking
|
|
882
|
+
this.agentSubscriptions.clear();
|
|
883
|
+
// SEC-DOS-002: Clear rate limiters
|
|
884
|
+
this.rateLimiters.clear();
|
|
885
|
+
if (this.rateLimiterCleanupInterval) {
|
|
886
|
+
clearInterval(this.rateLimiterCleanupInterval);
|
|
887
|
+
this.rateLimiterCleanupInterval = undefined;
|
|
888
|
+
}
|
|
889
|
+
// SEC-MEM-001: Clear subscription cleanup interval
|
|
890
|
+
if (this.subscriptionCleanupInterval) {
|
|
891
|
+
clearInterval(this.subscriptionCleanupInterval);
|
|
892
|
+
this.subscriptionCleanupInterval = undefined;
|
|
893
|
+
}
|
|
894
|
+
this.stats.totalSubscriptions = 0;
|
|
895
|
+
this.emitter.removeAllListeners();
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Shuts down the message broker gracefully.
|
|
899
|
+
* Waits for pending messages to be processed.
|
|
900
|
+
*
|
|
901
|
+
* @param timeout - Maximum wait time in milliseconds (default: 5000)
|
|
902
|
+
* @returns Promise resolving when shutdown complete
|
|
903
|
+
*/ async shutdown(timeout = 5000) {
|
|
904
|
+
const startTime = Date.now();
|
|
905
|
+
// Wait for queue to drain
|
|
906
|
+
while(this.messageQueue.length > 0 && Date.now() - startTime < timeout){
|
|
907
|
+
await new Promise((resolve)=>setTimeout(resolve, 100));
|
|
908
|
+
}
|
|
909
|
+
this.clear();
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Registers an error handler for message processing errors.
|
|
913
|
+
*
|
|
914
|
+
* @param handler - Error handler function
|
|
915
|
+
*/ onError(handler) {
|
|
916
|
+
this.emitter.on('error', handler);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
//# sourceMappingURL=message-broker.js.map
|