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
503 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";
11import type { Result } from "./result";
12
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.13/** Who is speaking: a person (by user id), or, on older messages, a former workspace agent (by agent id). */
Chat and workspace agents: channels, DMs and named agents you talk to14export type Principal = { kind: "user" | "agent"; id: string };
15
16export function principalKey(principal: Principal): string {
17 return `${principal.kind}:${principal.id}`;
18}
19
20export function parsePrincipalKey(key: string): Principal | null {
21 const at = key.indexOf(":");
22 if (at < 0) return null;
23 const kind = key.slice(0, at);
24 const id = key.slice(at + 1);
25 if ((kind !== "user" && kind !== "agent") || !id) return null;
26 return { kind, id };
27}
28
29/** How a member shows: resolved by the chat service when it answers. */
30export type MemberProfile = Principal & {
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar31 /** `username` for a person (lowercased: what `@` mentions), `handle` for an agent. */
Chat and workspace agents: channels, DMs and named agents you talk to32 name: string;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar33 /**
34 * A person's username as they wrote it (`Ana`), when that differs from
35 * `name`; absent for an agent. Shown in their card and in autocomplete.
36 */
37 display_username?: string | null;
38 /**
39 * What chat shows them as: a person's display name, else their username
40 * in its chosen case; an agent's display name. Read it with `memberName`.
41 */
Chat and workspace agents: channels, DMs and named agents you talk to42 display_name: string;
43 /** Uploaded avatar hash for a person, or null for the letter avatar. */
44 avatar: string | null;
45 /** An agent's one-line role ("Release manager for g1t"). */
46 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)47 /**
48 * An agent's title ("QA Engineer"); null for a person. Chat always sets
49 * it; optional so profiles a page makes for itself need not.
50 */
51 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.52 /** 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)53 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.54 /**
55 * An archived agent (docs.g1t.sh/guides/chat/, "Archived and deleted
56 * agents"): when, and by whom. Its conversations stay readable; it is
57 * never woken and never listed to talk to. Null or absent for a live
58 * agent and for a person.
59 */
60 archived_at?: string | null;
61 archived_by?: string | null;
62 /** An agent deleted for good: its messages stay under the name it had. Absent otherwise. */
63 deleted?: boolean;
Chat and workspace agents: channels, DMs and named agents you talk to64};
65
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar66/**
67 * A member's handle as it shows (`@Ana`, without the `@`): a person's
68 * username in its chosen case, an agent's handle.
69 */
70export function memberHandle(member: { name: string; display_username?: string | null }): string {
71 const display = member.display_username;
72 return display && display.toLowerCase() === member.name.toLowerCase() ? display : member.name;
73}
74
75/**
76 * How a member's name is shown everywhere in chat (messages, the
77 * sidebar, direct-message titles, typing, notifications and pushes): their
78 * display name, else their handle in its chosen case. One rule, so every
79 * surface agrees.
80 */
81export function memberName(member: { name: string; display_name?: string | null; display_username?: string | null }): string {
82 return member.display_name?.trim() || memberHandle(member);
83}
84
Chat and workspace agents: channels, DMs and named agents you talk to85export type ChannelKind = "channel" | "dm";
86
87export type Channel = {
88 id: string;
89 workspace_id: string;
90 kind: ChannelKind;
91 /** Lowercase, no `#`. Null for a direct message. */
92 name: string | null;
93 topic: string | null;
94 private: boolean;
95 created_by: Principal;
96 created_at: string;
97 archived_at: string | null;
98 last_message_at: string | null;
99};
100
101export type ChannelMember = {
102 channel_id: string;
103 member: MemberProfile;
104 role: "owner" | "member";
105 starred: boolean;
106 muted: boolean;
107 last_read_id: string | null;
108 joined_at: string;
109};
110
111/** A card g1t or an agent posts: an event, a task, an approval. */
112export type MessageCard = {
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar113 /** 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 to114 kind: string;
115 title: string;
116 /** A short line under the title: "3/3 checks · +214 −87". */
117 detail: string | null;
118 /** A status shown on the right: "Needs approval", "Merged". */
119 state: string | null;
120 /** Where clicking the card goes, relative to the site. */
121 href: string | null;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar122 /** A preview in Markdown under the title: a draft issue's body, a report's first lines. */
123 body?: string | null;
124 /** Labelled facts shown in two columns: "Repository · acme/web", "Cap · $2.00". */
125 fields?: CardField[];
126 /**
127 * What people can do right here. Pressing one goes to the service that
128 * owns the card (`owner`), which checks the person may, does it, and
129 * updates the card in place. A card without `owner` has no actions.
130 */
131 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.132 /** 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 calendar133 owner?: "agents" | null;
134 /** What the card is about, for its owner: a session id, a draft id. */
135 ref?: string | null;
136};
137
138export type CardField = { label: string; value: string };
139
140/** A button on a card. */
141export type CardAction = {
142 /** Unique on the card: `stop`, `approve`, `file`. */
143 id: string;
144 label: string;
145 style?: "primary" | "danger" | "default";
146 /** Asked before it runs: "Stop this session and everything under it?". */
147 confirm?: string | null;
148 /** A value it needs first, asked inline: an amount in dollars, or a line of text. */
149 input?: { kind: "money" | "text"; label: string; placeholder?: string | null; initial?: string | null } | null;
150 /** A link instead of an action: opens this place in the site. */
151 href?: string | null;
Chat and workspace agents: channels, DMs and named agents you talk to152};
153
154export type ChatMessage = {
155 /** Time-sortable (ULID-like), so ordering by id is ordering by time. */
156 id: string;
157 channel_id: string;
158 author: MemberProfile;
159 kind: "text" | "card";
160 body: string;
161 card: MessageCard | null;
162 /** The message this replies under, or null for a top-level message. */
163 thread_root: string | null;
164 reply_count: number;
165 last_reply_at: string | null;
166 created_at: string;
167 edited_at: string | null;
168 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)169 /**
170 * Its reactions, one per emoji in the order each was first used. Chat
171 * always sets it; optional so messages a page makes for itself need not.
172 */
173 reactions?: ChatReaction[];
Chat and workspace agents: channels, DMs and named agents you talk to174};
175
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 * One emoji's reactions on a message. `emoji` is a Unicode emoji or
178 * `:name:` for one of the workspace's own (`CustomEmoji`).
179 */
180export type ChatReaction = {
181 emoji: string;
182 /** Everyone who reacted with it, people and agents alike. */
183 count: number;
184 /**
185 * Whether the viewer did. In live `message.*` events, which everyone in
186 * the room gets, it is always false: keep your own from what you know.
187 */
188 me: boolean;
189 /** The first ten who reacted with it, for the hover list. */
190 by: MemberProfile[];
191};
192
193/** Who may add a workspace's emoji: every member (the default), or only its owners. */
194export type EmojiUpload = "members" | "admins";
195
196/**
197 * One of a workspace's own emoji, used as `:name:`. Its image is served
198 * from the usercontent origin at `/emoji/<file>` (never from the site).
199 */
200export type CustomEmoji = {
201 name: string;
202 /** For an alias, the emoji it is another name for; it shows that one's image. */
203 alias_of: string | null;
204 /** The SHA-256 of the image's bytes. */
205 file: string;
206 content_type: "image/png" | "image/gif" | "image/webp";
207 bytes: number;
208 created_by: MemberProfile;
209 created_at: string;
210};
211
212export type EmojiList = {
213 emoji: CustomEmoji[];
214 emoji_upload: EmojiUpload;
215 /** Whether the viewer may add emoji and aliases. */
216 can_upload: boolean;
217 /** Whether the viewer is an owner: may remove any emoji and change `emoji_upload`. */
218 can_manage: boolean;
219};
220
Chat controls, public profiles, shadcn selects, and no Docs tab in a project221/** Who may do something in a workspace's chat: every member, or only its owners. */
222export type ChatAllowed = "members" | "owners";
223
224/**
225 * Who may rename and archive a channel: its owners (whoever made it) and
226 * the workspace's owners (the default), or the workspace's owners only.
227 */
228export type ChannelManagers = "channel_owners" | "owners";
229
230/**
231 * A workspace's chat settings, which its owners choose (Workspace,
232 * Settings, Chat). The chat service enforces each one; the page only
233 * hides what the viewer may not do.
234 */
235export type ChatSettings = {
236 /** Who may create public channels. */
237 public_channels: ChatAllowed;
238 /** Who may create private channels. */
239 private_channels: ChatAllowed;
240 /** Who may rename, archive and unarchive channels. */
241 manage_channels: ChannelManagers;
242 /** Who may add custom emoji (`admins` is the workspace's owners). */
243 emoji_upload: EmojiUpload;
244 /**
245 * The public channels someone new is put in the first time they open
246 * Chat, by id. #general unless the owners chose otherwise.
247 */
248 default_channels: string[];
249};
250
251/** What the viewer may do in a workspace's chat, from its settings and their role. */
252export type ChatPermissions = {
253 create_public_channels: boolean;
254 create_private_channels: boolean;
255 add_emoji: boolean;
256 /** Whether they may change the workspace's chat settings: owners only. */
257 manage_settings: boolean;
258};
259
260/** The chat settings page: the settings, what the viewer may do, and the public channels to choose defaults from. */
261export type ChatSettingsView = {
262 settings: ChatSettings;
263 can: ChatPermissions;
264 channels: Channel[];
265};
266
267/** A change to a channel: its name, its topic, or whether it is archived. Anything left out stays. */
268export type ChannelChange = { name?: string; topic?: string | null; archived?: boolean };
269
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)270/** An image for a new emoji, as base64. Its type is read from its bytes, never taken from here. */
271export type EmojiFile = { data: string };
272
273/** The largest custom emoji image, in bytes, and the widest or tallest, in pixels. */
274export const MAX_EMOJI_BYTES = 256 * 1024;
275export const MAX_EMOJI_SIDE = 512;
276
Chat and workspace agents: channels, DMs and named agents you talk to277/** One row of the Chat sidebar. */
278export type ChatSidebarEntry = {
279 channel: Channel;
280 /** "g1t-core", or the other members' names for a direct message. */
281 title: string;
282 /** For a direct message: who else is in it (up to four). */
283 others: MemberProfile[];
284 starred: boolean;
285 muted: boolean;
286 unread: number;
287 mentions: number;
288};
289
290export type ChatSidebar = {
291 entries: ChatSidebarEntry[];
292 /** Public channels in the workspace the viewer has not joined. */
293 browsable: number;
Chat controls, public profiles, shadcn selects, and no Docs tab in a project294 /** What the viewer may do, from the workspace's chat settings: the page hides what they may not. */
295 can: ChatPermissions;
Chat and workspace agents: channels, DMs and named agents you talk to296};
297
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.298/**
299 * `activity`: what was said over a span, in the conversations the viewer
300 * can read (every public channel, and the private channels and direct
301 * messages they are in). Messages are text messages and thread replies
302 * that were not deleted; cards agents post are not messages. Home reads it
303 * for what people and agents said since you were last there.
304 */
305export type ChatActivity = {
306 /** RFC 3339: `[from, until)`. */
307 from: string;
308 until: string;
309 messages: number;
310 /** Conversations with at least one message. */
311 channels: number;
312 /** Who said how much: member keys (`user:<id>`, `agent:<id>`), most first. */
313 authors: { key: string; messages: number }[];
314};
315
Chat and workspace agents: channels, DMs and named agents you talk to316export type MessagePage = {
317 messages: ChatMessage[];
318 /** Pass as `before` to read further back; null at the beginning. */
319 older: string | null;
320 /**
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread321 * For a thread's first page: the message the thread is under, however
322 * many replies it has, so a thread panel always shows it (a session's
323 * live card, say) at its top.
324 */
325 root?: ChatMessage | null;
326 /**
Chat and workspace agents: channels, DMs and named agents you talk to327 * Set when the page was read with `after`: pass it as `after` again for
328 * the next messages, or null when this page reached the newest.
329 */
330 newer?: string | null;
331};
332
333export type NewChannel = { name: string; topic?: string | null; private?: boolean };
334
335export type PostMessage = { body: string; thread_root?: string | null };
336
337/**
338 * What the live socket sends. The site opens
339 * `wss://<site>/<workspace>/chat/live?channel=<id>`; the site checks the
340 * session and forwards the upgrade to the chat service with the viewer.
341 */
342export type ChatLiveEvent =
343 | { type: "message.created"; message: ChatMessage }
344 | { type: "message.updated"; message: ChatMessage }
345 | { type: "message.deleted"; channel_id: string; id: string }
346 | { 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)347 | { type: "read"; channel_id: string; principal: Principal; last_read_id: string }
Chat controls, public profiles, shadcn selects, and no Docs tab in a project348 | { 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)349 | { 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 to350
351/**
352 * Header the site sets on a forwarded live socket: the viewer, as JSON.
353 * The site forwards the upgrade to the chat service's
354 * `GET /live?workspace=<slug>&channel=<id>` (`workspace` may be left out,
355 * at the cost of looking up each of the viewer's workspaces).
356 */
357export const CHAT_VIEWER_HEADER = "x-g1t-chat-viewer";
358
Chat controls, public profiles, shadcn selects, and no Docs tab in a project359/** A channel with its members, and whether the viewer may rename or archive it. */
360export type ChannelDetail = { channel: Channel; members: ChannelMember[]; can_manage: boolean };
361
Chat and workspace agents: channels, DMs and named agents you talk to362export type ChatApi = {
363 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.364 /** What was said in `[from, until)` (RFC 3339) where the viewer can read, counted. */
365 activity(workspace: string, viewer: User, span: { from: string; until: string }): Promise<Result<ChatActivity>>;
Chat and workspace agents: channels, DMs and named agents you talk to366 channel(
367 workspace: string,
368 channelId: string,
369 viewer: User,
Chat controls, public profiles, shadcn selects, and no Docs tab in a project370 ): Promise<Result<ChannelDetail>>;
Chat and workspace agents: channels, DMs and named agents you talk to371 /** The same, found by its name in the workspace (`#general` or `general`), as the site's URLs name channels. */
372 channelByName(
373 workspace: string,
374 name: string,
375 viewer: User,
Chat controls, public profiles, shadcn selects, and no Docs tab in a project376 ): Promise<Result<ChannelDetail>>;
377 /**
378 * Browse channels: every public channel, and the private ones the viewer
379 * is in. With `archived`, the archived ones instead.
380 */
381 browse(workspace: string, viewer: User, options?: { archived?: boolean }): Promise<Result<Channel[]>>;
382 /** 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 to383 createChannel(workspace: string, viewer: User, input: NewChannel): Promise<Result<Channel>>;
384 /**
Chat controls, public profiles, shadcn selects, and no Docs tab in a project385 * Renames, archives or unarchives a channel (the workspace's
386 * `manage_channels` setting says who may), or changes its topic (any
387 * member). #general is never renamed or archived. Everyone looking at it
388 * gets `channel.updated`.
389 */
390 updateChannel(workspace: string, channelId: string, viewer: User, change: ChannelChange): Promise<Result<Channel>>;
391 /** The workspace's chat settings, for any member; only owners may change them. */
392 chatSettings(workspace: string, viewer: User): Promise<Result<ChatSettingsView>>;
393 /** Owners only: changes some of the workspace's chat settings, and answers all of them. */
394 setChatSettings(workspace: string, viewer: User, change: Partial<ChatSettings>): Promise<Result<ChatSettings>>;
395 /**
Chat and workspace agents: channels, DMs and named agents you talk to396 * The direct message between the viewer and these members, created on
397 * first use. The same set of members always gets the same channel.
398 */
399 openDm(workspace: string, viewer: User, members: Principal[]): Promise<Result<Channel>>;
400 join(workspace: string, channelId: string, viewer: User): Promise<Result<null>>;
401 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.402 /** Adds a person. An invite grants read, never write. */
Chat and workspace agents: channels, DMs and named agents you talk to403 invite(workspace: string, channelId: string, viewer: User, member: Principal): Promise<Result<null>>;
404 /**
405 * Newest first. Without `thread_root`, the channel's top-level messages;
406 * with it, that thread's replies, plus the message they reply to as the
407 * oldest once the page reaches the start of the thread (`older` null).
408 * With `after` (catching up after a reconnect): the messages after that
409 * id instead, oldest first, deleted ones included so the client can
410 * drop them; `newer` says whether there are more.
411 * Limit 50 by default, 200 at most.
412 */
413 messages(
414 workspace: string,
415 channelId: string,
416 viewer: User,
417 page?: { before?: string | null; after?: string | null; limit?: number; thread_root?: string | null },
418 ): Promise<Result<MessagePage>>;
419 post(workspace: string, channelId: string, viewer: User, message: PostMessage): Promise<Result<ChatMessage>>;
420 edit(workspace: string, channelId: string, viewer: User, id: string, body: string): Promise<Result<ChatMessage>>;
421 remove(workspace: string, channelId: string, viewer: User, id: string): Promise<Result<null>>;
422 markRead(workspace: string, channelId: string, viewer: User, id: string): Promise<Result<null>>;
423 setPreferences(
424 workspace: string,
425 channelId: string,
426 viewer: User,
427 prefs: { starred?: boolean; muted?: boolean },
428 ): Promise<Result<null>>;
429 /**
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)430 * Reacts to a message with `emoji` (one Unicode emoji, or `:name:` for
431 * one of the workspace's own). Once per member per emoji; at most 50
432 * different emoji on a message. Answers the message's reactions now.
433 */
434 react(workspace: string, channelId: string, viewer: User, messageId: string, emoji: string): Promise<Result<ChatReaction[]>>;
435 unreact(workspace: string, channelId: string, viewer: User, messageId: string, emoji: string): Promise<Result<ChatReaction[]>>;
436 /** The workspace's own emoji, by name, and who may add them. */
437 listEmoji(workspace: string, viewer: User): Promise<Result<EmojiList>>;
438 /** Adds an emoji: a PNG, GIF or WebP of at most 256 KB and 512×512. */
439 addEmoji(workspace: string, viewer: User, name: string, file: EmojiFile): Promise<Result<CustomEmoji>>;
440 /** Gives an existing emoji (`target`) another name. */
441 aliasEmoji(workspace: string, viewer: User, name: string, target: string): Promise<Result<CustomEmoji>>;
442 /** Removes an emoji, and its aliases with it. Its creator or an owner may. */
443 removeEmoji(workspace: string, viewer: User, name: string): Promise<Result<null>>;
444 /** Owners only: who may add emoji. */
445 setEmojiUpload(workspace: string, viewer: User, value: EmojiUpload): Promise<Result<EmojiUpload>>;
Chat and workspace agents: channels, DMs and named agents you talk to446};
447
448async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
449 const response = await service.fetch(`https://service/rpc/${method}`, {
450 method: "POST",
451 headers: { "content-type": "application/json" },
452 body: JSON.stringify(args),
453 });
454 if (!response.ok) {
455 throw new Error(`${method} failed with status ${response.status}`);
456 }
457 return (await response.json()) as T;
458}
459
460export function chatClient(service: ServiceBinding): ChatApi {
461 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
462 return {
463 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.464 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 to465 channel: (workspace, channelId, viewer) => call("channel", { workspace, channel_id: channelId, viewer }),
466 channelByName: (workspace, name, viewer) => call("channel_by_name", { workspace, name, viewer }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project467 browse: (workspace, viewer, options) => call("browse", { workspace, viewer, archived: options?.archived ?? false }),
Chat and workspace agents: channels, DMs and named agents you talk to468 createChannel: (workspace, viewer, input) => call("create_channel", { workspace, viewer, input }),
Chat controls, public profiles, shadcn selects, and no Docs tab in a project469 updateChannel: (workspace, channelId, viewer, change) => call("update_channel", { workspace, channel_id: channelId, viewer, change }),
470 chatSettings: (workspace, viewer) => call("chat_settings", { workspace, viewer }),
471 setChatSettings: (workspace, viewer, change) => call("set_chat_settings", { workspace, viewer, change }),
Chat and workspace agents: channels, DMs and named agents you talk to472 openDm: (workspace, viewer, members) => call("open_dm", { workspace, viewer, members }),
473 join: (workspace, channelId, viewer) => call("join", { workspace, channel_id: channelId, viewer }),
474 leave: (workspace, channelId, viewer) => call("leave", { workspace, channel_id: channelId, viewer }),
475 invite: (workspace, channelId, viewer, member) =>
476 call("invite", { workspace, channel_id: channelId, viewer, member }),
477 messages: (workspace, channelId, viewer, page) =>
478 call("messages", {
479 workspace,
480 channel_id: channelId,
481 viewer,
482 before: page?.before ?? null,
483 after: page?.after ?? null,
484 limit: page?.limit ?? null,
485 thread_root: page?.thread_root ?? null,
486 }),
487 post: (workspace, channelId, viewer, message) => call("post", { workspace, channel_id: channelId, viewer, message }),
488 edit: (workspace, channelId, viewer, id, body) => call("edit", { workspace, channel_id: channelId, viewer, id, body }),
489 remove: (workspace, channelId, viewer, id) => call("remove", { workspace, channel_id: channelId, viewer, id }),
490 markRead: (workspace, channelId, viewer, id) => call("mark_read", { workspace, channel_id: channelId, viewer, id }),
491 setPreferences: (workspace, channelId, viewer, prefs) =>
492 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)493 react: (workspace, channelId, viewer, messageId, emoji) =>
494 call("react", { workspace, channel_id: channelId, viewer, message_id: messageId, emoji }),
495 unreact: (workspace, channelId, viewer, messageId, emoji) =>
496 call("unreact", { workspace, channel_id: channelId, viewer, message_id: messageId, emoji }),
497 listEmoji: (workspace, viewer) => call("list_emoji", { workspace, viewer }),
498 addEmoji: (workspace, viewer, name, file) => call("add_emoji", { workspace, viewer, name, file }),
499 aliasEmoji: (workspace, viewer, name, target) => call("alias_emoji", { workspace, viewer, name, target }),
500 removeEmoji: (workspace, viewer, name) => call("remove_emoji", { workspace, viewer, name }),
501 setEmojiUpload: (workspace, viewer, value) => call("set_emoji_upload", { workspace, viewer, value }),
Chat and workspace agents: channels, DMs and named agents you talk to502 };
503}

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