Skip to content
800 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;
Docs: a workspace knowledge base people and agents write together88 /** The workspace's General space, made the first time Docs is opened. Can't be archived. */
89 is_default: boolean;
90 /** Projects (repositories, `owner/name`) the space is about: Docs filters by them. */
91 projects: string[];
92 created_by: Principal;
93 created_at: string;
94 archived_at: string | null;
95 /** The viewer's role in it. */
96 viewer_role: DocRole;
97 /** Pages in it that are not in the trash. */
98 page_count: number;
99};
100
101/** A space member, as the space's settings show it. */
102export type DocSpaceMember = {
103 key: DocMemberKey;
104 kind: "user" | "agent" | "team";
105 /** Username, agent handle or team slug. */
106 name: string;
107 display_name: string;
108 avatar: string | null;
109 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.110 look?: AgentLook | null;
Docs: a workspace knowledge base people and agents write together111 role: DocRole;
112};
113
114/** Enough to link to a page. */
115export type DocPageRef = {
116 id: string;
117 space_id: string;
118 space_slug: string;
119 title: string;
120 /** An emoji, or null for the default page icon. */
121 icon: string | null;
122 /** `<title-slug>-<id>`: the last part of the page's URL. */
123 slug: string;
124 /** `/<workspace>/-/docs/<space>/<slug>`. */
125 path: string;
126};
127
128export type DocPage = DocPageRef & {
129 parent_id: string | null;
130 position: number;
131 /** A cover image's URL, or a CSS gradient name (`gradient:<n>`); null for none. */
132 cover: string | null;
133 created_by: MemberProfile;
134 created_at: string;
135 updated_by: MemberProfile | null;
136 updated_at: string;
137 /** When it went to the trash; null when it is not there. */
138 archived_at: string | null;
139 has_children: boolean;
140 /** Projects (`owner/name`) the page is about, besides its space's. */
141 projects: string[];
142 owners: MemberProfile[];
143 /** The first lines of its text, for cards. */
144 excerpt: string;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store145 /** Whether code it cites changed since someone last marked it current: possibly out of date. */
146 stale: boolean;
147};
148
149// ── Citations and staleness ───────────────────────────────────────────────
150//
151// A page can cite code: a path (a file, a folder, or a glob like
152// `src/export/**`) in a repository, optionally naming what at that path it
153// describes (a symbol, an endpoint, an environment variable). Citations
154// come from the page's text (the editor's citation chips, and links to
155// files in a repository: `/<owner>/<repo>/blob/<ref>/<path>`) and from
156// the page's header ("Describes"). When a merged pull request or a push
157// to a repository's default branch changes a cited path, the page is
158// marked possibly out of date with that change, until someone with edit
159// access marks it current again (or an agent updates it).
160
161/** What a citation names at its path. */
162export type DocCitationKind = "path" | "symbol" | "endpoint" | "env";
163export const DOC_CITATION_KINDS: readonly DocCitationKind[] = ["path", "symbol", "endpoint", "env"];
164
165export const DOC_CITATION_KIND_LABELS: Record<DocCitationKind, string> = {
166 path: "A file or folder",
167 symbol: "A symbol",
168 endpoint: "An endpoint",
169 env: "An environment variable",
170};
171
172export type DocCitation = {
173 /** `owner/name`, lowercased. */
174 repo: string;
175 /** A file, a folder (everything under it), or a glob (`*`, `**`, `?`). */
176 path: string;
177 kind: DocCitationKind;
178 /** The symbol, endpoint (`POST /v1/export`) or variable (`EXPORT_BUCKET`), for those kinds. */
179 label: string | null;
180 /** The commit it was cited at, when known. */
181 ref: string | null;
182 /** From the page's text, or from its header's "Describes". */
183 source: "body" | "header";
184};
185
186/** One entry of a page's "Describes": a repository and a path in it. */
187export type DocDescribes = { repo: string; path: string };
188
189/** A change that made a page possibly out of date. */
190export type DocStaleChange = {
191 /**
192 * False when the viewer can't read the repository: then `repo`, `pull`,
193 * `commit` and `paths` are blank, and the page only says that a change
194 * they can't see touched code it cites.
195 */
196 visible: boolean;
197 repo: string | null;
198 commit: string | null;
199 /** The merged pull request, when the change came from one. */
200 pull: { number: number; title: string | null } | null;
201 /** The cited paths it changed (the changed files, at most 20). */
202 paths: string[];
203 /** When it was noticed. RFC 3339. */
204 at: string;
205};
206
207/** Why a page is possibly out of date: every change since it was last marked current, newest first. */
208export type DocStaleness = { since: string; changes: DocStaleChange[] };
209
210/** A page an agent may bring up to date, with what changed. */
211export type DocStalePage = {
212 page: DocPageRef & { updated_at: string };
213 space: { id: string; slug: string; name: string; agent_mode: DocAgentMode };
214 can: DocAgentAbilities;
215 owners: MemberProfile[];
216 citations: DocCitation[];
217 /** Changes in repositories the viewer (and audience) can read; newest first. */
218 changes: DocStaleChange[];
219 since: string;
220};
221
222// ── A project's docs ──────────────────────────────────────────────────────
223//
224// A repository's `docs/` folder (and README.md), shown read-only in Docs
225// next to the workspace's spaces and found by the same search. It is read
226// from the default branch and kept up to date on every push to it. Each
227// reader sees only the repositories they can read. Changes go through the
228// repository: "Edit in Code" opens the file.
229
230export type DocRepoFile = {
231 /** From the repository's root: `docs/guide/setup.md`, `README.md`. */
232 path: string;
233 /** Its first heading, or its file name. */
234 title: string;
235};
236
237export type DocRepoSpace = {
238 id: string;
239 /** `owner/name`, as the repository is named now. */
240 repo: string;
241 default_branch: string;
242 /** The commit it was read at; null until it has been. */
243 commit: string | null;
244 indexed_at: string | null;
245 added_by: MemberProfile;
246 files: DocRepoFile[];
247 /** Whether the viewer may stop showing it (whoever added it, or an owner). */
248 can_remove: boolean;
Docs: a workspace knowledge base people and agents write together249};
250
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store251export type DocRepoPage = {
252 space: DocRepoSpace;
253 file: DocRepoFile & {
254 markdown: string;
255 /** `/<workspace>/-/docs/repo/<owner>/<name>/<path>`. */
256 href: string;
257 /** The file in Code, on the default branch. */
258 code_href: string;
259 };
260};
261
Docs: a workspace knowledge base people and agents write together262/** A page as the sidebar's tree lists it: flat, ordered by `position` within each parent. */
263export type DocTreeNode = {
264 id: string;
265 parent_id: string | null;
266 position: number;
267 title: string;
268 icon: string | null;
269 slug: string;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store270 /** Possibly out of date (see `DocPage.stale`). */
271 stale?: boolean;
Docs: a workspace knowledge base people and agents write together272};
273
274export type DocsSidebarSpace = DocSpace & { pages: DocTreeNode[] };
275
276export type DocsSidebar = {
277 spaces: DocsSidebarSpace[];
278 favorites: DocPageRef[];
279 /** The pages the viewer opened last, newest first. */
280 recent: DocPageRef[];
281 /** Whether the viewer may make spaces (members of the workspace may). */
282 can_create_space: boolean;
283 trash_count: number;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store284 /** Pages the viewer can read that are possibly out of date. */
285 stale_count: number;
286 /** Projects' docs folders shown in Docs, those whose repository the viewer can read. */
287 repos: DocRepoSpace[];
Docs: a workspace knowledge base people and agents write together288};
289
290export type DocsHome = {
291 /** Recently edited pages the viewer can read, newest first. */
292 recent: DocPage[];
293 /** Pages the viewer made or owns. */
294 mine: DocPage[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store295 /** Pages possibly out of date, most recently flagged first. */
296 stale: DocPage[];
Docs: a workspace knowledge base people and agents write together297 spaces: DocSpace[];
298 /** Every project some space or page is linked to, for the filter. */
299 projects: string[];
300 /** The project the lists are filtered to, or null. */
301 project: string | null;
302};
303
304/** A tracked change an agent proposed. `blocks` targets name top-level block ids from `page_markdown`. */
305export type DocEditTarget =
306 /** Add to the end of the page. */
307 | { kind: "append" }
308 /** Replace the whole page. */
309 | { kind: "document" }
310 /** 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. */
311 | { kind: "section"; heading: string }
312 /** Replace top-level blocks `from_block` through `to_block`, inclusive (with any blocks nested under them). */
313 | { kind: "blocks"; from_block: string; to_block: string };
314
315export type DocSuggestionStatus = "open" | "accepted" | "rejected" | "stale";
316
317export type DocSuggestion = {
318 id: string;
319 page_id: string;
320 /** The agent that suggested it. */
321 author: MemberProfile;
322 /** The person it acted for. */
323 asked_by: MemberProfile | null;
324 target: DocEditTarget;
325 /** The target's Markdown when it was suggested. */
326 before_markdown: string;
327 /** What it proposes instead. */
328 after_markdown: string;
329 /** Why, in a line. */
330 note: string | null;
331 status: DocSuggestionStatus;
332 created_at: string;
333 decided_by: MemberProfile | null;
334 decided_at: string | null;
335 /** The top-level blocks it covers now, so the editor can mark them; empty for an append or when the target is gone. */
336 block_ids: string[];
337};
338
339export type DocVersionKind = "created" | "edit" | "agent" | "suggestion" | "restore";
340
341export type DocVersion = {
342 id: string;
343 page_id: string;
344 created_at: string;
345 /** Everyone whose changes are in it, people and agents. */
346 authors: MemberProfile[];
347 kind: DocVersionKind;
348 /** "Suggested by @inky, accepted by @ana"; "Restored from Oct 3, 14:02". */
349 note: string | null;
350};
351
352export type DocDiffLine = { op: "same" | "add" | "del"; text: string };
353
354export type DocVersionDetail = DocVersion & {
355 markdown: string;
356 /** Against the version before it (or nothing, for the first). */
357 diff: DocDiffLine[];
358};
359
360export type DocPageDetail = {
361 page: DocPage;
362 space: DocSpace;
363 /** The page's ancestors, root first. */
364 breadcrumbs: DocPageRef[];
365 /** As last saved: the read view, and what the editor shows before the socket connects. */
366 markdown: string;
367 role: DocRole;
368 backlinks: DocPageRef[];
369 children: DocPageRef[];
370 favorite: boolean;
371 /** When the viewer last opened it, before now. */
372 last_viewed_at: string | null;
373 suggestions: DocSuggestion[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store374 /** Code the page cites: from its text and its header. */
375 citations: DocCitation[];
376 /** The header's "Describes" list. */
377 describes: DocDescribes[];
378 /** Why it is possibly out of date; null when it isn't. */
379 staleness: DocStaleness | null;
Docs: a workspace knowledge base people and agents write together380};
381
382export type DocSearchHit = DocPageRef & {
383 space_name: string;
384 /** Text around the match; `[[` and `]]` mark matched words. */
385 snippet: string;
386 updated_at: string;
387 projects: string[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store388 /** Set for a file from a project's docs folder (then `id` is `repo:<space>:<path>` and `path` its address in Docs). */
389 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 people390 /** The heading of the passage that matched, when search found one (`mode: "hybrid"`). */
391 heading?: string | null;
392 /** How it was found: by its words, by meaning (the semantic index), or both. */
393 matched?: "words" | "meaning" | "both" | null;
Docs: a workspace knowledge base people and agents write together394};
395
396export type DocTemplate = {
397 id: string;
398 name: string;
399 description: string;
400 icon: string;
401 /** Built in (meeting notes, RFC, ...) rather than saved by the workspace. */
402 builtin: boolean;
403 markdown: string;
404 created_by: Principal | null;
405};
406
407/** One top-level block, as agents see a page. */
408export type DocBlockOutline = {
409 id: string;
410 /** BlockNote's type: `heading`, `paragraph`, `bulletListItem`, `codeBlock`, `callout`, ... */
411 type: string;
412 /** For a heading. */
413 level: number | null;
414 /** The block and anything nested under it. */
415 markdown: string;
416};
417
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.418/**
419 * What an agent may do on a page, for the person it acts for: read it,
420 * suggest changes, edit its body (only in a space whose agents edit), and
421 * attach a file it made (whenever the person could edit: a file beside the
422 * page changes nothing in it).
423 */
424export type DocAgentAbilities = { read: boolean; suggest: boolean; edit: boolean; attach: boolean };
Docs: a workspace knowledge base people and agents write together425
426export type DocAgentPage = {
427 page: DocPageRef & { updated_at: string };
428 space: { id: string; slug: string; name: string; agent_mode: DocAgentMode };
429 markdown: string;
430 blocks: DocBlockOutline[];
431 can: DocAgentAbilities;
432};
433
434/** Who will see what an agent says; it reads only what they all can. */
435export type DocAudience =
436 /** These people (by user id), e.g. a DM's or a private channel's members. */
437 | { kind: "people"; user_ids: string[] }
438 /** Everyone in the workspace, e.g. a public channel: only workspace-wide spaces. */
439 | { kind: "workspace" };
440
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store441/** An agent's edit to a page. */
442export type DocAgentEdit = {
443 target: DocEditTarget;
444 markdown: string;
445 note?: string | null;
446 /** The edit brings the page up to date with the code it cites: once applied, the page is no longer possibly out of date. */
447 marks_current?: boolean | null;
448};
449
Docs: a workspace knowledge base people and agents write together450/** What `apply_edit` did: applied it, or filed a suggestion because it may not edit there. */
451export type DocAgentEditResult =
452 | { mode: "applied"; version_id: string | null; page: DocPageRef }
453 | { mode: "suggested"; suggestion: DocSuggestion; page: DocPageRef };
454
455export type NewDocSpace = {
456 name: string;
457 slug?: string | null;
458 description?: string | null;
459 icon?: string | null;
460 kind: DocSpaceKind;
461 team?: string | null;
462 default_role?: DocRole | null;
463 agent_mode?: DocAgentMode | null;
464 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.465 editors_can_share?: boolean | null;
Docs: a workspace knowledge base people and agents write together466};
467
468export type DocSpaceChange = Partial<Omit<NewDocSpace, "kind">> & { kind?: DocSpaceKind; archived?: boolean };
469
470export type NewDocPage = {
471 space_id: string;
472 parent_id?: string | null;
473 title?: string | null;
474 icon?: string | null;
475 /** A built-in template's id (`builtin:<name>`) or a saved one's. */
476 template_id?: string | null;
477 /** Starting Markdown, when there is no template. */
478 markdown?: string | null;
479 projects?: string[] | null;
480};
481
482export type DocPageChange = {
483 title?: string;
484 icon?: string | null;
485 cover?: string | null;
486 projects?: string[];
487 /** Member keys (`user:<id>`, `agent:<id>`). */
488 owners?: string[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store489 /** Replaces the header's "Describes" list (at most 20). */
490 describes?: DocDescribes[];
Docs: a workspace knowledge base people and agents write together491};
492
493/** Where a page goes: under `parent_id` (null for the top of the space), before `before_id` (null for the end). */
494export type DocMove = { space_id?: string | null; parent_id: string | null; before_id?: string | null };
495
496export type DocSearchQuery = {
497 query: string;
498 space_id?: string | null;
499 /** `owner/name`: pages linked to it, or in a space linked to it. */
500 project?: string | null;
501 limit?: number | null;
Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people502 /**
503 * `words` (the default): titles and text by their words, as you type.
504 * `hybrid`: words and meaning (the semantic index) together, each hit
505 * with the passage and heading that matched; the Docs search page.
506 */
507 mode?: "words" | "hybrid" | null;
Docs: a workspace knowledge base people and agents write together508};
509
510/**
511 * The comment operations of the editor's thread store (BlockNote's
512 * `RESTYjsThreadStore`), applied by the page's room to the `threads` map in
513 * the page's Yjs document, so every open editor sees them at once.
514 */
515export type DocThreadAction =
516 | { op: "create"; body: unknown; metadata?: unknown; page_level?: boolean }
517 /** Anchors a thread to the text between two Yjs relative positions (JSON). */
518 | { op: "anchor"; thread_id: string; anchor: unknown; head: unknown }
519 | { op: "comment"; thread_id: string; body: unknown; metadata?: unknown }
520 | { op: "edit_comment"; thread_id: string; comment_id: string; body: unknown; metadata?: unknown }
521 | { op: "delete_comment"; thread_id: string; comment_id: string; soft?: boolean }
522 | { op: "delete_thread"; thread_id: string }
523 | { op: "resolve" | "unresolve"; thread_id: string }
524 | { op: "react" | "unreact"; thread_id: string; comment_id: string; emoji: string };
525
526/** A comment thread, as agents and the inbox see it. */
527export type DocThread = {
528 id: string;
529 /** The text it is on; null for a comment on the whole page. */
530 quote: string | null;
531 resolved: boolean;
532 comments: { id: string; author: MemberProfile; text: string; created_at: string }[];
533};
534
535/** A file put in a page: served from the usercontent origin. */
536export type DocFile = { id: string; url: string; name: string; content_type: string; bytes: number };
537
538/**
539 * What a page's live socket carries besides the Yjs protocol (binary
540 * frames: y-protocols sync and awareness). These are JSON text frames.
541 */
542export type DocsLiveEvent =
543 | { type: "page.updated"; page: DocPage }
544 | { type: "page.archived"; page_id: string }
545 | { type: "suggestion.created" | "suggestion.updated"; suggestion: DocSuggestion }
546 | { type: "version.created"; version: DocVersion }
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store547 /** 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). */
548 | { type: "page.staleness" }
Docs: a workspace knowledge base people and agents write together549 /** The viewer's role changed, or their access ended (`role` null). */
550 | { type: "access"; role: DocRole | null };
551
552/**
553 * 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.554 * viewer, as JSON. Trusted only because the artifacts service is reachable
Docs: a workspace knowledge base people and agents write together555 * through service bindings alone.
556 */
557export const DOCS_VIEWER_HEADER = "x-g1t-docs-viewer";
558
559/** The largest file a page takes, in bytes. */
560export const DOC_MAX_FILE_BYTES = 25 * 1024 * 1024;
561
562/** The name of the Yjs XML fragment that holds a page's blocks. */
563export const DOC_FRAGMENT = "document-store";
564/** The name of the Yjs map that holds a page's comment threads. */
565export const DOC_THREADS = "threads";
566
Agents recall what Docs say before they answer or work, and each has required reading567/** One passage of Docs, recalled for an agent: a section of a page or of a repository's docs. */
568export type DocPassage = {
569 /** The page; null for a repository's docs file. */
570 page: DocPageRef | null;
571 /** A repository's docs file: `owner/name`, its path, and where it reads in Docs. */
572 repo_file: { repo: string; path: string; href: string } | null;
573 space_name: string;
574 /** The heading the passage sits under, if any. */
575 heading: string | null;
576 /** The passage as Markdown, at most about 1,500 characters. */
577 text: string;
578 /** How close it is, 0 to 1. */
579 score: number;
580 updated_at: string;
581 /** The page is marked possibly out of date. */
582 stale: boolean;
583};
584
Docs: a workspace knowledge base people and agents write together585export type DocsApi = {
586 // ── The site ─────────────────────────────────────────────────────────
587
588 /** Spaces with their page trees, favorites, recent pages. Makes the General space the first time. */
589 sidebar(workspace: string, viewer: User): Promise<Result<DocsSidebar>>;
590 home(workspace: string, viewer: User, options?: { project?: string | null }): Promise<Result<DocsHome>>;
591 space(workspace: string, spaceSlug: string, viewer: User): Promise<Result<{ space: DocSpace; members: DocSpaceMember[]; pages: DocPage[] }>>;
592 createSpace(workspace: string, viewer: User, input: NewDocSpace): Promise<Result<DocSpace>>;
593 /** Manage role only. */
594 updateSpace(workspace: string, spaceId: string, viewer: User, change: DocSpaceChange): Promise<Result<DocSpace>>;
595 /** Adds, changes (`role`) or removes (`role` null) a member. Manage role only; the last manager stays. */
596 setSpaceMember(workspace: string, spaceId: string, viewer: User, member: DocMemberKey, role: DocRole | null): Promise<Result<DocSpaceMember[]>>;
597
598 /** A page, and the viewer's role in it; records the view. Not found when they can't read it. */
599 page(workspace: string, pageId: string, viewer: User): Promise<Result<DocPageDetail>>;
600 createPage(workspace: string, viewer: User, input: NewDocPage): Promise<Result<DocPage>>;
601 updatePage(workspace: string, pageId: string, viewer: User, change: DocPageChange): Promise<Result<DocPage>>;
602 /** Edit role in both spaces. Refuses moving a page under itself. */
603 movePage(workspace: string, pageId: string, viewer: User, move: DocMove): Promise<Result<DocPage>>;
604 duplicatePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
605 /** To the trash, with every page under it. */
606 archivePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
607 restorePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
608 /** For good: only from the trash, manage role. */
609 deletePage(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
610 trash(workspace: string, viewer: User): Promise<Result<DocPage[]>>;
611 favorite(workspace: string, pageId: string, viewer: User, on: boolean): Promise<Result<boolean>>;
612
613 search(workspace: string, viewer: User, query: DocSearchQuery): Promise<Result<DocSearchHit[]>>;
614
615 versions(workspace: string, pageId: string, viewer: User): Promise<Result<DocVersion[]>>;
616 version(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersionDetail>>;
617 /** Makes the page what it was then, as a new version. Edit role. */
618 restoreVersion(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersion>>;
619
620 templates(workspace: string, viewer: User): Promise<Result<DocTemplate[]>>;
621 /** Saves a page as one of the workspace's templates. */
622 saveTemplate(workspace: string, viewer: User, input: { page_id: string; name: string; description?: string | null }): Promise<Result<DocTemplate>>;
623 deleteTemplate(workspace: string, templateId: string, viewer: User): Promise<Result<boolean>>;
624
625 exportPage(workspace: string, pageId: string, viewer: User): Promise<Result<{ filename: string; markdown: string }>>;
626 /** Every page in a space as Markdown, at paths that follow the tree. */
627 exportSpace(workspace: string, spaceId: string, viewer: User): Promise<Result<{ name: string; files: { path: string; markdown: string }[] }>>;
628
629 suggestions(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
630 /** Accept (applies it to the live document, attributed to both) or reject. Edit role. */
631 decideSuggestion(workspace: string, suggestionId: string, viewer: User, decision: "accept" | "reject"): Promise<Result<DocSuggestion>>;
632 acceptAll(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
633
634 /** A comment operation from the editor; comment role (deleting others' comments or threads needs edit). */
635 thread(workspace: string, pageId: string, viewer: User, action: DocThreadAction): Promise<Result<unknown>>;
636 threads(workspace: string, pageId: string, viewer: User): Promise<Result<DocThread[]>>;
637
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store638 /** Clears "possibly out of date": the page says what the code does now. Edit role. */
639 markCurrent(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
640 /** Pages the viewer can read that are possibly out of date, most recently flagged first; `repo` (`owner/name`) narrows to changes there. */
641 stalePages(workspace: string, viewer: User, options?: { repo?: string | null }): Promise<Result<DocPage[]>>;
642
643 /**
644 * Shows a repository's `docs/` folder and README.md in Docs, read from
645 * its default branch. Any member who can read the repository may; the
646 * files are read at once and again on every push to the default branch.
647 */
648 addRepoSpace(workspace: string, viewer: User, repo: string): Promise<Result<DocRepoSpace>>;
649 /** Stops showing it. Whoever added it, or a workspace owner. */
650 removeRepoSpace(workspace: string, viewer: User, id: string): Promise<Result<boolean>>;
651 /** One file of a project's docs, for a viewer who can read the repository; not found otherwise. */
652 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 people653 /**
654 * Indexes the workspace's pages and projects' docs for agents' recall
655 * again, in batches in the background. Workspace owners. True when a run
656 * started (false: one is already going).
657 */
658 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 store659
Docs: a workspace knowledge base people and agents write together660 // ── Agents (services/agents) ─────────────────────────────────────────
661 //
662 // Each takes the agent and the person it acts for (`viewer`, the asker).
663 // The service checks the agent is a live agent of the workspace and caps
664 // everything by the viewer's access; `audience`, when given, narrows
665 // reads further to what every person in it can read. A page the agent
666 // may not read is not found, exactly as one that does not exist.
667
668 /** Spaces the agent may read for the viewer (and audience), with what it may do in each. */
669 spacesForAgent(workspace: string, agentId: string, viewer: User, audience?: DocAudience | null): Promise<Result<(Pick<DocSpace, "id" | "slug" | "name" | "description" | "kind" | "agent_mode" | "projects"> & { can: DocAgentAbilities })[]>>;
670 /** A page's Markdown and its top-level blocks (ids for `blocks` targets). */
671 pageMarkdown(workspace: string, agentId: string, viewer: User, pageId: string, audience?: DocAudience | null): Promise<Result<DocAgentPage>>;
672 /** Full-text search over pages every reader can read; at most 20, best first. */
673 searchForAgent(workspace: string, agentId: string, viewer: User, query: DocSearchQuery, audience?: DocAudience | null): Promise<Result<DocSearchHit[]>>;
674 /** 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 store675 suggestEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: DocAgentEdit): Promise<Result<DocSuggestion>>;
Docs: a workspace knowledge base people and agents write together676 /**
677 * Applies an edit to the live document when the space lets agents edit
678 * and the viewer can edit; otherwise files it as a suggestion (and says
679 * so in `mode`). Attributed to the agent in the page's history.
680 */
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store681 applyEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: DocAgentEdit): Promise<Result<DocAgentEditResult>>;
Docs: a workspace knowledge base people and agents write together682 /**
683 * A new page, written by the agent: in `space_id` (the General space
684 * when null), under `parent_id`. Needs the viewer's edit role there
685 * (whatever the space's agent mode: a new page changes nothing anyone
686 * wrote). `source` links where it came from ("write this up").
687 */
688 createPageAsAgent(
689 workspace: string,
690 agentId: string,
691 viewer: User,
692 input: { space_id?: string | null; parent_id?: string | null; title: string; icon?: string | null; markdown: string; source?: { title: string; href: string } | null },
693 ): Promise<Result<DocPageRef>>;
Agents recall what Docs say before they answer or work, and each has required reading694 /**
695 * What the workspace's Docs say about `query`, for an agent about to
696 * answer: the passages closest in meaning (and, where meaning finds too
697 * little, in words), each with the page and heading it came from. Only
698 * from spaces the viewer can read and, with `audience`, everyone it
699 * covers; repository docs only from repositories they can all read.
700 * `spaces` narrows to these space ids first (an agent's required
701 * reading) and fills from the rest. Empty when nothing is close enough.
702 */
703 recallForAgent(
704 workspace: string,
705 agentId: string,
706 viewer: User,
707 input: { query: string; limit?: number | null; spaces?: string[] | null },
708 audience?: DocAudience | null,
709 ): Promise<Result<DocPassage[]>>;
Docs: a workspace knowledge base people and agents write together710 /** A page's comment threads, for an agent asked about them. */
711 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 store712 /**
713 * Pages possibly out of date that the agent may read for the viewer (and
714 * audience), each with the changes that made it so: for a documenter
715 * routine to bring them up to date. At most 50, most recently flagged
716 * first.
717 *
718 * - `repo` (`owner/name`): only pages made stale by a change there.
719 * - `since` (RFC 3339): only pages flagged at or after it.
720 *
721 * Changes in repositories the viewer can't read are left out, and a page
722 * whose every change is one of those is left out too: an agent never
723 * learns of code its person can't see. To update a page, read it
724 * (`pageMarkdown`), then `applyEdit` or `suggestEdit` with
725 * `marks_current: true`: the page is marked current when the edit is
726 * applied (at once, or when a person accepts the suggestion).
727 */
728 stalePagesForAgent(
729 workspace: string,
730 agentId: string,
731 viewer: User,
732 options?: { repo?: string | null; since?: string | null },
733 audience?: DocAudience | null,
734 ): Promise<Result<DocStalePage[]>>;
Docs: a workspace knowledge base people and agents write together735};
736
737async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
738 const response = await service.fetch(`https://service/rpc/${method}`, {
739 method: "POST",
740 headers: { "content-type": "application/json" },
741 body: JSON.stringify(args),
742 });
743 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
744 return (await response.json()) as T;
745}
746
747export function docsClient(service: ServiceBinding): DocsApi {
748 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
749 return {
750 sidebar: (workspace, viewer) => call("sidebar", { workspace, viewer }),
751 home: (workspace, viewer, options) => call("home", { workspace, viewer, project: options?.project ?? null }),
752 space: (workspace, spaceSlug, viewer) => call("space", { workspace, space: spaceSlug, viewer }),
753 createSpace: (workspace, viewer, input) => call("create_space", { workspace, viewer, input }),
754 updateSpace: (workspace, spaceId, viewer, change) => call("update_space", { workspace, space_id: spaceId, viewer, change }),
755 setSpaceMember: (workspace, spaceId, viewer, member, role) => call("set_space_member", { workspace, space_id: spaceId, viewer, member, role }),
756 page: (workspace, pageId, viewer) => call("page", { workspace, page_id: pageId, viewer }),
757 createPage: (workspace, viewer, input) => call("create_page", { workspace, viewer, input }),
758 updatePage: (workspace, pageId, viewer, change) => call("update_page", { workspace, page_id: pageId, viewer, change }),
759 movePage: (workspace, pageId, viewer, move) => call("move_page", { workspace, page_id: pageId, viewer, move }),
760 duplicatePage: (workspace, pageId, viewer) => call("duplicate_page", { workspace, page_id: pageId, viewer }),
761 archivePage: (workspace, pageId, viewer) => call("archive_page", { workspace, page_id: pageId, viewer }),
762 restorePage: (workspace, pageId, viewer) => call("restore_page", { workspace, page_id: pageId, viewer }),
763 deletePage: (workspace, pageId, viewer) => call("delete_page", { workspace, page_id: pageId, viewer }),
764 trash: (workspace, viewer) => call("trash", { workspace, viewer }),
765 favorite: (workspace, pageId, viewer, on) => call("favorite", { workspace, page_id: pageId, viewer, on }),
766 search: (workspace, viewer, query) => call("search", { workspace, viewer, query }),
767 versions: (workspace, pageId, viewer) => call("versions", { workspace, page_id: pageId, viewer }),
768 version: (workspace, pageId, versionId, viewer) => call("version", { workspace, page_id: pageId, version_id: versionId, viewer }),
769 restoreVersion: (workspace, pageId, versionId, viewer) => call("restore_version", { workspace, page_id: pageId, version_id: versionId, viewer }),
770 templates: (workspace, viewer) => call("templates", { workspace, viewer }),
771 saveTemplate: (workspace, viewer, input) => call("save_template", { workspace, viewer, input }),
772 deleteTemplate: (workspace, templateId, viewer) => call("delete_template", { workspace, template_id: templateId, viewer }),
773 exportPage: (workspace, pageId, viewer) => call("export_page", { workspace, page_id: pageId, viewer }),
774 exportSpace: (workspace, spaceId, viewer) => call("export_space", { workspace, space_id: spaceId, viewer }),
775 suggestions: (workspace, pageId, viewer) => call("suggestions", { workspace, page_id: pageId, viewer }),
776 decideSuggestion: (workspace, suggestionId, viewer, decision) => call("decide_suggestion", { workspace, suggestion_id: suggestionId, viewer, decision }),
777 acceptAll: (workspace, pageId, viewer) => call("accept_all", { workspace, page_id: pageId, viewer }),
778 thread: (workspace, pageId, viewer, action) => call("thread", { workspace, page_id: pageId, viewer, action }),
779 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 store780 markCurrent: (workspace, pageId, viewer) => call("mark_current", { workspace, page_id: pageId, viewer }),
781 stalePages: (workspace, viewer, options) => call("stale_pages", { workspace, viewer, repo: options?.repo ?? null }),
782 addRepoSpace: (workspace, viewer, repo) => call("add_repo_space", { workspace, viewer, repo }),
783 removeRepoSpace: (workspace, viewer, id) => call("remove_repo_space", { workspace, viewer, id }),
784 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 people785 reindexDocs: (workspace, viewer) => call("reindex_docs", { workspace, viewer }),
Docs: a workspace knowledge base people and agents write together786 spacesForAgent: (workspace, agentId, viewer, audience) => call("spaces_for_agent", { workspace, agent_id: agentId, viewer, audience: audience ?? null }),
787 pageMarkdown: (workspace, agentId, viewer, pageId, audience) =>
788 call("page_markdown", { workspace, agent_id: agentId, viewer, page_id: pageId, audience: audience ?? null }),
789 searchForAgent: (workspace, agentId, viewer, query, audience) => call("search_for_agent", { workspace, agent_id: agentId, viewer, query, audience: audience ?? null }),
790 suggestEdit: (workspace, agentId, viewer, pageId, edit) => call("suggest_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
791 applyEdit: (workspace, agentId, viewer, pageId, edit) => call("apply_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
792 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 reading793 recallForAgent: (workspace, agentId, viewer, input, audience) =>
794 call("recall_for_agent", { workspace, agent_id: agentId, viewer, ...input, audience: audience ?? null }),
Docs: a workspace knowledge base people and agents write together795 threadsForAgent: (workspace, agentId, viewer, pageId, audience) =>
796 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 store797 stalePagesForAgent: (workspace, agentId, viewer, options, audience) =>
798 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 together799 };
800}