Documentation
¶
Overview ¶
Package notices assembles the per-user session-start briefing that platform_info carries (#1278).
The platform already tells a session about pending insights. Nothing told a person that feedback had been left on work they own, or that something new had been shared with them: the portal activity feed was the only surface that carried either, so a user who works through MCP sessions and never opens the portal discovered neither. This package answers one question for one caller — what is waiting for you that you have not been shown — and platform_info attaches the answer to the first call of the session.
"Have not been shown" is a watermark, not a read receipt: the digest advances it as it is delivered, so what a session is told about is not repeated to the next one. That makes delivery a single shot, which is why the agent instructions that ship alongside tell the agent to relay the digest rather than act on it silently.
Construction takes explicit inputs — the portal asset, share and thread stores plus a watermark store — so the digest is buildable and testable without a Platform. New returns nil when any of them is missing (no database, no portal), and every method on a nil Handle answers empty, so a deployment without a portal simply carries no notices.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AutomationNotice ¶ added in v1.137.1
type AutomationNotice struct {
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
// Reference dereferences the automation, for `fetch`.
Reference string `json:"reference"`
// Version is the version the failed run executed.
Version int `json:"version"`
// RunID is the failed run, for manage_script get_run.
RunID string `json:"run_id"`
// Cause is why it failed (script, upstream, transient, memory, ...), and
// Retryable whether running it again is expected to succeed.
Cause string `json:"cause"`
Retryable bool `json:"retryable"`
// Error is the failure's last line, which names what went wrong.
Error string `json:"error,omitempty"`
// ConsecutiveFailures is how many finished runs in a row have failed.
ConsecutiveFailures int `json:"consecutive_failures"`
// FailedAt is when the failed run finished, RFC3339.
FailedAt string `json:"failed_at,omitempty"`
// LastSucceededAt is when a run of it last succeeded, RFC3339; absent
// when none has.
LastSucceededAt string `json:"last_succeeded_at,omitempty"`
// Scheduled is whether a schedule is set to fire it again.
Scheduled bool `json:"scheduled"`
// New is whether it failed since the caller was last briefed.
New bool `json:"new"`
}
AutomationNotice is one automation the caller owns whose latest finished run failed (#1934). It is listed for as long as the automation keeps failing, because the problem it reports is not solved by having been told; New marks the ones whose latest failure arrived since the caller was last briefed.
type AutomationSource ¶ added in v1.137.1
type AutomationSource interface {
List(ctx context.Context, filter script.ListFilter) ([]script.Script, error)
ListSchedules(ctx context.Context, filter script.ScheduleFilter) ([]script.Schedule, error)
runstate.FailureStreakReader
}
AutomationSource is what the briefing reads a caller's automations from: their scripts, which of those are scheduled, and how each one's recent runs stand.
type Digest ¶
type Digest struct {
// Since is the watermark this digest covers, RFC3339. Everything reported
// arrived after it.
Since string `json:"since"`
// Feedback lists unresolved threads on assets the caller owns, newest
// activity first, capped at maxFeedbackNotices.
Feedback []FeedbackNotice `json:"feedback,omitempty"`
// FeedbackTotal is how many such threads exist. It exceeds len(Feedback)
// when the cap truncated the list, so the agent can say how many were not
// named rather than implying the list is complete.
FeedbackTotal int `json:"feedback_total,omitempty"`
// newest first, capped at maxShareNotices.
NewShares []ShareNotice `json:"new_shares,omitempty"`
// The share query is bounded rather than counted, so the digest reports
// "at least this many" rather than presenting a truncated list as the whole
// set -- an unknown remainder is its own state, not zero.
NewSharesTruncated bool `json:"new_shares_truncated,omitempty"`
// FailingAutomations lists the automations the caller owns whose latest
// finished run failed, newest failure first, capped at
// maxAutomationNotices (#1934). Unlike the other lists it is not bounded
// by the watermark: an automation that is still failing is still listed.
//
// It is not part of the notices block's JSON (#1971): releases up to
// v1.137.1 advertised that block as a closed object, and a client still
// holding one of those tool lists rejects a key it does not declare.
// platform_info carries the list at its top level, which every release
// since v1.102.0 has advertised open; see Failing.
FailingAutomations []AutomationNotice `json:"-"`
// FailingAutomationsTotal is how many there are, which exceeds the list
// when the cap cut it.
FailingAutomationsTotal int `json:"-"`
}
Digest is one caller's session-start briefing: feedback left on work they own and artifacts newly shared with them, both bounded to what has arrived since they were last briefed. It is nil, rather than empty, when there is nothing to say, so platform_info omits the block entirely.
func (*Digest) Counts ¶
Counts returns how many feedback threads, new shares and failing automations the digest reports. Feedback and automations are true totals; shares is what the list holds, which NewSharesTruncated marks as a floor. It is what the agent instructions are sized from, and answers zeros for a nil digest so the caller needs no nil check.
func (*Digest) Failing ¶ added in v1.138.1
func (d *Digest) Failing() (list []AutomationNotice, total int)
Failing returns the failing automations and how many there are, and answers none for a nil digest so the caller needs no nil check.
type FeedbackNotice ¶
type FeedbackNotice struct {
ThreadID string `json:"thread_id"`
// Kind is the thread's classification (comment, question, correction, ...).
Kind string `json:"kind"`
Status string `json:"status"`
Title string `json:"title,omitempty"`
// AuthorEmail is who opened the thread; never the caller.
AuthorEmail string `json:"author_email,omitempty"`
AssetID string `json:"asset_id"`
AssetName string `json:"asset_name,omitempty"`
// AssetReference dereferences the asset the thread is on, for `fetch`.
AssetReference string `json:"asset_reference"`
// LastActivityAt is when the thread last moved, RFC3339.
LastActivityAt string `json:"last_activity_at"`
}
FeedbackNotice is one unresolved feedback thread on an asset the caller owns, left by someone else. It carries enough to relay the thread without a further call, and the reference needed to read it in full.
type Handle ¶
type Handle struct {
// contains filtered or unexported fields
}
Handle assembles a caller's session-start digest from the portal stores and the notice watermark. A nil Handle builds nothing, which is what a deployment without a database or without the portal holds.
func New ¶
func New(db *sql.DB, assets portaldomain.AssetStore, shares portaldomain.ShareStore, ts threads.ThreadStore, scripts AutomationSource, ) *Handle
New builds the digest assembler over db and the portal stores. It returns nil when any portal input is missing, so a caller wires it unconditionally and every deployment that lacks a piece simply carries no notices. A nil scripts briefs no failing automations.
func (*Handle) Build ¶
func (h *Handle) Build(ctx context.Context, pc *middleware.PlatformContext) *Digest
Build returns the caller's digest, or nil when there is nothing to brief them on. Delivering a digest advances the caller's watermark, so what one session is told is not repeated to the next; a digest assembled from a failed query is still returned but does NOT advance the watermark, so a transient database error delays a notice rather than swallowing it.
Orientation must not fail because a notice could not be assembled, so every error here is logged and swallowed.
type PostgresWatermarkStore ¶
type PostgresWatermarkStore struct {
// contains filtered or unexported fields
}
PostgresWatermarkStore is the user_notice_watermarks-backed WatermarkStore.
func NewPostgresWatermarkStore ¶
func NewPostgresWatermarkStore(db *sql.DB) *PostgresWatermarkStore
NewPostgresWatermarkStore returns a watermark store over db.
func (*PostgresWatermarkStore) Get ¶
Get returns when this caller was last briefed, or nil if never.
func (*PostgresWatermarkStore) Now ¶ added in v1.123.0
Now returns the database server's current time, which is the clock every timestamp a digest compares against was stamped from.
type ShareNotice ¶
type ShareNotice struct {
// Kind is asset, collection, or prompt.
Reference string `json:"reference"`
// owner for a legacy row that recorded no creator.
SharedBy string `json:"shared_by,omitempty"`
SharedAt string `json:"shared_at"`
Permission string `json:"permission,omitempty"`
}
ShareNotice is one artifact newly shared with the caller.
type WatermarkStore ¶
type WatermarkStore interface {
// Now returns the clock the watermark is kept on.
//
// A digest compares its boundary against timestamps the database stamped
// (portal_shares.created_at defaults to NOW()), so the boundary has to be
// read from that same clock. Stamping it from the process clock instead
// compares two clocks: a few milliseconds of skew between the application
// host and the database server is enough to place an already-delivered
// share after the watermark and read it out to the caller a second time.
Now(ctx context.Context) (time.Time, error)
Get(ctx context.Context, userKey string) (*time.Time, error)
Set(ctx context.Context, userKey string, at time.Time) error
}
WatermarkStore records, per caller, when a notice digest was last delivered to them. Get answers nil (not an error) for a caller who has never been briefed; Set is what makes delivery single-shot.