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 PostFilesManifest(ctx context.Context, q *dbq.Queries, convID pgtype.UUID, files []wire.FileInfo) error
- func ResolveEcho(settingsJSON []byte, driverDefault bool) bool
- func StreamNDJSONResponse(body io.Reader, runID string, events chan<- ResponseEvent) (string, []message.Message, *usageInfo, error)
- type AgentSlashConv
- func (a *AgentSlashConv) Cancel(ctx context.Context, convID pgtype.UUID) bool
- func (a *AgentSlashConv) Clear(ctx context.Context, convID pgtype.UUID) (bool, error)
- func (a *AgentSlashConv) Compact(_ context.Context, _ pgtype.UUID) (string, bool, error)
- func (a *AgentSlashConv) Echo(ctx context.Context, convID pgtype.UUID, args string) (bool, error)
- func (a *AgentSlashConv) Start(ctx context.Context, convID pgtype.UUID) string
- type BridgeCallback
- type BridgeDriver
- type BridgeEvent
- type BridgeFile
- type BridgeManager
- func (m *BridgeManager) AddBridge(bridgeID uuid.UUID)
- func (m *BridgeManager) AttachManagedBotIngest(...)
- func (m *BridgeManager) AttachSysagent(s SysagentRuntime)
- func (m *BridgeManager) BotTokenForBridge(ctx context.Context, agentID, bridgeID uuid.UUID) (string, error)
- func (m *BridgeManager) HandleEvent(ctx context.Context, event BridgeEvent) error
- func (m *BridgeManager) RemoveBridge(bridgeID uuid.UUID)
- func (m *BridgeManager) RemoveBridgesByOwner(ctx context.Context, ownerID uuid.UUID) error
- func (m *BridgeManager) ResumeSystemConversation(ctx context.Context, conversationID, originRunID uuid.UUID) error
- 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()
- func (m *BridgeManager) StreamToBridge(ctx context.Context, bridgeID uuid.UUID, externalID string, ...) error
- func (m *BridgeManager) TeardownBridge(bridgeID uuid.UUID)
- type BridgeReferencedMessage
- type CommandRegistrar
- 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) EnsureRuntime(ctx context.Context, agentID uuid.UUID) error
- func (d *Dispatcher) FailRouteRun(runID uuid.UUID, err error)
- func (d *Dispatcher) ForwardJob(ctx context.Context, job dbq.AgentJob, attempt dbq.AgentJobAttempt) (wire.JobRunResponse, uuid.UUID, error)
- func (d *Dispatcher) ForwardPrompt(ctx context.Context, p authz.Principal, agentID uuid.UUID, ...) (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) InvokeRuntime(ctx context.Context, agentID uuid.UUID, input wire.RuntimeInvokeRequest) (wire.RuntimeInvokeResponse, error)
- func (d *Dispatcher) RecoverChat(ctx context.Context) error
- func (d *Dispatcher) RefreshAgent(ctx context.Context, agentID uuid.UUID) error
- func (d *Dispatcher) SetPromptRuntime(runtime PromptRuntime)
- func (d *Dispatcher) StartJSExecutor(ctx context.Context, runID, token uuid.UUID) (io.ReadWriteCloser, error)
- type JobWorker
- type ManagedBotEvent
- type PromptProxy
- func (p *PromptProxy) HandleCallback(ctx context.Context, agentID, bridgeID, userID uuid.UUID, ...) (staleRun bool, err error)
- func (p *PromptProxy) HandleMessage(ctx context.Context, agentID, bridgeID, userID uuid.UUID, externalID string, ...) (string, error)
- func (p *PromptProxy) TranscribeVoicePlain(ctx context.Context, files []BridgeFile) (text string, hasVoice bool, hasNonVoice bool)
- type PromptRuntime
- type ResponseEvent
- type RunCanceler
- type Scheduler
- func (s *Scheduler) ReconcileAgent(ctx context.Context, agentID uuid.UUID, tokenVersion int64, ...) error
- func (s *Scheduler) ReconcileAgentTx(ctx context.Context, tx pgx.Tx, agentID uuid.UUID, tokenVersion int64, ...) error
- func (s *Scheduler) Run(ctx context.Context) error
- func (s *Scheduler) Wake()
- type SlashCommand
- type SlashCommandResult
- type SlashConv
- type SysagentRuntime
- type SysagentSlashConv
- func (s *SysagentSlashConv) Cancel(ctx context.Context, id pgtype.UUID) bool
- func (s *SysagentSlashConv) Clear(ctx context.Context, id pgtype.UUID) (bool, error)
- func (s *SysagentSlashConv) Compact(ctx context.Context, id pgtype.UUID) (string, bool, error)
- func (s *SysagentSlashConv) Echo(ctx context.Context, id pgtype.UUID, args string) (bool, error)
- func (s *SysagentSlashConv) Start(context.Context, pgtype.UUID) string
- 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) GetManagedBotToken(ctx context.Context, managerToken string, botUserID int64) (string, error)
- func (d *TelegramDriver) GetMe(ctx context.Context, token string) (string, error)
- func (d *TelegramDriver) GetMeFull(ctx context.Context, token string) (username, name string, botUserID int64, canManageBots bool, err error)
- func (d *TelegramDriver) GetMenuButton(ctx context.Context, token string) (TelegramMenuButton, 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) RemoveButtons(ctx context.Context, br dbq.Bridge, externalID, messageID string) 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 []wire.DisplayPart) error
- func (d *TelegramDriver) SendStream(ctx context.Context, br dbq.Bridge, externalID string, echo bool, ...) (string, error)
- func (d *TelegramDriver) SetMenuButton(ctx context.Context, token, url string) error
- func (d *TelegramDriver) Teardown(ctx context.Context, br dbq.Bridge) error
- type TelegramMenuButton
- type TranscriptionResolver
Constants ¶
const ( // BridgeReferenceReply — the user replied to another message // (Telegram reply_to_message). BridgeReferenceReply = "reply" // BridgeReferenceForward — the user forwarded a message authored // elsewhere (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 CancelButtonAfter = 20 * time.Second
CancelButtonAfter is how long a bridge run can stream before the driver posts a "Still working… Tap to stop" message with a cancel button. The message is deleted when the run ends (naturally or via the user tap).
const FilesManifestSource = "llm"
FilesManifestSource tags the attached-files manifest message: a user-role message that IS sent to the model (non-ephemeral, so SessionStore.Load returns it) but is hidden from the human UI (the frontend drops source=="llm"). The human already sees the attachments via the separate upload echo / their chat platform, so rendering the manifest too would be noise.
const PromptHTTPCeiling = 30 * time.Minute
PromptHTTPCeiling is the absolute cap on a prompt run's outbound HTTP request. Generous on purpose: prompt runs may legitimately stream for many minutes (long tool chains, slow LLMs); the user cancels manually via DELETE /api/v1/runs/{runID} when they want to stop earlier. Cron and webhook callers pass their own (typically shorter) timeout.
Variables ¶
var ( // ErrAgentStopped — the agent is parked via /stop and only a manual // /start resumes it; EnsureRunning refuses to auto-start it. ErrAgentStopped = errors.New("agent is stopped") // ErrAgentNoImage — the agent has never finished a build, so there is // no container image to run. ErrAgentNoImage = errors.New("agent has no image") ErrAgentDeploying = errors.New("agent deployment is starting") ErrJobLeaseLost = errors.New("background job delivery lease lost") )
Sentinel errors from EnsureRunning for agents that exist but aren't in a runnable state. Callers map these to a surface-appropriate response (409 on HTTP, an in-chat notice on bridges, a JSON-RPC error on MCP) instead of a generic 500. Both are expected operator states, not faults.
var ( ErrInvalidJobCron = errors.New("invalid job cron declaration") ErrStaleJobCrons = errors.New("stale job cron manifest") )
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 = slashcommands.Registry
Registry supplies platform command-menu metadata from the command service.
Functions ¶
func PostFilesManifest ¶ added in v0.4.0
func PostFilesManifest(ctx context.Context, q *dbq.Queries, convID pgtype.UUID, files []wire.FileInfo) error
PostFilesManifest writes the attached-files manifest as its own conversation message, describing file attachments to the model. Web and bridge ingress call this before dispatch so the row is in history when the hosted SessionStore loads. No-op when there are no files.
Persist-only (q.CreateMessage) — deliberately NOT postToConversation: the manifest is model-only and must never reach a human WS or bridge channel. Sol's same-role coalescer folds it together with the user's actual message for providers that reject consecutive user turns.
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 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).
Types ¶
type AgentSlashConv ¶ added in v0.4.0
type AgentSlashConv struct {
// contains filtered or unexported fields
}
AgentSlashConv is the SlashConv implementation for agent-bridge and web conversations stored in agent_conversations / agent_messages. /compact forwards to the agent container (sets ForwardAsCompact); every other command operates locally against the airlock DB.
func NewAgentSlashConv ¶ added in v0.4.0
func NewAgentSlashConv(q *dbq.Queries, canceler RunCanceler, logger *zap.Logger, agentID uuid.UUID, agentBaseURL func(string) string) *AgentSlashConv
NewAgentSlashConv builds an adapter for the agent-conversation path. canceler dispatches /cancel into the agent dispatcher; nil yields the "Nothing to cancel" path on restart (no live in-memory state). agentID identifies the bound agent so /start can name and link it even before any conversation exists (slash commands don't create one).
func (*AgentSlashConv) Cancel ¶ added in v0.4.0
Cancel — q.GetLatestRunningPromptRun then dispatcher.CancelRun. The HTTP-request abort is what flips the run row out of 'running' (via the agent's r.Complete or the stuck-run sweeper as a backstop).
func (*AgentSlashConv) Clear ¶ added in v0.4.0
Clear writes a checkpoint marker row and advances the conversation's context_checkpoint_message_id. Also best-effort resolves any suspended run for this conversation.
func (*AgentSlashConv) Compact ¶ added in v0.4.0
Compact is a no-op locally: the agent container runs the actual summarization via Sol.Runner.Compact, so we just signal "forward as compact" and let the proxy set ForceCompact=true on the forwarded request.
func (*AgentSlashConv) Echo ¶ added in v0.4.0
Echo flips the conversation's settings.echo flag. Toggle treats unset as off so the first /echo in a chat that's quiet by default always turns echo on.
type BridgeCallback ¶
type BridgeCallback struct {
Data string // opaque payload, e.g. "approve:<runID>"
AckID string // platform-specific ack handle (Telegram callback_query.id)
MessageID string // ID of the message the button is attached to (so we
}
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
// RemoveButtons strips the inline keyboard / component buttons from a
// previously sent message, leaving its text intact. Called after the
// user taps an approve/deny button so the resolved confirmation can't
// be tapped again. Best-effort: errors are logged but not propagated.
RemoveButtons(ctx context.Context, br dbq.Bridge, externalID, messageID string) error
}
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)
ManagedBot *ManagedBotEvent // Telegram managed_bot_created service message (manager bridges only)
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 secrets.Store, hmacSecret, publicURL string, agentBaseURL func(slug string) string, logger *zap.Logger) *BridgeManager
NewBridgeManager creates a BridgeManager. agentBaseURL builds the external URL for an agent's subdomain ({scheme}://{slug}.{domain}[:port]); the Telegram driver needs it to register the web-app menu button.
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) AttachManagedBotIngest ¶ added in v0.4.0
func (m *BridgeManager) AttachManagedBotIngest(fn func(ctx context.Context, managerToken string, botUserID int64, botUsername string) (uuid.UUID, uuid.UUID, error))
AttachManagedBotIngest wires the managed-bot ingest callback (see the field doc). Idempotent; the last set wins.
func (*BridgeManager) AttachSysagent ¶ added in v0.4.0
func (m *BridgeManager) AttachSysagent(s SysagentRuntime)
AttachSysagent wires the sysagent runtime after the router has built it. Idempotent; the last set wins.
func (*BridgeManager) BotTokenForBridge ¶ added in v0.4.0
func (m *BridgeManager) BotTokenForBridge(ctx context.Context, agentID, bridgeID uuid.UUID) (string, error)
BotTokenForBridge returns the decrypted Telegram bot token for (agentID, bridgeID) — only if the bridge belongs to that agent and is of type "telegram". The agent guard is the cross-agent boundary at the lookup layer; HMAC verification of the caller's initData then gates the actual auth. Returns an error on any mismatch so the caller can 401 without leaking which constraint failed.
Used by the airlock proxy's Telegram Web App auth handler.
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) RemoveBridgesByOwner ¶ added in v0.4.0
RemoveBridgesByOwner stops every poller for bridges owned by a specific user. Called from service/users.Delete BEFORE the DB CASCADE removes the bridge rows — otherwise the poller goroutines would keep calling getUpdates against now-deleted bridges until their next transient failure, racing on the bot token with any replacement bridge that happened to land on the same row id.
func (*BridgeManager) ResumeSystemConversation ¶ added in v0.4.0
func (m *BridgeManager) ResumeSystemConversation(ctx context.Context, conversationID, originRunID uuid.UUID) error
ResumeSystemConversation runs a server-initiated auto-resume turn for a bridge-originated system conversation and streams it to the chat through the SAME bridgeSink + driver.SendStream the inbound poller uses. This is the delivery path for a build/upgrade completion follow-up: because it uses the real sink, a gated tool the resume chains into (e.g. create_tg_bot) renders Approve/Reject buttons in the chat — and the normal inbound callback path resumes the run on a tap — instead of silently suspending the way a text-only push would.
The conversation must already carry source="bridge" + bridge_id + external_id (EnsureSystemConversationForBridge refreshes external_id on every inbound turn). Invoked by sysagent's build/upgrade notifier via the BridgeResumer interface; it runs synchronously and the caller drives it from a goroutine.
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 []wire.DisplayPart) error
SendParts sends display parts to a bridge conversation. Looks up the bridge, decrypts the token, and delegates to the driver.
func (*BridgeManager) Start ¶
func (m *BridgeManager) Start(ctx context.Context) error
Start sets up all active bridges and starts pollers.
func (*BridgeManager) StreamToBridge ¶ added in v0.4.0
func (m *BridgeManager) StreamToBridge(ctx context.Context, bridgeID uuid.UUID, externalID string, settingsJSON []byte, events <-chan ResponseEvent) error
StreamToBridge is the single bridge-delivery primitive the completion-resume paths share: it resolves the bridge's driver + echo setting and streams a ResponseEvent channel to the chat via SendStream. The caller produces the events from whatever run source it has and closes the channel — an in-process sysagent run via bridgeSink (BridgeManager.ResumeSystemConversation), or an agent NDJSON stream via StreamNDJSONResponse (api NotifyUpgradeComplete). Because both go through SendStream, text, tool calls, and — crucially — confirmation_required prompts render identically; a gated tool the resume chains into gets Approve/Reject buttons instead of being silently swallowed.
settingsJSON is the conversation's settings blob (drives the per-thread echo override). On a resolution error the channel is drained so the producer goroutine never blocks on a full buffer.
func (*BridgeManager) TeardownBridge ¶ added in v0.4.0
func (m *BridgeManager) TeardownBridge(bridgeID uuid.UUID)
TeardownBridge runs the driver's teardown for a bridge — for Telegram this clears the chat menu button so a deleted/disabled bridge doesn't leave a dead web-app "Open" button behind. Best-effort: it logs and returns on lookup/decrypt/teardown failure so it never blocks an agent or bridge deletion. Call it BEFORE the bridge row is deleted and before RemoveBridge, while the bot token is still resolvable. Uses the manager's own context so the platform API call isn't cut short when the deleting request returns.
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 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) implement it to receive the slash-command registry on activation. Drivers without such a menu simply don't implement it.
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, database *db.DB, containers container.ContainerManager, enc secrets.Store, 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) EnsureRuntime ¶
func (*Dispatcher) FailRouteRun ¶ added in v0.5.0
func (d *Dispatcher) FailRouteRun(runID uuid.UUID, err error)
FailRouteRun terminalizes a route run when reverse proxying cannot establish or maintain the request to the agent runtime.
func (*Dispatcher) ForwardJob ¶ added in v0.5.0
func (d *Dispatcher) ForwardJob(ctx context.Context, job dbq.AgentJob, attempt dbq.AgentJobAttempt) (wire.JobRunResponse, uuid.UUID, error)
ForwardJob attaches a run to a leased attempt before synchronously invoking the exact registered handler version in the agent runtime.
func (*Dispatcher) ForwardPrompt ¶
func (d *Dispatcher) ForwardPrompt(ctx context.Context, p authz.Principal, agentID uuid.UUID, input wire.PromptInput) (io.ReadCloser, uuid.UUID, error)
ForwardPrompt starts app-bound chat in the hosted runtime. App startup only synchronizes its manifest and makes registered capability handlers available.
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 still-live run.
func (*Dispatcher) InvokeRuntime ¶
func (d *Dispatcher) InvokeRuntime(ctx context.Context, agentID uuid.UUID, input wire.RuntimeInvokeRequest) (wire.RuntimeInvokeResponse, error)
InvokeRuntime transports an already-authorized capability invocation. The app borrows the host run; this transport never creates or completes another run.
func (*Dispatcher) RecoverChat ¶
func (d *Dispatcher) RecoverChat(ctx context.Context) error
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.
func (*Dispatcher) SetPromptRuntime ¶
func (d *Dispatcher) SetPromptRuntime(runtime PromptRuntime)
SetPromptRuntime binds the hosted chat service during server construction.
func (*Dispatcher) StartJSExecutor ¶
func (d *Dispatcher) StartJSExecutor(ctx context.Context, runID, token uuid.UUID) (io.ReadWriteCloser, error)
type JobWorker ¶ added in v0.5.0
type JobWorker struct {
// contains filtered or unexported fields
}
JobWorker claims durable attempts under shared PostgreSQL concurrency limits and keeps token-guarded leases alive while the agent handler is executing.
func NewJobWorker ¶ added in v0.5.0
type ManagedBotEvent ¶ added in v0.4.0
type ManagedBotEvent struct {
BotID int64
Username string
// ExternalID is the chat the creation happened in — the exact account +
// device whose client just went through the flow. It's the unambiguous
// reply target for the post-create deep link (no platform_identities
// lookup, so a user with multiple linked Telegram accounts is a non-issue).
ExternalID string
// SenderID is the Telegram user who created the bot (optional sanity check
// against the session owner's linked identities).
SenderID string
}
ManagedBotEvent carries a Telegram `managed_bot_created` service message — a new bot a user created via the manager bot's deep-link flow. Only a manager bridge (is_manager) produces these; HandleEvent turns it into a bridge for the freshly-created bot.
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, agentBaseURL func(string) string, 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). agentBaseURL builds the canonical external web URL included in /start.
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 or does not match the callback's bridge conversation identity, emits one "info" event and returns so the driver's AnswerCallbackQuery can clear the spinner.
func (*PromptProxy) HandleMessage ¶
func (p *PromptProxy) HandleMessage( ctx context.Context, agentID, bridgeID, userID uuid.UUID, externalID string, storeHistory 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.
func (*PromptProxy) TranscribeVoicePlain ¶ added in v0.4.0
func (p *PromptProxy) TranscribeVoicePlain(ctx context.Context, files []BridgeFile) (text string, hasVoice bool, hasNonVoice bool)
TranscribeVoicePlain runs each voice-note file through the configured transcription model and returns the concatenated plain text — used by the sysagent-bridge path where there's no agent container, no per-file S3 key, and tagging transcripts with source keys would be meaningless. hasNonVoice signals that at least one non-voice file was attached so the caller can reject with a "files not supported" reply. Transcription failures degrade gracefully: the bool stays true if any voice file existed, the returned text just omits the failing entries.
type PromptRuntime ¶
type ResponseEvent ¶
type ResponseEvent struct {
Type string // "run_started", "text-delta", "tool-call", "tool-result", "confirmation_required", "compaction_started", "compaction_finished", "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
TokensFreed int // for compaction_finished
CompactionError string // for compaction_finished
Raw []byte // full NDJSON line (for non-text events drivers may need)
// Populated for run and compaction lifecycle events and confirmations.
RunID string
Permission string
Patterns []string
Code string
// Description is the plain-language summary a run_js confirmation carries;
// drivers lead with it instead of the permission name when present.
Description string
}
ResponseEvent represents an NDJSON event from the agent response stream, forwarded to the bridge driver for progressive delivery.
type RunCanceler ¶ added in v0.2.11
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler materializes due cron declarations into the durable job queue. PostgreSQL row locks serialize occurrences across Airlock replicas.
func NewScheduler ¶
func (*Scheduler) ReconcileAgent ¶ added in v0.4.0
func (s *Scheduler) ReconcileAgent(ctx context.Context, agentID uuid.UUID, tokenVersion int64, definitions []wire.JobCronDef) error
ReconcileAgent replaces one runtime generation's cron declarations while retaining operator state and the next occurrence for unchanged work.
func (*Scheduler) ReconcileAgentTx ¶ added in v0.5.0
func (s *Scheduler) ReconcileAgentTx(ctx context.Context, tx pgx.Tx, agentID uuid.UUID, tokenVersion int64, definitions []wire.JobCronDef) error
ReconcileAgentTx reconciles cron declarations as part of a larger manifest transaction. The caller commits the transaction and wakes the scheduler.
type SlashCommand ¶
type SlashCommand = slashcommands.SlashCommand
type SlashCommandResult ¶
type SlashCommandResult = slashcommands.SlashCommandResult
type SlashConv ¶ added in v0.4.0
type SlashConv = slashcommands.SlashConv
type SysagentRuntime ¶ added in v0.4.0
type SysagentRuntime interface {
CancelRun(context.Context, authz.Principal, uuid.UUID) (bool, error)
ControlConversation(context.Context, authz.Principal, uuid.UUID, string, string) (bool, error)
Compact(context.Context, authz.Principal, uuid.UUID) (string, error)
NotifyBotCreated(context.Context, uuid.UUID, uuid.UUID, string) error
ResumePrincipal(context.Context, uuid.UUID, uuid.UUID) (authz.Principal, error)
EnsureBridgeConversation(context.Context, authz.Principal) (dbq.SystemConversation, error)
GetConversation(context.Context, authz.Principal, uuid.UUID) (systemchat.ConversationDetail, error)
RunPromptInline(context.Context, authz.Principal, uuid.UUID, string, string, *bool, string, eventstream.Sink, func(uuid.UUID)) (uuid.UUID, error)
}
SysagentRuntime keeps the runtime orchestrator outside trigger's import graph.
type SysagentSlashConv ¶ added in v0.4.0
type SysagentSlashConv struct {
// contains filtered or unexported fields
}
func NewSysagentSlashConv ¶ added in v0.4.0
func NewSysagentSlashConv(svc SysagentRuntime, p authz.Principal, logger *zap.Logger) *SysagentSlashConv
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.
The HTTP client's Timeout caps the entire request — connect, TLS, upload, server processing, response body read. Sized to comfortably cover the long-poll getUpdates window (timeout=30s server-side) plus TLS/network slack, while still releasing a goroutine if a connection stalls on a short call (sendMessage, getMe, …). Without a ceiling here those short calls can hang indefinitely on a half-open TCP socket — the kind of failure that happens on a local network blip (WG/VPN reconnect, NIC cycle) when the OS hasn't yet noticed the peer is gone.
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) GetManagedBotToken ¶ added in v0.4.0
func (d *TelegramDriver) GetManagedBotToken(ctx context.Context, managerToken string, botUserID int64) (string, error)
GetManagedBotToken fetches the bot token for a managed bot the manager bot just created. Bot API getManagedBotToken takes the new bot's user_id and returns the token directly under `result` (managerToken authenticates the call — it must have can_manage_bots).
func (*TelegramDriver) GetMeFull ¶ added in v0.4.0
func (d *TelegramDriver) GetMeFull(ctx context.Context, token string) (username, name string, botUserID int64, canManageBots bool, err error)
GetMeFull calls getMe and returns the bot's username, display name (first_name — the human-readable name that may contain spaces), stable user id, and can_manage_bots capability. Used where airlock needs the bot identity (to dedupe one-listener-per-bot), its display name (the bridge name shown in the UI), and the manager capability (to gate the is_manager behavior).
func (*TelegramDriver) GetMenuButton ¶ added in v0.4.0
func (d *TelegramDriver) GetMenuButton(ctx context.Context, token string) (TelegramMenuButton, error)
GetMenuButton reads the bot's default chat menu button. Airlock uses the default button rather than per-chat buttons, so chat_id is intentionally omitted.
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) RemoveButtons ¶ added in v0.2.11
func (d *TelegramDriver) RemoveButtons(ctx context.Context, br dbq.Bridge, externalID, messageID string) error
RemoveButtons strips the inline keyboard from a previously sent message via editMessageReplyMarkup. The message text is left intact so the conversation history still shows what was being confirmed.
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 []wire.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)
func (*TelegramDriver) SetMenuButton ¶ added in v0.4.0
func (d *TelegramDriver) SetMenuButton(ctx context.Context, token, url string) error
SetMenuButton configures the bot's default chat menu button to launch a Telegram Web App at the given URL. The button is persistent — it shows for every private chat the bot is in, opens the URL in Telegram's in-app browser, and exposes initData to the page so airlock can authenticate the user automatically.
Passing url=="" clears the bot back to Telegram's default menu (the commands list). Each call re-publishes the button at Telegram, but repeated writes of the same value are safe.
func (*TelegramDriver) Teardown ¶
Teardown clears the bot's chat menu button so a deleted or disabled bridge doesn't leave a dead web-app "Open" button in Telegram (setChatMenuButton is bot-global server-side state that otherwise persists). br.BotTokenRef must be the decrypted bot token — BridgeManager.TeardownBridge resolves it.
type TelegramMenuButton ¶ added in v0.4.0
TelegramMenuButton is the subset of Telegram's ChatMenuButton shape Airlock needs to converge the persistent Web App entrypoint.
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 secrets.Store) TranscriptionResolver
NewTranscriptionResolver returns a resolver that reads system_settings.default_stt_provider_id + default_stt_model and looks up the associated provider row credentials.