channels

package
v0.14.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 3, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package channels provides message channel implementations for the Agents subsystem. Each channel (Slack, future: Telegram, Discord, etc.) satisfies the Channel interface and handles the full lifecycle:

  • Listening for incoming messages
  • Access control (channel-specific)
  • Dispatching to the agent pool via a SendFunc
  • Delivering responses back (reactions, threaded replies, SSE, etc.)

Wiring is done by *Registry — server constructs channels with cfg only, hands them to the registry, and the registry attaches dependencies via optional setter interfaces and fans out events.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnsureChannel

func EnsureChannel(db *gorm.DB, channelType string) error

EnsureChannel creates a default agent_channels row for channelType if none exists. Safe to call at boot — idempotent.

func GetChannelConfigMap

func GetChannelConfigMap(db *gorm.DB, channelType string) (map[string]string, error)

GetChannelConfigMap loads the JSON config for a channel type into a map. Returns an empty map when no row exists (not an error).

func ProjectOverride added in v0.14.21

func ProjectOverride(ctx context.Context) string

ProjectOverride returns the per-request project id set via WithProjectOverride, or "" when none.

func SetChannelConfigKey

func SetChannelConfigKey(db *gorm.DB, channelType, key, value string) error

SetChannelConfigKey updates a single key inside the channel's JSON config. Creates the row first if it doesn't exist.

func SwitchProvider added in v0.13.4

func SwitchProvider(layout agentconfig.Layout, pool SwitchPool, sessionID, agentName, tag, source string) error

SwitchProvider updates agents.json, records a system turn in conversation.jsonl, and kills the running agent so the next message spawns with the new provider. source is the transport label ("ui", "slack", etc.) written into conversation.jsonl.

func WithProjectOverride added in v0.14.21

func WithProjectOverride(ctx context.Context, projectID string) context.Context

WithProjectOverride returns ctx carrying a per-request project id. The pool send closure prefers this over the channel's configured project.

Types

type AgentEventReceiver added in v0.9.4

type AgentEventReceiver interface {
	OnAgentEvent(sessionID string, ev event.AgentEvent)
}

AgentEventReceiver is fanned-out per agent event (TextDelta, Done, …).

type ApprovalReceiver added in v0.9.4

type ApprovalReceiver interface {
	OnApprovalRequest(sessionID string, req gate.ApprovalRequest)
	OnApprovalResolved(sessionID, requestID, decision string)
}

ApprovalReceiver is fanned-out for gate approval lifecycle.

type ApproveFn

type ApproveFn func(sessionID, requestID, decision, matchKey string) error

ApproveFn resolves a gate approval request originating from a channel. sessionID is the wick session, requestID is the gate request UUID, decision is one of the gate.Decision* constants. channelName is the originating channel name ("slack", "telegram", …) — passed for audit logging only. The registry wraps the user-supplied ApproveFn at Add time so each channel sees a 4-arg setter that already binds its name.

type ApproveFnSetter added in v0.9.4

type ApproveFnSetter interface{ SetApproveFn(ApproveFn) }

ApproveFnSetter receives the gate approval resolver.

type Channel

type Channel interface {
	Name() string
	Start(ctx context.Context) error
	Stop()
	IsConfigured() bool
}

Channel is the minimal contract every transport must satisfy. The registry routes everything else (event fan-out, reload, http handlers) through the optional interfaces below — implementing those is opt-in.

type ChannelEnsurer added in v0.9.4

type ChannelEnsurer interface {
	EnsureChannel(channelType string) error
}

ChannelEnsurer guarantees a default agent_channels row exists for the given channel type. setup composers call it before loading config so first-boot operators see the channel listed in the UI even when the row is empty.

type ConfigSource added in v0.9.4

type ConfigSource interface {
	Hash() string
	Reload(ctx context.Context) error
}

ConfigSource is per-channel hot-reload glue. Hash returns a stable fingerprint of the currently-applied config; the registry watcher compares against the previous hash on each tick and calls Reload when it changes. Implementations decide where the config lives — see ConfigStore for the abstraction the bundled sources read from.

type DBStore added in v0.9.4

type DBStore struct {
	Configs *configs.Service
	// contains filtered or unexported fields
}

DBStore satisfies SlackConfigStore + TelegramConfigStore by delegating to configMap so callers always receive decrypted plaintext values. Server wires one of these at boot so per-channel ConfigSource implementations don't need to import gorm directly. Configs is optional — when set, wick_cenc_ tokens in the JSON config are decrypted before being returned to callers.

func NewDBStore added in v0.9.4

func NewDBStore(db *gorm.DB) DBStore

NewDBStore returns a DBStore bound to db.

func (DBStore) EnsureChannel added in v0.9.4

func (s DBStore) EnsureChannel(channelType string) error

EnsureChannel satisfies ChannelEnsurer.

func (DBStore) LoadRest added in v0.10.0

func (s DBStore) LoadRest() (agentconfig.RestChannelConfig, error)

LoadRest satisfies RestConfigStore.

func (DBStore) LoadSlack added in v0.9.4

LoadSlack satisfies SlackConfigStore.

func (DBStore) LoadTelegram added in v0.9.4

func (s DBStore) LoadTelegram() (agentconfig.TelegramChannelConfig, error)

LoadTelegram satisfies TelegramConfigStore.

type HTTPHandlerProvider added in v0.9.4

type HTTPHandlerProvider interface {
	HTTPPath() string
	HTTPHandler() http.Handler
}

HTTPHandlerProvider exposes a webhook handler the registry mounts on the public mux. Slack's HTTP-mode events use this; Telegram (long polling) does not.

type HealthCheck added in v0.10.0

type HealthCheck struct {
	Name   string `json:"name"`
	OK     bool   `json:"ok"`
	Error  string `json:"error,omitempty"`
	Detail string `json:"detail,omitempty"`
}

HealthCheck is one row of an integration self-test (e.g. "auth.test ok", "users.list missing scope"). OK=true means the upstream call succeeded with the result the operator expects; Detail is a short human-readable note (scope hint, count, etc.).

type HealthChecker added in v0.10.0

type HealthChecker interface {
	HealthCheck() []HealthCheck
}

HealthChecker lets a channel expose a "Test Integration" probe that runs from the admin UI. Implementations should cover the API calls the channel relies on (auth, listing, search, write) so missing scopes surface before runtime.

type IncomingMessage

type IncomingMessage struct {
	SessionKey string   // routing key
	UserID     string   // sender identifier
	GroupIDs   []string // user groups (Slack only — access control)
	Text       string
	Source     string // "slack" | "ui" | "api"
	Raw        any    // original payload from the channel
}

IncomingMessage is the canonical inbound shape produced by each channel. SessionKey is the routing key (thread_ts for Slack, UUID for UI/API).

type LookupItem added in v0.10.0

type LookupItem struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

LookupItem is one row returned by a picker lookup. ID is the stable identifier stored in the config; Name is the human label shown to the operator.

type LookupProvider added in v0.10.0

type LookupProvider interface {
	Lookup(source, query string) ([]LookupItem, error)
}

LookupProvider lets a channel back picker fields with a live search against its upstream. Source is the registered key from the wick tag (e.g. "slack.users"). Implementations should cap results and skip deleted/bot entries.

type MetaResult

type MetaResult struct {
	// IsMeta is true when the text is a wick meta-command and should NOT
	// be forwarded to the agent subprocess.
	IsMeta bool
	// Cmd is the canonical command name (e.g. "dashboard", "reset").
	Cmd string
	// Arg is the optional argument following the command (e.g. agent name
	// after "agent <name>").
	Arg string
}

MetaResult is what ParseMeta returns.

func ParseMeta

func ParseMeta(text string) MetaResult

ParseMeta checks whether text is one of the wick meta-commands intercepted before the pool. Commands are case-insensitive and may be prefixed with / or !. Returns IsMeta=false for regular user messages.

Supported commands (agents-design.md §10):

/agent <name>   — switch active agent
/reset          — clear session context (next send starts fresh)
/status         — reply with current session/agent state
/dashboard      — reply with dashboard URL for this session
/link           — alias for /dashboard
/log            — reply with last N command-gate log lines

type MultiHTTPHandlerProvider added in v0.13.0

type MultiHTTPHandlerProvider interface {
	HTTPHandlers() map[string]http.Handler
}

MultiHTTPHandlerProvider extends HTTPHandlerProvider for channels that need to register more than one HTTP route (e.g. Slack registers both the inbound event webhook and a local send-message proxy).

type OutgoingMessage

type OutgoingMessage struct {
	SessionKey string
	Text       string
	// State carries the reaction lifecycle marker for Slack:
	// "queued" | "running" | "done" | "blocked" | "error"
	State string
}

OutgoingMessage is what the channel delivers back to the user after an agent turn completes. Slack uses Text + State for reaction lifecycle; UI uses SSE which bypasses this struct entirely.

type ProviderSwitchResult added in v0.13.4

type ProviderSwitchResult struct {
	Tag    string // provider type parsed from #tag, empty if no tag
	Rest   string // message text after the tag (may be empty)
	HasTag bool
}

ProviderSwitchResult is returned by ParseProviderTag.

func ParseProviderTag added in v0.13.4

func ParseProviderTag(text string) ProviderSwitchResult

ParseProviderTag checks if text starts with #<provider> and splits it. Returns HasTag=false if text does not start with '#'.

type PublicURLSetter added in v0.9.4

type PublicURLSetter interface{ SetPublicURL(string) }

PublicURLSetter receives the public base URL for dashboard links. Slack uses this for /dashboard meta-command replies; Telegram doesn't.

type Registry added in v0.9.4

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds the shared dependency set every channel may need plus the channel list itself. Construct via NewRegistry, attach deps via the With* setters, then Add channels and Start.

func NewRegistry added in v0.9.4

func NewRegistry() *Registry

NewRegistry returns an empty registry. Use the With* methods to attach shared dependencies before calling Add.

func (*Registry) Add added in v0.9.4

func (r *Registry) Add(c Channel, src ConfigSource)

Add registers a channel and auto-wires every shared dependency the channel implements via its setter interface. Idempotent across dependencies — if a setter is missing the channel just skips that wire.

Pass an optional ConfigSource to enable hot-reload for the channel. Nil source = channel never reloaded by WatchConfigs.

func (*Registry) ChannelByName added in v0.10.0

func (r *Registry) ChannelByName(name string) Channel

ChannelByName returns the registered channel matching name, or nil.

func (*Registry) Channels added in v0.9.4

func (r *Registry) Channels() []Channel

Channels returns a snapshot of the registered channels.

func (*Registry) DispatchAgentEvent added in v0.9.4

func (r *Registry) DispatchAgentEvent(sessionID string, ev event.AgentEvent)

DispatchAgentEvent fans out one agent event to every channel that implements AgentEventReceiver. Channels filter by sessionID internally (events for sessions they didn't originate are ignored).

func (*Registry) DispatchApprovalRequest added in v0.9.4

func (r *Registry) DispatchApprovalRequest(sessionID string, req gate.ApprovalRequest)

DispatchApprovalRequest fans out an approval request to every channel that implements ApprovalReceiver. The channel decides whether the request belongs to one of its sessions (by checking its session table).

func (*Registry) DispatchApprovalResolved added in v0.9.4

func (r *Registry) DispatchApprovalResolved(sessionID, requestID, decision string)

DispatchApprovalResolved fans out an approval-resolved notification.

func (*Registry) HTTPHandlers added in v0.9.4

func (r *Registry) HTTPHandlers() map[string]http.Handler

HTTPHandlers returns the webhook handlers exposed by channels that implement HTTPHandlerProvider. Caller mounts them on the public mux.

func (*Registry) StartAll added in v0.9.4

func (r *Registry) StartAll(ctx context.Context)

StartAll starts every configured channel in its own goroutine. Unconfigured channels are skipped with an info log so operators see why their channel isn't live. Errors from Start are logged but non-fatal — one bad channel doesn't take down the rest.

func (*Registry) StopAll added in v0.9.4

func (r *Registry) StopAll()

StopAll signals every channel to shut down. Safe to call before any Start has returned — channels' Stop is expected to be idempotent.

func (*Registry) WatchConfigs added in v0.9.4

func (r *Registry) WatchConfigs(ctx context.Context, interval time.Duration)

WatchConfigs polls every registered ConfigSource at the given interval and triggers Reload when its Hash changes. Blocks until ctx is done. Run in its own goroutine.

func (*Registry) WithApproveFn added in v0.9.4

func (r *Registry) WithApproveFn(fn RegistryApproveFn) *Registry

WithApproveFn attaches the gate approval resolver. The signature includes channelName so the manager can record which transport posted the decision; Add binds each channel's name into a 4-arg ApproveFn before handing it to the channel's setter.

func (*Registry) WithPublicURL added in v0.9.4

func (r *Registry) WithPublicURL(u string) *Registry

WithPublicURL attaches the public base URL for dashboard links.

func (*Registry) WithSendFunc added in v0.9.4

func (r *Registry) WithSendFunc(fn SendFunc) *Registry

WithSendFunc attaches the pool dispatch closure. Called once at boot.

func (*Registry) WithSessionChecker added in v0.9.4

func (r *Registry) WithSessionChecker(c SessionChecker) *Registry

WithSessionChecker attaches the session-exists probe.

func (*Registry) WithSessionStartHook added in v0.9.4

func (r *Registry) WithSessionStartHook(h SessionStartHook) *Registry

WithSessionStartHook attaches the new-session notifier.

type RegistryApproveFn added in v0.9.4

type RegistryApproveFn func(channelName, sessionID, requestID, decision, matchKey string) error

RegistryApproveFn is the multi-source variant the registry holds. It is wrapped per-channel into ApproveFn during Add so each channel keeps a 4-arg signature.

type RestConfigStore added in v0.10.0

type RestConfigStore interface {
	LoadRest() (cfg agentconfig.RestChannelConfig, err error)
}

RestConfigStore mirrors SlackConfigStore for the OpenAI-compatible REST channel.

type SendFunc

type SendFunc func(ctx context.Context, sessionID, agentName, source, role, text string) error

SendFunc is the signature the pool exposes for sending a user message into a session. Channels call this after passing access control and meta-command checks.

func WrapSendFunc added in v0.13.4

func WrapSendFunc(fn SendFunc, layout agentconfig.Layout, pool SwitchPool, replyFn func(sessionID, agentName, source, text string)) SendFunc

WrapSendFunc wraps a SendFunc to intercept #<provider> prefix. On switch-only message (no body), the send is skipped after confirmation. On switch+message, provider is switched then message is forwarded. On unknown provider, an error is surfaced via replyFn. replyFn, if non-nil, is called with the confirmation text so the channel (Slack, REST) can deliver it without forwarding to the provider.

type SendFuncSetter added in v0.9.4

type SendFuncSetter interface{ SetSendFunc(SendFunc) }

SendFuncSetter receives the pool dispatch closure.

type SessionChecker added in v0.9.4

type SessionChecker interface {
	SessionExists(sessionID string) bool
}

SessionChecker reports whether a sessionID already exists. Implemented by *pool.Pool. Channels use it to decide whether the next inbound message starts a brand-new session — if so they prepend a one-time system turn (workspace/chat/user/link) so the agent's first reply is grounded.

type SessionCheckerSetter added in v0.9.4

type SessionCheckerSetter interface{ SetSessionChecker(SessionChecker) }

SessionCheckerSetter receives the session-exists probe.

type SessionStartHook added in v0.9.4

type SessionStartHook func(sessionID, source, ctxText string)

SessionStartHook fires once when a channel sees a brand-new session (no on-disk state yet). Optional — channels that don't track session origin (e.g. UI, API) never fire it. ctxText is the human-readable origin metadata composed for the agent's first turn.

type SessionStartHookSetter added in v0.9.4

type SessionStartHookSetter interface{ SetSessionStartHook(SessionStartHook) }

SessionStartHookSetter receives the new-session callback.

type SlackConfigStore added in v0.9.4

type SlackConfigStore interface {
	LoadSlack() (cfg agentconfig.SlackChannelConfig, pubURL string, err error)
}

SlackConfigStore is the storage abstraction SlackConfigSource reads from. It hides the backend (DB, file, in-memory test fake) so the channels package can stay free of gorm imports inside the source implementations. Server wires a DB-backed implementor at boot.

type SwitchPool added in v0.13.4

type SwitchPool interface {
	Kill(sessionID, agentName string) error
	Send(ctx context.Context, sessionID, agentName, source, role, text string) error
}

SwitchPool is the subset of pool.Pool needed for provider switching.

type TelegramConfigStore added in v0.9.4

type TelegramConfigStore interface {
	LoadTelegram() (cfg agentconfig.TelegramChannelConfig, err error)
}

TelegramConfigStore mirrors SlackConfigStore for Telegram.

type WorkflowActionProvider added in v0.13.0

type WorkflowActionProvider interface {
	WorkflowActionSpecs() []WorkflowActionSpec
	WorkflowSend(ctx context.Context, op string, args map[string]any) (any, error)
}

WorkflowActionProvider is implemented by channels that expose outbound operations (Send, react, open_modal, …) to workflow action nodes.

type WorkflowActionSpec added in v0.13.0

type WorkflowActionSpec struct {
	ID           string         `json:"id"`
	Description  string         `json:"description"`
	Destructive  bool           `json:"destructive,omitempty"`
	InputSchema  map[string]any `json:"input_schema"`
	OutputSchema map[string]any `json:"output_schema,omitempty"`
}

WorkflowActionSpec describes one outbound op a workflow channel-action node can invoke. Mirrors the input/output schema convention used by connector ops so the editor can render a typed args form.

type WorkflowSessionOriginator added in v0.13.0

type WorkflowSessionOriginator interface {
	SupportsSession() bool
}

WorkflowSessionOriginator reports whether this channel can be the origin of a multi-turn agent session. UI/Slack/Telegram return true; stateless transports (REST, one-shot webhook) return false. Workflow validator rejects channel triggers that need a reply path on channels that don't support sessions.

type WorkflowTriggerProvider added in v0.13.0

type WorkflowTriggerProvider interface {
	WorkflowTriggerSpecs() []WorkflowTriggerSpec
}

WorkflowTriggerProvider is implemented by channels that can fire workflow triggers (Slack, Telegram, …). Channels that only accept outbound calls (REST one-shot) skip this.

type WorkflowTriggerSpec added in v0.13.0

type WorkflowTriggerSpec struct {
	Type          string         `json:"type"` // always "channel"
	Events        []string       `json:"events"`
	Description   string         `json:"description"`
	MatchSchema   map[string]any `json:"match_schema,omitempty"`
	PayloadSchema map[string]any `json:"payload_schema,omitempty"`
}

WorkflowTriggerSpec describes one inbound event class the channel can fire as a workflow trigger. Surfaced via MCP for AI introspection + the editor's trigger-channel dropdown.

Directories

Path Synopsis
Package rest implements an OpenAI Chat Completions compatible HTTP channel for the agents pool.
Package rest implements an OpenAI Chat Completions compatible HTTP channel for the agents pool.
Package setup composes channel implementations into a registry.
Package setup composes channel implementations into a registry.
Package slack — send.go: local agent-proxy send handler + reply helpers.
Package slack — send.go: local agent-proxy send handler + reply helpers.
workflow
Slack picker resolvers — feed workflow_picker_resolve so AI authors editing a trigger match (channel_id whitelist, user whitelist) can get real IDs instead of guessing C123/U456.
Slack picker resolvers — feed workflow_picker_resolve so AI authors editing a trigger match (channel_id whitelist, user whitelist) can get real IDs instead of guessing C123/U456.
Package telegram implements the Telegram transport for the agents channel registry.
Package telegram implements the Telegram transport for the agents channel registry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL