Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
623 linesCodeBlameRaw

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 to1/**
2 * Chat: channels, direct messages, threads and messages, kept by the chat
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.3 * service (`services/chat`). Members are people; messages from the agents
4 * workspaces once had stay readable under the names they had.
Chat and workspace agents: channels, DMs and named agents you talk to5 *
6 * Wire shapes are snake_case end to end, so the site, the public API and
7 * the live socket all carry the same objects.
8 */
9import type { ServiceBinding } from "./clients";
10import type { User } from "./identity";
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.11import type { AskPage } from "./integrations";
Chat and workspace agents: channels, DMs and named agents you talk to12import type { Result } from "./result";
13
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.14/**
15 * Who is speaking: a person (by user id), g1t itself (`system:g1t`, the one
16 * reserved actor all of g1t's own work shows as), or, on older messages, a
17 * former workspace agent (by agent id).
18 */
19export type Principal = { kind: "user" | "agent" | "system"; id: string };
20
21/**
22 * g1t in chat: @g1t, the same reserved `system` actor (id `g1t`) the
23 * runner and the feed show g1t's own work as. Every member can talk with
24 * it in a direct message, where it answers with the workspace's own AI
25 * provider (docs.g1t.sh/guides/ask/). It is never a person: the People
26 * directory and member pickers never list it.
27 */
28export const G1T_PRINCIPAL: Principal = { kind: "system", id: "g1t" };
29/** `system:g1t`, as chat keeps members and authors. */
30export const G1T_KEY = "system:g1t";
31
32/** Whether a member is g1t itself. */
33export function isG1t(member: { kind: string; id?: string } | null | undefined): boolean {
34 return member?.kind === "system" && member.id === "g1t";
35}
Chat and workspace agents: channels, DMs and named agents you talk to36
37export function principalKey(principal: Principal): string {
38 return `${principal.kind}:${principal.id}`;
39}
40
41export function parsePrincipalKey(key: string): Principal | null {
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.42 // g1t is the only `system` member there is.
43 if (key === G1T_KEY) return { ...G1T_PRINCIPAL };
Chat and workspace agents: channels, DMs and named agents you talk to44 const at = key.indexOf(":");
45 if (at < 0) return null;
46 const kind = key.slice(0, at);
47 const id = key.slice(at + 1);
48 if ((kind !== "user" && kind !== "agent") || !id) return null;
49 return { kind, id };
50}
51
52/** How a member shows: resolved by the chat service when it answers. */
53export type MemberProfile = Principal & {
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar54 /** `username` for a person (lowercased: what `@` mentions), `handle` for an agent. */
Chat and workspace agents: channels, DMs and named agents you talk to55 name: string;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar56 /**
57 * A person's username as they wrote it (`Ana`), when that differs from
58 * `name`; absent for an agent. Shown in their card and in autocomplete.
59 */
60 display_username?: string | null;
61 /**
62 * What chat shows them as: a person's display name, else their username
63 * in its chosen case; an agent's display name. Read it with `memberName`.
64 */
Chat and workspace agents: channels, DMs and named agents you talk to65 display_name: string;
66 /** Uploaded avatar hash for a person, or null for the letter avatar. */
67 avatar: string | null;
68 /** An agent's one-line role ("Release manager for g1t"). */
69 role: 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)70 /**
71 * An agent's title ("QA Engineer"); null for a person. Chat always sets
72 * it; optional so profiles a page makes for itself need not.
73 */
74 title?: string | 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.75 /** What an agent's face is drawn from when it chose none; null for a person. Always set by chat. */
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?: string | null;
An archived agent can be found, brought back, or deleted for good. The Agents page ends with Archived, folded, where owners see every archived agent and members their own personal ones, each with when and by whom it was archived and what deleting it would take; Restore brings it back, under a new handle when a live agent has taken its old one, and Delete permanently asks for the handle to be typed and then removes its sessions, replies, memories, routines, versions, drafts, skills and its computer's home, leaving its messages in chat under its saved name, the workspace's spend totals and the audit log. In Chat an archived agent is deactivated the way people expect: never offered as someone to message or mention, never woken, a conversation with it kept in the list, dimmed and marked Archived, open to read with a note in place of the composer and a way to restore it for owners; after deletion the conversation still reads, its messages marked as a deleted agent's. The agents, chat and agent budgets guides say so.77 /**
78 * An archived agent (docs.g1t.sh/guides/chat/, "Archived and deleted
79 * agents"): when, and by whom. Its conversations stay readable; it is
80 * never woken and never listed to talk to. Null or absent for a live
81 * agent and for a person.
82 */
83 archived_at?: string | null;
84 archived_by?: string | null;
85 /** An agent deleted for good: its messages stay under the name it had. Absent otherwise. */
86 deleted?: boolean;
Chat and workspace agents: channels, DMs and named agents you talk to87};
88
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar89/**
90 * A member's handle as it shows (`@Ana`, without the `@`): a person's
91 * username in its chosen case, an agent's handle.
92 */
93export function memberHandle(member: { name: string; display_username?: string | null }): string {
94 const display = member.display_username;
95 return display && display.toLowerCase() === member.name.toLowerCase() ? display : member.name;
96}
97
98/**
99 * How a member's name is shown everywhere in chat (messages, the
100 * sidebar, direct-message titles, typing, notifications and pushes): their
101 * display name, else their handle in its chosen case. One rule, so every
102 * surface agrees.
103 */
104export function memberName(member: { name: string; display_name?: string | null; display_username?: string | null }): string {
105 return member.display_name?.trim() || memberHandle(member);
106}
107
Chat and workspace agents: channels, DMs and named agents you talk to108export type ChannelKind = "channel" | "dm";
109
110export type Channel = {
111 id: string;
112 workspace_id: string;
113 kind: ChannelKind;
114 /** Lowercase, no `#`. Null for a direct message. */
115 name: string | null;
116 topic: string | null;
117 private: boolean;
118 created_by: Principal;
119 created_at: string;
120 archived_at: string | null;
121 last_message_at: string | null;
122};
123
124export type ChannelMember = {
125 channel_id: string;
126 member: MemberProfile;
127 role: "owner" | "member";
128 starred: boolean;
129 muted: boolean;
130 last_read_id: string | null;
131 joined_at: string;
132};
133
134/** A card g1t or an agent posts: an event, a task, an approval. */
135export type MessageCard = {
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar136 /** What it is about, e.g. `pull`, `issue`, `task`, `deploy`, `approval`, `session`, `draft_issue`. */
Chat and workspace agents: channels, DMs and named agents you talk to137 kind: string;
138 title: string;
139 /** A short line under the title: "3/3 checks · +214 −87". */
140 detail: string | null;
141 /** A status shown on the right: "Needs approval", "Merged". */
142 state: string | null;
143 /** Where clicking the card goes, relative to the site. */
144 href: string | null;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar145 /** A preview in Markdown under the title: a draft issue's body, a report's first lines. */
146 body?: string | null;
147 /** Labelled facts shown in two columns: "Repository · acme/web", "Cap · $2.00". */
148 fields?: CardField[];
149 /**
150 * What people can do right here. Pressing one goes to the service that
151 * owns the card (`owner`), which checks the person may, does it, and
152 * updates the card in place. A card without `owner` has no actions.
153 */
154 actions?: CardAction[];
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.155 /** The service whose card this was and that answered its actions: `agents`, on older cards only. */
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar156 owner?: "agents" | null;
157 /** What the card is about, for its owner: a session id, a draft id. */
158 ref?: string | null;
159};
160
161export type CardField = { label: string; value: string };
162
163/** A button on a card. */
164export type CardAction = {
165 /** Unique on the card: `stop`, `approve`, `file`. */
166 id: string;
167 label: string;
168 style?: "primary" | "danger" | "default";
169 /** Asked before it runs: "Stop this session and everything under it?". */
170 confirm?: string | null;
171 /** A value it needs first, asked inline: an amount in dollars, or a line of text. */
172 input?: { kind: "money" | "text"; label: string; placeholder?: string | null; initial?: string | null } | null;
173 /** A link instead of an action: opens this place in the site. */
174 href?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to175};
176
177export type ChatMessage = {
178 /** Time-sortable (ULID-like), so ordering by id is ordering by time. */
179 id: string;
180 channel_id: string;
181 author: MemberProfile;
182 kind: "text" | "card";
183 body: string;
184 card: MessageCard | null;
185 /** The message this replies under, or null for a top-level message. */
186 thread_root: string | null;
187 reply_count: number;
188 last_reply_at: string | null;
189 created_at: string;
190 edited_at: string | null;
191 deleted_at: 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)192 /**
193 * Its reactions, one per emoji in the order each was first used. Chat
194 * always sets it; optional so messages a page makes for itself need not.
195 */
196 reactions?: ChatReaction[];
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.197 /**
198 * On a message from @g1t that answers someone: how the answer went, who
199 * wrote it, and the steps it took. Absent on every other message.
200 */
201 answer?: ChatAnswer | null;
202 /**
203 * On a question someone asked @g1t from a page (the top bar's Ask): the
204 * page they were on, which g1t is told. Absent otherwise.
205 */
206 page?: AskPage | null;
207 /**
208 * A "New conversation" mark in a direct message with @g1t: answers after
209 * it read only what was said after it. Absent otherwise.
210 */
211 reset?: boolean;
212};
213
214/**
215 * Where an answer from @g1t is: `thinking` until its first words,
216 * `streaming` while they come (live as `message.streaming`), then `done`,
217 * `stopped` (Stop, or a newer question took over) or `error`.
218 */
219export type ChatAnswerState = "thinking" | "streaming" | "done" | "stopped" | "error";
220
221export type ChatAnswer = {
222 state: ChatAnswerState;
223 /** The provider's name and the model, once the provider said. */
224 provider_label?: string | null;
225 model?: string | null;
226 /** `max_tokens`, `length`: cut off at its limit. */
227 stop_reason?: string | null;
228 /** What went wrong, for `error`. */
229 error?: string | null;
230 /**
231 * Steps the answer took on its way (tool calls and the like), as the
232 * integrations service streamed them: each `ask` event other than meta,
233 * text, done and error, kept as it came. At most 50.
234 */
235 steps?: Record<string, unknown>[];
236 /** Set when Ask is not set up in the workspace: the answer says where to set it up. */
237 setup?: "missing" | null;
Chat and workspace agents: channels, DMs and named agents you talk to238};
239
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)240/**
241 * One emoji's reactions on a message. `emoji` is a Unicode emoji or
242 * `:name:` for one of the workspace's own (`CustomEmoji`).
243 */
244export type ChatReaction = {
245 emoji: string;
246 /** Everyone who reacted with it, people and agents alike. */
247 count: number;
248 /**
249 * Whether the viewer did. In live `message.*` events, which everyone in
250 * the room gets, it is always false: keep your own from what you know.
251 */
252 me: boolean;
253 /** The first ten who reacted with it, for the hover list. */
254 by: MemberProfile[];
255};
256
257/** Who may add a workspace's emoji: every member (the default), or only its owners. */
258export type EmojiUpload = "members" | "admins";
259
260/**
261 * One of a workspace's own emoji, used as `:name:`. Its image is served
262 * from the usercontent origin at `/emoji/<file>` (never from the site).
263 */
264export type CustomEmoji = {
265 name: string;
266 /** For an alias, the emoji it is another name for; it shows that one's image. */
267 alias_of: string | null;
268 /** The SHA-256 of the image's bytes. */
269 file: string;
270 content_type: "image/png" | "image/gif" | "image/webp";
271 bytes: number;
272 created_by: MemberProfile;
273 created_at: string;
274};
275
276export type EmojiList = {
277 emoji: CustomEmoji[];
278 emoji_upload: EmojiUpload;
279 /** Whether the viewer may add emoji and aliases. */
280 can_upload: boolean;
281 /** Whether the viewer is an owner: may remove any emoji and change `emoji_upload`. */
282 can_manage: boolean;
283};
284
Chat controls, public profiles, shadcn selects, and no Docs tab in a project285/** Who may do something in a workspace's chat: every member, or only its owners. */
286export type ChatAllowed = "members" | "owners";
287
288/**
289 * Who may rename and archive a channel: its owners (whoever made it) and
290 * the workspace's owners (the default), or the workspace's owners only.
291 */
292export type ChannelManagers = "channel_owners" | "owners";
293
294/**
295 * A workspace's chat settings, which its owners choose (Workspace,
296 * Settings, Chat). The chat service enforces each one; the page only
297 * hides what the viewer may not do.
298 */
299export type ChatSettings = {
300 /** Who may create public channels. */
301 public_channels: ChatAllowed;
302 /** Who may create private channels. */
303 private_channels: ChatAllowed;
304 /** Who may rename, archive and unarchive channels. */
305 manage_channels: ChannelManagers;
306 /** Who may add custom emoji (`admins` is the workspace's owners). */
307 emoji_upload: EmojiUpload;
308 /**
309 * The public channels someone new is put in the first time they open
310 * Chat, by id. #general unless the owners chose otherwise.
311 */
312 default_channels: string[];
313};
314
315/** What the viewer may do in a workspace's chat, from its settings and their role. */
316export type ChatPermissions = {
317 create_public_channels: boolean;
318 create_private_channels: boolean;
319 add_emoji: boolean;
320 /** Whether they may change the workspace's chat settings: owners only. */
321 manage_settings: boolean;
322};
323
324/** The chat settings page: the settings, what the viewer may do, and the public channels to choose defaults from. */
325export type ChatSettingsView = {
326 settings: ChatSettings;
327 can: ChatPermissions;
328 channels: Channel[];
329};
330
331/** A change to a channel: its name, its topic, or whether it is archived. Anything left out stays. */
332export type ChannelChange = { name?: string; topic?: string | null; archived?: boolean };
333
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)334/** An image for a new emoji, as base64. Its type is read from its bytes, never taken from here. */
335export type EmojiFile = { data: string };
336
337/** The largest custom emoji image, in bytes, and the widest or tallest, in pixels. */
338export const MAX_EMOJI_BYTES = 256 * 1024;
339export const MAX_EMOJI_SIDE = 512;
340
Chat and workspace agents: channels, DMs and named agents you talk to341/** One row of the Chat sidebar. */
342export type ChatSidebarEntry = {
343 channel: Channel;
344 /** "g1t-core", or the other members' names for a direct message. */
345 title: string;
346 /** For a direct message: who else is in it (up to four). */
347 others: MemberProfile[];
348 starred: boolean;
349 muted: boolean;
350 unread: number;
351 mentions: number;
352};
353
354export type ChatSidebar = {
355 entries: ChatSidebarEntry[];
356 /** Public channels in the workspace the viewer has not joined. */
357 browsable: number;
Chat controls, public profiles, shadcn selects, and no Docs tab in a project358 /** What the viewer may do, from the workspace's chat settings: the page hides what they may not. */
359 can: ChatPermissions;
Chat and workspace agents: channels, DMs and named agents you talk to360};
361
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.362/**
363 * `activity`: what was said over a span, in the conversations the viewer
364 * can read (every public channel, and the private channels and direct
365 * messages they are in). Messages are text messages and thread replies
366 * that were not deleted; cards agents post are not messages. Home reads it
367 * for what people and agents said since you were last there.
368 */
369export type ChatActivity = {
370 /** RFC 3339: `[from, until)`. */
371 from: string;
372 until: string;
373 messages: number;
374 /** Conversations with at least one message. */
375 channels: number;
376 /** Who said how much: member keys (`user:<id>`, `agent:<id>`), most first. */
377 authors: { key: string; messages: number }[];
378};
379
Chat and workspace agents: channels, DMs and named agents you talk to380export type MessagePage = {
381 messages: ChatMessage[];
382 /** Pass as `before` to read further back; null at the beginning. */
383 older: string | null;
384 /**
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread385 * For a thread's first page: the message the thread is under, however
386 * many replies it has, so a thread panel always shows it (a session's
387 * live card, say) at its top.
388 */
389 root?: ChatMessage | null;
390 /**
Chat and workspace agents: channels, DMs and named agents you talk to391 * Set when the page was read with `after`: pass it as `after` again for
392 * the next messages, or null when this page reached the newest.
393 */
394 newer?: string | null;
395};
396
397export type NewChannel = { name: string; topic?: string | null; private?: boolean };
398
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.399export type PostMessage = {
400 body: string;
401 thread_root?: string | null;
402 /** In a direct message with @g1t: the page the person asks from, which g1t is told. Ignored elsewhere. */
403 page?: AskPage | null;
404};
Chat and workspace agents: channels, DMs and named agents you talk to405
406/**
407 * What the live socket sends. The site opens
408 * `wss://<site>/<workspace>/chat/live?channel=<id>`; the site checks the
409 * session and forwards the upgrade to the chat service with the viewer.
410 */
411export type ChatLiveEvent =
412 | { type: "message.created"; message: ChatMessage }
413 | { type: "message.updated"; message: ChatMessage }
414 | { type: "message.deleted"; channel_id: string; id: string }
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.415 /**
416 * An answer from @g1t as it is written: the whole text so far (not a
417 * delta, so a late or dropped frame never garbles it) and where it is.
418 * The stored message catches up with a `message.updated` at the end.
419 */
420 | { type: "message.streaming"; channel_id: string; id: string; body: string; answer: ChatAnswer }
Chat and workspace agents: channels, DMs and named agents you talk to421 | { type: "typing"; channel_id: string; member: MemberProfile; until: 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)422 | { type: "read"; channel_id: string; principal: Principal; last_read_id: string }
Chat controls, public profiles, shadcn selects, and no Docs tab in a project423 | { type: "channel.updated"; channel: Channel }
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)424 | { type: "reaction.added" | "reaction.removed"; channel_id: string; message_id: string; emoji: string; member: MemberProfile };
Chat and workspace agents: channels, DMs and named agents you talk to425
426/**
427 * Header the site sets on a forwarded live socket: the viewer, as JSON.
428 * The site forwards the upgrade to the chat service's
429 * `GET /live?workspace=<slug>&channel=<id>` (`workspace` may be left out,
430 * at the cost of looking up each of the viewer's workspaces).
431 */
432export const CHAT_VIEWER_HEADER = "x-g1t-chat-viewer";
433
Chat controls, public profiles, shadcn selects, and no Docs tab in a project434/** A channel with its members, and whether the viewer may rename or archive it. */
435export type ChannelDetail = { channel: Channel; members: ChannelMember[]; can_manage: boolean };
436
Chat and workspace agents: channels, DMs and named agents you talk to437export type ChatApi = {
438 sidebar(workspace: string, viewer: User): Promise<Result<ChatSidebar>>;
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.439 /** What was said in `[from, until)` (RFC 3339) where the viewer can read, counted. */
440 activity(workspace: string, viewer: User, span: { from: string; until: string }): Promise<Result<ChatActivity>>;
Chat and workspace agents: channels, DMs and named agents you talk to441 channel(
442 workspace: string,
443 channelId: string,
444 viewer: User,
Chat controls, public profiles, shadcn selects, and no Docs tab in a project445 ): Promise<Result<ChannelDetail>>;
Chat and workspace agents: channels, DMs and named agents you talk to446 /** The same, found by its name in the workspace (`#general` or `general`), as the site's URLs name channels. */
447 channelByName(
448 workspace: string,
449 name: string,
450 viewer: User,
Chat controls, public profiles, shadcn selects, and no Docs tab in a project451 ): Promise<Result<ChannelDetail>>;
452 /**
453 * Browse channels: every public channel, and the private ones the viewer
454 * is in. With `archived`, the archived ones instead.
455 */
456 browse(workspace: string, viewer: User, options?: { archived?: boolean }): Promise<Result<Channel[]>>;
457 /** Makes a channel. Who may make a public or a private one is the workspace's setting. */
Chat and workspace agents: channels, DMs and named agents you talk to458 createChannel(workspace: string, viewer: User, input: NewChannel): Promise<Result<Channel>>;
459 /**
Chat controls, public profiles, shadcn selects, and no Docs tab in a project460 * Renames, archives or unarchives a channel (the workspace's
461 * `manage_channels` setting says who may), or changes its topic (any
462 * member). #general is never renamed or archived. Everyone looking at it
463 * gets `channel.updated`.
464 */
465 updateChannel(workspace: string, channelId: string, viewer: User, change: ChannelChange): Promise<Result<Channel>>;
466 /** The workspace's chat settings, for any member; only owners may change them. */
467 chatSettings(workspace: string, viewer: User): Promise<Result<ChatSettingsView>>;
468 /** Owners only: changes some of the workspace's chat settings, and answers all of them. */
469 setChatSettings(workspace: string, viewer: User, change: Partial<ChatSettings>): Promise<Result<ChatSettings>>;
470 /**
Chat and workspace agents: channels, DMs and named agents you talk to471 * The direct message between the viewer and these members, created on
472 * first use. The same set of members always gets the same channel.
473 */
474 openDm(workspace: string, viewer: User, members: Principal[]): Promise<Result<Channel>>;
475 join(workspace: string, channelId: string, viewer: User): Promise<Result<null>>;
476 leave(workspace: string, channelId: string, viewer: User): Promise<Result<null>>;
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.477 /** Adds a person. An invite grants read, never write. */
Chat and workspace agents: channels, DMs and named agents you talk to478 invite(workspace: string, channelId: string, viewer: User, member: Principal): Promise<Result<null>>;
479 /**
480 * Newest first. Without `thread_root`, the channel's top-level messages;
481 * with it, that thread's replies, plus the message they reply to as the
482 * oldest once the page reaches the start of the thread (`older` null).
483 * With `after` (catching up after a reconnect): the messages after that
484 * id instead, oldest first, deleted ones included so the client can
485 * drop them; `newer` says whether there are more.
486 * Limit 50 by default, 200 at most.
487 */
488 messages(
489 workspace: string,
490 channelId: string,
491 viewer: User,
492 page?: { before?: string | null; after?: string | null; limit?: number; thread_root?: string | null },
493 ): Promise<Result<MessagePage>>;
494 post(workspace: string, channelId: string, viewer: User, message: PostMessage): Promise<Result<ChatMessage>>;
495 edit(workspace: string, channelId: string, viewer: User, id: string, body: string): Promise<Result<ChatMessage>>;
496 remove(workspace: string, channelId: string, viewer: User, id: string): Promise<Result<null>>;
497 markRead(workspace: string, channelId: string, viewer: User, id: string): Promise<Result<null>>;
498 setPreferences(
499 workspace: string,
500 channelId: string,
501 viewer: User,
502 prefs: { starred?: boolean; muted?: boolean },
503 ): Promise<Result<null>>;
504 /**
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)505 * Reacts to a message with `emoji` (one Unicode emoji, or `:name:` for
506 * one of the workspace's own). Once per member per emoji; at most 50
507 * different emoji on a message. Answers the message's reactions now.
508 */
509 react(workspace: string, channelId: string, viewer: User, messageId: string, emoji: string): Promise<Result<ChatReaction[]>>;
510 unreact(workspace: string, channelId: string, viewer: User, messageId: string, emoji: string): Promise<Result<ChatReaction[]>>;
511 /** The workspace's own emoji, by name, and who may add them. */
512 listEmoji(workspace: string, viewer: User): Promise<Result<EmojiList>>;
513 /** Adds an emoji: a PNG, GIF or WebP of at most 256 KB and 512×512. */
514 addEmoji(workspace: string, viewer: User, name: string, file: EmojiFile): Promise<Result<CustomEmoji>>;
515 /** Gives an existing emoji (`target`) another name. */
516 aliasEmoji(workspace: string, viewer: User, name: string, target: string): Promise<Result<CustomEmoji>>;
517 /** Removes an emoji, and its aliases with it. Its creator or an owner may. */
518 removeEmoji(workspace: string, viewer: User, name: string): Promise<Result<null>>;
519 /** Owners only: who may add emoji. */
520 setEmojiUpload(workspace: string, viewer: User, value: EmojiUpload): Promise<Result<EmojiUpload>>;
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.521 /** The viewer's direct message with @g1t, created on first use. */
522 g1tDm(workspace: string, viewer: User): Promise<Result<Channel>>;
523 /**
524 * Starts a new conversation in the viewer's direct message with @g1t: a
525 * mark after which answers read only what comes next. Stops an answer
526 * still coming.
527 */
528 g1tReset(workspace: string, channelId: string, viewer: User): Promise<Result<ChatMessage>>;
529 /** Has @g1t answer the latest question again, in place of its last answer. */
530 g1tRetry(workspace: string, channelId: string, viewer: User): Promise<Result<null>>;
531 /** Stops @g1t's answer still coming; what came stays. */
532 g1tStop(workspace: string, channelId: string, viewer: User): Promise<Result<null>>;
Chat and workspace agents: channels, DMs and named agents you talk to533};
534
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.535/**
536 * For g1t's own services only, never the site on a person's behalf: posts
537 * and edits messages as @g1t in a direct message with it. The chat service
538 * answers these at `POST /internal/<method>`, which no route from the site
539 * reaches, and refuses anything that came in from the internet.
540 */
541export type ChatInternalApi = {
542 /** Posts `body` as @g1t in a direct message with it, in the workspace with this slug. */
543 g1tPost(workspace: string, channelId: string, body: string): Promise<Result<ChatMessage>>;
544 /** Changes the text of one of @g1t's messages there. */
545 g1tEdit(workspace: string, channelId: string, id: string, body: string): Promise<Result<ChatMessage>>;
546};
547
548export function chatInternalClient(service: ServiceBinding): ChatInternalApi {
549 const call = async <T>(method: string, args: object): Promise<T> => {
550 const response = await service.fetch(`https://service/internal/${method}`, {
551 method: "POST",
552 headers: { "content-type": "application/json" },
553 body: JSON.stringify(args),
554 });
555 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
556 return (await response.json()) as T;
557 };
558 return {
559 g1tPost: (workspace, channelId, body) => call("g1t_post", { workspace, channel_id: channelId, body }),
560 g1tEdit: (workspace, channelId, id, body) => call("g1t_edit", { workspace, channel_id: channelId, id, body }),
561 };
562}
563
Chat and workspace agents: channels, DMs and named agents you talk to564async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
565 const response = await service.fetch(`https://service/rpc/${method}`, {
566 method: "POST",
567 headers: { "content-type": "application/json" },
568 body: JSON.stringify(args),
569 });
570 if (!response.ok) {
571 throw new Error(`${method} failed with status ${response.status}`);
572 }
573 return (await response.json()) as T;
574}
575
576export function chatClient(service: ServiceBinding): ChatApi {
577 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
578 return {
579 sidebar: (workspace, viewer) => call("sidebar", { workspace, viewer }),
Home says what people did as well as what agents did. Since you were last here now has a People column and an Agents column: who pushed how many commits to which projects, pull requests opened, merged and reviewed, issues opened and closed, docs edited, messages sent and deploys that went out, each line a link to where those are listed, with the agents' acceptance (first time, after review, didn't finish) kept as a row of their column; the sentence under the heading sums it up honestly, and says when a part of g1t could not be read, or that the span was quiet. Landed counts merged pull requests, commits pushed straight to a default branch, production deploys that went live, releases and packages, newest first; Running now adds workflow runs. Behind it, the events service answers an activity digest over a span in one round trip from its existing indexes, every push now records how many commits it carried, and chat counts the messages sent in the conversations you can read. The Home guide defines every line, and says how this scales.580 activity: (workspace, viewer, span) => call("activity", { workspace, viewer, from: span.from, until: span.until }),
Chat and workspace agents: channels, DMs and named agents you talk to581 channel: (workspace, channelId, viewer) => call("channel", { workspace, channel_id: channelId, viewer }),
582 channelByName: (workspace, name, viewer) => call("channel_by_name", { workspace, name, viewer }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project583 browse: (workspace, viewer, options) => call("browse", { workspace, viewer, archived: options?.archived ?? false }),
Chat and workspace agents: channels, DMs and named agents you talk to584 createChannel: (workspace, viewer, input) => call("create_channel", { workspace, viewer, input }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project585 updateChannel: (workspace, channelId, viewer, change) => call("update_channel", { workspace, channel_id: channelId, viewer, change }),
586 chatSettings: (workspace, viewer) => call("chat_settings", { workspace, viewer }),
587 setChatSettings: (workspace, viewer, change) => call("set_chat_settings", { workspace, viewer, change }),
Chat and workspace agents: channels, DMs and named agents you talk to588 openDm: (workspace, viewer, members) => call("open_dm", { workspace, viewer, members }),
589 join: (workspace, channelId, viewer) => call("join", { workspace, channel_id: channelId, viewer }),
590 leave: (workspace, channelId, viewer) => call("leave", { workspace, channel_id: channelId, viewer }),
591 invite: (workspace, channelId, viewer, member) =>
592 call("invite", { workspace, channel_id: channelId, viewer, member }),
593 messages: (workspace, channelId, viewer, page) =>
594 call("messages", {
595 workspace,
596 channel_id: channelId,
597 viewer,
598 before: page?.before ?? null,
599 after: page?.after ?? null,
600 limit: page?.limit ?? null,
601 thread_root: page?.thread_root ?? null,
602 }),
603 post: (workspace, channelId, viewer, message) => call("post", { workspace, channel_id: channelId, viewer, message }),
604 edit: (workspace, channelId, viewer, id, body) => call("edit", { workspace, channel_id: channelId, viewer, id, body }),
605 remove: (workspace, channelId, viewer, id) => call("remove", { workspace, channel_id: channelId, viewer, id }),
606 markRead: (workspace, channelId, viewer, id) => call("mark_read", { workspace, channel_id: channelId, viewer, id }),
607 setPreferences: (workspace, channelId, viewer, prefs) =>
608 call("set_preferences", { workspace, channel_id: channelId, viewer, prefs }),
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)609 react: (workspace, channelId, viewer, messageId, emoji) =>
610 call("react", { workspace, channel_id: channelId, viewer, message_id: messageId, emoji }),
611 unreact: (workspace, channelId, viewer, messageId, emoji) =>
612 call("unreact", { workspace, channel_id: channelId, viewer, message_id: messageId, emoji }),
613 listEmoji: (workspace, viewer) => call("list_emoji", { workspace, viewer }),
614 addEmoji: (workspace, viewer, name, file) => call("add_emoji", { workspace, viewer, name, file }),
615 aliasEmoji: (workspace, viewer, name, target) => call("alias_emoji", { workspace, viewer, name, target }),
616 removeEmoji: (workspace, viewer, name) => call("remove_emoji", { workspace, viewer, name }),
617 setEmojiUpload: (workspace, viewer, value) => call("set_emoji_upload", { workspace, viewer, value }),
g1t coordinates the work and the person's own sessions do it: Ask g1t reads g1t with the same tools any agent gets, answers in one conversation shared with the @g1t direct message in Chat, and hands work to sessions in the person's own harness, whose subagents g1t records, ties to issues and pull requests, and lets see each other.618 g1tDm: (workspace, viewer) => call("g1t_dm", { workspace, viewer }),
619 g1tReset: (workspace, channelId, viewer) => call("g1t_reset", { workspace, channel_id: channelId, viewer }),
620 g1tRetry: (workspace, channelId, viewer) => call("g1t_retry", { workspace, channel_id: channelId, viewer }),
621 g1tStop: (workspace, channelId, viewer) => call("g1t_stop", { workspace, channel_id: channelId, viewer }),
Chat and workspace agents: channels, DMs and named agents you talk to622 };
623}

This file's history is long; its oldest lines are credited to the oldest commit read.