import { documentPath, parseChildDocumentPath, parseDocumentPath, } from "@chopin/protocol/document-url"; import { documentUrl } from "../channels/document-url"; import { deterministicChannelId, isChannelId } from "../channels/id"; import { documentSlug } from "../channels/slug"; import { GitHubError } from "../github/client"; import * as Plan from "../plan/service"; import * as Rooms from "../rooms"; import { claimImplementation, reportImplementationLifecycle } from "../tasks/plan-graphs"; import { StorageError } from "../storage/errors"; import { implementationLifecycle } from "../tasks/lifecycle"; import type { Server } from "bun"; import type { HostedAuth } from "../auth/routes"; import type { GitHubUser } from "../github/client"; import type { AuthenticatedSession } from "../auth/session"; import type { HostedRepository } from "../agent/repository"; import type { DocumentSummary, Implementation, ImplementationInput, InvokePlannerError, McpOptions, RenameDocumentInput, } from "../mcp"; import type { UpdateClient, UpdateDocumentInput } from "./update"; import type { LifecycleArguments } from "./lifecycle"; import type { ChannelArchiveResult, ChannelRecord, Lease } from "../storage/model"; import type { ClaimResult, Run } from "../tasks/graphs"; import type { SocketData } from "../wire"; export type HostedCaller = { oauthToken: string; user: GitHubUser; }; export type PlannerInvocation = { channel: ChannelRecord; repository: HostedRepository; user: GitHubUser; /** The caller's live browser login, which may claim ownership for a channel without one. */ session?: AuthenticatedSession; instruction: string; /** Unverified; only the atomic harness reads it. */ checkout?: string; }; export type ImplementationPersistence = { lease(): Lease }; export type HostedCallbacks = { archiveChannel?: (channelId: string, now: Date) => Promise; restoreChannel?: (channelId: string, now: Date) => Promise; isChannelDeleting?: (channelId: string) => boolean; serializeDocument?: (channelId: string, action: () => Promise) => Promise; onChannelRenamed?: (channel: ChannelRecord) => void; onDocumentPersisted?: (target: Plan.DocumentTarget) => void; invokePlanner?: (input: PlannerInvocation) => Promise; }; const BEARER = new RegExp("^Bearer ([A-Za-z0-9._~+/-]+=*)$", "i"); type Document = { id: string; title: string; description?: string; creation?: Plan.CreationMetadata; source: string; revision: number; archivedAt?: Date; url?: string; }; type PublicDocument = Omit & { brief?: Plan.CreationMetadata["brief"]; archivedAt?: string; }; function summary(channel: ChannelRecord): DocumentSummary { return { id: channel.id, title: channel.title, ...(channel.description ? { description: channel.description.value } : {}), ...(channel.archivedAt ? { archivedAt: channel.archivedAt.toISOString() } : {}), }; } /** Strip durable idempotency and repository provenance from MCP responses. */ function document(value: Document & { url: string }): PublicDocument & { url: string }; function document(value: Document): PublicDocument; function document(value: Document): PublicDocument & { url?: string } { return { id: value.id, title: value.title, ...(value.description ? { description: value.description } : {}), ...(value.creation ? { brief: value.creation.brief } : {}), source: value.source, revision: value.revision, ...(value.archivedAt ? { archivedAt: value.archivedAt.toISOString() } : {}), ...(value.url ? { url: value.url } : {}), }; } function exposed( channel: ChannelRecord, state: Awaited>, ): Implementation | undefined { let version = state.graph?.versions.at(-1); if ( !state.creation || !version || (version.state !== "approved" && version.state !== "locked") ) { return undefined; } let lifecycle = state.graph && state.lifecycle ? implementationLifecycle({ graph: state.graph, execution: state.execution, lifecycle: state.lifecycle, }) : undefined; return { document: document({ id: channel.id, title: channel.title, ...(channel.description ? { description: channel.description.value } : {}), creation: state.creation, source: state.source, revision: state.revision, archivedAt: channel.archivedAt, }), repository: { name: state.creation.origin.repository, baseBranch: state.creation.origin.baseBranch, baseCommit: state.creation.origin.baseCommit, }, graph: version, execution: state.execution ? { state: "active", run: state.execution } : { state: "idle" }, activity: lifecycle?.activity, history: lifecycle?.history ?? [], }; } function claimResult(value: ClaimResult) { return value.kind === "started" ? { kind: "started" as const, run: value.run } : value; } /** Bind the backend-neutral MCP surface to hosted GitHub authentication. */ export function hosted( auth: HostedAuth, persistence?: ImplementationPersistence, callbacks: HostedCallbacks = {}, ): McpOptions { async function directRepository(caller: HostedCaller, owner: string, name: string) { try { return await auth.github.repository(caller.oauthToken, owner, name); } catch (err) { if (err instanceof GitHubError && (err.status === 403 || err.status === 404)) { return undefined; } throw err; } } async function locatedChannel(caller: HostedCaller, locator: string) { let path: URL | undefined; try { path = new URL(locator, auth.config.origin); } catch { path = undefined; } let local = path?.origin === auth.config.origin ? path.pathname : undefined; let child = local === undefined ? undefined : parseChildDocumentPath(local); if (child) { let repository = await directRepository(caller, child.owner, child.repository); if (!repository?.permissions.pull) return "forbidden" as const; let channel = await auth.storage.channels.resolve( repository.id, documentSlug(child.childSlug), ); let parent = channel?.parentChannelId ? await auth.storage.channels.resolve(repository.id, documentSlug(child.parentSlug)) : undefined; return channel && parent?.id === channel.parentChannelId ? { channel, repository } : undefined; } let parsed = local === undefined ? undefined : parseDocumentPath(local); if (parsed?.slug) { let repository = await directRepository(caller, parsed.owner, parsed.repository); if (!repository?.permissions.pull) return "forbidden" as const; let channel = await auth.storage.channels.resolve( repository.id, documentSlug(parsed.slug), ); return channel ? { channel, repository } : undefined; } let legacy = path?.origin === auth.config.origin ? /^\/channels\/([0-9a-f-]{36})\/?$/i.exec(path.pathname) : undefined; let id = legacy?.[1]?.toLowerCase(); let channel = await auth.storage.channels.get(id && isChannelId(id) ? id : locator); if (!channel) return undefined; let repository = await directRepository( caller, channel.repositoryOwner, channel.repositoryName, ); if (!repository?.permissions.pull || repository.id !== channel.repositoryId) { return "forbidden" as const; } return { channel, repository }; } async function writableChannel(caller: HostedCaller, locator: string) { let located = await locatedChannel(caller, locator); if (!located || located === "forbidden") return { kind: "unavailable" as const }; if (callbacks.isChannelDeleting?.(located.channel.id)) { return { kind: "unavailable" as const }; } if (!located.repository.permissions.push && !located.repository.permissions.admin) { return { kind: "forbidden" as const }; } return { kind: "allowed" as const, ...located }; } function run(caller: HostedCaller, input: ImplementationInput): Run { return { id: crypto.randomUUID(), user: caller.user.login, client: { name: input.client.name, version: input.client.version }, session: input.client.session, planRevision: input.planRevision, graphVersion: input.graphVersion, graphRevision: input.graphRevision, repository: input.repository, branch: input.branch, commit: input.commit, startedAt: auth.clock().toISOString(), }; } let documentOperations = new Map>(); function serializeDocument(channelId: string, action: () => Promise): Promise { if (callbacks.serializeDocument) return callbacks.serializeDocument(channelId, action); let previous = documentOperations.get(channelId) ?? Promise.resolve(); let operation = previous.then(action, action); let settled = operation.then(() => {}, () => {}); documentOperations.set(channelId, settled); void settled.then(() => { if (documentOperations.get(channelId) === settled) documentOperations.delete(channelId); }); return operation; } let silentServer = { publish() {} } as unknown as Server; async function withPlan( channelId: string, action: (plan: Plan.Plan) => Promise, ): Promise { let live = Rooms.get(channelId)?.plan; if (live) { await Plan.drain(live); return Plan.exclusive(live, () => action(live)); } if (!persistence) throw new Error("document is unavailable"); let opened = await Plan.open(channelId, { storage: auth.storage, lease: persistence.lease, fatal: error => { throw error; }, onDocumentPersisted: callbacks.onDocumentPersisted, }, silentServer); try { return await Plan.exclusive(opened, () => action(opened)); } finally { await Plan.close(opened); } } return { invoke: callbacks.invokePlanner ? { async invoke(caller, input) { let located = await locatedChannel(caller, input.id); if (!located) return { kind: "refused", code: "document-unavailable" }; if ( located === "forbidden" || (!located.repository.permissions.push && !located.repository.permissions.admin) ) { return { kind: "refused", code: "repository-forbidden" }; } let { channel, repository } = located; if (callbacks.isChannelDeleting?.(channel.id)) { return { kind: "refused", code: "document-unavailable", }; } if (channel.archivedAt) return { kind: "refused", code: "document-archived" }; let code = await callbacks.invokePlanner!({ channel, repository, user: caller.user, session: await auth.sessions.forUser(caller.user.id), instruction: input.instruction, ...(input.checkout === undefined ? {} : { checkout: input.checkout }), }); if (code) return { kind: "refused", code }; return { kind: "invoked", document: { ...summary(channel), url: new URL(await documentUrl(channel, auth.storage.channels), auth.config.origin).href, }, }; }, } : undefined, async caller(request) { let match = request.headers.get("authorization")?.match(BEARER); if (!match) return undefined; let oauthToken = match[1]!; try { return { oauthToken, user: await auth.admission.user(oauthToken) }; } catch (err) { if (err instanceof GitHubError && err.status === 401) return undefined; throw err; } }, documents: { async list(caller, repository, includeArchived = false) { let parts = repository.split("/"); if (parts.length !== 2 || !parts[0] || !parts[1]) return "forbidden"; let resolved = await directRepository(caller, parts[0], parts[1]); if (!resolved?.permissions.pull) return "forbidden"; let documents = []; let cursor; do { let page = await auth.storage.channels.scan( resolved.id, 100, cursor, includeArchived, ); documents.push(...page.channels.map(summary)); cursor = page.next; } while (cursor); return documents; }, async read(caller, id) { let located = await locatedChannel(caller, id); if (!located || located === "forbidden") return undefined; let { channel } = located; let url = await documentUrl(channel, auth.storage.channels); let live = Rooms.get(channel.id)?.plan; if (live) { try { return await Plan.exclusive(live, async () => document({ id: channel.id, title: channel.title, ...(channel.description ? { description: channel.description.value } : {}), creation: live.creation, source: Plan.source(live), revision: live.revision, archivedAt: channel.archivedAt, url, })); } catch { return undefined; } } let stored = await auth.storage.collaboration.load(channel.id, auth.clock()); if ( !stored || stored.channel.id !== channel.id || stored.channel.repositoryId !== channel.repositoryId || stored.channel.repositoryOwner !== channel.repositoryOwner || stored.channel.repositoryName !== channel.repositoryName ) return undefined; try { let projected = await Plan.readStored(stored); return document({ id: channel.id, title: channel.title, ...(channel.description ? { description: channel.description.value } : {}), creation: projected.creation, source: projected.source, revision: projected.revision, archivedAt: channel.archivedAt, url, }); } catch { return undefined; } }, }, rename: { async rename(caller, input: RenameDocumentInput) { let access = await writableChannel(caller, input.id); if (access.kind === "unavailable") return access; if (access.kind === "forbidden") { return { kind: "forbidden" as const }; } if (access.channel.archivedAt) return { kind: "archived" as const }; try { let result = await auth.storage.channels.rename({ id: access.channel.id, title: input.title, now: auth.clock(), }); if (result.changed) callbacks.onChannelRenamed?.(result.channel); return { kind: result.changed ? "renamed" as const : "unchanged" as const, document: summary(result.channel), }; } catch (err) { if (err instanceof StorageError && err.failure === "conflict") { let current = await auth.storage.channels.get(input.id); if (!current || callbacks.isChannelDeleting?.(input.id)) { return { kind: "unavailable" as const }; } if (current.archivedAt) return { kind: "archived" as const }; return { kind: "conflict" as const }; } if (err instanceof StorageError && err.failure === "missing") { return { kind: "unavailable" as const }; } throw err; } }, }, archive: { async archive(caller, id) { let access = await writableChannel(caller, id); if (access.kind !== "allowed") return access; try { let now = auth.clock(); let result = callbacks.archiveChannel ? await callbacks.archiveChannel(access.channel.id, now) : await auth.storage.channels.archive({ id: access.channel.id, now }); if (!result.channel.archivedAt) { throw new Error("archiving a document returned active metadata"); } return { kind: result.changed ? "archived" as const : "unchanged" as const, document: summary(result.channel), }; } catch (err) { if (err instanceof StorageError && err.failure === "missing") { return { kind: "unavailable" as const }; } throw err; } }, }, restore: { async restore(caller, id) { let access = await writableChannel(caller, id); if (access.kind !== "allowed") return access; try { let now = auth.clock(); let result = callbacks.restoreChannel ? await callbacks.restoreChannel(access.channel.id, now) : await auth.storage.channels.restore({ id: access.channel.id, now }); if (result.channel.archivedAt) { throw new Error("restoring a document returned archived metadata"); } return { kind: result.changed ? "restored" as const : "unchanged" as const, document: summary(result.channel), }; } catch (err) { if (err instanceof StorageError && err.failure === "missing") { return { kind: "unavailable" as const }; } throw err; } }, }, create: { async create(caller, input) { let parts = input.repository.split("/"); if (parts.length !== 2 || !parts[0] || !parts[1]) return { kind: "forbidden" }; let repository = await directRepository(caller, parts[0], parts[1]); if (!repository || (!repository.permissions.push && !repository.permissions.admin)) { return { kind: "forbidden" }; } let id = deterministicChannelId(repository.id, input.idempotencyKey); if (callbacks.isChannelDeleting?.(id)) return { kind: "unavailable" }; await auth.storage.users.put({ id: caller.user.id, login: caller.user.login, avatarUrl: caller.user.avatarUrl, now: auth.clock(), }); let { brief, plan, ...origin } = input; let creation: Plan.CreationMetadata = { brief, origin }; let initial = await Plan.initial(plan, creation); let created: ChannelRecord; try { created = await auth.storage.channels.create({ id, repositoryId: repository.id, repositoryOwner: repository.owner, repositoryName: repository.name, title: input.title, createdBy: caller.user.id, now: auth.clock(), initial, }); } catch (err) { if (!(err instanceof StorageError) || err.failure !== "conflict") throw err; if (callbacks.isChannelDeleting?.(id)) return { kind: "unavailable" }; let stored = await auth.storage.collaboration.load(id, auth.clock()); // Nothing stored under this key's id: the repository already has a document with this title. if (!stored) return { kind: "title-taken" }; if (stored.channel.repositoryId !== repository.id) return { kind: "conflict" }; let restored = await Plan.readStored(stored); if ( !restored.creation || restored.creation.origin.idempotencyKey !== input.idempotencyKey || restored.creation.origin.fingerprint !== input.fingerprint ) return { kind: "conflict" }; callbacks.onDocumentPersisted?.({ channelId: id, revision: restored.revision, source: restored.source, sourceHash: Plan.sourceHash(restored.source), }); return { kind: "replayed", document: document({ id, title: stored.channel.title, ...(stored.channel.description ? { description: stored.channel.description.value } : {}), creation: restored.creation, source: restored.source, revision: restored.revision, archivedAt: stored.channel.archivedAt, url: documentPath( repository.owner, repository.name, stored.channel.slug, ), }), }; } callbacks.onDocumentPersisted?.({ channelId: id, revision: 0, source: initial.source, sourceHash: initial.sourceHash, }); return { kind: "created", document: document({ id, title: created.title, ...(created.description ? { description: created.description.value } : {}), creation, source: initial.source, revision: 0, url: documentPath(repository.owner, repository.name, created.slug), }), }; }, }, update: { async update(caller, input: UpdateDocumentInput, client: UpdateClient) { let access = await writableChannel(caller, input.id); if (access.kind !== "allowed") return access; try { return await serializeDocument(access.channel.id, async () => { let current = await writableChannel(caller, access.channel.id); if (current.kind !== "allowed") return current; let { channel, repository } = current; return withPlan(channel.id, async plan => { let existing = plan.mcpUpdates.find(entry => entry.idempotencyKey === input.idempotencyKey ); if (existing) { return existing.fingerprint === input.fingerprint ? { kind: "replayed" as const, document: document({ id: channel.id, title: existing.document.title, ...(existing.document.description ? { description: existing.document.description } : {}), creation: plan.creation, source: existing.document.source, revision: existing.document.revision, url: existing.document.url, }), } : { kind: "conflict" as const }; } if (channel.archivedAt) return { kind: "archived" as const }; if (Plan.implementationActive(plan)) return { kind: "locked" as const }; if (input.revision !== plan.revision) { return { kind: "revision-conflict" as const, revision: plan.revision, }; } let url = documentPath(repository.owner, repository.name, channel.slug); let outcome = await Plan.rewrite(plan, input.plan, (source, revision) => ({ idempotencyKey: input.idempotencyKey, fingerprint: input.fingerprint, fromRevision: input.revision, client, document: { source, revision, title: channel.title, url, ...(channel.description ? { description: channel.description.value } : {}), }, })); if (!outcome.ok) { return outcome.reason === "stale" ? { kind: "revision-conflict" as const, revision: outcome.revision, } : { kind: "protected" as const }; } Plan.changes(plan, plan.server, channel.id, outcome.changes, { cursor: false, attribution: { client, user: caller.user.login, fromRevision: input.revision, revision: plan.revision, }, }); Plan.anchors(plan, plan.server, channel.id); return { kind: "updated" as const, document: document({ id: channel.id, title: channel.title, ...(channel.description ? { description: channel.description.value } : {}), creation: plan.creation, source: Plan.source(plan), revision: plan.revision, url, }), }; }); }); } catch (err) { if (err instanceof Error && err.message === "document is unavailable") { return { kind: "unavailable" as const }; } if (err instanceof StorageError && err.failure === "missing") { return { kind: "unavailable" as const }; } throw err; } }, }, ...(persistence ? { implementations: { async readImplementation(caller: HostedCaller, id: string) { let located = await locatedChannel(caller, id); if (located === "forbidden") return "forbidden" as const; if (!located) return undefined; let { channel } = located; let live = Rooms.get(channel.id)?.plan; if (live) { return Plan.exclusive(live, async () => exposed(channel, { source: Plan.source(live), revision: live.revision, ...(live.creation ? { creation: live.creation } : {}), ...(live.graph ? { graph: live.graph } : {}), ...(live.execution ? { execution: live.execution } : {}), lifecycle: live.lifecycle, })); } let stored = await auth.storage.collaboration.load(channel.id, auth.clock()); return stored ? exposed(channel, await Plan.readStored(stored)) : undefined; }, async startImplementation(caller: HostedCaller, input: ImplementationInput) { return serializeDocument(input.id, async () => { let access = await writableChannel(caller, input.id); if (access.kind !== "allowed") return access; let channel = access.channel; if (`${channel.repositoryOwner}/${channel.repositoryName}` !== input.repository) { return { kind: "forbidden" as const }; } if (channel.archivedAt) { return { kind: "refused" as const, reason: "document-archived" }; } let claimRun = run(caller, input); let live = Rooms.get(input.id)?.plan; if (live) { return claimResult( await claimImplementation(live, { planRevision: input.planRevision, graphRevision: input.graphRevision, run: claimRun, }), ); } for (let attempt = 0; attempt < 2; attempt++) { if (callbacks.isChannelDeleting?.(input.id)) { return { kind: "unavailable" as const }; } let stored = await auth.storage.collaboration.load(input.id, auth.clock()); if (!stored) return { kind: "unavailable" as const }; if (stored.channel.archivedAt) { return { kind: "refused" as const, reason: "document-archived" }; } if (!stored.snapshot) return { kind: "refused" as const, reason: "missing" }; let prepared = Plan.claimStored(stored, { planRevision: input.planRevision, graphRevision: input.graphRevision, run: claimRun, }); if (prepared.result.kind !== "started" || !prepared.sidecar) { return claimResult(prepared.result); } try { await auth.storage.collaboration.commit({ channelId: input.id, lease: persistence.lease(), expectedRevision: stored.channel.revision, operationId: `implementation:${claimRun.id}`, epoch: stored.snapshot.epoch, sidecar: prepared.sidecar, events: [], now: auth.clock(), }); return claimResult(prepared.result); } catch (err) { if (err instanceof StorageError && err.failure === "missing") { return { kind: "unavailable" as const }; } if (!(err instanceof StorageError) || err.failure !== "conflict") throw err; } } return { kind: "refused" as const, reason: "conflict" }; }); }, async reportLifecycle(caller: HostedCaller, input: LifecycleArguments) { return serializeDocument(input.id, async () => { let access = await writableChannel(caller, input.id); if (access.kind !== "allowed") return access; let { id: _id, ...event } = input; let live = Rooms.get(input.id)?.plan; if (live) { let result = await reportImplementationLifecycle(live, event); if (result.kind === "refused") return result; return { kind: result.kind, lifecycle: implementationLifecycle({ graph: result.state.graph, execution: result.state.execution, lifecycle: result.state.lifecycle, }), }; } for (let attempt = 0; attempt < 2; attempt++) { if (callbacks.isChannelDeleting?.(input.id)) { return { kind: "unavailable" as const }; } let stored = await auth.storage.collaboration.load(input.id, auth.clock()); if (!stored) return { kind: "unavailable" as const }; if (!stored.snapshot) return { kind: "refused" as const, reason: "inactive" }; let prepared = Plan.lifecycleStored(stored, event); if (prepared.result.kind === "refused") return prepared.result; if (prepared.result.kind === "replayed") { return { kind: "replayed" as const, lifecycle: implementationLifecycle({ graph: prepared.result.state.graph, execution: prepared.result.state.execution, lifecycle: prepared.result.state.lifecycle, }), }; } if (!prepared.sidecar) return { kind: "refused" as const, reason: "inactive" }; try { await auth.storage.collaboration.commit({ channelId: input.id, lease: persistence.lease(), expectedRevision: stored.channel.revision, operationId: `lifecycle:${input.idempotencyKey}`, epoch: stored.snapshot.epoch, sidecar: prepared.sidecar, events: [], now: auth.clock(), allowArchived: true, }); return { kind: "accepted" as const, lifecycle: implementationLifecycle({ graph: prepared.result.state.graph, execution: prepared.result.state.execution, lifecycle: prepared.result.state.lifecycle, }), }; } catch (err) { if (err instanceof StorageError && err.failure === "missing") { return { kind: "unavailable" as const }; } if (!(err instanceof StorageError) || err.failure !== "conflict") throw err; } } return { kind: "refused" as const, reason: "conflict" }; }); }, }, } : {}), }; }