Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

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.

syntaqxcommitted Parent2a290acBrowse files
6 files+53−70/6 viewed
+2−1
1111 import { Message, MessageContent } from "./ai-elements/message";
1212 import type { ResponseContext } from "./ai-elements/response";
1313 import { Suggestion, Suggestions } from "./ai-elements/suggestion";
14−import { G1tAnswer } from "./chat/answer";
14+import { AskedFrom, G1tAnswer } from "./chat/answer";
1515 import { useChatLive } from "./chat/live";
1616 import { MarkGlyph } from "./logo";
1717 import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
398398 ) : (
399399 <Message key={m.id} from="user">
400400 <MessageContent className={m.pending && !m.failed ? "opacity-70" : undefined}>{m.body}</MessageContent>
401+ <AskedFrom page={m.page} />
401402 {m.failed && <span className="text-xs text-danger">Not sent. Ask again.</span>}
402403 </Message>
403404 ),
+18−3
55 * as they stream, then the answer with who wrote it and what can be done
66 * with it: copy it, or have g1t answer again.
77 */
8−import { Check, CircleAlert, Copy, RefreshCw, Settings2 } from "lucide-react";
8+import { Check, CircleAlert, Copy, CornerDownRight, RefreshCw, Settings2 } from "lucide-react";
99 import { type ReactNode, useState } from "react";
1010
11−import type { ChatMessage } from "@g1t/contracts";
11+import type { AskPage, ChatMessage } from "@g1t/contracts";
1212
13−import { answerLabel, answering, cutOff } from "../../lib/g1t-chat";
13+import { answerLabel, answering, askedFrom, cutOff } from "../../lib/g1t-chat";
1414 import { AskTrail } from "../ask-steps";
1515 import { Loader } from "../ai-elements/loader";
1616 import { MessageAction, MessageActions } from "../ai-elements/message";
110110 </div>
111111 );
112112 }
113+
114+/**
115+ * Under a question to @g1t asked on a page: which page, so "this pull
116+ * request" reads as the one it was. g1t was told the same.
117+ */
118+export function AskedFrom({ page }: { page: AskPage | null | undefined }) {
119+ const from = askedFrom(page);
120+ if (!from) return null;
121+ return (
122+ <a href={from.href} className="mt-0.5 inline-flex max-w-full items-center gap-1 text-xs text-faint hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent">
123+ <CornerDownRight size={12} aria-hidden="true" className="shrink-0" />
124+ <span className="truncate">Asked on {from.label}</span>
125+ </a>
126+ );
127+}
+2−1
4747 import { BottomSheet, SheetRow, useBack, useSwipeBack } from "../mobile";
4848 import { MessageText, type TextContext } from "./text";
4949 // @g1t's answers and its direct message (lib/g1t-chat.ts, components/chat/answer.tsx).
50−import { ConversationMark, G1tAnswer } from "./answer";
50+import { AskedFrom, ConversationMark, G1tAnswer } from "./answer";
5151 import type { ResponseContext } from "../ai-elements/response";
5252 import { Suggestion, Suggestions } from "../ai-elements/suggestion";
5353 import { anyAnswering, applyLive, fromG1t, suggestions } from "../../lib/g1t-chat";
13341334 ) : (
13351335 message.body && <MessageText body={message.body} context={context} />
13361336 )}
1337+ {!editor && !answer && message.page && <AskedFrom page={message.page} />}
13371338 {!editor && !answer && message.body && !message.pending && <FolioUnfurls slug={slug} body={message.body} />}
13381339 {message.card && (
13391340 <CardBox card={message.card} slug={slug} code={code} />
+11−1
33
44 import type { ChatLiveEvent, ChatMessage } from "@g1t/contracts";
55
6−import { answerLabel, anyAnswering, applyLive, cutOff, fromG1t, refTarget, suggestions } from "./g1t-chat.ts";
6+import { askedFrom, answerLabel, anyAnswering, applyLive, cutOff, fromG1t, refTarget, suggestions } from "./g1t-chat.ts";
77
88 const g1t = { kind: "system" as const, id: "g1t", name: "g1t", display_name: "g1t", avatar: null, role: null };
99 const ana = { kind: "user" as const, id: "u1", name: "ana", display_name: "Ana", avatar: null, role: null };
7676 assert.equal(refTarget("//evil.example/x"), null);
7777 assert.equal(refTarget("javascript:alert(1)"), null);
7878 });
79+
80+test("a question asked on a page says which, and one written in Chat says nothing", () => {
81+ const repo = { namespace: "flagon-io", name: "g1t" };
82+ assert.deepEqual(askedFrom({ path: "/flagon-io/g1t/pull/8", repo, pull: 8 }), { label: "pull request #8 in flagon-io/g1t", href: "/flagon-io/g1t/pull/8" });
83+ assert.deepEqual(askedFrom({ repo, issue: 21 }), { label: "issue #21 in flagon-io/g1t", href: "/flagon-io/g1t/issues/21" });
84+ assert.equal(askedFrom({ path: "/flagon-io/g1t", repo })?.label, "flagon-io/g1t");
85+ assert.equal(askedFrom({ path: "/flagon-io/-/chat/dm/chn_1" }), null);
86+ assert.equal(askedFrom({ time_zone: "America/Denver" }), null);
87+ assert.equal(askedFrom(null), null);
88+});
+15−0
7373 return ["What shipped today?", "What needs my attention?", "Summarize what my team is working on", "How do I set up a deployment?"];
7474 }
7575
76+/**
77+ * Where a question to @g1t was asked from, when it was asked on a page
78+ * (the top bar's Ask on a pull request, an issue or a repository): what to
79+ * call it and where it leads. Null for a question written in Chat.
80+ */
81+export function askedFrom(page: AskPage | null | undefined): { label: string; href: string } | null {
82+ if (!page) return null;
83+ const repo = page.repo ? `${page.repo.namespace}/${page.repo.name}` : null;
84+ if (repo && page.pull) return { label: `pull request #${page.pull} in ${repo}`, href: page.path ?? `/${repo}/pull/${page.pull}` };
85+ if (repo && page.issue) return { label: `issue #${page.issue} in ${repo}`, href: page.path ?? `/${repo}/issues/${page.issue}` };
86+ if (repo) return { label: repo, href: page.path ?? `/${repo}` };
87+ if (page.path && !/\/-\/chat(\/|$)/.test(page.path)) return { label: page.path, href: page.path };
88+ return null;
89+}
90+
7691 /** A link g1t knows how to show as a chip: an issue or pull request, a commit, or another page of the site. */
7792 export type RefTarget =
7893 | { kind: "issue"; repo: string; number: number; href: string }
+5−1
182182 return found;
183183 }
184184
185+/** What every question the bench posts starts with. */
186+export const TEST_PREFIX = "[TEST] ";
187+
185188 /** A chat API call on the site as the user: the `{ ok, value | error }` it answers. */
186189 async function chatApi(workspace, body) {
187190 const response = await site(`/${workspace}/-/chat/api`, {
239242 for (const [index, question] of questions.entries()) {
240243 const last = index === questions.length - 1;
241244 const asked = performance.now();
242− const posted = await chatApi(workspace, { intent: "post", channel_id: channel, body: question.content, page: last ? (page ?? null) : null });
245+ // Marked, so the person whose direct message it is can tell the bench's questions from their own.
246+ const posted = await chatApi(workspace, { intent: "post", channel_id: channel, body: `${TEST_PREFIX}${question.content}`, page: last ? (page ?? null) : null });
243247 result.status = posted.ok ? 200 : 400;
244248 if (!posted.ok) throw new Error(posted.error?.message ?? "could not post");
245249 const mine = posted.value.id;