flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

Commit

Mission control drops the composer for now

syntaqxcommitted Parent7a21a23Browse files
1 file+4−1840/1 viewed
+4−184
66 Box,
77 CircleDot,
88 Coins,
9− Eye,
10− FolderPlus,
119 GitPullRequest,
1210 Hand,
1311 Lock,
14− Map as MapIcon,
1512 Plus,
1613 Radio,
17− Send,
1814 Settings,
1915 Sparkles,
2016 Users,
2117 } from "lucide-react";
2218 import { env } from "cloudflare:workers";
23−import { type ReactNode, useEffect, useRef, useState } from "react";
24−import { Form, Link, data, redirect, useNavigation, useRouteLoaderData } from "react-router";
19+import { type ReactNode, useEffect, useState } from "react";
20+import { Link, data, useRouteLoaderData } from "react-router";
2521
2622 import {
2723 type AgentRun,
6258 nextSeen,
6359 rankNeeds,
6460 readCookie,
65− splitRequest,
6661 stuckMinutes,
6762 } from "../lib/mission";
6863 import { Landing } from "../components/landing";
7469 import { ActivityFeed, Meter, NeedsList, Panel, PulseTile, Quiet, Unavailable, percent } from "../components/mission";
7570 import { Avatar, CopyLine, TimeAgo } from "../components/ui";
7671 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select";
77−import { Switch } from "../components/ui/switch";
7872 import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs";
7973 import { IssueIcon, PullIcon } from "../components/work";
8074 import {
8781 repos as reposApi,
8882 work,
8983 } from "../lib/services.server";
90−import { assertSameOrigin, getViewer, requireUser, roleIn } from "../lib/session.server";
84+import { getViewer } from "../lib/session.server";
9185
9286 /** The viewer's time zone, set by the page itself, so the greeting fits their day. */
9387 const TZ_COOKIE = "g1t_tz";
502496 greeting: greetingFor(hourIn(now, tz)),
503497 seenBefore: seen.since,
504498 repos: repoList,
505− // Projects the composer can open an issue in: those of the viewer's workspaces.
506− targets: repoList
507− .filter((repo) => roleIn(viewer, repo.namespace) != null && !repo.forkOf)
508− .map((repo) => ({ namespace: repo.namespace, name: repo.name, isPrivate: repo.isPrivate })),
509499 canRunAgents: models_ == null || models_.hosted || models_.own != null,
510500 trial: models_?.own == null ? (models_?.trial ?? null) : null,
511501 active: activeList.map(({ pull, lifecycle, repo }) => ({ pull, lifecycle, repo })),
531521 );
532522 }
533523
534−export async function action({ request, context }: Route.ActionArgs) {
535− assertSameOrigin(request);
536− const user = requireUser(context, request);
537− const form = await request.formData();
538− const [namespace, name] = String(form.get("project") ?? "").split("/");
539− if (!namespace || !name) return { error: "Choose the project this is for." };
540− const { title, body } = splitRequest(String(form.get("request") ?? ""));
541− if (!title) return { error: "Say what you want done first." };
542− const path = { namespace, name };
543− const opened = await work.openIssue(user, path, { title, body });
544− if (!opened.ok) return { error: opened.error.message };
545− const to = `/${namespace}/${name}/issues/${opened.value.number}`;
546− if (form.get("assign") === "on") {
547− // The issue exists either way; its page shows the agent's pull request,
548− // or, if none could start, how to put one on it.
549− await env.RUNNER.run(user, path, opened.value.number).catch(() => null);
550− }
551− throw redirect(to);
552−}
553−
554524 type Loaded = Extract<Route.ComponentProps["loaderData"], { signedIn: true }>;
555525
556526 // --- Pieces ---------------------------------------------------------------------
557−
558−function Composer({
559− targets,
560− canRunAgents,
561− workspace,
562− onReview,
563−}: {
564− targets: Loaded["targets"];
565− canRunAgents: boolean;
566− workspace: string | null;
567− onReview: () => void;
568−}) {
569− const navigation = useNavigation();
570− const busy = navigation.state === "submitting" && navigation.formAction === "/?index";
571− const [project, setProject] = useState(() => (targets[0] ? `${targets[0].namespace}/${targets[0].name}` : ""));
572− const [assign, setAssign] = useState(canRunAgents);
573− const text = useRef<HTMLTextAreaElement>(null);
574− const base = project ? `/${project}` : null;
575− const pill =
576− "inline-flex items-center gap-1.5 rounded-full border border-line bg-bg px-3 py-1.5 text-xs font-medium text-fg/80 transition-colors hover:border-line-strong hover:bg-raised hover:text-fg";
577− if (targets.length === 0) {
578− return (
579− <div className="rounded-2xl border border-line bg-surface p-5">
580− <p className="font-medium">Add a project to hand work to agents</p>
581− <p className="mt-1 text-sm text-muted">
582− Create a repository or import one by its address. Then describe what you want done here, and an agent starts on it.
583− </p>
584− <Link to={workspace ? `/new?workspace=${workspace}` : "/new"} className={`${pill} mt-4`}>
585− <FolderPlus size={13} /> New project
586− </Link>
587− </div>
588− );
589− }
590− return (
591− <div>
592− <Form
593− method="post"
594− action="/?index"
595− className="rounded-2xl border border-line bg-surface shadow-[0_1px_0_0_rgb(255_255_255/0.03)_inset] transition-colors focus-within:border-accent-dim/70"
596− >
597− <label htmlFor="composer" className="sr-only">
598− Describe what you want done
599− </label>
600− <textarea
601− id="composer"
602− ref={text}
603− name="request"
604− required
605− rows={3}
606− maxLength={20000}
607− autoComplete="off"
608− data-1p-ignore
609− placeholder="Describe what you want done… The first line becomes the issue's title."
610− onKeyDown={(event) => {
611− if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) event.currentTarget.form?.requestSubmit();
612− }}
613− className="block w-full resize-y rounded-t-2xl bg-transparent px-4 pt-4 pb-2 text-[0.9375rem] leading-6 outline-none placeholder:text-faint"
614− />
615− <div className="flex flex-wrap items-center gap-2 border-t border-line/70 px-3 py-2.5">
616− <Select name="project" value={project} onValueChange={setProject}>
617− <SelectTrigger size="sm" className="w-auto max-w-60" aria-label="Project">
618− <SelectValue placeholder="Project" />
619− </SelectTrigger>
620− <SelectContent>
621− {targets.map((target) => (
622− <SelectItem
623− key={`${target.namespace}/${target.name}`}
624− value={`${target.namespace}/${target.name}`}
625− icon={<Avatar name={target.name} size={16} square />}
626− description={target.namespace}
627− >
628− {target.name}
629− </SelectItem>
630− ))}
631− </SelectContent>
632− </Select>
633− <label
634− className={`inline-flex h-8 items-center gap-2 rounded-md px-2 text-xs ${canRunAgents ? "text-muted" : "text-faint"}`}
635− title={canRunAgents ? "Open the issue and start g1t-agent on it" : "Connect a model under Integrations to run agents"}
636− >
637− <Switch checked={assign} onCheckedChange={setAssign} disabled={!canRunAgents} aria-label="Hand to an agent" />
638− <Bot size={13} className={assign ? "text-merged" : ""} />
639− Hand to g1t-agent
640− </label>
641− {assign && <input type="hidden" name="assign" value="on" />}
642− <span className="grow" />
643− <span className="hidden text-[0.6875rem] text-faint sm:inline">Ctrl+Enter</span>
644− <button
645− type="submit"
646− disabled={busy}
647− className="inline-flex h-8 items-center gap-1.5 rounded-md bg-fg px-3 text-sm font-medium text-bg transition-colors hover:bg-white disabled:opacity-50"
648− >
649− <Send size={13} />
650− {busy ? (assign ? "Starting…" : "Opening…") : assign ? "Start" : "Open issue"}
651− </button>
652− </div>
653− </Form>
654− <div className="mt-3 flex flex-wrap gap-2">
655− {base && (
656− <Link to={`${base}/issues/new`} className={pill}>
657− <CircleDot size={13} /> New issue
658− </Link>
659− )}
660− <button
661− type="button"
662− className={pill}
663− disabled={!canRunAgents}
664− onClick={() => {
665− setAssign(true);
666− text.current?.focus();
667− }}
668− >
669− <Sparkles size={13} className="text-merged" /> Hand to an agent
670− </button>
671− {base && (
672− <Link to={`${base}/plans`} className={pill}>
673− <MapIcon size={13} /> Plan an outcome
674− </Link>
675− )}
676− <button type="button" className={pill} onClick={onReview}>
677− <Eye size={13} /> Review queue
678− </button>
679− <Link to={workspace ? `/new?workspace=${workspace}` : "/new"} className={pill}>
680− <FolderPlus size={13} /> New project
681− </Link>
682− </div>
683− </div>
684− );
685−}
686527
687528 const TONE = { fg: "text-fg", warn: "text-warn", danger: "text-danger", accent: "text-accent" };
688529
1151992 },
1152993 ];
1153994 const starting = steps.some((step) => !step.done);
1154− const showReview = () => {
1155− setPullsTab("review");
1156− document.getElementById("your-pulls")?.scrollIntoView({ behavior: "smooth", block: "start" });
1157− };
1158995 const counts = (n: number) => (n > 0 ? <span className="ml-1 tabular-nums text-faint">{n}</span> : null);
1159996
1160997 return (
11651002 {greeting}, {loaded.name}
11661003 </h1>
11671004 <Digest parts={loaded.digest} seenBefore={loaded.seenBefore} />
1168− <div className="mt-5">
1169− <Composer targets={loaded.targets} canRunAgents={canRunAgents} workspace={workspace} onReview={showReview} />
1170− </div>
11711005 </header>
11721006
11731007 {starting && <GetStarted steps={steps} />}
11901024 {!loaded.runsLoaded && workspace ? (
11911025 <Unavailable what="Agent runs" />
11921026 ) : live.length === 0 ? (
1193− <Quiet
1194− action={
1195− canRunAgents && loaded.targets.length > 0 ? (
1196− <button
1197− type="button"
1198− onClick={() => document.getElementById("composer")?.focus()}
1199− className="inline-flex items-center gap-1.5 rounded-md bg-fg px-3 py-1.5 text-xs font-medium text-bg hover:bg-white"
1200− >
1201− <Sparkles size={12} /> Hand something to an agent
1202− </button>
1203− ) : null
1204− }
1205− >
1206− No agent is at work right now. Describe a change above and one starts on it in seconds.
1207− </Quiet>
1027+ <Quiet>No agent is at work right now. Assign an issue to g1t-agent and one starts on it in seconds.</Quiet>
12081028 ) : (
12091029 <ul className="space-y-3">
12101030 {live.map((run) => (