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.
| The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how. | 1 | import { Activity, Brain, ChevronRight, Dices, LayoutTemplate, Network, Plus, Route as RouteIcon, Sparkles } from "lucide-react"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 2 | import { type ReactNode, useState } from "react"; |
| 3 | import { Form, NavLink, useLocation, useNavigation, useRouteLoaderData } from "react-router"; | |
| 4 | ||
| 5 | import type { AgentTemplate, ModelTier, WorkspaceAgent } from "@g1t/contracts"; | |
| 6 | ||
| 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) | 7 | import { type AgentLike, AgentAvatar, PixelCreature } from "./agent-avatar"; |
| 8 | import { StatusDot, statusLabel } from "./chat/marks"; | |
| 9 | import { isOrchestrator } from "./orchestrator"; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 10 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; |
| 11 | import { Skeleton } from "./ui/skeleton"; | |
| 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) | 12 | import { AUTONOMY, type AgentDraft, PRESETS, TIER_LABELS, cleanHandle, dollarsField } from "../lib/agent-form"; |
| 13 | import { Hint } from "./ui/hint"; | |
| 14 | import { DEPARTMENTS, RoleFields, SubagentsField } from "./agent-role"; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 15 | import type { AgentsLayoutData } from "../routes/workspace/agents/layout"; |
| 16 | ||
| 17 | /** The workspace's agents while an Agents page is open; undefined elsewhere. */ | |
| 18 | export function useAgentsData(): AgentsLayoutData | undefined { | |
| 19 | return useRouteLoaderData("routes/workspace/agents/layout") as AgentsLayoutData | undefined; | |
| 20 | } | |
| 21 | ||
| 22 | /** An agent's face: its picture, or the sparkle mark. */ | |
| 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) | 23 | export function AgentFace({ agent, size = 20 }: { agent: AgentLike; size?: number }) { |
| 24 | return <AgentAvatar agent={agent} size={size} />; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 25 | } |
| 26 | ||
| 27 | function SideLink({ to, end, icon, children, trailing }: { to: string; end?: boolean; icon: ReactNode; children: ReactNode; trailing?: ReactNode }) { | |
| 28 | return ( | |
| 29 | <NavLink | |
| 30 | to={to} | |
| 31 | end={end} | |
| 32 | prefetch="intent" | |
| 33 | className={({ isActive }) => | |
| 34 | `group flex h-9 items-center gap-2.5 rounded-md px-2 text-[0.8125rem] transition-colors ${ | |
| 35 | isActive ? "bg-raised font-medium text-fg" : "text-muted hover:bg-raised/60 hover:text-fg" | |
| 36 | }` | |
| 37 | } | |
| 38 | > | |
| 39 | <span className="flex w-5 shrink-0 justify-center">{icon}</span> | |
| 40 | <span className="min-w-0 grow truncate">{children}</span> | |
| 41 | {trailing} | |
| 42 | </NavLink> | |
| 43 | ); | |
| 44 | } | |
| 45 | ||
| 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) | 46 | /** An agent as the sidebar lists it: from the Agents pages' own data, or the shell's on any other page. */ |
| 47 | type Listed = Pick<WorkspaceAgent, "id" | "handle" | "display_name" | "avatar" | "role" | "status" | "title" | "team" | "department"> & { | |
| 48 | builtin?: boolean; | |
| 49 | avatar_seed?: string | null; | |
| 50 | }; | |
| 51 | ||
| 52 | /** Where an agent sits in the org chart: its team, else its department. */ | |
| 53 | export function placeOf(agent: Pick<WorkspaceAgent, "team" | "department">): string { | |
| 54 | if (agent.team) return agent.team.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); | |
| 55 | return agent.department?.trim() || "Unplaced"; | |
| 56 | } | |
| 57 | ||
| 58 | /** "Margo · QA Engineer": how an agent reads wherever it shows. */ | |
| 59 | export function nameAndTitle(agent: Pick<WorkspaceAgent, "display_name" | "title">): string { | |
| 60 | return agent.title ? `${agent.display_name} · ${agent.title}` : agent.display_name; | |
| 61 | } | |
| 62 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 63 | /** |
| 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) | 64 | * Agents mode's sidebar: the fleet's runs, context and memory; g1t, the |
| 65 | * orchestrator, pinned at the top; then the specialists, each with its | |
| 66 | * role and status; and the way to make a new one. | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 67 | */ |
| 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) | 68 | export function AgentsSidebar({ |
| 69 | slug, | |
| 70 | shellAgents, | |
| 71 | code, | |
| 72 | owner, | |
| 73 | phone = false, | |
| 74 | }: { | |
| 75 | slug: string; | |
| 76 | /** A phone's Agents tab: the list as the page, with no heading of its own and no fleet row (the fleet is under it). */ | |
| 77 | phone?: boolean; | |
| 78 | /** The shell's list, for pages outside `-/agents` (context, memory). */ | |
| 79 | shellAgents: Listed[] | null; | |
| 80 | /** Context and memory are about the code: only with Code access. */ | |
| 81 | code: boolean; | |
| 82 | owner: boolean; | |
| 83 | }) { | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 84 | const data = useAgentsData(); |
| 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 | const agents: Listed[] | null = data?.agents ?? shellAgents; |
| Agents mode: an overview of every agent's work and spend, and each agent's sessions, memory, routines, spend, activity and versions | 86 | const live = data?.live ?? {}; |
| 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) | 87 | const orchestrator = agents?.find((agent) => isOrchestrator(agent)) ?? null; |
| 88 | const specialists = (agents ?? []).filter((agent) => !isOrchestrator(agent)); | |
| 89 | const row = (agent: Listed) => ( | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 90 | <li key={agent.id}> |
| 91 | <NavLink | |
| 92 | to={`/${slug}/-/agents/${agent.handle}`} | |
| 93 | prefetch="intent" | |
| 94 | className={({ isActive }) => | |
| 95 | `group flex h-11 items-center gap-2.5 rounded-md px-2 transition-colors ${isActive ? "bg-raised" : "hover:bg-raised/60"}` | |
| 96 | } | |
| 97 | > | |
| 98 | <span className="relative shrink-0"> | |
| 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) | 99 | <AgentFace agent={{ ...agent, builtin: isOrchestrator(agent) }} size={26} /> |
| 100 | {!isOrchestrator(agent) && ( | |
| g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them. | 101 | <StatusDot status={agent.status} className="absolute -right-0.5 -bottom-0.5 ring-2 ring-shell" /> |
| 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) | 102 | )} |
| Chat and workspace agents: channels, DMs and named agents you talk to | 103 | </span> |
| 104 | <span className="min-w-0 grow leading-tight"> | |
| 105 | <span className="block truncate text-[0.8125rem] font-medium text-fg">{agent.display_name}</span> | |
| 106 | <span className="block truncate text-[0.6875rem] 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) | 107 | {isOrchestrator(agent) ? "Orchestrator" : agent.status === "idle" ? agent.title || agent.role : `${statusLabel(agent.status)} · ${agent.title || agent.role}`} |
| Chat and workspace agents: channels, DMs and named agents you talk to | 108 | </span> |
| 109 | </span> | |
| Agents mode: an overview of every agent's work and spend, and each agent's sessions, memory, routines, spend, activity and versions | 110 | {(live[agent.id] ?? 0) > 0 && ( |
| 111 | <Hint label={`${live[agent.id]} live ${live[agent.id] === 1 ? "session" : "sessions"}`}> | |
| 112 | <span className="flex h-5 min-w-5 shrink-0 items-center justify-center gap-1 rounded-full bg-accent/15 px-1.5 text-[0.6875rem] font-medium text-accent tabular-nums"> | |
| 113 | <span className="size-1.5 animate-pulse rounded-full bg-accent motion-reduce:animate-none" aria-hidden="true" /> | |
| 114 | {live[agent.id]} | |
| 115 | <span className="sr-only"> live sessions</span> | |
| 116 | </span> | |
| 117 | </Hint> | |
| 118 | )} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 119 | </NavLink> |
| 120 | </li> | |
| 121 | ); | |
| 122 | return ( | |
| 123 | <div className="flex h-full flex-col"> | |
| g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them. | 124 | <div className={`flex h-9 shrink-0 items-center justify-between pr-1 pl-3 ${phone ? "hidden" : ""}`}> |
| 125 | <h2 className="text-xs font-medium text-faint">Agents</h2> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 126 | <NavLink |
| 127 | to={`/${slug}/-/agents/new`} | |
| 128 | aria-label="New agent" | |
| 129 | className="flex size-8 items-center justify-center rounded-md text-faint transition-colors hover:bg-raised hover:text-fg" | |
| 130 | > | |
| 131 | <Plus size={16} /> | |
| 132 | </NavLink> | |
| 133 | </div> | |
| 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) | 134 | <nav aria-label="Agents" className={`min-h-0 grow overflow-y-auto px-2.5 pt-3 pb-4 [scrollbar-width:thin] ${phone ? "[&_a]:min-h-11" : ""}`}> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 135 | <div className="space-y-px"> |
| 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) | 136 | {!phone && ( |
| 137 | <SideLink to={`/${slug}/-/agents`} end icon={<Activity size={15} className="text-faint" />}> | |
| Agents mode: an overview of every agent's work and spend, and each agent's sessions, memory, routines, spend, activity and versions | 138 | Overview |
| 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) | 139 | </SideLink> |
| 140 | )} | |
| The Marketplace adds what g1t doesn't do and connects what you already use: extensions (Mail, Support, CRM, Recruiting, On-call, and bridges to a helpdesk, CRM or ERP you run, all Soon), starter kits, and integrations connected by the workspace or by each person; agents aren't installed there. Agent templates live in Agents, as starting points you configure, at Agents → Templates, and the Marketplace's old agent addresses lead there. The marketplace and agents guides say how. | 141 | <SideLink to={`/${slug}/-/agents/templates`} icon={<LayoutTemplate size={15} className="text-faint" />}> |
| 142 | Templates | |
| 143 | </SideLink> | |
| 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) | 144 | {code && ( |
| 145 | <> | |
| 146 | <SideLink to={`/${slug}/-/context`} icon={<Network size={15} className="text-faint" />}> | |
| 147 | Context | |
| 148 | </SideLink> | |
| 149 | <SideLink to={`/${slug}/-/memory`} icon={<Brain size={15} className="text-faint" />}> | |
| 150 | Memory | |
| 151 | </SideLink> | |
| 152 | </> | |
| 153 | )} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 154 | </div> |
| 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) | 155 | {agents == null ? ( |
| 156 | data === undefined && shellAgents === null ? ( | |
| 157 | <p className="mt-4 px-2 text-xs leading-relaxed text-faint">The agents service didn't answer. Your agents will show here once it does.</p> | |
| 158 | ) : ( | |
| 159 | <div className="mt-5 space-y-3 px-2" aria-busy="true"> | |
| 160 | {[0, 1, 2].map((i) => ( | |
| 161 | <div key={i} className="flex items-center gap-2.5"> | |
| 162 | <Skeleton className="size-6 rounded-md" /> | |
| 163 | <Skeleton className="h-3 w-28" /> | |
| 164 | </div> | |
| 165 | ))} | |
| 166 | </div> | |
| 167 | ) | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 168 | ) : ( |
| 169 | <> | |
| 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 | {orchestrator && <Group title="Orchestrator">{row(orchestrator)}</Group>} |
| 171 | <p className="mt-4 px-2 text-[0.6875rem] leading-snug text-faint">Specialists are colleagues hired into a role. g1t hands them work.</p> | |
| 172 | {[...orgChart(specialists)].map(([place, members]) => ( | |
| 173 | <Group key={place} title={`${place} · ${members.length}`} collapsible> | |
| 174 | {members.map(row)} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 175 | </Group> |
| 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) | 176 | ))} |
| 177 | {specialists.length === 0 && <p className="px-2 py-1 text-xs text-faint">No specialists yet. Hire one into a role.</p>} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 178 | </> |
| 179 | )} | |
| 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 | {owner && ( |
| 181 | <NavLink | |
| 182 | to={`/${slug}/-/agents/new`} | |
| 183 | className={({ isActive }) => | |
| 184 | `mt-2 flex h-9 items-center gap-2.5 rounded-md border border-dashed px-2 text-[0.8125rem] transition-colors ${ | |
| 185 | isActive ? "border-accent/50 bg-accent/10 text-fg" : "border-line text-muted hover:border-line-strong hover:text-fg" | |
| 186 | }` | |
| 187 | } | |
| 188 | > | |
| 189 | <span className="flex w-5 justify-center"> | |
| 190 | <Plus size={15} /> | |
| 191 | </span> | |
| 192 | New agent | |
| 193 | </NavLink> | |
| 194 | )} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 195 | </nav> |
| 196 | </div> | |
| 197 | ); | |
| 198 | } | |
| 199 | ||
| 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) | 200 | /** Specialists by team or department, each group by name, the groups in the gallery's order. */ |
| 201 | function orgChart(agents: Listed[]): Map<string, Listed[]> { | |
| 202 | const groups = new Map<string, Listed[]>(); | |
| 203 | for (const agent of agents) groups.set(placeOf(agent), [...(groups.get(placeOf(agent)) ?? []), agent]); | |
| 204 | const rank = (place: string) => { | |
| 205 | const at = DEPARTMENTS.indexOf(place); | |
| 206 | return at < 0 ? DEPARTMENTS.length : at; | |
| 207 | }; | |
| 208 | return new Map( | |
| 209 | [...groups] | |
| 210 | .sort(([a], [b]) => rank(a) - rank(b) || a.localeCompare(b)) | |
| 211 | .map(([place, list]) => [place, list.sort((x, y) => x.display_name.localeCompare(y.display_name))]), | |
| 212 | ); | |
| 213 | } | |
| 214 | ||
| 215 | function Group({ title, children, collapsible }: { title: string; children: ReactNode; collapsible?: boolean }) { | |
| 216 | const [open, setOpen] = useState(true); | |
| 217 | if (collapsible) { | |
| 218 | return ( | |
| 219 | <section className="mt-3"> | |
| 220 | <button | |
| 221 | type="button" | |
| 222 | onClick={() => setOpen(!open)} | |
| 223 | aria-expanded={open} | |
| 224 | className="mb-1 flex w-full items-center gap-1 rounded px-1 text-xs font-medium text-faint transition-colors hover:text-muted" | |
| 225 | > | |
| 226 | <ChevronRight size={12} className={`transition-transform ${open ? "rotate-90" : ""}`} /> | |
| 227 | {title} | |
| 228 | </button> | |
| 229 | {open && <ul className="space-y-px border-l border-line pl-1.5 ml-2.5">{children}</ul>} | |
| 230 | </section> | |
| 231 | ); | |
| 232 | } | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 233 | return ( |
| 234 | <section className="mt-4"> | |
| 235 | <h3 className="mb-1 px-2 text-xs font-medium text-faint">{title}</h3> | |
| 236 | <ul className="space-y-px">{children}</ul> | |
| 237 | </section> | |
| 238 | ); | |
| 239 | } | |
| 240 | ||
| 241 | /** The templates g1t ships, and a blank one, to start a new agent from. */ | |
| 242 | export function TemplateGallery({ templates, chosen, onChoose }: { templates: AgentTemplate[]; chosen: string | null; onChoose: (id: string | null) => void }) { | |
| 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) | 243 | // By department, in the order the departments are listed; any other after them. |
| 244 | const order = (department: string) => { | |
| 245 | const at = DEPARTMENTS.indexOf(department); | |
| 246 | return at < 0 ? DEPARTMENTS.length : at; | |
| 247 | }; | |
| 248 | const groups = new Map<string, AgentTemplate[]>(); | |
| 249 | for (const template of [...templates].sort((a, b) => order(a.department) - order(b.department))) { | |
| 250 | const key = template.department || "Other"; | |
| 251 | groups.set(key, [...(groups.get(key) ?? []), template]); | |
| 252 | } | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 253 | return ( |
| 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) | 254 | <div className="space-y-8"> |
| 255 | {[...groups].map(([department, list]) => ( | |
| 256 | <section key={department}> | |
| 257 | <h2 className="mb-3 text-xs font-semibold tracking-wide text-faint uppercase">{department}</h2> | |
| 258 | <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3"> | |
| 259 | {list.map((template) => ( | |
| 260 | <TemplateCard | |
| 261 | key={template.id} | |
| 262 | selected={chosen === template.id} | |
| 263 | onClick={() => onChoose(template.id)} | |
| 264 | name={template.display_name} | |
| 265 | title={template.title} | |
| 266 | seed={template.handle} | |
| 267 | duties={template.responsibilities.slice(0, 3)} | |
| 268 | /> | |
| 269 | ))} | |
| 270 | </div> | |
| 271 | </section> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 272 | ))} |
| 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) | 273 | <section> |
| 274 | <h2 className="mb-3 text-xs font-semibold tracking-wide text-faint uppercase">Your own</h2> | |
| 275 | <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3"> | |
| 276 | <TemplateCard | |
| 277 | selected={chosen === "blank"} | |
| 278 | onClick={() => onChoose("blank")} | |
| 279 | name="A new role" | |
| 280 | title="Start from nothing" | |
| 281 | blank | |
| 282 | duties={["Give it a name, a title and a team", "Say what it is responsible for"]} | |
| 283 | /> | |
| 284 | </div> | |
| 285 | </section> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 286 | </div> |
| 287 | ); | |
| 288 | } | |
| 289 | ||
| 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) | 290 | /** A role to hire into: its face, a name it suggests, its title and what it does. */ |
| Chat and workspace agents: channels, DMs and named agents you talk to | 291 | function TemplateCard({ |
| 292 | selected, | |
| 293 | onClick, | |
| 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) | 294 | name, |
| Chat and workspace agents: channels, DMs and named agents you talk to | 295 | title, |
| 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) | 296 | seed, |
| 297 | duties, | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 298 | blank, |
| 299 | }: { | |
| 300 | selected: boolean; | |
| 301 | onClick: () => void; | |
| 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) | 302 | name: string; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 303 | title: string; |
| 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) | 304 | seed?: string; |
| 305 | duties: string[]; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 306 | blank?: boolean; |
| 307 | }) { | |
| 308 | return ( | |
| 309 | <button | |
| 310 | type="button" | |
| 311 | onClick={onClick} | |
| 312 | aria-pressed={selected} | |
| 313 | className={`group flex flex-col items-start rounded-xl border p-4 text-left transition-all ${ | |
| 314 | selected | |
| 315 | ? "border-accent/60 bg-accent/[0.07] ring-1 ring-accent/40" | |
| 316 | : "border-line bg-surface hover:-translate-y-px hover:border-line-strong hover:bg-raised/40" | |
| 317 | }`} | |
| 318 | > | |
| 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) | 319 | <span className="flex w-full items-center gap-3"> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 320 | {blank ? ( |
| 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) | 321 | <span className="flex size-10 items-center justify-center rounded-[11px] border border-dashed border-line-strong text-muted"> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 322 | <Plus size={16} /> |
| 323 | </span> | |
| 324 | ) : ( | |
| 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) | 325 | <PixelCreature seed={seed ?? name} size={40} /> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 326 | )} |
| 327 | <span className="min-w-0 grow"> | |
| 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) | 328 | <span className="block truncate text-sm font-semibold">{name}</span> |
| 329 | <span className="block truncate text-xs text-muted">{title}</span> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 330 | </span> |
| 331 | <ChevronRight size={15} className={`shrink-0 transition-colors ${selected ? "text-accent" : "text-faint group-hover:text-muted"}`} /> | |
| 332 | </span> | |
| 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) | 333 | <ul className="mt-3 space-y-1"> |
| 334 | {duties.map((duty) => ( | |
| 335 | <li key={duty} className="flex gap-2 text-[0.8125rem] leading-snug text-muted"> | |
| 336 | <span className="mt-[0.45rem] size-1 shrink-0 rounded-full bg-line-strong" aria-hidden="true" /> | |
| 337 | <span className="line-clamp-1">{duty}</span> | |
| 338 | </li> | |
| 339 | ))} | |
| 340 | </ul> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 341 | </button> |
| 342 | ); | |
| 343 | } | |
| 344 | ||
| 345 | const FIELD = "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"; | |
| 346 | ||
| 347 | function Label({ children, hint, error, htmlFor }: { children: ReactNode; hint?: ReactNode; error?: string; htmlFor?: string }) { | |
| 348 | return ( | |
| 349 | <div className="mb-1.5 flex items-baseline justify-between gap-3"> | |
| 350 | <label htmlFor={htmlFor} className="text-sm font-medium text-fg-soft"> | |
| 351 | {children} | |
| 352 | </label> | |
| 353 | {error ? <span className="text-xs text-danger">{error}</span> : hint ? <span className="text-xs text-faint">{hint}</span> : null} | |
| 354 | </div> | |
| 355 | ); | |
| 356 | } | |
| 357 | ||
| 358 | /** A section of the form: what it is on the left, its fields on the right, on a wide screen. */ | |
| 359 | function FormSection({ title, about, children }: { title: string; about: ReactNode; children: ReactNode }) { | |
| 360 | return ( | |
| 361 | <section className="grid gap-x-10 gap-y-4 border-t border-line py-8 first-of-type:border-t-0 first-of-type:pt-0 md:grid-cols-[13rem_1fr]"> | |
| 362 | <div> | |
| 363 | <h2 className="text-sm font-semibold">{title}</h2> | |
| 364 | <p className="mt-1 text-[0.8125rem] leading-relaxed text-muted">{about}</p> | |
| 365 | </div> | |
| 366 | <div className="min-w-0 space-y-5">{children}</div> | |
| 367 | </section> | |
| 368 | ); | |
| 369 | } | |
| 370 | ||
| 371 | function TierSelect({ name, value, none }: { name: string; value: ModelTier | null; none: string }) { | |
| 372 | return ( | |
| 373 | <Select name={name} defaultValue={value ?? "none"}> | |
| 374 | <SelectTrigger id={name}> | |
| 375 | <SelectValue /> | |
| 376 | </SelectTrigger> | |
| 377 | <SelectContent> | |
| 378 | <SelectItem value="none">{none}</SelectItem> | |
| 379 | {(["small", "large", "frontier"] as const).map((tier) => ( | |
| 380 | <SelectItem key={tier} value={tier} description={TIER_LABELS[tier].split(": ")[1]}> | |
| 381 | {TIER_LABELS[tier].split(":")[0]} | |
| 382 | </SelectItem> | |
| 383 | ))} | |
| 384 | </SelectContent> | |
| 385 | </Select> | |
| 386 | ); | |
| 387 | } | |
| 388 | ||
| 389 | function Money({ name, value, label, hint, error }: { name: string; value: number | null; label: string; hint: string; error?: string }) { | |
| 390 | return ( | |
| 391 | <div> | |
| 392 | <Label htmlFor={name} hint={hint} error={error}> | |
| 393 | {label} | |
| 394 | </Label> | |
| 395 | <div className="flex items-center rounded-md border border-line bg-bg transition-colors focus-within:border-accent-dim hover:border-line-strong"> | |
| 396 | <span className="pl-3 text-sm text-faint">$</span> | |
| 397 | <input | |
| 398 | id={name} | |
| 399 | name={name} | |
| 400 | inputMode="decimal" | |
| 401 | defaultValue={dollarsField(value)} | |
| 402 | placeholder="No cap" | |
| 403 | autoComplete="off" | |
| 404 | data-1p-ignore | |
| 405 | className="min-w-0 grow bg-transparent px-2 py-2 text-sm tabular-nums outline-none placeholder:text-faint" | |
| 406 | /> | |
| 407 | </div> | |
| 408 | </div> | |
| 409 | ); | |
| 410 | } | |
| 411 | ||
| 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) | 412 | /** Names to shuffle through when a template has none of its own. */ |
| 413 | const FALLBACK_NAMES = ["Margo", "Juniper", "Otto", "Wren", "Basil", "Nova", "Clementine", "Fig", "Rook", "Pip"]; | |
| 414 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 415 | /** |
| 416 | * Everything that defines an agent, for making one and for its Profile: | |
| 417 | * who it is, its job, its voice, the limits on routing, its budget, what it | |
| 418 | * may do alone, and how much it takes on at once. | |
| 419 | */ | |
| 420 | export function AgentForm({ | |
| 421 | draft, | |
| 422 | errors, | |
| 423 | submit, | |
| 424 | intent, | |
| 425 | formKey, | |
| 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) | 426 | nameIdeas = [], |
| 427 | locked = false, | |
| 428 | seed, | |
| 429 | teams = [], | |
| Agents recall what Docs say before they answer or work, and each has required reading | 430 | spaces = [], |
| Chat and workspace agents: channels, DMs and named agents you talk to | 431 | }: { |
| 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) | 432 | /** The workspace's teams, to put it on one. */ |
| 433 | teams?: { slug: string; name: string }[]; | |
| Agents recall what Docs say before they answer or work, and each has required reading | 434 | /** The Docs spaces the person editing can read, for its required reading. */ |
| 435 | spaces?: { id: string; name: string; kind: string }[]; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 436 | draft: AgentDraft; |
| 437 | errors?: Record<string, string>; | |
| 438 | submit: string; | |
| 439 | intent: string; | |
| 440 | formKey?: string; | |
| 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) | 441 | /** Names to shuffle through (a template's `name_ideas`); the dice offers them. */ |
| 442 | nameIdeas?: string[]; | |
| 443 | /** g1t, the orchestrator: its identity and job are fixed; the rest is editable. */ | |
| 444 | locked?: boolean; | |
| 445 | /** The agent's own `avatar_seed`, once it has one; a new agent's face follows its handle. */ | |
| 446 | seed?: string | null; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 447 | }) { |
| 448 | const navigation = useNavigation(); | |
| 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) | 449 | // The handle follows the name until someone edits it. |
| 450 | const [name, setName] = useState(draft.display_name); | |
| 451 | const [handle, setHandle] = useState(draft.handle); | |
| 452 | const [handleEdited, setHandleEdited] = useState(Boolean(draft.handle) && cleanHandle(draft.display_name) !== draft.handle); | |
| 453 | const ideas = nameIdeas.length > 0 ? nameIdeas : FALLBACK_NAMES; | |
| 454 | const [idea, setIdea] = useState(() => Math.max(0, ideas.indexOf(draft.display_name))); | |
| 455 | const rename = (next: string) => { | |
| 456 | setName(next); | |
| 457 | if (!handleEdited) setHandle(cleanHandle(next)); | |
| 458 | }; | |
| 459 | const shuffle = () => { | |
| 460 | const at = (idea + 1) % ideas.length; | |
| 461 | setIdea(at); | |
| 462 | rename(ideas[at]!); | |
| 463 | }; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 464 | const { pathname } = useLocation(); |
| 465 | const busy = navigation.state === "submitting" && navigation.formAction === pathname; | |
| 466 | const [preset, setPreset] = useState(draft.personality_preset); | |
| 467 | const [advanced, setAdvanced] = useState(Boolean(draft.routing.pinned)); | |
| 468 | const e = errors ?? {}; | |
| 469 | const presetAbout = PRESETS.find((p) => p.value === preset)?.about; | |
| 470 | return ( | |
| 471 | <Form method="post" key={formKey} className="pb-24"> | |
| 472 | <input type="hidden" name="intent" value={intent} /> | |
| 473 | <input type="hidden" name="template" value={draft.template ?? ""} /> | |
| 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) | 474 | <FormSection |
| 475 | title="Identity" | |
| 476 | about={ | |
| 477 | locked | |
| 478 | ? "g1t is the workspace's orchestrator: its name, handle and role are the same everywhere, and it can't be archived." | |
| 479 | : "How it shows in chat, mentions, assignees and the audit log. Give it a name people will enjoy saying." | |
| 480 | } | |
| 481 | > | |
| 482 | <div className="flex items-center gap-3"> | |
| 483 | <AgentAvatar agent={{ handle: handle || "agent", avatar_seed: seed ?? handle, builtin: locked }} size={44} /> | |
| 484 | <div className="min-w-0 text-sm"> | |
| 485 | <p className="truncate font-semibold">{name || "Your new agent"}</p> | |
| 486 | <p className="truncate text-xs text-muted">{handle ? `@${handle}` : "@handle"}</p> | |
| 487 | </div> | |
| 488 | </div> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 489 | <div className="grid gap-5 sm:grid-cols-2"> |
| 490 | <div> | |
| 491 | <Label htmlFor="display_name" error={e.display_name}> | |
| 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) | 492 | Name |
| Chat and workspace agents: channels, DMs and named agents you talk to | 493 | </Label> |
| 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) | 494 | <div className="flex gap-2"> |
| 495 | <input | |
| 496 | id="display_name" | |
| 497 | name="display_name" | |
| 498 | value={name} | |
| 499 | onChange={(event) => rename(event.target.value)} | |
| 500 | readOnly={locked} | |
| 501 | placeholder="Margo" | |
| 502 | className={`${FIELD} ${locked ? "cursor-not-allowed text-muted" : ""}`} | |
| 503 | autoComplete="off" | |
| 504 | data-1p-ignore | |
| 505 | /> | |
| 506 | {!locked && ( | |
| 507 | <Hint label="Another name"> | |
| 508 | <button | |
| 509 | type="button" | |
| 510 | aria-label="Another name" | |
| 511 | onClick={shuffle} | |
| 512 | className="flex size-[38px] shrink-0 items-center justify-center rounded-md border border-line text-muted transition-colors hover:border-line-strong hover:bg-raised hover:text-fg active:rotate-12" | |
| 513 | > | |
| 514 | <Dices size={17} /> | |
| 515 | </button> | |
| 516 | </Hint> | |
| 517 | )} | |
| 518 | </div> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 519 | </div> |
| 520 | <div> | |
| 521 | <Label htmlFor="handle" error={e.handle} hint="Mentioned as @handle"> | |
| 522 | Handle | |
| 523 | </Label> | |
| 524 | <div className="flex items-center rounded-md border border-line bg-bg transition-colors focus-within:border-accent-dim hover:border-line-strong"> | |
| 525 | <span className="pl-3 font-mono text-sm text-faint">@</span> | |
| 526 | <input | |
| 527 | id="handle" | |
| 528 | name="handle" | |
| 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) | 529 | value={handle} |
| 530 | onChange={(event) => { | |
| 531 | setHandle(event.target.value); | |
| 532 | setHandleEdited(true); | |
| 533 | }} | |
| 534 | readOnly={locked} | |
| 535 | placeholder="margo" | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 536 | autoComplete="off" |
| 537 | data-1p-ignore | |
| 538 | className="min-w-0 grow bg-transparent py-2 pr-3 pl-0.5 font-mono text-sm outline-none placeholder:text-faint" | |
| 539 | /> | |
| 540 | </div> | |
| 541 | </div> | |
| 542 | </div> | |
| 543 | </FormSection> | |
| 544 | ||
| 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) | 545 | {!locked && ( |
| 546 | <FormSection | |
| 547 | title="Role" | |
| 548 | about="Hired into a role, not a task: a title, a team, and what it is responsible for. Shown as its name and title everywhere." | |
| 549 | > | |
| 550 | <RoleFields | |
| 551 | title={draft.title} | |
| 552 | team={draft.team} | |
| 553 | department={draft.department} | |
| 554 | responsibilities={draft.responsibilities} | |
| 555 | teams={teams} | |
| 556 | errors={e} | |
| 557 | locked={locked} | |
| 558 | /> | |
| 559 | </FormSection> | |
| 560 | )} | |
| 561 | ||
| 562 | <FormSection | |
| 563 | title="Job" | |
| 564 | about={ | |
| 565 | locked | |
| 566 | ? "Its job is fixed: know the team, hand work to the right specialist, do it itself when nobody fits, and report. Add your own instructions on top." | |
| 567 | : "What it is responsible for, how it works, and what good looks like. It reads this before every reply and task." | |
| 568 | } | |
| 569 | > | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 570 | <div> |
| 571 | <Label htmlFor="instructions" error={e.instructions}> | |
| 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) | 572 | {locked ? "Extra instructions" : "Instructions"} |
| Chat and workspace agents: channels, DMs and named agents you talk to | 573 | </Label> |
| 574 | <textarea | |
| 575 | id="instructions" | |
| 576 | name="instructions" | |
| 577 | defaultValue={draft.instructions} | |
| 578 | rows={9} | |
| 579 | placeholder={"Cut a release of g1t every Tuesday.\n- Read the merged pull requests since the last tag.\n- Write the release notes.\n- Ask in #releases before tagging."} | |
| 580 | className={`${FIELD} resize-y font-mono text-[0.8125rem] leading-relaxed`} | |
| 581 | /> | |
| 582 | </div> | |
| 583 | </FormSection> | |
| 584 | ||
| Agents recall what Docs say before they answer or work, and each has required reading | 585 | <FormSection |
| 586 | title="Required reading" | |
| 587 | about="Docs it checks first, every time it answers or works. It also recalls whatever else in Docs fits the question, but only from spaces everyone in the conversation can read." | |
| 588 | > | |
| 589 | {spaces.length ? ( | |
| 590 | <div className="grid gap-1.5 sm:grid-cols-2"> | |
| 591 | {spaces.map((space) => ( | |
| 592 | <label | |
| 593 | key={space.id} | |
| 594 | className="flex min-h-10 cursor-pointer items-center gap-2.5 rounded-lg border border-line bg-surface px-3 py-2 text-sm has-[:checked]:border-accent/50 has-[:checked]:bg-accent/5" | |
| 595 | > | |
| 596 | <input type="checkbox" name="reading" value={space.id} defaultChecked={(draft.reading ?? []).includes(space.id)} className="size-4 accent-[var(--color-accent)]" /> | |
| 597 | <span className="min-w-0 grow truncate">{space.name}</span> | |
| 598 | <span className="shrink-0 text-xs text-faint capitalize">{space.kind}</span> | |
| 599 | </label> | |
| 600 | ))} | |
| 601 | </div> | |
| 602 | ) : ( | |
| 603 | <p className="text-sm text-muted">No Docs spaces yet. Once your workspace has some, choose what it should know by heart.</p> | |
| 604 | )} | |
| 605 | {(draft.reading ?? []).filter((id) => !spaces.some((space) => space.id === id)).map((id) => ( | |
| 606 | // Spaces it reads that you can't see stay as they are. | |
| 607 | <input key={id} type="hidden" name="reading" value={id} /> | |
| 608 | ))} | |
| 609 | </FormSection> | |
| 610 | ||
| 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) | 611 | {!locked && ( |
| 612 | <FormSection title="Subagents" about="Help it calls on for one kind of work inside its own tasks. Never members of the workspace, and never wider than the agent itself."> | |
| 613 | <SubagentsField initial={draft.subagents} routing={draft.routing} /> | |
| 614 | {e.subagents && <p className="text-sm text-danger">{e.subagents}</p>} | |
| 615 | </FormSection> | |
| 616 | )} | |
| 617 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 618 | <FormSection title="Personality" about="Its voice only: tone, length, how it asks. A personality never changes what it may do."> |
| 619 | <div> | |
| 620 | <input type="hidden" name="personality_preset" value={preset} /> | |
| 621 | <div role="radiogroup" aria-label="Voice" className="grid grid-cols-2 gap-1 rounded-lg bg-surface p-1 ring-1 ring-line sm:grid-cols-4"> | |
| 622 | {PRESETS.map((option) => ( | |
| 623 | <button | |
| 624 | key={option.value} | |
| 625 | type="button" | |
| 626 | role="radio" | |
| 627 | aria-checked={preset === option.value} | |
| 628 | onClick={() => setPreset(option.value)} | |
| 629 | className={`h-8 rounded-md text-[0.8125rem] font-medium transition-colors ${ | |
| 630 | preset === option.value ? "bg-raised text-fg shadow-sm ring-1 ring-line-strong" : "text-muted hover:text-fg" | |
| 631 | }`} | |
| 632 | > | |
| 633 | {option.label} | |
| 634 | </button> | |
| 635 | ))} | |
| 636 | </div> | |
| 637 | <p className="mt-2 text-xs text-muted">{presetAbout}</p> | |
| 638 | </div> | |
| 639 | <div> | |
| 640 | <Label htmlFor="personality" hint="Optional"> | |
| 641 | In its own words | |
| 642 | </Label> | |
| 643 | <textarea | |
| 644 | id="personality" | |
| 645 | name="personality" | |
| 646 | defaultValue={draft.personality} | |
| 647 | rows={3} | |
| 648 | placeholder="Dry humor is fine. No emoji. Asks one question at a time." | |
| 649 | className={`${FIELD} resize-y`} | |
| 650 | /> | |
| 651 | </div> | |
| 652 | </FormSection> | |
| 653 | ||
| 654 | <FormSection | |
| 655 | title="Models" | |
| 656 | about="Nobody picks a model to get work done. g1t routes each step to the tier it needs and records which ran. These only limit the routing." | |
| 657 | > | |
| 658 | <div className="flex items-start gap-3 rounded-xl border border-accent/25 bg-accent/[0.06] p-4"> | |
| 659 | <span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-accent/15 text-accent"> | |
| 660 | <RouteIcon size={16} /> | |
| 661 | </span> | |
| 662 | <div> | |
| 663 | <p className="text-sm font-semibold">Auto: routed per step by need</p> | |
| 664 | <p className="mt-0.5 text-[0.8125rem] leading-relaxed text-muted"> | |
| 665 | Replies and triage run on Fast; making changes on Standard; planning, hard reviews and retries after a failure on Most capable. It steps up after a failure and back down when the cheaper tier works. | |
| 666 | </p> | |
| 667 | </div> | |
| 668 | </div> | |
| 669 | <div className="grid gap-5 sm:grid-cols-3"> | |
| 670 | <div> | |
| 671 | <Label htmlFor="floor">Floor</Label> | |
| 672 | <TierSelect name="floor" value={draft.routing.floor} none="No floor" /> | |
| 673 | </div> | |
| 674 | <div> | |
| 675 | <Label htmlFor="ceiling" error={e.ceiling}> | |
| 676 | Ceiling | |
| 677 | </Label> | |
| 678 | <TierSelect name="ceiling" value={draft.routing.ceiling} none="No ceiling" /> | |
| 679 | </div> | |
| 680 | <div> | |
| 681 | <Label htmlFor="providers">Providers</Label> | |
| 682 | <Select name="providers" defaultValue={draft.routing.providers[0] ?? "any"}> | |
| 683 | <SelectTrigger id="providers"> | |
| 684 | <SelectValue /> | |
| 685 | </SelectTrigger> | |
| 686 | <SelectContent> | |
| 687 | <SelectItem value="any" description="g1t's hosted models and the workspace's own"> | |
| 688 | Any the workspace allows | |
| 689 | </SelectItem> | |
| 690 | <SelectItem value="g1t" description="Never the workspace's own keys"> | |
| 691 | g1t's hosted models | |
| 692 | </SelectItem> | |
| 693 | <SelectItem value="workspace" description="Set up under Integrations; billed by the provider"> | |
| 694 | The workspace's own providers | |
| 695 | </SelectItem> | |
| 696 | </SelectContent> | |
| 697 | </Select> | |
| 698 | </div> | |
| 699 | </div> | |
| 700 | <div className="rounded-lg border border-line"> | |
| 701 | <button | |
| 702 | type="button" | |
| 703 | onClick={() => setAdvanced(!advanced)} | |
| 704 | aria-expanded={advanced} | |
| 705 | className="flex w-full items-center gap-2 px-3.5 py-2.5 text-left text-[0.8125rem] font-medium text-muted hover:text-fg" | |
| 706 | > | |
| 707 | <ChevronRight size={14} className={`transition-transform ${advanced ? "rotate-90" : ""}`} /> | |
| 708 | Advanced | |
| 709 | <span className="font-normal text-faint">Pin one model</span> | |
| 710 | </button> | |
| 711 | <div hidden={!advanced} className="border-t border-line px-3.5 py-3.5"> | |
| 712 | <Label htmlFor="pinned" error={e.pinned} hint="For your own endpoints. Turns routing off."> | |
| 713 | Pinned model | |
| 714 | </Label> | |
| 715 | <input id="pinned" name="pinned" defaultValue={draft.routing.pinned ?? ""} placeholder="provider/model" className={`${FIELD} font-mono`} autoComplete="off" data-1p-ignore /> | |
| 716 | </div> | |
| 717 | </div> | |
| 718 | </FormSection> | |
| 719 | ||
| 720 | <FormSection title="Budget" about="Checked before work starts and enforced while it runs. At 80% of the month it tells you; at 100% it takes no new tasks."> | |
| 721 | <div className="grid gap-5 sm:grid-cols-3"> | |
| 722 | <Money name="monthly" label="Monthly" hint="Per calendar month" value={draft.budget.monthly_micros} error={e.monthly} /> | |
| 723 | <Money name="daily" label="Daily" hint="Optional" value={draft.budget.daily_micros} error={e.daily} /> | |
| Agents mode: an overview of every agent's work and spend, and each agent's sessions, memory, routines, spend, activity and versions | 724 | <Money name="task" label="Per session" hint="Going over asks an owner" value={draft.budget.task_micros} error={e.task} /> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 725 | </div> |
| 726 | </FormSection> | |
| 727 | ||
| 728 | <FormSection title="Autonomy" about="What it may do alone and what needs a person. Rulesets and branch protection still apply on top."> | |
| 729 | <div className="grid gap-5 sm:grid-cols-2"> | |
| 730 | {(Object.keys(AUTONOMY) as (keyof typeof AUTONOMY)[]).map((key) => ( | |
| 731 | <div key={key}> | |
| 732 | <Label htmlFor={key}>{AUTONOMY[key].label}</Label> | |
| 733 | <Select name={key} defaultValue={draft.autonomy[key]}> | |
| 734 | <SelectTrigger id={key}> | |
| 735 | <SelectValue /> | |
| 736 | </SelectTrigger> | |
| 737 | <SelectContent> | |
| 738 | {AUTONOMY[key].options.map(([value, label]) => ( | |
| 739 | <SelectItem key={value} value={value}> | |
| 740 | {label} | |
| 741 | </SelectItem> | |
| 742 | ))} | |
| 743 | </SelectContent> | |
| 744 | </Select> | |
| 745 | </div> | |
| 746 | ))} | |
| 747 | </div> | |
| 748 | </FormSection> | |
| 749 | ||
| Agents mode: an overview of every agent's work and spend, and each agent's sessions, memory, routines, spend, activity and versions | 750 | <FormSection title="Capacity" about="How many sessions it works on at once. More wait in line, queued."> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 751 | <div className="max-w-40"> |
| 752 | <Label htmlFor="capacity" error={e.capacity}> | |
| Agents mode: an overview of every agent's work and spend, and each agent's sessions, memory, routines, spend, activity and versions | 753 | Sessions at once |
| Chat and workspace agents: channels, DMs and named agents you talk to | 754 | </Label> |
| 755 | <input id="capacity" name="capacity" type="number" min={1} max={20} defaultValue={draft.capacity} className={`${FIELD} tabular-nums`} /> | |
| 756 | </div> | |
| 757 | </FormSection> | |
| 758 | ||
| Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar | 759 | <div className="sticky bottom-(--tabbar-h) in-data-[keyboard=open]:bottom-0 z-10 -mx-4 flex items-center justify-end gap-3 border-t border-line bg-bg/90 px-4 py-3 backdrop-blur sm:-mx-8 sm:px-8"> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 760 | {Object.keys(e).length > 0 && <p className="mr-auto text-sm text-danger">Check the fields marked above.</p>} |
| 761 | <button | |
| 762 | type="submit" | |
| 763 | disabled={busy} | |
| 764 | className="inline-flex h-9 items-center gap-2 rounded-md bg-accent px-4 text-sm font-medium text-bg transition-colors hover:bg-accent-hover disabled:opacity-60" | |
| 765 | > | |
| 766 | <Sparkles size={15} /> | |
| 767 | {busy ? "Saving…" : submit} | |
| 768 | </button> | |
| 769 | </div> | |
| 770 | </Form> | |
| 771 | ); | |
| 772 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.