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. | 1 | import { ArrowUp, MessageSquare, RotateCcw, Settings2, Square } from "lucide-react"; |
| 2 | import { type FormEvent, type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; | |
| Merge branch 'ask-g1t' | 3 | import { 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. | 5 | import { 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. | 7 | import { askPage, askWhere } from "../lib/ask"; |
| 8 | import { anyAnswering, applyLive, fromG1t, suggestions } from "../lib/g1t-chat"; | |
| 9 | import { Conversation, ConversationContent, ConversationEmptyState, ConversationScrollButton } from "./ai-elements/conversation"; | |
| 10 | import { Message, MessageContent } from "./ai-elements/message"; | |
| 11 | import type { ResponseContext } from "./ai-elements/response"; | |
| 12 | import { Suggestion, Suggestions } from "./ai-elements/suggestion"; | |
| 13 | import { G1tAnswer } from "./chat/answer"; | |
| 14 | import { 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. | 15 | import { MarkGlyph } from "./logo"; |
| Merge branch 'ask-g1t' | 16 | import { 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. | 17 | import { Button } from "./ui/button"; |
| 18 | import { 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. | 19 | import { Kbd } from "./ui/kbd"; |
| Merge branch 'main' into ask-integration | 20 | import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "./ui/sheet"; |
| Merge branch 'ask-g1t' | 21 | import { 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. | 22 | import { 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 | |
| 38 | type Setup = AskSetup & { owner: boolean }; | |
| 39 | type 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. | 40 | type 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. | 42 | const 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. | 44 | function 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. | 52 | function 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. | 60 | async 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. */ | |
| 70 | function 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. | 79 | function 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. | 184 | type 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. */ | |
| 187 | export 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. */ |
| 218 | function 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 | ||
| 223 | function 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'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 | ||
| 462 | function 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'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 ? ( | |
| Merge branch 'main' into ask-integration | 471 | // Following it closes the panel, so the page it opens is not behind it. |
| 472 | <SheetClose asChild> | |
| 473 | <Link to={`/${slug}/-/integrations/models#ask`} className="text-accent hover:underline"> | |
| 474 | Connect a provider and choose it for Ask | |
| 475 | </Link> | |
| 476 | </SheetClose> | |
| Merge branch 'ask-g1t' | 477 | ) : ( |
| 478 | <>Ask an owner of {slug} to connect one and choose it for Ask.</> | |
| 479 | )} | |
| 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 | </p> |
| Merge branch 'ask-g1t' | 481 | </AlertDescription> |
| 482 | </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. | 483 | ); |
| 484 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.