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