import * as Cause from "effect/Cause";import * as DateTime from "effect/DateTime";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Queue from "effect/Queue";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream";import { DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL, type AuthAccessStreamEvent, AuthSessionId, CommandId, EventId, type OrchestrationCommand, type GitActionProgressEvent, type GitManagerServiceError, OrchestrationDispatchCommandError, type OrchestrationEvent, type OrchestrationShellStreamEvent, OrchestrationGetFullThreadDiffError, OrchestrationGetSnapshotError, OrchestrationGetTurnDiffError, ORCHESTRATION_WS_METHODS, ProjectSearchEntriesError, ProjectWriteFileError, OrchestrationReplayEventsError, FilesystemBrowseError, ThreadId, type TerminalEvent, WS_METHODS, WsRpcGroup,} from "@t3tools/contracts";import { clamp } from "effect/Number";import { HttpRouter, HttpServerRequest } from "effect/unstable/http";import { RpcSerialization, RpcServer } from "effect/unstable/rpc"; import { CheckpointDiffQuery } from "./checkpointing/Services/CheckpointDiffQuery.ts";import { ServerConfig } from "./config.ts";import { Keybindings } from "./keybindings.ts";import { Open, resolveAvailableEditors } from "./open.ts";import { normalizeDispatchCommand } from "./orchestration/Normalizer.ts";import { OrchestrationEngineService } from "./orchestration/Services/OrchestrationEngine.ts";import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery.ts";import { observeRpcEffect, observeRpcStream, observeRpcStreamEffect,} from "./observability/RpcInstrumentation.ts";import { ProviderRegistry } from "./provider/Services/ProviderRegistry.ts";import * as ProviderMaintenanceRunner from "./provider/providerMaintenanceRunner.ts";import { ServerLifecycleEvents } from "./serverLifecycleEvents.ts";import { ServerRuntimeStartup } from "./serverRuntimeStartup.ts";import { redactServerSettingsForClient, ServerSettingsService } from "./serverSettings.ts";import { TerminalManager } from "./terminal/Services/Manager.ts";import { WorkspaceEntries } from "./workspace/Services/WorkspaceEntries.ts";import { WorkspaceFileSystem } from "./workspace/Services/WorkspaceFileSystem.ts";import { WorkspacePathOutsideRootError } from "./workspace/Services/WorkspacePaths.ts";import { VcsStatusBroadcaster } from "./vcs/VcsStatusBroadcaster.ts";import { VcsProvisioningService } from "./vcs/VcsProvisioningService.ts";import { GitWorkflowService } from "./git/GitWorkflowService.ts";import { ProjectSetupScriptRunner } from "./project/Services/ProjectSetupScriptRunner.ts";import { RepositoryIdentityResolver } from "./project/Services/RepositoryIdentityResolver.ts";import { ServerEnvironment } from "./environment/Services/ServerEnvironment.ts";import { ServerAuth } from "./auth/Services/ServerAuth.ts";import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts";import * as SourceControlDiscoveryLayer from "./sourceControl/SourceControlDiscovery.ts";import { SourceControlRepositoryService } from "./sourceControl/SourceControlRepositoryService.ts";import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts";import * as BitbucketApi from "./sourceControl/BitbucketApi.ts";import * as GitHubCli from "./sourceControl/GitHubCli.ts";import * as GitLabCli from "./sourceControl/GitLabCli.ts";import * as SourceControlProviderRegistry from "./sourceControl/SourceControlProviderRegistry.ts";import * as GitVcsDriver from "./vcs/GitVcsDriver.ts";import * as VcsDriverRegistry from "./vcs/VcsDriverRegistry.ts";import * as VcsProjectConfig from "./vcs/VcsProjectConfig.ts";import * as VcsProcess from "./vcs/VcsProcess.ts";import { BootstrapCredentialService, type BootstrapCredentialChange,} from "./auth/Services/BootstrapCredentialService.ts";import { SessionCredentialService, type SessionCredentialChange,} from "./auth/Services/SessionCredentialService.ts";import { respondToAuthError } from "./auth/http.ts";const isOrchestrationDispatchCommandError = Schema.is(OrchestrationDispatchCommandError);const isWorkspacePathOutsideRootError = Schema.is(WorkspacePathOutsideRootError); const nowIso = Effect.map(DateTime.now, DateTime.formatIso); function isThreadDetailEvent(event: OrchestrationEvent): event is Extract< OrchestrationEvent, { type: | "thread.message-sent" | "thread.proposed-plan-upserted" | "thread.activity-appended" | "thread.turn-diff-completed" | "thread.reverted" | "thread.session-set"; }> { return ( event.type === "thread.message-sent" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set" );} const PROVIDER_STATUS_DEBOUNCE_MS = 200; function toAuthAccessStreamEvent( change: BootstrapCredentialChange | SessionCredentialChange, revision: number, currentSessionId: AuthSessionId,): AuthAccessStreamEvent { switch (change.type) { case "pairingLinkUpserted": return { version: 1, revision, type: "pairingLinkUpserted", payload: change.pairingLink, }; case "pairingLinkRemoved": return { version: 1, revision, type: "pairingLinkRemoved", payload: { id: change.id }, }; case "clientUpserted": return { version: 1, revision, type: "clientUpserted", payload: { ...change.clientSession, current: change.clientSession.sessionId === currentSessionId, }, }; case "clientRemoved": return { version: 1, revision, type: "clientRemoved", payload: { sessionId: change.sessionId }, }; }} const makeWsRpcLayer = (currentSessionId: AuthSessionId) => WsRpcGroup.toLayer( Effect.gen(function* () { const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; const orchestrationEngine = yield* OrchestrationEngineService; const checkpointDiffQuery = yield* CheckpointDiffQuery; const keybindings = yield* Keybindings; const open = yield* Open; const gitWorkflow = yield* GitWorkflowService; const vcsProvisioning = yield* VcsProvisioningService; const vcsStatusBroadcaster = yield* VcsStatusBroadcaster; const terminalManager = yield* TerminalManager; const providerRegistry = yield* ProviderRegistry; const providerMaintenanceRunner = yield* ProviderMaintenanceRunner.ProviderMaintenanceRunner; const config = yield* ServerConfig; const lifecycleEvents = yield* ServerLifecycleEvents; const serverSettings = yield* ServerSettingsService; const startup = yield* ServerRuntimeStartup; const workspaceEntries = yield* WorkspaceEntries; const workspaceFileSystem = yield* WorkspaceFileSystem; const projectSetupScriptRunner = yield* ProjectSetupScriptRunner; const repositoryIdentityResolver = yield* RepositoryIdentityResolver; const serverEnvironment = yield* ServerEnvironment; const serverAuth = yield* ServerAuth; const sourceControlDiscovery = yield* SourceControlDiscoveryLayer.SourceControlDiscovery; const automaticGitFetchInterval = serverSettings.getSettings.pipe( Effect.map((settings) => settings.automaticGitFetchInterval), Effect.catch((cause) => Effect.logWarning("Failed to read automatic Git fetch interval setting", { detail: cause.message, }).pipe(Effect.as(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)), ), ); const sourceControlRepositories = yield* SourceControlRepositoryService; const bootstrapCredentials = yield* BootstrapCredentialService; const sessions = yield* SessionCredentialService; const processDiagnostics = yield* ProcessDiagnostics.ProcessDiagnostics; const serverCommandId = (tag: string) => CommandId.make(`server:${tag}:${crypto.randomUUID()}`); const loadAuthAccessSnapshot = () => Effect.all({ pairingLinks: serverAuth.listPairingLinks().pipe(Effect.orDie), clientSessions: serverAuth.listClientSessions(currentSessionId).pipe(Effect.orDie), }); const appendSetupScriptActivity = (input: { readonly threadId: ThreadId; readonly kind: "setup-script.requested" | "setup-script.started" | "setup-script.failed"; readonly summary: string; readonly createdAt: string; readonly payload: Record<string, unknown>; readonly tone: "info" | "error"; }) => orchestrationEngine.dispatch({ type: "thread.activity.append", commandId: serverCommandId("setup-script-activity"), threadId: input.threadId, activity: { id: EventId.make(crypto.randomUUID()), tone: input.tone, kind: input.kind, summary: input.summary, payload: input.payload, turnId: null, createdAt: input.createdAt, }, createdAt: input.createdAt, }); const toDispatchCommandError = (cause: unknown, fallbackMessage: string) => isOrchestrationDispatchCommandError(cause) ? cause : new OrchestrationDispatchCommandError({ message: cause instanceof Error ? cause.message : fallbackMessage, cause, }); const toBootstrapDispatchCommandCauseError = (cause: Cause.Cause<unknown>) => { const error = Cause.squash(cause); return isOrchestrationDispatchCommandError(error) ? error : new OrchestrationDispatchCommandError({ message: error instanceof Error ? error.message : "Failed to bootstrap thread turn start.", cause, }); }; const enrichProjectEvent = ( event: OrchestrationEvent, ): Effect.Effect<OrchestrationEvent, never, never> => { switch (event.type) { case "project.created": return repositoryIdentityResolver.resolve(event.payload.workspaceRoot).pipe( Effect.map((repositoryIdentity) => ({ ...event, payload: { ...event.payload, repositoryIdentity, }, })), ); case "project.meta-updated": return Effect.gen(function* () { const workspaceRoot = event.payload.workspaceRoot ?? Option.match( yield* projectionSnapshotQuery.getProjectShellById(event.payload.projectId), { onNone: () => null, onSome: (project) => project.workspaceRoot, }, ) ?? null; if (workspaceRoot === null) { return event; } const repositoryIdentity = yield* repositoryIdentityResolver.resolve(workspaceRoot); return { ...event, payload: { ...event.payload, repositoryIdentity, }, } satisfies OrchestrationEvent; }).pipe(Effect.catch(() => Effect.succeed(event))); default: return Effect.succeed(event); } }; const enrichOrchestrationEvents = (events: ReadonlyArray<OrchestrationEvent>) => Effect.forEach(events, enrichProjectEvent, { concurrency: 4 }); const toShellStreamEvent = ( event: OrchestrationEvent, ): Effect.Effect<Option.Option<OrchestrationShellStreamEvent>, never, never> => { switch (event.type) { case "project.created": case "project.meta-updated": return projectionSnapshotQuery.getProjectShellById(event.payload.projectId).pipe( Effect.map((project) => Option.map(project, (nextProject) => ({ kind: "project-upserted" as const, sequence: event.sequence, project: nextProject, })), ), Effect.catch(() => Effect.succeed(Option.none())), ); case "project.deleted": return Effect.succeed( Option.some({ kind: "project-removed" as const, sequence: event.sequence, projectId: event.payload.projectId, }), ); case "thread.deleted": case "thread.archived": return Effect.succeed( Option.some({ kind: "thread-removed" as const, sequence: event.sequence, threadId: event.payload.threadId, }), ); case "thread.unarchived": return projectionSnapshotQuery.getThreadShellById(event.payload.threadId).pipe( Effect.map((thread) => Option.map(thread, (nextThread) => ({ kind: "thread-upserted" as const, sequence: event.sequence, thread: nextThread, })), ), Effect.catch(() => Effect.succeed(Option.none())), ); default: if (event.aggregateKind !== "thread") { return Effect.succeed(Option.none()); } return projectionSnapshotQuery .getThreadShellById(ThreadId.make(event.aggregateId)) .pipe( Effect.map((thread) => Option.map(thread, (nextThread) => ({ kind: "thread-upserted" as const, sequence: event.sequence, thread: nextThread, })), ), Effect.catch(() => Effect.succeed(Option.none())), ); } }; const dispatchBootstrapTurnStart = ( command: Extract<OrchestrationCommand, { type: "thread.turn.start" }>, ): Effect.Effect<{ readonly sequence: number }, OrchestrationDispatchCommandError> => Effect.gen(function* () { const bootstrap = command.bootstrap; const { bootstrap: _bootstrap, ...finalTurnStartCommand } = command; let createdThread = false; let targetProjectId = bootstrap?.createThread?.projectId; let targetProjectCwd = bootstrap?.prepareWorktree?.projectCwd; let targetWorktreePath = bootstrap?.createThread?.worktreePath ?? null; const cleanupCreatedThread = () => createdThread ? orchestrationEngine .dispatch({ type: "thread.delete", commandId: serverCommandId("bootstrap-thread-delete"), threadId: command.threadId, }) .pipe(Effect.ignoreCause({ log: true })) : Effect.void; const recordSetupScriptLaunchFailure = (input: { readonly error: unknown; readonly requestedAt: string; readonly worktreePath: string; }) => { const detail = input.error instanceof Error ? input.error.message : "Unknown setup failure."; return appendSetupScriptActivity({ threadId: command.threadId, kind: "setup-script.failed", summary: "Setup script failed to start", createdAt: input.requestedAt, payload: { detail, worktreePath: input.worktreePath, }, tone: "error", }).pipe( Effect.ignoreCause({ log: false }), Effect.flatMap(() => Effect.logWarning("bootstrap turn start failed to launch setup script", { threadId: command.threadId, worktreePath: input.worktreePath, detail, }), ), ); }; const recordSetupScriptStarted = (input: { readonly requestedAt: string; readonly worktreePath: string; readonly scriptId: string; readonly scriptName: string; readonly terminalId: string; }) => Effect.gen(function* () { const startedAt = yield* nowIso; const payload = { scriptId: input.scriptId, scriptName: input.scriptName, terminalId: input.terminalId, worktreePath: input.worktreePath, }; yield* Effect.all([ appendSetupScriptActivity({ threadId: command.threadId, kind: "setup-script.requested", summary: "Starting setup script", createdAt: input.requestedAt, payload, tone: "info", }), appendSetupScriptActivity({ threadId: command.threadId, kind: "setup-script.started", summary: "Setup script started", createdAt: startedAt, payload, tone: "info", }), ]).pipe( Effect.asVoid, Effect.catch((error) => Effect.logWarning( "bootstrap turn start launched setup script but failed to record setup activity", { threadId: command.threadId, worktreePath: input.worktreePath, scriptId: input.scriptId, terminalId: input.terminalId, detail: error.message, }, ), ), ); }); const runSetupProgram = () => Effect.gen(function* () { if (!bootstrap?.runSetupScript || !targetWorktreePath) { return; } const worktreePath = targetWorktreePath; const requestedAt = yield* nowIso; yield* projectSetupScriptRunner .runForThread({ threadId: command.threadId, ...(targetProjectId ? { projectId: targetProjectId } : {}), ...(targetProjectCwd ? { projectCwd: targetProjectCwd } : {}), worktreePath, }) .pipe( Effect.matchEffect({ onFailure: (error) => recordSetupScriptLaunchFailure({ error, requestedAt, worktreePath, }), onSuccess: (setupResult) => { if (setupResult.status !== "started") { return Effect.void; } return recordSetupScriptStarted({ requestedAt, worktreePath, scriptId: setupResult.scriptId, scriptName: setupResult.scriptName, terminalId: setupResult.terminalId, }); }, }), ); }); const bootstrapProgram = Effect.gen(function* () { if (bootstrap?.createThread) { yield* orchestrationEngine.dispatch({ type: "thread.create", commandId: serverCommandId("bootstrap-thread-create"),Lines 1–500 of 1281. Load more
import * as Cause from "effect/Cause"; import * as DateTime from "effect/DateTime"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import { DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL, type AuthAccessStreamEvent, AuthSessionId, CommandId, EventId, type OrchestrationCommand, type GitActionProgressEvent, type GitManagerServiceError, OrchestrationDispatchCommandError, type OrchestrationEvent, type OrchestrationShellStreamEvent, OrchestrationGetFullThreadDiffError, OrchestrationGetSnapshotError, OrchestrationGetTurnDiffError, ORCHESTRATION_WS_METHODS, ProjectSearchEntriesError, ProjectWriteFileError, OrchestrationReplayEventsError, FilesystemBrowseError, ThreadId, type TerminalEvent, WS_METHODS, WsRpcGroup, } from "@t3tools/contracts"; import { clamp } from "effect/Number"; import { HttpRouter, HttpServerRequest } from "effect/unstable/http"; import { RpcSerialization, RpcServer } from "effect/unstable/rpc"; import { CheckpointDiffQuery } from "./checkpointing/Services/CheckpointDiffQuery.ts"; import { ServerConfig } from "./config.ts"; import { Keybindings } from "./keybindings.ts"; import { Open, resolveAvailableEditors } from "./open.ts"; import { normalizeDispatchCommand } from "./orchestration/Normalizer.ts"; import { OrchestrationEngineService } from "./orchestration/Services/OrchestrationEngine.ts"; import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery.ts"; import { observeRpcEffect, observeRpcStream, observeRpcStreamEffect, } from "./observability/RpcInstrumentation.ts"; import { ProviderRegistry } from "./provider/Services/ProviderRegistry.ts"; import * as ProviderMaintenanceRunner from "./provider/providerMaintenanceRunner.ts"; import { ServerLifecycleEvents } from "./serverLifecycleEvents.ts"; import { ServerRuntimeStartup } from "./serverRuntimeStartup.ts"; import { redactServerSettingsForClient, ServerSettingsService } from "./serverSettings.ts"; import { TerminalManager } from "./terminal/Services/Manager.ts"; import { WorkspaceEntries } from "./workspace/Services/WorkspaceEntries.ts"; import { WorkspaceFileSystem } from "./workspace/Services/WorkspaceFileSystem.ts"; import { WorkspacePathOutsideRootError } from "./workspace/Services/WorkspacePaths.ts"; import { VcsStatusBroadcaster } from "./vcs/VcsStatusBroadcaster.ts"; import { VcsProvisioningService } from "./vcs/VcsProvisioningService.ts"; import { GitWorkflowService } from "./git/GitWorkflowService.ts"; import { ProjectSetupScriptRunner } from "./project/Services/ProjectSetupScriptRunner.ts"; import { RepositoryIdentityResolver } from "./project/Services/RepositoryIdentityResolver.ts"; import { ServerEnvironment } from "./environment/Services/ServerEnvironment.ts"; import { ServerAuth } from "./auth/Services/ServerAuth.ts"; import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts"; import * as SourceControlDiscoveryLayer from "./sourceControl/SourceControlDiscovery.ts"; import { SourceControlRepositoryService } from "./sourceControl/SourceControlRepositoryService.ts"; import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts"; import * as BitbucketApi from "./sourceControl/BitbucketApi.ts"; import * as GitHubCli from "./sourceControl/GitHubCli.ts"; import * as GitLabCli from "./sourceControl/GitLabCli.ts"; import * as SourceControlProviderRegistry from "./sourceControl/SourceControlProviderRegistry.ts"; import * as GitVcsDriver from "./vcs/GitVcsDriver.ts"; import * as VcsDriverRegistry from "./vcs/VcsDriverRegistry.ts"; import * as VcsProjectConfig from "./vcs/VcsProjectConfig.ts"; import * as VcsProcess from "./vcs/VcsProcess.ts"; import { BootstrapCredentialService, type BootstrapCredentialChange, } from "./auth/Services/BootstrapCredentialService.ts"; import { SessionCredentialService, type SessionCredentialChange, } from "./auth/Services/SessionCredentialService.ts"; import { respondToAuthError } from "./auth/http.ts"; const isOrchestrationDispatchCommandError = Schema.is(OrchestrationDispatchCommandError); const isWorkspacePathOutsideRootError = Schema.is(WorkspacePathOutsideRootError); const nowIso = Effect.map(DateTime.now, DateTime.formatIso); function isThreadDetailEvent(event: OrchestrationEvent): event is Extract< OrchestrationEvent, { type: | "thread.message-sent" | "thread.proposed-plan-upserted" | "thread.activity-appended" | "thread.turn-diff-completed" | "thread.reverted" | "thread.session-set"; } > { return ( event.type === "thread.message-sent" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set" ); } const PROVIDER_STATUS_DEBOUNCE_MS = 200; function toAuthAccessStreamEvent( change: BootstrapCredentialChange | SessionCredentialChange, revision: number, currentSessionId: AuthSessionId, ): AuthAccessStreamEvent { switch (change.type) { case "pairingLinkUpserted": return { version: 1, revision, type: "pairingLinkUpserted", payload: change.pairingLink, }; case "pairingLinkRemoved": return { version: 1, revision, type: "pairingLinkRemoved", payload: { id: change.id }, }; case "clientUpserted": return { version: 1, revision, type: "clientUpserted", payload: { ...change.clientSession, current: change.clientSession.sessionId === currentSessionId, }, }; case "clientRemoved": return { version: 1, revision, type: "clientRemoved", payload: { sessionId: change.sessionId }, }; } } const makeWsRpcLayer = (currentSessionId: AuthSessionId) => WsRpcGroup.toLayer( Effect.gen(function* () { const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; const orchestrationEngine = yield* OrchestrationEngineService; const checkpointDiffQuery = yield* CheckpointDiffQuery; const keybindings = yield* Keybindings; const open = yield* Open; const gitWorkflow = yield* GitWorkflowService; const vcsProvisioning = yield* VcsProvisioningService; const vcsStatusBroadcaster = yield* VcsStatusBroadcaster; const terminalManager = yield* TerminalManager; const providerRegistry = yield* ProviderRegistry; const providerMaintenanceRunner = yield* ProviderMaintenanceRunner.ProviderMaintenanceRunner; const config = yield* ServerConfig; const lifecycleEvents = yield* ServerLifecycleEvents; const serverSettings = yield* ServerSettingsService; const startup = yield* ServerRuntimeStartup; const workspaceEntries = yield* WorkspaceEntries; const workspaceFileSystem = yield* WorkspaceFileSystem; const projectSetupScriptRunner = yield* ProjectSetupScriptRunner; const repositoryIdentityResolver = yield* RepositoryIdentityResolver; const serverEnvironment = yield* ServerEnvironment; const serverAuth = yield* ServerAuth; const sourceControlDiscovery = yield* SourceControlDiscoveryLayer.SourceControlDiscovery; const automaticGitFetchInterval = serverSettings.getSettings.pipe( Effect.map((settings) => settings.automaticGitFetchInterval), Effect.catch((cause) => Effect.logWarning("Failed to read automatic Git fetch interval setting", { detail: cause.message, }).pipe(Effect.as(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)), ), ); const sourceControlRepositories = yield* SourceControlRepositoryService; const bootstrapCredentials = yield* BootstrapCredentialService; const sessions = yield* SessionCredentialService; const processDiagnostics = yield* ProcessDiagnostics.ProcessDiagnostics; const serverCommandId = (tag: string) => CommandId.make(`server:${tag}:${crypto.randomUUID()}`); const loadAuthAccessSnapshot = () => Effect.all({ pairingLinks: serverAuth.listPairingLinks().pipe(Effect.orDie), clientSessions: serverAuth.listClientSessions(currentSessionId).pipe(Effect.orDie), }); const appendSetupScriptActivity = (input: { readonly threadId: ThreadId; readonly kind: "setup-script.requested" | "setup-script.started" | "setup-script.failed"; readonly summary: string; readonly createdAt: string; readonly payload: Record<string, unknown>; readonly tone: "info" | "error"; }) => orchestrationEngine.dispatch({ type: "thread.activity.append", commandId: serverCommandId("setup-script-activity"), threadId: input.threadId, activity: { id: EventId.make(crypto.randomUUID()), tone: input.tone, kind: input.kind, summary: input.summary, payload: input.payload, turnId: null, createdAt: input.createdAt, }, createdAt: input.createdAt, }); const toDispatchCommandError = (cause: unknown, fallbackMessage: string) => isOrchestrationDispatchCommandError(cause) ? cause : new OrchestrationDispatchCommandError({ message: cause instanceof Error ? cause.message : fallbackMessage, cause, }); const toBootstrapDispatchCommandCauseError = (cause: Cause.Cause<unknown>) => { const error = Cause.squash(cause); return isOrchestrationDispatchCommandError(error) ? error : new OrchestrationDispatchCommandError({ message: error instanceof Error ? error.message : "Failed to bootstrap thread turn start.", cause, }); }; const enrichProjectEvent = ( event: OrchestrationEvent, ): Effect.Effect<OrchestrationEvent, never, never> => { switch (event.type) { case "project.created": return repositoryIdentityResolver.resolve(event.payload.workspaceRoot).pipe( Effect.map((repositoryIdentity) => ({ ...event, payload: { ...event.payload, repositoryIdentity, }, })), ); case "project.meta-updated": return Effect.gen(function* () { const workspaceRoot = event.payload.workspaceRoot ?? Option.match( yield* projectionSnapshotQuery.getProjectShellById(event.payload.projectId), { onNone: () => null, onSome: (project) => project.workspaceRoot, }, ) ?? null; if (workspaceRoot === null) { return event; } const repositoryIdentity = yield* repositoryIdentityResolver.resolve(workspaceRoot); return { ...event, payload: { ...event.payload, repositoryIdentity, }, } satisfies OrchestrationEvent; }).pipe(Effect.catch(() => Effect.succeed(event))); default: return Effect.succeed(event); } }; const enrichOrchestrationEvents = (events: ReadonlyArray<OrchestrationEvent>) => Effect.forEach(events, enrichProjectEvent, { concurrency: 4 }); const toShellStreamEvent = ( event: OrchestrationEvent, ): Effect.Effect<Option.Option<OrchestrationShellStreamEvent>, never, never> => { switch (event.type) { case "project.created": case "project.meta-updated": return projectionSnapshotQuery.getProjectShellById(event.payload.projectId).pipe( Effect.map((project) => Option.map(project, (nextProject) => ({ kind: "project-upserted" as const, sequence: event.sequence, project: nextProject, })), ), Effect.catch(() => Effect.succeed(Option.none())), ); case "project.deleted": return Effect.succeed( Option.some({ kind: "project-removed" as const, sequence: event.sequence, projectId: event.payload.projectId, }), ); case "thread.deleted": case "thread.archived": return Effect.succeed( Option.some({ kind: "thread-removed" as const, sequence: event.sequence, threadId: event.payload.threadId, }), ); case "thread.unarchived": return projectionSnapshotQuery.getThreadShellById(event.payload.threadId).pipe( Effect.map((thread) => Option.map(thread, (nextThread) => ({ kind: "thread-upserted" as const, sequence: event.sequence, thread: nextThread, })), ), Effect.catch(() => Effect.succeed(Option.none())), ); default: if (event.aggregateKind !== "thread") { return Effect.succeed(Option.none()); } return projectionSnapshotQuery .getThreadShellById(ThreadId.make(event.aggregateId)) .pipe( Effect.map((thread) => Option.map(thread, (nextThread) => ({ kind: "thread-upserted" as const, sequence: event.sequence, thread: nextThread, })), ), Effect.catch(() => Effect.succeed(Option.none())), ); } }; const dispatchBootstrapTurnStart = ( command: Extract<OrchestrationCommand, { type: "thread.turn.start" }>, ): Effect.Effect<{ readonly sequence: number }, OrchestrationDispatchCommandError> => Effect.gen(function* () { const bootstrap = command.bootstrap; const { bootstrap: _bootstrap, ...finalTurnStartCommand } = command; let createdThread = false; let targetProjectId = bootstrap?.createThread?.projectId; let targetProjectCwd = bootstrap?.prepareWorktree?.projectCwd; let targetWorktreePath = bootstrap?.createThread?.worktreePath ?? null; const cleanupCreatedThread = () => createdThread ? orchestrationEngine .dispatch({ type: "thread.delete", commandId: serverCommandId("bootstrap-thread-delete"), threadId: command.threadId, }) .pipe(Effect.ignoreCause({ log: true })) : Effect.void; const recordSetupScriptLaunchFailure = (input: { readonly error: unknown; readonly requestedAt: string; readonly worktreePath: string; }) => { const detail = input.error instanceof Error ? input.error.message : "Unknown setup failure."; return appendSetupScriptActivity({ threadId: command.threadId, kind: "setup-script.failed", summary: "Setup script failed to start", createdAt: input.requestedAt, payload: { detail, worktreePath: input.worktreePath, }, tone: "error", }).pipe( Effect.ignoreCause({ log: false }), Effect.flatMap(() => Effect.logWarning("bootstrap turn start failed to launch setup script", { threadId: command.threadId, worktreePath: input.worktreePath, detail, }), ), ); }; const recordSetupScriptStarted = (input: { readonly requestedAt: string; readonly worktreePath: string; readonly scriptId: string; readonly scriptName: string; readonly terminalId: string; }) => Effect.gen(function* () { const startedAt = yield* nowIso; const payload = { scriptId: input.scriptId, scriptName: input.scriptName, terminalId: input.terminalId, worktreePath: input.worktreePath, }; yield* Effect.all([ appendSetupScriptActivity({ threadId: command.threadId, kind: "setup-script.requested", summary: "Starting setup script", createdAt: input.requestedAt, payload, tone: "info", }), appendSetupScriptActivity({ threadId: command.threadId, kind: "setup-script.started", summary: "Setup script started", createdAt: startedAt, payload, tone: "info", }), ]).pipe( Effect.asVoid, Effect.catch((error) => Effect.logWarning( "bootstrap turn start launched setup script but failed to record setup activity", { threadId: command.threadId, worktreePath: input.worktreePath, scriptId: input.scriptId, terminalId: input.terminalId, detail: error.message, }, ), ), ); }); const runSetupProgram = () => Effect.gen(function* () { if (!bootstrap?.runSetupScript || !targetWorktreePath) { return; } const worktreePath = targetWorktreePath; const requestedAt = yield* nowIso; yield* projectSetupScriptRunner .runForThread({ threadId: command.threadId, ...(targetProjectId ? { projectId: targetProjectId } : {}), ...(targetProjectCwd ? { projectCwd: targetProjectCwd } : {}), worktreePath, }) .pipe( Effect.matchEffect({ onFailure: (error) => recordSetupScriptLaunchFailure({ error, requestedAt, worktreePath, }), onSuccess: (setupResult) => { if (setupResult.status !== "started") { return Effect.void; } return recordSetupScriptStarted({ requestedAt, worktreePath, scriptId: setupResult.scriptId, scriptName: setupResult.scriptName, terminalId: setupResult.terminalId, }); }, }), ); }); const bootstrapProgram = Effect.gen(function* () { if (bootstrap?.createThread) { yield* orchestrationEngine.dispatch({ type: "thread.create", commandId: serverCommandId("bootstrap-thread-create"),