Skip to content
53 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)1/**
2 * Which conversations an agent may read from while it answers in one
3 * (docs/WORKSPACE.md, "What an agent can and can't know"). Pure, so the
4 * rule is tested apart from the service, adversarially.
5 *
6 * The audience is everyone who will read the answer: a direct message's or
7 * private channel's people; for a public channel, the whole workspace. An
8 * agent may quote a conversation only when every one of them could read it
9 * themselves:
10 * - a public channel: always, since anyone in the workspace can open it;
11 * - a private channel: when every person in the audience is in it;
12 * - a direct message: only when it has exactly the audience's people, so
13 * an agent never carries one DM into another, even with the same person
14 * and someone more.
15 * A public channel's audience, or any of more than `SHARED_OVER` people,
16 * is the whole workspace: only public channels are readable to it.
17 */
18
19export type AudienceKind = "dm" | "private" | "public";
20
21/** Past this many people, an audience is the whole workspace's. */
22export const SHARED_OVER = 50;
23
24export type Conversation = { kind: "channel" | "dm"; private: boolean | number; user_ids: string[] };
25
26export function audienceKind(channel: { kind: "channel" | "dm"; private: boolean | number }): AudienceKind {
27 if (channel.kind === "dm") return "dm";
28 return channel.private ? "private" : "public";
29}
30
31/** Whether the audience is the workspace's as a whole. */
32export function isShared(audience: { kind: AudienceKind; user_ids: string[] }): boolean {
33 return audience.kind === "public" || audience.user_ids.length > SHARED_OVER;
34}
35
36/** Whether everyone in `audience` may read `target`. */
37export function readableBy(target: Conversation, audience: { kind: AudienceKind; user_ids: string[] }): boolean {
38 if (target.kind === "channel" && !target.private) return true;
39 if (isShared(audience) || !audience.user_ids.length) return false;
40 const members = new Set(target.user_ids);
41 if (target.kind === "dm") {
42 const people = new Set(audience.user_ids);
43 return members.size === people.size && [...people].every((id) => members.has(id));
44 }
45 return audience.user_ids.every((id) => members.has(id));
46}
47
48/** A search's words as a LIKE pattern, its wildcards escaped with `\`. */
49export function likePattern(query: string): string | null {
50 const words = String(query ?? "").trim().slice(0, 200);
51 if (words.length < 2) return null;
52 return `%${words.replace(/[\\%_]/g, (c) => `\\${c}`)}%`;
53}

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