Skip to content
265 linesCodeBlameRaw

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 all1/**
2 * The inbox: what needs a person, or what they follow, as it happens. The
Inbox: threads, reasons, subscriptions and watching3 * 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 all7 */
8import type { ServiceBinding } from "./clients";
9import 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 watching13 * 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 all15 */
16export type InboxSeverity = "error" | "warning" | "success" | "info";
17
18export const INBOX_SEVERITIES: InboxSeverity[] = ["error", "warning", "success", "info"];
19
Inbox: threads, reasons, subscriptions and watching20/**
21 * Why a person was told: what the thread asked of them, or what ties them
22 * to it. Most specific first.
23 */
24export 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 bar29 | "team_mention"
Inbox: threads, reasons, subscriptions and watching30 | "ci_activity"
31 | "security_alert"
32 | "state_change"
33 | "author"
34 | "comment"
35 | "manual"
36 | "subscribed";
37
38export 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 bar43 "team_mention",
Inbox: threads, reasons, subscriptions and watching44 "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 all52
Inbox: threads, reasons, subscriptions and watching53export 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 all56export type InboxItem = {
Inbox: threads, reasons, subscriptions and watching57 /** 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 all58 id: string;
Inbox: threads, reasons, subscriptions and watching59 /** 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 all62 severity: InboxSeverity;
Inbox: threads, reasons, subscriptions and watching63 /** One line: what happened last, and where. */
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all64 title: string;
65 /** One line: what it happened to, such as the pull request's title. */
66 body: string;
Inbox: threads, reasons, subscriptions and watching67 /** 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 all69 /** `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 watching78 /** 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 all81 createdAt: string;
Inbox: threads, reasons, subscriptions and watching82 /** RFC 3339: its latest activity. */
83 updatedAt: string;
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all84 readAt: string | null;
85 doneAt: string | null;
86 saved: boolean;
87 snoozedUntil: string | null;
88};
89
Inbox: threads, reasons, subscriptions and watching90/** One thing that happened on a thread, as the person was told of it. */
91export 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. */
102export 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. */
116export type InboxThread = InboxItem & {
117 activity: InboxActivity[];
118 subscription: ThreadSubscription | null;
119};
120
121/** How closely a person follows a repository. */
122export type WatchLevel = "participating" | "all" | "ignore" | "custom";
123
124export const WATCH_LEVELS: WatchLevel[] = ["participating", "all", "ignore", "custom"];
125
126/** The kinds of activity a custom watch can follow. */
127export type WatchEvent = "issues" | "pulls" | "deployments" | "security";
128
129export const WATCH_EVENTS: WatchEvent[] = ["issues", "pulls", "deployments", "security"];
130
131export 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 */
146export type DigestCadence = "daily" | "weekly" | "at_once" | "off";
147
148export const DIGEST_CADENCES: DigestCadence[] = ["daily", "weekly", "at_once", "off"];
149
Inbox: threads, reasons, subscriptions and watching150/** A person's choices about being told. */
151export 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 watching164};
165
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all166/** The inbox itself (not done, not snoozed), what was saved, or what is done. */
167export type InboxView = "inbox" | "saved" | "done";
168
169export type InboxQuery = {
170 view?: InboxView;
171 severity?: InboxSeverity | null;
Inbox: threads, reasons, subscriptions and watching172 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 all176 unread?: boolean;
Inbox: threads, reasons, subscriptions and watching177 /** 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 all181 /** The `next` of the page before. */
182 before?: string | null;
183 /** At most 100. */
184 limit?: number;
185};
186
187export type InboxPage = { items: InboxItem[]; next: string | null };
188
189/** Unread items in the inbox, by severity. */
190export type InboxCounts = { unread: number } & Record<InboxSeverity, number>;
191
Inbox: threads, reasons, subscriptions and watching192export 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 all193
194export 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 watching201 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 all204 /** For `snooze`: RFC 3339, later than now. */
205 until?: string | null;
206};
207
Inbox: threads, reasons, subscriptions and watching208/** Which issue or pull request: a thread's id, or a repository and number. */
209export 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 all211export interface InboxApi {
212 /**
Inbox: threads, reasons, subscriptions and watching213 * 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 all215 */
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 watching220 /** 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-a69aeabc4b0deeb97233 /** How many watch a repository: all of it, or some of it (custom). */
234 watchers(repoId: string): Promise<number>;
Inbox: threads, reasons, subscriptions and watching235 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 all237}
238
239/** The inbox, which the events service keeps. */
240export 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 watching254 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-a69aeabc4b0deeb97261 watchers: (repoId) => call("inbox_watchers", { repoId }),
Inbox: threads, reasons, subscriptions and watching262 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 all264 };
265}

This file's history is long; its oldest lines are credited to the oldest commit read.