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