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 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import type { AgentSession, Deployment, InboxItem, InstallRequest, Memory } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import { | |
| 7 | type AttentionRow, | |
| 8 | type CodeNeed, | |
| 9 | type CodePull, | |
| 10 | acceptance, | |
| 11 | agentRow, | |
| 12 | attention, | |
| 13 | chatRow, | |
| 14 | codeRow, | |
| 15 | dayIn, | |
| 16 | decisionsIn, | |
| 17 | deploysIn, | |
| 18 | duration, | |
| 19 | installRequestRow, | |
| 20 | landedIn, | |
| 21 | limitRow, | |
| 22 | notificationRow, | |
| 23 | parseWindow, | |
| 24 | pullOutcome, | |
| 25 | rankAttention, | |
| 26 | running, | |
| 27 | runningDeploys, | |
| 28 | runningSessions, | |
| 29 | sessionOutcome, | |
| 30 | sinceWords, | |
| 31 | spanFor, | |
| 32 | spanMonths, | |
| 33 | spanSentence, | |
| 34 | spanWork, | |
| 35 | spendIn, | |
| 36 | startHere, | |
| 37 | trendLabel, | |
| 38 | waitingSentence, | |
| 39 | } from "./home.ts"; | |
| 40 | ||
| 41 | const HOUR = 3_600_000; | |
| 42 | const DAY = 24 * HOUR; | |
| 43 | // Friday 2026-10-09, 18:00 UTC; 11:00 in Los Angeles. | |
| 44 | const NOW = Date.parse("2026-10-09T18:00:00Z"); | |
| 45 | const LA = "America/Los_Angeles"; | |
| 46 | const repo = { namespace: "acme", name: "web" }; | |
| 47 | const iso = (ms: number) => new Date(ms).toISOString(); | |
| 48 | ||
| 49 | function pull(over: Partial<CodePull> = {}): CodePull { | |
| 50 | return { | |
| 51 | repo, | |
| 52 | number: 1, | |
| 53 | title: "Fix the flaky test", | |
| 54 | status: "merged", | |
| 55 | mergedAt: iso(NOW - HOUR), | |
| 56 | createdAt: iso(NOW - 3 * HOUR), | |
| 57 | updatedAt: iso(NOW - HOUR), | |
| 58 | author: { username: "g1t", kind: "agent" }, | |
| 59 | ...over, | |
| 60 | }; | |
| 61 | } | |
| 62 | ||
| 63 | function session(over: Partial<AgentSession> = {}): AgentSession { | |
| 64 | return { | |
| 65 | id: "s1", | |
| 66 | workspace_id: "w", | |
| 67 | agent_id: "a", | |
| 68 | agent_handle: "margo", | |
| 69 | agent_name: "Margo", | |
| 70 | agent_avatar_seed: "margo", | |
| 71 | subagent: null, | |
| 72 | kind: "chat", | |
| 73 | parent_id: null, | |
| 74 | root_id: "s1", | |
| 75 | payer_agent_id: "a", | |
| 76 | title: "Draft the launch notes", | |
| 77 | goal: "", | |
| 78 | status: "done", | |
| 79 | status_note: null, | |
| 80 | summary: null, | |
| 81 | channel_id: "c", | |
| 82 | channel_kind: "channel", | |
| 83 | channel_name: "launch", | |
| 84 | card_message_id: null, | |
| 85 | asked_by: null, | |
| 86 | asked_by_username: "bo", | |
| 87 | routine_id: null, | |
| 88 | steps: 3, | |
| 89 | tool_calls: 2, | |
| 90 | input_tokens: 0, | |
| 91 | output_tokens: 0, | |
| 92 | charged_micros: 400_000, | |
| 93 | cap_micros: 2_000_000, | |
| 94 | model: null, | |
| 95 | outputs: [], | |
| 96 | created_at: iso(NOW - 2 * HOUR), | |
| 97 | updated_at: iso(NOW - HOUR), | |
| 98 | finished_at: iso(NOW - HOUR), | |
| 99 | visible: true, | |
| 100 | ...over, | |
| 101 | }; | |
| 102 | } | |
| 103 | const need = (over: Partial<CodeNeed>): CodeNeed => ({ | |
| 104 | key: "pull:p1", | |
| 105 | reason: "ready_to_merge", | |
| 106 | repo, | |
| 107 | ref: "#12", | |
| 108 | title: "Add retries", | |
| 109 | ask: "Waiting for you to merge it.", | |
| 110 | by: { name: "g1t", agent: true }, | |
| 111 | for: "bo", | |
| 112 | at: NOW - 2 * HOUR, | |
| 113 | to: "/acme/web/pull/12", | |
| 114 | facts: [{ label: "Files changed", value: "4", tone: null }], | |
| 115 | ...over, | |
| 116 | }); | |
| 117 | ||
| 118 | test("Code's needs become rows with what is at stake", () => { | |
| 119 | const ready = codeRow(need({})); | |
| 120 | assert.equal(ready.kind, "ready"); | |
| 121 | assert.equal(ready.stake?.text, "4 files to land"); | |
| 122 | assert.equal(ready.action.label, "Merge"); | |
| 123 | const deploy = codeRow(need({ key: "deploy:site", reason: "blocking", facts: [{ label: "Production", value: "Serving the build before", tone: "good" }] })); | |
| 124 | assert.equal(deploy.kind, "deploy"); | |
| 125 | assert.equal(deploy.stake?.text, "Production on an older build"); | |
| 126 | assert.equal(codeRow(need({ reason: "checks_failing" })).kind, "pull_blocked"); | |
| 127 | assert.equal(codeRow(need({ key: "run:r1", reason: "stalled", facts: [{ label: "Cost so far", value: "$1.20", tone: null }] })).stake?.text, "$1.20 so far"); | |
| 128 | }); | |
| 129 | ||
| 130 | const row = (over: Partial<AttentionRow>): AttentionRow => ({ | |
| 131 | key: "k", | |
| 132 | kind: "chat", | |
| 133 | title: "t", | |
| 134 | detail: "d", | |
| 135 | stake: null, | |
| 136 | action: { label: "Open", to: "/x" }, | |
| 137 | owner: null, | |
| 138 | at: NOW, | |
| 139 | from: "chat", | |
| 140 | ...over, | |
| 141 | }); | |
| 142 | ||
| 143 | test("Start here: the most pressing tier first, then what has waited longest", () => { | |
| 144 | const rows = [ | |
| 145 | row({ key: "chat:old", kind: "chat", at: NOW - 5 * DAY, action: { label: "Reply", to: "/acme/-/chat/general" } }), | |
| 146 | row({ key: "ready:new", kind: "ready", at: NOW - HOUR, action: { label: "Merge", to: "/acme/web/pull/2" } }), | |
| 147 | row({ key: "ready:old", kind: "ready", at: NOW - 3 * HOUR, action: { label: "Merge", to: "/acme/web/pull/1" } }), | |
| 148 | row({ key: "session:s", kind: "session_cap", at: NOW - 10 * 60_000, action: { label: "Approve more", to: "/acme/-/agents/margo/sessions/s" } }), | |
| 149 | ]; | |
| 150 | const ranked = rankAttention(rows); | |
| 151 | assert.deepEqual(ranked.map((r) => r.key), ["session:s", "ready:old", "ready:new", "chat:old"]); | |
| 152 | const start = startHere(ranked); | |
| 153 | assert.equal(start?.row.key, "session:s"); | |
| 154 | assert.match(start?.why ?? "", /spend cap/); | |
| 155 | // A failed production deploy beats everything. | |
| 156 | const withDeploy = rankAttention([...rows, row({ key: "deploy:site", kind: "deploy", at: NOW, action: { label: "See the build", to: "/acme/site/deployments/1" } })]); | |
| 157 | assert.equal(startHere(withDeploy)?.row.key, "deploy:site"); | |
| 158 | // Ties in tier say so. | |
| 159 | const two = startHere(rankAttention(rows.filter((r) => r.kind === "ready"))); | |
| 160 | assert.equal(two?.row.key, "ready:old"); | |
| 161 | assert.match(two?.why ?? "", /Of the 2 like it, it has waited longest/); | |
| 162 | assert.equal(startHere([]), null); | |
| 163 | }); | |
| 164 | ||
| 165 | test("the same order whatever order the rows came in", () => { | |
| 166 | const rows = [row({ key: "b", kind: "review", at: NOW }), row({ key: "a", kind: "review", at: NOW, action: { label: "Review", to: "/y" } })]; | |
| 167 | assert.deepEqual(rankAttention(rows).map((r) => r.key), ["a", "b"]); | |
| 168 | assert.deepEqual(rankAttention([...rows].reverse()).map((r) => r.key), ["a", "b"]); | |
| 169 | }); | |
| 170 | ||
| 171 | const item = (over: Partial<InboxItem>): InboxItem => ({ | |
| 172 | id: "n1", | |
| 173 | reason: "ci_activity", | |
| 174 | severity: "error", | |
| 175 | title: "Checks failed on #12", | |
| 176 | body: "Add retries", | |
| 177 | event: "check.failed", | |
| 178 | repo: "acme/web", | |
| 179 | workspace: "acme", | |
| 180 | subject: "pull", | |
| 181 | number: 12, | |
| 182 | url: "/acme/web/pull/12", | |
| 183 | actor: "g1t", | |
| 184 | count: 1, | |
| 185 | createdAt: iso(NOW - HOUR), | |
| 186 | updatedAt: iso(NOW - HOUR), | |
| 187 | readAt: null, | |
| 188 | doneAt: null, | |
| 189 | saved: false, | |
| 190 | snoozedUntil: null, | |
| 191 | ...over, | |
| 192 | }); | |
| 193 | ||
| 194 | test("notifications: warnings and failures in this workspace; none about repositories without Code", () => { | |
| 195 | assert.equal(notificationRow(item({}), "acme", true)?.kind, "notification"); | |
| 196 | assert.equal(notificationRow(item({}), "acme", false), null); | |
| 197 | assert.equal(notificationRow(item({ repo: null, url: "/acme/-/agents/margo" }), "acme", false)?.kind, "notification"); | |
| 198 | assert.equal(notificationRow(item({ severity: "info" }), "acme", true), null); | |
| 199 | assert.equal(notificationRow(item({ workspace: "other", repo: "other/web" }), "acme", true), null); | |
| 200 | assert.equal(notificationRow(item({ readAt: iso(NOW) }), "acme", true), null); | |
| 201 | }); | |
| 202 | ||
| 203 | test("a notification about something already listed is left out", () => { | |
| 204 | const out = attention({ | |
| 205 | slug: "acme", | |
| 206 | code: true, | |
| 207 | codeNeeds: [need({})], | |
| 208 | capped: [], | |
| 209 | agents: [], | |
| 210 | canManage: false, | |
| 211 | notifications: [item({})], | |
| 212 | chat: [], | |
| 213 | }); | |
| 214 | assert.deepEqual(out.rows.map((r) => r.key), ["pull:p1"]); | |
| 215 | assert.deepEqual(out.missing, []); | |
| 216 | }); | |
| 217 | ||
| 218 | test("without Code access no Code row is asked for or shown, and a silent source is named", () => { | |
| 219 | const out = attention({ | |
| 220 | slug: "acme", | |
| 221 | code: false, | |
| 222 | codeNeeds: [need({})], | |
| 223 | capped: null, | |
| 224 | agents: null, | |
| 225 | canManage: false, | |
| 226 | notifications: [item({}), item({ id: "n2", repo: null, reason: "agent", severity: "warning", url: "/acme/-/agents/margo" })], | |
| 227 | chat: null, | |
| 228 | }); | |
| 229 | assert.deepEqual(out.rows.map((r) => r.key), ["notification:n2"]); | |
| 230 | assert.equal(out.rows.some((r) => r.from === "code"), false); | |
| 231 | assert.deepEqual(out.missing, ["Agents", "Chat"]); | |
| 232 | }); | |
| 233 | ||
| 234 | test("an agent out of budget is listed only for those who may raise it", () => { | |
| 235 | const agent = { id: "a1", handle: "margo", display_name: "Margo", status: "out_of_budget", spent_month_micros: 52_000_000, budget: { monthly_micros: 50_000_000 }, updated_at: iso(NOW) }; | |
| 236 | assert.equal(agentRow(agent, "acme")?.stake?.text, "$0.00 left"); | |
| 237 | const base = { slug: "acme", code: true, codeNeeds: [], capped: [], notifications: [], chat: [] }; | |
| 238 | assert.equal(attention({ ...base, agents: [agent], canManage: false }).rows.length, 0); | |
| 239 | assert.equal(attention({ ...base, agents: [agent], canManage: true }).rows[0]?.kind, "agent_budget"); | |
| 240 | }); | |
| 241 | ||
| 242 | test("chat: mentions anywhere and unread direct messages, never muted", () => { | |
| 243 | const channel = { id: "c1", kind: "channel" as const, name: "general", last_message_at: iso(NOW) }; | |
| 244 | assert.equal(chatRow({ channel, title: "general", muted: false, unread: 4, mentions: 0 }, "acme"), null); | |
| 245 | assert.equal(chatRow({ channel, title: "general", muted: false, unread: 4, mentions: 2 }, "acme")?.stake?.text, "2 mentions"); | |
| 246 | assert.equal(chatRow({ channel, title: "general", muted: true, unread: 4, mentions: 2 }, "acme"), null); | |
| 247 | const dm = chatRow({ channel: { ...channel, kind: "dm", name: null }, title: "Bo Reed", muted: false, unread: 1, mentions: 0 }, "acme"); | |
| 248 | assert.equal(dm?.action.to, "/acme/-/chat/dm/c1"); | |
| 249 | }); | |
| 250 | ||
| 251 | // The span is NOW back to 3 days ago unless a test says otherwise. | |
| 252 | const SPAN = { from: NOW - 3 * DAY, now: NOW }; | |
| 253 | ||
| 254 | test("a day is the reader's day", () => { | |
| 255 | assert.equal(dayIn(NOW, LA), "2026-10-09"); | |
| 256 | assert.equal(dayIn(Date.parse("2026-10-10T02:00:00Z"), LA), "2026-10-09"); | |
| 257 | assert.equal(dayIn(Date.parse("2026-10-10T02:00:00Z"), null), "2026-10-10"); | |
| 258 | assert.equal(dayIn(NOW, "Not/AZone"), "2026-10-09"); | |
| 259 | }); | |
| 260 | ||
| 261 | test("since your last visit, in words, in the reader's time zone", () => { | |
| 262 | // NOW is Friday 11:00 in Los Angeles. | |
| 263 | assert.equal(sinceWords(NOW - 20_000, NOW, LA), "a moment ago"); | |
| 264 | assert.equal(sinceWords(NOW - 40 * 60_000, NOW, LA), "40 minutes ago"); | |
| 265 | assert.equal(sinceWords(NOW - HOUR - 60_000, NOW, LA), "an hour ago"); | |
| 266 | assert.equal(sinceWords(NOW - 2 * HOUR, NOW, LA), "2 hours ago"); | |
| 267 | // 07:00 Friday. | |
| 268 | assert.equal(sinceWords(NOW - 4 * HOUR, NOW, LA), "this morning"); | |
| 269 | // 02:00 Friday is Thursday night. | |
| 270 | assert.equal(sinceWords(NOW - 9 * HOUR, NOW, LA), "last night"); | |
| 271 | // 19:00 Thursday. | |
| 272 | assert.equal(sinceWords(NOW - 16 * HOUR, NOW, LA), "yesterday evening"); | |
| 273 | // 19:00 Tuesday. | |
| 274 | assert.equal(sinceWords(NOW - 2 * DAY - 16 * HOUR, NOW, LA), "Tuesday evening"); | |
| 275 | // 02:00 Wednesday is Tuesday night. | |
| 276 | assert.equal(sinceWords(NOW - 2 * DAY - 9 * HOUR, NOW, LA), "Tuesday night"); | |
| 277 | assert.equal(sinceWords(NOW - 9 * DAY, NOW, LA), "Sep 30"); | |
| 278 | }); | |
| 279 | ||
| 280 | test("how long a span is", () => { | |
| 281 | assert.equal(duration(30_000), "a moment"); | |
| 282 | assert.equal(duration(45 * 60_000), "45 minutes"); | |
| 283 | assert.equal(duration(HOUR), "1 hour"); | |
| 284 | assert.equal(duration(3 * DAY + HOUR), "3 days"); | |
| 285 | assert.equal(duration(15 * DAY), "2 weeks"); | |
| 286 | }); | |
| 287 | ||
| 288 | test("the span: since your last visit, capped, or a fixed window", () => { | |
| 289 | assert.equal(parseWindow("24h"), "24h"); | |
| 290 | assert.equal(parseWindow("7d"), "7d"); | |
| 291 | assert.equal(parseWindow("nonsense"), "last"); | |
| 292 | assert.equal(parseWindow(null), "last"); | |
| 293 | const away = spanFor("last", NOW, NOW - 2 * DAY - 16 * HOUR, LA); | |
| 294 | assert.equal(away.from, NOW - 2 * DAY - 16 * HOUR); | |
| 295 | assert.equal(away.words, "Since Tuesday evening"); | |
| 296 | assert.equal(away.length, "2 days"); | |
| 297 | assert.equal(away.note, null); | |
| 298 | // Never visited: the last 24 hours, and it says why. | |
| 299 | const first = spanFor("last", NOW, null, LA); | |
| 300 | assert.equal(first.from, NOW - DAY); | |
| 301 | assert.equal(first.note, "first"); | |
| 302 | assert.equal(spanFor("last", NOW, undefined, LA).note, "unknown"); | |
| 303 | // Away a month: 14 days at most. | |
| 304 | const capped = spanFor("last", NOW, NOW - 30 * DAY, LA); | |
| 305 | assert.equal(capped.from, NOW - 14 * DAY); | |
| 306 | assert.equal(capped.note, "capped"); | |
| 307 | assert.equal(capped.words, "In the last 14 days"); | |
| 308 | // The fixed windows ignore the last visit. | |
| 309 | assert.equal(spanFor("24h", NOW, NOW - 3 * DAY, LA).from, NOW - DAY); | |
| 310 | assert.deepEqual([spanFor("7d", NOW, NOW - HOUR, LA).from, spanFor("7d", NOW, NOW - HOUR, LA).words], [NOW - 7 * DAY, "In the last 7 days"]); | |
| 311 | }); | |
| 312 | ||
| 313 | test("an agent's pull request is accepted first time when it merged in the span with no revision", () => { | |
| 314 | assert.equal(pullOutcome(pull(), 0, SPAN), "accepted"); | |
| 315 | assert.equal(pullOutcome(pull(), 2, SPAN), "fixed"); | |
| 316 | assert.equal(pullOutcome(pull({ mergedAt: iso(NOW - 4 * DAY) }), 0, SPAN), null); | |
| 317 | assert.equal(pullOutcome(pull({ status: "closed", mergedAt: null }), 0, SPAN), "dropped"); | |
| 318 | // Still open is not settled: it is under Running now. | |
| 319 | assert.equal(pullOutcome(pull({ status: "open", mergedAt: null }), 1, SPAN), null); | |
| 320 | }); | |
| 321 | ||
| 322 | test("a session counts once, at the root of its tree, when it settled in the span", () => { | |
| 323 | assert.equal(sessionOutcome(session(), SPAN), "finished"); | |
| 324 | assert.equal(sessionOutcome(session({ parent_id: "s0" }), SPAN), null); | |
| 325 | assert.equal(sessionOutcome(session({ status: "failed" }), SPAN), "dropped"); | |
| 326 | assert.equal(sessionOutcome(session({ status: "stopped" }), SPAN), "dropped"); | |
| 327 | assert.equal(sessionOutcome(session({ status: "working", finished_at: null }), SPAN), null); | |
| 328 | assert.equal(sessionOutcome(session({ finished_at: iso(NOW - 4 * DAY) }), SPAN), null); | |
| 329 | }); | |
| 330 | ||
| 331 | test("accepted first time is accepted over every agent pull request that settled", () => { | |
| 332 | assert.deepEqual(acceptance(["accepted", "accepted", "fixed", "dropped", "finished"]), { value: 0.5, of: 4 }); | |
| 333 | assert.equal(acceptance(["finished"]), null); | |
| 334 | }); | |
| 335 | ||
| 336 | test("the span's work: pull requests and sessions, sources, and the 7 days before", () => { | |
| 337 | const work = spanWork({ | |
| 338 | span: SPAN, | |
| 339 | slug: "acme", | |
| 340 | code: { | |
| 341 | pulls: [ | |
| 342 | pull({ number: 1 }), | |
| 343 | pull({ number: 2 }), | |
| 344 | pull({ number: 3, status: "open", mergedAt: null }), | |
| 345 | // A person's pull request is not agent work. | |
| 346 | pull({ number: 4, author: { username: "bo", kind: "user" } }), | |
| 347 | // The 7 days before the span: one accepted, one fixed. | |
| 348 | pull({ number: 5, mergedAt: iso(NOW - 4 * DAY), updatedAt: iso(NOW - 4 * DAY) }), | |
| 349 | pull({ number: 6, mergedAt: iso(NOW - 5 * DAY), updatedAt: iso(NOW - 5 * DAY) }), | |
| 350 | // Older than that: not counted. | |
| 351 | pull({ number: 7, mergedAt: iso(NOW - 11 * DAY), updatedAt: iso(NOW - 11 * DAY) }), | |
| 352 | ], | |
| 353 | revisions: { "acme/web#2": 1, "acme/web#6": 2 }, | |
| 354 | complete: true, | |
| 355 | }, | |
| 356 | sessions: { sessions: [session(), session({ id: "s2", kind: "routine", status: "working", finished_at: null }), session({ id: "s3", parent_id: "s1" })], complete: true }, | |
| 357 | }); | |
| 358 | assert.equal(work.tasks.length, 3); | |
| 359 | assert.deepEqual(work.counts, { accepted: 1, fixed: 1, finished: 1, dropped: 0 }); | |
| 360 | assert.deepEqual(work.rate, { value: 0.5, of: 2 }); | |
| 361 | assert.deepEqual(work.before, { value: 0.5, of: 2 }); | |
| 362 | assert.deepEqual(work.sources, [ | |
| 363 | { source: "code", count: 2 }, | |
| 364 | { source: "chat", count: 1 }, | |
| 365 | ]); | |
| 366 | assert.equal(work.code, "read"); | |
| 367 | assert.equal(work.partial, false); | |
| 368 | assert.equal( | |
| 369 | spanSentence({ words: "Since Tuesday evening" }, work, 3), | |
| 370 | "Since Tuesday evening, agents finished 3 tasks and 3 changes landed. 1 needed a fix after review.", | |
| 371 | ); | |
| 372 | }); | |
| 373 | ||
| 374 | test("no trend when the 7 days before could not all be read, and none without Code", () => { | |
| 375 | const partial = spanWork({ span: SPAN, slug: "acme", code: { pulls: [pull()], revisions: {}, complete: false }, sessions: null }); | |
| 376 | assert.equal(partial.before, null); | |
| 377 | assert.equal(partial.partial, true); | |
| 378 | assert.equal(partial.sessions, "unavailable"); | |
| 379 | const member = spanWork({ span: SPAN, slug: "acme", code: "no_access", sessions: { sessions: [session()], complete: true } }); | |
| 380 | assert.equal(member.code, "no_access"); | |
| 381 | assert.equal(member.rate, null); | |
| 382 | assert.equal(member.tasks.every((t) => t.source !== "code"), true); | |
| 383 | }); | |
| 384 | ||
| 385 | test("the trend is in whole points", () => { | |
| 386 | assert.equal(trendLabel({ value: 0.89 }, { value: 0.83 }), "+6 pts vs the 7 days before"); | |
| 387 | assert.equal(trendLabel({ value: 0.5 }, { value: 0.75 }), "−25 pts vs the 7 days before"); | |
| 388 | assert.equal(trendLabel({ value: 0.5 }, { value: 0.5 }), "Same as the 7 days before"); | |
| 389 | assert.equal(trendLabel(null, { value: 0.5 }), null); | |
| 390 | }); | |
| 391 | ||
| 392 | test("the sentence for a quiet span, and for what needs you", () => { | |
| 393 | const quiet = spanWork({ span: SPAN, slug: "acme", code: { pulls: [], revisions: {}, complete: true }, sessions: { sessions: [], complete: true } }); | |
| 394 | assert.equal(spanSentence({ words: "Since an hour ago" }, quiet, 0), "Since an hour ago, nothing new has settled."); | |
| 395 | assert.equal(spanSentence({ words: "In the last 24 hours" }, quiet, 2), "In the last 24 hours, 2 changes landed."); | |
| 396 | assert.equal(spanSentence({ words: "Since this morning" }, null, null), "Since this morning, agent work couldn't be read."); | |
| 397 | assert.equal(waitingSentence(0), "Nothing needs you right now."); | |
| 398 | assert.equal(waitingSentence(1), "1 thing needs you."); | |
| 399 | assert.equal(waitingSentence(8), "8 things need you."); | |
| 400 | }); | |
| 401 | ||
| 402 | test("what landed: every merge in the span, a person's or an agent's, newest first", () => { | |
| 403 | const landed = landedIn( | |
| 404 | [ | |
| 405 | pull({ number: 1, mergedAt: iso(NOW - 2 * DAY), mergedBy: "bo" }), | |
| 406 | pull({ number: 2, author: { username: "bo", kind: "user" }, mergedAt: iso(NOW - HOUR) }), | |
| 407 | pull({ number: 3, mergedAt: iso(NOW - 5 * DAY) }), | |
| 408 | pull({ number: 4, status: "open", mergedAt: null }), | |
| 409 | ], | |
| 410 | SPAN, | |
| 411 | ); | |
| 412 | assert.deepEqual(landed.map((change) => [change.number, change.agent]), [ | |
| 413 | [2, false], | |
| 414 | [1, true], | |
| 415 | ]); | |
| 416 | assert.equal(landed[1]!.mergedBy, "bo"); | |
| 417 | }); | |
| 418 | ||
| 419 | const deploy = (over: Partial<Deployment>): Deployment => ({ | |
| 420 | id: "d1", | |
| 421 | kind: "production", | |
| 422 | branch: null, | |
| 423 | number: null, | |
| 424 | commit: "abcdef1234", | |
| 425 | status: "ready", | |
| 426 | url: "https://site.g1t.page", | |
| 427 | error: null, | |
| 428 | warnings: [], | |
| 429 | buildSeconds: 40, | |
| 430 | createdBy: "bo", | |
| 431 | createdAt: iso(NOW - 2 * HOUR), | |
| 432 | finishedAt: iso(NOW - HOUR), | |
| 433 | ...over, | |
| 434 | }); | |
| 435 | ||
| 436 | test("builds that finished in the span: live or failed, never skipped", () => { | |
| 437 | const rows = deploysIn( | |
| 438 | [ | |
| 439 | { | |
| 440 | slug: "site", | |
| 441 | deployments: [ | |
| 442 | deploy({}), | |
| 443 | deploy({ id: "d2", status: "replaced", finishedAt: iso(NOW - DAY) }), | |
| 444 | deploy({ id: "d3", status: "failed", finishedAt: iso(NOW - 2 * DAY) }), | |
| 445 | deploy({ id: "d4", status: "skipped", finishedAt: iso(NOW - HOUR) }), | |
| 446 | deploy({ id: "d5", status: "building", finishedAt: null }), | |
| 447 | deploy({ id: "d6", finishedAt: iso(NOW - 5 * DAY) }), | |
| 448 | ], | |
| 449 | }, | |
| 450 | ], | |
| 451 | "acme", | |
| 452 | SPAN, | |
| 453 | ); | |
| 454 | assert.deepEqual(rows.map((row) => [row.key, row.outcome]), [ | |
| 455 | ["deploy:site:d1", "live"], | |
| 456 | ["deploy:site:d2", "live"], | |
| 457 | ["deploy:site:d3", "failed"], | |
| 458 | ]); | |
| 459 | assert.equal(rows[0]!.to, "/acme/site/deployments/d1"); | |
| 460 | assert.equal(rows[0]!.commit, "abcdef1"); | |
| 461 | }); | |
| 462 | ||
| 463 | const request = (over: Partial<InstallRequest>): InstallRequest => ({ | |
| 464 | id: "r1", | |
| 465 | listing: "integration:sentry", | |
| 466 | kind: "integration", | |
| 467 | name: "Sentry", | |
| 468 | note: "For the error alerts", | |
| 469 | requested_by: "bo", | |
| 470 | requested_at: iso(NOW - 2 * DAY), | |
| 471 | status: "open", | |
| 472 | resolved_by: null, | |
| 473 | resolved_at: null, | |
| 474 | ...over, | |
| 475 | }); | |
| 476 | ||
| 477 | const memory = (over: Partial<Memory>): Memory => ({ | |
| 478 | id: "m1", | |
| 479 | scope: "workspace", | |
| 480 | workspace: "acme", | |
| 481 | repo: null, | |
| 482 | text: "Use Postgres for the ledger", | |
| 483 | kind: "decision", | |
| 484 | source: { kind: "person" } as Memory["source"], | |
| 485 | createdBy: "ada", | |
| 486 | pinned: false, | |
| 487 | createdAt: iso(NOW - DAY), | |
| 488 | updatedAt: iso(NOW - DAY), | |
| 489 | lastUsedAt: null, | |
| 490 | ...over, | |
| 491 | }); | |
| 492 | ||
| 493 | test("decisions: kept decisions in memory and requests answered, in the span", () => { | |
| 494 | const rows = decisionsIn( | |
| 495 | { | |
| 496 | memories: [memory({}), memory({ id: "m2", kind: "fact" }), memory({ id: "m3", status: "candidate" }), memory({ id: "m4", createdAt: iso(NOW - 6 * DAY) })], | |
| 497 | requests: [request({}), request({ id: "r2", status: "declined", resolved_by: "ada", resolved_at: iso(NOW - HOUR) })], | |
| 498 | }, | |
| 499 | "acme", | |
| 500 | SPAN, | |
| 501 | ); | |
| 502 | assert.deepEqual(rows.map((row) => row.key), ["request:r2", "memory:m1"]); | |
| 503 | assert.equal(rows[0]!.text, "Turned down Sentry, which bo asked for"); | |
| 504 | }); | |
| 505 | ||
| 506 | test("an open install request needs an owner; answered ones don't", () => { | |
| 507 | assert.equal(installRequestRow(request({}), "acme")?.action.to, "/acme/-/marketplace/requests"); | |
| 508 | assert.equal(installRequestRow(request({ status: "done" }), "acme"), null); | |
| 509 | const base = { slug: "acme", code: true, codeNeeds: [], capped: [], agents: [], canManage: true, notifications: [], chat: [] }; | |
| 510 | assert.equal(attention({ ...base, requests: [request({})] }).rows[0]?.kind, "install_request"); | |
| 511 | assert.equal(attention({ ...base, requests: null }).rows.length, 0); | |
| 512 | }); | |
| 513 | ||
| 514 | test("the workspace's agent budget used up comes first, for those who may raise it", () => { | |
| 515 | assert.equal(limitRow({ alert: 90, spentMicros: 1, since: NOW }, "acme"), null); | |
| 516 | const limit = { alert: 100, spentMicros: 500_000_000, since: NOW - DAY }; | |
| 517 | assert.equal(limitRow(limit, "acme")?.action.to, "/acme/-/spend"); | |
| 518 | const base = { slug: "acme", code: true, codeNeeds: [need({})], capped: [], agents: [], notifications: [], chat: [], limit }; | |
| 519 | assert.equal(attention({ ...base, canManage: true }).start?.row.kind, "limit"); | |
| 520 | assert.equal(attention({ ...base, canManage: false }).rows.some((r) => r.kind === "limit"), false); | |
| 521 | }); | |
| 522 | ||
| 523 | test("running now: live root sessions, agents' changes and builds going; a capped session is under Needs you", () => { | |
| 524 | const sessions = runningSessions( | |
| 525 | [ | |
| 526 | session({ id: "a", status: "working", finished_at: null, created_at: iso(NOW - HOUR) }), | |
| 527 | session({ id: "b", status: "needs_approval", finished_at: null }), | |
| 528 | session({ id: "c", status: "queued", finished_at: null, parent_id: "a" }), | |
| 529 | session({ id: "d", status: "done" }), | |
| 530 | session({ id: "e", status: "queued", finished_at: null, visible: false, created_at: iso(NOW - 2 * HOUR) }), | |
| 531 | ], | |
| 532 | "acme", | |
| 533 | ); | |
| 534 | assert.deepEqual(sessions.map((row) => [row.key, row.status]), [ | |
| 535 | ["session:a", "Working"], | |
| 536 | ["session:e", "Queued"], | |
| 537 | ]); | |
| 538 | assert.equal(sessions[1]!.title, "A session in a conversation you're not in"); | |
| 539 | const builds = runningDeploys( | |
| 540 | [ | |
| 541 | { slug: "site", enabled: true, production: null, previews: 0, latest: deploy({ status: "building", finishedAt: null }) }, | |
| 542 | { slug: "docs", enabled: true, production: null, previews: 0, latest: deploy({}) }, | |
| 543 | ], | |
| 544 | "acme", | |
| 545 | ); | |
| 546 | assert.deepEqual(builds.map((row) => row.status), ["Building"]); | |
| 547 | // Longest-going first within a kind. | |
| 548 | assert.deepEqual(running([builds, sessions]).map((row) => row.key), ["session:e", "session:a", "deploy:site:d1"]); | |
| 549 | }); | |
| 550 | ||
| 551 | ||
| 552 | test("the span's months: one, or the ones it crosses", () => { | |
| 553 | assert.deepEqual(spanMonths(SPAN), ["2026-10"]); | |
| 554 | assert.deepEqual(spanMonths({ from: Date.parse("2026-09-28T00:00:00Z"), now: NOW }), ["2026-09", "2026-10"]); | |
| 555 | assert.deepEqual(spanMonths({ from: Date.parse("2025-12-30T00:00:00Z"), now: Date.parse("2026-01-02T00:00:00Z") }), ["2025-12", "2026-01"]); | |
| 556 | }); | |
| 557 | ||
| 558 | test("spend in the span: the UTC days it covers across months, at price, without money in", () => { | |
| 559 | const september = { | |
| 560 | groups: [ | |
| 561 | { key: "2026-09-29", label: "", chargedMicros: 0, lines: [{ kind: "Agent runs", count: 2, chargedMicros: 1_000_000, costMicros: 0 }] }, | |
| 562 | { key: "2026-09-20", label: "", chargedMicros: 0, lines: [{ kind: "Agent runs", count: 9, chargedMicros: 9_000_000, costMicros: 0 }] }, | |
| 563 | ], | |
| 564 | totals: { chargedMicros: 10_000_000, paidMicros: 0, costMicros: 0, entries: 2 }, | |
| 565 | }; | |
| 566 | const october = { | |
| 567 | groups: [ | |
| 568 | { | |
| 569 | key: "2026-10-09", | |
| 570 | label: "2026-10-09", | |
| 571 | chargedMicros: 0, | |
| 572 | lines: [ | |
| 573 | { kind: "Agent runs", count: 12, chargedMicros: 3_000_000, costMicros: 2_500_000, priceMicros: 4_000_000 }, | |
| 574 | { kind: "Sandbox time", count: 12, chargedMicros: 600_000, costMicros: 500_000 }, | |
| 575 | { kind: "Payments", count: 1, chargedMicros: -50_000_000, costMicros: 0, priceMicros: 0 }, | |
| 576 | ], | |
| 577 | }, | |
| 578 | ], | |
| 579 | totals: { chargedMicros: 12_600_000, paidMicros: 50_000_000, costMicros: 0, entries: 4, priceMicros: 13_600_000 }, | |
| 580 | }; | |
| 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. | 581 | const spend = spendIn([september, october], { from: Date.parse("2026-09-29T15:00:00Z"), now: NOW }, 13_609_000); |
| 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. | 582 | assert.deepEqual([spend.from, spend.to], ["2026-09-29", "2026-10-09"]); |
| 583 | assert.equal(spend.totalMicros, 5_600_000); | |
| 584 | assert.deepEqual(spend.lines.map((l) => [l.kind, l.micros, l.count]), [ | |
| 585 | ["Agent runs", 5_000_000, 14], | |
| 586 | ["Sandbox time", 600_000, 12], | |
| 587 | ]); | |
| 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. | 588 | // The month so far is billing's usage report, the top bar's figure, not the statement: it carries what is not yet closed. |
| 589 | assert.equal(spend.monthMicros, 13_609_000); | |
| 590 | assert.equal(spendIn([september, october], { from: Date.parse("2026-09-29T15:00:00Z"), now: NOW }, null).monthMicros, 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. | 591 | }); |