flagon-io/g1t

public

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/apps/web/app/routes/repo/agents-live.ts

61 lines2,765 bytesCodeBlame
1/**
2 * A project's agent runs as JSON, for the parts of other pages that show
3 * them live (a pull request's Agent panel, the pull request list), and
4 * the one place runs are stopped and messaged from.
5 */
6import { env } from "cloudflare:workers";
7import { data } from "react-router";
8
9import { RUN_KINDS, type RunKind, takesMessages } from "@g1t/contracts";
10
11import type { Route } from "./+types/agents-live";
12import { agents, work } from "../../lib/services.server";
13import { assertSameOrigin, getViewer, roleIn } from "../../lib/session.server";
14
15export async function loader({ params, context, request }: Route.LoaderArgs) {
16 const viewer = getViewer(context);
17 const query = new URL(request.url).searchParams;
18 const number = Number(query.get("number"));
19 const kind = query.get("kind");
20 const found = await agents.listRuns(viewer, {
21 repo: { namespace: params.owner, name: params.repo },
22 active: query.get("active") === "1",
23 number: number > 0 ? number : undefined,
24 kind: kind && RUN_KINDS.includes(kind as RunKind) ? (kind as RunKind) : undefined,
25 limit: Math.min(Number(query.get("limit")) || 50, 200),
26 });
27 if (!found.ok) throw data({ error: found.error.message }, { status: 404 });
28 return Response.json(
29 { runs: found.value, member: roleIn(viewer, params.owner) != null },
30 { headers: { "cache-control": "no-store" } },
31 );
32}
33
34export async function action({ params, context, request }: Route.ActionArgs) {
35 assertSameOrigin(request);
36 const viewer = getViewer(context);
37 if (!viewer) return { ok: false, error: "Sign in first." };
38 const path = { namespace: params.owner, name: params.repo };
39 const form = await request.formData();
40 const intent = String(form.get("intent") ?? "");
41 if (intent === "stop") {
42 const stopped = await env.RUNNER.stopRun(viewer, path, String(form.get("run") ?? ""));
43 return stopped.ok ? { ok: true, notice: "Stopped." } : { ok: false, error: stopped.error.message };
44 }
45 if (intent === "message") {
46 const number = Number(form.get("number"));
47 const sent = await work.messageAgent(viewer, path, number, String(form.get("body") ?? ""));
48 if (!sent.ok) return { ok: false, error: sent.error.message };
49 // Whether the agent reads it now, or its next run on the pull request does.
50 const active = await agents.listRuns(viewer, { repo: path, number, active: true, limit: 1 });
51 const run = active.ok ? active.value[0] : undefined;
52 return {
53 ok: true,
54 notice:
55 run && takesMessages(run.kind)
56 ? "Sent. The agent reads it at its next step."
57 : "Sent. This run does not read messages while it works, so the agent's next run on this pull request gets it.",
58 };
59 }
60 return { ok: false, error: "Unknown action." };
61}