Skip to content
659 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 */
27import type { MemberProfile, Principal } from "./chat";
28import type { ServiceBinding } from "./clients";
29import type { User } from "./identity";
30import type { Result } from "./result";
31
32/** What someone may do in a space, weakest first. */
33export type DocRole = "view" | "comment" | "edit" | "manage";
34export const DOC_ROLES: readonly DocRole[] = ["view", "comment", "edit", "manage"];
35
36export const DOC_ROLE_LABELS: Record<DocRole, string> = {
37 view: "Can view",
38 comment: "Can comment",
39 edit: "Can edit",
40 manage: "Full access",
41};
42
43export const DOC_ROLE_SUMMARIES: Record<DocRole, string> = {
44 view: "Read pages and their history.",
45 comment: "Read pages and comment on them.",
46 edit: "Write and organize pages, and accept or reject suggestions.",
47 manage: "Edit, and change who has access and how agents work here.",
48};
49
50/** Who a space is for. */
51export type DocSpaceKind = "workspace" | "team" | "private";
52
53export const DOC_SPACE_KIND_LABELS: Record<DocSpaceKind, string> = {
54 workspace: "Everyone in the workspace",
55 team: "A team",
56 private: "Only its members",
57};
58
59/** How agents change pages in a space: as tracked suggestions (the default), or directly. */
60export type DocAgentMode = "suggest" | "edit";
61
62export const DOC_AGENT_MODE_LABELS: Record<DocAgentMode, string> = {
63 suggest: "Suggest changes",
64 edit: "Edit directly",
65};
66
67/** A key for a space member: `user:<id>`, `agent:<id>` or `team:<slug>`. */
68export type DocMemberKey = string;
69
70export type DocSpace = {
71 id: string;
72 workspace_id: string;
73 /** Unique in the workspace; in the URL: `/<workspace>/-/docs/<slug>`. */
74 slug: string;
75 name: string;
76 description: string | null;
77 /** An emoji, or null for the default book. */
78 icon: string | null;
79 kind: DocSpaceKind;
80 /** The team's slug, for a team space. */
81 team: string | null;
82 /** What every workspace member (workspace) or team member (team) gets; null for a private space. */
83 default_role: DocRole | null;
84 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.85 /** People with edit access may share what is in it (Artifacts), as people with full access can. Off unless turned on. */
86 editors_can_share: boolean;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r987 /**
88 * The workspace's General space, made once when Artifacts is first
89 * opened and shown to every member without joining. It can be archived
90 * and deleted like any other; once deleted it is not made again, and no
91 * other space takes its place.
92 */
Docs: a workspace knowledge base people and agents write together93 is_default: boolean;
94 /** Projects (repositories, `owner/name`) the space is about: Docs filters by them. */
95 projects: string[];
96 created_by: Principal;
97 created_at: string;
98 archived_at: string | null;
99 /** The viewer's role in it. */
100 viewer_role: DocRole;
101 /** Pages in it that are not in the trash. */
102 page_count: number;
103};
104
105/** A space member, as the space's settings show it. */
106export type DocSpaceMember = {
107 key: DocMemberKey;
108 kind: "user" | "agent" | "team";
109 /** Username, agent handle or team slug. */
110 name: string;
111 display_name: string;
112 avatar: string | null;
113 avatar_seed?: string | null;
114 role: DocRole;
115};
116
117/** Enough to link to a page. */
118export type DocPageRef = {
119 id: string;
120 space_id: string;
121 space_slug: string;
122 title: string;
123 /** An emoji, or null for the default page icon. */
124 icon: string | null;
125 /** `<title-slug>-<id>`: the last part of the page's URL. */
126 slug: string;
127 /** `/<workspace>/-/docs/<space>/<slug>`. */
128 path: string;
129};
130
131export type DocPage = DocPageRef & {
132 parent_id: string | null;
133 position: number;
134 /** A cover image's URL, or a CSS gradient name (`gradient:<n>`); null for none. */
135 cover: string | null;
136 created_by: MemberProfile;
137 created_at: string;
138 updated_by: MemberProfile | null;
139 updated_at: string;
140 /** When it went to the trash; null when it is not there. */
141 archived_at: string | null;
142 has_children: boolean;
143 /** Projects (`owner/name`) the page is about, besides its space's. */
144 projects: string[];
145 owners: MemberProfile[];
146 /** The first lines of its text, for cards. */
147 excerpt: string;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store148 /** Whether code it cites changed since someone last marked it current: possibly out of date. */
149 stale: boolean;
150};
151
152// ── Citations and staleness ───────────────────────────────────────────────
153//
154// A page can cite code: a path (a file, a folder, or a glob like
155// `src/export/**`) in a repository, optionally naming what at that path it
156// describes (a symbol, an endpoint, an environment variable). Citations
157// come from the page's text (the editor's citation chips, and links to
158// files in a repository: `/<owner>/<repo>/blob/<ref>/<path>`) and from
159// the page's header ("Describes"). When a merged pull request or a push
160// to a repository's default branch changes a cited path, the page is
161// marked possibly out of date with that change, until someone with edit
162// access marks it current again (or an agent updates it).
163
164/** What a citation names at its path. */
165export type DocCitationKind = "path" | "symbol" | "endpoint" | "env";
166export const DOC_CITATION_KINDS: readonly DocCitationKind[] = ["path", "symbol", "endpoint", "env"];
167
168export const DOC_CITATION_KIND_LABELS: Record<DocCitationKind, string> = {
169 path: "A file or folder",
170 symbol: "A symbol",
171 endpoint: "An endpoint",
172 env: "An environment variable",
173};
174
175export type DocCitation = {
176 /** `owner/name`, lowercased. */
177 repo: string;
178 /** A file, a folder (everything under it), or a glob (`*`, `**`, `?`). */
179 path: string;
180 kind: DocCitationKind;
181 /** The symbol, endpoint (`POST /v1/export`) or variable (`EXPORT_BUCKET`), for those kinds. */
182 label: string | null;
183 /** The commit it was cited at, when known. */
184 ref: string | null;
185 /** From the page's text, or from its header's "Describes". */
186 source: "body" | "header";
187};
188
189/** One entry of a page's "Describes": a repository and a path in it. */
190export type DocDescribes = { repo: string; path: string };
191
192/** A change that made a page possibly out of date. */
193export type DocStaleChange = {
194 /**
195 * False when the viewer can't read the repository: then `repo`, `pull`,
196 * `commit` and `paths` are blank, and the page only says that a change
197 * they can't see touched code it cites.
198 */
199 visible: boolean;
200 repo: string | null;
201 commit: string | null;
202 /** The merged pull request, when the change came from one. */
203 pull: { number: number; title: string | null } | null;
204 /** The cited paths it changed (the changed files, at most 20). */
205 paths: string[];
206 /** When it was noticed. RFC 3339. */
207 at: string;
208};
209
210/** Why a page is possibly out of date: every change since it was last marked current, newest first. */
211export type DocStaleness = { since: string; changes: DocStaleChange[] };
212
213// ── A project's docs ──────────────────────────────────────────────────────
214//
215// A repository's `docs/` folder (and README.md), shown read-only in Docs
216// next to the workspace's spaces and found by the same search. It is read
217// from the default branch and kept up to date on every push to it. Each
218// reader sees only the repositories they can read. Changes go through the
219// repository: "Edit in Code" opens the file.
220
221export type DocRepoFile = {
222 /** From the repository's root: `docs/guide/setup.md`, `README.md`. */
223 path: string;
224 /** Its first heading, or its file name. */
225 title: string;
226};
227
228export type DocRepoSpace = {
229 id: string;
230 /** `owner/name`, as the repository is named now. */
231 repo: string;
232 default_branch: string;
233 /** The commit it was read at; null until it has been. */
234 commit: string | null;
235 indexed_at: string | null;
236 added_by: MemberProfile;
237 files: DocRepoFile[];
238 /** Whether the viewer may stop showing it (whoever added it, or an owner). */
239 can_remove: boolean;
Docs: a workspace knowledge base people and agents write together240};
241
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store242export type DocRepoPage = {
243 space: DocRepoSpace;
244 file: DocRepoFile & {
245 markdown: string;
246 /** `/<workspace>/-/docs/repo/<owner>/<name>/<path>`. */
247 href: string;
248 /** The file in Code, on the default branch. */
249 code_href: string;
250 };
251};
252
Docs: a workspace knowledge base people and agents write together253/** A page as the sidebar's tree lists it: flat, ordered by `position` within each parent. */
254export type DocTreeNode = {
255 id: string;
256 parent_id: string | null;
257 position: number;
258 title: string;
259 icon: string | null;
260 slug: string;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store261 /** Possibly out of date (see `DocPage.stale`). */
262 stale?: boolean;
Docs: a workspace knowledge base people and agents write together263};
264
265export type DocsSidebarSpace = DocSpace & { pages: DocTreeNode[] };
266
267export type DocsSidebar = {
268 spaces: DocsSidebarSpace[];
269 favorites: DocPageRef[];
270 /** The pages the viewer opened last, newest first. */
271 recent: DocPageRef[];
272 /** Whether the viewer may make spaces (members of the workspace may). */
273 can_create_space: boolean;
274 trash_count: number;
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store275 /** Pages the viewer can read that are possibly out of date. */
276 stale_count: number;
277 /** Projects' docs folders shown in Docs, those whose repository the viewer can read. */
278 repos: DocRepoSpace[];
Docs: a workspace knowledge base people and agents write together279};
280
281export type DocsHome = {
282 /** Recently edited pages the viewer can read, newest first. */
283 recent: DocPage[];
284 /** Pages the viewer made or owns. */
285 mine: DocPage[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store286 /** Pages possibly out of date, most recently flagged first. */
287 stale: DocPage[];
Docs: a workspace knowledge base people and agents write together288 spaces: DocSpace[];
289 /** Every project some space or page is linked to, for the filter. */
290 projects: string[];
291 /** The project the lists are filtered to, or null. */
292 project: string | null;
293};
294
295/** A tracked change an agent proposed. `blocks` targets name top-level block ids from `page_markdown`. */
296export type DocEditTarget =
297 /** Add to the end of the page. */
298 | { kind: "append" }
299 /** Replace the whole page. */
300 | { kind: "document" }
301 /** 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. */
302 | { kind: "section"; heading: string }
303 /** Replace top-level blocks `from_block` through `to_block`, inclusive (with any blocks nested under them). */
304 | { kind: "blocks"; from_block: string; to_block: string };
305
306export type DocSuggestionStatus = "open" | "accepted" | "rejected" | "stale";
307
308export type DocSuggestion = {
309 id: string;
310 page_id: string;
311 /** The agent that suggested it. */
312 author: MemberProfile;
313 /** The person it acted for. */
314 asked_by: MemberProfile | null;
315 target: DocEditTarget;
316 /** The target's Markdown when it was suggested. */
317 before_markdown: string;
318 /** What it proposes instead. */
319 after_markdown: string;
320 /** Why, in a line. */
321 note: string | null;
322 status: DocSuggestionStatus;
323 created_at: string;
324 decided_by: MemberProfile | null;
325 decided_at: string | null;
326 /** The top-level blocks it covers now, so the editor can mark them; empty for an append or when the target is gone. */
327 block_ids: string[];
328};
329
330export type DocVersionKind = "created" | "edit" | "agent" | "suggestion" | "restore";
331
332export type DocVersion = {
333 id: string;
334 page_id: string;
335 created_at: string;
336 /** Everyone whose changes are in it, people and agents. */
337 authors: MemberProfile[];
338 kind: DocVersionKind;
339 /** "Suggested by @inky, accepted by @ana"; "Restored from Oct 3, 14:02". */
340 note: string | null;
341};
342
343export type DocDiffLine = { op: "same" | "add" | "del"; text: string };
344
345export type DocVersionDetail = DocVersion & {
346 markdown: string;
347 /** Against the version before it (or nothing, for the first). */
348 diff: DocDiffLine[];
349};
350
351export type DocPageDetail = {
352 page: DocPage;
353 space: DocSpace;
354 /** The page's ancestors, root first. */
355 breadcrumbs: DocPageRef[];
356 /** As last saved: the read view, and what the editor shows before the socket connects. */
357 markdown: string;
358 role: DocRole;
359 backlinks: DocPageRef[];
360 children: DocPageRef[];
361 favorite: boolean;
362 /** When the viewer last opened it, before now. */
363 last_viewed_at: string | null;
364 suggestions: DocSuggestion[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store365 /** Code the page cites: from its text and its header. */
366 citations: DocCitation[];
367 /** The header's "Describes" list. */
368 describes: DocDescribes[];
369 /** Why it is possibly out of date; null when it isn't. */
370 staleness: DocStaleness | null;
Docs: a workspace knowledge base people and agents write together371};
372
373export type DocSearchHit = DocPageRef & {
374 space_name: string;
375 /** Text around the match; `[[` and `]]` mark matched words. */
376 snippet: string;
377 updated_at: string;
378 projects: string[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store379 /** Set for a file from a project's docs folder (then `id` is `repo:<space>:<path>` and `path` its address in Docs). */
380 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 people381 /** The heading of the passage that matched, when search found one (`mode: "hybrid"`). */
382 heading?: string | null;
383 /** How it was found: by its words, by meaning (the semantic index), or both. */
384 matched?: "words" | "meaning" | "both" | null;
Docs: a workspace knowledge base people and agents write together385};
386
387export type DocTemplate = {
388 id: string;
389 name: string;
390 description: string;
391 icon: string;
392 /** Built in (meeting notes, RFC, ...) rather than saved by the workspace. */
393 builtin: boolean;
394 markdown: string;
395 created_by: Principal | null;
396};
397
398/** One top-level block, as agents see a page. */
399export type DocBlockOutline = {
400 id: string;
401 /** BlockNote's type: `heading`, `paragraph`, `bulletListItem`, `codeBlock`, `callout`, ... */
402 type: string;
403 /** For a heading. */
404 level: number | null;
405 /** The block and anything nested under it. */
406 markdown: string;
407};
408
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.409/**
410 * What an agent may do on a page, for the person it acts for: read it,
411 * suggest changes, edit its body (only in a space whose agents edit), and
412 * attach a file it made (whenever the person could edit: a file beside the
413 * page changes nothing in it).
414 */
415export type DocAgentAbilities = { read: boolean; suggest: boolean; edit: boolean; attach: boolean };
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store416
Docs: a workspace knowledge base people and agents write together417export type NewDocSpace = {
418 name: string;
419 slug?: string | null;
420 description?: string | null;
421 icon?: string | null;
422 kind: DocSpaceKind;
423 team?: string | null;
424 default_role?: DocRole | null;
425 agent_mode?: DocAgentMode | null;
426 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.427 editors_can_share?: boolean | null;
Docs: a workspace knowledge base people and agents write together428};
429
430export type DocSpaceChange = Partial<Omit<NewDocSpace, "kind">> & { kind?: DocSpaceKind; archived?: boolean };
431
432export type NewDocPage = {
433 space_id: string;
434 parent_id?: string | null;
435 title?: string | null;
436 icon?: string | null;
437 /** A built-in template's id (`builtin:<name>`) or a saved one's. */
438 template_id?: string | null;
439 /** Starting Markdown, when there is no template. */
440 markdown?: string | null;
441 projects?: string[] | null;
442};
443
444export type DocPageChange = {
445 title?: string;
446 icon?: string | null;
447 cover?: string | null;
448 projects?: string[];
449 /** Member keys (`user:<id>`, `agent:<id>`). */
450 owners?: string[];
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store451 /** Replaces the header's "Describes" list (at most 20). */
452 describes?: DocDescribes[];
Docs: a workspace knowledge base people and agents write together453};
454
455/** Where a page goes: under `parent_id` (null for the top of the space), before `before_id` (null for the end). */
456export type DocMove = { space_id?: string | null; parent_id: string | null; before_id?: string | null };
457
458export type DocSearchQuery = {
459 query: string;
460 space_id?: string | null;
461 /** `owner/name`: pages linked to it, or in a space linked to it. */
462 project?: string | null;
463 limit?: number | null;
Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people464 /**
465 * `words` (the default): titles and text by their words, as you type.
466 * `hybrid`: words and meaning (the semantic index) together, each hit
467 * with the passage and heading that matched; the Docs search page.
468 */
469 mode?: "words" | "hybrid" | null;
Docs: a workspace knowledge base people and agents write together470};
471
472/**
473 * The comment operations of the editor's thread store (BlockNote's
474 * `RESTYjsThreadStore`), applied by the page's room to the `threads` map in
475 * the page's Yjs document, so every open editor sees them at once.
476 */
477export type DocThreadAction =
478 | { op: "create"; body: unknown; metadata?: unknown; page_level?: boolean }
479 /** Anchors a thread to the text between two Yjs relative positions (JSON). */
480 | { op: "anchor"; thread_id: string; anchor: unknown; head: unknown }
481 | { op: "comment"; thread_id: string; body: unknown; metadata?: unknown }
482 | { op: "edit_comment"; thread_id: string; comment_id: string; body: unknown; metadata?: unknown }
483 | { op: "delete_comment"; thread_id: string; comment_id: string; soft?: boolean }
484 | { op: "delete_thread"; thread_id: string }
485 | { op: "resolve" | "unresolve"; thread_id: string }
486 | { op: "react" | "unreact"; thread_id: string; comment_id: string; emoji: string };
487
488/** A comment thread, as agents and the inbox see it. */
489export type DocThread = {
490 id: string;
491 /** The text it is on; null for a comment on the whole page. */
492 quote: string | null;
493 resolved: boolean;
494 comments: { id: string; author: MemberProfile; text: string; created_at: string }[];
495};
496
497/** A file put in a page: served from the usercontent origin. */
498export type DocFile = { id: string; url: string; name: string; content_type: string; bytes: number };
499
500/**
501 * What a page's live socket carries besides the Yjs protocol (binary
502 * frames: y-protocols sync and awareness). These are JSON text frames.
503 */
504export type DocsLiveEvent =
505 | { type: "page.updated"; page: DocPage }
506 | { type: "page.archived"; page_id: string }
507 | { type: "suggestion.created" | "suggestion.updated"; suggestion: DocSuggestion }
508 | { type: "version.created"; version: DocVersion }
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store509 /** 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). */
510 | { type: "page.staleness" }
Docs: a workspace knowledge base people and agents write together511 /** The viewer's role changed, or their access ended (`role` null). */
512 | { type: "access"; role: DocRole | null };
513
514/**
515 * 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.516 * viewer, as JSON. Trusted only because the artifacts service is reachable
Docs: a workspace knowledge base people and agents write together517 * through service bindings alone.
518 */
519export const DOCS_VIEWER_HEADER = "x-g1t-docs-viewer";
520
521/** The largest file a page takes, in bytes. */
522export const DOC_MAX_FILE_BYTES = 25 * 1024 * 1024;
523
524/** The name of the Yjs XML fragment that holds a page's blocks. */
525export const DOC_FRAGMENT = "document-store";
526/** The name of the Yjs map that holds a page's comment threads. */
527export const DOC_THREADS = "threads";
528
529export type DocsApi = {
530 // ── The site ─────────────────────────────────────────────────────────
531
532 /** Spaces with their page trees, favorites, recent pages. Makes the General space the first time. */
533 sidebar(workspace: string, viewer: User): Promise<Result<DocsSidebar>>;
534 home(workspace: string, viewer: User, options?: { project?: string | null }): Promise<Result<DocsHome>>;
535 space(workspace: string, spaceSlug: string, viewer: User): Promise<Result<{ space: DocSpace; members: DocSpaceMember[]; pages: DocPage[] }>>;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9536 /** Every space the viewer can open, archived ones included (never deleted ones), General first. */
537 spaces(workspace: string, viewer: User): Promise<Result<DocSpace[]>>;
Docs: a workspace knowledge base people and agents write together538 createSpace(workspace: string, viewer: User, input: NewDocSpace): Promise<Result<DocSpace>>;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9539 /** Manage role only. `archived` true or false archives a space or brings it back, the General space too. */
Docs: a workspace knowledge base people and agents write together540 updateSpace(workspace: string, spaceId: string, viewer: User, change: DocSpaceChange): Promise<Result<DocSpace>>;
541 /** Adds, changes (`role`) or removes (`role` null) a member. Manage role only; the last manager stays. */
542 setSpaceMember(workspace: string, spaceId: string, viewer: User, member: DocMemberKey, role: DocRole | null): Promise<Result<DocSpaceMember[]>>;
543
544 /** A page, and the viewer's role in it; records the view. Not found when they can't read it. */
545 page(workspace: string, pageId: string, viewer: User): Promise<Result<DocPageDetail>>;
546 createPage(workspace: string, viewer: User, input: NewDocPage): Promise<Result<DocPage>>;
547 updatePage(workspace: string, pageId: string, viewer: User, change: DocPageChange): Promise<Result<DocPage>>;
548 /** Edit role in both spaces. Refuses moving a page under itself. */
549 movePage(workspace: string, pageId: string, viewer: User, move: DocMove): Promise<Result<DocPage>>;
550 duplicatePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
551 /** To the trash, with every page under it. */
552 archivePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
553 restorePage(workspace: string, pageId: string, viewer: User): Promise<Result<DocPage>>;
554 /** For good: only from the trash, manage role. */
555 deletePage(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
556 trash(workspace: string, viewer: User): Promise<Result<DocPage[]>>;
557 favorite(workspace: string, pageId: string, viewer: User, on: boolean): Promise<Result<boolean>>;
558
559 search(workspace: string, viewer: User, query: DocSearchQuery): Promise<Result<DocSearchHit[]>>;
560
561 versions(workspace: string, pageId: string, viewer: User): Promise<Result<DocVersion[]>>;
562 version(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersionDetail>>;
563 /** Makes the page what it was then, as a new version. Edit role. */
564 restoreVersion(workspace: string, pageId: string, versionId: string, viewer: User): Promise<Result<DocVersion>>;
565
566 templates(workspace: string, viewer: User): Promise<Result<DocTemplate[]>>;
567 /** Saves a page as one of the workspace's templates. */
568 saveTemplate(workspace: string, viewer: User, input: { page_id: string; name: string; description?: string | null }): Promise<Result<DocTemplate>>;
569 deleteTemplate(workspace: string, templateId: string, viewer: User): Promise<Result<boolean>>;
570
571 exportPage(workspace: string, pageId: string, viewer: User): Promise<Result<{ filename: string; markdown: string }>>;
572 /** Every page in a space as Markdown, at paths that follow the tree. */
573 exportSpace(workspace: string, spaceId: string, viewer: User): Promise<Result<{ name: string; files: { path: string; markdown: string }[] }>>;
574
575 suggestions(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
576 /** Accept (applies it to the live document, attributed to both) or reject. Edit role. */
577 decideSuggestion(workspace: string, suggestionId: string, viewer: User, decision: "accept" | "reject"): Promise<Result<DocSuggestion>>;
578 acceptAll(workspace: string, pageId: string, viewer: User): Promise<Result<DocSuggestion[]>>;
579
580 /** A comment operation from the editor; comment role (deleting others' comments or threads needs edit). */
581 thread(workspace: string, pageId: string, viewer: User, action: DocThreadAction): Promise<Result<unknown>>;
582 threads(workspace: string, pageId: string, viewer: User): Promise<Result<DocThread[]>>;
583
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store584 /** Clears "possibly out of date": the page says what the code does now. Edit role. */
585 markCurrent(workspace: string, pageId: string, viewer: User): Promise<Result<boolean>>;
586 /** Pages the viewer can read that are possibly out of date, most recently flagged first; `repo` (`owner/name`) narrows to changes there. */
587 stalePages(workspace: string, viewer: User, options?: { repo?: string | null }): Promise<Result<DocPage[]>>;
588
589 /**
590 * Shows a repository's `docs/` folder and README.md in Docs, read from
591 * its default branch. Any member who can read the repository may; the
592 * files are read at once and again on every push to the default branch.
593 */
594 addRepoSpace(workspace: string, viewer: User, repo: string): Promise<Result<DocRepoSpace>>;
595 /** Stops showing it. Whoever added it, or a workspace owner. */
596 removeRepoSpace(workspace: string, viewer: User, id: string): Promise<Result<boolean>>;
597 /** One file of a project's docs, for a viewer who can read the repository; not found otherwise. */
598 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 people599 /**
600 * Indexes the workspace's pages and projects' docs for agents' recall
601 * again, in batches in the background. Workspace owners. True when a run
602 * started (false: one is already going).
603 */
604 reindexDocs(workspace: string, viewer: User): Promise<Result<boolean>>;
Docs: a workspace knowledge base people and agents write together605
606};
607
608async function rpc<T>(service: ServiceBinding, method: string, args: object): Promise<T> {
609 const response = await service.fetch(`https://service/rpc/${method}`, {
610 method: "POST",
611 headers: { "content-type": "application/json" },
612 body: JSON.stringify(args),
613 });
614 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
615 return (await response.json()) as T;
616}
617
618export function docsClient(service: ServiceBinding): DocsApi {
619 const call = <T>(method: string, args: object) => rpc<T>(service, method, args);
620 return {
621 sidebar: (workspace, viewer) => call("sidebar", { workspace, viewer }),
622 home: (workspace, viewer, options) => call("home", { workspace, viewer, project: options?.project ?? null }),
623 space: (workspace, spaceSlug, viewer) => call("space", { workspace, space: spaceSlug, viewer }),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9624 spaces: (workspace, viewer) => call("spaces", { workspace, viewer }),
Docs: a workspace knowledge base people and agents write together625 createSpace: (workspace, viewer, input) => call("create_space", { workspace, viewer, input }),
626 updateSpace: (workspace, spaceId, viewer, change) => call("update_space", { workspace, space_id: spaceId, viewer, change }),
627 setSpaceMember: (workspace, spaceId, viewer, member, role) => call("set_space_member", { workspace, space_id: spaceId, viewer, member, role }),
628 page: (workspace, pageId, viewer) => call("page", { workspace, page_id: pageId, viewer }),
629 createPage: (workspace, viewer, input) => call("create_page", { workspace, viewer, input }),
630 updatePage: (workspace, pageId, viewer, change) => call("update_page", { workspace, page_id: pageId, viewer, change }),
631 movePage: (workspace, pageId, viewer, move) => call("move_page", { workspace, page_id: pageId, viewer, move }),
632 duplicatePage: (workspace, pageId, viewer) => call("duplicate_page", { workspace, page_id: pageId, viewer }),
633 archivePage: (workspace, pageId, viewer) => call("archive_page", { workspace, page_id: pageId, viewer }),
634 restorePage: (workspace, pageId, viewer) => call("restore_page", { workspace, page_id: pageId, viewer }),
635 deletePage: (workspace, pageId, viewer) => call("delete_page", { workspace, page_id: pageId, viewer }),
636 trash: (workspace, viewer) => call("trash", { workspace, viewer }),
637 favorite: (workspace, pageId, viewer, on) => call("favorite", { workspace, page_id: pageId, viewer, on }),
638 search: (workspace, viewer, query) => call("search", { workspace, viewer, query }),
639 versions: (workspace, pageId, viewer) => call("versions", { workspace, page_id: pageId, viewer }),
640 version: (workspace, pageId, versionId, viewer) => call("version", { workspace, page_id: pageId, version_id: versionId, viewer }),
641 restoreVersion: (workspace, pageId, versionId, viewer) => call("restore_version", { workspace, page_id: pageId, version_id: versionId, viewer }),
642 templates: (workspace, viewer) => call("templates", { workspace, viewer }),
643 saveTemplate: (workspace, viewer, input) => call("save_template", { workspace, viewer, input }),
644 deleteTemplate: (workspace, templateId, viewer) => call("delete_template", { workspace, template_id: templateId, viewer }),
645 exportPage: (workspace, pageId, viewer) => call("export_page", { workspace, page_id: pageId, viewer }),
646 exportSpace: (workspace, spaceId, viewer) => call("export_space", { workspace, space_id: spaceId, viewer }),
647 suggestions: (workspace, pageId, viewer) => call("suggestions", { workspace, page_id: pageId, viewer }),
648 decideSuggestion: (workspace, suggestionId, viewer, decision) => call("decide_suggestion", { workspace, suggestion_id: suggestionId, viewer, decision }),
649 acceptAll: (workspace, pageId, viewer) => call("accept_all", { workspace, page_id: pageId, viewer }),
650 thread: (workspace, pageId, viewer, action) => call("thread", { workspace, page_id: pageId, viewer, action }),
651 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 store652 markCurrent: (workspace, pageId, viewer) => call("mark_current", { workspace, page_id: pageId, viewer }),
653 stalePages: (workspace, viewer, options) => call("stale_pages", { workspace, viewer, repo: options?.repo ?? null }),
654 addRepoSpace: (workspace, viewer, repo) => call("add_repo_space", { workspace, viewer, repo }),
655 removeRepoSpace: (workspace, viewer, id) => call("remove_repo_space", { workspace, viewer, id }),
656 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 people657 reindexDocs: (workspace, viewer) => call("reindex_docs", { workspace, viewer }),
Docs: a workspace knowledge base people and agents write together658 };
659}

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