Documentation
¶
Overview ¶
Package trigger provides services that trigger agent containers in response to external events: webhooks, cron schedules, and channel messages.
Index ¶
- Constants
- Variables
- func ResolveAgentAccess(ctx context.Context, q *dbq.Queries, agentID, userID uuid.UUID) agentsdk.Access
- func ResolveEcho(settingsJSON []byte, driverDefault bool) bool
- func StartPublicSweeper(ctx context.Context, database *db.DB, mgr *BridgeManager, ...)
- func StreamNDJSONResponse(body io.Reader, runID string, events chan<- ResponseEvent) (string, []message.Message, *usageInfo, error)
- func SweepExpiredPublicConversations(ctx context.Context, database *db.DB, mgr *BridgeManager, logger *zap.Logger)
- type BridgeCallback
- type BridgeDriver
- type BridgeEvent
- type BridgeFile
- type BridgeManager
- func (m *BridgeManager) AddBridge(bridgeID uuid.UUID)
- func (m *BridgeManager) HandleEvent(ctx context.Context, event BridgeEvent) error
- func (m *BridgeManager) RemoveBridge(bridgeID uuid.UUID)
- func (m *BridgeManager) SendMessage(ctx context.Context, bridgeID uuid.UUID, externalID, text string) error
- func (m *BridgeManager) SendParts(ctx context.Context, bridgeID uuid.UUID, externalID string, ...) error
- func (m *BridgeManager) Start(ctx context.Context) error
- func (m *BridgeManager) Stop()
- type BridgeReferencedMessage
- type BridgeSettings
- type CommandRegistrar
- type DiscordDriver
- func (d *DiscordDriver) Activate(ctx context.Context, br dbq.Bridge) error
- func (d *DiscordDriver) CreateDMChannel(ctx context.Context, token, recipientID string) (string, error)
- func (d *DiscordDriver) DefaultEcho() bool
- func (d *DiscordDriver) FetchUser(ctx context.Context, token, userID string) (DiscordUserInfo, error)
- func (d *DiscordDriver) GetMe(ctx context.Context, token string) (string, error)
- func (d *DiscordDriver) Init(ctx context.Context, br *dbq.Bridge) error
- func (d *DiscordDriver) Poll(ctx context.Context, br *dbq.Bridge) ([]BridgeEvent, error)
- func (d *DiscordDriver) RegisterCommands(ctx context.Context, br dbq.Bridge, cmds []SlashCommand) error
- func (d *DiscordDriver) SendDM(ctx context.Context, token, recipientID, content string) error
- func (d *DiscordDriver) SendParts(ctx context.Context, token, channelID string, parts []agentsdk.DisplayPart) error
- func (d *DiscordDriver) SendStream(ctx context.Context, br dbq.Bridge, externalID string, echo bool, ...) (string, error)
- func (d *DiscordDriver) Teardown(_ context.Context, br dbq.Bridge) error
- type DiscordUserInfo
- type Dispatcher
- func (d *Dispatcher) CancelRun(runID uuid.UUID) bool
- func (d *Dispatcher) EnsureRunning(ctx context.Context, agentID uuid.UUID) (*container.Container, error)
- func (d *Dispatcher) ExtendRun(runID uuid.UUID, by time.Duration) (time.Time, int, error)
- func (d *Dispatcher) ForwardCron(ctx context.Context, agentID uuid.UUID, cronName string, timeout time.Duration) (io.ReadCloser, uuid.UUID, error)
- func (d *Dispatcher) ForwardPrompt(ctx context.Context, agentID uuid.UUID, input agentsdk.PromptInput, ...) (io.ReadCloser, uuid.UUID, error)
- func (d *Dispatcher) ForwardWebhook(ctx context.Context, agentID uuid.UUID, path string, body []byte, ...) (io.ReadCloser, uuid.UUID, error)
- func (d *Dispatcher) InFlightIDs() []uuid.UUID
- func (d *Dispatcher) RefreshAgent(ctx context.Context, agentID uuid.UUID) error
- type PromptProxy
- type ResponseEvent
- type Scheduler
- type SlashCommand
- type SlashCommandResult
- type TelegramChatInfo
- type TelegramDriver
- func (d *TelegramDriver) Activate(ctx context.Context, br dbq.Bridge) error
- func (d *TelegramDriver) AnswerCallbackQuery(ctx context.Context, token, callbackID, text string) error
- func (d *TelegramDriver) DefaultEcho() bool
- func (d *TelegramDriver) GetChat(ctx context.Context, token, chatID string) (TelegramChatInfo, error)
- func (d *TelegramDriver) GetMe(ctx context.Context, token string) (string, error)
- func (d *TelegramDriver) Init(ctx context.Context, br *dbq.Bridge) error
- func (d *TelegramDriver) Poll(ctx context.Context, br *dbq.Bridge) ([]BridgeEvent, error)
- func (d *TelegramDriver) RegisterCommands(ctx context.Context, br dbq.Bridge, cmds []SlashCommand) error
- func (d *TelegramDriver) SendMessage(ctx context.Context, token string, chatID int64, text string) error
- func (d *TelegramDriver) SendParts(ctx context.Context, token string, chatID int64, parts []agentsdk.DisplayPart) error
- func (d *TelegramDriver) SendStream(ctx context.Context, br dbq.Bridge, externalID string, echo bool, ...) (string, error)
- func (d *TelegramDriver) Teardown(_ context.Context, _ dbq.Bridge) error
- type TranscriptionResolver
Constants ¶
const ( // PublicSessionModeSession persists turns in a per-channel conversation // (the default). The sweeper finalizes idle ones. PublicSessionModeSession = "session" // PublicSessionModeOneShot creates a fresh ephemeral conversation per // turn — no history loaded, conversation deleted immediately after the // run. If the user's message is a reply, the referenced text is // included as a wrapped context block so the LLM has at least the // thing being replied to. PublicSessionModeOneShot = "one_shot" )
PublicSessionMode controls how public conversations carry context across turns.
const ( // BridgeReferenceReply — the user replied to another message (Discord // reply, Telegram reply_to_message, Slack thread parent). BridgeReferenceReply = "reply" // BridgeReferenceForward — the user forwarded a message authored // elsewhere (Discord message snapshot, Telegram forward_origin). BridgeReferenceForward = "forward" )
BridgeReferenceKind distinguishes the platform mechanism that produced the reference so the prompt builder can label it for the LLM.
const ( PromptBaseDuration = 2 * time.Minute ExtendIncrement = 5 * time.Minute MaxExtensions = 5 PromptHTTPCeiling = 35 * time.Minute )
Prompt-run timeout shape:
- PromptBaseDuration: deadline armed when ForwardPrompt registers the run. A timer fires cancel() at this point unless extended.
- ExtendIncrement / MaxExtensions: ExtendRun adds Increment to the deadline up to MaxExtensions times. 2 + 5×5 = 27 min absolute cap.
- PromptHTTPCeiling: the http.Client.Timeout on the outbound request. Has to clear MaxExtensions×ExtendIncrement + base + grace, otherwise the client kills the connection before the deadline timer fires.
Cron and webhook callers pass their own timeout to ForwardCron/Webhook; they don't get the timer-driven extension treatment because no user is watching to click Extend.
const DefaultPublicSessionTTLSeconds = 3 * 60 * 60
DefaultPublicSessionTTLSeconds is the inactivity window after which a public bridge conversation is swept and finalized. Three hours covers the "user wandered off" case while keeping public state from accumulating indefinitely.
Variables ¶
var ErrExtensionCeiling = errors.New("max extensions reached")
ErrExtensionCeiling is returned by ExtendRun when the run has already been extended MaxExtensions times.
var ErrRunNotInFlight = errors.New("run not in flight")
ErrRunNotInFlight is returned by ExtendRun when no live run matches the given ID — finished, never started, or already cancelled.
var ErrTranscriptionNotConfigured = errors.New("transcription not configured")
ErrTranscriptionNotConfigured is returned when no system-wide transcription model is set. Callers handle this non-fatally (degrade to file-only delivery).
var Registry = []SlashCommand{ {Name: "auth", Description: "Link your Airlock account", Access: agentsdk.AccessPublic}, {Name: "clear", Description: "Clear conversation context", Access: agentsdk.AccessUser}, {Name: "compact", Description: "Summarize and compact context", Access: agentsdk.AccessUser}, {Name: "echo", Description: "Toggle tool output bubbles (on / off / blank=flip)", Access: agentsdk.AccessUser}, }
Registry is the canonical list of commands. Order is preserved when rendered into platform command menus.
Functions ¶
func ResolveAgentAccess ¶
func ResolveAgentAccess(ctx context.Context, q *dbq.Queries, agentID, userID uuid.UUID) agentsdk.Access
ResolveAgentAccess returns the caller's effective per-agent access level from agent_members. Non-members get AccessPublic, members map straight to AccessUser or AccessAdmin. Tenant role is deliberately not consulted — see airlock/CLAUDE.md "Permission Model" for why these axes are separate.
func ResolveEcho ¶
ResolveEcho returns the effective echo flag for a conversation given its raw settings JSON and the driver's default. Explicit conversation settings win over the driver default; missing JSON or missing key falls through to the default.
func StartPublicSweeper ¶ added in v0.2.8
func StartPublicSweeper(ctx context.Context, database *db.DB, mgr *BridgeManager, interval time.Duration, logger *zap.Logger)
StartPublicSweeper kicks off a goroutine that runs SweepExpiredPublicConversations every interval until ctx is done. Single-replica only — for multi-replica deployments this needs a Postgres advisory lock so exactly one airlock holds the sweeper at any moment.
func StreamNDJSONResponse ¶
func StreamNDJSONResponse(body io.Reader, runID string, events chan<- ResponseEvent) (string, []message.Message, *usageInfo, error)
StreamNDJSONResponse reads NDJSON events from a response stream, forwards ResponseEvents to the events channel for real-time delivery, and collects the full text response. Closes the events channel when done. runID is stamped onto confirmation_required events so drivers can build callback-bound UI (e.g. Telegram inline keyboards).
func SweepExpiredPublicConversations ¶ added in v0.2.8
func SweepExpiredPublicConversations(ctx context.Context, database *db.DB, mgr *BridgeManager, logger *zap.Logger)
SweepExpiredPublicConversations runs one pass: list public bridge conversations whose updated_at is older than the bridge's configured TTL, send a final "Conversation completed" message, then delete the conversation row (which cascade-drops messages via the FK).
Sends are best-effort — a failure to deliver the final message (network blip, revoked bot token, deleted DM channel) does not block deletion. Local state is the source of truth; the user just doesn't see the goodbye.
Types ¶
type BridgeCallback ¶
type BridgeCallback struct {
Data string // opaque payload, e.g. "approve:<runID>"
AckID string // platform-specific ack handle (Telegram callback_query.id)
}
BridgeCallback represents an interactive UI acknowledgement — a button tap on an inline keyboard or similar platform-native affordance. Drivers that don't support rich UI leave this nil.
type BridgeDriver ¶
type BridgeDriver interface {
// Init is called once when a bridge is first created.
// Uses pointer so the driver can set initial config (e.g. poll offset).
Init(ctx context.Context, br *dbq.Bridge) error
// Activate is called on every startup for active bridges.
Activate(ctx context.Context, br dbq.Bridge) error
// Teardown is called when a bridge is deleted or disabled.
Teardown(ctx context.Context, br dbq.Bridge) error
// Poll fetches new events from the platform.
// Uses pointer so the driver can update br.Config (e.g. poll offset).
Poll(ctx context.Context, br *dbq.Bridge) ([]BridgeEvent, error)
// SendStream delivers a response, streaming text deltas as they arrive.
// echo controls whether tool-call / tool-result bubbles are rendered;
// drivers that collapse tool output some other way may ignore it.
// Returns the final assembled text.
SendStream(ctx context.Context, br dbq.Bridge, externalID string, echo bool, events <-chan ResponseEvent) (string, error)
// DefaultEcho returns whether tool bubbles render by default on this
// platform. Drivers that display each tool-call/tool-result as its own
// chat message (Telegram) should return false; drivers whose UI can
// collapse tool output inline (web) should return true.
// Used when a conversation has no explicit settings.echo override.
DefaultEcho() bool
}
BridgeDriver handles platform-specific message parsing and delivery.
type BridgeEvent ¶
type BridgeEvent struct {
BridgeID uuid.UUID
ExternalID string // platform chat_id (Telegram chat ID, etc.)
SenderID string // platform user ID of sender (for identity lookup)
SenderName string
Text string
Files []BridgeFile // attached files (photos, documents)
Callback *BridgeCallback
ReferencedMessage *BridgeReferencedMessage // reply target / forward source (driver-populated)
RawPayload []byte
}
BridgeEvent represents a normalized incoming event from any platform. Either Text/Files (new user message) or Callback (button tap) is populated.
type BridgeFile ¶
type BridgeFile struct {
FileID string // platform file ID (e.g. Telegram file_id)
Filename string
ContentType string
Size int64
Data []byte // file content (downloaded by driver)
// IsVoiceNote marks a short voice recording (e.g. Telegram "voice")
// that the bridge layer should auto-transcribe before forwarding to
// the agent. Plain audio/video/document attachments leave this false.
IsVoiceNote bool
}
BridgeFile is a file attached to a bridge message.
type BridgeManager ¶
type BridgeManager struct {
// contains filtered or unexported fields
}
BridgeManager manages bridge drivers and routes events to agents.
func NewBridgeManager ¶
func NewBridgeManager(drivers map[string]BridgeDriver, prompter *PromptProxy, database *db.DB, encryptor *crypto.Encryptor, hmacSecret, publicURL string, logger *zap.Logger) *BridgeManager
NewBridgeManager creates a BridgeManager.
func (*BridgeManager) AddBridge ¶
func (m *BridgeManager) AddBridge(bridgeID uuid.UUID)
AddBridge activates a newly created bridge and starts its poller. Idempotent: if a poller is already running for this bridge ID (e.g. the bridge was re-registered after a config change), the existing one is cancelled first so only one poller hits the platform at a time.
func (*BridgeManager) HandleEvent ¶
func (m *BridgeManager) HandleEvent(ctx context.Context, event BridgeEvent) error
HandleEvent processes a parsed BridgeEvent — routes to agent via PromptProxy.
func (*BridgeManager) RemoveBridge ¶
func (m *BridgeManager) RemoveBridge(bridgeID uuid.UUID)
RemoveBridge stops the poller for a bridge. Safe to call for an unknown bridge ID — it's a no-op. The DB row is NOT touched here; callers that want full deletion do the DB work separately (typically by calling q.DeleteBridge alongside this).
func (*BridgeManager) SendMessage ¶
func (m *BridgeManager) SendMessage(ctx context.Context, bridgeID uuid.UUID, externalID, text string) error
SendMessage sends a text message to a bridge conversation. Convenience wrapper.
func (*BridgeManager) SendParts ¶
func (m *BridgeManager) SendParts(ctx context.Context, bridgeID uuid.UUID, externalID string, parts []agentsdk.DisplayPart) error
SendParts sends display parts to a bridge conversation. Looks up the bridge, decrypts the token, and delegates to the driver.
type BridgeReferencedMessage ¶ added in v0.2.8
type BridgeReferencedMessage struct {
Kind string // BridgeReferenceReply | BridgeReferenceForward
SenderName string // author of the referenced content
Text string
AuthoredAt time.Time
FromBot bool // true when the referenced message was authored by our bot (replies only)
}
BridgeReferencedMessage describes a message the current event points at — either a reply target or a forwarded message. Surfaced to the LLM as a wrapped context block regardless of session mode, so the model has the referenced content even when it's outside the active conversation history (or when there is no history at all).
type BridgeSettings ¶ added in v0.2.8
type BridgeSettings struct {
// AllowPublicDMs lets unauthenticated users DM the bot at AccessPublic.
// When false, unauth DMs are dropped (except /auth, which is the
// linking opt-in path and always works).
AllowPublicDMs bool `json:"allow_public_dms"`
// PublicSessionTTLSeconds is the inactivity window before a public
// conversation is finalized. 0 disables sweeping for that bridge.
// Only meaningful when PublicSessionMode == "session".
PublicSessionTTLSeconds int `json:"public_session_ttl_seconds"`
// PublicSessionMode chooses between persistent ("session") and
// stateless ("one_shot") public conversations. Defaults to session.
PublicSessionMode string `json:"public_session_mode"`
}
BridgeSettings is the user-tunable subset of bridge config exposed in the dashboard. Stored in bridges.settings JSONB. Distinct from bridges.config which carries driver-internal state.
func DecodeBridgeSettings ¶ added in v0.2.8
func DecodeBridgeSettings(raw []byte) BridgeSettings
DecodeBridgeSettings parses a bridges.settings JSON blob, falling back to defaults for any missing keys. Empty / invalid JSON returns the default settings.
func DefaultBridgeSettings ¶ added in v0.2.8
func DefaultBridgeSettings() BridgeSettings
DefaultBridgeSettings returns the settings a freshly created bridge row should carry. New bridges currently insert `{}` and rely on this function to materialize defaults at read time.
type CommandRegistrar ¶
type CommandRegistrar interface {
RegisterCommands(ctx context.Context, br dbq.Bridge, cmds []SlashCommand) error
}
CommandRegistrar is an optional BridgeDriver capability: platforms with a native command menu (Telegram setMyCommands, Discord app commands, ...) implement it to receive the slash-command registry on activation. Drivers without such a menu simply don't implement it.
type DiscordDriver ¶ added in v0.2.8
type DiscordDriver struct {
// contains filtered or unexported fields
}
DiscordDriver speaks the Discord Gateway WebSocket for inbound events and the v10 REST API for outbound message delivery. Single-replica only today: the Gateway connection holds session state and only one process can identify per token, so multi-replica needs a per-bridge advisory lock added later.
func NewDiscordDriver ¶ added in v0.2.8
func NewDiscordDriver(logger *zap.Logger) *DiscordDriver
NewDiscordDriver creates a DiscordDriver. logger surfaces gateway and REST errors. Pass zap.NewNop() in tests.
func NewDiscordDriverWithBase ¶ added in v0.2.8
func NewDiscordDriverWithBase(apiBase string, client *http.Client) *DiscordDriver
NewDiscordDriverWithBase creates a DiscordDriver pointed at an alternate REST base URL. The Gateway URL is fetched via GET /gateway/bot and inherits the same prefix for tests.
func (*DiscordDriver) CreateDMChannel ¶ added in v0.2.8
func (d *DiscordDriver) CreateDMChannel(ctx context.Context, token, recipientID string) (string, error)
CreateDMChannel opens (or fetches) a DM channel with a recipient and returns its channel ID. Idempotent — Discord returns the same channel every time for a given (bot, user) pair.
func (*DiscordDriver) DefaultEcho ¶ added in v0.2.8
func (d *DiscordDriver) DefaultEcho() bool
DefaultEcho mirrors Telegram: tool-call/tool-result bubbles are off by default. Each tool call would render as a separate message and a chatty agent quickly fills the DM.
func (*DiscordDriver) FetchUser ¶ added in v0.2.8
func (d *DiscordDriver) FetchUser(ctx context.Context, token, userID string) (DiscordUserInfo, error)
FetchUser hits GET /users/{id} for a snowflake. Bot tokens have permission to look up any user by ID, returning the public-profile fields (username, global_name, avatar). Used by the identity-linking preview so the user can verify which Discord account is about to bind.
func (*DiscordDriver) GetMe ¶ added in v0.2.8
GetMe is the public-facing token validator used by the bridge create handler. Returns the bot's username for storage on the bridge row.
func (*DiscordDriver) Poll ¶ added in v0.2.8
func (d *DiscordDriver) Poll(ctx context.Context, br *dbq.Bridge) ([]BridgeEvent, error)
Poll blocks until the gateway goroutine has buffered events or the timeout fires. The driver handles its own reconnect, so Poll never returns errors — a dead WS just yields no events until reconnect.
func (*DiscordDriver) RegisterCommands ¶ added in v0.2.8
func (d *DiscordDriver) RegisterCommands(ctx context.Context, br dbq.Bridge, cmds []SlashCommand) error
RegisterCommands bulk-overwrites the global slash commands for this bot's application. Global commands take up to ~1 hour to propagate across Discord's edge — first-time install is fine, name changes lag.
func (*DiscordDriver) SendDM ¶ added in v0.2.8
func (d *DiscordDriver) SendDM(ctx context.Context, token, recipientID, content string) error
SendDM is a convenience wrapper that opens (or reuses) a DM channel with a user and posts a plain-text message. Used by the bridge layer for the identity-linking prompt so the link is never posted publicly.
func (*DiscordDriver) SendParts ¶ added in v0.2.8
func (d *DiscordDriver) SendParts(ctx context.Context, token, channelID string, parts []agentsdk.DisplayPart) error
SendParts sends display parts to a Discord channel — text, images, files.
func (*DiscordDriver) SendStream ¶ added in v0.2.8
func (d *DiscordDriver) SendStream(ctx context.Context, br dbq.Bridge, externalID string, echo bool, events <-chan ResponseEvent) (string, error)
type DiscordUserInfo ¶ added in v0.2.8
type DiscordUserInfo struct {
ID string
Username string // legacy @handle (no discriminator)
GlobalName string // newer "display name" (Discord rolled out 2023)
AvatarURL string
}
DiscordUserInfo is the slice of /users/{id} we surface in identity linking. AvatarURL is the resolved CDN URL ("" if the user has no avatar set) so callers don't have to know the snowflake-hash format.
type Dispatcher ¶
type Dispatcher struct {
// contains filtered or unexported fields
}
Dispatcher ensures agent containers are running and forwards HTTP requests to them.
func NewDispatcher ¶
func NewDispatcher(cfg *config.Config, db *db.DB, containers container.ContainerManager, enc *crypto.Encryptor, logger *zap.Logger) *Dispatcher
NewDispatcher creates a Dispatcher.
func (*Dispatcher) CancelRun ¶ added in v0.2.8
func (d *Dispatcher) CancelRun(runID uuid.UUID) bool
CancelRun aborts the in-flight outbound request for the given run, if any. Returns true if a cancel was fired. Idempotent — repeat calls and calls for runs that already finished are no-ops.
func (*Dispatcher) EnsureRunning ¶
func (d *Dispatcher) EnsureRunning(ctx context.Context, agentID uuid.UUID) (*container.Container, error)
EnsureRunning looks up the agent, decrypts its DB credentials, and starts (or reconnects to) the agent container. Returns the running container.
func (*Dispatcher) ExtendRun ¶ added in v0.2.8
ExtendRun pushes the deadline of an extendable in-flight run by `by`. Returns the new deadline and the number of extensions still available. Errors with ErrRunNotInFlight if the run finished/cancelled (or is a non-extendable cron/webhook run), or ErrExtensionCeiling if the run has already been extended MaxExtensions times.
func (*Dispatcher) ForwardCron ¶
func (d *Dispatcher) ForwardCron(ctx context.Context, agentID uuid.UUID, cronName string, timeout time.Duration) (io.ReadCloser, uuid.UUID, error)
ForwardCron ensures the agent is running, creates a run record, and POSTs to the agent's cron endpoint. Returns the response body stream and the run ID. The timeout parameter controls the HTTP client timeout.
func (*Dispatcher) ForwardPrompt ¶
func (d *Dispatcher) ForwardPrompt(ctx context.Context, agentID uuid.UUID, input agentsdk.PromptInput, bridgeID *uuid.UUID) (io.ReadCloser, uuid.UUID, error)
ForwardPrompt ensures the agent is running, creates a run record, and POSTs the prompt input to the agent container. Returns the response body stream (NDJSON) and the run ID.
func (*Dispatcher) ForwardWebhook ¶
func (d *Dispatcher) ForwardWebhook(ctx context.Context, agentID uuid.UUID, path string, body []byte, bridgeID *uuid.UUID, timeout time.Duration) (io.ReadCloser, uuid.UUID, error)
ForwardWebhook ensures the agent is running, creates a run record, and POSTs the webhook payload to the agent container. Returns the response body stream and the run ID. The timeout parameter controls the HTTP client timeout.
func (*Dispatcher) InFlightIDs ¶ added in v0.2.8
func (d *Dispatcher) InFlightIDs() []uuid.UUID
InFlightIDs returns a snapshot of currently-tracked run IDs. Used by the stuck-run sweeper so it doesn't race the dispatcher and prematurely terminate a long (extended) run that's still alive in memory.
func (*Dispatcher) RefreshAgent ¶
RefreshAgent triggers a synchronous re-sync on the agent container. Used after server-side state changes the cached system prompt depends on (typically MCP OAuth completion) so the running agent picks up new tools without a restart. If the container isn't running, returns nil — there's nothing to refresh; the agent will sync fresh on its next startup.
type PromptProxy ¶
type PromptProxy struct {
// contains filtered or unexported fields
}
PromptProxy manages conversation history and forwards prompts to agent containers.
func NewPromptProxy ¶
func NewPromptProxy(dispatcher *Dispatcher, database *db.DB, s3 *storage.S3Client, resolveTranscription TranscriptionResolver, logger *zap.Logger) *PromptProxy
NewPromptProxy creates a PromptProxy. The resolver is invoked to obtain the admin-configured transcription model when a voice note arrives; pass nil to disable auto-transcription (voice notes flow through as plain attachments).
func (*PromptProxy) HandleCallback ¶
func (p *PromptProxy) HandleCallback( ctx context.Context, agentID, bridgeID, userID uuid.UUID, externalID, data string, events chan<- ResponseEvent, ) (staleRun bool, err error)
HandleCallback resolves a suspended run based on a bridge UI callback (inline-keyboard tap). data is the opaque platform payload — expected format:
"approve:<runID>" — resume with Approved=true, no prompt "deny:<runID>" — resume with Approved=false + a "Rejected by user." prompt
If the referenced run is no longer suspended (e.g. already resolved via web), emits a single "info" event and returns — the driver's AnswerCallbackQuery should still fire to clear the spinner.
func (*PromptProxy) HandleMessage ¶
func (p *PromptProxy) HandleMessage( ctx context.Context, agentID, bridgeID, userID uuid.UUID, externalID string, storeHistory bool, oneShot bool, userMessage string, files []BridgeFile, referenced *BridgeReferencedMessage, events chan<- ResponseEvent, ) (string, error)
HandleMessage processes an incoming DM for an agent via a bridge. Manages conversation history, forwards to agent, streams response events to the provided channel, stores response when complete. The events channel is closed when streaming completes.
type ResponseEvent ¶
type ResponseEvent struct {
Type string // "text-delta", "tool-call", "tool-result", "confirmation_required", "info"
Text string // for text-delta / info: the delta text or info message
ToolCallID string // for tool_call/tool_result
ToolName string // for tool_call/tool_result
ToolInput string // for tool_call: the tool arguments
ToolOutput string // for tool_result: the tool output
ToolError string // for tool_result: error message if failed
Raw []byte // full NDJSON line (for non-text events drivers may need)
// Populated when Type == "confirmation_required":
RunID string
Permission string
Patterns []string
Code string
}
ResponseEvent represents an NDJSON event from the agent response stream, forwarded to the bridge driver for progressive delivery.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler manages cron schedules for all active agents.
func NewScheduler ¶
NewScheduler creates a Scheduler.
func (*Scheduler) ReloadAgent ¶
ReloadAgent reloads cron entries for a specific agent after a sync.
type SlashCommand ¶
SlashCommand describes a user-typable command. Registry is the single source of truth: TrySlashCommand dispatches from it, and bridge drivers that support native command menus (Telegram setMyCommands, etc.) expose it verbatim. Name is stored without the leading slash.
type SlashCommandResult ¶
SlashCommandResult is the outcome of handling a slash command. Handled=false means the message was not a slash command — callers should forward it to the agent as usual. Handled=true means the command was recognized and processed. Reply is the short message to show the user, unless ForwardAsCompact is set — in that case the caller should invoke the agent with PromptInput.ForceCompact=true so Sol's summarization produces the user-visible text via a normal run.
func TrySlashCommand ¶
func TrySlashCommand( ctx context.Context, q *dbq.Queries, convID pgtype.UUID, agentID uuid.UUID, access agentsdk.Access, message string, logger *zap.Logger, ) (SlashCommandResult, error)
TrySlashCommand parses message for a leading slash command and, if recognized, executes it against the conversation. Returns Handled=false when the message is a plain user prompt. Both web (api.conversationsHandler.Prompt) and bridge (PromptProxy) paths call this before forwarding to the agent.
access is the caller's resolved agent access (see ResolveAgentAccess) and is compared against the command's required level. agentID is used by /clear to resolve any pending suspended run so the pending-confirmation dialog doesn't linger after context clear.
type TelegramChatInfo ¶
TelegramChatInfo holds the subset of getChat fields we expose.
type TelegramDriver ¶
type TelegramDriver struct {
// contains filtered or unexported fields
}
TelegramDriver handles Telegram bridges via long-polling.
func NewTelegramDriver ¶
func NewTelegramDriver(logger *zap.Logger) *TelegramDriver
NewTelegramDriver creates a TelegramDriver. logger is used to surface Telegram API errors (notably the silent-failure Markdown/HTML parse rejections that otherwise vanish). Pass zap.NewNop() in tests.
func NewTelegramDriverWithBaseURL ¶
func NewTelegramDriverWithBaseURL(baseURL string, client *http.Client) *TelegramDriver
NewTelegramDriverWithBaseURL creates a TelegramDriver with a custom base URL and HTTP client (for testing).
func (*TelegramDriver) AnswerCallbackQuery ¶
func (d *TelegramDriver) AnswerCallbackQuery(ctx context.Context, token, callbackID, text string) error
AnswerCallbackQuery clears the loading spinner on a tapped inline-keyboard button. If text is non-empty, Telegram shows it as a transient toast.
func (*TelegramDriver) DefaultEcho ¶
func (d *TelegramDriver) DefaultEcho() bool
DefaultEcho reports that Telegram defaults to hiding tool bubbles: each tool-call / tool-result is rendered as its own chat message and a chatty agent quickly swamps the conversation. Users can opt in per-chat with `/echo on`.
func (*TelegramDriver) GetChat ¶
func (d *TelegramDriver) GetChat(ctx context.Context, token, chatID string) (TelegramChatInfo, error)
GetChat calls the Telegram getChat API for a private chat. Because private chat IDs equal the user ID, this resolves a telegram user's public username and display name as long as that user has DM'd the bot.
func (*TelegramDriver) Poll ¶
func (d *TelegramDriver) Poll(ctx context.Context, br *dbq.Bridge) ([]BridgeEvent, error)
func (*TelegramDriver) RegisterCommands ¶
func (d *TelegramDriver) RegisterCommands(ctx context.Context, br dbq.Bridge, cmds []SlashCommand) error
RegisterCommands publishes the slash-command registry to Telegram's global command menu via setMyCommands. Telegram stores names without the leading slash.
func (*TelegramDriver) SendMessage ¶
func (d *TelegramDriver) SendMessage(ctx context.Context, token string, chatID int64, text string) error
SendMessage sends a text message to a Telegram chat.
func (*TelegramDriver) SendParts ¶
func (d *TelegramDriver) SendParts(ctx context.Context, token string, chatID int64, parts []agentsdk.DisplayPart) error
SendParts sends display parts to a Telegram chat. Renders each part appropriately: text → sendMessage, image → sendPhoto, file → sendDocument.
func (*TelegramDriver) SendStream ¶
func (d *TelegramDriver) SendStream(ctx context.Context, br dbq.Bridge, externalID string, echo bool, events <-chan ResponseEvent) (string, error)
type TranscriptionResolver ¶
type TranscriptionResolver func(ctx context.Context) (model.TranscriptionModel, error)
TranscriptionResolver returns the admin-configured transcription model or ErrTranscriptionNotConfigured when no model is set.
func NewTranscriptionResolver ¶
func NewTranscriptionResolver(database *db.DB, encryptor *crypto.Encryptor) TranscriptionResolver
NewTranscriptionResolver returns a resolver that reads system_settings.default_transcription_model and looks up the associated provider credentials.