Documentation
¶
Overview ¶
Package events is the wake-on-event inbox: external events (a mail reply, a CI failure) enter the server, are stored durably, and are either injected into the project's active session or wait in the inbox for the owner to decide. It owns the model and the store only; routing and injection live in pkg/serve.
Index ¶
- Constants
- Variables
- func NewID() string
- type Event
- type ParsedHook
- type Store
- func (s *Store) Add(ev Event) (Event, bool, error)
- func (s *Store) DismissSource(source string) (int, error)
- func (s *Store) Get(id string) (Event, bool)
- func (s *Store) List(state string) []Event
- func (s *Store) MarkDismissed(id string) (Event, error)
- func (s *Store) MarkRouted(id, sessionID string) (Event, error)
- func (s *Store) MarkRouting(id string) (Event, error)
- func (s *Store) ReleaseRouting(id string) (Event, error)
- func (s *Store) SetPendingReason(id, reason string) (Event, error)
- func (s *Store) SetSuggested(id, sessionID string) error
Constants ¶
const ( StateNew = "new" StateRouting = "routing" StateRouted = "routed" StateDismissed = "dismissed" )
Event states. An event has exactly one place: while `new` it is in the inbox, once `routed` it is the message in a session, and `dismissed` is the end of the line. There is no "read".
const ( MaxSourceBytes = 64 MaxTitleBytes = 200 MaxBodyBytes = 256 << 10 MaxPayloadBytes = 64 << 10 MaxKeyBytes = 256 )
Field limits. The body as a whole is capped by the HTTP layer; a single oversized field is still harmful on its own (a 1 MiB title would pollute every inbox render).
const ( PendingInbox = "inbox" PendingNoSession = "no_session" PendingManySessions = "many_sessions" PendingSessionBusy = "session_busy" PendingRateLimited = "rate_limited" )
PendingReason values. Stable API tokens; the client maps them to copy.
Variables ¶
var ErrNotFound = errors.New("event not found")
ErrNotFound reports an unknown event id.
var ErrSettled = errors.New("event already settled")
ErrSettled reports an event that has already been routed or dismissed, so it cannot be acted on a second time (a double tap, or a retried request).
Functions ¶
Types ¶
type Event ¶
type Event struct {
ID string `json:"id"`
Key string `json:"key,omitempty"` // idempotency key, e.g. "agentmail:<message_id>"
Source string `json:"source"` // "agentmail" | "ci" | free-form label
Project string `json:"project"` // canonical cwd
Title string `json:"title"`
Body string `json:"body,omitempty"`
// Payload is opaque caller data. It is stored and returned but never
// injected into a session: it is bookkeeping for the emitter, not something
// an agent should be asked to read.
Payload json.RawMessage `json:"payload,omitempty"`
Created time.Time `json:"created"`
State string `json:"state"`
RoutedTo string `json:"routed_to,omitempty"`
RoutedAt time.Time `json:"routed_at,omitzero"`
// Suggested is the session the inbox would send this event to, computed at
// ingress so the card can name it. Empty when the project had no candidate.
Suggested string `json:"suggested,omitempty"`
// Autorun / Create* snapshot the source policy at ingest so a later config
// change (or a deleted source) cannot start a turn the owner had not opted
// into. Missing source → these stay at their zero values (autorun false).
Autorun bool `json:"autorun,omitempty"`
CreateModel string `json:"create_model,omitempty"`
CreateThinking string `json:"create_thinking,omitempty"`
CreateYolo bool `json:"create_yolo,omitempty"`
CreateTitle string `json:"create_title,omitempty"`
// PendingReason is why a `new` event is still in the inbox, set at the
// routing decision so the row can say so without the owner guessing.
PendingReason string `json:"pending_reason,omitempty"`
}
Event is one external notification addressed to a project (a canonical cwd).
type ParsedHook ¶
ParsedHook is the title, body and idempotency key extracted from an arbitrary webhook payload.
func ParseHookBody ¶
func ParseHookBody(source string, raw []byte) ParsedHook
ParseHookBody extracts a title, a body and a dedupe key from a hook payload. JSON objects look at title|subject|summary|event|message for a title and at id|event_id|key for a provider id; anything else is pretty-printed (or kept as text) and keyed by the SHA-256 of the raw bytes.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a thread-safe, disk-backed inbox of events. Single-user personal use: one file, loaded at startup, rewritten atomically on every change.
func (*Store) Add ¶
Add stores an event and reports whether it was created now. An event whose (Source, Key) matches a stored one is NOT stored again: the existing event comes back with created=false, so a redelivered webhook neither injects a second message nor sends a second push.
func (*Store) DismissSource ¶
DismissSource settles every pending event from source. Already routed or dismissed rows are left alone — history is not rewritten.
func (*Store) List ¶
List returns the stored events in the given state, newest first. An empty state lists everything.
func (*Store) MarkDismissed ¶
MarkDismissed settles an event without sending it anywhere.
func (*Store) MarkRouted ¶
MarkRouted settles a claimed event onto a session. Only `routing` can become `routed`; a `new` event must be claimed first.
func (*Store) MarkRouting ¶
MarkRouting CAS-claims a `new` event for delivery. A second claim (two concurrent route requests) reports ErrSettled so the message is injected at most once. Persist this transition before sending.
func (*Store) ReleaseRouting ¶
ReleaseRouting returns a claimed event to `new` after a failed delivery.
func (*Store) SetPendingReason ¶
SetPendingReason records why a `new` event stayed in the inbox. Settled events are left alone — the reason only applies while a decision is open.
func (*Store) SetSuggested ¶
SetSuggested updates the session an inbox card offers to send to. Used when the suggestion is recomputed after ingress; a settled event is left alone.