Documentation
¶
Index ¶
- Constants
- Variables
- func ValidStatus(s Status) bool
- type ActionFunc
- type ActionKind
- type Handler
- type Manager
- func (m *Manager) ExpirePending(ctx context.Context) (int, error)
- func (m *Manager) Get(ctx context.Context, id string) (*Request, error)
- func (m *Manager) GetByCallbackData(ctx context.Context, callbackData string) (*Request, error)
- func (m *Manager) List(ctx context.Context, status Status) ([]Request, error)
- func (m *Manager) Resolve(ctx context.Context, id string, approved bool, resolvedBy string) (*Request, error)
- func (m *Manager) ResolveByCallback(ctx context.Context, callbackData string, resolvedBy string) (*Request, error)
- func (m *Manager) StartExpiryWorker(ctx context.Context, interval time.Duration)
- func (m *Manager) Submit(ctx context.Context, agentName string, kind ActionKind, summary string, ...) (*Request, error)
- type Registry
- type Request
- type SQLiteStore
- func (s *SQLiteStore) Close() error
- func (s *SQLiteStore) Create(ctx context.Context, req Request) (string, error)
- func (s *SQLiteStore) ExpireBefore(ctx context.Context, deadline time.Time) (int, error)
- func (s *SQLiteStore) ExpirePending(ctx context.Context) (int, error)
- func (s *SQLiteStore) Get(ctx context.Context, id string) (*Request, error)
- func (s *SQLiteStore) GetByCallbackData(ctx context.Context, callbackData string) (*Request, error)
- func (s *SQLiteStore) List(ctx context.Context, status Status) ([]Request, error)
- func (s *SQLiteStore) Resolve(ctx context.Context, id string, status Status, resolvedBy string) error
- func (s *SQLiteStore) ResolveByCallbackPrefix(ctx context.Context, prefix string, status Status, resolvedBy string) (*Request, error)
- type Status
- type Store
Constants ¶
const DefaultTTL = 24 * time.Hour
DefaultTTL is the time an approval request stays pending before it is automatically expired by the background worker.
Variables ¶
var ( ErrNotFound = errors.New("approval: not found") ErrAlreadyResolved = errors.New("approval: already resolved") )
Sentinel errors returned by Store implementations.
var ErrStaleCallback = fmt.Errorf("approval: callback refers to a non-pending request")
ErrStaleCallback is returned by ResolveByCallback when the callback refers to an approval that exists but is no longer pending (already resolved, expired, or approved). The caller should surface its Status to the user.
Functions ¶
func ValidStatus ¶
ValidStatus reports whether s is one of the four known status values. An empty string is also accepted (means "all" in list queries).
Types ¶
type ActionFunc ¶
ActionFunc is the callback invoked when an approval is resolved. It receives the stored payload and performs the approved action.
type ActionKind ¶
type ActionKind string
ActionKind categorises what kind of action is awaiting approval.
const ( // ActionKindUserUpdate is a request to update the agent's USER.md persona file. ActionKindUserUpdate ActionKind = "user_update" // ActionKindCreateSkill is a request to create a new skill file in the agent's skills directory. ActionKindCreateSkill ActionKind = "create_skill" // ActionKindModifySchedule is a request to register a new schedule entry at runtime. ActionKindModifySchedule ActionKind = "modify_schedule" // ActionKindInstallTool is a request to add or remove an MCP tool or plugin at runtime. ActionKindInstallTool ActionKind = "install_tool" // ActionKindModifyConfig is a request to change a runtime configuration setting (e.g. fallback rules). ActionKindModifyConfig ActionKind = "modify_config" )
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler implements the adapter.CallbackResolver interface for approval callbacks. It maps Telegram inline keyboard button data (e.g. "appr:{id}:approve") to human-readable confirmation strings by delegating resolution to the Manager.
It satisfies adapter.CallbackResolver without importing that package:
var _ adapter.CallbackResolver = (*Handler)(nil)
func NewCallbackHandler ¶
NewCallbackHandler returns a Handler that resolves approval callbacks via m.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager coordinates the persistent Store with the in-memory action Registry. It is the primary API used by the Engine and REST API server.
func NewManager ¶
NewManager creates a Manager backed by the given store.
func (*Manager) ExpirePending ¶
ExpirePending expires all pending approvals. Call at startup.
func (*Manager) GetByCallbackData ¶
GetByCallbackData fetches an approval by its callback_data prefix regardless of status. Used to provide informative feedback when a user clicks an already-resolved or expired Telegram button.
func (*Manager) Resolve ¶
func (m *Manager) Resolve(ctx context.Context, id string, approved bool, resolvedBy string) (*Request, error)
Resolve marks an approval as approved or denied and, if approved, invokes the registered action closure. Returns the updated Request.
func (*Manager) ResolveByCallback ¶
func (m *Manager) ResolveByCallback(ctx context.Context, callbackData string, resolvedBy string) (*Request, error)
ResolveByCallback parses the full Telegram callback data string ("appr:{id}:approve" or "appr:{id}:deny"), resolves the approval, and returns the updated Request. Returns ErrNotFound for unknown callbacks, ErrStaleCallback when the approval is no longer pending.
func (*Manager) StartExpiryWorker ¶
StartExpiryWorker starts a background goroutine that expires pending approvals whose TTL has elapsed. It ticks every interval until ctx is cancelled. Expired closures are removed from the in-memory registry. Safe to call once per process lifetime.
func (*Manager) Submit ¶
func (m *Manager) Submit( ctx context.Context, agentName string, kind ActionKind, summary string, payload string, externalID string, adapterName string, conversationID string, action ActionFunc, ) (*Request, error)
Submit creates a new pending approval, registers the action closure, and returns the persisted Request with its ID populated. The request expires after DefaultTTL if not resolved.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds in-memory action closures keyed by approval ID. These are ephemeral: on restart the registry is empty, and ExpirePending ensures no stale DB entries are left in "pending" state.
func (*Registry) Pop ¶
func (r *Registry) Pop(id string) (ActionFunc, bool)
Pop retrieves and removes the action for the given ID atomically. Returns (nil, false) if no action is registered for that ID.
func (*Registry) Register ¶
func (r *Registry) Register(id string, fn ActionFunc)
Register stores an action closure under the given ID.
type Request ¶
type Request struct {
ID string `db:"id" json:"id"`
AgentName string `db:"agent_name" json:"agent_name"`
Kind ActionKind `db:"kind" json:"kind"`
Status Status `db:"status" json:"status"`
// Summary is a human-readable one-liner shown in the approval UI.
Summary string `db:"summary" json:"summary"`
// Payload is the content to apply when approved (e.g. full USER.md text).
Payload string `db:"payload" json:"payload"`
// CallbackData is the base prefix embedded in Telegram inline button data.
// Format: "appr:{id}" — buttons append ":approve" or ":deny".
CallbackData string `db:"callback_data" json:"callback_data,omitempty"`
// ExternalID is the adapter-level chat/channel ID to reply to after resolution.
ExternalID string `db:"external_id" json:"external_id"`
// AdapterName identifies which adapter to use for confirmation messages.
AdapterName string `db:"adapter_name" json:"adapter_name"`
// ConversationID links this approval to the engine conversation that created it.
ConversationID string `db:"conversation_id" json:"conversation_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
ExpiresAt *time.Time `db:"expires_at" json:"expires_at,omitempty"`
ResolvedAt *time.Time `db:"resolved_at" json:"resolved_at,omitempty"`
// ResolvedBy records who resolved the approval: "telegram", "api", or "expired".
ResolvedBy string `db:"resolved_by" json:"resolved_by,omitempty"`
}
Request is the persisted record of a pending or resolved approval.
type SQLiteStore ¶
type SQLiteStore struct {
// contains filtered or unexported fields
}
SQLiteStore implements Store using SQLite.
func NewInMemoryStore ¶
func NewInMemoryStore() (*SQLiteStore, error)
NewInMemoryStore creates an in-memory SQLite approval store (for testing).
func NewSQLiteStore ¶
func NewSQLiteStore(dbPath string) (*SQLiteStore, error)
NewSQLiteStore opens or creates a SQLite database at the given path and applies the approval schema. The file is opened with WAL mode so it can coexist with the memory store's connection to the same file.
func (*SQLiteStore) Close ¶
func (s *SQLiteStore) Close() error
func (*SQLiteStore) ExpireBefore ¶
func (*SQLiteStore) ExpirePending ¶
func (s *SQLiteStore) ExpirePending(ctx context.Context) (int, error)
func (*SQLiteStore) GetByCallbackData ¶
func (*SQLiteStore) ResolveByCallbackPrefix ¶
type Store ¶
type Store interface {
// Create persists a new approval request and returns the assigned ID.
Create(ctx context.Context, req Request) (string, error)
// Get fetches a single approval by ID. Returns ErrNotFound if absent.
Get(ctx context.Context, id string) (*Request, error)
// GetByCallbackData fetches a single approval by its callback_data prefix,
// regardless of status. Returns ErrNotFound if absent.
GetByCallbackData(ctx context.Context, callbackData string) (*Request, error)
// List returns approvals filtered by status. Pass an empty string for all.
List(ctx context.Context, status Status) ([]Request, error)
// Resolve transitions the status of a pending approval.
// Returns ErrNotFound if the ID does not exist.
// Returns ErrAlreadyResolved if the approval is not currently pending.
Resolve(ctx context.Context, id string, status Status, resolvedBy string) error
// ResolveByCallbackPrefix looks up by callback_data prefix, then resolves.
// Returns ErrNotFound if no pending approval matches.
ResolveByCallbackPrefix(ctx context.Context, prefix string, status Status, resolvedBy string) (*Request, error)
// ExpirePending marks all pending approvals as expired. Call at startup.
// Returns the number of rows affected.
ExpirePending(ctx context.Context) (int, error)
// ExpireBefore marks all pending approvals whose expires_at is before
// deadline as expired. Used by the background expiry worker.
// Returns the number of rows affected.
ExpireBefore(ctx context.Context, deadline time.Time) (int, error)
Close() error
}
Store defines the persistence interface for approval requests. In-memory action closures are managed separately by the Registry, since closures cannot be serialised. On restart, any pending rows are expired by ExpirePending so stale entries are never silently lost.