Skip to content
289 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 * The reply's audience and tools, wired to g1t's services. Each call here
3 * runs only after `Audience` and `ToolBox` decided it may (audience.ts,
4 * tools.ts); the backing services check the viewer again, as they do for
5 * every request.
6 */
7import {
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.8 type Result,
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)9 type ServiceBinding,
10 type User,
11 chatClient,
Docs: a workspace knowledge base people and agents write together12 docsClient,
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.13 foliosClient,
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)14 identityClient,
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.15 notifyClient,
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)16 reposClient,
17 searchClient,
18 workClient,
19} from "@g1t/contracts";
20
21import type { AudiencePorts, RepoRef } from "./audience.ts";
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.22import type { FolioDone, FoliosPorts, FoundMessage, ToolPorts } from "./tools.ts";
23import { RECALL_LIMIT, passageSource } from "./recall.ts";
Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.24import { base64 } from "./files.ts";
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.25import type { TeamAgentInfo, TeamsHere } from "./teammates.ts";
26
27export type TeamsEnv = { DB: D1Database; IDENTITY: ServiceBinding; NOTIFY?: ServiceBinding };
28
29/**
30 * The teams an agent is on (teammates.ts), with who is on them and how
31 * they show now. Null when identity can't be asked; presence is left out
32 * when notify can't be (then no one reads as offline).
33 */
34export async function loadTeams(env: TeamsEnv, slug: string, workspaceId: string, agent: { id: string; team: string | null }): Promise<TeamsHere | null> {
35 const teams = await identityClient(env.IDENTITY)
36 .agentTeams(slug, agent.id, agent.team)
37 .catch(() => null);
38 if (!teams) return null;
39 if (!teams.length) return { teams: [], agents: [], presence: [] };
40 const userIds = [...new Set(teams.flatMap((team) => team.people.map((person) => person.user_id)))];
41 const slugs = teams.map((team) => team.slug);
42 const added = [...new Set(teams.flatMap((team) => team.agent_ids))].slice(0, 90);
43 const [presence, rows] = await Promise.all([
44 env.NOTIFY && userIds.length ? notifyClient(env.NOTIFY).workspacePresence(slug, userIds).catch(() => null) : Promise.resolve(null),
45 env.DB.prepare(
46 `SELECT id, handle, display_name, title, team FROM agents
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.47 WHERE workspace_id = ?1 AND archived_at IS NULL AND scope = 'workspace'
People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how.48 AND (id IN (SELECT value FROM json_each(?2)) OR team IN (SELECT value FROM json_each(?3)))
49 ORDER BY builtin DESC, handle LIMIT 200`,
50 )
51 .bind(workspaceId, JSON.stringify(added), JSON.stringify(slugs))
52 .all<TeamAgentInfo>(),
53 ]);
54 return { teams, agents: rows.results, presence: presence ?? [] };
55}
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)56
57export type PortsEnv = {
58 DB: D1Database;
59 CHAT: ServiceBinding;
60 IDENTITY: ServiceBinding;
61 REPOS: ServiceBinding;
62 WORK: ServiceBinding;
63 SEARCH: ServiceBinding;
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.64 /** The artifacts service, for agents reading and writing artifacts; absent on an installation without it. */
65 ARTIFACTS?: ServiceBinding;
Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.66 /**
67 * Where files kept with artifacts are served (g1tusercontent.com for
68 * g1t.sh), for the links to files agents make. Empty: the site's own
69 * `/-/usercontent` path.
70 */
71 USERCONTENT_URL?: string;
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)72};
73
Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.74/** The usercontent origin files are linked under, without a trailing slash. */
75export function usercontentBase(env: { USERCONTENT_URL?: string }): string {
76 return (env.USERCONTENT_URL ?? "").trim().replace(/\/+$/, "") || "/-/usercontent";
77}
78
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)79const refOf = (repo: { id: string; namespace: string; name: string; isPrivate: boolean; defaultBranch: string; forkOf?: string | null }): RepoRef => ({
80 id: repo.id,
81 namespace: repo.namespace,
82 name: repo.name,
83 isPrivate: repo.isPrivate,
84 defaultBranch: repo.defaultBranch,
85 forkOf: repo.forkOf ?? null,
86});
87
88export function audiencePorts(env: PortsEnv, workspace: string, channelId: string): AudiencePorts {
89 const repos = reposClient(env.REPOS);
90 return {
91 async info() {
92 const found = await chatClient(env.CHAT).audience(workspace, channelId);
93 if (!found.ok) throw new Error(`the conversation's audience could not be read: ${found.error.message}`);
94 return found.value;
95 },
96 users: (ids) => identityClient(env.IDENTITY).usersForAudience(ids),
97 workspaceRepos: async (viewer) => (await repos.list(viewer, { namespace: workspace, memberOnly: true })).map(refOf),
98 readable: async (ids, viewer) => (await repos.readable(ids.slice(0, 500), viewer)).map(refOf),
99 };
100}
101
102const found = (m: { channel_id: string; channel: string | null; message: { id: string; author: { name: string }; body: string; created_at: string; card: { title: string } | null } }): FoundMessage => ({
103 channel: m.channel,
104 channel_id: m.channel_id,
105 id: m.message.id,
106 author: m.message.author.name,
107 body: m.message.body || (m.message.card ? `[card: ${m.message.card.title}]` : ""),
108 created_at: m.message.created_at,
109});
110
111export function toolPorts(
112 env: PortsEnv,
113 workspace: string,
114 workspaceId: string,
115 channelId: string,
116 consult: ToolPorts["consult"],
Docs: a workspace knowledge base people and agents write together117 agentId: string | null = 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)118): ToolPorts {
119 const repos = reposClient(env.REPOS);
120 const work = workClient(env.WORK);
121 const path = (repo: RepoRef) => ({ namespace: repo.namespace, name: repo.name });
122 return {
123 async readFile(repo, viewer, ref, file) {
124 const blob = await repos.blob(path(repo), viewer, ref, file);
125 return blob.ok ? { text: blob.value.text, size: blob.value.size } : null;
126 },
127 async searchCode(viewer, query, repo) {
128 const results = await searchClient(env.SEARCH).search(viewer, repo ? `${query} repo:${repo.namespace}/${repo.name}` : query, { type: "code", perPage: 20 });
129 if (!results.ok) return [];
130 return results.value.hits
131 .filter((hit) => hit.kind === "code" && hit.repo && hit.path)
132 .map((hit) => ({
133 repo: hit.repo!,
134 path: hit.path!,
135 snippet: hit.lines.map((line) => `${line.number}: ${line.parts.map((part) => part.text).join("")}`).join("\n"),
136 }));
137 },
138 async listIssues(repo, viewer, state) {
139 const issues = await work.listIssues(path(repo), viewer, { state });
140 return issues.ok ? issues.value.map((i) => ({ number: i.number, title: i.title, state: i.state, labels: i.labels })) : null;
141 },
142 async getIssue(repo, number, viewer) {
143 const detail = await work.getIssue(path(repo), number, viewer);
144 if (!detail.ok) return null;
145 const { issue, comments } = detail.value;
146 return {
147 number: issue.number,
148 title: issue.title,
149 state: issue.state,
150 body: issue.body,
151 comments: comments.filter((c) => c.kind === "comment").slice(-20).map((c) => ({ author: c.author.username, body: c.body })),
152 };
153 },
154 async getPull(repo, number, viewer) {
155 const detail = await work.getPull(path(repo), number, viewer);
156 if (!detail.ok) return null;
157 const { pull, checks } = detail.value;
158 return {
159 number: pull.number,
160 title: pull.title,
161 status: pull.status,
162 body: pull.body ?? "",
163 checks: checks ? `${checks.status}${checks.results.length ? `: ${checks.results.map((r) => `${r.command} ${r.passed ? "passed" : "failed"}`).join(", ")}` : ""}` : null,
164 };
165 },
166 async recentPulls(list, viewer) {
167 const byId = new Map(list.map((repo) => [repo.id, repo]));
168 const groups = await work.pullsForRepos([...byId.keys()], viewer, 5);
169 return groups
170 .flatMap((group) => [...group.open, ...group.closed].map((pull) => ({ pull, repo: byId.get(group.repoId) })))
171 .filter((entry) => entry.repo)
172 .map(({ pull, repo }) => ({
173 repo: `${repo!.namespace}/${repo!.name}`,
174 number: pull.number,
175 title: pull.title,
176 status: pull.status,
177 updated_at: pull.mergedAt ?? pull.updatedAt,
178 }))
179 .sort((a, b) => b.updated_at.localeCompare(a.updated_at))
180 .slice(0, 25);
181 },
182 async searchMessages(query) {
183 const result = await chatClient(env.CHAT).searchForAgent(workspace, channelId, query, 20);
184 return result.ok ? result.value.map(found) : null;
185 },
186 async readThread(target, id) {
187 const result = await chatClient(env.CHAT).threadForAgent(workspace, channelId, target, id);
188 return result.ok ? result.value.map(found) : null;
189 },
190 async roster(viewer: User | null) {
191 const identity = identityClient(env.IDENTITY);
192 const [members, teams, agents] = await Promise.all([
193 viewer ? identity.listMembers(workspace, viewer).catch(() => null) : Promise.resolve(null),
194 viewer ? identity.listTeams(viewer, workspace).catch(() => null) : Promise.resolve(null),
Anyone makes an agent by describing it: one box drafts the whole agent (name, job, responsibilities, voice, skills to keep, integrations it needs, model limits and budget) as a card to change and try in a chat beside it before creating it, and an agent changes later by saying what to change, shown as a diff before it's saved. Members can make personal agents, which only they talk to and which spend from their own budget, unless owners turn that off; owners promote one to a workspace agent. The agents guide says how.195 env.DB.prepare("SELECT handle, display_name, title, role FROM agents WHERE workspace_id = ? AND archived_at IS NULL AND scope = 'workspace' ORDER BY builtin DESC, handle")
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)196 .bind(workspaceId)
197 .all<{ handle: string; display_name: string; title: string; role: string }>(),
198 ]);
199 const people = members?.ok ? members.value.map((m) => `- @${m.username}${m.name ? ` (${m.name})` : ""}, ${m.role}`).join("\n") : "(people could not be listed)";
200 const teamLines = teams?.ok ? teams.value.map((t) => `- ${t.slug}: ${t.name}${t.description ? `, ${t.description}` : ""}`).join("\n") : "";
201 const agentLines = agents.results.map((a) => `- @${a.handle} (${a.display_name}): ${a.title || a.role}`).join("\n");
202 return `People:\n${people}${teamLines ? `\n\nTeams:\n${teamLines}` : ""}\n\nAgents:\n${agentLines}`;
203 },
204 consult,
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.205 ...(env.ARTIFACTS && agentId ? { folios: folioPorts(env.ARTIFACTS, env.CHAT, workspace, agentId, usercontentBase(env)) } : {}),
Docs: a workspace knowledge base people and agents write together206 };
207}
208
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.209/** A call's result as the tools take it: the value, or the error's code and sentence. */
210const done = <T>(result: Result<T>): FolioDone<T> => (result.ok ? result : { ok: false, code: result.error.code, message: result.error.message });
211
212/**
213 * Artifacts (folios) as an agent reads and writes them: Markdown in,
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.214 * Markdown out, every call checked by the artifacts service. Spaces still come
215 * from the artifacts service's `spaces_for_agent`: spaces are shared by pages
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.216 * and folios, and have no folio method of their own.
217 */
Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.218function folioPorts(docs: ServiceBinding, chatBinding: ServiceBinding, workspace: string, agentId: string, usercontent: string): FoliosPorts {
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.219 const folios = foliosClient(docs);
220 const where = (f: { title: string; path: string; id: string }) => `${f.title} (${f.path}, id ${f.id})`;
Docs: a workspace knowledge base people and agents write together221 return {
222 async spaces(viewer, audience) {
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.223 const found = await docsClient(docs).spacesForAgent(workspace, agentId, viewer, audience);
Docs: a workspace knowledge base people and agents write together224 if (!found.ok) return null;
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.225 return found.value.map((s) => ({ id: s.id, slug: s.slug, name: s.name, description: s.description, kind: s.kind, projects: s.projects ?? [], can: s.can }));
Docs: a workspace knowledge base people and agents write together226 },
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.227 async recall(viewer, audience, query, spaces, kinds) {
228 const found = await folios.recallForAgent(workspace, agentId, viewer, { query, limit: RECALL_LIMIT, spaces, kinds: kinds ?? null }, audience);
Agents recall what Docs say before they answer or work, and each has required reading229 return found.ok ? found.value : null;
230 },
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.231 async search(viewer, audience, input) {
232 const kinds = input.kind ? [input.kind] : null;
233 // Passages too, when the search isn't narrowed to a space or project: recall can't be.
234 const [list, passages] = await Promise.all([
235 folios.foliosForAgent(workspace, agentId, viewer, { tab: "all", q: input.query, kinds, space_id: input.space_id, project: input.project, limit: 10 }, audience),
236 input.space_id || input.project ? Promise.resolve(null) : folios.recallForAgent(workspace, agentId, viewer, { query: input.query, limit: 4, kinds }, audience).catch(() => null),
237 ]);
238 if (!list.ok) return null;
239 const lines = list.value.items.map((f) => {
240 const space = f.space ? `in ${f.space.name}` : "not in a space";
241 return `- ${where(f)}: a ${f.kind} ${space}, edited ${f.edited_at.slice(0, 10)}${f.stale ? ", possibly out of date" : ""}${f.excerpt ? `: ${f.excerpt.replace(/\s+/g, " ").slice(0, 300)}` : ""}`;
242 });
243 const found = passages?.ok ? passages.value.map((p) => `### ${passageSource(p)}\n${p.text.trim().slice(0, 800)}`) : [];
244 if (!lines.length && !found.length) return "No artifacts found.";
245 return [lines.length ? lines.join("\n") : "No artifacts matched by title or words.", ...(found.length ? ["", "Passages that match:", "", found.join("\n\n")] : [])].join("\n");
Docs: a workspace knowledge base people and agents write together246 },
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.247 read: async (viewer, audience, folioId) => done(await folios.readForAgent(workspace, agentId, viewer, folioId, audience)),
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store248 async stale(viewer, audience, repo) {
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.249 const found = await folios.staleForAgent(workspace, agentId, viewer, { repo }, audience);
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store250 if (!found.ok) return null;
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.251 if (!found.value.length) return "No artifacts are marked possibly out of date.";
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store252 return found.value
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.253 .map((f) => {
254 const can = f.viewer_role === "edit" || f.viewer_role === "manage" ? (f.agent_mode === "edit" ? "you can edit" : "you can suggest") : f.viewer_role === "comment" ? "you can suggest" : "read only";
255 return `- ${where(f)}, a ${f.kind} ${f.space ? `in ${f.space.name}` : "not in a space"} (${can}), edited ${f.edited_at.slice(0, 10)}`;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store256 })
257 .join("\n");
258 },
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.259 async create(viewer, input) {
260 const made = await folios.createAsAgent(workspace, agentId, viewer, {
261 kind: input.kind,
262 title: input.title,
263 content: input.markdown === null ? null : { markdown: input.markdown },
264 template_id: input.template_id,
265 where: input.where,
266 parent_id: input.parent_id,
267 source: input.source,
268 });
269 return done(made);
270 },
271 edit: async (viewer, folioId, edit) => done(await folios.editAsAgent(workspace, agentId, viewer, folioId, edit)),
272 async share(viewer, audience, folioId, userIds, role) {
273 const shared = await folios.shareAsAgent(workspace, agentId, viewer, folioId, { user_ids: userIds, role }, audience);
274 return shared.ok ? { ok: true, value: null } : done(shared);
Docs: a workspace knowledge base people and agents write together275 },
Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.276 async attach(viewer, folioId, file) {
277 const kept = await folios.attachAsAgent(workspace, agentId, viewer, folioId, { name: file.name, content_type: file.content_type, data: base64(file.bytes) });
278 return kept.ok ? { ok: true, value: { url: `${usercontent}${kept.value.url}`, name: kept.value.name, bytes: kept.value.bytes } } : done(kept);
279 },
Workspace agents find, read, write and share the workspace's artifacts with search_artifacts, read_artifact, create_artifact (docs for now), edit_artifact, stale_artifacts, share_artifact and list_spaces, and their recall reads artifacts and projects' docs through the folio RPCs instead of Docs' pages.280 async sendLink(asker, link, note) {
281 const chat = chatClient(chatBinding);
282 const dm = await chat.openDm(workspace, asker, [{ kind: "agent", id: agentId }]);
283 if (!dm.ok) return false;
284 // No mention, so it wakes nobody.
285 const posted = await chat.postAsAgent(workspace, dm.value.id, agentId, { body: `${note} [${link.title.replace(/[[\]]/g, "")}](${link.path})`, asked_by: asker.id });
286 return posted.ok;
Docs: a workspace knowledge base people and agents write together287 },
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)288 };
289}

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