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 | * Agents as stored in D1, and their spend. Shared by the RPC methods and | |
| 3 | * the desk. | |
| 4 | */ | |
| 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) | 5 | import type { SubagentDef, WorkspaceAgent } from "@g1t/contracts"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 6 | |
| 7 | import { agentStatus, budgetBlock, dayKey, monthKey } from "./budget.ts"; | |
| 8 | import { type Definition, DEFAULT_AUTONOMY, DEFAULT_BUDGET, DEFAULT_ROUTING, PRESETS, readJson } from "./definition.ts"; | |
| 9 | ||
| 10 | export type Row = { | |
| 11 | id: string; | |
| 12 | workspace_id: string; | |
| 13 | handle: string; | |
| 14 | display_name: string; | |
| 15 | avatar: string | null; | |
| 16 | role: string; | |
| 17 | instructions: string; | |
| 18 | personality_preset: string; | |
| 19 | personality: string; | |
| 20 | routing: string; | |
| 21 | budget: string; | |
| 22 | autonomy: string; | |
| 23 | capacity: number; | |
| 24 | template: 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) | 25 | avatar_seed: string | null; |
| 26 | title: string | null; | |
| 27 | team: string | null; | |
| 28 | department: string | null; | |
| 29 | responsibilities: string | null; | |
| 30 | subagents: string | null; | |
| 31 | faces: string | null; | |
| Agents recall what Docs say before they answer or work, and each has required reading | 32 | reading?: string | null; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 33 | version: number; |
| 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) | 34 | /** 1 for the workspace's built-in @g1t. */ |
| 35 | builtin: number; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 36 | busy_until: string | null; |
| 37 | created_by: string; | |
| 38 | created_at: string; | |
| 39 | updated_at: string; | |
| 40 | archived_at: string | null; | |
| 41 | /** Joined from agent_spend: this month and today. */ | |
| 42 | spent_month?: number | null; | |
| 43 | spent_day?: number | null; | |
| 44 | }; | |
| 45 | ||
| 46 | /** | |
| 47 | * Agents with this month's and today's spend: `where` filters `agents` | |
| 48 | * (aliased `a`); its parameters follow the two periods. | |
| 49 | */ | |
| 50 | export function selectAgents(where: string): string { | |
| 51 | return `SELECT a.*, m.micros AS spent_month, d.micros AS spent_day FROM agents a | |
| 52 | LEFT JOIN agent_spend m ON m.agent_id = a.id AND m.period = ?1 | |
| 53 | LEFT JOIN agent_spend d ON d.agent_id = a.id AND d.period = ?2 | |
| 54 | WHERE ${where}`; | |
| 55 | } | |
| 56 | ||
| 57 | export function periods(now: Date): [string, string] { | |
| 58 | return [monthKey(now), dayKey(now)]; | |
| 59 | } | |
| 60 | ||
| 61 | export function definitionOf(row: Row): Definition { | |
| 62 | return { | |
| 63 | handle: row.handle, | |
| 64 | display_name: row.display_name, | |
| 65 | role: row.role, | |
| 66 | instructions: row.instructions, | |
| 67 | personality_preset: PRESETS.includes(row.personality_preset as Definition["personality_preset"]) | |
| 68 | ? (row.personality_preset as Definition["personality_preset"]) | |
| 69 | : "crisp", | |
| 70 | personality: row.personality, | |
| 71 | routing: readJson(row.routing, DEFAULT_ROUTING), | |
| 72 | budget: readJson(row.budget, DEFAULT_BUDGET), | |
| 73 | autonomy: readJson(row.autonomy, DEFAULT_AUTONOMY), | |
| 74 | capacity: row.capacity, | |
| 75 | template: row.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) | 76 | avatar_seed: row.avatar_seed || row.handle, |
| 77 | title: row.title ?? "", | |
| 78 | team: row.team ?? null, | |
| 79 | department: row.department ?? "", | |
| 80 | responsibilities: readList<string>(row.responsibilities), | |
| 81 | subagents: readList<SubagentDef>(row.subagents), | |
| 82 | faces: "internal", | |
| Agents recall what Docs say before they answer or work, and each has required reading | 83 | reading: readList<string>(row.reading ?? null), |
| Chat and workspace agents: channels, DMs and named agents you talk to | 84 | }; |
| 85 | } | |
| 86 | ||
| 87 | export function toAgent(row: Row, now: Date): WorkspaceAgent { | |
| 88 | const definition = definitionOf(row); | |
| 89 | const spent = { month: row.spent_month ?? 0, day: row.spent_day ?? 0 }; | |
| 90 | return { | |
| 91 | id: row.id, | |
| 92 | workspace_id: row.workspace_id, | |
| 93 | avatar: row.avatar, | |
| 94 | ...definition, | |
| 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) | 95 | builtin: !!row.builtin, |
| Chat and workspace agents: channels, DMs and named agents you talk to | 96 | version: row.version, |
| 97 | status: row.archived_at ? "paused" : agentStatus({ busyUntil: row.busy_until, now, blocked: budgetBlock(definition.budget, spent, now) !== null }), | |
| 98 | spent_month_micros: spent.month, | |
| 99 | created_by: row.created_by, | |
| 100 | created_at: row.created_at, | |
| 101 | updated_at: row.updated_at, | |
| 102 | archived_at: row.archived_at, | |
| 103 | }; | |
| 104 | } | |
| 105 | ||
| Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked | 106 | /** |
| 107 | * Adds a charge to the paying agent's month and day: a reply's, or a | |
| 108 | * session step's (`task`), counted as one of each. | |
| 109 | */ | |
| 110 | export function spendStatements(db: D1Database, agentId: string, micros: number, now: Date, task: "reply" | "session" = "reply"): D1PreparedStatement[] { | |
| 111 | const [replies, sessions] = task === "reply" ? [1, 0] : [0, 1]; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 112 | return periods(now).map((period) => |
| 113 | db | |
| 114 | .prepare( | |
| Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked | 115 | `INSERT INTO agent_spend (agent_id, period, micros, replies, sessions) VALUES (?1, ?2, ?3, ?4, ?5) |
| 116 | ON CONFLICT (agent_id, period) DO UPDATE SET micros = micros + ?3, replies = replies + ?4, sessions = sessions + ?5`, | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 117 | ) |
| Agents work in sessions: bounded, visible, steerable work spun off from chat, with subagents and colleagues in a tree paid by its root; memory with sources and scopes; routines; a workspace budget for every agent; agents file issues for whoever asked | 118 | .bind(agentId, period, Math.max(0, Math.ceil(micros)), replies, sessions), |
| Chat and workspace agents: channels, DMs and named agents you talk to | 119 | ); |
| 120 | } | |
| 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) | 121 | |
| 122 | /** A stored JSON list, read defensively: anything else is empty. */ | |
| 123 | function readList<T>(raw: string | null): T[] { | |
| 124 | if (!raw) return []; | |
| 125 | try { | |
| 126 | const parsed = JSON.parse(raw) as unknown; | |
| 127 | return Array.isArray(parsed) ? (parsed as T[]) : []; | |
| 128 | } catch { | |
| 129 | return []; | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | /** | |
| 134 | * The agents table's definition columns, in the order `definitionColumns` | |
| 135 | * gives their values. Statements are built from this one list, so a new | |
| 136 | * field is added in one place. | |
| 137 | */ | |
| 138 | export const DEFINITION_COLUMNS = [ | |
| 139 | "handle", | |
| 140 | "display_name", | |
| 141 | "role", | |
| 142 | "instructions", | |
| 143 | "personality_preset", | |
| 144 | "personality", | |
| 145 | "routing", | |
| 146 | "budget", | |
| 147 | "autonomy", | |
| 148 | "capacity", | |
| 149 | "template", | |
| 150 | "avatar_seed", | |
| 151 | "title", | |
| 152 | "team", | |
| 153 | "department", | |
| 154 | "responsibilities", | |
| 155 | "subagents", | |
| 156 | "faces", | |
| Agents recall what Docs say before they answer or work, and each has required reading | 157 | "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) | 158 | ] as const; |
| 159 | ||
| 160 | /** A definition's values, in `DEFINITION_COLUMNS` order. */ | |
| 161 | export function definitionColumns(d: Definition): (string | number | null)[] { | |
| 162 | return [ | |
| 163 | d.handle, | |
| 164 | d.display_name, | |
| 165 | d.role, | |
| 166 | d.instructions, | |
| 167 | d.personality_preset, | |
| 168 | d.personality, | |
| 169 | JSON.stringify(d.routing), | |
| 170 | JSON.stringify(d.budget), | |
| 171 | JSON.stringify(d.autonomy), | |
| 172 | d.capacity, | |
| 173 | d.template, | |
| 174 | d.avatar_seed, | |
| 175 | d.title, | |
| 176 | d.team, | |
| 177 | d.department, | |
| 178 | JSON.stringify(d.responsibilities), | |
| 179 | JSON.stringify(d.subagents), | |
| 180 | d.faces, | |
| Agents recall what Docs say before they answer or work, and each has required reading | 181 | JSON.stringify(d.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) | 182 | ]; |
| 183 | } | |
| 184 | ||
| 185 | /** | |
| 186 | * A new agent row: `id`, `workspace_id`, the definition, then `extra` | |
| 187 | * columns (version, builtin, who made it, when), all bound in order. | |
| 188 | */ | |
| 189 | export function insertAgent(db: D1Database, id: string, workspaceId: string, d: Definition, extra: Record<string, string | number>, orIgnore = false): D1PreparedStatement { | |
| 190 | const names = ["id", "workspace_id", ...DEFINITION_COLUMNS, ...Object.keys(extra)]; | |
| 191 | const values = [id, workspaceId, ...definitionColumns(d), ...Object.values(extra)]; | |
| 192 | return db | |
| 193 | .prepare(`INSERT ${orIgnore ? "OR IGNORE " : ""}INTO agents (${names.join(", ")}) VALUES (${names.map(() => "?").join(", ")})`) | |
| 194 | .bind(...values); | |
| 195 | } | |
| 196 | ||
| 197 | /** Sets a row's definition, from the version read only, with `extra` columns. */ | |
| 198 | export function updateAgent(db: D1Database, id: string, readVersion: number, d: Definition, extra: Record<string, string | number>): D1PreparedStatement { | |
| 199 | const names = [...DEFINITION_COLUMNS, ...Object.keys(extra)]; | |
| 200 | return db | |
| 201 | .prepare(`UPDATE agents SET ${names.map((name) => `${name} = ?`).join(", ")} WHERE id = ? AND version = ?`) | |
| 202 | .bind(...definitionColumns(d), ...Object.values(extra), id, readVersion); | |
| 203 | } | |
| 204 | ||
| 205 | /** A version of a definition, as saved. */ | |
| 206 | export function versionStatement(db: D1Database, agentId: string, version: number, d: Definition, by: string, at: string): D1PreparedStatement { | |
| 207 | return db | |
| 208 | .prepare("INSERT INTO agent_versions (agent_id, version, definition, changed_by, created_at) VALUES (?, ?, ?, ?, ?)") | |
| 209 | .bind(agentId, version, JSON.stringify(d), by, at); | |
| 210 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.