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