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
479 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.

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.1import { ArrowUp, MessageSquare, RotateCcw, Settings2, Square } from "lucide-react";
2import { type FormEvent, type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
Merge branch 'ask-g1t'3import { Link, useLocation } from "react-router";
4
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.5import { type AskSetup, type ChatLiveEvent, type ChatMessage, type Result } from "@g1t/contracts";
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.6
Ask answers "what shipped today" for the asker's own today, and says what changed rather than only how much. "Today" begins at the asker's local midnight: integrations reads the time zone on their profile, or failing that the one their browser sends with the question (the Ask panel and the Chat composer send it; chat keeps it on a question to @g1t, an IANA name only), works out with the runtime's Intl when their day began in UTC (local_day.rs, tested across zones west and east of UTC and on a half hour), and the system prompt says "they are in America/Denver, where it is 2026-10-12: today means since 2026-10-12T06:00:00Z"; without a zone it stays UTC. get_workspace_activity's answer gains `landed`: for the repositories whose default branch was pushed to in the span (busiest first, at most eight), the commits made in the span, one line each (repo, short sha, subject, author, time, link), at most forty, so a workspace that pushes to main rather than merging pull requests still shows what shipped; the prompt has Ask say what those changes were, pass `since` as the start of the asker's today, and narrow the kinds or the span when `complete` is false rather than guessing. A question asked in Chat keeps the direct message's path when its page has none of its own. The quality bar (bench/ask) asks through the direct message with @g1t, as the panel and Chat do now: it opens it, marks a new conversation, posts each question and follows g1t's answer to its end, and grades the steps, sources and proposals it kept. Its first run on production, with tools, had shipped-today pass every check (8/8) and the judge mark it 2.83, for the UTC day and the thin summary this fixes. Checked: cargo test for the workspace (the actions crates' repository-file tests on their own, as on this machine they cannot find their files from the workspace), the wasm build of integrations and the API, the TypeScript typecheck, every workspace's tests, the deploy tool's and the plugin's tests, and the OpenAPI document regenerated.7import { askPage, askWhere } from "../lib/ask";
8import { browserTimeZone } from "../lib/time-zone";
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.9import { anyAnswering, applyLive, fromG1t, suggestions } from "../lib/g1t-chat";
10import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton } from "./ai-elements/conversation";
11import { Message, MessageContent } from "./ai-elements/message";
12import type { ResponseContext } from "./ai-elements/response";
13import { Suggestion, Suggestions } from "./ai-elements/suggestion";
A question to @g1t asked on a page says which page under it, in Chat and in the Ask panel: "Asked on pull request #8 in flagon-io/g1t", linking back, so "summarize this pull request" or "is this done?" reads as the one it was about when the conversation is read later, as g1t was told. A question written in Chat itself, whose page is the direct message, says nothing. The quality bar's questions start with [TEST], so they are told apart from the person's own in the direct message they are asked in. Checked: the site's typecheck and its 890 tests, a production build whose chunks load first without error (scripted in a browser over the build), and the bench's next run.14import { AskedFrom, G1tAnswer } from "./chat/answer";
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.15import { useChatLive } from "./chat/live";
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.16import { MarkGlyph } from "./logo";
Merge branch 'ask-g1t'17import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
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.18import { Button } from "./ui/button";
19import { Hint } from "./ui/hint";
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.20import { Kbd } from "./ui/kbd";
Every page loads again: the top bar's Ask loads its panel the first time it is wanted instead of with every page. The shell imported the whole answer stack (AI Elements, Streamdown and its highlighter, the chat helpers) for the Ask panel, and the bundler's shared groups put the chat chunk and the shell's chunk each importing the other; a page that loaded the chat chunk first ran the shell's module before the chat chunk had finished, and its repository tabs read an array still undefined (`Cannot read properties of undefined (reading 'flatMap')`). components/ask-button.tsx is the button alone, in the shell; the panel (components/ask-composer.tsx, now AskSheet) is a lazy chunk, fetched when the pointer comes near the button or it takes focus, shown with a skeleton until it arrives, and kept mounted once loaded so the conversation stays while it is closed. A question asked from the panel also says the browser's time zone. Checked: a production build of the site has no import cycle between those two chunks any more (scripted over the built chunks), the site's typecheck and its 888 tests.21import { SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./ui/sheet";
Merge branch 'ask-g1t'22import { Skeleton } from "./ui/skeleton";
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.23import { Textarea } from "./ui/textarea";
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.24
25/**
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.26 * Ask g1t: a conversation with g1t about your work, from the top bar. It
27 * is your direct message with @g1t in Chat: the same messages, the same
28 * answers, whichever you write in. The answers come from the AI provider
29 * and model an owner chose for the workspace (Integrations → Your AI
30 * providers → Ask g1t), on the workspace's own key: g1t hosts no model.
Merge branch 'ask-g1t'31 *
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.32 * Each question carries the page you are on, which g1t is told. The chat
33 * service has g1t answer, and the answer streams into the direct message
34 * over its live socket, so it shows here and in Chat at once. The panel
35 * shows the current conversation, since the latest "New conversation";
36 * everything before it stays in 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.37 */
Merge branch 'ask-g1t'38
39type Setup = AskSetup & { owner: boolean };
40type SetupAnswer = { ok: boolean; value?: AskSetup; error?: { message: string }; owner: boolean };
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.41type Shown = ChatMessage & { pending?: boolean; failed?: boolean };
Merge branch 'ask-g1t'42
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.43const channelKey = (slug: string) => `g1t:ask-dm:${slug}`;
Merge branch 'ask-g1t'44
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.45function rememberedChannel(slug: string): string | null {
Merge branch 'ask-g1t'46 try {
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.47 return sessionStorage.getItem(channelKey(slug));
Merge branch 'ask-g1t'48 } catch {
49 return null;
50 }
51}
52
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.53function rememberChannel(slug: string, id: string) {
Merge branch 'ask-g1t'54 try {
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.55 sessionStorage.setItem(channelKey(slug), id);
Merge branch 'ask-g1t'56 } catch {
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.57 // Not kept: it is asked for again next time.
Merge branch 'ask-g1t'58 }
59}
60
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.61async function chatApi<T>(slug: string, body: Record<string, unknown>): Promise<Result<T>> {
62 try {
63 const response = await fetch(`/${slug}/-/chat/api`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
64 return (await response.json()) as Result<T>;
65 } catch {
66 return { ok: false, error: { code: "conflict", message: "g1t could not be reached. Check your connection and try again." } };
67 }
68}
69
70/** Oldest first, the newest message from each id. */
71function byId(list: Shown[]): Shown[] {
72 return [...new Map(list.map((m) => [m.id, m])).values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
73}
74
Merge branch 'ask-g1t'75/**
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.76 * The viewer's direct message with @g1t, as the panel shows it. It belongs
77 * to the top bar's button, not the panel, so what was said stays while the
78 * panel is closed; the answer itself is kept by chat either way.
Merge branch 'ask-g1t'79 */
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.80function useG1tConversation(slug: string, open: boolean) {
81 const [channelId, setChannelId] = useState<string | null>(null);
82 const [messages, setMessages] = useState<Shown[]>([]);
83 const [loaded, setLoaded] = useState(false);
84 const [problem, setProblem] = useState<string | null>(null);
85 const opening = useRef<Promise<string | null> | null>(null);
86
87 const channel = useCallback(async (): Promise<string | null> => {
88 if (channelId) return channelId;
89 opening.current ??= (async () => {
90 const remembered = rememberedChannel(slug);
91 if (remembered) return remembered;
92 const opened = await chatApi<{ channel: { id: string } }>(slug, { intent: "g1t_dm" });
93 if (!opened.ok) {
94 setProblem(opened.error.message);
95 return null;
96 }
97 rememberChannel(slug, opened.value.channel.id);
98 return opened.value.channel.id;
99 })().finally(() => {
100 opening.current = null;
101 });
102 const id = await opening.current;
103 if (id) setChannelId(id);
104 return id;
105 }, [channelId, slug]);
106
107 const load = useCallback(async () => {
108 const id = await channel();
109 if (!id) return;
110 try {
111 const response = await fetch(`/${slug}/-/chat/api?channel=${encodeURIComponent(id)}&limit=60`, { headers: { accept: "application/json" } });
112 const page = (await response.json()) as Result<{ messages: ChatMessage[] }>;
113 if (page.ok) {
114 setMessages((now) => byId([...now.filter((m) => m.pending), ...page.value.messages.filter((m) => !m.deleted_at && !m.thread_root)]));
115 setProblem(null);
116 } else if (page.error.code === "not_found") {
117 // Kept from another session that no longer has it: open it again.
118 try {
119 sessionStorage.removeItem(channelKey(slug));
120 } catch {
121 // Nothing kept.
122 }
123 setChannelId(null);
124 } else setProblem(page.error.message);
125 } catch {
126 setProblem("g1t could not be reached. Check your connection and try again.");
127 }
128 setLoaded(true);
129 }, [channel, slug]);
Merge branch 'ask-g1t'130
131 useEffect(() => {
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.132 if (open) void load();
133 }, [open, load]);
Merge branch 'ask-g1t'134
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.135 const onEvent = useCallback((event: ChatLiveEvent) => {
136 if (!channelId) return;
137 setMessages((now) => applyLive(now, event, channelId) as Shown[]);
138 }, [channelId]);
Merge branch 'ask-g1t'139
140 const ask = useCallback(
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.141 async (text: string, pathname: string) => {
142 const body = text.trim();
143 if (!body) return;
144 const id = await channel();
145 if (!id) return;
146 const pendingId = `~pending-${Date.now().toString(36)}`;
147 const pending: Shown = {
148 id: pendingId,
149 channel_id: id,
150 author: { kind: "user", id: "", name: "", display_name: "", avatar: null, role: null },
151 kind: "text",
152 body,
153 card: null,
154 thread_root: null,
155 reply_count: 0,
156 last_reply_at: null,
157 created_at: new Date().toISOString(),
158 edited_at: null,
159 deleted_at: null,
160 pending: true,
Merge branch 'ask-g1t'161 };
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.162 setMessages((now) => [...now, pending]);
Every page loads again: the top bar's Ask loads its panel the first time it is wanted instead of with every page. The shell imported the whole answer stack (AI Elements, Streamdown and its highlighter, the chat helpers) for the Ask panel, and the bundler's shared groups put the chat chunk and the shell's chunk each importing the other; a page that loaded the chat chunk first ran the shell's module before the chat chunk had finished, and its repository tabs read an array still undefined (`Cannot read properties of undefined (reading 'flatMap')`). components/ask-button.tsx is the button alone, in the shell; the panel (components/ask-composer.tsx, now AskSheet) is a lazy chunk, fetched when the pointer comes near the button or it takes focus, shown with a skeleton until it arrives, and kept mounted once loaded so the conversation stays while it is closed. A question asked from the panel also says the browser's time zone. Checked: a production build of the site has no import cycle between those two chunks any more (scripted over the built chunks), the site's typecheck and its 888 tests.163 const saved = await chatApi<ChatMessage>(slug, { intent: "post", channel_id: id, body, page: { ...askPage(pathname), time_zone: browserTimeZone() } });
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.164 setMessages((now) => (saved.ok ? byId([...now.filter((m) => m.id !== pendingId), saved.value]) : now.map((m) => (m.id === pendingId ? { ...m, failed: true } : m))));
165 // The answer arrives over the socket; should it have opened late, ask once more.
166 if (saved.ok) setTimeout(() => void load(), 1_500);
Merge branch 'ask-g1t'167 },
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.168 [channel, slug, load],
Merge branch 'ask-g1t'169 );
170
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.171 const act = useCallback(
172 async (intent: "g1t_stop" | "g1t_reset" | "g1t_retry") => {
173 const id = await channel();
174 if (!id) return;
175 const done = await chatApi<ChatMessage | null>(slug, { intent, channel_id: id });
176 if (!done.ok) setProblem(done.error.message);
177 else if (intent === "g1t_reset" && done.value) setMessages((now) => byId([...now, done.value as ChatMessage]));
178 },
179 [channel, slug],
180 );
Merge branch 'ask-g1t'181
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.182 return { channelId, messages, loaded, problem, ask, act, load, onEvent };
Merge branch 'ask-g1t'183}
184
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.185type G1tConversation = ReturnType<typeof useG1tConversation>;
Merge branch 'ask-g1t'186
Every page loads again: the top bar's Ask loads its panel the first time it is wanted instead of with every page. The shell imported the whole answer stack (AI Elements, Streamdown and its highlighter, the chat helpers) for the Ask panel, and the bundler's shared groups put the chat chunk and the shell's chunk each importing the other; a page that loaded the chat chunk first ran the shell's module before the chat chunk had finished, and its repository tabs read an array still undefined (`Cannot read properties of undefined (reading 'flatMap')`). components/ask-button.tsx is the button alone, in the shell; the panel (components/ask-composer.tsx, now AskSheet) is a lazy chunk, fetched when the pointer comes near the button or it takes focus, shown with a skeleton until it arrives, and kept mounted once loaded so the conversation stays while it is closed. A question asked from the panel also says the browser's time zone. Checked: a production build of the site has no import cycle between those two chunks any more (scripted over the built chunks), the site's typecheck and its 888 tests.187/**
188 * The Ask panel's sheet (components/ask-button.tsx loads it the first time
189 * the top bar's Ask opens, so pages don't carry it until then): the
190 * conversation, kept while the panel is closed.
191 */
192export function AskSheet({ slug, open }: { slug: string; open: boolean }) {
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.193 const conversation = useG1tConversation(slug, open);
194 const busy = anyAnswering(conversation.messages);
Merge branch 'ask-g1t'195 return (
Every page loads again: the top bar's Ask loads its panel the first time it is wanted instead of with every page. The shell imported the whole answer stack (AI Elements, Streamdown and its highlighter, the chat helpers) for the Ask panel, and the bundler's shared groups put the chat chunk and the shell's chunk each importing the other; a page that loaded the chat chunk first ran the shell's module before the chat chunk had finished, and its repository tabs read an array still undefined (`Cannot read properties of undefined (reading 'flatMap')`). components/ask-button.tsx is the button alone, in the shell; the panel (components/ask-composer.tsx, now AskSheet) is a lazy chunk, fetched when the pointer comes near the button or it takes focus, shown with a skeleton until it arrives, and kept mounted once loaded so the conversation stays while it is closed. A question asked from the panel also says the browser's time zone. Checked: a production build of the site has no import cycle between those two chunks any more (scripted over the built chunks), the site's typecheck and its 888 tests.196 <SheetContent
197 side="right"
198 className="sm:max-w-xl"
199 // Esc stops an answer still coming first; pressed again, it closes the panel.
200 onEscapeKeyDown={(event) => {
201 if (!busy) return;
202 event.preventDefault();
203 void conversation.act("g1t_stop");
204 }}
205 >
206 <AskPanel slug={slug} open={open} conversation={conversation} />
207 </SheetContent>
Merge branch 'ask-g1t'208 );
209}
210
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.211/** The live socket of the direct message, while the panel is open. */
212function LiveSocket({ slug, channelId, onEvent, onReconnect }: { slug: string; channelId: string; onEvent: (event: ChatLiveEvent) => void; onReconnect: () => void }) {
213 useChatLive(slug, channelId, onEvent, onReconnect);
214 return null;
215}
216
217function AskPanel({ slug, open, conversation }: { slug: string; open: boolean; conversation: G1tConversation }) {
Merge branch 'ask-g1t'218 const { pathname } = useLocation();
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.219 const { channelId, messages, loaded, problem, ask, act, load, onEvent } = conversation;
Merge branch 'ask-g1t'220 const [setup, setSetup] = useState<Setup | null>(null);
221 const [setupError, setSetupError] = useState<string | null>(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.222 const [draft, setDraft] = useState("");
Merge branch 'ask-g1t'223 const input = useRef<HTMLTextAreaElement>(null);
224
225 // What Ask answers with, each time it opens: an owner may have changed it.
226 useEffect(() => {
227 if (!open) return;
228 let live = true;
229 fetch(`/${slug}/-/ask`, { headers: { accept: "application/json" } })
230 .then((response) => (response.ok ? (response.json() as Promise<SetupAnswer>) : Promise.reject(new Error(String(response.status)))))
231 .then((answer) => {
232 if (!live) return;
233 if (answer.ok && answer.value) {
234 setSetup({ ...answer.value, owner: answer.owner });
235 setSetupError(null);
236 } else setSetupError(answer.error?.message ?? "Ask could not be loaded.");
237 })
238 .catch(() => live && setSetupError("Ask could not be loaded. Try again in a moment."));
239 return () => {
240 live = false;
241 };
242 }, [open, slug]);
243
244 useEffect(() => {
245 if (open) requestAnimationFrame(() => input.current?.focus());
246 }, [open]);
247
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.248 // ⌘K or Ctrl+K, while the panel is open, goes to its box rather than the command palette.
Merge branch 'ask-g1t'249 useEffect(() => {
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.250 if (!open) return;
251 const onKey = (event: globalThis.KeyboardEvent) => {
252 if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
253 event.preventDefault();
254 event.stopImmediatePropagation();
255 input.current?.focus();
256 }
257 };
258 window.addEventListener("keydown", onKey, { capture: true });
259 return () => window.removeEventListener("keydown", onKey, { capture: true });
260 }, [open]);
Merge branch 'ask-g1t'261
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.262 // The current conversation: what came after the latest "New conversation".
263 const shown = useMemo(() => {
264 let start = 0;
265 messages.forEach((m, index) => {
266 if (m.reset && fromG1t(m)) start = index + 1;
267 });
268 return messages.slice(start);
269 }, [messages]);
270 const earlier = shown.length < messages.length;
271 const busy = anyAnswering(messages);
272 const ready = setup != null && setup.problem == null;
273 const where = askPage(pathname);
274 const lastAnswer = [...shown].reverse().find((m) => !m.pending);
275 const context = useMemo<ResponseContext>(() => ({ slug, repo: where.repo ?? null }), [slug, where.repo?.namespace, where.repo?.name]);
Merge branch 'ask-g1t'276
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.277 const send = (text: string) => {
278 if (!ready || busy || !text.trim()) return;
279 setDraft("");
280 void ask(text, pathname);
281 };
Merge branch 'ask-g1t'282 const submit = (event: FormEvent) => {
283 event.preventDefault();
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.284 send(draft);
Merge branch 'ask-g1t'285 };
286 const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
287 if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
288 event.preventDefault();
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.289 send(draft);
Merge branch 'ask-g1t'290 }
291 };
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.292 const fresh = () => {
293 void act("g1t_reset");
294 input.current?.focus();
295 };
Merge branch 'ask-g1t'296
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.297 return (
Merge branch 'ask-g1t'298 <>
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.299 {open && channelId && <LiveSocket slug={slug} channelId={channelId} onEvent={onEvent} onReconnect={() => void load()} />}
300 <SheetHeader className="gap-1 border-b border-line pb-3">
Merge branch 'ask-g1t'301 <SheetTitle className="flex items-center gap-2">
302 <MarkGlyph className="h-3.5" />
303 Ask g1t
304 </SheetTitle>
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.305 <SheetDescription className="sr-only">A conversation with g1t about your work in {slug}. It is your direct message with g1t in Chat.</SheetDescription>
306 <div className="flex min-h-7 flex-wrap items-center gap-2 text-xs text-muted">
Merge branch 'ask-g1t'307 {setup == null && !setupError ? (
308 <Skeleton className="h-4 w-40" />
309 ) : ready ? (
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.310 <span className="min-w-0 truncate">
Merge branch 'ask-g1t'311 Answers with {setup.connection_name} · <span className="font-mono">{setup.model}</span>
312 </span>
313 ) : (
314 <span>Not set up yet</span>
315 )}
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.316 <span className="ml-auto flex items-center gap-0.5">
Merge branch 'ask-g1t'317 {setup?.owner && (
318 <Hint label="Choose the provider and model">
319 <Button asChild variant="ghost" size="icon-xs" aria-label="Choose the provider and model">
320 <Link to={`/${slug}/-/integrations/models#ask`}>
321 <Settings2 size={14} />
322 </Link>
323 </Button>
324 </Hint>
325 )}
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.326 {channelId && (
327 <Hint label="Open this conversation in Chat">
328 <Button asChild variant="ghost" size="icon-xs" aria-label="Open in Chat">
329 <Link to={`/${slug}/-/chat/dm/${channelId}`}>
330 <MessageSquare size={14} />
331 </Link>
332 </Button>
333 </Hint>
334 )}
335 <Hint label="New conversation: g1t reads only what comes next">
336 <Button variant="ghost" size="xs" onClick={fresh} disabled={shown.length === 0}>
337 <RotateCcw size={13} />
338 New conversation
339 </Button>
340 </Hint>
Merge branch 'ask-g1t'341 </span>
342 </div>
343 </SheetHeader>
344
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.345 <Conversation aria-busy={busy}>
346 <ConversationContent>
347 {setupError && (
348 <Alert tone="danger">
349 <AlertDescription className="text-danger">{setupError}</AlertDescription>
350 </Alert>
351 )}
352 {problem && (
353 <Alert tone="danger">
354 <AlertDescription className="text-danger">{problem}</AlertDescription>
355 </Alert>
356 )}
357 {setup && setup.problem && <NotSetUp slug={slug} setup={setup} />}
358 {earlier && channelId && (
359 <p className="text-center text-xs text-faint">
360 Earlier conversations are in{" "}
361 <Link to={`/${slug}/-/chat/dm/${channelId}`} className="text-accent hover:underline">
362 your direct message with g1t
363 </Link>
364 .
365 </p>
366 )}
367 {!loaded && open ? (
368 <div className="space-y-3" aria-busy="true">
369 <Skeleton className="ml-auto h-9 w-2/3 rounded-2xl" />
370 <Skeleton className="h-4 w-11/12" />
371 <Skeleton className="h-4 w-3/4" />
372 </div>
373 ) : shown.length === 0 && ready ? (
374 <ConversationEmptyState className="pt-16">
375 <MarkGlyph className="h-7" />
376 <div className="space-y-1">
377 <h3 className="text-base font-semibold text-fg">Ask about your work</h3>
378 <p className="max-w-sm text-sm text-muted">
379 {where.repo
380 ? `g1t is told you're in ${where.repo.namespace}/${where.repo.name}${where.issue ? `, on issue #${where.issue}` : where.pull ? `, on pull request #${where.pull}` : ""}.`
381 : "g1t is told which page you're on, and reads the issue or pull request in front of you."}
382 </p>
383 </div>
384 <Suggestions className="mt-3 max-w-md justify-center">
385 {suggestions(where).map((text) => (
386 <Suggestion key={text} suggestion={text} onPick={send} />
387 ))}
388 </Suggestions>
389 </ConversationEmptyState>
390 ) : (
391 shown.map((m) =>
392 fromG1t(m) ? (
393 m.reset ? null : (
394 <Message key={m.id} from="assistant">
395 <G1tAnswer message={m} context={context} onRetry={!busy && lastAnswer?.id === m.id ? () => void act("g1t_retry") : undefined} />
396 </Message>
397 )
398 ) : (
399 <Message key={m.id} from="user">
400 <MessageContent className={m.pending && !m.failed ? "opacity-70" : undefined}>{m.body}</MessageContent>
A question to @g1t asked on a page says which page under it, in Chat and in the Ask panel: "Asked on pull request #8 in flagon-io/g1t", linking back, so "summarize this pull request" or "is this done?" reads as the one it was about when the conversation is read later, as g1t was told. A question written in Chat itself, whose page is the direct message, says nothing. The quality bar's questions start with [TEST], so they are told apart from the person's own in the direct message they are asked in. Checked: the site's typecheck and its 890 tests, a production build whose chunks load first without error (scripted in a browser over the build), and the bench's next run.401 <AskedFrom page={m.page} />
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.402 {m.failed && <span className="text-xs text-danger">Not sent. Ask again.</span>}
403 </Message>
404 ),
405 )
406 )}
407 </ConversationContent>
408 <ConversationScrollButton />
409 </Conversation>
Merge branch 'ask-g1t'410
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.411 <form onSubmit={submit} className="border-t border-line px-4 pt-3 pb-3">
412 <div className="rounded-xl border border-line bg-bg transition-shadow focus-within:border-accent-dim focus-within:ring-2 focus-within:ring-accent/25">
Merge branch 'ask-g1t'413 <label htmlFor="ask-g1t" className="sr-only">
414 Ask g1t
415 </label>
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.416 <Textarea
Merge branch 'ask-g1t'417 id="ask-g1t"
418 ref={input}
419 value={draft}
420 onChange={(event) => setDraft(event.target.value)}
421 onKeyDown={onKeyDown}
422 rows={2}
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.423 maxLength={16_000}
Merge branch 'ask-g1t'424 disabled={!ready}
425 placeholder={ready ? "Ask about this page, a repository, or your work" : "Ask is not set up yet"}
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.426 className="max-h-48 min-h-14 resize-none border-0 bg-transparent px-3 pt-2.5 pb-1 shadow-none focus-visible:ring-0"
Merge branch 'ask-g1t'427 />
428 <div className="flex items-center gap-2 px-2 pb-2">
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.429 <span className="truncate pl-1 text-xs text-faint">{askWhere(where)}</span>
Merge branch 'ask-g1t'430 <span className="ml-auto" />
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.431 {busy ? (
432 <Hint label="Stop the answer (Esc)">
433 <Button type="button" variant="outline" size="icon-sm" onClick={() => void act("g1t_stop")} aria-label="Stop">
Merge branch 'ask-g1t'434 <Square size={12} className="fill-current" />
435 </Button>
436 </Hint>
437 ) : (
438 <Hint label="Send (Enter)">
439 <Button type="submit" variant="accent" size="icon-sm" disabled={!ready || !draft.trim()} aria-label="Send">
440 <ArrowUp size={15} />
441 </Button>
442 </Hint>
443 )}
444 </div>
445 </div>
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.446 <p className="mt-2 px-1 text-[0.6875rem] leading-relaxed text-faint">
447 Answers come from your workspace&apos;s own AI provider and can be wrong. Kept in your direct message with g1t.{" "}
448 <span className="whitespace-nowrap max-sm:hidden">
449 <Kbd>Shift</Kbd> <Kbd>Enter</Kbd> for a new line.
450 </span>
Merge branch 'ask-g1t'451 </p>
452 </form>
453 </>
454 );
455}
456
457function NotSetUp({ slug, setup }: { slug: string; setup: Setup }) {
458 return (
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.459 <Alert tone="info">
460 <AlertTitle>Ask needs one of your workspace&apos;s AI providers</AlertTitle>
Merge branch 'ask-g1t'461 <AlertDescription>
462 <p>{setup.problem}</p>
463 <p className="mt-2">
464 Ask answers with an AI provider your workspace connects, on its own key: g1t hosts no model for it.{" "}
465 {setup.owner ? (
Merge branch 'main' into ask-integration466 // Following it closes the panel, so the page it opens is not behind it.
467 <SheetClose asChild>
468 <Link to={`/${slug}/-/integrations/models#ask`} className="text-accent hover:underline">
469 Connect a provider and choose it for Ask
470 </Link>
471 </SheetClose>
Merge branch 'ask-g1t'472 ) : (
473 <>Ask an owner of {slug} to connect one and choose it for Ask.</>
474 )}
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.475 </p>
Merge branch 'ask-g1t'476 </AlertDescription>
477 </Alert>
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.478 );
479}

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