Skip to content
385 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.

Merge branch 'ask-g1t'1import { ArrowUp, CircleAlert, RotateCcw, Settings2, Square } from "lucide-react";
2import { type FormEvent, type KeyboardEvent, useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
3import { Link, useLocation } from "react-router";
4
5import { ASK_MAX_MESSAGE_CHARS, type AskEvent, type AskSetup } 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
Merge branch 'ask-g1t'7import { EMPTY_ASK, type Turn, askPage, askReducer, cutOff, history, readLines } from "../lib/ask";
8import { MessageText, type TextContext } from "./chat/text";
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.9import { MarkGlyph } from "./logo";
Merge branch 'ask-g1t'10import { 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.11import { Badge } from "./ui/badge";
12import { Button } from "./ui/button";
13import { Hint } from "./ui/hint";
Merge branch 'ask-g1t'14import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "./ui/sheet";
15import { Skeleton } from "./ui/skeleton";
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.16
17/**
Merge branch 'ask-g1t'18 * Ask g1t: a conversation with g1t about your work, from the top bar. The
19 * answers come from the AI provider and model an owner chose for the
20 * workspace (Integrations → Your AI providers), on the workspace's own key:
21 * g1t hosts no model for it. Each question goes to `-/ask`
22 * (routes/workspace/ask.ts) with the page you are on, and the answer
23 * streams back.
24 *
25 * The conversation lives in this browser tab (session storage), so it
26 * survives moving between pages and reloading, and goes with the tab.
27 * Keeping conversations on the server comes later.
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.28 */
Merge branch 'ask-g1t'29
30type Setup = AskSetup & { owner: boolean };
31type SetupAnswer = { ok: boolean; value?: AskSetup; error?: { message: string }; owner: boolean };
32
33const storageKey = (slug: string) => `g1t:ask:${slug}`;
34
35function load(slug: string) {
36 try {
37 const kept = sessionStorage.getItem(storageKey(slug));
38 return kept ? (JSON.parse(kept) as unknown) : null;
39 } catch {
40 return null;
41 }
42}
43
44function save(slug: string, value: unknown) {
45 try {
46 sessionStorage.setItem(storageKey(slug), JSON.stringify(value));
47 } catch {
48 // Storage full or blocked: the conversation stays for this page.
49 }
50}
51
52/**
53 * The conversation and what it can do. It belongs to the top bar's button,
54 * not the panel, so closing the panel keeps an answer coming.
55 */
56function useAskConversation(slug: string) {
57 const { pathname } = useLocation();
58 const [state, dispatch] = useReducer(askReducer, EMPTY_ASK);
59 const [draft, setDraft] = useState("");
60 const controller = useRef<AbortController | null>(null);
61 const restored = useRef(false);
62
63 // The conversation kept in this tab, once; then kept as it changes.
64 useEffect(() => {
65 if (!restored.current) {
66 restored.current = true;
67 const kept = load(slug);
68 if (kept) dispatch({ type: "restore", state: kept as never });
69 return;
70 }
71 save(slug, state);
72 }, [slug, state]);
73
74 // Leaving the workspace stops an answer still coming.
75 useEffect(() => () => controller.current?.abort(), []);
76
77 const ask = useCallback(
78 async (question: string) => {
79 const text = question.trim();
80 if (!text || state.busy) return;
81 const messages = history(state.turns, text);
82 const id = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
83 dispatch({ type: "ask", question: text, id });
84 setDraft("");
85 const abort = new AbortController();
86 controller.current = abort;
87 let ended = false;
88 const take = (events: AskEvent[]) => {
89 for (const event of events) {
90 dispatch({ type: "event", event });
91 if (event.type === "done" || event.type === "error") ended = true;
92 }
93 };
94 try {
95 const response = await fetch(`/${slug}/-/ask`, {
96 method: "POST",
97 headers: { "content-type": "application/json", accept: "application/x-ndjson" },
98 body: JSON.stringify({ messages, page: askPage(pathname) }),
99 signal: abort.signal,
100 });
101 if (!response.body) throw new Error("no body");
102 const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
103 let rest = "";
104 for (;;) {
105 const { value, done } = await reader.read();
106 if (done) break;
107 const read = readLines(rest, value);
108 rest = read.rest;
109 take(read.events);
110 }
111 take(readLines(rest, "\n").events);
112 if (!ended) dispatch({ type: "failed", message: "The answer broke off. Ask again." });
113 } catch {
114 if (abort.signal.aborted) dispatch({ type: "stop" });
115 else dispatch({ type: "failed", message: "Ask could not be reached. Check your connection and ask again." });
116 } finally {
117 if (controller.current === abort) controller.current = null;
118 }
119 },
120 [pathname, slug, state.busy, state.turns],
121 );
122
123 const stop = useCallback(() => {
124 controller.current?.abort();
125 dispatch({ type: "stop" });
126 }, []);
127
128 const fresh = useCallback(() => {
129 controller.current?.abort();
130 dispatch({ type: "reset" });
131 setDraft("");
132 }, []);
133
134 return { state, draft, setDraft, ask, stop, fresh };
135}
136
137type Conversation = ReturnType<typeof useAskConversation>;
138
139/** The top bar's Ask: the lavender pixel 1 beside its words. Opens the conversation beside the page. */
140export function AskButton({ slug }: { slug: string }) {
141 const [open, setOpen] = useState(false);
142 const conversation = useAskConversation(slug);
143 return (
144 <Sheet open={open} onOpenChange={setOpen}>
145 <Hint label="Ask g1t about your work">
146 <SheetTrigger asChild>
147 <Button variant="soft" size="bar" aria-label="Ask g1t" className="gap-2 max-md:ring-0 lg:px-3">
148 <MarkGlyph className="h-3.5" />
149 <span className="max-lg:hidden">Ask</span>
150 </Button>
151 </SheetTrigger>
152 </Hint>
153 <SheetContent side="right" className="sm:max-w-lg">
154 <AskPanel slug={slug} open={open} conversation={conversation} />
155 </SheetContent>
156 </Sheet>
157 );
158}
159
160function AskPanel({ slug, open, conversation }: { slug: string; open: boolean; conversation: Conversation }) {
161 const { pathname } = useLocation();
162 const { state, draft, setDraft, ask, stop } = conversation;
163 const [setup, setSetup] = useState<Setup | null>(null);
164 const [setupError, setSetupError] = useState<string | null>(null);
165 const end = useRef<HTMLDivElement>(null);
166 const input = useRef<HTMLTextAreaElement>(null);
167
168 // What Ask answers with, each time it opens: an owner may have changed it.
169 useEffect(() => {
170 if (!open) return;
171 let live = true;
172 fetch(`/${slug}/-/ask`, { headers: { accept: "application/json" } })
173 .then((response) => (response.ok ? (response.json() as Promise<SetupAnswer>) : Promise.reject(new Error(String(response.status)))))
174 .then((answer) => {
175 if (!live) return;
176 if (answer.ok && answer.value) {
177 setSetup({ ...answer.value, owner: answer.owner });
178 setSetupError(null);
179 } else setSetupError(answer.error?.message ?? "Ask could not be loaded.");
180 })
181 .catch(() => live && setSetupError("Ask could not be loaded. Try again in a moment."));
182 return () => {
183 live = false;
184 };
185 }, [open, slug]);
186
187 useEffect(() => {
188 if (open) requestAnimationFrame(() => input.current?.focus());
189 }, [open]);
190
191 // Keep the newest words in view as they come.
192 const last = state.turns.at(-1);
193 useEffect(() => {
194 end.current?.scrollIntoView({ block: "end" });
195 }, [state.turns.length, last?.content.length]);
196
197 const fresh = () => {
198 conversation.fresh();
199 input.current?.focus();
200 };
201
202 const submit = (event: FormEvent) => {
203 event.preventDefault();
204 void ask(draft);
205 };
206
207 const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
208 if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
209 event.preventDefault();
210 void ask(draft);
211 }
212 };
213
214 const ready = setup != null && setup.problem == null;
215 const context: TextContext = useMemo(() => ({ slug, me: null, channels: new Set(), project: null }), [slug]);
216 const where = askPage(pathname);
217
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.218 return (
Merge branch 'ask-g1t'219 <>
220 <SheetHeader className="gap-1">
221 <SheetTitle className="flex items-center gap-2">
222 <MarkGlyph className="h-3.5" />
223 Ask g1t
224 </SheetTitle>
225 <SheetDescription className="sr-only">A conversation with g1t about your work in {slug}.</SheetDescription>
226 <div className="flex min-h-6 flex-wrap items-center gap-2 text-xs text-muted">
227 {setup == null && !setupError ? (
228 <Skeleton className="h-4 w-40" />
229 ) : ready ? (
230 <span className="truncate">
231 Answers with {setup.connection_name} · <span className="font-mono">{setup.model}</span>
232 </span>
233 ) : (
234 <span>Not set up yet</span>
235 )}
236 <span className="ml-auto flex items-center gap-1">
237 {setup?.owner && (
238 <Hint label="Choose the provider and model">
239 <Button asChild variant="ghost" size="icon-xs" aria-label="Choose the provider and model">
240 <Link to={`/${slug}/-/integrations/models#ask`}>
241 <Settings2 size={14} />
242 </Link>
243 </Button>
244 </Hint>
245 )}
246 <Button variant="ghost" size="xs" onClick={fresh} disabled={state.turns.length === 0}>
247 <RotateCcw size={13} />
248 New conversation
249 </Button>
250 </span>
251 </div>
252 </SheetHeader>
253
254 <div className="min-h-0 grow overflow-y-auto px-5 py-4" aria-live="polite" aria-busy={state.busy}>
255 {setupError && (
256 <Alert tone="danger">
257 <AlertDescription className="text-danger">{setupError}</AlertDescription>
258 </Alert>
259 )}
260 {setup && setup.problem && <NotSetUp slug={slug} setup={setup} />}
261 {state.turns.length === 0 && ready && <Welcome where={where.repo ? `${where.repo.namespace}/${where.repo.name}` : null} />}
262 <ol className="space-y-5">
263 {state.turns.map((turn) => (
264 <li key={turn.id}>{turn.role === "user" ? <Question turn={turn} /> : <Answer turn={turn} context={context} />}</li>
265 ))}
266 </ol>
267 <div ref={end} />
268 </div>
269
270 <form onSubmit={submit} className="border-t border-line px-4 py-3">
271 <div className="rounded-lg border border-line bg-bg focus-within:border-accent-dim focus-within:ring-2 focus-within:ring-accent/25">
272 <label htmlFor="ask-g1t" className="sr-only">
273 Ask g1t
274 </label>
275 <textarea
276 id="ask-g1t"
277 ref={input}
278 value={draft}
279 onChange={(event) => setDraft(event.target.value)}
280 onKeyDown={onKeyDown}
281 rows={2}
282 maxLength={ASK_MAX_MESSAGE_CHARS}
283 disabled={!ready}
284 placeholder={ready ? "Ask about this page, a repository, or your work" : "Ask is not set up yet"}
285 className="block max-h-48 min-h-14 w-full resize-none bg-transparent px-3 pt-2.5 pb-1 text-sm text-fg placeholder:text-faint focus:outline-none disabled:cursor-not-allowed"
286 />
287 <div className="flex items-center gap-2 px-2 pb-2">
288 <span className="truncate pl-1 text-xs text-faint">
289 {where.issue ? `On issue #${where.issue}` : where.pull ? `On pull request #${where.pull}` : where.repo ? `In ${where.repo.name}` : null}
290 </span>
291 <span className="ml-auto" />
292 {state.busy ? (
293 <Hint label="Stop the answer">
294 <Button type="button" variant="outline" size="icon-sm" onClick={stop} aria-label="Stop">
295 <Square size={12} className="fill-current" />
296 </Button>
297 </Hint>
298 ) : (
299 <Hint label="Send (Enter)">
300 <Button type="submit" variant="accent" size="icon-sm" disabled={!ready || !draft.trim()} aria-label="Send">
301 <ArrowUp size={15} />
302 </Button>
303 </Hint>
304 )}
305 </div>
306 </div>
307 <p className="mt-2 px-1 text-[0.6875rem] text-faint">
308 Answers come from your workspace's own AI provider and can be wrong. Kept in this tab only.
309 </p>
310 </form>
311 </>
312 );
313}
314
315function NotSetUp({ slug, setup }: { slug: string; setup: Setup }) {
316 return (
317 <Alert tone="info" className="mb-4">
318 <AlertTitle>Ask needs one of your workspace's AI providers</AlertTitle>
319 <AlertDescription>
320 <p>{setup.problem}</p>
321 <p className="mt-2">
322 Ask answers with an AI provider your workspace connects, on its own key: g1t hosts no model for it.{" "}
323 {setup.owner ? (
324 <Link to={`/${slug}/-/integrations/models#ask`} className="text-accent hover:underline">
325 Connect a provider and choose it for Ask
326 </Link>
327 ) : (
328 <>Ask an owner of {slug} to connect one and choose it for Ask.</>
329 )}
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.330 </p>
Merge branch 'ask-g1t'331 </AlertDescription>
332 </Alert>
333 );
334}
335
336function Welcome({ where }: { where: string | null }) {
337 return (
338 <div className="mb-4 rounded-lg border border-dashed border-line px-4 py-5 text-sm text-muted">
339 <p className="font-medium text-fg">Ask about your work</p>
340 <p className="mt-1">
341 g1t tells the model which page you are on{where ? `, here ${where}` : ""}, and the issue or pull request you are looking at, so you can ask
342 about it directly.
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.343 </p>
Merge branch 'ask-g1t'344 </div>
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.345 );
346}
347
Merge branch 'ask-g1t'348function Question({ turn }: { turn: Turn }) {
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.349 return (
Merge branch 'ask-g1t'350 <div className="ml-auto w-fit max-w-[85%] rounded-lg bg-raised px-3 py-2 text-[0.9375rem] leading-[1.55] wrap-break-word whitespace-pre-wrap text-fg">
351 {turn.content}
352 </div>
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.353 );
354}
355
Merge branch 'ask-g1t'356function Answer({ turn, context }: { turn: Turn; context: TextContext }) {
357 const waiting = turn.status === "streaming" && !turn.content;
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.358 return (
Merge branch 'ask-g1t'359 <div className="space-y-2">
360 {waiting ? (
361 <div className="flex items-center gap-2 text-sm text-muted" role="status">
362 <MarkGlyph className="h-3 animate-pulse motion-reduce:animate-none" />
363 Thinking…
364 </div>
365 ) : (
366 turn.content && <MessageText body={turn.content} context={context} />
367 )}
368 {turn.status === "error" && (
369 <Alert tone="danger" className="flex items-start gap-2">
370 <CircleAlert size={14} className="mt-0.5 shrink-0" />
371 <span>{turn.error}</span>
372 </Alert>
373 )}
374 <div className="flex flex-wrap items-center gap-1.5 text-xs text-faint">
375 {turn.provider && (
376 <Badge className="px-1.5 py-0 font-normal">
377 {turn.provider} · <span className="font-mono">{turn.model}</span>
378 </Badge>
379 )}
380 {turn.status === "stopped" && <span>Stopped</span>}
381 {turn.status === "done" && cutOff(turn) && <span>Cut off at the length limit</span>}
382 </div>
383 </div>
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.384 );
385}

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