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
204 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.

Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { FeedNotification } from "@g1t/contracts";
5
6import { applyCounts, totals } from "./counts.ts";
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread7import { STALE_MS, anyFocused, cleanCard, cleanNotification, decide, levelFor, mergePreferences, pushActions, pushPayload, readPreferences, wants } from "./prefs.ts";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)8
9const NOW = 1_800_000_000_000;
10const dms = { level: "dms_mentions" as const, workspaces: {} };
11
12test("the default hears of what is said to you or waits on you, not everything", () => {
13 assert.deepEqual(readPreferences(null), dms);
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.14 for (const kind of ["dm", "mention", "thread_reply", "approval"] as const) assert.ok(wants("dms_mentions", kind), kind);
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)15 assert.ok(!wants("dms_mentions", "inbox"));
16 assert.ok(wants("all", "inbox"));
17 for (const kind of ["dm", "mention", "inbox"] as const) assert.ok(!wants("none", kind));
18});
19
20test("a workspace's own level wins over the general one", () => {
21 const prefs = mergePreferences(dms, { workspaces: { Acme: "none", side: "all" } });
22 assert.equal(levelFor(prefs, "acme"), "none");
23 assert.equal(levelFor(prefs, "side"), "all");
24 assert.equal(levelFor(prefs, "other"), "dms_mentions");
25});
26
27test("changes are checked: unknown levels ignored, null clears, the same as the general level is no override", () => {
28 let prefs = mergePreferences(dms, { level: "loud", workspaces: { a: "all", b: "bogus" } });
29 assert.deepEqual(prefs, { level: "dms_mentions", workspaces: { a: "all" } });
30 prefs = mergePreferences(prefs, { workspaces: { a: null } });
31 assert.deepEqual(prefs.workspaces, {});
32 prefs = mergePreferences(mergePreferences(dms, { workspaces: { a: "all" } }), { level: "all" });
33 assert.deepEqual(prefs, { level: "all", workspaces: {} });
34 assert.deepEqual(readPreferences("not json"), dms);
35 assert.deepEqual(mergePreferences(dms, "nonsense"), dms);
36});
37
38test("a tab counts as in front of you only while focused and recently heard from", () => {
39 assert.ok(anyFocused([{ focused: true, seen_at: NOW - 1000 }], NOW));
40 assert.ok(!anyFocused([{ focused: true, seen_at: NOW - STALE_MS - 1 }], NOW));
41 assert.ok(!anyFocused([{ focused: false, seen_at: NOW }], NOW));
42 assert.ok(!anyFocused([], NOW));
43});
44
45test("push only when no tab is focused, there is a browser to push to, and the level wants it", () => {
46 const dm = { kind: "dm" as const, workspace: "acme" };
47 const away = [{ focused: false, seen_at: NOW }];
48 const here = [{ focused: true, seen_at: NOW }];
49 assert.deepEqual(decide({ prefs: dms, notification: dm, tabs: away, subscriptions: 1, now: NOW }), { toast: true, push: true });
50 assert.deepEqual(decide({ prefs: dms, notification: dm, tabs: here, subscriptions: 1, now: NOW }), { toast: true, push: false });
51 assert.deepEqual(decide({ prefs: dms, notification: dm, tabs: [], subscriptions: 0, now: NOW }), { toast: true, push: false });
52 // No tab open at all: pushed.
53 assert.deepEqual(decide({ prefs: dms, notification: dm, tabs: [], subscriptions: 2, now: NOW }), { toast: true, push: true });
54 // A focused tab gone quiet (the laptop slept): pushed.
55 assert.equal(decide({ prefs: dms, notification: dm, tabs: [{ focused: true, seen_at: NOW - STALE_MS * 2 }], subscriptions: 1, now: NOW }).push, true);
56 const inbox = { kind: "inbox" as const, workspace: "acme" };
57 assert.deepEqual(decide({ prefs: dms, notification: inbox, tabs: away, subscriptions: 1, now: NOW }), { toast: false, push: false });
58 const muted = mergePreferences(dms, { workspaces: { acme: "none" } });
59 assert.deepEqual(decide({ prefs: muted, notification: dm, tabs: away, subscriptions: 1, now: NOW }), { toast: false, push: false });
60 // A test goes through whatever the preferences and focus.
61 assert.deepEqual(decide({ prefs: muted, notification: dm, tabs: here, subscriptions: 1, now: NOW, test: true }), { toast: true, push: true });
62});
63
64test("notifications are cleaned: kinds checked, links kept on the site, text trimmed", () => {
65 assert.equal(cleanNotification({ id: "x", kind: "shout", title: "t" }), null);
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.66 // Workspace agents are gone: what they once sent is no notification now.
67 assert.equal(cleanNotification({ id: "x", kind: "agent_waiting", title: "t" }), null);
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)68 assert.equal(cleanNotification({ kind: "dm", title: "t" }), null);
69 const n = cleanNotification({
70 id: "n1",
71 kind: "mention",
72 workspace: "Acme",
73 title: " Ana in #design ",
74 body: "x".repeat(1000),
75 href: "//evil.example/",
76 actor: { kind: "user", id: "usr_1", name: "Ana", avatar: "abc" },
77 channel_id: "chn_1",
78 })!;
79 assert.equal(n.workspace, "acme");
80 assert.equal(n.title, "Ana in #design");
81 assert.equal(n.body.length, 300);
82 assert.equal(n.href, "/");
83 assert.equal(n.actor.avatar, "abc");
84 assert.equal(n.actor.avatar_seed, null);
85 assert.equal(cleanNotification({ id: "n2", kind: "dm", title: "t", href: "/acme/-/chat/dm/chn_1", actor: { kind: "robot" } })!.actor.kind, "system");
86});
87
88test("a push is small and collapses by conversation", () => {
89 const n: FeedNotification = {
90 id: "msg_1",
91 kind: "dm",
92 workspace: "acme",
93 title: "Ana",
94 body: "y".repeat(400),
95 href: "/acme/-/chat/dm/chn_1",
96 actor: { kind: "user", id: "usr_1", name: "Ana" },
97 channel_id: "chn_1",
98 created_at: "2026-10-08T00:00:00Z",
99 };
100 const payload = pushPayload(n);
101 assert.equal(payload.tag, "chat:chn_1");
102 assert.equal(payload.body.length, 240);
103 assert.equal(payload.urgent, true);
104 assert.equal(pushPayload({ ...n, kind: "inbox", channel_id: null }).tag, "inbox:msg_1");
105});
106
107test("counts add up, replace on set, never go below zero, and keep mute", () => {
108 let c = applyCounts(null, { channel_id: "c", unread: 1, mentions: 0 });
109 c = applyCounts(c, { channel_id: "c", unread: 1, mentions: 1, muted: true });
110 assert.deepEqual(c, { channel_id: "c", unread: 2, mentions: 1, muted: true });
111 c = applyCounts(c, { channel_id: "c", unread: -5, mentions: -5 });
112 assert.deepEqual(c, { channel_id: "c", unread: 0, mentions: 0, muted: true });
113 c = applyCounts(c, { channel_id: "c", unread: 3, mentions: 0, set: true });
114 assert.deepEqual(c, { channel_id: "c", unread: 3, mentions: 0, muted: true });
115});
116
117test("totals leave muted unread out, as the rail does, but count their mentions", () => {
118 const t = totals(
119 "acme",
120 [
121 { channel_id: "a", unread: 3, mentions: 1, muted: false },
122 { channel_id: "b", unread: 5, mentions: 2, muted: true },
123 { channel_id: "c", unread: 0, mentions: 0, muted: false },
124 ],
125 4,
126 true,
127 );
128 assert.deepEqual(t, {
129 workspace: "acme",
130 chat_unread: 3,
131 chat_mentions: 3,
132 inbox_unread: 4,
133 per_channel: [
134 { channel_id: "a", unread: 3, mentions: 1 },
135 { channel_id: "b", unread: 5, mentions: 2 },
136 ],
137 complete: true,
138 });
139});
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread140
141const capped = {
142 channel_id: "chn_1",
143 message_id: "msg_card",
144 actions: [
145 { id: "approve", label: "Approve more", style: "primary", input: { kind: "money", label: "New cap", initial: "4.00" } },
146 { id: "stop", label: "Stop", style: "danger", confirm: "Stop this session and everything under it?" },
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.147 { id: "open", label: "Open", href: "/acme/web/pull/1" },
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread148 ],
149};
150
151test("a notification's card is checked: its place, its actions, links kept on the site", () => {
152 const card = cleanCard(capped)!;
153 assert.equal(card.message_id, "msg_card");
154 assert.deepEqual(
155 card.actions.map((a) => [a.id, a.style, a.input?.kind ?? null, a.href ?? null]),
156 [
157 ["approve", "primary", "money", null],
158 ["stop", "danger", null, null],
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.159 ["open", "default", null, "/acme/web/pull/1"],
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread160 ],
161 );
162 assert.equal(card.actions[1].confirm, "Stop this session and everything under it?");
163 assert.equal(cleanCard({ ...capped, actions: [{ id: "x", label: "X", href: "https://evil.example/" }] })!.actions[0].href, null);
164 assert.equal(cleanCard({ ...capped, message_id: "" }), null);
165 assert.equal(cleanCard({ ...capped, actions: [{ id: "", label: "" }] }), null);
166 assert.equal(cleanCard("nope"), null);
167 // Carried on the notification when it is one; left off otherwise.
168 const n = cleanNotification({ id: "approval:ses_1:2000000", kind: "approval", workspace: "acme", title: "g1t needs more budget", card: capped })!;
169 assert.equal(n.card?.actions.length, 3);
170 assert.equal("card" in cleanNotification({ id: "n", kind: "dm", title: "t" })!, false);
171});
172
173test("a push shows only the card's actions that need nothing typed or confirmed, two at most", () => {
174 const card = cleanCard(capped)!;
175 // Stop asks first in the app; a push can't, so it isn't offered there.
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.176 assert.deepEqual(pushActions(card), [{ id: "open", label: "Open", href: "/acme/web/pull/1" }]);
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread177 assert.deepEqual(pushActions(null), []);
178 const payload = pushPayload({
179 id: "approval:ses_1:2000000",
180 kind: "approval",
181 workspace: "acme",
182 title: "g1t needs more budget",
183 body: "",
Chat, artifacts and notifications no longer serve g1t's own agents. Direct messages and conversation invites take people only, and a space or folio is shared with people and teams. Chat stops waking agents, drops the RPCs agents posted, read, searched and handed off through, and stops answering buttons on agent cards; old cards keep their links. An agent's old messages still show, under its saved name or as "A deleted agent". Artifacts drops the folio and docs RPCs agents read and edited pages with, and an old agent member of a space can still be removed. Notifications drop "an agent is waiting on you". A deployment no longer trusts a pull request with secrets just because g1t's agent opened it, and the sign-up card no longer promises a team of agents.184 href: "/acme/web/pull/1",
Write a thread up in Docs from chat; cards' buttons in notifications; a session's card stays at the top of its thread185 actor: { kind: "agent", id: "a1", name: "g1t" },
186 channel_id: "chn_1",
187 card,
188 created_at: "2026-10-08T00:00:00Z",
189 });
190 assert.deepEqual(payload.card, { channel_id: "chn_1", message_id: "msg_card" });
191 assert.equal(payload.workspace, "acme");
192 // Its own notification: a later message in the conversation does not replace it.
193 assert.equal(payload.tag, "approval:approval:ses_1:2000000");
194});
The phone app: Chat in full with phone notifications, plus Code and Customize (#4)195
196test("a person with only the phone app is pushed to: the feed counts browsers and phones together", () => {
197 const dm = { kind: "dm" as const, workspace: "acme" };
198 const browsers = 0;
199 const phones = 1;
200 assert.deepEqual(decide({ prefs: dms, notification: dm, tabs: [], subscriptions: browsers + phones, now: NOW }), { toast: true, push: true });
201 // The same rules hold: not while a tab is in front of them, nor in Do Not Disturb.
202 assert.equal(decide({ prefs: dms, notification: dm, tabs: [{ focused: true, seen_at: NOW }], subscriptions: browsers + phones, now: NOW }).push, false);
203 assert.equal(decide({ prefs: dms, notification: dm, tabs: [], subscriptions: browsers + phones, now: NOW, dnd: true }).push, false);
204});

This file's history is long; its oldest lines are credited to the oldest commit read.