Skip to content
481 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
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.7import { askPage, askWhere } from "../lib/ask";
8import { anyAnswering, applyLive, fromG1t, suggestions } from "../lib/g1t-chat";
9import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton } from "./ai-elements/conversation";
10import { Message, MessageContent } from "./ai-elements/message";
11import type { ResponseContext } from "./ai-elements/response";
12import { Suggestion, Suggestions } from "./ai-elements/suggestion";
13import { G1tAnswer } from "./chat/answer";
14import { 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.15import { MarkGlyph } from "./logo";
Merge branch 'ask-g1t'16import { 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.17import { Button } from "./ui/button";
18import { 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.19import { Kbd } from "./ui/kbd";
Merge branch 'ask-g1t'20import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "./ui/sheet";
21import { 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.22import { 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.23
24/**
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.25 * Ask g1t: a conversation with g1t about your work, from the top bar. It
26 * is your direct message with @g1t in Chat: the same messages, the same
27 * answers, whichever you write in. The answers come from the AI provider
28 * and model an owner chose for the workspace (Integrations → Your AI
29 * providers → Ask g1t), on the workspace's own key: g1t hosts no model.
Merge branch 'ask-g1t'30 *
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.31 * Each question carries the page you are on, which g1t is told. The chat
32 * service has g1t answer, and the answer streams into the direct message
33 * over its live socket, so it shows here and in Chat at once. The panel
34 * shows the current conversation, since the latest "New conversation";
35 * 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.36 */
Merge branch 'ask-g1t'37
38type Setup = AskSetup & { owner: boolean };
39type 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.40type Shown = ChatMessage & { pending?: boolean; failed?: boolean };
Merge branch 'ask-g1t'41
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.42const channelKey = (slug: string) => `g1t:ask-dm:${slug}`;
Merge branch 'ask-g1t'43
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.44function rememberedChannel(slug: string): string | null {
Merge branch 'ask-g1t'45 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.46 return sessionStorage.getItem(channelKey(slug));
Merge branch 'ask-g1t'47 } catch {
48 return null;
49 }
50}
51
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.52function rememberChannel(slug: string, id: string) {
Merge branch 'ask-g1t'53 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.54 sessionStorage.setItem(channelKey(slug), id);
Merge branch 'ask-g1t'55 } 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.56 // Not kept: it is asked for again next time.
Merge branch 'ask-g1t'57 }
58}
59
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.60async function chatApi<T>(slug: string, body: Record<string, unknown>): Promise<Result<T>> {
61 try {
62 const response = await fetch(`/${slug}/-/chat/api`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
63 return (await response.json()) as Result<T>;
64 } catch {
65 return { ok: false, error: { code: "conflict", message: "g1t could not be reached. Check your connection and try again." } };
66 }
67}
68
69/** Oldest first, the newest message from each id. */
70function byId(list: Shown[]): Shown[] {
71 return [...new Map(list.map((m) => [m.id, m])).values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
72}
73
Merge branch 'ask-g1t'74/**
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.75 * The viewer's direct message with @g1t, as the panel shows it. It belongs
76 * to the top bar's button, not the panel, so what was said stays while the
77 * panel is closed; the answer itself is kept by chat either way.
Merge branch 'ask-g1t'78 */
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.79function useG1tConversation(slug: string, open: boolean) {
80 const [channelId, setChannelId] = useState<string | null>(null);
81 const [messages, setMessages] = useState<Shown[]>([]);
82 const [loaded, setLoaded] = useState(false);
83 const [problem, setProblem] = useState<string | null>(null);
84 const opening = useRef<Promise<string | null> | null>(null);
85
86 const channel = useCallback(async (): Promise<string | null> => {
87 if (channelId) return channelId;
88 opening.current ??= (async () => {
89 const remembered = rememberedChannel(slug);
90 if (remembered) return remembered;
91 const opened = await chatApi<{ channel: { id: string } }>(slug, { intent: "g1t_dm" });
92 if (!opened.ok) {
93 setProblem(opened.error.message);
94 return null;
95 }
96 rememberChannel(slug, opened.value.channel.id);
97 return opened.value.channel.id;
98 })().finally(() => {
99 opening.current = null;
100 });
101 const id = await opening.current;
102 if (id) setChannelId(id);
103 return id;
104 }, [channelId, slug]);
105
106 const load = useCallback(async () => {
107 const id = await channel();
108 if (!id) return;
109 try {
110 const response = await fetch(`/${slug}/-/chat/api?channel=${encodeURIComponent(id)}&limit=60`, { headers: { accept: "application/json" } });
111 const page = (await response.json()) as Result<{ messages: ChatMessage[] }>;
112 if (page.ok) {
113 setMessages((now) => byId([...now.filter((m) => m.pending), ...page.value.messages.filter((m) => !m.deleted_at && !m.thread_root)]));
114 setProblem(null);
115 } else if (page.error.code === "not_found") {
116 // Kept from another session that no longer has it: open it again.
117 try {
118 sessionStorage.removeItem(channelKey(slug));
119 } catch {
120 // Nothing kept.
121 }
122 setChannelId(null);
123 } else setProblem(page.error.message);
124 } catch {
125 setProblem("g1t could not be reached. Check your connection and try again.");
126 }
127 setLoaded(true);
128 }, [channel, slug]);
Merge branch 'ask-g1t'129
130 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.131 if (open) void load();
132 }, [open, load]);
Merge branch 'ask-g1t'133
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.134 const onEvent = useCallback((event: ChatLiveEvent) => {
135 if (!channelId) return;
136 setMessages((now) => applyLive(now, event, channelId) as Shown[]);
137 }, [channelId]);
Merge branch 'ask-g1t'138
139 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.140 async (text: string, pathname: string) => {
141 const body = text.trim();
142 if (!body) return;
143 const id = await channel();
144 if (!id) return;
145 const pendingId = `~pending-${Date.now().toString(36)}`;
146 const pending: Shown = {
147 id: pendingId,
148 channel_id: id,
149 author: { kind: "user", id: "", name: "", display_name: "", avatar: null, role: null },
150 kind: "text",
151 body,
152 card: null,
153 thread_root: null,
154 reply_count: 0,
155 last_reply_at: null,
156 created_at: new Date().toISOString(),
157 edited_at: null,
158 deleted_at: null,
159 pending: true,
Merge branch 'ask-g1t'160 };
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.161 setMessages((now) => [...now, pending]);
162 const saved = await chatApi<ChatMessage>(slug, { intent: "post", channel_id: id, body, page: askPage(pathname) });
163 setMessages((now) => (saved.ok ? byId([...now.filter((m) => m.id !== pendingId), saved.value]) : now.map((m) => (m.id === pendingId ? { ...m, failed: true } : m))));
164 // The answer arrives over the socket; should it have opened late, ask once more.
165 if (saved.ok) setTimeout(() => void load(), 1_500);
Merge branch 'ask-g1t'166 },
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.167 [channel, slug, load],
Merge branch 'ask-g1t'168 );
169
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.170 const act = useCallback(
171 async (intent: "g1t_stop" | "g1t_reset" | "g1t_retry") => {
172 const id = await channel();
173 if (!id) return;
174 const done = await chatApi<ChatMessage | null>(slug, { intent, channel_id: id });
175 if (!done.ok) setProblem(done.error.message);
176 else if (intent === "g1t_reset" && done.value) setMessages((now) => byId([...now, done.value as ChatMessage]));
177 },
178 [channel, slug],
179 );
Merge branch 'ask-g1t'180
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.181 return { channelId, messages, loaded, problem, ask, act, load, onEvent };
Merge branch 'ask-g1t'182}
183
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.184type G1tConversation = ReturnType<typeof useG1tConversation>;
Merge branch 'ask-g1t'185
186/** The top bar's Ask: the lavender pixel 1 beside its words. Opens the conversation beside the page. */
187export function AskButton({ slug }: { slug: string }) {
188 const [open, setOpen] = useState(false);
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.189 const conversation = useG1tConversation(slug, open);
190 const busy = anyAnswering(conversation.messages);
Merge branch 'ask-g1t'191 return (
192 <Sheet open={open} onOpenChange={setOpen}>
193 <Hint label="Ask g1t about your work">
194 <SheetTrigger asChild>
195 <Button variant="soft" size="bar" aria-label="Ask g1t" className="gap-2 max-md:ring-0 lg:px-3">
196 <MarkGlyph className="h-3.5" />
197 <span className="max-lg:hidden">Ask</span>
198 </Button>
199 </SheetTrigger>
200 </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.201 <SheetContent
202 side="right"
203 className="sm:max-w-xl"
204 // Esc stops an answer still coming first; pressed again, it closes the panel.
205 onEscapeKeyDown={(event) => {
206 if (!busy) return;
207 event.preventDefault();
208 void conversation.act("g1t_stop");
209 }}
210 >
Merge branch 'ask-g1t'211 <AskPanel slug={slug} open={open} conversation={conversation} />
212 </SheetContent>
213 </Sheet>
214 );
215}
216
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.217/** The live socket of the direct message, while the panel is open. */
218function LiveSocket({ slug, channelId, onEvent, onReconnect }: { slug: string; channelId: string; onEvent: (event: ChatLiveEvent) => void; onReconnect: () => void }) {
219 useChatLive(slug, channelId, onEvent, onReconnect);
220 return null;
221}
222
223function AskPanel({ slug, open, conversation }: { slug: string; open: boolean; conversation: G1tConversation }) {
Merge branch 'ask-g1t'224 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.225 const { channelId, messages, loaded, problem, ask, act, load, onEvent } = conversation;
Merge branch 'ask-g1t'226 const [setup, setSetup] = useState<Setup | null>(null);
227 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.228 const [draft, setDraft] = useState("");
Merge branch 'ask-g1t'229 const input = useRef<HTMLTextAreaElement>(null);
230
231 // What Ask answers with, each time it opens: an owner may have changed it.
232 useEffect(() => {
233 if (!open) return;
234 let live = true;
235 fetch(`/${slug}/-/ask`, { headers: { accept: "application/json" } })
236 .then((response) => (response.ok ? (response.json() as Promise<SetupAnswer>) : Promise.reject(new Error(String(response.status)))))
237 .then((answer) => {
238 if (!live) return;
239 if (answer.ok && answer.value) {
240 setSetup({ ...answer.value, owner: answer.owner });
241 setSetupError(null);
242 } else setSetupError(answer.error?.message ?? "Ask could not be loaded.");
243 })
244 .catch(() => live && setSetupError("Ask could not be loaded. Try again in a moment."));
245 return () => {
246 live = false;
247 };
248 }, [open, slug]);
249
250 useEffect(() => {
251 if (open) requestAnimationFrame(() => input.current?.focus());
252 }, [open]);
253
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.254 // ⌘K or Ctrl+K, while the panel is open, goes to its box rather than the command palette.
Merge branch 'ask-g1t'255 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.256 if (!open) return;
257 const onKey = (event: globalThis.KeyboardEvent) => {
258 if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
259 event.preventDefault();
260 event.stopImmediatePropagation();
261 input.current?.focus();
262 }
263 };
264 window.addEventListener("keydown", onKey, { capture: true });
265 return () => window.removeEventListener("keydown", onKey, { capture: true });
266 }, [open]);
Merge branch 'ask-g1t'267
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.268 // The current conversation: what came after the latest "New conversation".
269 const shown = useMemo(() => {
270 let start = 0;
271 messages.forEach((m, index) => {
272 if (m.reset && fromG1t(m)) start = index + 1;
273 });
274 return messages.slice(start);
275 }, [messages]);
276 const earlier = shown.length < messages.length;
277 const busy = anyAnswering(messages);
278 const ready = setup != null && setup.problem == null;
279 const where = askPage(pathname);
280 const lastAnswer = [...shown].reverse().find((m) => !m.pending);
281 const context = useMemo<ResponseContext>(() => ({ slug, repo: where.repo ?? null }), [slug, where.repo?.namespace, where.repo?.name]);
Merge branch 'ask-g1t'282
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.283 const send = (text: string) => {
284 if (!ready || busy || !text.trim()) return;
285 setDraft("");
286 void ask(text, pathname);
287 };
Merge branch 'ask-g1t'288 const submit = (event: FormEvent) => {
289 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.290 send(draft);
Merge branch 'ask-g1t'291 };
292 const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
293 if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
294 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.295 send(draft);
Merge branch 'ask-g1t'296 }
297 };
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.298 const fresh = () => {
299 void act("g1t_reset");
300 input.current?.focus();
301 };
Merge branch 'ask-g1t'302
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.303 return (
Merge branch 'ask-g1t'304 <>
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 {open && channelId && <LiveSocket slug={slug} channelId={channelId} onEvent={onEvent} onReconnect={() => void load()} />}
306 <SheetHeader className="gap-1 border-b border-line pb-3">
Merge branch 'ask-g1t'307 <SheetTitle className="flex items-center gap-2">
308 <MarkGlyph className="h-3.5" />
309 Ask g1t
310 </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.311 <SheetDescription className="sr-only">A conversation with g1t about your work in {slug}. It is your direct message with g1t in Chat.</SheetDescription>
312 <div className="flex min-h-7 flex-wrap items-center gap-2 text-xs text-muted">
Merge branch 'ask-g1t'313 {setup == null && !setupError ? (
314 <Skeleton className="h-4 w-40" />
315 ) : 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.316 <span className="min-w-0 truncate">
Merge branch 'ask-g1t'317 Answers with {setup.connection_name} · <span className="font-mono">{setup.model}</span>
318 </span>
319 ) : (
320 <span>Not set up yet</span>
321 )}
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.322 <span className="ml-auto flex items-center gap-0.5">
Merge branch 'ask-g1t'323 {setup?.owner && (
324 <Hint label="Choose the provider and model">
325 <Button asChild variant="ghost" size="icon-xs" aria-label="Choose the provider and model">
326 <Link to={`/${slug}/-/integrations/models#ask`}>
327 <Settings2 size={14} />
328 </Link>
329 </Button>
330 </Hint>
331 )}
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.332 {channelId && (
333 <Hint label="Open this conversation in Chat">
334 <Button asChild variant="ghost" size="icon-xs" aria-label="Open in Chat">
335 <Link to={`/${slug}/-/chat/dm/${channelId}`}>
336 <MessageSquare size={14} />
337 </Link>
338 </Button>
339 </Hint>
340 )}
341 <Hint label="New conversation: g1t reads only what comes next">
342 <Button variant="ghost" size="xs" onClick={fresh} disabled={shown.length === 0}>
343 <RotateCcw size={13} />
344 New conversation
345 </Button>
346 </Hint>
Merge branch 'ask-g1t'347 </span>
348 </div>
349 </SheetHeader>
350
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.351 <Conversation aria-busy={busy}>
352 <ConversationContent>
353 {setupError && (
354 <Alert tone="danger">
355 <AlertDescription className="text-danger">{setupError}</AlertDescription>
356 </Alert>
357 )}
358 {problem && (
359 <Alert tone="danger">
360 <AlertDescription className="text-danger">{problem}</AlertDescription>
361 </Alert>
362 )}
363 {setup && setup.problem && <NotSetUp slug={slug} setup={setup} />}
364 {earlier && channelId && (
365 <p className="text-center text-xs text-faint">
366 Earlier conversations are in{" "}
367 <Link to={`/${slug}/-/chat/dm/${channelId}`} className="text-accent hover:underline">
368 your direct message with g1t
369 </Link>
370 .
371 </p>
372 )}
373 {!loaded && open ? (
374 <div className="space-y-3" aria-busy="true">
375 <Skeleton className="ml-auto h-9 w-2/3 rounded-2xl" />
376 <Skeleton className="h-4 w-11/12" />
377 <Skeleton className="h-4 w-3/4" />
378 </div>
379 ) : shown.length === 0 && ready ? (
380 <ConversationEmptyState className="pt-16">
381 <MarkGlyph className="h-7" />
382 <div className="space-y-1">
383 <h3 className="text-base font-semibold text-fg">Ask about your work</h3>
384 <p className="max-w-sm text-sm text-muted">
385 {where.repo
386 ? `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}` : ""}.`
387 : "g1t is told which page you're on, and reads the issue or pull request in front of you."}
388 </p>
389 </div>
390 <Suggestions className="mt-3 max-w-md justify-center">
391 {suggestions(where).map((text) => (
392 <Suggestion key={text} suggestion={text} onPick={send} />
393 ))}
394 </Suggestions>
395 </ConversationEmptyState>
396 ) : (
397 shown.map((m) =>
398 fromG1t(m) ? (
399 m.reset ? null : (
400 <Message key={m.id} from="assistant">
401 <G1tAnswer message={m} context={context} onRetry={!busy && lastAnswer?.id === m.id ? () => void act("g1t_retry") : undefined} />
402 </Message>
403 )
404 ) : (
405 <Message key={m.id} from="user">
406 <MessageContent className={m.pending && !m.failed ? "opacity-70" : undefined}>{m.body}</MessageContent>
407 {m.failed && <span className="text-xs text-danger">Not sent. Ask again.</span>}
408 </Message>
409 ),
410 )
411 )}
412 </ConversationContent>
413 <ConversationScrollButton />
414 </Conversation>
Merge branch 'ask-g1t'415
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 <form onSubmit={submit} className="border-t border-line px-4 pt-3 pb-3">
417 <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'418 <label htmlFor="ask-g1t" className="sr-only">
419 Ask g1t
420 </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.421 <Textarea
Merge branch 'ask-g1t'422 id="ask-g1t"
423 ref={input}
424 value={draft}
425 onChange={(event) => setDraft(event.target.value)}
426 onKeyDown={onKeyDown}
427 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.428 maxLength={16_000}
Merge branch 'ask-g1t'429 disabled={!ready}
430 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.431 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'432 />
433 <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.434 <span className="truncate pl-1 text-xs text-faint">{askWhere(where)}</span>
Merge branch 'ask-g1t'435 <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.436 {busy ? (
437 <Hint label="Stop the answer (Esc)">
438 <Button type="button" variant="outline" size="icon-sm" onClick={() => void act("g1t_stop")} aria-label="Stop">
Merge branch 'ask-g1t'439 <Square size={12} className="fill-current" />
440 </Button>
441 </Hint>
442 ) : (
443 <Hint label="Send (Enter)">
444 <Button type="submit" variant="accent" size="icon-sm" disabled={!ready || !draft.trim()} aria-label="Send">
445 <ArrowUp size={15} />
446 </Button>
447 </Hint>
448 )}
449 </div>
450 </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.451 <p className="mt-2 px-1 text-[0.6875rem] leading-relaxed text-faint">
452 Answers come from your workspace&apos;s own AI provider and can be wrong. Kept in your direct message with g1t.{" "}
453 <span className="whitespace-nowrap max-sm:hidden">
454 <Kbd>Shift</Kbd> <Kbd>Enter</Kbd> for a new line.
455 </span>
Merge branch 'ask-g1t'456 </p>
457 </form>
458 </>
459 );
460}
461
462function NotSetUp({ slug, setup }: { slug: string; setup: Setup }) {
463 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.464 <Alert tone="info">
465 <AlertTitle>Ask needs one of your workspace&apos;s AI providers</AlertTitle>
Merge branch 'ask-g1t'466 <AlertDescription>
467 <p>{setup.problem}</p>
468 <p className="mt-2">
469 Ask answers with an AI provider your workspace connects, on its own key: g1t hosts no model for it.{" "}
470 {setup.owner ? (
471 <Link to={`/${slug}/-/integrations/models#ask`} className="text-accent hover:underline">
472 Connect a provider and choose it for Ask
473 </Link>
474 ) : (
475 <>Ask an owner of {slug} to connect one and choose it for Ask.</>
476 )}
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.477 </p>
Merge branch 'ask-g1t'478 </AlertDescription>
479 </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.480 );
481}

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