Skip to content
247 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
116/** How `update_team` takes a lead: `@username`, or `agent:<id>`; `""` for none. */
117export function leadInput(lead: { kind: "user"; username: string } | { kind: "agent"; agent_id: string } | null): string {
118 if (!lead) return "";
119 return lead.kind === "user" ? `@${lead.username}` : `agent:${lead.agent_id}`;
120}
121
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar122export type Team = {
123 id: string;
124 workspace: string;
125 slug: string;
126 name: string;
127 description: string | null;
128 visibility: TeamVisibility;
129 parent: TeamRef | null;
130 notify: boolean;
131 review_assignment: ReviewAssignment;
132 members_count: number;
133 repos_count: number;
134 child_teams_count: number;
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.135 /** Agents added to it. Agents whose home team it is are on it too. */
136 agents_count: number;
137 lead: TeamLead | null;
138 channel: TeamChannel | null;
139 /** What its agents may spend together in a calendar month, in micro-dollars; null for none. */
140 budget_micros: number | null;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar141 viewer_role: TeamRole | null;
142 can_manage: boolean;
143 created_at: string;
144 updated_at: string;
145};
146
147export type TeamMember = {
148 username: string;
149 name: string | null;
150 avatar: string | null;
151 role: TeamRole;
152 /** The child team they are in, when listed through one. */
153 via: string | null;
154};
155
156export type TeamRepo = {
157 /** `workspace/name`. */
158 repo: string;
159 repo_id: string;
160 role: RepoRole;
161 /** The parent team it comes from, when inherited. */
162 inherited_from: string | null;
163};
164
165/** A team with a role on a repository, as its Access settings list it. */
166export type RepoTeam = {
167 slug: string;
168 name: string;
169 role: RepoRole;
170 members_count: number;
171 visibility: TeamVisibility;
172};
173
174export type NewTeam = {
175 name: string;
176 slug?: string | null;
177 description?: string | null;
178 visibility?: TeamVisibility | null;
179 parent?: string | null;
180 notify?: boolean | null;
181 members?: string[];
182};
183
184/** What changes; the rest stays. `parent: ""` takes the team out from under its parent. */
185export type TeamChanges = {
186 name?: string;
187 slug?: string;
188 description?: string;
189 visibility?: TeamVisibility;
190 parent?: string;
191 notify?: boolean;
192 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.193 /** `@username` or `agent:<id>`, someone on the team (see `leadInput`); `""` for no lead. */
194 lead?: string;
195 /** The chat channel's id; `""` for none. Given with `channel_name`. */
196 channel_id?: string;
197 channel_name?: string;
198 /** What its agents may spend together in a month, in micro-dollars; 0 for no team budget. */
199 budget_micros?: number;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar200};
201
202/** `@acme/backend`, the way a team is mentioned. */
203export function teamHandle(team: { workspace: string; slug: string }): string {
204 return `@${team.workspace}/${team.slug}`;
205}
206
207/** A team's slug from its name, as identity makes it. Null when nothing is left. */
208export function teamSlug(name: string): string | null {
209 let slug = "";
210 for (const c of name.trim()) {
211 if (/[A-Za-z0-9]/.test(c)) slug += c.toLowerCase();
212 else if (slug && !slug.endsWith("-")) slug += "-";
213 }
214 slug = slug.replace(/-+$/, "").slice(0, 60).replace(/-+$/, "");
215 return slug ? slug : null;
216}
217
218/** One person's teams in a workspace, for the Members page. */
219export type MemberTeams = { username: string; teams: TeamRef[] };
220
221/** Identity's team methods. */
222export interface TeamsClient {
223 listTeams(viewer: User | null, workspace: string, query?: string | null): Promise<Result<Team[]>>;
224 getTeam(viewer: User | null, workspace: string, team: string): Promise<Result<Team>>;
225 createTeam(actor: User, workspace: string, team: NewTeam): Promise<Result<Team>>;
Merge branch 'worktree-agent-ad7c6d88d93adc817'226 /** Who may create the workspace's teams. Owners only. */
227 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 bar228 updateTeam(actor: User, workspace: string, team: string, changes: TeamChanges): Promise<Result<Team>>;
229 deleteTeam(actor: User, workspace: string, team: string): Promise<Result<boolean>>;
230 teamMembers(viewer: User | null, workspace: string, team: string, includeChildTeams?: boolean): Promise<Result<TeamMember[]>>;
231 setTeamMember(actor: User, workspace: string, team: string, username: string, role: TeamRole): Promise<Result<TeamMember>>;
232 removeTeamMember(actor: User, workspace: string, team: string, username: string): Promise<Result<boolean>>;
233 childTeams(viewer: User | null, workspace: string, team: string): Promise<Result<Team[]>>;
234 teamRepos(viewer: User | null, workspace: string, team: string): Promise<Result<TeamRepo[]>>;
235 setTeamRepo(actor: User, workspace: string, team: string, owner: string, name: string, role: RepoRole): Promise<Result<TeamRepo>>;
236 removeTeamRepo(actor: User, workspace: string, team: string, owner: string, name: string): Promise<Result<boolean>>;
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.237 /** The agents added to a team. Agents whose home team it is are on it too (`agentsOnTeam`). */
238 teamAgents(viewer: User | null, workspace: string, team: string): Promise<Result<TeamAgent[]>>;
239 /** Adds one of the workspace's agents, by id; check it is the workspace's first. Owners and maintainers. */
240 setTeamAgent(actor: User, workspace: string, team: string, agentId: string): Promise<Result<TeamAgent>>;
241 removeTeamAgent(actor: User, workspace: string, team: string, agentId: string): Promise<Result<boolean>>;
242 /** For the agents service: the visible teams an agent is on, with everyone on each. */
243 agentTeams(workspace: string, agentId: string, homeTeam: string | null): Promise<AgentTeam[]>;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar244 userTeams(viewer: User | null, workspace: string, username: string): Promise<Result<Team[]>>;
245 /** Each member's teams the viewer can see. Members only. */
246 teamMemberships(viewer: User | null, workspace: string): Promise<Result<MemberTeams[]>>;
247}

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