import { CheckpointScopeId, RunId, ThreadId } from "@t3tools/contracts"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import * as VcsStatusBroadcaster from "../vcs/VcsStatusBroadcaster.ts"; import * as WorkspaceEntries from "../workspace/WorkspaceEntries.ts"; import * as CheckpointCapture from "./CheckpointCaptureService.ts"; import * as ProjectionStore from "./ProjectionStore.ts"; export class RunFinalizationError extends Schema.TaggedErrorClass()( "RunFinalizationError", { threadId: ThreadId, runId: RunId, scopeId: CheckpointScopeId, operation: Schema.Literals(["capture-checkpoint", "refresh-workspace"]), cause: Schema.Defect(), }, ) {} export class RunFinalizationRefreshError extends Schema.TaggedErrorClass()( "RunFinalizationRefreshError", { cwd: Schema.String, cause: Schema.Defect() }, ) {} export class RunFinalizationObserver extends Context.Reference<{ readonly refresh: (cwd: string) => Effect.Effect; }>("t3/orchestration-v2/RunFinalizationObserver", { defaultValue: () => ({ refresh: () => Effect.void }), }) {} export class RunFinalizationService extends Context.Service< RunFinalizationService, { readonly finalize: (input: { readonly threadId: ThreadId; readonly runId: RunId; readonly scopeId: CheckpointScopeId; }) => Effect.Effect; } >()("t3/orchestration-v2/RunFinalizationService") {} export const make = Effect.gen(function* () { const checkpointCapture = yield* CheckpointCapture.CheckpointCaptureServiceV2; const projections = yield* ProjectionStore.ProjectionStoreV2; const observer = yield* RunFinalizationObserver; const finalize: RunFinalizationService["Service"]["finalize"] = Effect.fn( "RunFinalizationService.finalize", )(function* (input) { yield* checkpointCapture .execute(input) .pipe( Effect.mapError( (cause) => new RunFinalizationError({ ...input, operation: "capture-checkpoint", cause }), ), ); const projection = yield* projections .getThreadProjection(input.threadId) .pipe( Effect.mapError( (cause) => new RunFinalizationError({ ...input, operation: "refresh-workspace", cause }), ), ); const cwd = projection.checkpointScopes.find((scope) => scope.id === input.scopeId)?.cwd; if (cwd !== undefined) { yield* observer .refresh(cwd) .pipe( Effect.mapError( (cause) => new RunFinalizationError({ ...input, operation: "refresh-workspace", cause }), ), ); } }); return RunFinalizationService.of({ finalize }); }); export const layer = Layer.effect(RunFinalizationService, make); export const observerLive = Layer.effect( RunFinalizationObserver, Effect.gen(function* () { const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries; const vcsStatus = yield* VcsStatusBroadcaster.VcsStatusBroadcaster; return { refresh: (cwd: string) => Effect.all([workspaceEntries.refresh(cwd), vcsStatus.refreshStatus(cwd)], { discard: true, concurrency: "unbounded", }).pipe(Effect.mapError((cause) => new RunFinalizationRefreshError({ cwd, cause }))), }; }), );