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 | ||
| 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`). */ |
| 117 | export 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. */ | |
| 120 | export 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. */ |
| 123 | export 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 bar | 128 | export 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 bar | 147 | viewer_role: TeamRole | null; |
| 148 | can_manage: boolean; | |
| 149 | created_at: string; | |
| 150 | updated_at: string; | |
| 151 | }; | |
| 152 | ||
| 153 | export 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 | ||
| 162 | export 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. */ | |
| 172 | export type RepoTeam = { | |
| 173 | slug: string; | |
| 174 | name: string; | |
| 175 | role: RepoRole; | |
| 176 | members_count: number; | |
| 177 | visibility: TeamVisibility; | |
| 178 | }; | |
| 179 | ||
| 180 | export 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. */ | |
| 191 | export 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 bar | 206 | }; |
| 207 | ||
| 208 | /** `@acme/backend`, the way a team is mentioned. */ | |
| 209 | export 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. */ | |
| 214 | export 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. */ | |
| 225 | export type MemberTeams = { username: string; teams: TeamRef[] }; | |
| 226 | ||
| 227 | /** Identity's team methods. */ | |
| 228 | export 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 bar | 234 | 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 bar | 258 | 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.