Documentation
¶
Overview ¶
Package notify is §10's local notifications: the OS notifier, the mapping from engine event kinds to a short title and one-line body, the delivery loop that walks the event log once per event, and the optional webhook (notify.webhook) that POSTs the same moments as plain JSON.
The package is deliberately generic — nothing here knows how events are produced, only how the log's rows read. Further remote channels (Telegram and the like) ride the relay a webhook already covers: the POST is plain JSON, with no per-service adapters.
Index ¶
Constants ¶
const ( KindPlanDone = "plan_done" // the plan's last open task finished (§6.2) KindTaskCheckFailed = "task_check_failed" // the check failed; the task waits for the foreman (§3) KindTaskNeedsHelp = "task_needs_help" // the worker asked its question (§3) KindTaskFailed = "task_failed" // the run failed; the foreman decides (§3) KindReviewChanges = "review_changes" // the reviewer wants changes (§7) KindReviewNoVerdict = "review_no_verdict" // the review ended without a usable verdict (§7) KindTaskOutOfRounds = "task_out_of_rounds" // the task spent its rounds; arrives with the rounds work KindForemanReport = "foreman_report" // a done foreman turn's final message, its report to the user (§3, §10) KindForemanLimited = "foreman_limited" // the foreman's CLI hit its subscription limit (§4.4, §10) KindForemanWakeBudgetSpent = "foreman_wake_budget_spent" // the day's foreman wake budget is spent (§4.3, §10) KindAgentBroken = "agent_broken" // an agent was marked broken (§8 "Health", §10) KindTaskUnattended = "task_unattended" // a task parked at needs_foreman that the foreman does not hold (§10) // KindReviewUnservable: a task's queued review can never be taken — // no agent of another family offers review (§7, §10). KindReviewUnservable = "review_unservable" // KindPlanAwaitingApproval: a plan is over the approval limit and // waits for the user's yes (§6.3, §10). KindPlanAwaitingApproval = "plan_awaiting_approval" )
The event kinds the engine's log can carry that wake the user (§10). The set is the notification vocabulary: a plan finishing, the foreman's report after a headless turn, the foreman's limit and its budget out (§4.4, §4.3), and the reasons a task parks at needs_foreman — the same kinds the "needs you" queue reads in `brigada status`. Everything else in the log never notifies.
const ( ModeAuto = "auto" ModeNone = "none" )
The config file's notify values (lifecycle's FileConfig): auto picks the OS tool on PATH, none silences notifications. Anything else is refused when the config is read.
Variables ¶
This section is empty.
Functions ¶
func Message ¶
Message maps one event to its notification's title and one-line body (§10). ok is false for every event kind that never wakes the user — the log's routine entries — which the delivery loop skips while still advancing its cursor past them.
func ValidWebhookURL ¶
ValidWebhookURL reports whether raw is an https URL with a host — the shape a relay publishes — or an http one on a loopback host, where nothing crosses a network. The URL may carry a token, so plain http is refused anywhere else. The config reader refuses anything else, so a typo cannot point notifications nowhere.
Types ¶
type Event ¶
type Event struct {
ID int64
Kind string
TaskID int64 // 0 when the event names no task
PlanID int64 // 0 when the event names no plan
PlanTitle string
// JobID is the foreman job a foreman_report names (§9): the id the
// pointer to the whole text names. 0 when the event names no job.
JobID int64
// Project is the event's project repo URL — the log's own project
// reference first, else the plan its task or payload names. Empty
// when no referent survives; the webhook carries it as-is.
Project string
// At is the log's created_at stamp: the time the webhook reports.
At string
// Reason is the payload's words for why the user is woken — the
// check's tail, the worker's question, the review's findings —
// already picked for the kind and flattened to one line, except a
// report's, whose own lines the mapper cuts to the first.
Reason string
// Parked is true when the event parked its task (its payload names
// needs_foreman or needs_user as the task's state); ParkedAtUser is
// true when that state is needs_user. A needs-you kind wakes the user
// only for a park at needs_user: a park at needs_foreman is the
// foreman's, and task_unattended speaks when nobody handles it.
Parked bool
ParkedAtUser bool
// ParkKind is the kind of the parking event a task_unattended names;
// the webhook's text reads it to keep a check's output off the relay.
ParkKind string
}
Event is one log row in the shape the mapper needs: the delivery loop resolves the event's references (the task's plan, the title) and its payload's reason before mapping, so Message stays pure.
type Fake ¶
type Fake struct {
// Err is returned by every Notify while set.
Err error
// contains filtered or unexported fields
}
Fake is the recording notifier the tests stand in for the OS tools: every Notify appends to its list. Err, when set, is what every Notify returns — the delivery-loop tests use it to play a broken notifier.
type Loop ¶
type Loop struct {
// contains filtered or unexported fields
}
Loop is the delivery loop (§13): it polls the event log for rows past a persisted cursor and notifies each mapped event once. The cursor lives in the store's meta row, so a restart resumes exactly after the last delivered event — never re-notifying old ones, never missing new ones — and advances even past events whose notification failed, so one broken banner never repeats forever.
func NewLoop ¶
NewLoop returns a delivery loop over st, delivering through n. A nil logger keeps slog's default.
func (*Loop) Flush ¶
Flush delivers everything pending, in passes of batch events: read the cursor and the events past it, notify the mapped ones — advancing the cursor after each event — then read again, until a pass finds nothing or the cap or ctx says stop. A failing notifier is logged and does not stop the loop; the store is what fails the flush.
func (*Loop) WithWebhook ¶
WithWebhook adds the optional webhook sink (§10): every notification the banner fires is also queued for one small JSON POST to the URL, beside the OS notification, never instead of it. nil h keeps the loop local-only. The posts ride a background worker on their own context, not the engine's tick: the loop queues and moves on, so a relay that hangs, fails or backs up never delays the sweep, the snapshot or the idle exit — fire and forget.
type None ¶
type None struct{}
None is the notifier for a host with no banner tool, or for `notify = none`: it drops every notification and reports success, so the delivery loop above it runs unchanged — the cursor keeps advancing and flipping the config to auto later never re-notifies ancient events.
type Notifier ¶
Notifier delivers one local notification (§10). Title is the short name the OS banner shows bold; body is one line of detail. ctx bounds the attempt: shutdown and the delivery loop's per-pass deadline cancel it, and implementations cap it regardless, so a hung banner tool never stalls the engine's tick or its idle exit. An error means the banner did not reach the OS; the delivery loop logs it and goes on, so one broken notification never stalls or repeats forever.
type NotifySend ¶
type NotifySend struct {
// Tool is the notify-send path; empty keeps "notify-send" (PATH lookup).
Tool string
}
NotifySend is the Linux notifier: it calls notify-send, which takes the title and body as plain arguments — no shell, no interpolation.
func (NotifySend) Name ¶
func (n NotifySend) Name() string
Name is the log name: the tool it shells out to.
type Osascript ¶
type Osascript struct {
// Tool is the osascript path; empty keeps "osascript" (PATH lookup).
Tool string
}
Osascript is the macOS notifier: it asks osascript to display a notification. Title and body reach the script only as the run handler's argv — after `--`, as whole arguments — never interpolated into the script source, so no text in a plan or task title can become AppleScript (TestOsascriptArgsGolden in notify_test.go pins the argv golden).
type Webhook ¶
type Webhook struct {
// URL is the relay's http or https address.
URL string
// Client posts; nil uses the default. Tests inject one.
Client *http.Client
// Timeout caps one attempt; zero keeps webhookTimeout.
Timeout time.Duration
}
Webhook POSTs one small JSON document per delivered notification to a single URL (§10), set by the config file's notify.webhook — never by the project's own files, for the URL may carry a token. The URL is the one place the token lives: errors are stripped of it before they surface, so it never reaches a log line, the status or the history.