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.
| Chat and workspace agents: channels, DMs and named agents you talk to | 1 | /** |
| 2 | * The agent form (components/agents-mode.tsx) as the agents service takes | |
| 3 | * it: what each field means, and the checks made before it is sent. Pure, | |
| 4 | * so it is tested on its own. | |
| 5 | */ | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 6 | import type { AgentEffort, AgentLook, ModelTier, NewWorkspaceAgent, PersonalityPreset, SubagentDef, WorkspaceAgent } from "@g1t/contracts"; |
| 7 | import { readLook } from "@g1t/contracts/agent-look"; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 8 | |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 9 | import { plainDollars } from "./money.ts"; |
| 10 | ||
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 11 | /** An effort setting from a form: one of the five, or null. */ |
| 12 | export function readEffort(value: FormDataEntryValue | null): AgentEffort | null { | |
| 13 | const text = String(value ?? ""); | |
| 14 | return text === "auto" || text === "low" || text === "medium" || text === "high" || text === "max" ? text : null; | |
| 15 | } | |
| 16 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 17 | /** The tiers, cheapest first: `MODEL_TIERS` in the contracts. */ |
| 18 | const MODEL_TIERS: ModelTier[] = ["small", "large", "frontier"]; | |
| 19 | ||
| 20 | export const PRESETS: { value: PersonalityPreset; label: string; about: string }[] = [ | |
| 21 | { value: "crisp", label: "Crisp", about: "Clear and to the point, with enough context to act on." }, | |
| 22 | { value: "friendly", label: "Friendly", about: "Warm and encouraging; explains as it goes." }, | |
| 23 | { value: "socratic", label: "Socratic", about: "Asks before it assumes; draws out what you mean." }, | |
| 24 | { value: "terse", label: "Terse operator", about: "As few words as will do. Status, then done." }, | |
| 25 | ]; | |
| 26 | ||
| 27 | export const TIER_LABELS: Record<ModelTier, string> = { | |
| 28 | small: "Fast: replies and triage", | |
| 29 | large: "Standard: making changes, most reviews", | |
| 30 | frontier: "Most capable: planning and hard work", | |
| 31 | }; | |
| 32 | ||
| 33 | export const AUTONOMY = { | |
| 34 | open_pull_requests: { label: "Open pull requests", options: [["alone", "On its own"], ["approval", "After approval"]] }, | |
| 35 | merge: { label: "Merge", options: [["alone", "On its own"], ["approval", "After approval"], ["never", "Never"]] }, | |
| 36 | deploy_production: { label: "Deploy to production", options: [["approval", "After approval"], ["never", "Never"]] }, | |
| 37 | edit_docs: { label: "Edit docs", options: [["alone", "Directly"], ["suggest", "As suggestions"]] }, | |
| 38 | } as const; | |
| 39 | ||
| 40 | /** The handle as people type it: lowercase letters, digits and dashes. */ | |
| 41 | export function cleanHandle(typed: string): string { | |
| 42 | return typed | |
| 43 | .trim() | |
| 44 | .toLowerCase() | |
| 45 | .replace(/^@/, "") | |
| 46 | .replace(/[\s_]+/g, "-") | |
| 47 | .replace(/[^a-z0-9-]/g, "") | |
| 48 | .replace(/-{2,}/g, "-") | |
| 49 | .replace(/^-|-$/g, "") | |
| 50 | .slice(0, 39); | |
| 51 | } | |
| 52 | ||
| 53 | /** Handles the site's own addresses use, and g1t's: never an agent's. */ | |
| 54 | const RESERVED_HANDLES = new Set(["g1t", "new", "runs", "fleet"]); | |
| 55 | ||
| 56 | function tier(value: FormDataEntryValue | null): ModelTier | null { | |
| 57 | const text = String(value ?? ""); | |
| 58 | return (MODEL_TIERS as string[]).includes(text) ? (text as ModelTier) : null; | |
| 59 | } | |
| 60 | ||
| 61 | function pick<T extends string>(value: FormDataEntryValue | null, allowed: readonly (readonly [T, string])[], fallback: T): T { | |
| 62 | const text = String(value ?? ""); | |
| 63 | return (allowed.find(([key]) => key === text)?.[0] ?? fallback) as T; | |
| 64 | } | |
| 65 | ||
| 66 | export type AgentFormResult = { ok: true; input: NewWorkspaceAgent } | { ok: false; errors: Record<string, string> }; | |
| 67 | ||
| 68 | /** The form, checked: the agent to make or the changes to save, or what to fix, by field. */ | |
| 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) | 69 | export function readAgentForm(form: FormData, options: { orchestrator?: boolean } = {}): AgentFormResult { |
| Chat and workspace agents: channels, DMs and named agents you talk to | 70 | const errors: Record<string, string> = {}; |
| 71 | const display_name = String(form.get("display_name") ?? "").trim(); | |
| 72 | const handle = cleanHandle(String(form.get("handle") ?? "") || 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) | 73 | const title = String(form.get("title") ?? "").trim(); |
| 74 | const responsibilities = form | |
| 75 | .getAll("responsibility") | |
| 76 | .map((value) => String(value).trim()) | |
| 77 | .filter(Boolean) | |
| 78 | .slice(0, MAX_RESPONSIBILITIES); | |
| 79 | const subagents = readSubagents(form.get("subagents")); | |
| Agents recall what Docs say before they answer or work, and each has required reading | 80 | // Docs spaces it reads first; the agents service checks them. |
| 81 | const reading = [...new Set(form.getAll("reading").map((value) => String(value).trim()).filter(Boolean))].slice(0, 10); | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 82 | const instructions = String(form.get("instructions") ?? "").trim(); |
| 83 | if (!display_name) errors.display_name = "Give it a name."; | |
| 84 | if (!handle) errors.handle = "Give it a handle, like @ship."; | |
| 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 | else if (RESERVED_HANDLES.has(handle) && !(options.orchestrator && handle === "g1t")) errors.handle = `@${handle} is taken by g1t.`; |
| 86 | if (!title && !options.orchestrator) errors.title = "Give it a title, like QA Engineer."; | |
| 87 | if (subagents == null) errors.subagents = "The subagents couldn't be read. Edit one and save again."; | |
| 88 | // g1t's job is fixed; what is written here is added to it, and may be nothing. | |
| 89 | if (!instructions && !options.orchestrator) errors.instructions = "Tell it what it's responsible for."; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 90 | const floor = tier(form.get("floor")); |
| 91 | const ceiling = tier(form.get("ceiling")); | |
| 92 | if (floor && ceiling && MODEL_TIERS.indexOf(floor) > MODEL_TIERS.indexOf(ceiling)) errors.ceiling = "The ceiling can't be below the floor."; | |
| 93 | const budget = { | |
| 94 | monthly_micros: microsFromDollars(form.get("monthly")), | |
| 95 | daily_micros: microsFromDollars(form.get("daily")), | |
| 96 | task_micros: microsFromDollars(form.get("task")), | |
| 97 | }; | |
| 98 | for (const [field, value] of [["monthly", budget.monthly_micros], ["daily", budget.daily_micros], ["task", budget.task_micros]] as const) { | |
| 99 | if (value != null && Number.isNaN(value)) errors[field] = "A sum in dollars, like 25."; | |
| 100 | } | |
| 101 | const capacity = Number(form.get("capacity") ?? 3); | |
| 102 | if (!Number.isInteger(capacity) || capacity < 1 || capacity > 20) errors.capacity = "Between 1 and 20."; | |
| 103 | const providers = String(form.get("providers") ?? "any"); | |
| 104 | const pinned = String(form.get("pinned") ?? "").trim(); | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 105 | // How hard it works; a form without the control leaves it as it is. |
| 106 | const effort = readEffort(form.get("effort")); | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 107 | if (pinned && !/^[\w.-]+\/[\w.:@-]+$/.test(pinned)) errors.pinned = "As provider/model."; |
| 108 | if (Object.keys(errors).length > 0) return { ok: false, errors }; | |
| 109 | return { | |
| 110 | ok: true, | |
| 111 | input: { | |
| 112 | handle, | |
| 113 | display_name, | |
| 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. | 114 | // Its title, made by the agents service. |
| 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) | 115 | role: "", |
| 116 | title, | |
| 117 | responsibilities, | |
| 118 | subagents: subagents ?? [], | |
| Agents recall what Docs say before they answer or work, and each has required reading | 119 | reading, |
| Chat and workspace agents: channels, DMs and named agents you talk to | 120 | instructions, |
| 121 | personality_preset: pick(form.get("personality_preset"), PRESETS.map((p) => [p.value, p.label] as const), "crisp"), | |
| 122 | personality: String(form.get("personality") ?? "").trim(), | |
| 123 | routing: { | |
| 124 | floor, | |
| 125 | ceiling, | |
| 126 | providers: providers === "any" ? [] : [providers], | |
| 127 | pinned: pinned || null, | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 128 | ...(effort ? { effort } : {}), |
| Chat and workspace agents: channels, DMs and named agents you talk to | 129 | }, |
| 130 | budget, | |
| 131 | autonomy: { | |
| 132 | open_pull_requests: pick(form.get("open_pull_requests"), AUTONOMY.open_pull_requests.options, "alone"), | |
| 133 | merge: pick(form.get("merge"), AUTONOMY.merge.options, "approval"), | |
| 134 | deploy_production: pick(form.get("deploy_production"), AUTONOMY.deploy_production.options, "approval"), | |
| 135 | edit_docs: pick(form.get("edit_docs"), AUTONOMY.edit_docs.options, "suggest"), | |
| 136 | }, | |
| 137 | capacity, | |
| 138 | template: String(form.get("template") ?? "") || null, | |
| 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. | 139 | // A drafted agent carries its scope, the skills it keeps off and its face. |
| 140 | ...(form.get("scope") === "personal" || form.get("scope") === "workspace" ? { scope: form.get("scope") as "personal" | "workspace" } : {}), | |
| 141 | ...(form.has("skills_off") ? { skills_off: String(form.get("skills_off") ?? "").split(",").map((id) => id.trim()).filter(Boolean) } : {}), | |
| 142 | ...(String(form.get("avatar_seed") ?? "").trim() ? { avatar_seed: String(form.get("avatar_seed")).trim().slice(0, 64) } : {}), | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 143 | // Its face: a chosen look, or `null` for the seed's; a form without the field leaves it as it is. |
| 144 | ...readLookField(form.get("look")), | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 145 | }, |
| 146 | }; | |
| 147 | } | |
| 148 | ||
| 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) | 149 | /** Most responsibilities an agent lists. */ |
| 150 | export const MAX_RESPONSIBILITIES = 8; | |
| 151 | ||
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 152 | /** |
| 153 | * The Face section's field: `null` (the word) gives the agent back its | |
| 154 | * seed's face, a look as JSON sets one, and nothing or something that is | |
| 155 | * not a look changes nothing. | |
| 156 | */ | |
| 157 | export function readLookField(value: FormDataEntryValue | null): { look?: AgentLook | null } { | |
| 158 | const text = String(value ?? "").trim(); | |
| 159 | if (!text) return {}; | |
| 160 | if (text === "null") return { look: null }; | |
| 161 | const look = readLook(text); | |
| 162 | return look ? { look } : {}; | |
| 163 | } | |
| 164 | ||
| 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) | 165 | /** The subagents, as the form sends them (JSON); null when they are not valid. */ |
| 166 | function readSubagents(value: FormDataEntryValue | null): SubagentDef[] | null { | |
| 167 | const text = String(value ?? "").trim(); | |
| 168 | if (!text) return []; | |
| 169 | try { | |
| 170 | const parsed = JSON.parse(text) as unknown; | |
| 171 | if (!Array.isArray(parsed)) return null; | |
| 172 | return parsed.filter( | |
| 173 | (item): item is SubagentDef => | |
| 174 | item != null && typeof item === "object" && typeof (item as SubagentDef).name === "string" && typeof (item as SubagentDef).description === "string", | |
| 175 | ); | |
| 176 | } catch { | |
| 177 | return null; | |
| 178 | } | |
| 179 | } | |
| 180 | ||
| 181 | /** A subagent's name: lowercase letters, digits and hyphens. */ | |
| 182 | export function cleanSubagentName(typed: string): string { | |
| 183 | return cleanHandle(typed).slice(0, 40); | |
| 184 | } | |
| 185 | ||
| 186 | /** | |
| 187 | * A subagent's routing limits, kept within its agent's: a floor below the | |
| 188 | * agent's is raised to it, a ceiling above the agent's is lowered to it, | |
| 189 | * and a floor above the ceiling comes down to it. | |
| 190 | */ | |
| 191 | export function clampRouting( | |
| 192 | sub: { floor: ModelTier | null; ceiling: ModelTier | null }, | |
| 193 | agent: { floor: ModelTier | null; ceiling: ModelTier | null }, | |
| 194 | ): { floor: ModelTier | null; ceiling: ModelTier | null } { | |
| 195 | const rank = (tier: ModelTier) => MODEL_TIERS.indexOf(tier); | |
| 196 | let floor = sub.floor; | |
| 197 | let ceiling = sub.ceiling; | |
| 198 | if (agent.floor && (!floor || rank(floor) < rank(agent.floor))) floor = agent.floor; | |
| 199 | if (agent.ceiling && (!ceiling || rank(ceiling) > rank(agent.ceiling))) ceiling = agent.ceiling; | |
| 200 | if (floor && ceiling && rank(floor) > rank(ceiling)) floor = ceiling; | |
| 201 | return { floor, ceiling }; | |
| 202 | } | |
| 203 | ||
| 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. | 204 | /** |
| 205 | * The teams a new agent joins as it is made (the form's "Add to teams"), | |
| 206 | * by slug: membership is the team's, never part of the agent. | |
| 207 | */ | |
| 208 | export function readTeams(form: FormData): string[] { | |
| 209 | return [...new Set(form.getAll("teams").map((value) => String(value).trim().toLowerCase()).filter((slug) => /^[a-z0-9][a-z0-9-]{0,63}$/.test(slug)))].slice(0, 20); | |
| 210 | } | |
| 211 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 212 | /** What the form starts from: an agent being edited, a template, or nothing. */ |
| 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) | 213 | export type AgentDraft = Pick< |
| 214 | WorkspaceAgent, | |
| 215 | | "handle" | |
| 216 | | "display_name" | |
| 217 | | "role" | |
| 218 | | "title" | |
| 219 | | "responsibilities" | |
| 220 | | "subagents" | |
| Agents recall what Docs say before they answer or work, and each has required reading | 221 | | "reading" |
| 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) | 222 | | "instructions" |
| 223 | | "personality_preset" | |
| 224 | | "personality" | |
| 225 | | "routing" | |
| 226 | | "budget" | |
| 227 | | "autonomy" | |
| 228 | | "capacity" | |
| 229 | | "template" | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 230 | | "look" |
| 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) | 231 | >; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 232 | |
| 233 | export const BLANK_DRAFT: AgentDraft = { | |
| 234 | handle: "", | |
| 235 | display_name: "", | |
| 236 | role: "", | |
| 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) | 237 | title: "", |
| 238 | responsibilities: [], | |
| 239 | subagents: [], | |
| Agents recall what Docs say before they answer or work, and each has required reading | 240 | reading: [], |
| Chat and workspace agents: channels, DMs and named agents you talk to | 241 | instructions: "", |
| 242 | personality_preset: "crisp", | |
| 243 | personality: "", | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 244 | routing: { floor: null, ceiling: null, providers: [], pinned: null, effort: "auto" }, |
| Chat and workspace agents: channels, DMs and named agents you talk to | 245 | budget: { monthly_micros: 50_000_000, daily_micros: null, task_micros: 5_000_000 }, |
| 246 | autonomy: { open_pull_requests: "alone", merge: "approval", deploy_production: "approval", edit_docs: "suggest" }, | |
| 247 | capacity: 3, | |
| 248 | template: null, | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 249 | look: null, |
| Chat and workspace agents: channels, DMs and named agents you talk to | 250 | }; |
| 251 | ||
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 252 | /** Dollars from micro-dollars, for a form field: empty for none, `20` for a whole sum, else to the cent. */ |
| Chat and workspace agents: channels, DMs and named agents you talk to | 253 | export function dollarsField(micros: number | null | undefined): string { |
| 254 | if (micros == null) return ""; | |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 255 | return plainDollars(micros, { whole: true }); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 256 | } |
| 257 | ||
| 258 | /** Micro-dollars from a form field in dollars: null when empty, NaN when not a sum. */ | |
| 259 | export function microsFromDollars(value: FormDataEntryValue | null): number | null { | |
| 260 | const text = String(value ?? "").trim().replace(/^\$/, "").replaceAll(",", ""); | |
| 261 | if (!text) return null; | |
| 262 | const dollars = Number(text); | |
| 263 | if (!Number.isFinite(dollars) || dollars < 0) return Number.NaN; | |
| 264 | return Math.round(dollars * 1_000_000); | |
| 265 | } | |
| 266 |
This file's history is long; its oldest lines are credited to the oldest commit read.