Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 1 | /** |
| 2 | * The inbox: what needs a person, or what they follow, as it happens. The | |
| Inbox: threads, reasons, subscriptions and watching | 3 | * events service keeps it and brings each person's thread about a thing |
| 4 | * (an issue, a pull request, a workflow on a branch, a deployment) back to | |
| 5 | * the top as things happen to it. Mirrors `crates/contracts/src/inbox.rs`, | |
| 6 | * which says who is told of what, and why. | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 7 | */ |
| 8 | import type { ServiceBinding } from "./clients"; | |
| 9 | import type { User } from "./identity"; | |
| 10 | ||
| 11 | /** | |
| 12 | * How much an item matters: a failure, something a person must answer (an | |
| Inbox: threads, reasons, subscriptions and watching | 13 | * agent waiting on them, a review asked of them), something that went well, |
| 14 | * or something to know. | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 15 | */ |
| 16 | export type InboxSeverity = "error" | "warning" | "success" | "info"; | |
| 17 | ||
| 18 | export const INBOX_SEVERITIES: InboxSeverity[] = ["error", "warning", "success", "info"]; | |
| 19 | ||
| Inbox: threads, reasons, subscriptions and watching | 20 | /** |
| 21 | * Why a person was told: what the thread asked of them, or what ties them | |
| 22 | * to it. Most specific first. | |
| 23 | */ | |
| 24 | export type InboxReason = | |
| 25 | | "agent" | |
| 26 | | "review_requested" | |
| 27 | | "assign" | |
| 28 | | "mention" | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 29 | | "team_mention" |
| Inbox: threads, reasons, subscriptions and watching | 30 | | "ci_activity" |
| 31 | | "security_alert" | |
| 32 | | "state_change" | |
| 33 | | "author" | |
| 34 | | "comment" | |
| 35 | | "manual" | |
| 36 | | "subscribed"; | |
| 37 | ||
| 38 | export const INBOX_REASONS: InboxReason[] = [ | |
| 39 | "agent", | |
| 40 | "review_requested", | |
| 41 | "assign", | |
| 42 | "mention", | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 43 | "team_mention", |
| Inbox: threads, reasons, subscriptions and watching | 44 | "ci_activity", |
| 45 | "security_alert", | |
| 46 | "state_change", | |
| 47 | "author", | |
| 48 | "comment", | |
| 49 | "manual", | |
| 50 | "subscribed", | |
| 51 | ]; | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 52 | |
| Inbox: threads, reasons, subscriptions and watching | 53 | export type InboxSubjectKind = "issue" | "pull" | "run" | "deploy"; |
| 54 | ||
| 55 | /** One thread in a person's inbox: what it is about, and its latest activity. */ | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 56 | export type InboxItem = { |
| Inbox: threads, reasons, subscriptions and watching | 57 | /** The thread's id: the same for as long as the person has it. */ |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 58 | id: string; |
| Inbox: threads, reasons, subscriptions and watching | 59 | /** Why they were told of the latest activity. */ |
| 60 | reason: InboxReason; | |
| 61 | /** While unread, the most urgent of what happened since it was last read. */ | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 62 | severity: InboxSeverity; |
| Inbox: threads, reasons, subscriptions and watching | 63 | /** One line: what happened last, and where. */ |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 64 | title: string; |
| 65 | /** One line: what it happened to, such as the pull request's title. */ | |
| 66 | body: string; | |
| Inbox: threads, reasons, subscriptions and watching | 67 | /** The event behind the latest activity, such as `pull.merged`. */ |
| 68 | event: string | null; | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 69 | /** `owner/name`. */ |
| 70 | repo: string | null; | |
| 71 | workspace: string | null; | |
| 72 | subject: InboxSubjectKind | null; | |
| 73 | number: number | null; | |
| 74 | /** A path on g1t.sh, such as `/acme/rocket/pull/12`. */ | |
| 75 | url: string; | |
| 76 | /** A username, or `g1t`. */ | |
| 77 | actor: string | null; | |
| Inbox: threads, reasons, subscriptions and watching | 78 | /** How many things have happened on the thread. */ |
| 79 | count: number; | |
| 80 | /** RFC 3339: when the person was first told of the thread. */ | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 81 | createdAt: string; |
| Inbox: threads, reasons, subscriptions and watching | 82 | /** RFC 3339: its latest activity. */ |
| 83 | updatedAt: string; | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 84 | readAt: string | null; |
| 85 | doneAt: string | null; | |
| 86 | saved: boolean; | |
| 87 | snoozedUntil: string | null; | |
| 88 | }; | |
| 89 | ||
| Inbox: threads, reasons, subscriptions and watching | 90 | /** One thing that happened on a thread, as the person was told of it. */ |
| 91 | export type InboxActivity = { | |
| 92 | reason: InboxReason; | |
| 93 | severity: InboxSeverity; | |
| 94 | title: string; | |
| 95 | body: string; | |
| 96 | event: string | null; | |
| 97 | actor: string | null; | |
| 98 | createdAt: string; | |
| 99 | }; | |
| 100 | ||
| 101 | /** A person's subscription to an issue or pull request. */ | |
| 102 | export type ThreadSubscription = { | |
| 103 | /** Whether they hear of what happens on it. */ | |
| 104 | subscribed: boolean; | |
| 105 | /** Whether they hear of nothing on it at all, not even a mention. */ | |
| 106 | ignored: boolean; | |
| 107 | /** Why they are subscribed; null when they are not. */ | |
| 108 | reason: InboxReason | null; | |
| 109 | repo: string | null; | |
| 110 | number: number | null; | |
| 111 | /** When they last chose, or null if they never did. */ | |
| 112 | updatedAt: string | null; | |
| 113 | }; | |
| 114 | ||
| 115 | /** A thread with its history, newest first, and the person's subscription. */ | |
| 116 | export type InboxThread = InboxItem & { | |
| 117 | activity: InboxActivity[]; | |
| 118 | subscription: ThreadSubscription | null; | |
| 119 | }; | |
| 120 | ||
| 121 | /** How closely a person follows a repository. */ | |
| 122 | export type WatchLevel = "participating" | "all" | "ignore" | "custom"; | |
| 123 | ||
| 124 | export const WATCH_LEVELS: WatchLevel[] = ["participating", "all", "ignore", "custom"]; | |
| 125 | ||
| 126 | /** The kinds of activity a custom watch can follow. */ | |
| 127 | export type WatchEvent = "issues" | "pulls" | "deployments" | "security"; | |
| 128 | ||
| 129 | export const WATCH_EVENTS: WatchEvent[] = ["issues", "pulls", "deployments", "security"]; | |
| 130 | ||
| 131 | export type Watching = { | |
| 132 | repoId: string; | |
| 133 | repo: string | null; | |
| 134 | level: WatchLevel; | |
| 135 | /** With `custom`: what it follows. */ | |
| 136 | events: WatchEvent[]; | |
| 137 | updatedAt: string | null; | |
| 138 | }; | |
| 139 | ||
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 140 | /** |
| 141 | * When security alerts that are not urgent (medium, low, and what is only to | |
| 142 | * know) reach a person: in a digest once a day or once a week, at once like | |
| 143 | * the urgent ones, or not at all. Critical and high alerts, a refused push | |
| 144 | * and a vulnerability report never wait for the digest. | |
| 145 | */ | |
| 146 | export type DigestCadence = "daily" | "weekly" | "at_once" | "off"; | |
| 147 | ||
| 148 | export const DIGEST_CADENCES: DigestCadence[] = ["daily", "weekly", "at_once", "off"]; | |
| 149 | ||
| Inbox: threads, reasons, subscriptions and watching | 150 | /** A person's choices about being told. */ |
| 151 | export type InboxSettings = { | |
| 152 | /** The reasons they are also emailed for. */ | |
| 153 | email: InboxReason[]; | |
| 154 | /** How they watch a repository they create. */ | |
| 155 | defaultWatch: WatchLevel; | |
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 156 | /** |
| 157 | * Whether critical and high security alerts on repositories they watch for | |
| 158 | * security reach their inbox at once. A workspace's owners and security | |
| 159 | * managers are told whatever this says. | |
| 160 | */ | |
| 161 | securityAlerts: boolean; | |
| 162 | /** When the rest of the security alerts reach them. */ | |
| 163 | securityDigest: DigestCadence; | |
| Inbox: threads, reasons, subscriptions and watching | 164 | }; |
| 165 | ||
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 166 | /** The inbox itself (not done, not snoozed), what was saved, or what is done. */ |
| 167 | export type InboxView = "inbox" | "saved" | "done"; | |
| 168 | ||
| 169 | export type InboxQuery = { | |
| 170 | view?: InboxView; | |
| 171 | severity?: InboxSeverity | null; | |
| Inbox: threads, reasons, subscriptions and watching | 172 | reason?: InboxReason | null; |
| 173 | /** Only threads the person takes part in: not those followed by watching or by hand. */ | |
| 174 | participating?: boolean; | |
| 175 | repoId?: string | null; | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 176 | unread?: boolean; |
| Inbox: threads, reasons, subscriptions and watching | 177 | /** RFC 3339: only threads with activity at or after it. */ |
| 178 | since?: string | null; | |
| 179 | /** RFC 3339: only threads whose latest activity was before it. */ | |
| 180 | updatedBefore?: string | null; | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 181 | /** The `next` of the page before. */ |
| 182 | before?: string | null; | |
| 183 | /** At most 100. */ | |
| 184 | limit?: number; | |
| 185 | }; | |
| 186 | ||
| 187 | export type InboxPage = { items: InboxItem[]; next: string | null }; | |
| 188 | ||
| 189 | /** Unread items in the inbox, by severity. */ | |
| 190 | export type InboxCounts = { unread: number } & Record<InboxSeverity, number>; | |
| 191 | ||
| Inbox: threads, reasons, subscriptions and watching | 192 | export type InboxMark = "read" | "unread" | "done" | "undone" | "save" | "unsave" | "snooze" | "unsnooze"; |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 193 | |
| 194 | export type InboxMarkArgs = { | |
| 195 | mark: InboxMark; | |
| 196 | /** At most 100. */ | |
| 197 | ids?: string[]; | |
| 198 | /** Every item in the inbox, when `ids` is empty: Mark all read. */ | |
| 199 | all?: boolean; | |
| 200 | severity?: InboxSeverity | null; | |
| Inbox: threads, reasons, subscriptions and watching | 201 | repoId?: string | null; |
| 202 | /** With `all`: only threads whose latest activity was at or before this. */ | |
| 203 | lastReadAt?: string | null; | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 204 | /** For `snooze`: RFC 3339, later than now. */ |
| 205 | until?: string | null; | |
| 206 | }; | |
| 207 | ||
| Inbox: threads, reasons, subscriptions and watching | 208 | /** Which issue or pull request: a thread's id, or a repository and number. */ |
| 209 | export type SubscriptionTarget = { id: string } | { repoId: string; number: number }; | |
| 210 | ||
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 211 | export interface InboxApi { |
| 212 | /** | |
| Inbox: threads, reasons, subscriptions and watching | 213 | * Latest activity first; in the inbox unfiltered, unread warnings first. |
| 214 | * Items about a repository the viewer can no longer read are dropped. | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 215 | */ |
| 216 | list(viewer: User, query?: InboxQuery): Promise<InboxPage>; | |
| 217 | counts(username: string): Promise<InboxCounts>; | |
| 218 | /** Changes the person's own items. Returns how many changed. */ | |
| 219 | mark(username: string, args: InboxMarkArgs): Promise<number>; | |
| Inbox: threads, reasons, subscriptions and watching | 220 | /** One of the viewer's threads with its history, or null. */ |
| 221 | thread(viewer: User, id: string): Promise<InboxThread | null>; | |
| 222 | /** The viewer's subscription to an issue or pull request; null when there is none to have. */ | |
| 223 | subscription(viewer: User, target: SubscriptionTarget): Promise<ThreadSubscription | null>; | |
| 224 | /** | |
| 225 | * Subscribes (`true`), unsubscribes (`false`), ignores, or goes back to | |
| 226 | * the default (`null`, subscribed only while taking part). | |
| 227 | */ | |
| 228 | subscribe(viewer: User, target: SubscriptionTarget, subscribed: boolean | null, ignored?: boolean): Promise<ThreadSubscription | null>; | |
| 229 | watching(username: string, repoId: string): Promise<Watching>; | |
| 230 | /** `null` goes back to the default, participating. */ | |
| 231 | watch(username: string, repoId: string, repo: string, level: WatchLevel | null, events?: WatchEvent[]): Promise<Watching>; | |
| 232 | watched(username: string): Promise<Watching[]>; | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 233 | /** How many watch a repository: all of it, or some of it (custom). */ |
| 234 | watchers(repoId: string): Promise<number>; | |
| Inbox: threads, reasons, subscriptions and watching | 235 | settings(username: string): Promise<InboxSettings>; |
| 236 | updateSettings(username: string, changes: Partial<InboxSettings>): Promise<InboxSettings>; | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 237 | } |
| 238 | ||
| 239 | /** The inbox, which the events service keeps. */ | |
| 240 | export function inboxClient(events: ServiceBinding): InboxApi { | |
| 241 | const call = async <T>(method: string, args: object): Promise<T> => { | |
| 242 | const response = await events.fetch(`https://service/rpc/${method}`, { | |
| 243 | method: "POST", | |
| 244 | headers: { "content-type": "application/json" }, | |
| 245 | body: JSON.stringify(args), | |
| 246 | }); | |
| 247 | if (!response.ok) throw new Error(`${method} failed with status ${response.status}`); | |
| 248 | return (await response.json()) as T; | |
| 249 | }; | |
| 250 | return { | |
| 251 | list: (viewer, query = {}) => call("inbox_list", { viewer, ...query }), | |
| 252 | counts: (username) => call("inbox_counts", { username }), | |
| 253 | mark: (username, args) => call("inbox_mark", { username, ...args }), | |
| Inbox: threads, reasons, subscriptions and watching | 254 | thread: (viewer, id) => call("inbox_thread", { viewer, id }), |
| 255 | subscription: (viewer, target) => call("inbox_subscription", { viewer, ...target }), | |
| 256 | subscribe: (viewer, target, subscribed, ignored = false) => | |
| 257 | call("inbox_subscribe", { viewer, ...target, subscribed, ignored }), | |
| 258 | watching: (username, repoId) => call("inbox_watching", { username, repoId }), | |
| 259 | watch: (username, repoId, repo, level, events = []) => call("inbox_watch", { username, repoId, repo, level, events }), | |
| 260 | watched: (username) => call("inbox_watched", { username }), | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 261 | watchers: (repoId) => call("inbox_watchers", { repoId }), |
| Inbox: threads, reasons, subscriptions and watching | 262 | settings: (username) => call("inbox_settings", { username }), |
| 263 | updateSettings: (username, changes) => call("inbox_update_settings", { username, ...changes }), | |
| Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all | 264 | }; |
| 265 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.