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.
| 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. | 1 | import { Check, GripVertical, Pencil, Plus, Trash2, X } from "lucide-react"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 2 | import { useState } from "react"; |
| 3 | ||
| 4 | import type { AgentRouting, ModelTier, SubagentDef } from "@g1t/contracts"; | |
| 5 | ||
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 6 | import { Button } from "./ui/button"; |
| 7 | import { Card } from "./ui/card"; | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 8 | import { Hint } from "./ui/hint"; |
| The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work. | 9 | import { Input } from "./ui/input"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 10 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; |
| 11 | import { Sheet, SheetContent, SheetDescription, SheetTitle } from "./ui/sheet"; | |
| 12 | import { MAX_RESPONSIBILITIES, TIER_LABELS, clampRouting, cleanSubagentName } from "../lib/agent-form"; | |
| 13 | ||
| 14 | const FIELD = | |
| 15 | "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"; | |
| 16 | ||
| 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. | 17 | /** |
| 18 | * The teams to add a new agent to as it is made: the workspace's teams the | |
| 19 | * person manages, as chips to press. Membership is the team's, as anyone's | |
| 20 | * is; with `name`, the chosen teams post as that field, one each. | |
| 21 | */ | |
| 22 | export function TeamPicker({ | |
| 23 | teams, | |
| 24 | value, | |
| 25 | onChange, | |
| 26 | name, | |
| 27 | }: { | |
| 28 | teams: { slug: string; name: string }[]; | |
| 29 | /** Controlled: the chosen slugs; otherwise the picker keeps its own. */ | |
| 30 | value?: string[]; | |
| 31 | onChange?: (next: string[]) => void; | |
| 32 | name?: string; | |
| 33 | }) { | |
| 34 | const [own, setOwn] = useState<string[]>([]); | |
| 35 | const chosen = value ?? own; | |
| 36 | const set = onChange ?? setOwn; | |
| 37 | if (!teams.length) { | |
| 38 | return <p className="text-[0.8125rem] text-muted">No teams you can add agents to yet. Owners and a team's maintainers add agents to it.</p>; | |
| 39 | } | |
| 40 | return ( | |
| 41 | <div> | |
| 42 | <div role="group" aria-label="Add to teams" className="flex flex-wrap gap-1.5"> | |
| 43 | {teams.map((team) => { | |
| 44 | const on = chosen.includes(team.slug); | |
| 45 | return ( | |
| 46 | <button | |
| 47 | key={team.slug} | |
| 48 | type="button" | |
| 49 | aria-pressed={on} | |
| 50 | onClick={() => set(on ? chosen.filter((slug) => slug !== team.slug) : [...chosen, team.slug])} | |
| 51 | className={`inline-flex h-9 items-center gap-1.5 rounded-full border px-3 text-[0.8125rem] transition-colors sm:h-8 ${ | |
| 52 | on ? "border-accent/50 bg-accent/10 text-fg" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 53 | }`} | |
| 54 | > | |
| 55 | {on ? <Check size={13} className="text-accent" /> : <Plus size={13} />} | |
| 56 | {team.name} | |
| 57 | </button> | |
| 58 | ); | |
| 59 | })} | |
| 60 | </div> | |
| 61 | {name && chosen.map((slug) => <input key={slug} type="hidden" name={name} value={slug} />)} | |
| 62 | </div> | |
| 63 | ); | |
| 64 | } | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 65 | |
| 66 | /** | |
| 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. | 67 | * An agent's role: its title and what it is responsible for, as a list to |
| 68 | * add to, edit and reorder. Making one, the teams to add it to as well: | |
| 69 | * teams are memberships, changed on the team or the agent's profile after. | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 70 | */ |
| 71 | export function RoleFields({ | |
| 72 | title, | |
| 73 | responsibilities, | |
| 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. | 74 | joinable, |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 75 | errors, |
| 76 | locked, | |
| Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how. | 77 | personal = false, |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 78 | }: { |
| 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. | 79 | /** A personal agent: on no team. */ |
| Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how. | 80 | personal?: boolean; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 81 | title: string; |
| 82 | responsibilities: string[]; | |
| 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. | 83 | /** Making an agent: the teams the person may add it to. Null on a profile, where its Teams section does that. */ |
| 84 | joinable?: { slug: string; name: string }[] | null; | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 85 | errors: Record<string, string>; |
| 86 | locked?: boolean; | |
| 87 | }) { | |
| 88 | const [items, setItems] = useState<string[]>(responsibilities.length > 0 ? responsibilities : [""]); | |
| 89 | const [dragging, setDragging] = useState<number | null>(null); | |
| 90 | const move = (from: number, to: number) => | |
| 91 | setItems((now) => { | |
| 92 | const next = [...now]; | |
| 93 | const [item] = next.splice(from, 1); | |
| 94 | next.splice(to, 0, item!); | |
| 95 | return next; | |
| 96 | }); | |
| 97 | return ( | |
| 98 | <> | |
| 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. | 99 | <div className="max-w-sm"> |
| 100 | <label htmlFor="title" className="mb-1.5 flex items-baseline justify-between text-sm font-medium text-fg-soft"> | |
| 101 | Title | |
| 102 | {errors.title && <span className="text-xs font-normal text-danger">{errors.title}</span>} | |
| 103 | </label> | |
| 104 | <input | |
| 105 | id="title" | |
| 106 | name="title" | |
| 107 | defaultValue={title} | |
| 108 | readOnly={locked} | |
| 109 | placeholder="QA Engineer" | |
| 110 | className={`${FIELD} ${locked ? "cursor-not-allowed text-muted" : ""}`} | |
| 111 | autoComplete="off" | |
| 112 | data-1p-ignore | |
| 113 | /> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 114 | </div> |
| 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. | 115 | {joinable && ( |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 116 | <div> |
| 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. | 117 | <p className="mb-1.5 flex items-baseline justify-between gap-3 text-sm font-medium text-fg-soft"> |
| 118 | Add to teams | |
| 119 | <span className="text-xs font-normal text-faint">{personal ? "" : "Optional. Like adding a person."}</span> | |
| 120 | </p> | |
| 121 | {personal ? ( | |
| 122 | <p className="text-[0.8125rem] text-muted">A personal agent is on no team. Once an owner promotes it, add it to teams like anyone.</p> | |
| 123 | ) : ( | |
| 124 | <TeamPicker teams={joinable} name="teams" /> | |
| 125 | )} | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 126 | </div> |
| 127 | )} | |
| 128 | <div> | |
| 129 | <p className="mb-1.5 flex items-baseline justify-between text-sm font-medium text-fg-soft"> | |
| 130 | Responsibilities | |
| 131 | <span className="text-xs font-normal text-faint">Broad duties, not tasks. Up to {MAX_RESPONSIBILITIES}.</span> | |
| 132 | </p> | |
| 133 | <ul className="space-y-1.5"> | |
| 134 | {items.map((item, index) => ( | |
| 135 | <li | |
| 136 | key={index} | |
| 137 | draggable={!locked && items.length > 1} | |
| 138 | onDragStart={() => setDragging(index)} | |
| 139 | onDragOver={(event) => dragging != null && event.preventDefault()} | |
| 140 | onDrop={() => { | |
| 141 | if (dragging != null && dragging !== index) move(dragging, index); | |
| 142 | setDragging(null); | |
| 143 | }} | |
| 144 | onDragEnd={() => setDragging(null)} | |
| 145 | className={`group flex items-center gap-1.5 ${dragging === index ? "opacity-50" : ""}`} | |
| 146 | > | |
| 147 | <span className="cursor-grab text-faint opacity-0 transition-opacity group-hover:opacity-100" aria-hidden="true"> | |
| 148 | <GripVertical size={14} /> | |
| 149 | </span> | |
| 150 | <input | |
| 151 | name="responsibility" | |
| 152 | value={item} | |
| 153 | onChange={(event) => setItems((now) => now.map((v, i) => (i === index ? event.target.value : v)))} | |
| 154 | readOnly={locked} | |
| 155 | aria-label={`Responsibility ${index + 1}`} | |
| 156 | placeholder={index === 0 ? "Keep the test suite green and fast" : "Another duty"} | |
| 157 | className={FIELD} | |
| 158 | autoComplete="off" | |
| 159 | data-1p-ignore | |
| 160 | /> | |
| 161 | {!locked && ( | |
| 162 | <Hint label="Remove"> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 163 | <Button |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 164 | type="button" |
| 165 | aria-label={`Remove responsibility ${index + 1}`} | |
| 166 | onClick={() => setItems((now) => (now.length > 1 ? now.filter((_, i) => i !== index) : [""]))} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 167 | variant="ghost" |
| 168 | size="icon-sm" | |
| 169 | className="text-faint" | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 170 | > |
| 171 | <X size={14} /> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 172 | </Button> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 173 | </Hint> |
| 174 | )} | |
| 175 | </li> | |
| 176 | ))} | |
| 177 | </ul> | |
| 178 | {!locked && items.length < MAX_RESPONSIBILITIES && ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 179 | <Button |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 180 | type="button" |
| 181 | onClick={() => setItems((now) => [...now, ""])} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 182 | variant="ghost" |
| 183 | size="xs" | |
| 184 | className="mt-2 gap-1.5 text-[0.8125rem] font-normal" | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 185 | > |
| 186 | <Plus size={14} /> | |
| 187 | Add a responsibility | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 188 | </Button> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 189 | )} |
| 190 | </div> | |
| 191 | </> | |
| 192 | ); | |
| 193 | } | |
| 194 | ||
| 195 | const BLANK_SUBAGENT: SubagentDef = { name: "", description: "", instructions: "", routing: { floor: null, ceiling: null }, max_parallel: 2 }; | |
| 196 | ||
| 197 | /** | |
| 198 | * Subagents: help an agent keeps for its own work, as a list, edited in a | |
| 199 | * drawer. Their routing stays within the agent's own. Posted as JSON. | |
| 200 | */ | |
| 201 | export function SubagentsField({ initial, routing }: { initial: SubagentDef[]; routing: Pick<AgentRouting, "floor" | "ceiling"> }) { | |
| 202 | const [list, setList] = useState<SubagentDef[]>(initial); | |
| 203 | const [editing, setEditing] = useState<{ index: number | null; draft: SubagentDef } | null>(null); | |
| 204 | const [error, setError] = useState<string | null>(null); | |
| 205 | const save = () => { | |
| 206 | if (!editing) return; | |
| 207 | const name = cleanSubagentName(editing.draft.name); | |
| 208 | if (!name) return setError("Give it a name, like flake-hunter."); | |
| 209 | if (list.some((s, i) => s.name === name && i !== editing.index)) return setError(`There's already a ${name}.`); | |
| 210 | const next: SubagentDef = { ...editing.draft, name, routing: clampRouting(editing.draft.routing, routing) }; | |
| 211 | setList((now) => (editing.index == null ? [...now, next] : now.map((s, i) => (i === editing.index ? next : s)))); | |
| 212 | setEditing(null); | |
| 213 | }; | |
| 214 | const tier = (value: string): ModelTier | null => (value === "none" ? null : (value as ModelTier)); | |
| 215 | return ( | |
| 216 | <div> | |
| 217 | <input type="hidden" name="subagents" value={JSON.stringify(list)} /> | |
| 218 | <p className="mb-3 inline-flex items-center gap-1.5 rounded-full border border-line px-2.5 py-0.5 text-xs text-muted">Used inside tasks: coming soon</p> | |
| 219 | {list.length === 0 ? ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 220 | <Card asChild tone="plain" radius="lg" className="border-dashed px-4 py-5 text-sm text-muted"> |
| 221 | <p>None yet. A subagent is help it calls on for one kind of work, such as finding flaky tests.</p> | |
| 222 | </Card> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 223 | ) : ( |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 224 | <Card asChild tone="plain" radius="lg" divided> |
| 225 | <ul> | |
| 226 | {list.map((sub, index) => ( | |
| 227 | <li key={sub.name} className="flex items-center gap-3 px-3.5 py-2.5"> | |
| 228 | <span className="min-w-0 grow"> | |
| 229 | <span className="block font-mono text-sm text-fg">{sub.name}</span> | |
| 230 | <span className="block truncate text-xs text-muted">{sub.description || "No description"}</span> | |
| 231 | </span> | |
| 232 | <span className="hidden text-xs text-faint sm:inline"> | |
| 233 | {sub.routing.floor ?? "any"} to {sub.routing.ceiling ?? "any"} · {sub.max_parallel} at once | |
| 234 | </span> | |
| 235 | <Hint label="Edit"> | |
| 236 | <Button | |
| 237 | type="button" | |
| 238 | aria-label={`Edit ${sub.name}`} | |
| 239 | onClick={() => { | |
| 240 | setError(null); | |
| 241 | setEditing({ index, draft: sub }); | |
| 242 | }} | |
| 243 | variant="ghost" | |
| 244 | size="icon-sm" | |
| 245 | className="text-faint" | |
| 246 | > | |
| 247 | <Pencil size={14} /> | |
| 248 | </Button> | |
| 249 | </Hint> | |
| 250 | <Hint label="Remove"> | |
| 251 | <Button | |
| 252 | type="button" | |
| 253 | aria-label={`Remove ${sub.name}`} | |
| 254 | onClick={() => setList((now) => now.filter((_, i) => i !== index))} | |
| 255 | variant="ghost" | |
| 256 | size="icon-sm" | |
| 257 | className="text-faint hover:text-danger" | |
| 258 | > | |
| 259 | <Trash2 size={14} /> | |
| 260 | </Button> | |
| 261 | </Hint> | |
| 262 | </li> | |
| 263 | ))} | |
| 264 | </ul> | |
| 265 | </Card> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 266 | )} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 267 | <Button |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 268 | type="button" |
| 269 | onClick={() => { | |
| 270 | setError(null); | |
| 271 | setEditing({ index: null, draft: { ...BLANK_SUBAGENT, routing: clampRouting(BLANK_SUBAGENT.routing, routing) } }); | |
| 272 | }} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 273 | variant="ghost" |
| 274 | size="xs" | |
| 275 | className="mt-2 gap-1.5 text-[0.8125rem] font-normal" | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 276 | > |
| 277 | <Plus size={14} /> | |
| 278 | Add a subagent | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 279 | </Button> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 280 | <Sheet open={editing != null} onOpenChange={(open) => !open && setEditing(null)}> |
| 281 | <SheetContent side="right" className="w-[28rem] max-w-[100vw]"> | |
| 282 | {editing && ( | |
| 283 | <div className="flex h-full flex-col"> | |
| 284 | <div className="border-b border-line px-5 py-4"> | |
| 285 | <SheetTitle>{editing.index == null ? "New subagent" : editing.draft.name}</SheetTitle> | |
| 286 | <SheetDescription className="mt-1 text-sm text-muted">Its routing stays within its agent's own limits.</SheetDescription> | |
| 287 | </div> | |
| 288 | <div className="min-h-0 grow space-y-5 overflow-y-auto px-5 py-5"> | |
| 289 | <label className="block"> | |
| 290 | <span className="mb-1.5 block text-sm font-medium text-fg-soft">Name</span> | |
| 291 | <input | |
| 292 | value={editing.draft.name} | |
| 293 | onChange={(event) => setEditing({ ...editing, draft: { ...editing.draft, name: event.target.value } })} | |
| 294 | placeholder="flake-hunter" | |
| 295 | className={`${FIELD} font-mono`} | |
| 296 | autoComplete="off" | |
| 297 | data-1p-ignore | |
| 298 | /> | |
| 299 | </label> | |
| 300 | <label className="block"> | |
| 301 | <span className="mb-1.5 block text-sm font-medium text-fg-soft">Description</span> | |
| 302 | <input | |
| 303 | value={editing.draft.description} | |
| 304 | onChange={(event) => setEditing({ ...editing, draft: { ...editing.draft, description: event.target.value } })} | |
| 305 | placeholder="Finds which tests flake, and why" | |
| 306 | className={FIELD} | |
| 307 | autoComplete="off" | |
| 308 | data-1p-ignore | |
| 309 | /> | |
| 310 | </label> | |
| 311 | <label className="block"> | |
| 312 | <span className="mb-1.5 block text-sm font-medium text-fg-soft">Instructions</span> | |
| 313 | <textarea | |
| 314 | value={editing.draft.instructions} | |
| 315 | onChange={(event) => setEditing({ ...editing, draft: { ...editing.draft, instructions: event.target.value } })} | |
| 316 | rows={6} | |
| 317 | placeholder="Rerun the failing test 20 times; report the failure rate and the first stack trace." | |
| 318 | className={`${FIELD} resize-y font-mono text-[0.8125rem]`} | |
| 319 | /> | |
| 320 | </label> | |
| 321 | <div className="grid grid-cols-2 gap-4"> | |
| 322 | {(["floor", "ceiling"] as const).map((key) => ( | |
| 323 | <div key={key}> | |
| 324 | <span className="mb-1.5 block text-sm font-medium text-fg-soft capitalize">{key}</span> | |
| 325 | <Select | |
| 326 | value={editing.draft.routing[key] ?? "none"} | |
| 327 | onValueChange={(value) => | |
| 328 | setEditing({ ...editing, draft: { ...editing.draft, routing: clampRouting({ ...editing.draft.routing, [key]: tier(value) }, routing) } }) | |
| 329 | } | |
| 330 | > | |
| 331 | <SelectTrigger aria-label={key}> | |
| 332 | <SelectValue /> | |
| 333 | </SelectTrigger> | |
| 334 | <SelectContent> | |
| 335 | <SelectItem value="none">{key === "floor" ? "No floor" : "No ceiling"}</SelectItem> | |
| 336 | {(["small", "large", "frontier"] as const).map((t) => ( | |
| 337 | <SelectItem key={t} value={t}> | |
| 338 | {TIER_LABELS[t].split(":")[0]} | |
| 339 | </SelectItem> | |
| 340 | ))} | |
| 341 | </SelectContent> | |
| 342 | </Select> | |
| 343 | </div> | |
| 344 | ))} | |
| 345 | </div> | |
| 346 | <label className="block max-w-40"> | |
| 347 | <span className="mb-1.5 block text-sm font-medium text-fg-soft">At once, in one task</span> | |
| The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work. | 348 | <Input |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 349 | type="number" |
| 350 | min={1} | |
| 351 | max={8} | |
| 352 | value={editing.draft.max_parallel} | |
| 353 | onChange={(event) => | |
| 354 | setEditing({ ...editing, draft: { ...editing.draft, max_parallel: Math.max(1, Math.min(8, Number(event.target.value) || 1)) } }) | |
| 355 | } | |
| 356 | /> | |
| 357 | </label> | |
| 358 | </div> | |
| 359 | <div className="flex items-center gap-3 border-t border-line px-5 py-3"> | |
| 360 | {error && <p className="text-sm text-danger">{error}</p>} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 361 | <Button |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 362 | type="button" |
| 363 | onClick={() => setEditing(null)} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 364 | variant="outline" |
| 365 | className="ml-auto text-fg/90" | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 366 | > |
| 367 | Cancel | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 368 | </Button> |
| 369 | <Button type="button" onClick={save} variant="accent"> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 370 | {editing.index == null ? "Add" : "Done"} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 371 | </Button> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 372 | </div> |
| 373 | </div> | |
| 374 | )} | |
| 375 | </SheetContent> | |
| 376 | </Sheet> | |
| 377 | </div> | |
| 378 | ); | |
| 379 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.