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 bar | 1 | /** |
| 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 | */ | |
| 6 | import type { RepoRole } from "./access"; | |
| 7 | import type { User } from "./identity"; | |
| 8 | import type { Result } from "./result"; | |
| 9 | ||
| 10 | export 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 | */ | |
| 16 | export type TeamCreation = "members" | "owners"; | |
| 17 | ||
| 18 | /** Whether someone with `role` in a workspace may create its teams. */ | |
| 19 | export 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 bar | 23 | export type TeamRole = "member" | "maintainer"; |
| 24 | export type ReviewAlgorithm = "round_robin" | "load_balance"; | |
| 25 | ||
| 26 | export const TEAM_VISIBILITY_LABELS: Record<TeamVisibility, string> = { | |
| 27 | visible: "Visible", | |
| 28 | secret: "Secret", | |
| 29 | }; | |
| 30 | ||
| 31 | export 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 | ||
| 36 | export const REVIEW_ALGORITHM_LABELS: Record<ReviewAlgorithm, string> = { | |
| 37 | round_robin: "Round robin", | |
| 38 | load_balance: "Load balance", | |
| 39 | }; | |
| 40 | ||
| 41 | export 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. */ | |
| 47 | export const MAX_ASSIGNED = 10; | |
| 48 | ||
| 49 | export 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 | ||
| 60 | export 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 | ||
| 71 | export 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). */ |
| 74 | export 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. */ | |
| 79 | export type TeamChannel = { id: string; name: string }; | |
| 80 | ||
| 81 | /** An agent added to a team. */ | |
| 82 | export 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`). */ | |
| 90 | export 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. */ | |
| 104 | export 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. */ | |
| 117 | export 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 bar | 122 | export 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 bar | 141 | viewer_role: TeamRole | null; |
| 142 | can_manage: boolean; | |
| 143 | created_at: string; | |
| 144 | updated_at: string; | |
| 145 | }; | |
| 146 | ||
| 147 | export 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 | ||
| 156 | export 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. */ | |
| 166 | export type RepoTeam = { | |
| 167 | slug: string; | |
| 168 | name: string; | |
| 169 | role: RepoRole; | |
| 170 | members_count: number; | |
| 171 | visibility: TeamVisibility; | |
| 172 | }; | |
| 173 | ||
| 174 | export 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. */ | |
| 185 | export 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 bar | 200 | }; |
| 201 | ||
| 202 | /** `@acme/backend`, the way a team is mentioned. */ | |
| 203 | export 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. */ | |
| 208 | export 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. */ | |
| 219 | export type MemberTeams = { username: string; teams: TeamRef[] }; | |
| 220 | ||
| 221 | /** Identity's team methods. */ | |
| 222 | export 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 bar | 228 | 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 bar | 244 | 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.