Skip to content
214 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.

Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { ChatMessage, FeedNotification } from "@g1t/contracts";
5import { DEFAULT_SOUND_SETTINGS, type SoundSettings } from "@g1t/contracts/sounds";
6
7import {
8 type Arrival,
9 SAME_MESSAGE_MS,
10 SOUND_GAP_MS,
11 type SoundContext,
12 SoundGate,
13 arrivalKey,
14 cueFor,
15 cueForNotificationKind,
16 lookingAt,
17 mentionsMe,
18 wantsDesktopToast,
19} from "./chat-sounds.ts";
20
21const ana = { kind: "user" as const, id: "u1", name: "ana", display_name: "Ana", avatar: null, role: null };
22const bea = { kind: "user" as const, id: "u2", name: "bea", display_name: "Bea", avatar: null, role: null };
23const margo = { kind: "agent" as const, id: "a1", name: "margo", display_name: "Margo", avatar: null, role: "Reviews pull requests" };
24
25function message(over: Partial<ChatMessage> = {}): ChatMessage {
26 return {
27 id: over.id ?? "msg_1",
28 channel_id: "chn_general",
29 author: bea,
30 kind: "text",
31 body: "hello there",
32 card: null,
33 thread_root: null,
34 reply_count: 0,
35 last_reply_at: null,
36 created_at: "2026-10-10T10:00:00.000Z",
37 edited_at: null,
38 deleted_at: null,
39 ...over,
40 };
41}
42
43function notification(over: Partial<FeedNotification> = {}): FeedNotification {
44 return {
45 id: "msg_9",
46 kind: "dm",
47 workspace: "acme",
48 title: "Bea",
49 body: "hi",
50 href: "/acme/-/chat/dm/chn_dm",
51 actor: { kind: "user", id: "u2", name: "Bea" },
52 channel_id: "chn_dm",
53 created_at: "2026-10-10T10:00:00.000Z",
54 ...over,
55 };
56}
57
58/** Ana, in #general, window in front: the quiet case every rule starts from. */
59function ctx(over: Partial<SoundContext> = {}, settings: Partial<SoundSettings> = {}): SoundContext {
60 return {
61 me: { id: "u1", username: "ana" },
62 focus: { visible: true, focused: true, viewingChannel: "chn_general" },
63 muted: new Set(),
64 dnd: false,
65 settings: { ...DEFAULT_SOUND_SETTINGS, ...settings, sound_cues: { ...DEFAULT_SOUND_SETTINGS.sound_cues, ...settings.sound_cues } },
66 ...over,
67 };
68}
69
70const chat = (m: ChatMessage, channelKind: "channel" | "dm" = "channel"): Arrival => ({ source: "chat", message: m, channelKind });
71const feed = (n: FeedNotification): Arrival => ({ source: "feed", notification: n });
72
73test("a message in the conversation on screen, in a window in front, is silent", () => {
74 assert.equal(cueFor(chat(message()), ctx()), null);
75 assert.ok(lookingAt("chn_general", ctx().focus));
76});
77
78test("the same message plays `message` once the window is not in front, or the tab is hidden", () => {
79 assert.equal(cueFor(chat(message()), ctx({ focus: { visible: true, focused: false, viewingChannel: "chn_general" } })), "message");
80 assert.equal(cueFor(chat(message()), ctx({ focus: { visible: false, focused: false, viewingChannel: "chn_general" } })), "message");
81});
82
83test("a message in another conversation you have open plays `message`", () => {
84 assert.equal(cueFor(chat(message({ channel_id: "chn_other" })), ctx()), "message");
85 assert.equal(cueFor(feed(notification({ kind: "thread_reply", channel_id: "chn_other" })), ctx()), "message");
86});
87
88test("a direct message not from you plays `direct`; one from you plays nothing", () => {
89 const dm = message({ channel_id: "chn_dm" });
90 assert.equal(cueFor(chat(dm, "dm"), ctx()), "direct");
91 assert.equal(cueFor(chat({ ...dm, author: ana }, "dm"), ctx()), null);
92 assert.equal(cueFor(feed(notification()), ctx()), "direct");
93 assert.equal(cueFor(feed(notification({ actor: { kind: "user", id: "u1", name: "Ana" } })), ctx()), null);
94});
95
96test("a mention of you plays `mention`, over `direct` and `message`", () => {
97 assert.equal(cueFor(chat(message({ channel_id: "chn_other", body: "hey @ana look" })), ctx()), "mention");
98 assert.equal(cueFor(chat(message({ channel_id: "chn_dm", body: "@ana?" }), "dm"), ctx()), "mention");
99 assert.equal(cueFor(feed(notification({ kind: "mention", channel_id: "chn_other" })), ctx()), "mention");
100 // Looking at it, in front: still silent.
101 assert.equal(cueFor(chat(message({ body: "@ana" })), ctx()), null);
102});
103
104test("@you is a whole handle, outside code", () => {
105 assert.ok(mentionsMe("thanks @ana", "ana"));
106 assert.ok(mentionsMe("@Ana: see this", "ana"));
107 assert.ok(mentionsMe("(@ana)", "ana"));
108 assert.ok(!mentionsMe("@ana-marie did it", "ana"));
109 assert.ok(!mentionsMe("@anatole", "ana"));
110 assert.ok(!mentionsMe("mail me@ana.example", "ana"));
111 assert.ok(!mentionsMe("run `@ana` in code", "ana"));
112 assert.ok(!mentionsMe("```\n@ana\n```", "ana"));
113 assert.ok(!mentionsMe("@ana", ""));
114});
115
116test("your own messages never sound, whichever way they arrive", () => {
117 const unfocused = ctx({ focus: { visible: true, focused: false, viewingChannel: "chn_general" } });
118 assert.equal(cueFor(chat(message({ author: ana })), unfocused), null);
119 assert.equal(cueFor(chat(message({ author: ana, body: "@ana" })), unfocused), null);
120 assert.equal(cueFor(feed(notification({ kind: "mention", actor: { kind: "user", id: "u1", name: "Ana" } })), unfocused), null);
121});
122
123test("a muted conversation is silent, even for a mention or a DM", () => {
124 const muted = ctx({ muted: new Set(["chn_other", "chn_dm"]) });
125 assert.equal(cueFor(chat(message({ channel_id: "chn_other", body: "@ana" })), muted), null);
126 assert.equal(cueFor(chat(message({ channel_id: "chn_dm" }), "dm"), muted), null);
127 assert.equal(cueFor(feed(notification()), muted), null);
128 assert.equal(cueFor(chat(message({ channel_id: "chn_third" })), muted), "message");
129});
130
131test("Do not disturb silences everything", () => {
132 const quiet = ctx({ dnd: true, focus: { visible: false, focused: false, viewingChannel: null } });
133 assert.equal(cueFor(chat(message({ body: "@ana" })), quiet), null);
134 assert.equal(cueFor(chat(message({ channel_id: "chn_dm" }), "dm"), quiet), null);
135 assert.equal(cueFor(feed(notification({ kind: "agent_waiting" })), quiet), null);
136});
137
138test("sounds off, or one cue off, is silent for that", () => {
139 const away = { visible: false, focused: false, viewingChannel: null };
140 assert.equal(cueFor(chat(message()), ctx({ focus: away }, { sounds_enabled: false })), null);
141 assert.equal(cueFor(chat(message()), ctx({ focus: away }, { sound_cues: { message: false } })), null);
142 assert.equal(cueFor(chat(message({ body: "@ana" })), ctx({ focus: away }, { sound_cues: { message: false } })), "mention");
143 assert.equal(cueFor(chat(message({ body: "@ana" })), ctx({ focus: away }, { sound_cues: { mention: false } })), null);
144});
145
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.146test("an old agent's message counts like a person's", () => {
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.147 const away = ctx({ focus: { visible: true, focused: false, viewingChannel: "chn_general" } });
148 assert.equal(cueFor(chat(message({ author: margo })), away), "message");
149 assert.equal(cueFor(chat(message({ author: margo, channel_id: "chn_dm" }), "dm"), away), "direct");
150 assert.equal(cueFor(chat(message({ author: margo, body: "@ana done?" })), away), "mention");
151 const card = message({
152 author: margo,
153 kind: "card",
154 body: "",
155 card: { kind: "session", title: "Fix the flaky test", detail: "2 files changed", state: "Done", href: "/acme/web/pull/4" },
156 });
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.157 assert.equal(cueFor(chat(card), away), "message");
158 // An old "waiting on you" notification makes no sound of its own.
159 assert.equal(cueFor(feed(notification({ kind: "agent_waiting", actor: { kind: "agent", id: "a1", name: "Margo" } })), away), null);
Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules.160});
161
162test("a deleted message, and an inbox notification, make no sound", () => {
163 const away = ctx({ focus: { visible: false, focused: false, viewingChannel: null } });
164 assert.equal(cueFor(chat(message({ deleted_at: "2026-10-10T10:01:00.000Z" })), away), null);
165 assert.equal(cueFor(feed(notification({ kind: "inbox", channel_id: null })), away), null);
166 assert.equal(cueForNotificationKind("inbox"), null);
167 assert.equal(cueForNotificationKind("approval"), "mention");
168});
169
170test("sounds come at most one every 1.5 s, and one message sounds once however it arrives", () => {
171 const gate = new SoundGate();
172 const t0 = 1_000_000;
173 assert.ok(gate.allow("msg:1", t0));
174 assert.ok(!gate.allow("msg:2", t0 + 200), "a burst is one sound");
175 assert.ok(!gate.allow("msg:3", t0 + SOUND_GAP_MS - 1));
176 assert.ok(gate.allow("msg:4", t0 + SOUND_GAP_MS));
177 // The same message, from the conversation's socket and then the feed.
178 assert.ok(!gate.allow("msg:4", t0 + SOUND_GAP_MS * 3));
179 assert.ok(!gate.allow("msg:4", t0 + SOUND_GAP_MS + SAME_MESSAGE_MS - 1));
180 assert.ok(gate.allow("msg:4", t0 + SOUND_GAP_MS + SAME_MESSAGE_MS + 1), "long after, it is a new message to the gate");
181 // A key refused by the gap is still remembered, so the feed's copy a moment later stays quiet.
182 const again = new SoundGate();
183 assert.ok(again.allow("msg:a", t0));
184 assert.ok(!again.allow("msg:b", t0 + 100));
185 assert.ok(!again.allow("msg:b", t0 + SOUND_GAP_MS + 100), "msg:b was heard of, if not sounded");
186 // No key: only the gap applies.
187 assert.ok(again.allow(null, t0 + SOUND_GAP_MS * 2));
188 assert.ok(!again.allow(null, t0 + SOUND_GAP_MS * 2 + 10));
189});
190
191test("an arrival's key is its message id, the same over the socket and the feed", () => {
192 assert.equal(arrivalKey(chat(message({ id: "msg_7" }))), "msg:msg_7");
193 assert.equal(arrivalKey(feed(notification({ id: "msg_7" }))), "msg:msg_7");
194});
195
196test("a desktop notification shows only when wanted, allowed, out of front, and not already pushed", () => {
197 const base = {
198 notification: { kind: "dm" as const },
199 toast: true,
200 ctx: ctx({ focus: { visible: true, focused: false, viewingChannel: null } }, { desktop_toasts: true }),
201 permission: "granted" as const,
202 pushOn: false,
203 };
204 assert.ok(wantsDesktopToast(base));
205 assert.ok(!wantsDesktopToast({ ...base, ctx: ctx({ focus: base.ctx.focus }) }), "off unless turned on");
206 assert.ok(!wantsDesktopToast({ ...base, permission: "default" }));
207 assert.ok(!wantsDesktopToast({ ...base, permission: "denied" }));
208 assert.ok(!wantsDesktopToast({ ...base, pushOn: true }), "the push shows it instead");
209 assert.ok(!wantsDesktopToast({ ...base, toast: false }), "what the feed would not toast");
210 assert.ok(!wantsDesktopToast({ ...base, ctx: { ...base.ctx, dnd: true } }));
211 assert.ok(!wantsDesktopToast({ ...base, ctx: { ...base.ctx, focus: { visible: true, focused: true, viewingChannel: null } } }), "in front: the toast is enough");
212 assert.ok(wantsDesktopToast({ ...base, ctx: { ...base.ctx, focus: { visible: false, focused: false, viewingChannel: null } } }));
213 assert.ok(!wantsDesktopToast({ ...base, notification: { kind: "inbox" } }));
214});