/** * WorkflowProgressBroker — the single seam that turns mid-run `workflow` * snapshots into IM messages (Telegram today, more channels later). * * Architecture: * * pi-agent ──tool_execution_update──▶ AgentEventHandler / SessionEventBus * │ * attachTo(handler) │ * ▼ * WorkflowProgressBroker * │ * per-(sessionKey, toolCallId) state * │ * ┌───────────────────┼───────────────────┐ * ▼ ▼ ▼ * Telegram cap Feishu cap WeChat cap * * Why broker + capability instead of "each channel subscribes the bus"? * - DRY snapshot aggregation and key-event detection. * - Per-channel throttling is enforced by the broker, so a slow / rate-limited * channel can't block a fast one. * - Adding a new channel = one capability + one register call. Broker code * never grows. */ import type { AgentEvent } from '@earendil-works/pi-agent-core'; import type { Config } from '../../config/schema.js'; import type { ChannelProgressCapability } from './channel-capability.js'; import type { WorkflowSnapshot } from './types.js'; export interface BrokerListenerHandle { /** Detach broker from the session bus and clear all in-flight state. */ dispose(): void; } /** * Tiny façade onto the AgentEventHandler. We don't import the concrete class to * keep this module test-friendly — a stub listener pump is fine for unit tests. */ export interface SessionBusLike { registerListener(type: AgentEvent['type'] | 'all', listener: (event: AgentEvent, context: { sessionKey: string; }) => void): () => void; } export declare class WorkflowProgressBroker { private readonly opts; private subscribers; private states; /** Now() factory — overridable in tests for deterministic time. */ private readonly now; /** Cached resolved settings per (channelId), invalidated on registration. */ private resolved; constructor(opts?: { getConfig?: () => Config | undefined; now?: () => number; }); registerChannel(cap: ChannelProgressCapability): () => void; attachTo(bus: SessionBusLike): BrokerListenerHandle; /** Visible for tests — direct entry path bypassing the SessionBus glue. */ onUpdate(sessionKey: string, toolCallId: string, snapshot: WorkflowSnapshot): void; /** Visible for tests — direct entry path bypassing the SessionBus glue. */ onEnd(sessionKey: string, toolCallId: string, snapshot: WorkflowSnapshot | null): void; private dispatchToChannel; private sendNow; private cancelPending; private disposeAllPending; private getOrCreateState; private getOrCreateChannelState; /** Resolved (enabled / throttleMs / mode) for a channel, with config overrides. */ private resolveChannelSettings; /** Drop any cached config so the next dispatch re-reads. Call after config reload. */ invalidateConfigCache(): void; /** @internal — for tests only. */ _stateCount(): number; } /** * Process-wide broker singleton. Channels register against this one; the * service wires it to the session bus during startup. */ export declare function getWorkflowProgressBroker(): WorkflowProgressBroker; /** Test-only — reset the singleton between cases. */ export declare function _resetWorkflowProgressBrokerForTests(): void;