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 together | 1 | /** |
| 2 | * What a page's room saves to D1 after a burst of edits: the Markdown | |
| 3 | * rendition, the search index, backlinks, history, and telling people | |
| 4 | * newly mentioned in the page. Run by the room (src/room.ts), which owns | |
| 5 | * the live document; nothing here reads the document itself. | |
| 6 | */ | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 7 | import { newId, notifyClient, type DocCitation, type DocVersionKind, type FeedNotification, type ServiceBinding } from "@g1t/contracts"; |
| Docs: a workspace knowledge base people and agents write together | 8 | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 9 | import { publishDocEvent } from "./events.ts"; |
| Docs: a workspace knowledge base people and agents write together | 10 | import { excerpt, searchText } from "./markdown.ts"; |
| 11 | import { linkedPageIds, pageSlug } from "./slugs.ts"; | |
| 12 | ||
| 13 | /** How long edits gather into one version before the next starts. */ | |
| 14 | export const VERSION_EVERY_MS = 10 * 60 * 1000; | |
| 15 | /** The largest Yjs state a version keeps; past it, only its Markdown. */ | |
| 16 | const MAX_VERSION_STATE = 1_500_000; | |
| 17 | ||
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 18 | export type SaveEnv = { DB: D1Database; NOTIFY?: ServiceBinding; EVENTS?: ServiceBinding }; |
| Docs: a workspace knowledge base people and agents write together | 19 | |
| 20 | export type Save = { | |
| 21 | page_id: string; | |
| 22 | markdown: string; | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 23 | /** Code the document cites (src/citations.ts `bodyCitations`). */ |
| 24 | citations: DocCitation[]; | |
| Docs: a workspace knowledge base people and agents write together | 25 | /** The editors since the last save, member keys, last one last. */ |
| 26 | editors: string[]; | |
| 27 | /** People mentioned in the document now (usernames, lowercased). */ | |
| 28 | mentioned: string[]; | |
| 29 | /** The usernames of the people who made these edits, lowercased: nobody is told they mentioned themselves. */ | |
| 30 | editor_names: string[]; | |
| 31 | /** The whole document, for a version. */ | |
| 32 | state: Uint8Array; | |
| 33 | /** A version to record now, whatever the time since the last. */ | |
| 34 | version: { kind: DocVersionKind; note: string | null; authors: string[] } | null; | |
| 35 | /** Editors since the last version, for a timed one. */ | |
| 36 | pending_authors: string[]; | |
| 37 | /** When the last version was recorded (ms), or 0. */ | |
| 38 | last_version_at: number; | |
| 39 | /** The workspace's slug as the room last heard it, for links in notifications. */ | |
| 40 | workspace_slug: string | null; | |
| 41 | }; | |
| 42 | ||
| 43 | type PageRow = { id: string; workspace_id: string; space_id: string; title: string; mentioned: string; markdown: string; slug: string; archived_at: string | null }; | |
| 44 | ||
| Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people | 45 | /** Saves; returns whether a version was recorded, and its id, and whether the Markdown changed (so the room indexes it again, src/indexer.ts). */ |
| 46 | export async function save(env: SaveEnv, input: Save, now = new Date()): Promise<{ version_id: string | null; changed: boolean }> { | |
| Docs: a workspace knowledge base people and agents write together | 47 | const at = now.toISOString(); |
| 48 | const page = await env.DB.prepare( | |
| 49 | "SELECT p.id, p.workspace_id, p.space_id, p.title, p.mentioned, p.markdown, p.archived_at, s.slug AS slug FROM pages p JOIN spaces s ON s.id = p.space_id WHERE p.id = ?", | |
| 50 | ) | |
| 51 | .bind(input.page_id) | |
| 52 | .first<PageRow>(); | |
| Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people | 53 | if (!page) return { version_id: null, changed: false }; |
| Docs: a workspace knowledge base people and agents write together | 54 | const changed = page.markdown !== input.markdown; |
| 55 | const last = input.editors[input.editors.length - 1] ?? null; | |
| 56 | const statements: D1PreparedStatement[] = []; | |
| 57 | if (changed || last) { | |
| 58 | statements.push( | |
| 59 | env.DB.prepare("UPDATE pages SET markdown = ?, updated_at = ?, updated_by = COALESCE(?, updated_by) WHERE id = ?").bind(input.markdown, at, last, page.id), | |
| 60 | ); | |
| 61 | } | |
| 62 | if (changed) { | |
| 63 | statements.push(env.DB.prepare("DELETE FROM pages_fts WHERE page_id = ?").bind(page.id)); | |
| 64 | statements.push(env.DB.prepare("INSERT INTO pages_fts (page_id, title, body) VALUES (?, ?, ?)").bind(page.id, page.title, searchText(input.markdown))); | |
| 65 | statements.push(env.DB.prepare("DELETE FROM page_links WHERE from_page = ?").bind(page.id)); | |
| 66 | for (const to of linkedPageIds(input.markdown).filter((id) => id !== page.id).slice(0, 200)) { | |
| 67 | statements.push(env.DB.prepare("INSERT OR IGNORE INTO page_links (from_page, to_page) VALUES (?, ?)").bind(page.id, to)); | |
| 68 | } | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 69 | // What it cites, from its text; the header's own stay. |
| 70 | statements.push(env.DB.prepare("DELETE FROM citations WHERE page_id = ? AND source = 'body'").bind(page.id)); | |
| 71 | for (const c of input.citations) { | |
| 72 | statements.push( | |
| 73 | env.DB.prepare("INSERT OR IGNORE INTO citations (page_id, repo, path, kind, label, ref, source) VALUES (?, ?, ?, ?, ?, ?, 'body')").bind(page.id, c.repo, c.path, c.kind, c.label ?? "", c.ref), | |
| 74 | ); | |
| 75 | } | |
| Docs: a workspace knowledge base people and agents write together | 76 | } |
| 77 | // A version: asked for (an agent's edit, a suggestion, a restore), or | |
| 78 | // the first save after enough time since the last one. | |
| 79 | let versionId: string | null = null; | |
| 80 | const timed = input.pending_authors.length > 0 && now.getTime() - input.last_version_at >= VERSION_EVERY_MS; | |
| 81 | if (input.version || (timed && changed)) { | |
| 82 | versionId = newId("ver", now.getTime()); | |
| 83 | const authors = input.version?.authors.length ? input.version.authors : input.pending_authors; | |
| 84 | const state = input.state.byteLength <= MAX_VERSION_STATE ? input.state : null; | |
| 85 | statements.push( | |
| 86 | env.DB.prepare("INSERT INTO page_versions (id, page_id, created_at, kind, authors, note, markdown, state) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").bind( | |
| 87 | versionId, | |
| 88 | page.id, | |
| 89 | at, | |
| 90 | input.version?.kind ?? "edit", | |
| 91 | JSON.stringify([...new Set(authors)]), | |
| 92 | input.version?.note ?? null, | |
| 93 | input.markdown, | |
| 94 | state, | |
| 95 | ), | |
| 96 | ); | |
| 97 | } | |
| 98 | // People mentioned for the first time. | |
| 99 | let told: string[] = []; | |
| 100 | try { | |
| 101 | told = JSON.parse(page.mentioned) as string[]; | |
| 102 | } catch { | |
| 103 | told = []; | |
| 104 | } | |
| 105 | const editors = new Set(input.editor_names); | |
| 106 | const fresh = input.mentioned.filter((name) => !told.includes(name) && !editors.has(name)); | |
| 107 | if (fresh.length || input.mentioned.length !== told.length) { | |
| 108 | statements.push(env.DB.prepare("UPDATE pages SET mentioned = ? WHERE id = ?").bind(JSON.stringify([...new Set([...told.filter((id) => input.mentioned.includes(id)), ...fresh])]), page.id)); | |
| 109 | } | |
| 110 | if (statements.length) await env.DB.batch(statements); | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 111 | // A version is what the rest of g1t hears of: at most every ten minutes of editing, and each agent edit, suggestion and restore. |
| 112 | const kind = input.version?.kind ?? "edit"; | |
| 113 | if (versionId && kind !== "created" && input.workspace_slug && !page.archived_at) { | |
| 114 | await publishDocEvent( | |
| 115 | env.EVENTS, | |
| 116 | "doc.page.updated", | |
| 117 | { | |
| 118 | workspace: input.workspace_slug, | |
| 119 | workspaceId: page.workspace_id, | |
| 120 | pageId: page.id, | |
| 121 | spaceId: page.space_id, | |
| 122 | title: page.title, | |
| 123 | path: `/${input.workspace_slug}/-/docs/${page.slug}/${pageSlug(page.title, page.id)}`, | |
| 124 | versionId, | |
| 125 | kind, | |
| 126 | authors: [...new Set(input.version?.authors.length ? input.version.authors : input.pending_authors)], | |
| 127 | }, | |
| 128 | last, | |
| 129 | ); | |
| 130 | } | |
| Docs: a workspace knowledge base people and agents write together | 131 | if (fresh.length && env.NOTIFY && !page.archived_at) { |
| 132 | const slug = input.workspace_slug; | |
| 133 | if (slug) { | |
| 134 | const href = `/${slug}/-/docs/${page.slug}/${pageSlug(page.title, page.id)}`; | |
| 135 | const notification = (username: string): FeedNotification => ({ | |
| 136 | id: `doc-mention:${page.id}:${username}`, | |
| 137 | kind: "mention", | |
| 138 | workspace: slug, | |
| 139 | title: `You were mentioned in ${page.title || "Untitled"}`, | |
| 140 | body: excerpt(input.markdown, 140), | |
| 141 | href, | |
| 142 | actor: { kind: last?.startsWith("agent:") ? "agent" : "user", id: last?.slice(last.indexOf(":") + 1) ?? "", name: "Docs" }, | |
| 143 | created_at: at, | |
| 144 | }); | |
| 145 | const notify = notifyClient(env.NOTIFY); | |
| 146 | await Promise.all(fresh.map((name) => notify.notify({ username: name }, notification(name)).catch(() => undefined))); | |
| 147 | } | |
| 148 | } | |
| Docs index by meaning: passages of every page and project doc, embedded on save and recalled for agents; hybrid search for people | 149 | return { version_id: versionId, changed }; |
| Docs: a workspace knowledge base people and agents write together | 150 | } |