Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt

Use this file to discover all available pages before exploring further.

Overview

ChannelContext extends ExecutionContextBase and provides the foundation for all channel implementations. It handles event transformation via onEvent() and optional reply handling via onReply().

Class Hierarchy

ExecutionContextBase<ChannelNotification>
  └── ChannelContext (abstract)
        ├── Your @Channel class
        └── FunctionChannelContext (internal, for channel() builder)

Abstract Methods

onEvent

Transform an incoming event payload into a channel notification.
abstract onEvent(payload: unknown): Promise<ChannelNotification>;
ParameterTypeDescription
payloadunknownRaw event payload from the source. Shape depends on source type.
Returns: Promise<ChannelNotification> — the notification to push to Claude Code sessions.

Payload Shapes by Source

SourcePayload Shape
webhookWebhookPayload { body, headers, method, query }
app-eventWhatever was passed to eventBus.emit(event, payload)
agent-completionAgentCompletionEvent { agentId, agentName, status, durationMs, output, error }
job-completionJobCompletionEvent { jobName, jobId, status, durationMs, output, error, sessionId }
serviceWhatever pushIncoming(payload) receives from your connection listener
file-watcherWhatever pushIncoming(payload) receives from your file watcher
manualWhatever was passed to handleEvent(payload)

Optional Methods

onReply

Handle a reply from Claude Code. Only called when the channel has twoWay: true.
async onReply(reply: string, meta?: Record<string, string>): Promise<void>
ParameterTypeDescription
replystringThe reply text from Claude
metaRecord<string, string>Metadata from the channel-reply tool call (e.g., chat_id)
Default implementation logs a warning. Override to forward replies to external systems.

Lifecycle Hooks (Service Connectors)

For channels with source: { type: 'service' } or source: { type: 'file-watcher' }, these lifecycle hooks manage persistent connections.

onConnect

Establish a persistent connection to an external service. Called during scope initialization after the notification service is wired.
async onConnect(): Promise<void>
Use this to set up WebSocket connections, API clients, polling loops, or file watchers. Call pushIncoming() inside event listeners to feed incoming events into the notification pipeline.
async onConnect(): Promise<void> {
  this.client = new WhatsAppClient(process.env['WA_TOKEN']);
  this.client.on('message', (msg) => {
    this.pushIncoming({ sender: msg.from, text: msg.body, chatId: msg.chatId });
  });
  await this.client.connect();
}

onDisconnect

Tear down the persistent connection. Called during scope shutdown.
async onDisconnect(): Promise<void>
async onDisconnect(): Promise<void> {
  await this.client?.disconnect();
  this.watcher?.close();
}

pushIncoming

Push an incoming event from a service connection into the notification pipeline. Triggers onEvent() → notification push to subscribed sessions.
protected pushIncoming(payload: unknown): void
ParameterTypeDescription
payloadunknownRaw event payload to process through onEvent()
Only call pushIncoming() inside onConnect() event listeners. Calling it before onConnect() completes will log a warning since the handler is not yet wired.

Inherited Properties

From ExecutionContextBase:
PropertyTypeDescription
loggerFrontMcpLoggerScoped logger for this channel
metadataChannelMetadataThe channel’s decorator metadata
channelNamestringThe channel name (from metadata)

Inherited Methods

From ExecutionContextBase:
MethodDescription
get<T>(token)Resolve a dependency from the DI container
tryGet<T>(token)Resolve or return undefined if not found
scopeAccess the parent scope
fail(error)Throw an MCP error

Examples

Webhook Channel

@Channel({
  name: 'monitoring',
  source: { type: 'webhook', path: '/hooks/monitor' },
  twoWay: true,
})
class MonitoringChannel extends ChannelContext {
  async onEvent(payload: unknown): Promise<ChannelNotification> {
    const { body } = payload as { body: { alert: string; severity: string } };
    return { content: `Alert: ${body.alert}`, meta: { severity: body.severity } };
  }

  async onReply(reply: string, meta?: Record<string, string>): Promise<void> {
    const messenger = this.get(MessengerServiceToken);
    await messenger.send(meta?.chat_id, reply);
  }
}

Service Connector (Full Lifecycle)

@Channel({
  name: 'whatsapp',
  source: { type: 'service', service: 'whatsapp-business' },
  tools: [SendWhatsAppTool],  // Claude calls this to send outbound messages
  twoWay: true,
})
class WhatsAppChannel extends ChannelContext {
  private client: WhatsAppClient;

  async onConnect(): Promise<void> {
    // Called during scope initialization
    this.client = new WhatsAppClient(process.env['WA_TOKEN']);
    this.client.on('message', (msg) => {
      // Feed incoming messages into the notification pipeline
      this.pushIncoming({ from: msg.sender, text: msg.body, chatId: msg.chatId });
    });
    await this.client.connect();
  }

  async onDisconnect(): Promise<void> {
    // Called during scope teardown
    await this.client.disconnect();
  }

  async onEvent(payload: unknown): Promise<ChannelNotification> {
    // Transform incoming service event → notification for Claude
    const msg = payload as { from: string; text: string; chatId: string };
    return {
      content: `${msg.from}: ${msg.text}`,
      meta: { chat_id: msg.chatId, sender: msg.from },
    };
  }

  async onReply(reply: string, meta?: Record<string, string>): Promise<void> {
    // Forward Claude's reply back through the service
    await this.client.sendMessage(meta?.chat_id, reply);
  }
}