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

Docs: a workspace knowledge base people and agents write together1/**
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.2 * Docs: the workspace's written knowledge, kept by the artifacts service
3 * (`services/artifacts`). Spaces hold trees of pages; each page is a CRDT
Docs: a workspace knowledge base people and agents write together4 * document (Yjs) edited live over a socket, saved with a Markdown
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.5 * rendition that search, agents, export and the read view use.
Docs: a workspace knowledge base people and agents write together6 *
7 * Wire shapes are snake_case end to end: the site, agents and the live
8 * socket all carry the same objects.
9 *
10 * Access, in one place:
11 *
12 * - A space has a kind: `workspace` (every member gets `default_role`),
13 * `team` (the team's members get `default_role`) or `private` (only the
14 * people, agents and teams listed as its members).
15 * - Members are listed with a role: view < comment < edit < manage. A
16 * person's role is the highest of the space's base role (when it applies
17 * to them) and every listing that names them or one of their teams.
18 * Workspace owners manage every workspace and team space; a private space
19 * is its members' alone.
20 * - An agent never sees or changes more than the person it acts for (the
21 * `viewer` on every agent call). It reads what that person can read,
22 * narrowed further to what every person in the `audience` can read; it
23 * suggests where that person can comment; and it edits directly only
24 * where that person can edit AND the space lets agents edit
25 * (`agent_mode: "edit"`). Otherwise its edit becomes a suggestion.
26 */
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.27import type { AgentLook } from "./agent-look";
Docs: a workspace knowledge base people and agents write together28import type { MemberProfile, Principal } from "./chat";
29import type { ServiceBinding } from "./clients";
30import type { User } from "./identity";
31import type { Result } from "./result";
32
33/** What someone may do in a space, weakest first. */
34export type DocRole = "view" | "comment" | "edit" | "manage";
35export const DOC_ROLES: readonly DocRole[] = ["view", "comment", "edit", "manage"];
36
37export const DOC_ROLE_LABELS: Record<DocRole, string> = {
38 view: "Can view",
39 comment: "Can comment",
40 edit: "Can edit",
41 manage: "Full access",
42};
43
44export const DOC_ROLE_SUMMARIES: Record<DocRole, string> = {
45 view: "Read pages and their history.",
46 comment: "Read pages and comment on them.",
47 edit: "Write and organize pages, and accept or reject suggestions.",
48 manage: "Edit, and change who has access and how agents work here.",
49};
50
51/** Who a space is for. */
52export type DocSpaceKind = "workspace" | "team" | "private";
53
54export const DOC_SPACE_KIND_LABELS: Record<DocSpaceKind, string> = {
55 workspace: "Everyone in the workspace",
56 team: "A team",
57 private: "Only its members",
58};
59
60/** How agents change pages in a space: as tracked suggestions (the default), or directly. */
61export type DocAgentMode = "suggest" | "edit";
62
63export const DOC_AGENT_MODE_LABELS: Record<DocAgentMode, string> = {
64 suggest: "Suggest changes",
65 edit: "Edit directly",
66};
67
68/** A key for a space member: `user:<id>`, `agent:<id>` or `team:<slug>`. */
69export type DocMemberKey = string;
70
71export type DocSpace = {
72 id: string;
73 workspace_id: string;
74 /** Unique in the workspace; in the URL: `/<workspace>/-/docs/<slug>`. */
75 slug: string;
76 name: string;
77 description: string | null;
78 /** An emoji, or null for the default book. */
79 icon: string | null;
80 kind: DocSpaceKind;
81 /** The team's slug, for a team space. */
82 team: string | null;
83 /** What every workspace member (workspace) or team member (team) gets; null for a private space. */
84 default_role: DocRole | null;
85 agent_mode: DocAgentMode;
The docs service answers folio_page with a folio and what its page shows around it, a space can let its editors share what is in it, and someone asking for access to an artifact is told to wait when they already asked for it in the last day.86 /** People with edit access may share what is in it (Artifacts), as people with full access can. Off unless turned on. */
87 editors_can_share: boolean;
Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.88 /**
89 * The workspace's General space, made once when Artifacts is first
90 * opened and shown to every member without joining. It can be archived
91 * and deleted like any other; once deleted it is not made again, and no
92 * other space takes its place.
93 */
Docs: a workspace knowledge base people and agents write together94 is_default: boolean;
95 /** Projects (repositories, `owner/name`) the space is about: Docs filters by them. */
96 projects: string[];
97 created_by: Principal;
98 created_at: string;
99 archived_at: string | null;
100 /** The viewer's role in it. */
101 viewer_role: DocRole;
102 /** Pages in it that are not in the trash. */
103 page_count: number;
104};
105
106/** A space member, as the space's settings show it. */
107export type DocSpaceMember = {
108 key: DocMemberKey;
109 kind: "user" | "agent" | "team";
110 /** Username, agent handle or team slug. */
111 name: string;
112 display_name: string;
113 avatar: string | null;
114 avatar_seed?: string | null;
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.115 look?: AgentLook | null;
Docs: a workspace knowledge base people and agents write together116 role: DocRole;
117};
118
119/** Enough to link to a page. */
120export type DocPageRef = {
121 id: string;
122 space_id: string;
123 space_slug: string;
124 title: string;
125 /** An emoji, or null for the default page icon. */
126 icon: string | null;
127 /** `<title-slug>-<id>`: the last part of the page's URL. */
128 slug: string;
129 /** `/<workspace>/-/docs/<space>/<slug>`. */
130 path: string;
131};
132
133export type DocPage = DocPageRef & {
134 parent_id: string | null;
135 position: number;
136 /** A cover image's URL, or a CSS gradient name (`gradient:<n>`); null for none. */
137 cover: string | null;
138 created_by: MemberProfile;
139 created_at: string;
140 updated_by: MemberProfile | null;
141 updated_at: string;
142 /** When it went to the trash; null when it is not there. */
143 archived_at: string | null;
144 has_children: boolean;
145 /** Projects (`owner/name`) the page is about, besides its space's. */
146 projects: string[];
147 owners: MemberProfile[];
148 /** The first lines of its text, for cards. */
149 excerpt: string;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store150 /** Whether code it cites changed since someone last marked it current: possibly out of date. */
151 stale: boolean;
152};
153
154// ── Citations and staleness ───────────────────────────────────────────────
155//
156// A page can cite code: a path (a file, a folder, or a glob like
157// `src/export/**`) in a repository, optionally naming what at that path it
158// describes (a symbol, an endpoint, an environment variable). Citations
159// come from the page's text (the editor's citation chips, and links to
160// files in a repository: `/<owner>/<repo>/blob/<ref>/<path>`) and from
161// the page's header ("Describes"). When a merged pull request or a push
162// to a repository's default branch changes a cited path, the page is
163// marked possibly out of date with that change, until someone with edit
164// access marks it current again (or an agent updates it).
165
166/** What a citation names at its path. */
167export type DocCitationKind = "path" | "symbol" | "endpoint" | "env";
168export const DOC_CITATION_KINDS: readonly DocCitationKind[] = ["path", "symbol", "endpoint", "env"];
169
170export const DOC_CITATION_KIND_LABELS: Record<DocCitationKind, string> = {
171 path: "A file or folder",
172 symbol: "A symbol",
173 endpoint: "An endpoint",
174 env: "An environment variable",
175};
176
177export type DocCitation = {
178 /** `owner/name`, lowercased. */
179 repo: string;
180 /** A file, a folder (everything under it), or a glob (`*`, `**`, `?`). */
181 path: string;
182 kind: DocCitationKind;
183 /** The symbol, endpoint (`POST /v1/export`) or variable (`EXPORT_BUCKET`), for those kinds. */
184 label: string | null;
185 /** The commit it was cited at, when known. */
186 ref: string | null;
187 /** From the page's text, or from its header's "Describes". */
188 source: "body" | "header";
189};
190
191/** One entry of a page's "Describes": a repository and a path in it. */
192export type DocDescribes = { repo: string; path: string };
193
194/** A change that made a page possibly out of date. */
195export type DocStaleChange = {
196 /**
197 * False when the viewer can't read the repository: then `repo`, `pull`,
198 * `commit` and `paths` are blank, and the page only says that a change
199 * they can't see touched code it cites.
200 */
201 visible: boolean;
202 repo: string | null;
203 commit: string | null;
204 /** The merged pull request, when the change came from one. */
205 pull: { number: number; title: string | null } | null;
206 /** The cited paths it changed (the changed files, at most 20). */
207 paths: string[];
208 /** When it was noticed. RFC 3339. */
209 at: string;
210};
211
212/** Why a page is possibly out of date: every change since it was last marked current, newest first. */
213export type DocStaleness = { since: string; changes: DocStaleChange[] };
214
215/** A page an agent may bring up to date, with what changed. */
216export type DocStalePage = {
217 page: DocPageRef & { updated_at: string };
218 space: { id: string; slug: string; name: string; agent_mode: DocAgentMode };
219 can: DocAgentAbilities;
220 owners: MemberProfile[];
221 citations: DocCitation[];
222 /** Changes in repositories the viewer (and audience) can read; newest first. */
223 changes: DocStaleChange[];
224 since: string;
225};
226
227// ── A project's docs ──────────────────────────────────────────────────────
228//
229// A repository's `docs/` folder (and README.md), shown read-only in Docs
230// next to the workspace's spaces and found by the same search. It is read
231// from the default branch and kept up to date on every push to it. Each
232// reader sees only the repositories they can read. Changes go through the
233// repository: "Edit in Code" opens the file.
234
235export type DocRepoFile = {
236 /** From the repository's root: `docs/guide/setup.md`, `README.md`. */
237 path: string;
238 /** Its first heading, or its file name. */
239 title: string;
240};
241
242export type DocRepoSpace = {
243 id: string;
244 /** `owner/name`, as the repository is named now. */
245 repo: string;
246 default_branch: string;
247 /** The commit it was read at; null until it has been. */
248 commit: string | null;
249 indexed_at: string | null;
250 added_by: MemberProfile;
251 files: DocRepoFile[];
252 /** Whether the viewer may stop showing it (whoever added it, or an owner). */
253 can_remove: boolean;
Docs: a workspace knowledge base people and agents write together254};
255
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store256export type DocRepoPage = {
257 space: DocRepoSpace;
258 file: DocRepoFile & {
259 markdown: string;
260 /** `/<workspace>/-/docs/repo/<owner>/<name>/<path>`. */
261 href: string;
262 /** The file in Code, on the default branch. */
263 code_href: string;
264 };
265};
266
Docs: a workspace knowledge base people and agents write together267/** A page as the sidebar's tree lists it: flat, ordered by `position` within each parent. */
268export type DocTreeNode = {
269 id: string;
270 parent_id: string | null;
271 position: number;
272 title: string;
273 icon: string | null;
274 slug: string;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store275 /** Possibly out of date (see `DocPage.stale`). */
276 stale?: boolean;
Docs: a workspace knowledge base people and agents write together277};
278
279export type DocsSidebarSpace = DocSpace & { pages: DocTreeNode[] };
280
281export type DocsSidebar = {
282 spaces: DocsSidebarSpace[];
283 favorites: DocPageRef[];
284 /** The pages the viewer opened last, newest first. */
285 recent: DocPageRef[];
286 /** Whether the viewer may make spaces (members of the workspace may). */
287 can_create_space: boolean;
288 trash_count: number;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store289 /** Pages the viewer can read that are possibly out of date. */
290 stale_count: number;
291 /** Projects' docs folders shown in Docs, those whose repository the viewer can read. */
292 repos: DocRepoSpace[];
Docs: a workspace knowledge base people and agents write together293};
294
295export type DocsHome = {
296 /** Recently edited pages the viewer can read, newest first. */
297 recent: DocPage[];
298 /** Pages the viewer made or owns. */
299 mine: DocPage[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store300 /** Pages possibly out of date, most recently flagged first. */
301 stale: DocPage[];
Docs: a workspace knowledge base people and agents write together302 spaces: DocSpace[];
303 /** Every project some space or page is linked to, for the filter. */
304 projects: string[];
305 /** The project the lists are filtered to, or null. */
306 project: string | null;
307};
308
309/** A tracked change an agent proposed. `blocks` targets name top-level block ids from `page_markdown`. */
310export type DocEditTarget =
311 /** Add to the end of the page. */
312 | { kind: "append" }
313 /** Replace the whole page. */
314 | { kind: "document" }
315 /** Replace a section: the heading whose text matches (case-insensitive) and everything under it, up to the next heading of the same or a higher level. The new Markdown should include the heading if it is to stay. */
316 | { kind: "section"; heading: string }
317 /** Replace top-level blocks `from_block` through `to_block`, inclusive (with any blocks nested under them). */
318 | { kind: "blocks"; from_block: string; to_block: string };
319
320export type DocSuggestionStatus = "open" | "accepted" | "rejected" | "stale";
321
322export type DocSuggestion = {
323 id: string;
324 page_id: string;
325 /** The agent that suggested it. */
326 author: MemberProfile;
327 /** The person it acted for. */
328 asked_by: MemberProfile | null;
329 target: DocEditTarget;
330 /** The target's Markdown when it was suggested. */
331 before_markdown: string;
332 /** What it proposes instead. */
333 after_markdown: string;
334 /** Why, in a line. */
335 note: string | null;
336 status: DocSuggestionStatus;
337 created_at: string;
338 decided_by: MemberProfile | null;
339 decided_at: string | null;
340 /** The top-level blocks it covers now, so the editor can mark them; empty for an append or when the target is gone. */
341 block_ids: string[];
342};
343
344export type DocVersionKind = "created" | "edit" | "agent" | "suggestion" | "restore";
345
346export type DocVersion = {
347 id: string;
348 page_id: string;
349 created_at: string;
350 /** Everyone whose changes are in it, people and agents. */
351 authors: MemberProfile[];
352 kind: DocVersionKind;
353 /** "Suggested by @inky, accepted by @ana"; "Restored from Oct 3, 14:02". */
354 note: string | null;
355};
356
357export type DocDiffLine = { op: "same" | "add" | "del"; text: string };
358
359export type DocVersionDetail = DocVersion & {
360 markdown: string;
361 /** Against the version before it (or nothing, for the first). */
362 diff: DocDiffLine[];
363};
364
365export type DocPageDetail = {
366 page: DocPage;
367 space: DocSpace;
368 /** The page's ancestors, root first. */
369 breadcrumbs: DocPageRef[];
370 /** As last saved: the read view, and what the editor shows before the socket connects. */
371 markdown: string;
372 role: DocRole;
373 backlinks: DocPageRef[];
374 children: DocPageRef[];
375 favorite: boolean;
376 /** When the viewer last opened it, before now. */
377 last_viewed_at: string | null;
378 suggestions: DocSuggestion[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store379 /** Code the page cites: from its text and its header. */
380 citations: DocCitation[];
381 /** The header's "Describes" list. */
382 describes: DocDescribes[];
383 /** Why it is possibly out of date; null when it isn't. */
384 staleness: DocStaleness | null;
Docs: a workspace knowledge base people and agents write together385};
386
387export type DocSearchHit = DocPageRef & {
388 space_name: string;
389 /** Text around the match; `[[` and `]]` mark matched words. */
390 snippet: string;
391 updated_at: string;
392 projects: string[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store393 /** Set for a file from a project's docs folder (then `id` is `repo:<space>:<path>` and `path` its address in Docs). */
394 repo_file?: { repo: string; path: string } | null;
Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people395 /** The heading of the passage that matched, when search found one (`mode: "hybrid"`). */
396 heading?: string | null;
397 /** How it was found: by its words, by meaning (the semantic index), or both. */
398 matched?: "words" | "meaning" | "both" | null;
Docs: a workspace knowledge base people and agents write together399};
400
401export type DocTemplate = {
402 id: string;
403 name: string;
404 description: string;
405 icon: string;
406 /** Built in (meeting notes, RFC, ...) rather than saved by the workspace. */
407 builtin: boolean;
408 markdown: string;
409 created_by: Principal | null;
410};
411
412/** One top-level block, as agents see a page. */
413export type DocBlockOutline = {
414 id: string;
415 /** BlockNote's type: `heading`, `paragraph`, `bulletListItem`, `codeBlock`, `callout`, ... */
416 type: string;
417 /** For a heading. */
418 level: number | null;
419 /** The block and anything nested under it. */
420 markdown: string;
421};
422
Four small things seen today. An agent can attach a file it made to any doc the person who asked can edit, whatever the space's agent mode: attaching changes nothing in the doc, so the suggest mode that stopped every PDF and spreadsheet no longer does; the ability is its own, attach, beside read, suggest and edit. Home no longer counts robots among the people: events that name an agent by its own id, or g1t's upkeep as the workspace or as g1t, read as the agent and as g1t, in the digest and on the page, so the faces say who instead of someone. Tooltips are the inverted bubble, the page's foreground behind the page's background, with no border. The Spend page's slice tabs keep their ring inside the row instead of losing its top to the scroll edge, and the Apps launcher keeps its search box and footer in place while it reads which apps there are, with skeleton tiles between.423/**
424 * What an agent may do on a page, for the person it acts for: read it,
425 * suggest changes, edit its body (only in a space whose agents edit), and
426 * attach a file it made (whenever the person could edit: a file beside the
427 * page changes nothing in it).
428 */
429export type DocAgentAbilities = { read: boolean; suggest: boolean; edit: boolean; attach: boolean };
Docs: a workspace knowledge base people and agents write together430
431export type DocAgentPage = {
432 page: DocPageRef & { updated_at: string };
433 space: { id: string; slug: string; name: string; agent_mode: DocAgentMode };
434 markdown: string;
435 blocks: DocBlockOutline[];
436 can: DocAgentAbilities;
437};
438
439/** Who will see what an agent says; it reads only what they all can. */
440export type DocAudience =
441 /** These people (by user id), e.g. a DM's or a private channel's members. */
442 | { kind: "people"; user_ids: string[] }
443 /** Everyone in the workspace, e.g. a public channel: only workspace-wide spaces. */
444 | { kind: "workspace" };
445
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store446/** An agent's edit to a page. */
447export type DocAgentEdit = {
448 target: DocEditTarget;
449 markdown: string;
450 note?: string | null;
451 /** The edit brings the page up to date with the code it cites: once applied, the page is no longer possibly out of date. */
452 marks_current?: boolean | null;
453};
454
Docs: a workspace knowledge base people and agents write together455/** What `apply_edit` did: applied it, or filed a suggestion because it may not edit there. */
456export type DocAgentEditResult =
457 | { mode: "applied"; version_id: string | null; page: DocPageRef }
458 | { mode: "suggested"; suggestion: DocSuggestion; page: DocPageRef };
459
460export type NewDocSpace = {
461 name: string;
462 slug?: string | null;
463 description?: string | null;
464 icon?: string | null;
465 kind: DocSpaceKind;
466 team?: string | null;
467 default_role?: DocRole | null;
468 agent_mode?: DocAgentMode | null;
469 projects?: string[] | null;
The docs service answers folio_page with a folio and what its page shows around it, a space can let its editors share what is in it, and someone asking for access to an artifact is told to wait when they already asked for it in the last day.470 editors_can_share?: boolean | null;
Docs: a workspace knowledge base people and agents write together471};
472
473export type DocSpaceChange = Partial<Omit<NewDocSpace, "kind">> & { kind?: DocSpaceKind; archived?: boolean };
474
475export type NewDocPage = {
476 space_id: string;
477 parent_id?: string | null;
478 title?: string | null;
479 icon?: string | null;
480 /** A built-in template's id (`builtin:<name>`) or a saved one's. */
481 template_id?: string | null;
482 /** Starting Markdown, when there is no template. */
483 markdown?: string | null;
484 projects?: string[] | null;
485};
486
487export type DocPageChange = {
488 title?: string;
489 icon?: string | null;
490 cover?: string | null;
491 projects?: string[];
492 /** Member keys (`user:<id>`, `agent:<id>`). */
493 owners?: string[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store494 /** Replaces the header's "Describes" list (at most 20). */
495 describes?: DocDescribes[];
Docs: a workspace knowledge base people and agents write together496};
497
498/** Where a page goes: under `parent_id` (null for the top of the space), before `before_id` (null for the end). */
499export type DocMove = { space_id?: string | null; parent_id: string | null; before_id?: string | null };
500
501export type DocSearchQuery = {
502 query: string;
503 space_id?: string | null;
504 /** `owner/name`: pages linked to it, or in a space linked to it. */
505 project?: string | null;
506 limit?: number | null;
Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people507 /**
508 * `words` (the default): titles and text by their words, as you type.
509 * `hybrid`: words and meaning (the semantic index) together, each hit
510 * with the passage and heading that matched; the Docs search page.
511 */
512 mode?: "words" | "hybrid" | null;
Docs: a workspace knowledge base people and agents write together513};
514
515/**
516 * The comment operations of the editor's thread store (BlockNote's
517 * `RESTYjsThreadStore`), applied by the page's room to the `threads` map in
518 * the page's Yjs document, so every open editor sees them at once.
519 */
520export type DocThreadAction =
521 | { op: "create"; body: unknown; metadata?: unknown; page_level?: boolean }
522 /** Anchors a thread to the text between two Yjs relative positions (JSON). */
523 | { op: "anchor"; thread_id: string; anchor: unknown; head: unknown }
524 | { op: "comment"; thread_id: string; body: unknown; metadata?: unknown }
525 | { op: "edit_comment"; thread_id: string; comment_id: string; body: unknown; metadata?: unknown }
526 | { op: "delete_comment"; thread_id: string; comment_id: string; soft?: boolean }
527 | { op: "delete_thread"; thread_id: string }
528 | { op: "resolve" | "unresolve"; thread_id: string }
529 | { op: "react" | "unreact"; thread_id: string; comment_id: string; emoji: string };
530
531/** A comment thread, as agents and the inbox see it. */
532export type DocThread = {
533 id: string;
534 /** The text it is on; null for a comment on the whole page. */
535 quote: string | null;
536 resolved: boolean;
537 comments: { id: string; author: MemberProfile; text: string; created_at: string }[];
538};
539
540/** A file put in a page: served from the usercontent origin. */
541export type DocFile = { id: string; url: string; name: string; content_type: string; bytes: number };
542
543/**
544 * What a page's live socket carries besides the Yjs protocol (binary
545 * frames: y-protocols sync and awareness). These are JSON text frames.
546 */
547export type DocsLiveEvent =
548 | { type: "page.updated"; page: DocPage }
549 | { type: "page.archived"; page_id: string }
550 | { type: "suggestion.created" | "suggestion.updated"; suggestion: DocSuggestion }
551 | { type: "version.created"; version: DocVersion }
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store552 /** Whether it is possibly out of date changed: ask for the page again (what each reader sees of why depends on what they can read). */
553 | { type: "page.staleness" }
Docs: a workspace knowledge base people and agents write together554 /** The viewer's role changed, or their access ended (`role` null). */
555 | { type: "access"; role: DocRole | null };
556
557/**
558 * Header the site sets on a forwarded live socket and on uploads: the
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.559 * viewer, as JSON. Trusted only because the artifacts service is reachable
Docs: a workspace knowledge base people and agents write together560 * through service bindings alone.
561 */
562export const DOCS_VIEWER_HEADER = "x-g1t-docs-viewer";
563
564/** The largest file a page takes, in bytes. */
565export const DOC_MAX_FILE_BYTES = 25 * 1024 * 1024;
566
567/** The name of the Yjs XML fragment that holds a page's blocks. */
568export const DOC_FRAGMENT = "document-store";
569/** The name of the Yjs map that holds a page's comment threads. */
570export const DOC_THREADS = "threads";
571
Agents recall what Docs say before they answer or work, and each has required reading572/** One passage of Docs, recalled for an agent: a section of a page or of a repository's docs. */
573export type DocPassage = {
574 /** The page; null for a repository's docs file. */
575 page: DocPageRef | null;
576 /** A repository's docs file: `owner/name`, its path, and where it reads in Docs. */
577 repo_file: { repo: string; path: string; href: string } | null;
578 space_name: string;
579 /** The heading the passage sits under, if any. */
580 heading: string | null;
581 /** The passage as Markdown, at most about 1,500 characters. */
582 text: string;
583 /** How close it is, 0 to 1. */
584 score: number;
585 updated_at: string;
586 /** The page is marked possibly out of date. */
587 stale: boolean;
588};
589
Docs: a workspace knowledge base people and agents write together590export type DocsApi = {
591 // ── The site ─────────────────────────────────────────────────────────
592
593 /** Spaces with their page trees, favorites, recent pages. Makes the General space the first time. */
594 sidebar(workspace: string, viewer: User): Promise<Result<DocsSidebar>>;
595 home(workspace: string, viewer: User, options?: { project?: string | null }): Promise<Result<DocsHome>>;
596 space(workspace: string, spaceSlug: string, viewer: User): Promise<Result<{ space: DocSpace; members: DocSpaceMember[]; pages: DocPage[] }>>;
Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.597 /** Every space the viewer can open, archived ones included (never deleted ones), General first. */
598 spaces(workspace: string, viewer: User): Promise<Result<DocSpace[]>>;
Docs: a workspace knowledge base people and agents write together599 createSpace(workspace: string, viewer: User, input: NewDocSpace): Promise<Result<DocSpace>>;
Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.600 /** Manage role only. `archived` true or false archives a space or brings it back, the General space too. */
Docs: a workspace knowledge base people and agents write together601 updateSpace(workspace: string, spaceId: string, viewer: User, change: DocSpaceChange): Promise<Result<DocSpace>>;
602 /** Adds, changes (`role`) or removes (`role` null) a member. Manage role only; the last manager stays. */
603 setSpaceMember(workspace: string, spaceId: string, viewer: User, member: DocMemberKey, role: DocRole | null): Promise<Result<DocSpaceMember[]>>;
604
605 /** A page, and the viewer's role in it; records the view. Not found when they can't read it. */
606 page(workspace: string, pageId: string, viewer: User): Promise<Result<DocPageDetail>>;
607 createPage(workspace: string, viewer: User, input: NewDocPage): Promise<Result<DocPage>>;
608 updatePage(workspace: string, pageId: string, viewer: User, change: DocPageChange): Promise<Result<DocPage>>;
609 /** Edit role in both spaces. Refuses moving a page under itself. */
610 movePage(workspace: string, pageId: string, viewer: User, move: DocMove): Promise<Result<DocPage>>;
611 duplicatePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
612 /** To the trash, with every page under it. */
613 archivePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
614 restorePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
615 /** For good: only from the trash, manage role. */
616 deletePage(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
617 trash(workspace: string, viewer: User): Promise<Result<DocPage[]>>;
618 favorite(workspace: string, pageId: string, viewer: User, on: boolean): Promise<Result<boolean>>;
619
620 search(workspace: string, viewer: User, query: DocSearchQuery): Promise<Result<DocSearchHit[]>>;
621
622 versions(workspace: string, pageId: string, viewer: User): Promise<Result<DocVersion[]>>;
623 version(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersionDetail>>;
624 /** Makes the page what it was then, as a new version. Edit role. */
625 restoreVersion(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersion>>;
626
627 templates(workspace: string, viewer: User): Promise<Result<DocTemplate[]>>;
628 /** Saves a page as one of the workspace's templates. */
629 saveTemplate(workspace: string, viewer: User, input: { page_id: string; name: string; description?: string | null }): Promise<Result<DocTemplate>>;
630 deleteTemplate(workspace: string, templateId: string, viewer: User): Promise<Result<boolean>>;
631
632 exportPage(workspace: string, pageId: string, viewer: User): Promise<Result<{ filename: string; markdown: string }>>;
633 /** Every page in a space as Markdown, at paths that follow the tree. */
634 exportSpace(workspace: string, spaceId: string, viewer: User): Promise<Result<{ name: string; files: { path: string; markdown: string }[] }>>;
635
636 suggestions(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
637 /** Accept (applies it to the live document, attributed to both) or reject. Edit role. */
638 decideSuggestion(workspace: string, suggestionId: string, viewer: User, decision: "accept" | "reject"): Promise<Result<DocSuggestion>>;
639 acceptAll(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
640
641 /** A comment operation from the editor; comment role (deleting others' comments or threads needs edit). */
642 thread(workspace: string, pageId: string, viewer: User, action: DocThreadAction): Promise<Result<unknown>>;
643 threads(workspace: string, pageId: string, viewer: User): Promise<Result<DocThread[]>>;
644
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store645 /** Clears "possibly out of date": the page says what the code does now. Edit role. */
646 markCurrent(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
647 /** Pages the viewer can read that are possibly out of date, most recently flagged first; `repo` (`owner/name`) narrows to changes there. */
648 stalePages(workspace: string, viewer: User, options?: { repo?: string | null }): Promise<Result<DocPage[]>>;
649
650 /**
651 * Shows a repository's `docs/` folder and README.md in Docs, read from
652 * its default branch. Any member who can read the repository may; the
653 * files are read at once and again on every push to the default branch.
654 */
655 addRepoSpace(workspace: string, viewer: User, repo: string): Promise<Result<DocRepoSpace>>;
656 /** Stops showing it. Whoever added it, or a workspace owner. */
657 removeRepoSpace(workspace: string, viewer: User, id: string): Promise<Result<boolean>>;
658 /** One file of a project's docs, for a viewer who can read the repository; not found otherwise. */
659 repoPage(workspace: string, viewer: User, repo: string, path: string): Promise<Result<DocRepoPage>>;
Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people660 /**
661 * Indexes the workspace's pages and projects' docs for agents' recall
662 * again, in batches in the background. Workspace owners. True when a run
663 * started (false: one is already going).
664 */
665 reindexDocs(workspace: string, viewer: User): Promise<Result<boolean>>;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store666
Docs: a workspace knowledge base people and agents write together667 // ── Agents (services/agents) ─────────────────────────────────────────
668 //
669 // Each takes the agent and the person it acts for (`viewer`, the asker).
670 // The service checks the agent is a live agent of the workspace and caps
671 // everything by the viewer's access; `audience`, when given, narrows
672 // reads further to what every person in it can read. A page the agent
673 // may not read is not found, exactly as one that does not exist.
674
675 /** Spaces the agent may read for the viewer (and audience), with what it may do in each. */
676 spacesForAgent(workspace: string, agentId: string, viewer: User, audience?: DocAudience | null): Promise<Result<(Pick<DocSpace, "id" | "slug" | "name" | "description" | "kind" | "agent_mode" | "projects"> & { can: DocAgentAbilities })[]>>;
677 /** A page's Markdown and its top-level blocks (ids for `blocks` targets). */
678 pageMarkdown(workspace: string, agentId: string, viewer: User, pageId: string, audience?: DocAudience | null): Promise<Result<DocAgentPage>>;
679 /** Full-text search over pages every reader can read; at most 20, best first. */
680 searchForAgent(workspace: string, agentId: string, viewer: User, query: DocSearchQuery, audience?: DocAudience | null): Promise<Result<DocSearchHit[]>>;
681 /** Files a tracked suggestion; people with edit access accept or reject it inline. Needs the viewer's comment role. Notifies the page's owners. */
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store682 suggestEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: DocAgentEdit): Promise<Result<DocSuggestion>>;
Docs: a workspace knowledge base people and agents write together683 /**
684 * Applies an edit to the live document when the space lets agents edit
685 * and the viewer can edit; otherwise files it as a suggestion (and says
686 * so in `mode`). Attributed to the agent in the page's history.
687 */
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store688 applyEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: DocAgentEdit): Promise<Result<DocAgentEditResult>>;
Docs: a workspace knowledge base people and agents write together689 /**
690 * A new page, written by the agent: in `space_id` (the General space
691 * when null), under `parent_id`. Needs the viewer's edit role there
692 * (whatever the space's agent mode: a new page changes nothing anyone
693 * wrote). `source` links where it came from ("write this up").
694 */
695 createPageAsAgent(
696 workspace: string,
697 agentId: string,
698 viewer: User,
699 input: { space_id?: string | null; parent_id?: string | null; title: string; icon?: string | null; markdown: string; source?: { title: string; href: string } | null },
700 ): Promise<Result<DocPageRef>>;
Agents recall what Docs say before they answer or work, and each has required reading701 /**
702 * What the workspace's Docs say about `query`, for an agent about to
703 * answer: the passages closest in meaning (and, where meaning finds too
704 * little, in words), each with the page and heading it came from. Only
705 * from spaces the viewer can read and, with `audience`, everyone it
706 * covers; repository docs only from repositories they can all read.
707 * `spaces` narrows to these space ids first (an agent's required
708 * reading) and fills from the rest. Empty when nothing is close enough.
709 */
710 recallForAgent(
711 workspace: string,
712 agentId: string,
713 viewer: User,
714 input: { query: string; limit?: number | null; spaces?: string[] | null },
715 audience?: DocAudience | null,
716 ): Promise<Result<DocPassage[]>>;
Docs: a workspace knowledge base people and agents write together717 /** A page's comment threads, for an agent asked about them. */
718 threadsForAgent(workspace: string, agentId: string, viewer: User, pageId: string, audience?: DocAudience | null): Promise<Result<DocThread[]>>;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store719 /**
720 * Pages possibly out of date that the agent may read for the viewer (and
721 * audience), each with the changes that made it so: for a documenter
722 * routine to bring them up to date. At most 50, most recently flagged
723 * first.
724 *
725 * - `repo` (`owner/name`): only pages made stale by a change there.
726 * - `since` (RFC 3339): only pages flagged at or after it.
727 *
728 * Changes in repositories the viewer can't read are left out, and a page
729 * whose every change is one of those is left out too: an agent never
730 * learns of code its person can't see. To update a page, read it
731 * (`pageMarkdown`), then `applyEdit` or `suggestEdit` with
732 * `marks_current: true`: the page is marked current when the edit is
733 * applied (at once, or when a person accepts the suggestion).
734 */
735 stalePagesForAgent(
736 workspace: string,
737 agentId: string,
738 viewer: User,
739 options?: { repo?: string | null; since?: string | null },
740 audience?: DocAudience | null,
741 ): Promise<Result<DocStalePage[]>>;
Docs: a workspace knowledge base people and agents write together742};
743
744async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
745 const response = await service.fetch(`https://service/rpc/${method}`, {
746 method: "POST",
747 headers: { "content-type": "application/json" },
748 body: JSON.stringify(args),
749 });
750 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
751 return (await response.json()) as T;
752}
753
754export function docsClient(service: ServiceBinding): DocsApi {
755 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
756 return {
757 sidebar: (workspace, viewer) => call("sidebar", { workspace, viewer }),
758 home: (workspace, viewer, options) => call("home", { workspace, viewer, project: options?.project ?? null }),
759 space: (workspace, spaceSlug, viewer) => call("space", { workspace, space: spaceSlug, viewer }),
Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell.760 spaces: (workspace, viewer) => call("spaces", { workspace, viewer }),
Docs: a workspace knowledge base people and agents write together761 createSpace: (workspace, viewer, input) => call("create_space", { workspace, viewer, input }),
762 updateSpace: (workspace, spaceId, viewer, change) => call("update_space", { workspace, space_id: spaceId, viewer, change }),
763 setSpaceMember: (workspace, spaceId, viewer, member, role) => call("set_space_member", { workspace, space_id: spaceId, viewer, member, role }),
764 page: (workspace, pageId, viewer) => call("page", { workspace, page_id: pageId, viewer }),
765 createPage: (workspace, viewer, input) => call("create_page", { workspace, viewer, input }),
766 updatePage: (workspace, pageId, viewer, change) => call("update_page", { workspace, page_id: pageId, viewer, change }),
767 movePage: (workspace, pageId, viewer, move) => call("move_page", { workspace, page_id: pageId, viewer, move }),
768 duplicatePage: (workspace, pageId, viewer) => call("duplicate_page", { workspace, page_id: pageId, viewer }),
769 archivePage: (workspace, pageId, viewer) => call("archive_page", { workspace, page_id: pageId, viewer }),
770 restorePage: (workspace, pageId, viewer) => call("restore_page", { workspace, page_id: pageId, viewer }),
771 deletePage: (workspace, pageId, viewer) => call("delete_page", { workspace, page_id: pageId, viewer }),
772 trash: (workspace, viewer) => call("trash", { workspace, viewer }),
773 favorite: (workspace, pageId, viewer, on) => call("favorite", { workspace, page_id: pageId, viewer, on }),
774 search: (workspace, viewer, query) => call("search", { workspace, viewer, query }),
775 versions: (workspace, pageId, viewer) => call("versions", { workspace, page_id: pageId, viewer }),
776 version: (workspace, pageId, versionId, viewer) => call("version", { workspace, page_id: pageId, version_id: versionId, viewer }),
777 restoreVersion: (workspace, pageId, versionId, viewer) => call("restore_version", { workspace, page_id: pageId, version_id: versionId, viewer }),
778 templates: (workspace, viewer) => call("templates", { workspace, viewer }),
779 saveTemplate: (workspace, viewer, input) => call("save_template", { workspace, viewer, input }),
780 deleteTemplate: (workspace, templateId, viewer) => call("delete_template", { workspace, template_id: templateId, viewer }),
781 exportPage: (workspace, pageId, viewer) => call("export_page", { workspace, page_id: pageId, viewer }),
782 exportSpace: (workspace, spaceId, viewer) => call("export_space", { workspace, space_id: spaceId, viewer }),
783 suggestions: (workspace, pageId, viewer) => call("suggestions", { workspace, page_id: pageId, viewer }),
784 decideSuggestion: (workspace, suggestionId, viewer, decision) => call("decide_suggestion", { workspace, suggestion_id: suggestionId, viewer, decision }),
785 acceptAll: (workspace, pageId, viewer) => call("accept_all", { workspace, page_id: pageId, viewer }),
786 thread: (workspace, pageId, viewer, action) => call("thread", { workspace, page_id: pageId, viewer, action }),
787 threads: (workspace, pageId, viewer) => call("threads", { workspace, page_id: pageId, viewer }),
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store788 markCurrent: (workspace, pageId, viewer) => call("mark_current", { workspace, page_id: pageId, viewer }),
789 stalePages: (workspace, viewer, options) => call("stale_pages", { workspace, viewer, repo: options?.repo ?? null }),
790 addRepoSpace: (workspace, viewer, repo) => call("add_repo_space", { workspace, viewer, repo }),
791 removeRepoSpace: (workspace, viewer, id) => call("remove_repo_space", { workspace, viewer, id }),
792 repoPage: (workspace, viewer, repo, path) => call("repo_page", { workspace, viewer, repo, path }),
Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people793 reindexDocs: (workspace, viewer) => call("reindex_docs", { workspace, viewer }),
Docs: a workspace knowledge base people and agents write together794 spacesForAgent: (workspace, agentId, viewer, audience) => call("spaces_for_agent", { workspace, agent_id: agentId, viewer, audience: audience ?? null }),
795 pageMarkdown: (workspace, agentId, viewer, pageId, audience) =>
796 call("page_markdown", { workspace, agent_id: agentId, viewer, page_id: pageId, audience: audience ?? null }),
797 searchForAgent: (workspace, agentId, viewer, query, audience) => call("search_for_agent", { workspace, agent_id: agentId, viewer, query, audience: audience ?? null }),
798 suggestEdit: (workspace, agentId, viewer, pageId, edit) => call("suggest_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
799 applyEdit: (workspace, agentId, viewer, pageId, edit) => call("apply_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
800 createPageAsAgent: (workspace, agentId, viewer, input) => call("create_page_as_agent", { workspace, agent_id: agentId, viewer, input }),
Agents recall what Docs say before they answer or work, and each has required reading801 recallForAgent: (workspace, agentId, viewer, input, audience) =>
802 call("recall_for_agent", { workspace, agent_id: agentId, viewer, ...input, audience: audience ?? null }),
Docs: a workspace knowledge base people and agents write together803 threadsForAgent: (workspace, agentId, viewer, pageId, audience) =>
804 call("threads_for_agent", { workspace, agent_id: agentId, viewer, page_id: pageId, audience: audience ?? null }),
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store805 stalePagesForAgent: (workspace, agentId, viewer, options, audience) =>
806 call("stale_pages_for_agent", { workspace, agent_id: agentId, viewer, repo: options?.repo ?? null, since: options?.since ?? null, audience: audience ?? null }),
Docs: a workspace knowledge base people and agents write together807 };
808}

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