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
110 linesCodeBlameRaw

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.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { AskSetup, ChatAnswer } from "@g1t/contracts";
5
6import { runAnswer, type AnswerDeps } from "./answer.ts";
7
8const ready: AskSetup = { connection_id: "int_1", connection_name: "Anthropic", provider: "anthropic", model: "m", problem: null };
9
10function ndjson(lines: object[], { split = false } = {}): Response {
11 const text = lines.map((line) => `${JSON.stringify(line)}\n`).join("");
12 const parts = split ? text.match(/[\s\S]{1,7}/g)! : [text];
13 return new Response(
14 new ReadableStream({
15 start(controller) {
16 for (const part of parts) controller.enqueue(new TextEncoder().encode(part));
17 controller.close();
18 },
19 }),
20 );
21}
22
23function harness(setup: AskSetup | null, answer: () => Response | Promise<Response>) {
24 const seen = { typing: 0, shown: [] as string[], finished: null as null | { body: string; answer: ChatAnswer }, asked: 0 };
25 let clock = 0;
26 const deps: AnswerDeps = {
27 slug: "acme",
28 setup: async () => setup,
29 ask: async () => {
30 seen.asked++;
31 return answer();
32 },
33 typing: () => void seen.typing++,
34 show: (body) => void seen.shown.push(body),
35 save: async () => {},
36 finish: async (body, answer) => void (seen.finished = { body, answer }),
37 now: () => (clock += 200),
38 };
39 return { deps, seen };
40}
41
42test("an answer streams into the message and ends as a normal one", async () => {
43 const { deps, seen } = harness(ready, () =>
44 ndjson(
45 [
46 { type: "meta", provider: "anthropic", provider_label: "Anthropic", model: "m" },
47 { type: "text", text: "Two pull " },
48 { type: "tool", name: "list_pulls" },
49 { type: "text", text: "requests merged." },
50 { type: "done", stop_reason: "end_turn" },
51 ],
52 { split: true },
53 ),
54 );
55 await runAnswer(deps, new AbortController().signal);
56 assert.equal(seen.typing, 1);
57 assert.ok(seen.shown.length > 0);
58 assert.equal(seen.finished!.body, "Two pull requests merged.");
59 assert.equal(seen.finished!.answer.state, "done");
60 assert.equal(seen.finished!.answer.model, "m");
61 assert.equal(seen.finished!.answer.steps!.length, 1);
62});
63
64test("with no provider chosen, g1t says so once, without asking", async () => {
65 const { deps, seen } = harness({ ...ready, connection_id: null, problem: "No AI provider is chosen for Ask in this workspace yet." }, () => ndjson([]));
66 await runAnswer(deps, new AbortController().signal);
67 assert.equal(seen.asked, 0);
68 assert.equal(seen.finished!.answer.setup, "missing");
69 assert.match(seen.finished!.body, /Integrations → Your AI providers → Ask g1t/);
70});
71
72test("a chosen provider that cannot answer is a short message, not silence", async () => {
73 const { deps, seen } = harness({ ...ready, problem: "The key for Anthropic cannot be read. Replace it." }, () => ndjson([]));
74 await runAnswer(deps, new AbortController().signal);
75 assert.equal(seen.finished!.answer.state, "error");
76 assert.equal(seen.finished!.body, "I couldn't answer that: The key for Anthropic cannot be read. Replace it.");
77});
78
79test("a refusal or an unreachable service ends as an error message", async () => {
80 const refused = harness(ready, () => ndjson([{ type: "error", message: "Write a question to ask." }]));
81 await runAnswer(refused.deps, new AbortController().signal);
82 assert.equal(refused.seen.finished!.body, "I couldn't answer that: Write a question to ask.");
83 const down = harness(ready, () => {
84 throw new Error("binding down");
85 });
86 await runAnswer(down.deps, new AbortController().signal);
87 assert.equal(down.seen.finished!.answer.state, "error");
88 assert.match(down.seen.finished!.body, /broke off/);
89});
90
91test("Stop keeps what came, marked stopped", async () => {
92 const stop = new AbortController();
93 let push!: (text: string) => void;
94 const { deps, seen } = harness(ready, () => {
95 const stream = new ReadableStream<Uint8Array>({
96 start(controller) {
97 push = (text) => controller.enqueue(new TextEncoder().encode(text));
98 },
99 });
100 return new Response(stream);
101 });
102 const running = runAnswer(deps, stop.signal);
103 await new Promise((resolve) => setTimeout(resolve, 10));
104 push(`${JSON.stringify({ type: "text", text: "Partly" })}\n`);
105 await new Promise((resolve) => setTimeout(resolve, 10));
106 stop.abort();
107 await running;
108 assert.equal(seen.finished!.answer.state, "stopped");
109 assert.equal(seen.finished!.body, "Partly");
110});