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 | */ | |
| 7 | import { | |
| 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 together | 12 | 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 | ||
| 21 | import 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. | 22 | import type { FolioDone, FoliosPorts, FoundMessage, ToolPorts } from "./tools.ts"; |
| 23 | import { 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. | 24 | import { 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. | 25 | import type { TeamAgentInfo, TeamsHere } from "./teammates.ts"; |
| 26 | ||
| 27 | export 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 | */ | |
| 34 | export 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 | |
| 57 | export 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. */ |
| 75 | export 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) | 79 | const 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 | ||
| 88 | export 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 | ||
| 102 | const 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 | ||
| 111 | export 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 together | 117 | 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 together | 206 | }; |
| 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. */ |
| 210 | const 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. | 218 | function 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 together | 221 | 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 together | 224 | 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 together | 226 | }, |
| 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 reading | 229 | 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 together | 246 | }, |
| 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 store | 248 | 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 store | 250 | 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 store | 252 | 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 store | 256 | }) |
| 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 together | 275 | }, |
| 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 together | 287 | }, |
| 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.