Skip to content
319 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.

The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.1/**
2 * Home's reads: each section's data from its own service, every call on
3 * its own so one that fails leaves only its section saying so. The shaping
4 * is in ./home.ts.
5 */
6import { env } from "cloudflare:workers";
7
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.8import {
9 type ActivityDigest,
10 type AgentSession,
11 type AgentsOverview,
12 type ChatActivity,
13 type Deployment,
14 type InstallRequests,
15 MAX_DIGEST_REPOS,
16 type Memory,
17 type ProjectDeploys,
18 type Repo,
19 type RepoPath,
20 type User,
21 type WorkflowRun,
22 type WorkspaceAgent,
23 notifyClient,
24} from "@g1t/contracts";
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.25
Notifications is the bell in the top bar alone: the rail no longer carries it, and the notifications guide points at the bell; a phone keeps it in the bottom bar, which has no bell. Home names who made a deployment, by username or by an agent's handle, instead of showing the account's id, and says someone when identity doesn't know the id. The Apps launcher's skeleton tiles are drawn in the line colour so they show on its raised surface.26import { type CodePull, type CodeWork, G1T_ACTOR, type RepoRef, type SessionWork, type Span, type Spend, deployActorKey, pullKey, spanMonths, spendIn } from "./home";
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.27import { monthSpan, spentMicros } from "./spend";
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.28import { actions, agents, billing, chat, deployments, events, identity, repos, work, workspaceAgents } from "./services.server";
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.29
30/** Pull requests read per project and list: `LIST_PAGE` in services/work. */
31const PULL_PAGE = 100;
32/** The most projects whose pull requests are read. */
33const MAX_PROJECTS = 50;
34/** The most projects whose builds are read for the span. */
35const MAX_DEPLOY_PROJECTS = 20;
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.36/** The most projects whose workflow runs are read for Running now: those pushed to most in the span. */
37const MAX_WORKFLOW_PROJECTS = 10;
38/** Runs read per project: enough to find the ones not finished. */
39const WORKFLOW_RUNS = 20;
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.40/** Revise runs and sessions read: the services' most in one list. */
41const RUN_PAGE = 200;
42const SESSION_PAGE = 200;
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.43/** The most member keys named in one lookup. */
44const MAX_NAMES = 200;
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.45const DAY = 24 * 60 * 60 * 1000;
46
47const warn = (what: string) => (error: unknown) => {
48 console.warn(`home: ${what} failed`, error);
49 return null;
50};
51
52/**
53 * When the viewer was last on this workspace's Home, from notify, where it
54 * is kept with them (the same on every device). Null when there is no
55 * visit on record; undefined when notify did not answer.
56 */
57export async function loadLastVisit(viewer: User, slug: string): Promise<number | null | undefined> {
58 if (!env.NOTIFY) return undefined;
59 const visit = await notifyClient(env.NOTIFY).lastVisit(viewer, slug).catch(warn("last visit"));
60 if (!visit) return undefined;
61 return visit.seen_at ? Date.parse(visit.seen_at) : null;
62}
63
64/** Marks the viewer's visit at `at`, the time the page they looked at loaded. */
65export async function markVisit(viewer: User, slug: string, at: number): Promise<boolean> {
66 if (!env.NOTIFY) return false;
67 const marked = await notifyClient(env.NOTIFY).markVisit(viewer, slug, new Date(at).toISOString()).catch(warn("mark visit"));
68 return marked != null;
69}
70
71/**
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.72 * The workspace's own repositories the viewer may read (no pull request
73 * working copies), as every Code section reads them. Null when repos did
74 * not answer.
75 */
76export async function loadRepos(viewer: User, slug: string): Promise<Repo[] | null> {
77 const list = await repos.list(viewer, { namespace: slug }).catch(warn("repos"));
78 return list ? list.filter((repo) => !repo.forkOf && repo.namespace.toLowerCase() === slug) : null;
79}
80
81/** The repositories by id, as the digest's lines and links need them. */
82export function repoRefs(list: Repo[] | null): Record<string, RepoRef> {
83 const refs: Record<string, RepoRef> = {};
84 for (const repo of list ?? []) refs[repo.id] = { namespace: repo.namespace, name: repo.name, defaultBranch: repo.defaultBranch };
85 return refs;
86}
87
88/**
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.89 * The workspace's projects' pull requests, open and recently merged or
90 * closed, any author's, with how many times each agent's was sent back to
91 * revise. `complete` when every list reaches back to `reach`. Null when
92 * Code did not answer.
93 */
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.94export async function loadCodeWork(viewer: User, slug: string, reach: number, owned: Repo[] | null): Promise<CodeWork | null> {
95 const runs = await agents.listRuns(viewer, { workspace: slug, kind: "revise", limit: RUN_PAGE }).catch(warn("revise runs"));
96 if (!owned || !runs?.ok) return null;
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.97 const chosen = owned.slice(0, MAX_PROJECTS);
98 const byId = new Map<string, Repo>(chosen.map((repo) => [repo.id, repo]));
99 const batch = chosen.length > 0 ? await work.pullsForRepos([...byId.keys()], viewer, PULL_PAGE).catch(warn("pull requests")) : [];
100 if (!batch) return null;
101 const pulls: CodePull[] = [];
102 let complete = owned.length <= MAX_PROJECTS;
103 for (const entry of batch) {
104 const repo = byId.get(entry.repoId);
105 if (!repo) continue;
106 const path = { namespace: repo.namespace, name: repo.name };
107 for (const pull of [...entry.open, ...entry.closed]) {
108 pulls.push({
109 repo: path,
110 number: pull.number,
111 title: pull.title,
112 status: pull.status,
113 mergedAt: pull.mergedAt,
114 mergedBy: pull.mergedBy,
115 createdAt: pull.createdAt,
116 updatedAt: pull.updatedAt,
117 author: { username: pull.author.username, kind: pull.author.kind },
118 });
119 }
120 // A full page that does not reach back far enough may leave some out.
121 for (const list of [entry.open, entry.closed]) {
122 if (list.length >= PULL_PAGE) {
123 const oldest = Math.min(...list.map((pull) => Date.parse(pull.mergedAt ?? pull.updatedAt)));
124 if (oldest > reach) complete = false;
125 }
126 }
127 }
128 const revisions: Record<string, number> = {};
129 for (const run of runs.value) {
130 if (run.number == null) continue;
131 const key = pullKey(run.repo, run.number);
132 revisions[key] = (revisions[key] ?? 0) + 1;
133 }
134 if (runs.value.length >= RUN_PAGE && Date.parse(runs.value[runs.value.length - 1].createdAt) > reach) complete = false;
135 return { pulls, revisions, complete };
136}
137
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.138/**
139 * What happened in the span, counted by who did it: the events service's
140 * digest of the workspace's repositories (the first MAX_DIGEST_REPOS of
141 * them; none without Code access) and of its artifacts. Null when events
142 * did not answer.
143 */
144export async function loadActivity(slug: string, span: Pick<Span, "from" | "now">, owned: Repo[] | null): Promise<ActivityDigest | null> {
145 const digest = await events
146 .activityDigest({
147 repo_ids: (owned ?? []).slice(0, MAX_DIGEST_REPOS).map((repo) => repo.id),
148 workspace: slug,
149 from: new Date(span.from).toISOString(),
150 until: new Date(span.now + 1).toISOString(),
151 })
152 .catch(warn("activity digest"));
153 if (!digest) return null;
154 // More repositories than the digest reads: it says so.
155 return owned && owned.length > MAX_DIGEST_REPOS ? { ...digest, complete: false } : digest;
156}
157
158/** What was said in the span where the viewer can read, counted. Null when chat did not answer. */
159export async function loadChatActivity(viewer: User, slug: string, span: Pick<Span, "from" | "now">): Promise<ChatActivity | null> {
160 const activity = await chat
161 .activity(slug, viewer, { from: new Date(span.from).toISOString(), until: new Date(span.now + 1).toISOString() })
162 .catch(warn("chat activity"));
163 return activity?.ok ? activity.value : null;
164}
165
166/** Every member key the digest and chat name. */
Notifications is the bell in the top bar alone: the rail no longer carries it, and the notifications guide points at the bell; a phone keeps it in the bottom bar, which has no bell. Home names who made a deployment, by username or by an agent's handle, instead of showing the account's id, and says someone when identity doesn't know the id. The Apps launcher's skeleton tiles are drawn in the line colour so they show on its raised surface.167export function actorKeys(activity: ActivityDigest | null, chatActivity: ChatActivity | null, deploys?: { projects: { deployments: { createdBy: string }[] }[] } | null): string[] {
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.168 const keys = new Set<string>();
Notifications is the bell in the top bar alone: the rail no longer carries it, and the notifications guide points at the bell; a phone keeps it in the bottom bar, which has no bell. Home names who made a deployment, by username or by an agent's handle, instead of showing the account's id, and says someone when identity doesn't know the id. The Apps launcher's skeleton tiles are drawn in the line colour so they show on its raised surface.169 for (const project of deploys?.projects ?? []) for (const deploy of project.deployments) keys.add(deployActorKey(deploy.createdBy) ?? "");
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.170 for (const repo of activity?.repos ?? []) {
171 for (const by of [...repo.pushes.by, ...repo.pushes.default_branch.by]) keys.add(by.actor);
172 for (const list of [repo.pulls.opened, repo.pulls.merged, repo.pulls.closed, repo.issues.opened, repo.issues.closed, repo.reviews, repo.comments, repo.deployments.succeeded, repo.deployments.failed]) {
173 for (const entry of list) keys.add(entry.actor);
174 }
175 for (const release of repo.releases) keys.add(release.actor);
176 for (const pkg of repo.packages) keys.add(pkg.actor);
177 }
178 for (const entry of activity?.folios?.created ?? []) keys.add(entry.actor);
179 for (const folio of activity?.folios?.edited ?? []) for (const author of folio.authors) keys.add(author);
180 for (const author of chatActivity?.authors ?? []) keys.add(author.key);
181 keys.delete("");
182 return [...keys];
183}
184
185/**
186 * The names behind member keys: people's usernames from identity (one
187 * lookup for them all), agents' handles from the workspace's agents, g1t
188 * as itself. A key identity does not know is left out, and shows as
189 * "someone".
190 */
191export async function loadNames(keys: string[], agentsList: Pick<WorkspaceAgent, "id" | "handle">[] | null): Promise<Record<string, string>> {
192 const names: Record<string, string> = {};
193 const userIds: string[] = [];
194 const byAgentId = new Map((agentsList ?? []).map((agent) => [agent.id, agent.handle]));
195 for (const key of keys.slice(0, MAX_NAMES)) {
Four small things seen today. An agent can attach a file it made to any doc the person who asked can edit, whatever the space's agent mode: attaching changes nothing in the doc, so the suggest mode that stopped every PDF and spreadsheet no longer does; the ability is its own, attach, beside read, suggest and edit. Home no longer counts robots among the people: events that name an agent by its own id, or g1t's upkeep as the workspace or as g1t, read as the agent and as g1t, in the digest and on the page, so the faces say who instead of someone. Tooltips are the inverted bubble, the page's foreground behind the page's background, with no border. The Spend page's slice tabs keep their ring inside the row instead of losing its top to the scroll edge, and the Apps launcher keeps its search box and footer in place while it reads which apps there are, with skeleton tiles between.196 if (key === G1T_ACTOR || key === "user:g1t" || key.startsWith("user:wsp_")) names[key] = "g1t";
197 else if (key.startsWith("agent:") || key.startsWith("user:agt_")) {
198 // An agent, by its own id: under `agent:` as events name it, or under `user:` from older ones.
199 const handle = byAgentId.get(key.replace(/^(agent|user):/, ""));
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.200 if (handle) names[key] = handle;
201 } else if (key.startsWith("user:")) userIds.push(key.slice("user:".length));
202 }
203 if (userIds.length > 0) {
204 const found = await identity.usernames(userIds).catch(warn("usernames"));
205 for (const [id, username] of Object.entries(found ?? {})) names[`user:${id}`] = username;
206 }
207 return names;
208}
209
210/**
211 * The newest workflow runs of the projects pushed to most in the span (at
212 * most MAX_WORKFLOW_PROJECTS), for the ones still going. A project whose
213 * runs could not be read is left out; `complete` says whether every one was.
214 */
215export async function loadWorkflowRuns(
216 viewer: User,
217 activity: ActivityDigest | null,
218 owned: Repo[] | null,
219): Promise<{ projects: { repo: RepoPath; runs: WorkflowRun[] }[]; complete: boolean }> {
220 const byId = new Map((owned ?? []).map((repo) => [repo.id, repo]));
221 const active = [...(activity?.repos ?? [])]
222 .filter((repo) => repo.pushes.count > 0 && byId.has(repo.repo_id))
223 .sort((a, b) => b.pushes.count - a.pushes.count || a.repo_id.localeCompare(b.repo_id))
224 .slice(0, MAX_WORKFLOW_PROJECTS);
225 const lists = await Promise.all(
226 active.map((digest) => {
227 const repo = byId.get(digest.repo_id)!;
228 const path = { namespace: repo.namespace, name: repo.name };
229 return actions
230 .runs(path, viewer, { limit: WORKFLOW_RUNS })
231 .then((result) => (result.ok ? { repo: path, runs: result.value } : null))
232 .catch(warn(`workflow runs of ${repo.name}`));
233 }),
234 );
235 const projects = lists.filter((entry) => entry != null);
236 return { projects, complete: projects.length === active.length };
237}
238
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.239/** The workspace's newest sessions; `complete` when the list reaches back before `from`. */
240export async function loadSessions(viewer: User, slug: string, from: number): Promise<SessionWork | null> {
241 const listed = await workspaceAgents.sessions(slug, viewer, { limit: SESSION_PAGE }).catch(warn("sessions"));
242 if (!listed?.ok) return null;
243 const sessions: AgentSession[] = listed.value;
244 const oldest = sessions.at(-1);
245 const complete = sessions.length < SESSION_PAGE || (oldest != null && Date.parse(oldest.created_at) < from);
246 return { sessions, complete };
247}
248
249/** The agents overview: sessions waiting on the viewer, live ones, and the workspace's budget alert. */
250export async function loadAgentsOverview(viewer: User, slug: string): Promise<AgentsOverview | null> {
251 const overview = await workspaceAgents.overview(slug, viewer).catch(warn("agents overview"));
252 return overview?.ok ? overview.value : null;
253}
254
255/** Install requests as the viewer sees them; only an owner's (`can_resolve`) are anyone else's. */
256export async function loadInstallRequests(viewer: User, slug: string): Promise<InstallRequests | null> {
257 const requests = await workspaceAgents.installRequests(slug, viewer).catch(warn("install requests"));
258 return requests?.ok ? requests.value : null;
259}
260
261/** The workspace's memory, for the decisions recorded in it. */
262export async function loadMemories(viewer: User, slug: string): Promise<Memory[] | null> {
263 const memories = await agents.listMemories(viewer, slug, null).catch(warn("memories"));
264 return memories?.ok ? memories.value.workspace : null;
265}
266
267/**
268 * Every project's builds at a glance, and the builds of the projects that
269 * deploy, for the span: the projects whose newest build is older than the
270 * span are not read again.
271 */
272export async function loadDeploys(
273 viewer: User,
274 slug: string,
275 from: number,
276): Promise<{ overview: ProjectDeploys[]; projects: { slug: string; deployments: Deployment[] }[]; complete: boolean } | null> {
277 const overview = await deployments.overview(slug, viewer).catch(warn("deploys overview"));
278 if (!overview?.ok) return null;
279 const recent = overview.value.filter((project) => project.latest && Date.parse(project.latest.finishedAt ?? project.latest.createdAt) >= from);
280 const read = recent.slice(0, MAX_DEPLOY_PROJECTS);
281 const lists = await Promise.all(
282 read.map((project) =>
283 deployments
284 .list({ workspace: slug, slug: project.slug }, viewer)
285 .then((result) => (result.ok ? { slug: project.slug, deployments: result.value.deployments } : null))
286 .catch(warn(`deploys of ${project.slug}`)),
287 ),
288 );
289 const projects = lists.filter((entry) => entry != null);
290 return { overview: overview.value, projects, complete: projects.length === recent.length };
291}
292
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.293/**
294 * The span's spend from the statements of the months it touches, and the
295 * month so far from the usage report the top bar, Spend and Usage read
296 * (`spend.ts` `monthSpan`, `spentMicros`), so Home's figure is theirs. Null
297 * when billing did not answer; the month alone null when only it did not.
298 */
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.299export async function loadSpend(viewer: User, slug: string, span: Pick<Span, "from" | "now">): Promise<Spend | null> {
300 const months = spanMonths(span);
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.301 const [statements, month] = await Promise.all([
302 Promise.all(months.map((month) => billing.statement(slug, viewer, month, "day").catch(warn(`statement ${month}`)))),
303 billing
304 .usageReport(slug, viewer, monthSpan(new Date(span.now)))
305 .then((result) => (result.ok ? spentMicros(result.value) : null))
306 .catch(warn("month usage")),
307 ]);
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.308 if (statements.some((statement) => !statement?.ok)) return null;
309 return spendIn(
310 statements.map((statement) => (statement as Extract<typeof statement, { ok: true }>).value),
311 span,
Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means.312 month,
The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how.313 );
314}
315
316/** How far back Code is read: 7 days before the span, for the comparison. */
317export function reachFor(span: Pick<Span, "from">): number {
318 return span.from - 7 * DAY;
319}