import path from "node:path"; import crypto from "node:crypto"; import type { ProviderEvent, ProviderSession, TerminalEvent } from "@t3tools/contracts"; import { EffectFanout, OrchestrationEngine, QueueProjector, type AppViewState } from "@t3tools/core"; import { createSqliteStores } from "@t3tools/infra-sqlite"; import type { ProviderManager } from "./providerManager"; import type { TerminalManager } from "./terminalManager"; function nowIso(): string { return new Date().toISOString(); } export class CoreRuntime { private readonly fanout = new EffectFanout(); private readonly stores; private readonly projector: QueueProjector; private readonly engine: OrchestrationEngine; constructor(dbDir: string) { const dbPath = path.join(dbDir, "event-store.sqlite"); this.stores = createSqliteStores(dbPath); this.projector = new QueueProjector(this.stores.projectionStore, this.fanout); this.engine = new OrchestrationEngine(this.stores.eventStore, this.stores.projectionStore, this.projector); } async start(cwd: string, projectName: string): Promise { await this.engine.start(); await this.engine.execute({ id: crypto.randomUUID(), type: "app.bootstrap", issuedAt: nowIso(), payload: { cwd, projectName }, }); } async stop(): Promise { await this.engine.stop(); this.stores.db.close(); } async state(): Promise { return this.engine.currentState(); } async dispatch(command: Parameters[0]): Promise { return this.engine.execute(command); } subscribe() { return this.fanout.subscribe(); } bindProviderEvents(providerManager: ProviderManager): void { providerManager.on("event", (event: ProviderEvent) => { void this.ingestProviderEvent(event); }); } bindTerminalEvents(terminalManager: TerminalManager): void { terminalManager.on("event", (event: TerminalEvent) => { if (event.type !== "activity" && event.type !== "error" && event.type !== "exited") return; void this.dispatch({ id: crypto.randomUUID(), type: "thread.setTerminalActivity", issuedAt: event.createdAt, payload: { threadId: event.threadId, terminalId: event.terminalId, running: event.type === "activity" ? event.hasRunningSubprocess : false, }, }); }); } async bindProviderSession(threadId: string, session: ProviderSession): Promise { await this.dispatch({ id: crypto.randomUUID(), type: "thread.updateProviderSession", issuedAt: nowIso(), payload: { threadId, session }, }); } async clearProviderSession(threadId: string): Promise { await this.dispatch({ id: crypto.randomUUID(), type: "thread.updateProviderSession", issuedAt: nowIso(), payload: { threadId, session: null }, }); } async ingestProviderEvent(event: ProviderEvent): Promise { const state = await this.state(); const target = state.threads.find((thread) => thread.session?.sessionId === event.sessionId); if (!target) return; await this.dispatch({ id: crypto.randomUUID(), type: "thread.recordProviderEvent", issuedAt: event.createdAt, payload: { threadId: target.id, event, }, }); } }