/** * The server. * * One process: the WebSocket every room talks over, and — in production — the * built client alongside it. In development Vite serves the client and proxies * `/ws` here, so the browser sees one origin either way and the client never * needs to know which mode it is in. */ import { join } from "node:path"; import { ulid } from "@chopin/dialect"; import { harnessFor, shutdownHarnesses } from "./harness/harnesses"; import { ActiveOwnerBindings } from "./agent/active-owner"; import { registerAuthRoutes } from "./auth/routes"; import { registerExperimentRoutes } from "./experiments/routes"; import type { ExperimentRuntime } from "./experiments/routes"; import { fingerprint } from "./experiments/service"; import * as Chat from "./chat/service"; import { CHAT_CAPABILITIES, incomingFrame, sidebarFrame } from "./chat/incoming"; import { ReferenceService } from "./chat/references"; import { createConversationRuntime } from "./conversation-plan/runtime"; import { researchBriefDefinition } from "./jobs/research-brief"; import { greetJoinedPlan, prepareOpenedPlan, readyPlan, recoverAttachedPlan, } from "./conversation-plan/service-opening"; import { handleConversationCommand } from "./conversation-plan/commands"; import { handleResearchCommand } from "./conversation-plan/research-commands"; import { startAcceptedResearch } from "./conversation-plan/accepted-research"; import { registerChannelRoutes } from "./channels/routes"; import * as Comments from "./comments/service"; import { proxy, serve } from "./client"; import { describe, load } from "./config"; import { GitHubClient, GitHubError } from "./github/client"; import { GitHubReferences } from "./github/references"; import { Router } from "./http/router"; import { JobExecutionError, JobRegistry } from "./jobs/registry"; import * as JobBrowser from "./jobs/browser"; import { DocumentDescriptionProjector } from "./jobs/document-description"; import { documentSummaryDefinition } from "./jobs/document-summary"; import { JobRunner } from "./jobs/runner"; import { researchAnswerDefinition, researchEvidenceDefinition } from "./jobs/research-workspace"; import { JobService } from "./jobs/service"; import { DocumentSummaryCoordinator } from "./jobs/summary-coordinator"; import { registerMcpRoutes } from "./mcp/routes"; import { registerNavigationRoutes } from "./navigation/routes"; import * as Service from "./plan/service"; import * as Inject from "./questions/inject"; import * as Marks from "./comments/inject"; import * as Questions from "./questions/service"; import { backfillPlannerAskThreads } from "./questions/backfill"; import { registerResearchWorkspaceRoutes } from "./research/routes"; import { ResearchWorkspaceError, ResearchWorkspaceService } from "./research/service"; import { placeResearchReference as placeResearch } from "./research/placement"; import * as Rooms from "./rooms"; import { admit, admitSidebar } from "./socket/admission"; import { refreshAuthorization } from "./socket/authorization"; import { recheckDecisionWatch, releaseDecisionWatch, repositoryReader, unwatchDecisions, unwatchRequest, watchDecisions, watchRequest, } from "./socket/decision-watch"; import { StorageError } from "./storage/errors"; import { createStorage } from "./storage/registry"; import { broadcast, fail, relay, reply, repositoryTopic, tell, topic } from "./wire"; import type { Server, ServerWebSocket } from "bun"; import type { RepositoryAuthorizer, WatchTopics } from "./socket/decision-watch"; import type { DocumentSummaryInput } from "./jobs/document-summary"; import type { JobDefinition } from "./jobs/registry"; import type { ChannelRecord, Lease } from "./storage/model"; import type { AuthorizationResult, SidebarSocket, SidebarSocketData, Socket, SocketData, } from "./wire"; const config = load(); harnessFor(config); const storage = createStorage(config.storage); const router = new Router(); /** Where the built client lands. Used only when there is no dev client. */ const CLIENT = join(import.meta.dir, "../../web/dist"); /** * How long an empty room is kept before its document is released. * * A reload is a departure followed by an arrival, and discarding the document * in between would cost the epoch and everyone's undo history for a keypress. */ const EVICT_MS = 30_000; const LEASE_TTL_MS = 30_000; const LEASE_RENEW_MS = 10_000; const LEASE_SAFETY_MS = 5_000; const SESSION_CLEANUP_MS = 5 * 60_000; const ACCESS_RECHECK_MS = 60_000; const RESEARCH_RECOVERY_RETRY_MS = 10_000; let server: Server; let heldLease: Lease | undefined; let experiments: ExperimentRuntime | undefined; let experimentTimer: ReturnType | undefined; let leaseRenewal: ReturnType | undefined; let leaseWatchdog: ReturnType | undefined; let renewingLease: Promise | undefined; let sessionCleanup: ReturnType | undefined; let cleaningSessions: Promise | undefined; let ownerBindings: ActiveOwnerBindings | undefined; let jobRunner: JobRunner | undefined; let researchService: ResearchWorkspaceService | undefined; let researchRecoveryTimer: ReturnType | undefined; let recoveringResearch: Promise | undefined; let referenceService: ReferenceService | undefined; let summaryCoordinator: DocumentSummaryCoordinator | undefined; let descriptionProjector: DocumentDescriptionProjector | undefined; let researchBriefJobs: JobService | undefined; let documentLocks = new Map>(); let documentTransitions = new Map>(); let archivingChannels = new Set(); let deletingChannels = new Set(); let conversationRuntime = createConversationRuntime({ config, server: () => server, unavailable: id => archivingChannels.has(id) || deletingChannels.has(id), researchJobs: () => researchBriefJobs, }); function withDocumentLock(channelId: string, action: () => Promise): Promise { let previous = documentLocks.get(channelId) ?? Promise.resolve(); let operation = previous.then(action, action); let settled = operation.then(() => {}, () => {}); documentLocks.set(channelId, settled); void settled.finally(() => { if (documentLocks.get(channelId) === settled) documentLocks.delete(channelId); }); return operation; } function withDocumentTransition(channelId: string, action: () => Promise): Promise { let previous = documentTransitions.get(channelId) ?? Promise.resolve(); let operation = previous.then(action, action); let settled = operation.then(() => {}, () => {}); documentTransitions.set(channelId, settled); void settled.finally(() => { if (documentTransitions.get(channelId) === settled) documentTransitions.delete(channelId); }); return operation; } function presence(server: Server, room: Rooms.Room): void { broadcast(server, room.id, { kind: "session:presence", ts: 0, members: Rooms.members(room), }); } function documentBackend(): Service.Backend { return { storage, lease: () => { if (!heldLease) throw new Error("storage writer lease is unavailable"); return heldLease; }, fatal: err => { console.error("chopin: plan persistence failed -", err); signal(); }, onDocumentPersisted: target => summaryCoordinator?.schedule(target), }; } function placeResearchReference( channelId: string, workspaceId: string, ): Promise<"placed" | "deferred"> { return placeResearch(channelId, workspaceId, { get: Rooms.get, exclusive: withDocumentLock, detached: async (id, action) => { let detached = await Service.open(id, documentBackend(), server); try { return await action(detached); } finally { await Service.close(detached); } }, }); } function scheduleResearchRecovery(deferred: number): void { if (deferred === 0 || researchRecoveryTimer || draining) return; researchRecoveryTimer = setTimeout(() => { researchRecoveryTimer = undefined; if (draining) return; recoveringResearch = researchService!.recoverPendingPlannerInline(placeResearchReference).then( result => { scheduleResearchRecovery(result.deferred); }, err => { console.error("chopin: research reference recovery failed -", err); signal(); }, ).finally(() => { recoveringResearch = undefined; }); }, RESEARCH_RECOVERY_RETRY_MS); } /** * Attach the document to a room, once. * * Two clients opening at the same moment must not build two documents, so the * first stores its promise and the second waits on it. */ async function plan(room: Rooms.Room, server: Server): Promise { if (room.closing) await room.closing; if (deletingChannels.has(room.id)) throw new Error("document is unavailable"); if (room.opening) { let opened = await room.opening; conversationRuntime.wake(opened); return opened; } if (room.plan) { conversationRuntime.wake(room.plan); return room.plan; } let backend = documentBackend(); let opening = room.opening ??= withDocumentLock(room.id, async () => { if (deletingChannels.has(room.id)) throw new Error("document is unavailable"); if (room.plan) return room.plan; let opened = await Service.open(room.id, backend, server); let channel: ChannelRecord | undefined; await prepareOpenedPlan(room, opened, async () => { channel = await storage.channels.get(room.id); if (room.closing) throw new Error("document is unavailable"); if (config.conversationPlan && !channel?.archivedAt) { await backfillPlannerAskThreads(opened); } }); await conversationRuntime.attach( room, opened, !!channel?.archivedAt, !!channel && !channel.parentChannelId, ); if (!channel?.archivedAt) { if (summaryCoordinator) void summaryCoordinator.ensure(room.id).catch(() => {}); if (Inject.enabled(opened)) Inject.ask(opened, server, room.id); if (Marks.enabled(opened)) await Marks.mark(opened); } return opened; }); try { let opened = await opening; conversationRuntime.wake(opened); return opened; } finally { if (room.opening === opening) room.opening = undefined; } } function chat(room: Rooms.Room, ws: Socket): Chat.Room { return conversation(room, ws.data.sessionId, { id: ws.data.repositoryId, owner: ws.data.repositoryOwner, name: ws.data.repositoryName, defaultBranch: ws.data.repositoryDefaultBranch, }); } /** A room's Chat, shared by browser messages and MCP handoffs. */ function conversation( room: Rooms.Room, claimantSessionId: string | undefined, repository: Chat.Room["repository"], ): Chat.Room { let opened = room.plan!; return conversationRuntime.bind({ chat: opened.chat, plan: opened, server, room: room.id, config, auth: hostedAuth, claimantSessionId, repository, persist: chat => Service.persist(opened, chat), activeOwner: () => ownerBindings!.resolve(room.id), ownerAvailable: () => jobRunner?.ownerAvailable(room.id) ?? Promise.resolve(), jobs: config.backgroundJobs ? jobService : undefined, references: referenceService, investigations: experiments ? { async list() { return { investigations: (await experiments!.service.store.list(room.id)).slice(0, 20).map( value => ({ id: value.id, brief: value.brief.slice(0, 400), state: value.state }), ), workspaces: experiments!.connections.list(room.id).map(value => ({ label: value.label, owner: value.login, })), }; }, async read(id, dataset, offset = 0) { let value = await experiments!.service.store.get(id); if (!value || value.documentId !== room.id) throw new Error("Investigation unavailable"); if (dataset) { let data = value.result?.datasets.find(item => item.key === dataset); if (!data) throw new Error("Dataset unavailable"); let rows: typeof data.rows = []; for (let row of data.rows.slice(offset, offset + 50)) { if (JSON.stringify([...rows, row]).length > 40_000) break; rows.push(row); } if (!rows.length && offset < data.rows.length) { throw new Error( "This row exceeds the tool response limit. Inspect the dataset download or request a later offset.", ); } return { ...data, rows, totalRows: data.rows.length, nextOffset: offset + rows.length < data.rows.length ? offset + rows.length : undefined, }; } return { id: value.id, state: value.state, source: value.input?.source, report: value.result?.report.slice(0, 16_000), reportTruncated: (value.result?.report.length ?? 0) > 16_000, datasets: value.result?.datasets.map(data => ({ key: data.key, title: data.title, rows: data.rows.length, })), decisions: value.decisions.slice(-10).map(decision => ({ ...decision, rationale: decision.rationale.slice(0, 2000), rationaleTruncated: decision.rationale.length > 2000, })), }; }, async propose(request) { await experiments!.access( await hostedAuth.sessions.forUser(request.userId), room.id, true, ); await experiments!.mutationAllowed(room.id); let hash = fingerprint([room.id, request.entryId, request.key]); let id = `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-a${ hash.slice(17, 20) }-${hash.slice(20, 32)}`; let value = await experiments!.service.create(room.id, request.userId, request.brief, id); return { id: value.id, state: value.state, message: "Open Investigations and authorize Run on my workspace to execute.", }; }, } : undefined, hold: () => { let held = Rooms.hold(room.id); return () => { held.release(); evict(held.room); }; }, createResearch: config.agent ? async request => { let service = researchService; if (!service) throw new Error("research workspaces are unavailable"); let created; try { created = await service.startPlannerInline({ channelId: room.id, question: request.question, originMessageId: request.entryId, requestedBy: request.userId, requestedByHandle: request.handle, beforeStart: () => jobRunner?.ownerAvailable(room.id) ?? Promise.resolve(), placeReference: id => placeResearchReference(room.id, id), }); } catch (err) { if (err instanceof ResearchWorkspaceError && err.code === "not-ready") { scheduleResearchRecovery(1); } throw err; } return { workspaceId: created.request.id, state: created.request.state, stage: created.request.stage, }; } : undefined, }); } async function closeRoom(room: Rooms.Room, force = false): Promise { if (room.closing) return room.closing; let closing = withDocumentLock(room.id, async () => { if (!force && (room.members.size > 0 || room.holds)) return; let held = room.plan; room.plan = undefined; if (held) { let stopped = conversationRuntime.stop(held); await stopped; await Service.close(held); } if (force || room.members.size === 0) Rooms.forget(room); }); room.closing = closing; try { await closing; } finally { if (room.closing === closing) room.closing = undefined; } } function evict(room: Rooms.Room): void { if (room.eviction || room.members.size > 0 || room.holds) return; room.eviction = setTimeout(() => { room.eviction = undefined; if (room.members.size > 0 || room.holds) return; void closeRoom(room).catch(err => { console.error("chopin: room close failed -", err); }); }, EVICT_MS); } async function receive(ws: Socket, raw: string): Promise { let frame = incomingFrame(raw); if (!frame) return; let room = Rooms.get(ws.data.room); if (!room) return; if (deletingChannels.has(room.id)) { if (frame.rid) fail(ws, frame.rid, "document is unavailable"); return; } if (!VIEWER_ALLOWED.has(frame.kind)) { let access = await refreshAccess(ws); if (access === "unavailable") { if (frame.rid) fail(ws, frame.rid, "authorization is temporarily unavailable"); return; } if (access === "denied") { if (frame.rid) fail(ws, frame.rid, "authorization expired"); ws.close(4403, "authorization expired"); return; } } if (!ws.data.canEdit && !VIEWER_ALLOWED.has(frame.kind)) { if (frame.rid) fail(ws, frame.rid, "repository write access is required"); return; } switch (frame.kind) { case "session:ping": return tell(ws, { kind: "session:ping", ts: 0, rid: frame.rid }); case "plan:open": { try { let opened = await plan(room, server); await greetJoinedPlan( opened, ws, frame, !!config.conversationPlan, () => Rooms.get(room.id) === room && room.plan === opened && !room.closing && !deletingChannels.has(room.id) && !ws.data.closed && ws.data.room === room.id && room.members.get(ws.data.client) === ws, ); } catch (err) { fail(ws, frame.rid, err instanceof Error ? err.message : "cannot open plan"); } return; } case "plan:update": if (room.plan) Service.submit(room.plan, ws, frame); return; case "plan:awareness": if (room.plan) Service.awareness(room.plan, ws, frame); return; case "plan:close": if (room.plan) Service.departed(room.plan, ws); return; case "chat:send": try { let opened = await readyPlan(room); if (!opened) { fail(ws, frame.rid, "document is unavailable"); return; } let access = await refreshAccess(ws); if (access === "unavailable") { fail(ws, frame.rid, "authorization is temporarily unavailable"); return; } if (access === "denied") { fail(ws, frame.rid, "authorization expired"); ws.close(4403, "authorization expired"); return; } if (!ws.data.canEdit) { fail(ws, frame.rid, "repository write access is required"); return; } if ( Rooms.get(room.id) !== room || room.plan !== opened || room.closing || archivingChannels.has(room.id) || deletingChannels.has(room.id) || ws.data.closed || ws.data.room !== room.id || room.members.get(ws.data.client) !== ws ) { fail(ws, frame.rid, "document is unavailable"); return; } await Chat.send(chat(room, ws), ws, frame); } catch (error) { fail(ws, frame.rid, error instanceof Error ? error.message : "cannot send message"); } return; case "chat:abort": if (room.plan) await Chat.abort(chat(room, ws), ws); return; case "chat:resume": if (room.plan) await Chat.resume(chat(room, ws), ws); return; case "chat:pause-run": case "chat:resume-run": if (room.plan) await Chat.controlRun(chat(room, ws), ws, frame); return; case "chat:unqueue": if (room.plan) Chat.unqueue(chat(room, ws), ws, frame); return; case "conversation-plan:correct": case "conversation-plan:scoped-choice-save": case "conversation-plan:retry": case "conversation-plan:retry-job": await handleConversationCommand(frame, room, ws, { enabled: !!config.conversationPlan, runtime: conversationRuntime, unavailable: id => archivingChannels.has(id) || deletingChannels.has(id), refreshAccess: () => refreshAccess(ws), chat: () => chat(room, ws), }); return; case "conversation-plan:research": case "conversation-plan:research-link": case "conversation-plan:research-edit": case "conversation-plan:research-presence": await handleResearchCommand(frame, room, ws, { enabled: !!config.conversationPlan, canExecute: config.webResearch, eligible: async () => { let channel = await storage.channels.get(room.id); return !!channel && !channel.parentChannelId && !channel.archivedAt; }, runtime: conversationRuntime, research: () => researchService, unavailable: id => archivingChannels.has(id) || deletingChannels.has(id), refreshAccess, start: (current, socket, opened, offer) => startAcceptedResearch(current, socket, opened, offer, { research: () => researchService, auth: hostedAuth, refreshAccess, unavailable: id => archivingChannels.has(id) || deletingChannels.has(id), ownerAvailable: id => jobRunner?.ownerAvailable(id) ?? Promise.resolve(), placeReference: placeResearchReference, scheduleRecovery: scheduleResearchRecovery, }), }); return; case "question:open": if (room.plan) Questions.open(room.plan, ws, frame); return; case "question:edit": if (room.plan) await Questions.edit(room.plan, ws, frame); return; case "question:discard": if (room.plan) await Questions.discard(room.plan, server, room.id, ws, frame); return; case "question:reopen": if (room.plan) await Questions.reopen(room.plan, server, room.id, ws, frame); return; case "question:presence": if (room.plan) Questions.focus(room.plan, ws, frame); return; case "question:submit": if (room.plan) { let opened = room.plan; let claimant = chat(room, ws); await Questions.submit( opened, server, room.id, ws, frame, intent => conversationRuntime.contexts.remember(opened, intent.trigger, claimant), ); } return; case "question:cancel": if (room.plan) await Questions.cancel(room.plan, server, room.id, ws, frame); return; case "question:option": if (room.plan) await Questions.addOption(room.plan, server, room.id, ws, frame); return; case "comment:start": if (room.plan) await Comments.start(room.plan, server, room.id, ws, frame); return; case "comment:reply": if (room.plan) await Comments.respond(room.plan, ws, frame); return; case "comment:typing": if (room.plan) Comments.typing(room.plan, ws, frame); return; case "comment:accept": if (room.plan) await Comments.accept(chat(room, ws), ws, frame); return; case "comment:dismiss": if (room.plan) await Comments.dismiss(chat(room, ws), ws, frame); return; case "job:list": try { if (!config.backgroundJobs) throw new Error("background jobs are disabled"); reply(ws, frame.rid, await JobBrowser.listJobs(jobService, room.id)); } catch (err) { fail(ws, frame.rid, err instanceof Error ? err.message : "cannot list jobs"); } return; case "job:get": try { if (!config.backgroundJobs) throw new Error("background jobs are disabled"); reply(ws, frame.rid, await JobBrowser.getJob(jobService, room.id, frame.id)); } catch (err) { fail(ws, frame.rid, err instanceof Error ? err.message : "cannot read job"); } return; case "job:cancel": try { if (!config.backgroundJobs) throw new Error("background jobs are disabled"); reply( ws, frame.rid, await JobBrowser.cancelResearchWorkspaceJob( jobService, room.id, frame.id, ), ); } catch (err) { fail(ws, frame.rid, err instanceof Error ? err.message : "cannot cancel job"); } return; } } const VIEWER_ALLOWED = new Set([ "session:ping", "plan:open", "plan:close", "job:list", "job:get", "conversation-plan:research-link", ]); type AnySocket = ServerWebSocket; function isSidebarSocket(ws: AnySocket): ws is SidebarSocket { return ws.data.sidebar === true; } function decisionTopics(ws: SidebarSocket): WatchTopics { return { subscribe: repositoryId => ws.subscribe(repositoryTopic(repositoryId)), unsubscribe: repositoryId => ws.unsubscribe(repositoryTopic(repositoryId)), }; } function watchingRepository(ws: SidebarSocket, repositoryId: string): boolean { return !ws.data.closed && ws.data.decisionWatch.repositories.has(repositoryId); } async function sidebarRepositoryReader( ws: SidebarSocket, ): Promise { let data = ws.data; if (data.closed || data.authorizedUntil <= Date.now()) return undefined; let request = new Request(hostedAuth.config.origin, { headers: { cookie: data.credential } }); let session = await hostedAuth.sessions.authenticate(request); if (!session || session.user.id !== data.principalId) return undefined; data.authorizedUntil = session.session.expiresAt.getTime(); return repositoryReader(async (owner, name) => { let access = await hostedAuth.sessions.use( session, token => hostedAuth.github.repositoryAccess(token, owner, name), ); return access.value; }); } function expireSidebar(ws: SidebarSocket): void { if (!ws.data.closed) ws.close(4403, "authorization expired"); } async function receiveSidebar(ws: SidebarSocket, raw: string): Promise { let frame = sidebarFrame(raw); if (!frame) return; switch (frame.kind) { case "session:ping": return tell(ws, { kind: "session:ping", ts: 0, rid: frame.rid }); case "sidebar:watch": return watchSidebarDecisions(ws, frame.rid, frame.repositories); case "sidebar:unwatch": { let repositoryIds = unwatchRequest(frame.repositoryIds); if (!repositoryIds) return fail(ws, frame.rid, "invalid decision unwatch"); unwatchDecisions(ws.data.decisionWatch, repositoryIds, decisionTopics(ws)); return; } default: return fail(ws, (frame as { rid: string }).rid, "unsupported sidebar request"); } } async function watchSidebarDecisions( ws: SidebarSocket, rid: string, value: unknown, ): Promise { let requested = watchRequest(value); if (!requested) return fail(ws, rid, "invalid decision watch"); let authorize: RepositoryAuthorizer | undefined; try { authorize = await sidebarRepositoryReader(ws); } catch { return fail(ws, rid, "authorization is temporarily unavailable"); } if (!authorize) { fail(ws, rid, "authorization expired"); return expireSidebar(ws); } let outcome = await watchDecisions( ws.data.decisionWatch, requested, authorize, decisionTopics(ws), ); if (ws.data.closed) return; reply(ws, rid, { kind: "sidebar:watched", ts: 0, watched: outcome.watched.map(repository => repository.repositoryId), refused: outcome.refused, unavailable: outcome.unavailable, }); for (let { repositoryId, channelIds } of outcome.watched) { void Service.tellRepositoryUnanswered( storage, ws, repositoryId, channelIds, () => watchingRepository(ws, repositoryId), ).catch(err => { if (!ws.data.closed) console.warn("chopin: could not send decision snapshot -", err); }); } } async function recheckSidebar(ws: SidebarSocket): Promise { if (ws.data.closed) return; let authorize = await sidebarRepositoryReader(ws); if (ws.data.closed) return; if (!authorize) return expireSidebar(ws); await recheckDecisionWatch(ws.data.decisionWatch, authorize, decisionTopics(ws)); } function scheduleSidebarRecheck(ws: SidebarSocket): void { if (ws.data.closed) return; ws.data.recheckTimer = setTimeout(() => { void recheckSidebar(ws).catch(err => { if (!ws.data.closed) console.warn("chopin: could not recheck sidebar repositories -", err); }).finally(() => scheduleSidebarRecheck(ws)); }, ACCESS_RECHECK_MS); } async function refreshAccess(ws: Socket, forceGitHub = false): Promise { return refreshAuthorization(ws.data, forceGitHub, forced => checkAccess(ws, forced)); } function applyChannelAccess( ws: Socket, channel: ChannelRecord, canManage: boolean, publish = true, ): void { let data = ws.data; let archivedAt = channel.archivedAt?.toISOString(); let description = channel.description?.value; let descriptionRevision = channel.description?.revision ?? 0; let canEdit = canManage && !archivedAt && !archivingChannels.has(data.room); if ( (channel.updatedAt.toISOString() !== data.channelUpdatedAt || archivedAt !== data.channelArchivedAt || descriptionRevision !== data.channelDescriptionRevision) && !data.closed && publish ) { tell(ws, { kind: "session:channel", ts: 0, channelId: channel.id, title: channel.title, slug: channel.slug, updatedAt: channel.updatedAt.toISOString(), descriptionRevision, ...(description ? { description } : {}), canManage, ...(archivedAt ? { archivedAt } : {}), }); } if ((canEdit !== data.canEdit || canManage !== data.canManage) && !data.closed && publish) { tell(ws, { kind: "session:access", ts: 0, canEdit, canManage }); } data.channelTitle = channel.title; data.channelSlug = channel.slug; data.channelUpdatedAt = channel.updatedAt.toISOString(); data.channelDescription = description; data.channelDescriptionRevision = descriptionRevision; data.channelArchivedAt = archivedAt; data.canEdit = canEdit; data.canManage = canManage; } async function checkAccess(ws: Socket, forceGitHub: boolean): Promise { if (ws.data.closed) return "denied"; let data = ws.data; if ( !data.credential || !data.principalId || !data.repositoryId || !data.repositoryOwner || !data.repositoryName || (data.authorizedUntil ?? 0) <= Date.now() ) return "denied"; try { let request = new Request(hostedAuth.config.origin, { headers: { cookie: data.credential } }); let session = await hostedAuth.sessions.authenticate(request); if (!session || session.user.id !== data.principalId) return "denied"; let channel = deletingChannels.has(data.room) ? undefined : await storage.channels.get(data.room); if (!channel || channel.repositoryId !== data.repositoryId) return "denied"; if (!forceGitHub && Date.now() - (data.accessCheckedAt ?? 0) < ACCESS_RECHECK_MS) { applyChannelAccess(ws, channel, data.canManage); return "allowed"; } let access = await hostedAuth.sessions.use( session, token => hostedAuth.github.repositoryAccess( token, data.repositoryOwner!, data.repositoryName!, ), ); let repository = access.value; if (!repository || repository.id !== data.repositoryId || !repository.permissions.pull) { return "denied"; } if (channel.repositoryId !== repository.id) return "denied"; let canManage = repository.permissions.push || repository.permissions.admin; applyChannelAccess(ws, channel, canManage); data.accessCheckedAt = Date.now(); data.authorizedUntil = access.authenticated.session.expiresAt.getTime(); data.repositoryDefaultBranch = repository.defaultBranch; return "allowed"; } catch (err) { if ( (err instanceof GitHubError && (err.status === 429 || err.status === 502 || err.status === 503)) || (err instanceof StorageError && err.failure === "unavailable") ) return "unavailable"; return "denied"; } } function scheduleAuthorization(ws: Socket): void { if (ws.data.closed) return; ws.data.authorizationTimer = setTimeout(() => { void refreshAccess(ws, true).then(result => { if (ws.data.closed) return; if (result === "denied") { ws.close(4403, "authorization expired"); return; } scheduleAuthorization(ws); }); }, ACCESS_RECHECK_MS); } async function validateOpenedSocket(ws: Socket): Promise { let channel = deletingChannels.has(ws.data.room) ? undefined : await storage.channels.get(ws.data.room); if ( !channel || channel.repositoryId !== ws.data.repositoryId || deletingChannels.has(channel.id) ) { tell(ws, { kind: "session:deleted", ts: 0, channelId: ws.data.room }); ws.close(4404, "document deleted"); return; } if (ws.data.closed) return; applyChannelAccess(ws, channel, ws.data.canManage); } function openRoomSocket(ws: Socket): void { // Every mutation revalidates document state; the admission snapshot keeps startup stable. ws.data.canEdit = ws.data.canEdit && !archivingChannels.has(ws.data.room) && !deletingChannels.has(ws.data.room); ws.subscribe(topic(ws.data.room)); let room = Rooms.join(ws); tell(ws, { kind: "session:hello", ts: 0, channelId: room.id, title: ws.data.channelTitle, slug: ws.data.channelSlug, updatedAt: ws.data.channelUpdatedAt, descriptionRevision: ws.data.channelDescriptionRevision, ...(ws.data.channelDescription ? { description: ws.data.channelDescription } : {}), you: { handle: ws.data.handle, client: ws.data.client }, members: Rooms.members(room), canEdit: ws.data.canEdit, canManage: ws.data.canManage, ...(ws.data.channelArchivedAt ? { archivedAt: ws.data.channelArchivedAt } : {}), backgroundJobs: config.backgroundJobs, webResearch: config.webResearch, ...CHAT_CAPABILITIES, }); relay(ws, { kind: "session:presence", ts: 0, members: Rooms.members(room) }); scheduleAuthorization(ws); void validateOpenedSocket(ws).catch(err => { console.error("chopin: WebSocket open failed -", err); ws.close(1011, "cannot open document"); }); } function closeRoomSocket(ws: Socket): void { ws.data.closed = true; if (ws.data.authorizationTimer) clearTimeout(ws.data.authorizationTimer); let room = Rooms.leave(ws); ws.unsubscribe(topic(ws.data.room)); if (!room) return; if (room.plan) { Service.departed(room.plan, ws); Questions.away(room.plan, ws); conversationRuntime.processor(room.plan)?.leaveResearch(ws.data.client); Comments.away(room.plan, ws); } if (room.members.size > 0) presence(server, room); else evict(room); } function closeSidebarSocket(ws: SidebarSocket): void { ws.data.closed = true; if (ws.data.recheckTimer) clearTimeout(ws.data.recheckTimer); releaseDecisionWatch(ws.data.decisionWatch, decisionTopics(ws)); } function upgraded( req: Request, self: Server, data: SocketData | SidebarSocketData, ): Response | undefined { if ( req.headers.get("x-chopin-socket-probe") === "1" && req.headers.get("upgrade")?.toLowerCase() !== "websocket" ) return new Response(null, { status: 204 }); if (self.upgrade(req, { data })) return undefined; return new Response("upgrade failed", { status: 400 }); } function listen(): Server { return Bun.serve({ hostname: config.host, port: config.port, async fetch(req, self) { let url = new URL(req.url); if (url.pathname === "/ws") { let outcome = await admit(req, url, hostedAuth, { deleting: channelId => deletingChannels.has(channelId), readOnly: channelId => archivingChannels.has(channelId), }); if ("status" in outcome) { return new Response(outcome.reason, { status: outcome.status }); } return upgraded(req, self, outcome.data); } if (url.pathname === "/ws/sidebar") { let outcome = await admitSidebar(req, hostedAuth); if ("status" in outcome) { return new Response(outcome.reason, { status: outcome.status }); } return upgraded(req, self, outcome.data); } let routed = await router.handle(req, url); if (routed) return routed; return config.devClient ? proxy(req, url, config.devClient) : serve(url, CLIENT); }, websocket: { open(ws: AnySocket) { if (isSidebarSocket(ws)) scheduleSidebarRecheck(ws); else openRoomSocket(ws as Socket); }, message(ws: AnySocket, raw) { if (typeof raw !== "string") return; let received = isSidebarSocket(ws) ? receiveSidebar(ws, raw) : receive(ws as Socket, raw); void received.catch(err => { console.error("chopin: WebSocket receive failed -", err); }); }, close(ws: AnySocket) { if (isSidebarSocket(ws)) closeSidebarSocket(ws); else closeRoomSocket(ws as Socket); }, }, }) as Server; } /** Nothing in memory is worth losing to a Ctrl-C. */ let draining: Promise | undefined; function drain(): Promise { return draining ??= (async () => { let errors: Error[] = []; let record = (reason: unknown) => { errors.push(reason instanceof Error ? reason : new Error(String(reason))); }; let attempt = async (operation: () => Promise) => { try { await operation(); } catch (err) { record(err); } }; await attempt(() => server.stop(true)); if (researchRecoveryTimer) clearTimeout(researchRecoveryTimer); if (experimentTimer) clearInterval(experimentTimer); if (recoveringResearch) await attempt(() => recoveringResearch!); if (sessionCleanup) clearInterval(sessionCleanup); for (let result of await Promise.allSettled([cleaningSessions])) { if (result.status === "rejected") record(result.reason); } let rooms = await Promise.allSettled( Rooms.all().map(async room => { let opened = room.plan; if (!opened) return; let stopped = conversationRuntime.stop(opened); await stopped; await Service.close(opened); }), ); for (let result of rooms) { if (result.status === "rejected") record(result.reason); } if (summaryCoordinator) await attempt(() => summaryCoordinator!.flush()); summaryCoordinator?.close(); let stoppingJobs = jobRunner?.shutdown(); ownerBindings?.revokeAll(); for (let result of await Promise.allSettled([stoppingJobs])) { if (result.status === "rejected") record(result.reason); } await attempt(() => shutdownHarnesses()); if (leaseRenewal) clearInterval(leaseRenewal); if (leaseWatchdog) clearTimeout(leaseWatchdog); for (let result of await Promise.allSettled([renewingLease])) { if (result.status === "rejected") record(result.reason); } let lease = heldLease; if (lease) await attempt(() => storage.leases.release(lease)); await attempt(() => storage.close()); if (errors.length > 0) throw new AggregateError(errors, "Chopin shutdown failed."); })(); } function signal(): void { void drain().then( () => process.exit(0), err => { console.error("chopin: shutdown failed -", err); process.exit(1); }, ); } function renewLease(): void { if (!heldLease || renewingLease) return; renewingLease = storage.leases.renew(heldLease, LEASE_TTL_MS).then(renewed => { if (renewed) { heldLease = renewed; armLeaseWatchdog(); } else { console.error("chopin: lost the storage writer lease"); heldLease = undefined; signal(); } }, err => { console.error("chopin: could not renew the storage writer lease -", err); heldLease = undefined; signal(); }).finally(() => { renewingLease = undefined; }); } function armLeaseWatchdog(): void { if (leaseWatchdog) clearTimeout(leaseWatchdog); leaseWatchdog = setTimeout(() => { console.error("chopin: storage writer lease was not renewed before its safety deadline"); heldLease = undefined; signal(); }, LEASE_TTL_MS - LEASE_SAFETY_MS); } function cleanSessions(): void { if (cleaningSessions) return; cleaningSessions = hostedAuth.sessions.cleanupExpired().then(() => {}, err => { console.error("chopin: could not delete expired login sessions -", err); }).finally(() => { cleaningSessions = undefined; }); } async function resetOpenAgents( filter: (room: Rooms.Room) => boolean, sessionId?: string, revision?: number, reason?: string, ): Promise { await Promise.all( Rooms.all().filter(filter).map(room => room.plan ? Chat.resetAgent(room.plan.chat, sessionId, revision, reason) : Promise.resolve() ), ); } function announceChannel(channel: ChannelRecord): void { let room = Rooms.get(channel.id); if (!room) return; for (let ws of room.members.values()) { ws.data.channelTitle = channel.title; ws.data.channelSlug = channel.slug; ws.data.channelUpdatedAt = channel.updatedAt.toISOString(); ws.data.channelDescription = channel.description?.value; ws.data.channelDescriptionRevision = channel.description?.revision ?? 0; ws.data.channelArchivedAt = channel.archivedAt?.toISOString(); ws.data.canEdit = ws.data.canManage && !channel.archivedAt; tell(ws, { kind: "session:channel", ts: 0, channelId: channel.id, title: channel.title, slug: channel.slug, updatedAt: channel.updatedAt.toISOString(), descriptionRevision: channel.description?.revision ?? 0, ...(channel.description ? { description: channel.description.value } : {}), canManage: ws.data.canManage, ...(channel.archivedAt ? { archivedAt: channel.archivedAt.toISOString() } : {}), }); tell(ws, { kind: "session:access", ts: 0, canEdit: ws.data.canEdit, canManage: ws.data.canManage, }); } } async function archiveChannelLocked(channelId: string, now: Date) { if (deletingChannels.has(channelId)) { throw new StorageError("missing", `channel ${channelId} is being deleted`); } archivingChannels.add(channelId); let room = Rooms.get(channelId); for (let ws of room?.members.values() ?? []) ws.data.canEdit = false; try { await summaryCoordinator?.suspend(channelId); let result = await withDocumentLock(channelId, async () => { let active = Rooms.get(channelId)?.plan; if (active) { let stopped = conversationRuntime.stop(active); await Chat.resetAgent(active.chat, undefined, undefined, "This document was archived."); await stopped; await conversationRuntime.interrupt(active); await Service.drain(active); await Service.persist(active); } return storage.channels.archive({ id: channelId, now }); }); announceChannel(result.channel); void Service.announceCatalogueUnanswered(server, storage, result.channel); return result; } catch (err) { summaryCoordinator?.resume(channelId); for (let ws of room?.members.values() ?? []) ws.data.canEdit = ws.data.canManage; throw err; } finally { archivingChannels.delete(channelId); let current = Rooms.get(channelId); let opened = await recoverAttachedPlan(current); if (current && opened) { let channel = await storage.channels.get(channelId); if ( Rooms.get(channelId) === current && current.plan === opened && !current.closing && !deletingChannels.has(channelId) ) { await conversationRuntime.attach( current, opened, !!channel?.archivedAt, !!channel && !channel.parentChannelId, ); } } } } function archiveChannel(channelId: string, now: Date) { return withDocumentTransition(channelId, () => archiveChannelLocked(channelId, now)); } async function restoreChannelLocked(channelId: string, now: Date) { if (deletingChannels.has(channelId)) { throw new StorageError("missing", `channel ${channelId} is being deleted`); } let result = await withDocumentLock( channelId, () => storage.channels.restore({ id: channelId, now }), ); let recovery = await researchService?.recoverPendingPlannerInline( placeResearchReference, channelId, ); scheduleResearchRecovery(recovery?.deferred ?? 0); summaryCoordinator?.resume(channelId); let current = Rooms.get(channelId); if (current?.plan) { await conversationRuntime.attach(current, current.plan, false, !result.channel.parentChannelId); } announceChannel(result.channel); void Service.announceCatalogueUnanswered(server, storage, result.channel); if (summaryCoordinator) void summaryCoordinator.ensure(channelId).catch(() => {}); return result; } function restoreChannel(channelId: string, now: Date) { return withDocumentTransition(channelId, () => restoreChannelLocked(channelId, now)); } async function deleteChannelLocked(channelId: string): Promise { if (deletingChannels.has(channelId)) return false; let channel = await storage.channels.get(channelId); if (!channel) return false; if (!channel.archivedAt) { throw new StorageError("conflict", `channel ${channelId} must be archived before deletion`); } deletingChannels.add(channelId); let room = Rooms.get(channelId); for (let ws of room?.members.values() ?? []) ws.data.canEdit = false; try { await summaryCoordinator?.suspend(channelId); await jobRunner?.cancelChannel(channelId); ownerBindings?.revokeChannel(channelId); let deleted = await withDocumentLock(channelId, async () => { let activeRoom = Rooms.get(channelId); let active = activeRoom?.plan; if (activeRoom) activeRoom.plan = undefined; if (active) { let stopped = conversationRuntime.stop(active); await stopped; await Service.close(active); } return storage.channels.delete(channelId); }); if (!deleted) throw new StorageError("missing", `channel ${channelId} does not exist`); for (let ws of room?.members.values() ?? []) { tell(ws, { kind: "session:deleted", ts: 0, channelId }); ws.close(4404, "document deleted"); } if (room) Rooms.forget(room); void Service.announceDeletedUnanswered(server, storage, channel); summaryCoordinator?.resume(channelId); return true; } catch (err) { deletingChannels.delete(channelId); summaryCoordinator?.resume(channelId); jobRunner?.unblockChannel(channelId); let channel: ChannelRecord | undefined; try { channel = await storage.channels.get(channelId); } catch { for (let ws of room?.members.values() ?? []) { ws.close(1012, "document state is temporarily unavailable"); } throw err; } if (channel) { let survivingRoom = Rooms.get(channelId); if (survivingRoom?.members.size) { try { await plan(survivingRoom, server); } catch { for (let ws of survivingRoom.members.values()) { ws.close(1012, "document reload required"); } } } announceChannel(channel); } else { for (let ws of room?.members.values() ?? []) { tell(ws, { kind: "session:deleted", ts: 0, channelId }); ws.close(4404, "document deleted"); } if (room) Rooms.forget(room); } throw err; } finally { deletingChannels.delete(channelId); jobRunner?.unblockChannel(channelId); } } function deleteChannel(channelId: string): Promise { return withDocumentTransition(channelId, () => deleteChannelLocked(channelId)); } async function announceJobsChanged(channelId: string): Promise { if (draining) return; let page = await jobService.list(channelId, 1); if (!page) return; broadcast(server, channelId, { kind: "job:changed", ts: 0, revision: page.revision }); let opened = Rooms.get(channelId)?.plan; if (opened) conversationRuntime.refreshBriefs(opened); } function announceResearchChanged(channelId: string, workspaceId: string, revision: number): void { if (draining) return; broadcast(server, channelId, { kind: "research:changed", ts: 0, workspaceId, revision, }); } function announceResearchTerminal(channelId: string, id: string, text: string): Promise { return withDocumentLock(channelId, async () => { let active = Rooms.get(channelId)?.plan; if (active) { let existing = active.chat.entries.find(entry => entry.id === id); await Chat.noticeOnce( { chat: active.chat, plan: active, server, room: channelId }, id, existing?.text ?? text, ); return; } let detached = await Service.open(channelId, documentBackend(), server); try { let existing = detached.chat.entries.find(entry => entry.id === id); await Chat.noticeOnce( { chat: detached.chat, plan: detached, server, room: channelId }, id, existing?.text ?? text, ); } finally { await Service.close(detached); } }); } async function currentDocumentTarget( channelId: string, ): Promise { let active = Rooms.get(channelId); if (active?.opening) await active.opening.catch(() => undefined); if (active?.plan) return Service.readCurrentDocument(active.plan); let stored = await storage.collaboration.load(channelId, new Date()); if (!stored) return undefined; let projected = await Service.readStored(stored); active = Rooms.get(channelId); if (active?.opening) await active.opening.catch(() => undefined); if (active?.plan) return Service.readCurrentDocument(active.plan); return { channelId, revision: projected.revision, source: projected.source, sourceHash: Service.sourceHash(projected.source), }; } async function commitCurrentSummary( channelId: string, expected: DocumentSummaryInput, commit: () => Promise, ): Promise { return withDocumentLock(channelId, async () => { let active = Rooms.get(channelId); if (active?.plan) { let plan = active.plan; return Service.exclusive(plan, async () => { let source = Service.source(plan); if ( plan.revision !== expected.revision || Service.sourceHash(source) !== expected.sourceHash ) return false; await commit(); return true; }); } let stored = await storage.collaboration.load(channelId, new Date()); if (!stored) return false; let projected = await Service.readStored(stored); if ( projected.revision !== expected.revision || Service.sourceHash(projected.source) !== expected.sourceHash ) return false; await commit(); return true; }); } async function sessionRevoked(sessionId: string): Promise { experiments?.connections.revokeSession(sessionId); let jobs = jobRunner?.ownerRevoked(sessionId); ownerBindings?.revokeSession(sessionId); await Promise.all([resetOpenAgents(() => true, sessionId), jobs]); } async function credentialsWillRotate(sessionId: string, revision: number): Promise { let jobs = jobRunner?.credentialsWillRotate(sessionId, revision); ownerBindings?.revokeCredential(sessionId, revision); await Promise.all([ resetOpenAgents( () => true, sessionId, revision, "GitHub credentials refreshed, so the Planner session was restarted. Ask it to continue.", ), jobs, ]); } async function channelOwnerReset(channelId: string): Promise { let jobs = jobRunner?.channelOwnerReset(channelId); ownerBindings?.revokeChannel(channelId); await Promise.all([resetOpenAgents(room => room.id === channelId), jobs]); } let hostedAuth = registerAuthRoutes(router, { config: config.auth, storage, agent: config.agent, onSessionRevoked: sessionRevoked, onCredentialsWillRotate: credentialsWillRotate, }); experiments = registerExperimentRoutes(router, hostedAuth, { lease() { if (!heldLease) throw new Error("storage writer lease is unavailable"); return heldLease; }, context: currentDocumentTarget, place: (id, experiment, view, decision, remove) => withDocumentTransition(id, async () => { await Rooms.get(id)?.closing; await Rooms.get(id)?.opening; return withDocumentLock(id, async () => { let active = Rooms.get(id)?.plan; if (active) { return Service.placeExperimentReference(active, experiment, view, decision, remove); } let detached = await Service.open(id, documentBackend(), server); try { await Service.placeExperimentReference(detached, experiment, view, decision, remove); } finally { await Service.close(detached); } }); }), changed(documentId) { if (server && !draining) { broadcast(server, documentId, { kind: "experiment:changed", ts: 0, documentId }); } }, async canMutate(id) { let active = Rooms.get(id)?.plan; if (active) return !Service.implementationActive(active); let stored = await storage.collaboration.load(id, new Date()); return !stored || !(stored.sidecar as { execution?: unknown }).execution; }, }); ownerBindings = new ActiveOwnerBindings(hostedAuth); let definitions: JobDefinition[] = []; if (config.backgroundJobs) { definitions.push(researchBriefDefinition({ config })); definitions.push(documentSummaryDefinition({ config, current: currentDocumentTarget, refresh: target => summaryCoordinator?.enqueueNow(target) ?? Promise.resolve(), commitCurrent: commitCurrentSummary, })); } if (config.backgroundJobs && config.agent) { definitions.push(researchAnswerDefinition({ config })); } if (config.webResearch) { definitions.push(researchEvidenceDefinition({ config })); } let jobRegistry = new JobRegistry(definitions); let jobService = new JobService({ storage, registry: jobRegistry, lease() { if (!heldLease) throw new Error("storage writer lease is unavailable"); return heldLease; }, onChange: job => { jobRunner?.notify(job); if (job.state === "completed") { void descriptionProjector?.jobChanged(job).catch(err => { console.error("chopin: document description reconciliation failed -", err); }); void researchService?.jobChanged(job).catch(err => { console.error("chopin: research workspace reconciliation failed -", err); }); } }, publish: announceJobsChanged, }); if (config.agent && config.backgroundJobs) researchBriefJobs = jobService; if (config.backgroundJobs) { descriptionProjector = new DocumentDescriptionProjector({ storage, lease() { if (!heldLease) throw new Error("storage writer lease is unavailable"); return heldLease; }, publish: announceChannel, }); } researchService = new ResearchWorkspaceService({ storage, jobs: jobService, lease() { if (!heldLease) throw new Error("storage writer lease is unavailable"); return heldLease; }, current: currentDocumentTarget, publish: announceResearchChanged, terminalNotice: announceResearchTerminal, }); referenceService = new ReferenceService({ storage, current: currentDocumentTarget, research: researchService, id: ulid, }); if (config.agent && config.backgroundJobs) { summaryCoordinator = new DocumentSummaryCoordinator({ service: jobService, current: currentDocumentTarget, completed: job => descriptionProjector?.jobChanged(job) ?? Promise.resolve(), error: err => console.error("chopin: document description scheduling failed -", err), }); } jobRunner = new JobRunner({ storage, service: jobService, registry: jobRegistry, lease() { if (!heldLease) throw new Error("storage writer lease is unavailable"); return heldLease; }, resolveActivePlanner: async job => { let binding = await ownerBindings!.resolve(job.channelId); if (!binding) return undefined; return { credential: { kind: "active-planner", token: binding.token, ownerSessionId: binding.ownerSessionId, ownerGeneration: binding.ownerGeneration, credentialRevision: binding.credentialRevision, expiresAt: new Date(binding.expiresAt), signal: binding.signal, authorize: binding.revalidate, }, ownerKey: binding.ownerSessionId, binding: { kind: "active-planner", ownerSessionId: binding.ownerSessionId, ownerGeneration: binding.ownerGeneration, credentialRevision: binding.credentialRevision, repositoryId: binding.repository.id, }, signal: binding.signal, active: binding.revalidate, release: binding.release, }; }, enabled: config.agent && config.backgroundJobs, globalConcurrency: 2, ownerConcurrency: 1, changed: announceJobsChanged, attemptProgress(job, diagnostic) { console.info( `chopin: background job ${job.type} ${job.id} attempt ${job.attempts} progress ${ JSON.stringify(diagnostic) }`, ); }, attemptFailed(job, err, latestDiagnostic) { let reason = err instanceof Error ? `${err.name}: ${err.message}` : String(err); let detail = err instanceof JobExecutionError && err.diagnostic ? err.diagnostic : latestDiagnostic; let diagnostic = detail ? ` diagnostic=${JSON.stringify(detail)}` : ""; console.warn( `chopin: background job ${job.type} ${job.id} attempt ${job.attempts} failed - ${reason}${diagnostic}`, ); }, fatal: err => console.error("chopin: background job runner failed -", err), }); registerMcpRoutes(router, hostedAuth, { lease() { if (!heldLease) throw new Error("storage writer lease is unavailable"); return heldLease; }, }, { invokePlanner: input => withDocumentTransition(input.channel.id, async () => { let channel = await storage.channels.get(input.channel.id); if (!channel || deletingChannels.has(channel.id)) return "document-unavailable"; if (channel.archivedAt) return "document-archived"; await Rooms.get(channel.id)?.closing; let held = Rooms.hold(channel.id); let release = () => { held.release(); evict(held.room); }; let running = false; try { await plan(held.room, server); let context = conversation(held.room, input.session?.session.id, input.repository); let code = await Chat.invoke(context, input.user, input.instruction, input.checkout); if (!code && context.chat.running) { running = true; void context.chat.running.finally(release).catch(() => {}); } return code; } finally { if (!running) release(); } }), archiveChannel, isChannelDeleting: channelId => deletingChannels.has(channelId), onChannelRenamed: announceChannel, onDocumentPersisted: target => { if (summaryCoordinator) void summaryCoordinator.enqueueNow(target).catch(() => {}); }, restoreChannel, serializeDocument: (channelId, action) => withDocumentTransition(channelId, () => withDocumentLock(channelId, action)), }); registerChannelRoutes(router, hostedAuth, { onAgentReset: channelOwnerReset, onChannelArchived: archiveChannel, onChannelDeleted: deleteChannel, onChannelRenamed: announceChannel, onChannelRestored: restoreChannel, references: hostedAuth.github instanceof GitHubClient ? new GitHubReferences(hostedAuth.github) : undefined, }); registerResearchWorkspaceRoutes(router, hostedAuth, { service: researchService, async ensureOwner(channel, session, repository) { await Chat.resolveOwner( hostedAuth, { id: channel.repositoryId, owner: channel.repositoryOwner, name: channel.repositoryName, defaultBranch: repository.defaultBranch, }, channel.id, session.session.id, ); await jobRunner?.ownerAvailable(channel.id); }, }); registerNavigationRoutes(router, hostedAuth, { storage }); try { await storage.health(); } catch (err) { await storage.close().catch(() => {}); let reason = err instanceof Error ? err.message : String(err); console.error(`chopin: storage could not start - ${reason}`); process.exit(1); } try { heldLease = await storage.leases.acquire( "chopin:writer", crypto.randomUUID(), LEASE_TTL_MS, ); if (!heldLease) throw new Error("another Chopin instance owns the database"); let reset = await storage.sessions.reset(new Date(), heldLease, LEASE_TTL_MS); heldLease = reset.lease; } catch (err) { await shutdownHarnesses(); if (heldLease) await storage.leases.release(heldLease).catch(() => {}); await storage.close().catch(() => {}); let reason = err instanceof Error ? err.message : String(err); console.error(`chopin: storage writer lease could not start - ${reason}`); process.exit(1); } armLeaseWatchdog(); leaseRenewal = setInterval(renewLease, LEASE_RENEW_MS); cleanSessions(); sessionCleanup = setInterval(cleanSessions, SESSION_CLEANUP_MS); let listening: Server | undefined; try { await experiments.service.recover(true); server = listening = listen(); experimentTimer = setInterval(() => { void experiments!.sweep().catch(error => console.error("chopin: experiment recovery failed", error) ); }, 15_000); experimentTimer.unref(); let recovery = await researchService.recoverPendingPlannerInline(placeResearchReference); scheduleResearchRecovery(recovery.deferred); await researchService.recoverTerminalPlannerInline(); jobRunner.start(); } catch (err) { if (listening) await listening.stop(true).catch(() => {}); if (researchRecoveryTimer) clearTimeout(researchRecoveryTimer); if (sessionCleanup) clearInterval(sessionCleanup); if (leaseRenewal) clearInterval(leaseRenewal); if (leaseWatchdog) clearTimeout(leaseWatchdog); await cleaningSessions; await renewingLease; summaryCoordinator?.close(); let stoppingJobs = jobRunner.shutdown(); ownerBindings.revokeAll(); await stoppingJobs.catch(() => {}); await shutdownHarnesses(); if (heldLease) await storage.leases.release(heldLease).catch(() => {}); await storage.close().catch(() => {}); throw err; } process.on("SIGINT", signal); process.on("SIGTERM", signal); console.log(describe(config));