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

Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1/**
2 * Teams: groups of a workspace's members, given roles on repositories
3 * together, mentioned together and asked to review together. Kept by the
4 * identity service; mirrors `crates/contracts/src/teams.rs`.
5 */
6import type { RepoRole } from "./access";
7import type { User } from "./identity";
8import type { Result } from "./result";
9
10export type TeamVisibility = "visible" | "secret";
Merge branch 'worktree-agent-ad7c6d88d93adc817'11
12/**
13 * Who may create a workspace's teams: any member (the default) or its
14 * owners only. Owners change it in the workspace's settings.
15 */
16export type TeamCreation = "members" | "owners";
17
18/** Whether someone with `role` in a workspace may create its teams. */
19export function mayCreateTeams(setting: TeamCreation | null | undefined, role: "owner" | "member" | null | undefined): boolean {
20 if (!role) return false;
21 return (setting ?? "members") === "members" || role === "owner";
22}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar23export type TeamRole = "member" | "maintainer";
24export type ReviewAlgorithm = "round_robin" | "load_balance";
25
26export const TEAM_VISIBILITY_LABELS: Record<TeamVisibility, string> = {
27 visible: "Visible",
28 secret: "Secret",
29};
30
31export const TEAM_VISIBILITY_SUMMARIES: Record<TeamVisibility, string> = {
32 visible: "Every member of the workspace can see it and mention it.",
33 secret: "Only its own people and the workspace's owners can see it. Secret teams cannot be nested.",
34};
35
36export const REVIEW_ALGORITHM_LABELS: Record<ReviewAlgorithm, string> = {
37 round_robin: "Round robin",
38 load_balance: "Load balance",
39};
40
41export const REVIEW_ALGORITHM_SUMMARIES: Record<ReviewAlgorithm, string> = {
42 round_robin: "Whoever this team asked least recently goes first.",
43 load_balance: "Whoever has the fewest pull requests waiting on their review goes first.",
44};
45
46/** The most people review assignment picks for one request. */
47export const MAX_ASSIGNED = 10;
48
49export type ReviewAssignment = {
50 enabled: boolean;
51 algorithm: ReviewAlgorithm;
52 count: number;
53 skip_busy: boolean;
54 busy_at: number;
55 include_child_teams: boolean;
56 excluded: string[];
57 notify_team: boolean;
58};
59
60export const DEFAULT_REVIEW_ASSIGNMENT: ReviewAssignment = {
61 enabled: false,
62 algorithm: "round_robin",
63 count: 1,
64 skip_busy: false,
65 busy_at: 5,
66 include_child_teams: false,
67 excluded: [],
68 notify_team: false,
69};
70
71export type TeamRef = { slug: string; name: string };
72
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.73/** Who leads a team: a person on it, or an agent on it (by its id in the agents service). */
74export type TeamLead =
75 | { kind: "user"; username: string; name: string | null; avatar: string | null }
76 | { kind: "agent"; agent_id: string };
77
78/** A team's chat channel: its id, and its name (without `#`) when it was chosen. */
79export type TeamChannel = { id: string; name: string };
80
81/** An agent added to a team. */
82export type TeamAgent = {
83 agent_id: string;
84 /** Who added it, by username. */
85 added_by: string | null;
86 created_at: string;
87};
88
89/** A person on a team, as an agent on it is told of them (`agent_teams`). */
90export type RosterPerson = {
91 user_id: string;
92 username: string;
93 name: string | null;
94 title: string | null;
95 /** An IANA time zone, for their local time. */
96 timezone: string | null;
97 owns: string[];
98 /** Who they report to, by username. */
99 manager: string | null;
100 maintainer: boolean;
101};
102
103/** A visible team an agent is on, with everyone on it. For the agents service. */
104export type AgentTeam = {
105 slug: string;
106 name: string;
107 description: string | null;
108 lead: TeamLead | null;
109 channel: TeamChannel | null;
110 budget_micros: number | null;
111 people: RosterPerson[];
112 /** Agents added to it, by id. */
113 agent_ids: string[];
114};
115
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.116/** A visible team and the agents on it (`team_agent_index`). */
117export type TeamAgentsEntry = { slug: string; name: string; agent_ids: string[] };
118
119/** What `adopt_agent_teams` did for one agent: `added`, `already` on it, or `no_team` of that name. */
120export type AdoptedAgentTeam = { agent_id: string; team: string; outcome: "added" | "already" | "no_team" };
121
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.122/** How `update_team` takes a lead: `@username`, or `agent:<id>`; `""` for none. */
123export function leadInput(lead: { kind: "user"; username: string } | { kind: "agent"; agent_id: string } | null): string {
124 if (!lead) return "";
125 return lead.kind === "user" ? `@${lead.username}` : `agent:${lead.agent_id}`;
126}
127
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar128export type Team = {
129 id: string;
130 workspace: string;
131 slug: string;
132 name: string;
133 description: string | null;
134 visibility: TeamVisibility;
135 parent: TeamRef | null;
136 notify: boolean;
137 review_assignment: ReviewAssignment;
138 members_count: number;
139 repos_count: number;
140 child_teams_count: number;
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.141 /** Agents on it. */
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.142 agents_count: number;
143 lead: TeamLead | null;
144 channel: TeamChannel | null;
145 /** What its agents may spend together in a calendar month, in micro-dollars; null for none. */
146 budget_micros: number | null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar147 viewer_role: TeamRole | null;
148 can_manage: boolean;
149 created_at: string;
150 updated_at: string;
151};
152
153export type TeamMember = {
154 username: string;
155 name: string | null;
156 avatar: string | null;
157 role: TeamRole;
158 /** The child team they are in, when listed through one. */
159 via: string | null;
160};
161
162export type TeamRepo = {
163 /** `workspace/name`. */
164 repo: string;
165 repo_id: string;
166 role: RepoRole;
167 /** The parent team it comes from, when inherited. */
168 inherited_from: string | null;
169};
170
171/** A team with a role on a repository, as its Access settings list it. */
172export type RepoTeam = {
173 slug: string;
174 name: string;
175 role: RepoRole;
176 members_count: number;
177 visibility: TeamVisibility;
178};
179
180export type NewTeam = {
181 name: string;
182 slug?: string | null;
183 description?: string | null;
184 visibility?: TeamVisibility | null;
185 parent?: string | null;
186 notify?: boolean | null;
187 members?: string[];
188};
189
190/** What changes; the rest stays. `parent: ""` takes the team out from under its parent. */
191export type TeamChanges = {
192 name?: string;
193 slug?: string;
194 description?: string;
195 visibility?: TeamVisibility;
196 parent?: string;
197 notify?: boolean;
198 review_assignment?: ReviewAssignment;
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.199 /** `@username` or `agent:<id>`, someone on the team (see `leadInput`); `""` for no lead. */
200 lead?: string;
201 /** The chat channel's id; `""` for none. Given with `channel_name`. */
202 channel_id?: string;
203 channel_name?: string;
204 /** What its agents may spend together in a month, in micro-dollars; 0 for no team budget. */
205 budget_micros?: number;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar206};
207
208/** `@acme/backend`, the way a team is mentioned. */
209export function teamHandle(team: { workspace: string; slug: string }): string {
210 return `@${team.workspace}/${team.slug}`;
211}
212
213/** A team's slug from its name, as identity makes it. Null when nothing is left. */
214export function teamSlug(name: string): string | null {
215 let slug = "";
216 for (const c of name.trim()) {
217 if (/[A-Za-z0-9]/.test(c)) slug += c.toLowerCase();
218 else if (slug && !slug.endsWith("-")) slug += "-";
219 }
220 slug = slug.replace(/-+$/, "").slice(0, 60).replace(/-+$/, "");
221 return slug ? slug : null;
222}
223
224/** One person's teams in a workspace, for the Members page. */
225export type MemberTeams = { username: string; teams: TeamRef[] };
226
227/** Identity's team methods. */
228export interface TeamsClient {
229 listTeams(viewer: User | null, workspace: string, query?: string | null): Promise<Result<Team[]>>;
230 getTeam(viewer: User | null, workspace: string, team: string): Promise<Result<Team>>;
231 createTeam(actor: User, workspace: string, team: NewTeam): Promise<Result<Team>>;
Merge branch 'worktree-agent-ad7c6d88d93adc817'232 /** Who may create the workspace's teams. Owners only. */
233 setTeamCreation(actor: User, slug: string, setting: TeamCreation): Promise<Result<TeamCreation>>;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar234 updateTeam(actor: User, workspace: string, team: string, changes: TeamChanges): Promise<Result<Team>>;
235 deleteTeam(actor: User, workspace: string, team: string): Promise<Result<boolean>>;
236 teamMembers(viewer: User | null, workspace: string, team: string, includeChildTeams?: boolean): Promise<Result<TeamMember[]>>;
237 setTeamMember(actor: User, workspace: string, team: string, username: string, role: TeamRole): Promise<Result<TeamMember>>;
238 removeTeamMember(actor: User, workspace: string, team: string, username: string): Promise<Result<boolean>>;
239 childTeams(viewer: User | null, workspace: string, team: string): Promise<Result<Team[]>>;
240 teamRepos(viewer: User | null, workspace: string, team: string): Promise<Result<TeamRepo[]>>;
241 setTeamRepo(actor: User, workspace: string, team: string, owner: string, name: string, role: RepoRole): Promise<Result<TeamRepo>>;
242 removeTeamRepo(actor: User, workspace: string, team: string, owner: string, name: string): Promise<Result<boolean>>;
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.243 /** The agents on a team: added to it, as people are. */
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.244 teamAgents(viewer: User | null, workspace: string, team: string): Promise<Result<TeamAgent[]>>;
245 /** Adds one of the workspace's agents, by id; check it is the workspace's first. Owners and maintainers. */
246 setTeamAgent(actor: User, workspace: string, team: string, agentId: string): Promise<Result<TeamAgent>>;
247 removeTeamAgent(actor: User, workspace: string, team: string, agentId: string): Promise<Result<boolean>>;
248 /** For the agents service: the visible teams an agent is on, with everyone on each. */
An agent is on teams the way a person is: through team membership, with no team or department of its own. Add it to teams when you create it, from its profile or from the team's page; the Agents sidebar groups agents by the teams they're on, with those on none last; what an agent is told about its teammates, team budgets, skills reach, Spend's By team and the People pages all read memberships. Agents with an old team name matching a team are moved onto it once; the rest are left off a team. Templates name a role only. The agents, teams and people guides say how.249 agentTeams(workspace: string, agentId: string): Promise<AgentTeam[]>;
250 /** For the agents service: the workspace's visible teams, each with the agents on it. */
251 teamAgentIndex(workspace: string): Promise<TeamAgentsEntry[]>;
252 /**
253 * For the agents service, once: puts agents on the teams they named when
254 * an agent carried its own team (by slug, any case), in the workspace
255 * (by id). Safe to repeat: an agent already on the team stays as it is.
256 */
257 adoptAgentTeams(workspaceId: string, agents: { agent_id: string; team: string }[]): Promise<AdoptedAgentTeam[]>;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar258 userTeams(viewer: User | null, workspace: string, username: string): Promise<Result<Team[]>>;
259 /** Each member's teams the viewer can see. Members only. */
260 teamMemberships(viewer: User | null, workspace: string): Promise<Result<MemberTeams[]>>;
261}

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