Skip to content
795 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
418/** What an agent may do on a page, for the person it acts for. */
419export type DocAgentAbilities = { read: boolean; suggest: boolean; edit: boolean };
420
421export type DocAgentPage = {
422 page: DocPageRef & { updated_at: string };
423 space: { id: string; slug: string; name: string; agent_mode: DocAgentMode };
424 markdown: string;
425 blocks: DocBlockOutline[];
426 can: DocAgentAbilities;
427};
428
429/** Who will see what an agent says; it reads only what they all can. */
430export type DocAudience =
431 /** These people (by user id), e.g. a DM's or a private channel's members. */
432 | { kind: "people"; user_ids: string[] }
433 /** Everyone in the workspace, e.g. a public channel: only workspace-wide spaces. */
434 | { kind: "workspace" };
435
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store436/** An agent's edit to a page. */
437export type DocAgentEdit = {
438 target: DocEditTarget;
439 markdown: string;
440 note?: string | null;
441 /** The edit brings the page up to date with the code it cites: once applied, the page is no longer possibly out of date. */
442 marks_current?: boolean | null;
443};
444
Docs: a workspace knowledge base people and agents write together445/** What `apply_edit` did: applied it, or filed a suggestion because it may not edit there. */
446export type DocAgentEditResult =
447 | { mode: "applied"; version_id: string | null; page: DocPageRef }
448 | { mode: "suggested"; suggestion: DocSuggestion; page: DocPageRef };
449
450export type NewDocSpace = {
451 name: string;
452 slug?: string | null;
453 description?: string | null;
454 icon?: string | null;
455 kind: DocSpaceKind;
456 team?: string | null;
457 default_role?: DocRole | null;
458 agent_mode?: DocAgentMode | null;
459 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.460 editors_can_share?: boolean | null;
Docs: a workspace knowledge base people and agents write together461};
462
463export type DocSpaceChange = Partial<Omit<NewDocSpace, "kind">> & { kind?: DocSpaceKind; archived?: boolean };
464
465export type NewDocPage = {
466 space_id: string;
467 parent_id?: string | null;
468 title?: string | null;
469 icon?: string | null;
470 /** A built-in template's id (`builtin:<name>`) or a saved one's. */
471 template_id?: string | null;
472 /** Starting Markdown, when there is no template. */
473 markdown?: string | null;
474 projects?: string[] | null;
475};
476
477export type DocPageChange = {
478 title?: string;
479 icon?: string | null;
480 cover?: string | null;
481 projects?: string[];
482 /** Member keys (`user:<id>`, `agent:<id>`). */
483 owners?: string[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store484 /** Replaces the header's "Describes" list (at most 20). */
485 describes?: DocDescribes[];
Docs: a workspace knowledge base people and agents write together486};
487
488/** Where a page goes: under `parent_id` (null for the top of the space), before `before_id` (null for the end). */
489export type DocMove = { space_id?: string | null; parent_id: string | null; before_id?: string | null };
490
491export type DocSearchQuery = {
492 query: string;
493 space_id?: string | null;
494 /** `owner/name`: pages linked to it, or in a space linked to it. */
495 project?: string | null;
496 limit?: number | null;
Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people497 /**
498 * `words` (the default): titles and text by their words, as you type.
499 * `hybrid`: words and meaning (the semantic index) together, each hit
500 * with the passage and heading that matched; the Docs search page.
501 */
502 mode?: "words" | "hybrid" | null;
Docs: a workspace knowledge base people and agents write together503};
504
505/**
506 * The comment operations of the editor's thread store (BlockNote's
507 * `RESTYjsThreadStore`), applied by the page's room to the `threads` map in
508 * the page's Yjs document, so every open editor sees them at once.
509 */
510export type DocThreadAction =
511 | { op: "create"; body: unknown; metadata?: unknown; page_level?: boolean }
512 /** Anchors a thread to the text between two Yjs relative positions (JSON). */
513 | { op: "anchor"; thread_id: string; anchor: unknown; head: unknown }
514 | { op: "comment"; thread_id: string; body: unknown; metadata?: unknown }
515 | { op: "edit_comment"; thread_id: string; comment_id: string; body: unknown; metadata?: unknown }
516 | { op: "delete_comment"; thread_id: string; comment_id: string; soft?: boolean }
517 | { op: "delete_thread"; thread_id: string }
518 | { op: "resolve" | "unresolve"; thread_id: string }
519 | { op: "react" | "unreact"; thread_id: string; comment_id: string; emoji: string };
520
521/** A comment thread, as agents and the inbox see it. */
522export type DocThread = {
523 id: string;
524 /** The text it is on; null for a comment on the whole page. */
525 quote: string | null;
526 resolved: boolean;
527 comments: { id: string; author: MemberProfile; text: string; created_at: string }[];
528};
529
530/** A file put in a page: served from the usercontent origin. */
531export type DocFile = { id: string; url: string; name: string; content_type: string; bytes: number };
532
533/**
534 * What a page's live socket carries besides the Yjs protocol (binary
535 * frames: y-protocols sync and awareness). These are JSON text frames.
536 */
537export type DocsLiveEvent =
538 | { type: "page.updated"; page: DocPage }
539 | { type: "page.archived"; page_id: string }
540 | { type: "suggestion.created" | "suggestion.updated"; suggestion: DocSuggestion }
541 | { type: "version.created"; version: DocVersion }
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store542 /** 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). */
543 | { type: "page.staleness" }
Docs: a workspace knowledge base people and agents write together544 /** The viewer's role changed, or their access ended (`role` null). */
545 | { type: "access"; role: DocRole | null };
546
547/**
548 * 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.549 * viewer, as JSON. Trusted only because the artifacts service is reachable
Docs: a workspace knowledge base people and agents write together550 * through service bindings alone.
551 */
552export const DOCS_VIEWER_HEADER = "x-g1t-docs-viewer";
553
554/** The largest file a page takes, in bytes. */
555export const DOC_MAX_FILE_BYTES = 25 * 1024 * 1024;
556
557/** The name of the Yjs XML fragment that holds a page's blocks. */
558export const DOC_FRAGMENT = "document-store";
559/** The name of the Yjs map that holds a page's comment threads. */
560export const DOC_THREADS = "threads";
561
Agents recall what Docs say before they answer or work, and each has required reading562/** One passage of Docs, recalled for an agent: a section of a page or of a repository's docs. */
563export type DocPassage = {
564 /** The page; null for a repository's docs file. */
565 page: DocPageRef | null;
566 /** A repository's docs file: `owner/name`, its path, and where it reads in Docs. */
567 repo_file: { repo: string; path: string; href: string } | null;
568 space_name: string;
569 /** The heading the passage sits under, if any. */
570 heading: string | null;
571 /** The passage as Markdown, at most about 1,500 characters. */
572 text: string;
573 /** How close it is, 0 to 1. */
574 score: number;
575 updated_at: string;
576 /** The page is marked possibly out of date. */
577 stale: boolean;
578};
579
Docs: a workspace knowledge base people and agents write together580export type DocsApi = {
581 // ── The site ─────────────────────────────────────────────────────────
582
583 /** Spaces with their page trees, favorites, recent pages. Makes the General space the first time. */
584 sidebar(workspace: string, viewer: User): Promise<Result<DocsSidebar>>;
585 home(workspace: string, viewer: User, options?: { project?: string | null }): Promise<Result<DocsHome>>;
586 space(workspace: string, spaceSlug: string, viewer: User): Promise<Result<{ space: DocSpace; members: DocSpaceMember[]; pages: DocPage[] }>>;
587 createSpace(workspace: string, viewer: User, input: NewDocSpace): Promise<Result<DocSpace>>;
588 /** Manage role only. */
589 updateSpace(workspace: string, spaceId: string, viewer: User, change: DocSpaceChange): Promise<Result<DocSpace>>;
590 /** Adds, changes (`role`) or removes (`role` null) a member. Manage role only; the last manager stays. */
591 setSpaceMember(workspace: string, spaceId: string, viewer: User, member: DocMemberKey, role: DocRole | null): Promise<Result<DocSpaceMember[]>>;
592
593 /** A page, and the viewer's role in it; records the view. Not found when they can't read it. */
594 page(workspace: string, pageId: string, viewer: User): Promise<Result<DocPageDetail>>;
595 createPage(workspace: string, viewer: User, input: NewDocPage): Promise<Result<DocPage>>;
596 updatePage(workspace: string, pageId: string, viewer: User, change: DocPageChange): Promise<Result<DocPage>>;
597 /** Edit role in both spaces. Refuses moving a page under itself. */
598 movePage(workspace: string, pageId: string, viewer: User, move: DocMove): Promise<Result<DocPage>>;
599 duplicatePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
600 /** To the trash, with every page under it. */
601 archivePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
602 restorePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
603 /** For good: only from the trash, manage role. */
604 deletePage(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
605 trash(workspace: string, viewer: User): Promise<Result<DocPage[]>>;
606 favorite(workspace: string, pageId: string, viewer: User, on: boolean): Promise<Result<boolean>>;
607
608 search(workspace: string, viewer: User, query: DocSearchQuery): Promise<Result<DocSearchHit[]>>;
609
610 versions(workspace: string, pageId: string, viewer: User): Promise<Result<DocVersion[]>>;
611 version(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersionDetail>>;
612 /** Makes the page what it was then, as a new version. Edit role. */
613 restoreVersion(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersion>>;
614
615 templates(workspace: string, viewer: User): Promise<Result<DocTemplate[]>>;
616 /** Saves a page as one of the workspace's templates. */
617 saveTemplate(workspace: string, viewer: User, input: { page_id: string; name: string; description?: string | null }): Promise<Result<DocTemplate>>;
618 deleteTemplate(workspace: string, templateId: string, viewer: User): Promise<Result<boolean>>;
619
620 exportPage(workspace: string, pageId: string, viewer: User): Promise<Result<{ filename: string; markdown: string }>>;
621 /** Every page in a space as Markdown, at paths that follow the tree. */
622 exportSpace(workspace: string, spaceId: string, viewer: User): Promise<Result<{ name: string; files: { path: string; markdown: string }[] }>>;
623
624 suggestions(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
625 /** Accept (applies it to the live document, attributed to both) or reject. Edit role. */
626 decideSuggestion(workspace: string, suggestionId: string, viewer: User, decision: "accept" | "reject"): Promise<Result<DocSuggestion>>;
627 acceptAll(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
628
629 /** A comment operation from the editor; comment role (deleting others' comments or threads needs edit). */
630 thread(workspace: string, pageId: string, viewer: User, action: DocThreadAction): Promise<Result<unknown>>;
631 threads(workspace: string, pageId: string, viewer: User): Promise<Result<DocThread[]>>;
632
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store633 /** Clears "possibly out of date": the page says what the code does now. Edit role. */
634 markCurrent(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
635 /** Pages the viewer can read that are possibly out of date, most recently flagged first; `repo` (`owner/name`) narrows to changes there. */
636 stalePages(workspace: string, viewer: User, options?: { repo?: string | null }): Promise<Result<DocPage[]>>;
637
638 /**
639 * Shows a repository's `docs/` folder and README.md in Docs, read from
640 * its default branch. Any member who can read the repository may; the
641 * files are read at once and again on every push to the default branch.
642 */
643 addRepoSpace(workspace: string, viewer: User, repo: string): Promise<Result<DocRepoSpace>>;
644 /** Stops showing it. Whoever added it, or a workspace owner. */
645 removeRepoSpace(workspace: string, viewer: User, id: string): Promise<Result<boolean>>;
646 /** One file of a project's docs, for a viewer who can read the repository; not found otherwise. */
647 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 people648 /**
649 * Indexes the workspace's pages and projects' docs for agents' recall
650 * again, in batches in the background. Workspace owners. True when a run
651 * started (false: one is already going).
652 */
653 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 store654
Docs: a workspace knowledge base people and agents write together655 // ── Agents (services/agents) ─────────────────────────────────────────
656 //
657 // Each takes the agent and the person it acts for (`viewer`, the asker).
658 // The service checks the agent is a live agent of the workspace and caps
659 // everything by the viewer's access; `audience`, when given, narrows
660 // reads further to what every person in it can read. A page the agent
661 // may not read is not found, exactly as one that does not exist.
662
663 /** Spaces the agent may read for the viewer (and audience), with what it may do in each. */
664 spacesForAgent(workspace: string, agentId: string, viewer: User, audience?: DocAudience | null): Promise<Result<(Pick<DocSpace, "id" | "slug" | "name" | "description" | "kind" | "agent_mode" | "projects"> & { can: DocAgentAbilities })[]>>;
665 /** A page's Markdown and its top-level blocks (ids for `blocks` targets). */
666 pageMarkdown(workspace: string, agentId: string, viewer: User, pageId: string, audience?: DocAudience | null): Promise<Result<DocAgentPage>>;
667 /** Full-text search over pages every reader can read; at most 20, best first. */
668 searchForAgent(workspace: string, agentId: string, viewer: User, query: DocSearchQuery, audience?: DocAudience | null): Promise<Result<DocSearchHit[]>>;
669 /** 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 store670 suggestEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: DocAgentEdit): Promise<Result<DocSuggestion>>;
Docs: a workspace knowledge base people and agents write together671 /**
672 * Applies an edit to the live document when the space lets agents edit
673 * and the viewer can edit; otherwise files it as a suggestion (and says
674 * so in `mode`). Attributed to the agent in the page's history.
675 */
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store676 applyEdit(workspace: string, agentId: string, viewer: User, pageId: string, edit: DocAgentEdit): Promise<Result<DocAgentEditResult>>;
Docs: a workspace knowledge base people and agents write together677 /**
678 * A new page, written by the agent: in `space_id` (the General space
679 * when null), under `parent_id`. Needs the viewer's edit role there
680 * (whatever the space's agent mode: a new page changes nothing anyone
681 * wrote). `source` links where it came from ("write this up").
682 */
683 createPageAsAgent(
684 workspace: string,
685 agentId: string,
686 viewer: User,
687 input: { space_id?: string | null; parent_id?: string | null; title: string; icon?: string | null; markdown: string; source?: { title: string; href: string } | null },
688 ): Promise<Result<DocPageRef>>;
Agents recall what Docs say before they answer or work, and each has required reading689 /**
690 * What the workspace's Docs say about `query`, for an agent about to
691 * answer: the passages closest in meaning (and, where meaning finds too
692 * little, in words), each with the page and heading it came from. Only
693 * from spaces the viewer can read and, with `audience`, everyone it
694 * covers; repository docs only from repositories they can all read.
695 * `spaces` narrows to these space ids first (an agent's required
696 * reading) and fills from the rest. Empty when nothing is close enough.
697 */
698 recallForAgent(
699 workspace: string,
700 agentId: string,
701 viewer: User,
702 input: { query: string; limit?: number | null; spaces?: string[] | null },
703 audience?: DocAudience | null,
704 ): Promise<Result<DocPassage[]>>;
Docs: a workspace knowledge base people and agents write together705 /** A page's comment threads, for an agent asked about them. */
706 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 store707 /**
708 * Pages possibly out of date that the agent may read for the viewer (and
709 * audience), each with the changes that made it so: for a documenter
710 * routine to bring them up to date. At most 50, most recently flagged
711 * first.
712 *
713 * - `repo` (`owner/name`): only pages made stale by a change there.
714 * - `since` (RFC 3339): only pages flagged at or after it.
715 *
716 * Changes in repositories the viewer can't read are left out, and a page
717 * whose every change is one of those is left out too: an agent never
718 * learns of code its person can't see. To update a page, read it
719 * (`pageMarkdown`), then `applyEdit` or `suggestEdit` with
720 * `marks_current: true`: the page is marked current when the edit is
721 * applied (at once, or when a person accepts the suggestion).
722 */
723 stalePagesForAgent(
724 workspace: string,
725 agentId: string,
726 viewer: User,
727 options?: { repo?: string | null; since?: string | null },
728 audience?: DocAudience | null,
729 ): Promise<Result<DocStalePage[]>>;
Docs: a workspace knowledge base people and agents write together730};
731
732async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
733 const response = await service.fetch(`https://service/rpc/${method}`, {
734 method: "POST",
735 headers: { "content-type": "application/json" },
736 body: JSON.stringify(args),
737 });
738 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
739 return (await response.json()) as T;
740}
741
742export function docsClient(service: ServiceBinding): DocsApi {
743 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
744 return {
745 sidebar: (workspace, viewer) => call("sidebar", { workspace, viewer }),
746 home: (workspace, viewer, options) => call("home", { workspace, viewer, project: options?.project ?? null }),
747 space: (workspace, spaceSlug, viewer) => call("space", { workspace, space: spaceSlug, viewer }),
748 createSpace: (workspace, viewer, input) => call("create_space", { workspace, viewer, input }),
749 updateSpace: (workspace, spaceId, viewer, change) => call("update_space", { workspace, space_id: spaceId, viewer, change }),
750 setSpaceMember: (workspace, spaceId, viewer, member, role) => call("set_space_member", { workspace, space_id: spaceId, viewer, member, role }),
751 page: (workspace, pageId, viewer) => call("page", { workspace, page_id: pageId, viewer }),
752 createPage: (workspace, viewer, input) => call("create_page", { workspace, viewer, input }),
753 updatePage: (workspace, pageId, viewer, change) => call("update_page", { workspace, page_id: pageId, viewer, change }),
754 movePage: (workspace, pageId, viewer, move) => call("move_page", { workspace, page_id: pageId, viewer, move }),
755 duplicatePage: (workspace, pageId, viewer) => call("duplicate_page", { workspace, page_id: pageId, viewer }),
756 archivePage: (workspace, pageId, viewer) => call("archive_page", { workspace, page_id: pageId, viewer }),
757 restorePage: (workspace, pageId, viewer) => call("restore_page", { workspace, page_id: pageId, viewer }),
758 deletePage: (workspace, pageId, viewer) => call("delete_page", { workspace, page_id: pageId, viewer }),
759 trash: (workspace, viewer) => call("trash", { workspace, viewer }),
760 favorite: (workspace, pageId, viewer, on) => call("favorite", { workspace, page_id: pageId, viewer, on }),
761 search: (workspace, viewer, query) => call("search", { workspace, viewer, query }),
762 versions: (workspace, pageId, viewer) => call("versions", { workspace, page_id: pageId, viewer }),
763 version: (workspace, pageId, versionId, viewer) => call("version", { workspace, page_id: pageId, version_id: versionId, viewer }),
764 restoreVersion: (workspace, pageId, versionId, viewer) => call("restore_version", { workspace, page_id: pageId, version_id: versionId, viewer }),
765 templates: (workspace, viewer) => call("templates", { workspace, viewer }),
766 saveTemplate: (workspace, viewer, input) => call("save_template", { workspace, viewer, input }),
767 deleteTemplate: (workspace, templateId, viewer) => call("delete_template", { workspace, template_id: templateId, viewer }),
768 exportPage: (workspace, pageId, viewer) => call("export_page", { workspace, page_id: pageId, viewer }),
769 exportSpace: (workspace, spaceId, viewer) => call("export_space", { workspace, space_id: spaceId, viewer }),
770 suggestions: (workspace, pageId, viewer) => call("suggestions", { workspace, page_id: pageId, viewer }),
771 decideSuggestion: (workspace, suggestionId, viewer, decision) => call("decide_suggestion", { workspace, suggestion_id: suggestionId, viewer, decision }),
772 acceptAll: (workspace, pageId, viewer) => call("accept_all", { workspace, page_id: pageId, viewer }),
773 thread: (workspace, pageId, viewer, action) => call("thread", { workspace, page_id: pageId, viewer, action }),
774 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 store775 markCurrent: (workspace, pageId, viewer) => call("mark_current", { workspace, page_id: pageId, viewer }),
776 stalePages: (workspace, viewer, options) => call("stale_pages", { workspace, viewer, repo: options?.repo ?? null }),
777 addRepoSpace: (workspace, viewer, repo) => call("add_repo_space", { workspace, viewer, repo }),
778 removeRepoSpace: (workspace, viewer, id) => call("remove_repo_space", { workspace, viewer, id }),
779 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 people780 reindexDocs: (workspace, viewer) => call("reindex_docs", { workspace, viewer }),
Docs: a workspace knowledge base people and agents write together781 spacesForAgent: (workspace, agentId, viewer, audience) => call("spaces_for_agent", { workspace, agent_id: agentId, viewer, audience: audience ?? null }),
782 pageMarkdown: (workspace, agentId, viewer, pageId, audience) =>
783 call("page_markdown", { workspace, agent_id: agentId, viewer, page_id: pageId, audience: audience ?? null }),
784 searchForAgent: (workspace, agentId, viewer, query, audience) => call("search_for_agent", { workspace, agent_id: agentId, viewer, query, audience: audience ?? null }),
785 suggestEdit: (workspace, agentId, viewer, pageId, edit) => call("suggest_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
786 applyEdit: (workspace, agentId, viewer, pageId, edit) => call("apply_edit", { workspace, agent_id: agentId, viewer, page_id: pageId, edit }),
787 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 reading788 recallForAgent: (workspace, agentId, viewer, input, audience) =>
789 call("recall_for_agent", { workspace, agent_id: agentId, viewer, ...input, audience: audience ?? null }),
Docs: a workspace knowledge base people and agents write together790 threadsForAgent: (workspace, agentId, viewer, pageId, audience) =>
791 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 store792 stalePagesForAgent: (workspace, agentId, viewer, options, audience) =>
793 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 together794 };
795}