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

People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { AgentTeam, PresenceEntry } from "@g1t/contracts";
5
6import { agentIdsOn, localTime, showing, teamBudgetBlock, teamsSection, whoToPage, type TeamsHere } from "./teammates.ts";
7
8const NOW = new Date("2026-10-09T01:12:00Z");
9
10const person = (username: string, extra: Partial<AgentTeam["people"][number]> = {}): AgentTeam["people"][number] => ({
11 user_id: `usr_${username}`,
12 username,
13 name: null,
14 title: null,
15 timezone: null,
16 owns: [],
17 manager: null,
18 maintainer: false,
19 ...extra,
20});
21
22const entry = (username: string, presence: PresenceEntry["presence"], extra: Partial<PresenceEntry> = {}): PresenceEntry => ({
23 user_id: `usr_${username}`,
24 username,
25 presence,
26 dnd_until: null,
27 status: null,
28 at: 1,
29 ...extra,
30});
31
32const sales: AgentTeam = {
33 slug: "sales",
34 name: "Sales",
35 description: "Two people and one agent",
36 lead: { kind: "user", username: "sofia", name: "Sofia Reyes", avatar: null },
37 channel: { id: "chn_1", name: "sales" },
38 budget_micros: 150_000_000,
39 people: [
40 person("sofia", { name: "Sofia Reyes", title: "Head of Sales", owns: ["the forecast"], manager: "chase", maintainer: true, timezone: "America/Chicago" }),
41 person("jordan", { name: "Jordan Lee", title: "Account Executive", owns: ["Halcyon", "Bluebird"], manager: "sofia" }),
42 ],
43 agent_ids: [],
44};
45
46const here = (presence: PresenceEntry[]): TeamsHere => ({
47 teams: [sales],
48 agents: [
49 { id: "agt_david", handle: "david", display_name: "David", title: "CRM keeper", team: "sales" },
50 { id: "agt_pax", handle: "pax", display_name: "Pax", title: "Billing", team: null },
51 ],
52 presence,
53});
54
55test("an agent is on the teams it was added to and its home team", () => {
56 const agents = here([]).agents;
57 assert.deepEqual(agentIdsOn(sales, agents), ["agt_david"]);
58 assert.deepEqual(agentIdsOn({ ...sales, agent_ids: ["agt_pax"] }, agents), ["agt_pax", "agt_david"]);
59});
60
61test("an agent is told who leads, who owns what and who is around", () => {
62 const dnd = new Date(NOW.getTime() + 3 * 3600_000).toISOString();
63 const text = teamsSection("agt_david", here([entry("sofia", "active", { dnd_until: dnd }), entry("jordan", "active")]), NOW)!;
64 assert.match(text, /^## Your teams/);
65 assert.match(text, /### Sales/);
66 assert.match(text, /Led by Sofia Reyes \(@sofia\)\./);
67 assert.match(text, /Its channel is #sales\./);
68 assert.match(text, /share a budget of \$150\.00 a month/);
69 assert.match(text, /Sofia Reyes \(@sofia\); Head of Sales, the lead, a maintainer; owns the forecast; reports to @chase \(focusing in Do Not Disturb until 23:12 their time; it's 20:12 for them\)/);
70 assert.match(text, /Jordan Lee \(@jordan\); Account Executive; owns Halcyon, Bluebird; reports to @sofia \(online\)/);
71 assert.match(text, /- @david: you/);
72 // Sofia is focusing, so Jordan is the one to page.
73 assert.match(text, /When a person is needed: Jordan Lee \(@jordan\), who is online now\./);
74});
75
76test("with no one reachable, the agent is told to post and wait", () => {
77 const text = teamsSection("agt_david", here([entry("sofia", "away"), entry("jordan", "offline")]), NOW)!;
78 assert.match(text, /No one on Sales can be reached now; post in #sales and say they'll see it when they're back\./);
79});
80
81test("without presence, nobody is said to be offline", () => {
82 const text = teamsSection("agt_david", here([]), NOW)!;
83 assert.doesNotMatch(text, /offline/);
84 assert.doesNotMatch(text, /When a person is needed/);
85});
86
87test("an agent on no team is told nothing", () => {
88 assert.equal(teamsSection("agt_david", { teams: [], agents: [], presence: [] }, NOW), null);
89 assert.equal(teamsSection("agt_david", null, NOW), null);
90});
91
92test("who to page: the lead, then a maintainer, then anyone reachable", () => {
93 const presence = new Map([entry("sofia", "active"), entry("jordan", "active")].map((e) => [e.user_id, e]));
94 assert.equal(whoToPage(sales, presence, NOW, true)?.username, "sofia");
95 presence.set("usr_sofia", entry("sofia", "away"));
96 assert.equal(whoToPage(sales, presence, NOW, true)?.username, "jordan");
97 assert.equal(whoToPage(sales, presence, NOW, false), null);
98});
99
100test("presence and local time read plainly", () => {
101 assert.equal(showing(undefined, null, NOW, true), "offline");
102 assert.equal(showing(entry("a", "away", { status: { emoji: "🌴", text: "On leave", clear_at: null, source: "manual", set_at: "" } }), null, NOW, true), 'away; status "🌴 On leave"');
103 assert.equal(showing(entry("a", "active"), null, NOW, false), null);
104 assert.equal(localTime("Europe/London", NOW), "02:12");
105 assert.equal(localTime("Not/AZone", NOW), null);
106});
107
108test("a team's budget stops its agents once they have spent it together", () => {
109 assert.equal(teamBudgetBlock([{ name: "Sales", budget_micros: 150_000_000, spent_micros: 149_000_000 }]), null);
110 assert.match(teamBudgetBlock([{ name: "Sales", budget_micros: 150_000_000, spent_micros: 150_000_000 }])!, /^Sales's agents have used the team's budget of \$150\.00/);
111});