tests — proactive

Module: tests-proactive Cohesion: 0.80 Members: 0

tests — proactive

This document describes the proactive module, which is responsible for managing and orchestrating proactive communications from the system to external channels and users. It encompasses components for managing notification rules and history, as well as an agent for sending messages, asking questions, and handling responses.

1. Introduction

The proactive module provides the core functionality for the system to initiate communication with users or external systems. This includes sending informational messages, asking for user input, and notifying about task completions. It is designed to be flexible, allowing for various communication channels and custom delivery mechanisms, while also enforcing rules around notification frequency and user preferences.

The module is composed of two primary components:

2. NotificationManager

The NotificationManager class is responsible for enforcing policies and tracking the history of proactive notifications. It ensures that messages are sent only when permitted by configured channels, rate limits, and quiet hours.

2.1. Purpose

Its primary purpose is to:

2.2. Key Features

2.3. API Overview

Constructor

new NotificationManager(options: { channels: string[]; maxPerHour: number; })

Initializes the manager with a list of enabled channels and a global rate limit.

shouldSend

shouldSend(notification: { channelType: string; channelId: string; message: string; priority: 'low' | 'normal' | 'high' | 'urgent'; }): { allowed: boolean; reason?: string; }

Evaluates whether a given notification is allowed to be sent based on:

Returns an object indicating allowed status and a reason if blocked.

record

record(notification: { channelType: string; channelId: string; message: string; priority: 'low' | 'normal' | 'high' | 'urgent'; }, delivered: boolean): void

Records a notification attempt. This method should be called after an attempt to send a message, indicating whether it was delivered successfully. This data is used for rate limiting, history, and statistics.

getStats

getStats(): { totalSent: number; deliveryRate: number; }

Returns aggregated statistics about notification delivery, including the total number of recorded attempts and the success rate.

getHistory

getHistory(limit: number): Array<{ timestamp: Date; channelType: string; channelId: string; message: string; priority: string; delivered: boolean; }>

Retrieves a list of recent notification records, up to the specified limit.

setPreferences / getPreferences

setPreferences(prefs: { quietHoursStart?: number; quietHoursEnd?: number; quietHoursMinPriority?: &#39;low&#39; | &#39;normal&#39; | &#39;high&#39; | &#39;urgent&#39;; maxPerHour?: number; }): void
getPreferences(): { quietHoursStart: number; quietHoursEnd: number; quietHoursMinPriority: &#39;low&#39; | &#39;normal&#39; | &#39;high&#39; | &#39;urgent&#39;; maxPerHour: number; }

Allows updating and retrieving the notification preferences at runtime. This includes quiet hour settings and the global rate limit.

3. ProactiveAgent

The ProactiveAgent class serves as the primary interface for the system to initiate proactive communications. It handles sending various types of messages, managing interactive question-answer flows, and notifying about task completions.

3.1. Purpose

Its main responsibilities include:

3.2. Key Features

3.3. API Overview

Constructor

new ProactiveAgent()

Initializes the agent. It extends EventEmitter, allowing for event subscriptions.

sendMessage

sendMessage(msg: { channelType: string; channelId: string; message: string; priority: &#39;low&#39; | &#39;normal&#39; | &#39;high&#39; | &#39;urgent&#39;; }): Promise<{ delivered: boolean; channelType: string; channelId: string; messageId?: string; timestamp?: Date; error?: string; }>

Sends a message to the specified channelType and channelId.

Returns a promise that resolves with the delivery status, including delivered, messageId (if successful), and error (if failed).

setSendMethod

setSendMethod(method: (msg: { channelType: string; channelId: string; message: string; priority: &#39;low&#39; | &#39;normal&#39; | &#39;high&#39; | &#39;urgent&#39;; }) => Promise<{ delivered: boolean; channelType: string; channelId: string; messageId?: string; timestamp?: Date; }>): void

Allows injecting a custom asynchronous function to handle the actual delivery of messages. This is the primary extension point for integrating with external messaging platforms (e.g., Telegram API, Discord webhooks).

askQuestion

askQuestion(question: string, options: string[], channelType: string, channelId: string, timeoutMs: number): Promise<{ answered: boolean; response?: string; timedOut: boolean; }>

Sends a question with a list of options to a user on a specific channel.

Returns a promise that resolves when a response is received or the timeout occurs.

receiveResponse

receiveResponse(questionId: string, response: string): void

Used to feed a user's response back to a pending question identified by questionId. This method is typically called by an external integration after receiving user input.

getPendingQuestions

getPendingQuestions(): number

Returns the current count of questions that have been asked and are awaiting a response.

notifyCompletion

notifyCompletion(taskId: string, result: { success: boolean; output: string; }, channelType: string, channelId: string): Promise<{ delivered: boolean; channelType: string; channelId: string; messageId?: string; timestamp?: Date; error?: string; }>

Sends a notification about the completion of a specific taskId, including its success status and output. This leverages the same underlying message sending mechanism as sendMessage.

3.4. Question-Answering Flow

The interactive question-answering process involves several steps, often spanning internal agent logic and external communication channels.

sequenceDiagram
    participant Caller
    participant ProactiveAgent
    participant InternalTimer
    participant ExternalChannel

    Caller->>ProactiveAgent: askQuestion(question, options, channel, timeout)
    ProactiveAgent->>ProactiveAgent: Generate questionId
    ProactiveAgent->>ProactiveAgent: Emit 'question:asked' event
    ProactiveAgent->>ExternalChannel: Send question message (via sendMethod)
    ProactiveAgent->>InternalTimer: Set timeout for questionId
    ProactiveAgent-->>Caller: (Promise pending)
    Note over ProactiveAgent,ExternalChannel: User sees question, might respond

    alt User responds within timeout
        ExternalChannel->>ProactiveAgent: receiveResponse(questionId, answer)
        ProactiveAgent->>InternalTimer: Clear timeout for questionId
        ProactiveAgent-->>Caller: Resolve askQuestion Promise with { answered: true, response: answer }
    else Timeout occurs
        InternalTimer->>ProactiveAgent: Timeout for questionId
        ProactiveAgent-->>Caller: Resolve askQuestion Promise with { answered: false, timedOut: true }
    end

3.5. Eventing

ProactiveAgent extends EventEmitter and emits the following events:

4. Integration Considerations

While the provided tests do not explicitly show ProactiveAgent directly using NotificationManager, in a production environment, it is highly recommended that the ProactiveAgent consults the NotificationManager before attempting to send any message.

A typical integration flow would be:

  1. ProactiveAgent.sendMessage (or askQuestion, notifyCompletion) is called.
  2. Inside ProactiveAgent, before attempting delivery, call NotificationManager.shouldSend() with the message details.
  3. If shouldSend() returns allowed: false, the ProactiveAgent should log the reason and return a delivered: false result without attempting actual delivery.
  4. If shouldSend() returns allowed: true, the ProactiveAgent proceeds with its sendMethod.
  5. After the sendMethod completes (successfully or with an error), the ProactiveAgent should call NotificationManager.record() with the message details and the actual delivery status.

This ensures that all proactive communications adhere to the configured rules and preferences, and their history is properly tracked.

5. Contributing and Extending

Developers looking to contribute to or extend the proactive communication capabilities should focus on: