import { ChatAttachment, CheckpointRef, IsoDateTime, MessageId, NonNegativeInt, OrchestrationCheckpointFile, OrchestrationProposedPlanId, OrchestrationReadModel, OrchestrationThreadSearchSource, OrchestrationShellSnapshot, OrchestrationThread, OrchestrationThreadDetailSnapshot, ProjectScript, TurnId, type OrchestrationCheckpointSummary, type OrchestrationLatestTurn, type OrchestrationMessage, type OrchestrationProjectShell, type OrchestrationProposedPlan, type OrchestrationProject, type OrchestrationSession, type OrchestrationThreadActivity, type OrchestrationThreadShell, ModelSelection, ProjectId, ThreadLinkedPullRequest, ThreadId, } from "@t3tools/contracts"; import * as Arr from "effect/Array"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; import * as Struct from "effect/Struct"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import { isPersistenceError, toPersistenceDecodeError, toPersistenceSqlError, type ProjectionRepositoryError, } from "../../persistence/Errors.ts"; import { ProjectionCheckpoint } from "../../persistence/Services/ProjectionCheckpoints.ts"; import { ThreadBackgroundLivenessService } from "../ThreadBackgroundLiveness.ts"; import { ThreadPlanProgressService } from "../ThreadPlanProgress.ts"; import { ProjectionProject } from "../../persistence/Services/ProjectionProjects.ts"; import { ProjectionState } from "../../persistence/Services/ProjectionState.ts"; import { ProjectionThreadActivity } from "../../persistence/Services/ProjectionThreadActivities.ts"; import { ProjectionThreadMessage } from "../../persistence/Services/ProjectionThreadMessages.ts"; import { ProjectionThreadProposedPlan } from "../../persistence/Services/ProjectionThreadProposedPlans.ts"; import { ProjectionThreadSession } from "../../persistence/Services/ProjectionThreadSessions.ts"; import { ProjectionThread } from "../../persistence/Services/ProjectionThreads.ts"; import { decodeThreadDetailPageCursor, encodeThreadDetailPageCursor, } from "../threadDetailCursor.ts"; import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts"; import { ORCHESTRATION_PROJECTOR_NAMES } from "./ProjectionPipeline.ts"; import { ProjectionSnapshotQuery, type ProjectionFullThreadDiffContext, type ProjectionSnapshotCounts, type ProjectionThreadCheckpointContext, type ProjectionSnapshotQueryShape, } from "../Services/ProjectionSnapshotQuery.ts"; const decodeReadModel = Schema.decodeUnknownEffect(OrchestrationReadModel); const decodeShellSnapshot = Schema.decodeUnknownEffect(OrchestrationShellSnapshot); const decodeThread = Schema.decodeUnknownEffect(OrchestrationThread); // Keep detail reads consistent with the in-memory projector's retained // activity window. Applying the limit in SQL avoids decoding an unbounded // payload_json set before the projector can enforce that invariant. const THREAD_DETAIL_ACTIVITY_LIMIT = 500; const ProjectionProjectDbRowSchema = ProjectionProject.mapFields( Struct.assign({ defaultModelSelection: Schema.NullOr(Schema.fromJsonString(ModelSelection)), scripts: Schema.fromJsonString(Schema.Array(ProjectScript)), }), ); const ProjectionThreadMessageDbRowSchema = ProjectionThreadMessage.mapFields( Struct.assign({ isStreaming: Schema.Number, attachments: Schema.NullOr(Schema.fromJsonString(Schema.Array(ChatAttachment))), }), ); const ProjectionThreadProposedPlanDbRowSchema = ProjectionThreadProposedPlan; const ProjectionThreadDbRowSchema = ProjectionThread.mapFields( Struct.assign({ modelSelection: Schema.fromJsonString(ModelSelection), linkedPullRequest: Schema.NullOr(Schema.fromJsonString(ThreadLinkedPullRequest)), }), ); const ProjectionThreadActivityDbRowSchema = ProjectionThreadActivity.mapFields( Struct.assign({ payload: Schema.fromJsonString(Schema.Unknown), sequence: Schema.NullOr(NonNegativeInt), }), ); const ProjectionThreadSessionDbRowSchema = ProjectionThreadSession; const ProjectionCheckpointDbRowSchema = ProjectionCheckpoint.mapFields( Struct.assign({ files: Schema.fromJsonString(Schema.Array(OrchestrationCheckpointFile)), }), ); const ProjectionLatestTurnDbRowSchema = Schema.Struct({ threadId: ProjectionThread.fields.threadId, turnId: TurnId, state: Schema.String, requestedAt: IsoDateTime, startedAt: Schema.NullOr(IsoDateTime), completedAt: Schema.NullOr(IsoDateTime), assistantMessageId: Schema.NullOr(MessageId), sourceProposedPlanThreadId: Schema.NullOr(ThreadId), sourceProposedPlanId: Schema.NullOr(OrchestrationProposedPlanId), }); const ProjectionStateDbRowSchema = ProjectionState; const ProjectionCountsRowSchema = Schema.Struct({ projectCount: Schema.Number, threadCount: Schema.Number, }); const ProjectionThreadSearchRequest = Schema.Struct({ pattern: Schema.String, limit: Schema.Int, }); const ProjectionThreadSearchRow = Schema.Struct({ threadId: ThreadId, projectId: ProjectId, source: OrchestrationThreadSearchSource, matchText: Schema.String, messageCreatedAt: Schema.NullOr(IsoDateTime), }); const WorkspaceRootLookupInput = Schema.Struct({ workspaceRoot: Schema.String, }); const ProjectIdLookupInput = Schema.Struct({ projectId: ProjectId, }); const ThreadIdLookupInput = Schema.Struct({ threadId: ThreadId, }); // Windowed reads order turns by the stable keyset (anchor, turn key), where // anchor is requested_at and turn key is // COALESCE(turn_id, ''). Both are event-derived, so cursors survive the // revert projector's row-id rewrite and full projection rebuilds. const ThreadTurnWindowLookupInput = Schema.Struct({ threadId: ThreadId, // Exclusive keyset upper bound. Sentinels "~"/"" mean unbounded ("~" sorts // after every ISO timestamp). beforeAnchorAt: Schema.String, beforeTurnKey: Schema.String, userTurnLimit: Schema.Number, maxRawTurns: Schema.Number, }); const ProjectionTurnWindowRowSchema = Schema.Struct({ // The turn's timeline anchor, used to bound rows that have no turn linkage // (user messages and turnless activities) to the same page window. anchorAt: Schema.String, turnKey: Schema.String, }); const ThreadTurnRangeLookupInput = Schema.Struct({ threadId: ThreadId, // Turn-linked rows are bounded by the keyset range [min, before) over // (anchor, turn key); turnless rows by the matching [minAnchorAt, // beforeAnchorAt) time range. Unbounded ends use sentinels: "" for the // lower bound, "~" (sorts after ISO dates) for the upper bound. minAnchorAt: Schema.String, minTurnKey: Schema.String, beforeAnchorAt: Schema.String, beforeTurnKey: Schema.String, }); const ProjectionProjectLookupRowSchema = ProjectionProjectDbRowSchema; const ProjectionThreadIdLookupRowSchema = Schema.Struct({ threadId: ThreadId, }); const ProjectionThreadCheckpointContextThreadRowSchema = Schema.Struct({ threadId: ThreadId, projectId: ProjectId, workspaceRoot: Schema.String, worktreePath: Schema.NullOr(Schema.String), }); const FullThreadDiffContextLookupInput = Schema.Struct({ threadId: ThreadId, checkpointTurnCount: NonNegativeInt, }); const ProjectionFullThreadDiffContextRowSchema = Schema.Struct({ threadId: ThreadId, projectId: ProjectId, workspaceRoot: Schema.String, worktreePath: Schema.NullOr(Schema.String), latestCheckpointTurnCount: Schema.NullOr(NonNegativeInt), toCheckpointRef: Schema.NullOr(CheckpointRef), }); const REQUIRED_SNAPSHOT_PROJECTORS = [ ORCHESTRATION_PROJECTOR_NAMES.projects, ORCHESTRATION_PROJECTOR_NAMES.threads, ORCHESTRATION_PROJECTOR_NAMES.threadMessages, ORCHESTRATION_PROJECTOR_NAMES.threadProposedPlans, ORCHESTRATION_PROJECTOR_NAMES.threadActivities, ORCHESTRATION_PROJECTOR_NAMES.threadSessions, ORCHESTRATION_PROJECTOR_NAMES.checkpoints, ] as const; function maxIso(left: string | null, right: string): string { if (left === null) { return right; } return left > right ? left : right; } function escapeLikePattern(value: string): string { return value.replaceAll("!", "!!").replaceAll("%", "!%").replaceAll("_", "!_"); } function foldAsciiCase(value: string): string { return value.replace(/[A-Z]/g, (character) => character.toLowerCase()); } function buildSearchSnippet(text: string, query: string): string { const normalizedText = text.replace(/\s+/g, " ").trim(); if (normalizedText.length <= 240) { return normalizedText; } const normalizedQuery = foldAsciiCase(query.replace(/\s+/g, " ").trim()); const matchIndex = foldAsciiCase(normalizedText).indexOf(normalizedQuery); const bodyLength = 236; const idealStart = Math.max(0, matchIndex - 72); const start = Math.min(idealStart, normalizedText.length - bodyLength); const end = Math.min(normalizedText.length, start + bodyLength); return `${start > 0 ? "…" : ""}${normalizedText.slice(start, end)}${ end < normalizedText.length ? "…" : "" }`; } function computeSnapshotSequence( stateRows: ReadonlyArray>, ): number { if (stateRows.length === 0) { return 0; } const sequenceByProjector = new Map( stateRows.map((row) => [row.projector, row.lastAppliedSequence] as const), ); let minSequence = Number.POSITIVE_INFINITY; for (const projector of REQUIRED_SNAPSHOT_PROJECTORS) { const sequence = sequenceByProjector.get(projector); if (sequence === undefined) { return 0; } if (sequence < minSequence) { minSequence = sequence; } } return Number.isFinite(minSequence) ? minSequence : 0; } function mapLatestTurn( row: Schema.Schema.Type, ): OrchestrationLatestTurn { return { turnId: row.turnId, state: row.state === "error" ? "error" : row.state === "interrupted" ? "interrupted" : row.state === "completed" ? "completed" : "running", requestedAt: row.requestedAt, startedAt: row.startedAt, completedAt: row.completedAt, assistantMessageId: row.assistantMessageId, ...(row.sourceProposedPlanThreadId !== null && row.sourceProposedPlanId !== null ? { sourceProposedPlan: { threadId: row.sourceProposedPlanThreadId, planId: row.sourceProposedPlanId, }, } : {}), }; } function mapTitleRegeneration(row: Schema.Schema.Type) { return row.titleRegenerationRequestId != null && row.titleRegenerationStartedAt != null ? { requestId: row.titleRegenerationRequestId, startedAt: row.titleRegenerationStartedAt, } : null; } function mapSessionRow( row: Schema.Schema.Type, ): OrchestrationSession { return { threadId: row.threadId, status: row.status, providerName: row.providerName, ...(row.providerInstanceId !== null ? { providerInstanceId: row.providerInstanceId } : {}), runtimeMode: row.runtimeMode, activeTurnId: row.activeTurnId, lastError: row.lastError, updatedAt: row.updatedAt, }; } function mapProjectShellRow( row: Schema.Schema.Type, repositoryIdentity: OrchestrationProject["repositoryIdentity"], ): OrchestrationProjectShell { return { id: row.projectId, title: row.title, workspaceRoot: row.workspaceRoot, repositoryIdentity, defaultModelSelection: row.defaultModelSelection, defaultThreadEnvMode: row.defaultThreadEnvMode, faviconPath: row.faviconPath ?? null, scripts: row.scripts, createdAt: row.createdAt, updatedAt: row.updatedAt, }; } function mapProposedPlanRow( row: Schema.Schema.Type, ): OrchestrationProposedPlan { return { id: row.planId, turnId: row.turnId, planMarkdown: row.planMarkdown, implementedAt: row.implementedAt, implementationThreadId: row.implementationThreadId, createdAt: row.createdAt, updatedAt: row.updatedAt, }; } function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) { return (cause: unknown): ProjectionRepositoryError => Schema.isSchemaError(cause) ? toPersistenceDecodeError(decodeOperation)(cause) : toPersistenceSqlError(sqlOperation)(cause); } const makeProjectionSnapshotQuery = Effect.gen(function* () { const threadBackgroundLiveness = yield* ThreadBackgroundLivenessService; const threadPlanProgress = yield* ThreadPlanProgressService; const sql = yield* SqlClient.SqlClient; const repositoryIdentityResolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver; const repositoryIdentityResolutionConcurrency = 4; const resolveRepositoryIdentitiesForProjects = Effect.fn( "ProjectionSnapshotQuery.resolveRepositoryIdentitiesForProjects", )(function* ( projectRows: ReadonlyArray>, options?: { readonly includeDeleted?: boolean; }, ) { const filteredProjectRows = options?.includeDeleted === true ? projectRows : projectRows.filter((row) => row.deletedAt === null); const uniqueWorkspaceRoots = [...new Set(filteredProjectRows.map((row) => row.workspaceRoot))]; const repositoryIdentityByWorkspaceRoot = new Map( yield* Effect.forEach( uniqueWorkspaceRoots, (workspaceRoot) => repositoryIdentityResolver .resolve(workspaceRoot) .pipe(Effect.map((identity) => [workspaceRoot, identity] as const)), { concurrency: repositoryIdentityResolutionConcurrency }, ), ); return new Map( filteredProjectRows.map((row) => [ row.projectId, repositoryIdentityByWorkspaceRoot.get(row.workspaceRoot) ?? null, ]), ); }); const listProjectRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionProjectDbRowSchema, execute: () => sql` SELECT project_id AS "projectId", title, workspace_root AS "workspaceRoot", default_model_selection_json AS "defaultModelSelection", default_thread_env_mode AS "defaultThreadEnvMode", favicon_path AS "faviconPath", scripts_json AS "scripts", created_at AS "createdAt", updated_at AS "updatedAt", deleted_at AS "deletedAt" FROM projection_projects ORDER BY created_at ASC, project_id ASC `, }); const listThreadRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadDbRowSchema, execute: () => sql` SELECT thread_id AS "threadId", project_id AS "projectId", title, model_selection_json AS "modelSelection", runtime_mode AS "runtimeMode", interaction_mode AS "interactionMode", branch, worktree_path AS "worktreePath", linked_pull_request_json AS "linkedPullRequest", latest_turn_id AS "latestTurnId", created_at AS "createdAt", updated_at AS "updatedAt", archived_at AS "archivedAt", settled_override AS "settledOverride", settled_at AS "settledAt", snoozed_until AS "snoozedUntil", snoozed_at AS "snoozedAt", pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", pending_approval_count AS "pendingApprovalCount", pending_user_input_count AS "pendingUserInputCount", has_actionable_proposed_plan AS "hasActionableProposedPlan", deleted_at AS "deletedAt" FROM projection_threads ORDER BY created_at ASC, thread_id ASC `, }); const listActiveThreadRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadDbRowSchema, execute: () => sql` SELECT thread_id AS "threadId", project_id AS "projectId", title, model_selection_json AS "modelSelection", runtime_mode AS "runtimeMode", interaction_mode AS "interactionMode", branch, worktree_path AS "worktreePath", linked_pull_request_json AS "linkedPullRequest", latest_turn_id AS "latestTurnId", created_at AS "createdAt", updated_at AS "updatedAt", archived_at AS "archivedAt", settled_override AS "settledOverride", settled_at AS "settledAt", snoozed_until AS "snoozedUntil", snoozed_at AS "snoozedAt", pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", pending_approval_count AS "pendingApprovalCount", pending_user_input_count AS "pendingUserInputCount", has_actionable_proposed_plan AS "hasActionableProposedPlan", deleted_at AS "deletedAt" FROM projection_threads WHERE deleted_at IS NULL AND archived_at IS NULL ORDER BY project_id ASC, created_at ASC, thread_id ASC `, }); const listArchivedThreadRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadDbRowSchema, execute: () => sql` SELECT thread_id AS "threadId", project_id AS "projectId", title, model_selection_json AS "modelSelection", runtime_mode AS "runtimeMode", interaction_mode AS "interactionMode", branch, worktree_path AS "worktreePath", linked_pull_request_json AS "linkedPullRequest", latest_turn_id AS "latestTurnId", created_at AS "createdAt", updated_at AS "updatedAt", archived_at AS "archivedAt", settled_override AS "settledOverride", settled_at AS "settledAt", snoozed_until AS "snoozedUntil", snoozed_at AS "snoozedAt", pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", pending_approval_count AS "pendingApprovalCount", pending_user_input_count AS "pendingUserInputCount", has_actionable_proposed_plan AS "hasActionableProposedPlan", deleted_at AS "deletedAt" FROM projection_threads WHERE deleted_at IS NULL AND archived_at IS NOT NULL ORDER BY project_id ASC, archived_at DESC, thread_id DESC `, }); const listThreadMessageRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadMessageDbRowSchema, execute: () => sql` SELECT message_id AS "messageId", thread_id AS "threadId", turn_id AS "turnId", role, text, attachments_json AS "attachments", is_streaming AS "isStreaming", created_at AS "createdAt", updated_at AS "updatedAt" FROM projection_thread_messages ORDER BY thread_id ASC, created_at ASC, message_id ASC `, }); const listThreadProposedPlanRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadProposedPlanDbRowSchema, execute: () => sql` SELECT plan_id AS "planId", thread_id AS "threadId", turn_id AS "turnId", plan_markdown AS "planMarkdown", implemented_at AS "implementedAt", implementation_thread_id AS "implementationThreadId", created_at AS "createdAt", updated_at AS "updatedAt" FROM projection_thread_proposed_plans ORDER BY thread_id ASC, created_at ASC, plan_id ASC `, }); const listThreadActivityRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadActivityDbRowSchema, execute: () => sql` SELECT activity_id AS "activityId", thread_id AS "threadId", turn_id AS "turnId", tone, kind, summary, payload_json AS "payload", sequence, created_at AS "createdAt" FROM projection_thread_activities ORDER BY thread_id ASC, sequence ASC, created_at ASC, activity_id ASC `, }); const listThreadSessionRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadSessionDbRowSchema, execute: () => sql` SELECT thread_id AS "threadId", status, provider_name AS "providerName", provider_instance_id AS "providerInstanceId", provider_session_id AS "providerSessionId", provider_thread_id AS "providerThreadId", runtime_mode AS "runtimeMode", active_turn_id AS "activeTurnId", last_error AS "lastError", updated_at AS "updatedAt" FROM projection_thread_sessions ORDER BY thread_id ASC `, }); const listActiveThreadSessionRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadSessionDbRowSchema, execute: () => sql` SELECT sessions.thread_id AS "threadId", sessions.status, sessions.provider_name AS "providerName", sessions.provider_instance_id AS "providerInstanceId", sessions.provider_session_id AS "providerSessionId", sessions.provider_thread_id AS "providerThreadId", sessions.runtime_mode AS "runtimeMode", sessions.active_turn_id AS "activeTurnId", sessions.last_error AS "lastError", sessions.updated_at AS "updatedAt" FROM projection_thread_sessions sessions INNER JOIN projection_threads threads ON threads.thread_id = sessions.thread_id WHERE threads.deleted_at IS NULL AND threads.archived_at IS NULL ORDER BY sessions.thread_id ASC `, }); const listArchivedThreadSessionRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionThreadSessionDbRowSchema, execute: () => sql` SELECT sessions.thread_id AS "threadId", sessions.status, sessions.provider_name AS "providerName", sessions.provider_instance_id AS "providerInstanceId", sessions.provider_session_id AS "providerSessionId", sessions.provider_thread_id AS "providerThreadId", sessions.runtime_mode AS "runtimeMode", sessions.active_turn_id AS "activeTurnId", sessions.last_error AS "lastError", sessions.updated_at AS "updatedAt" FROM projection_thread_sessions sessions INNER JOIN projection_threads threads ON threads.thread_id = sessions.thread_id WHERE threads.deleted_at IS NULL AND threads.archived_at IS NOT NULL ORDER BY sessions.thread_id ASC `, }); const listCheckpointRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionCheckpointDbRowSchema, execute: () => sql` SELECT thread_id AS "threadId", turn_id AS "turnId", checkpoint_turn_count AS "checkpointTurnCount", checkpoint_ref AS "checkpointRef", checkpoint_status AS "status", checkpoint_files_json AS "files", assistant_message_id AS "assistantMessageId", completed_at AS "completedAt" FROM projection_turns WHERE checkpoint_turn_count IS NOT NULL ORDER BY thread_id ASC, checkpoint_turn_count ASC `, }); const listLatestTurnRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionLatestTurnDbRowSchema, execute: () => sql` SELECT turns.thread_id AS "threadId", turns.turn_id AS "turnId", turns.state, turns.requested_at AS "requestedAt", turns.started_at AS "startedAt", turns.completed_at AS "completedAt", turns.assistant_message_id AS "assistantMessageId", turns.source_proposed_plan_thread_id AS "sourceProposedPlanThreadId", turns.source_proposed_plan_id AS "sourceProposedPlanId" FROM projection_threads threads JOIN projection_turns turns ON turns.thread_id = threads.thread_id AND turns.turn_id = threads.latest_turn_id WHERE threads.latest_turn_id IS NOT NULL ORDER BY turns.thread_id ASC `, }); const listActiveLatestTurnRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionLatestTurnDbRowSchema, execute: () => sql` SELECT turns.thread_id AS "threadId", turns.turn_id AS "turnId", turns.state, turns.requested_at AS "requestedAt", turns.started_at AS "startedAt", turns.completed_at AS "completedAt", turns.assistant_message_id AS "assistantMessageId", turns.source_proposed_plan_thread_id AS "sourceProposedPlanThreadId", turns.source_proposed_plan_id AS "sourceProposedPlanId" FROM projection_threads threads JOIN projection_turns turns ON turns.thread_id = threads.thread_id AND turns.turn_id = threads.latest_turn_id WHERE threads.deleted_at IS NULL AND threads.archived_at IS NULL AND threads.latest_turn_id IS NOT NULL ORDER BY turns.thread_id ASC `, }); const listArchivedLatestTurnRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionLatestTurnDbRowSchema, execute: () => sql` SELECT turns.thread_id AS "threadId", turns.turn_id AS "turnId", turns.state, turns.requested_at AS "requestedAt", turns.started_at AS "startedAt", turns.completed_at AS "completedAt", turns.assistant_message_id AS "assistantMessageId", turns.source_proposed_plan_thread_id AS "sourceProposedPlanThreadId", turns.source_proposed_plan_id AS "sourceProposedPlanId" FROM projection_threads threads JOIN projection_turns turns ON turns.thread_id = threads.thread_id AND turns.turn_id = threads.latest_turn_id WHERE threads.deleted_at IS NULL AND threads.archived_at IS NOT NULL AND threads.latest_turn_id IS NOT NULL ORDER BY turns.thread_id ASC `, }); const listProjectionStateRows = SqlSchema.findAll({ Request: Schema.Void, Result: ProjectionStateDbRowSchema, execute: () => sql` SELECT projector, last_applied_sequence AS "lastAppliedSequence", updated_at AS "updatedAt" FROM projection_state `, }); const readProjectionCounts = SqlSchema.findOne({ Request: Schema.Void, Result: ProjectionCountsRowSchema, execute: () => sql` SELECT (SELECT COUNT(*) FROM projection_projects) AS "projectCount", (SELECT COUNT(*) FROM projection_threads) AS "threadCount" `, }); const searchActiveThreadRows = SqlSchema.findAll({ Request: ProjectionThreadSearchRequest, Result: ProjectionThreadSearchRow, execute: ({ pattern, limit }) => sql` WITH ranked AS ( SELECT threads.thread_id AS thread_id, threads.project_id AS project_id, CASE messages.role WHEN 'user' THEN 'user' ELSE 'assistant' END AS source, messages.text AS match_text, messages.created_at AS message_created_at, CASE messages.role WHEN 'user' THEN 0 ELSE 1 END AS match_rank, threads.updated_at AS thread_updated_at, ROW_NUMBER() OVER ( PARTITION BY threads.thread_id ORDER BY CASE messages.role WHEN 'user' THEN 0 ELSE 1 END ASC, messages.created_at DESC, messages.message_id ASC ) AS thread_match_rank FROM projection_thread_messages AS messages INNER JOIN projection_threads AS threads ON threads.thread_id = messages.thread_id INNER JOIN projection_projects AS projects ON projects.project_id = threads.project_id WHERE threads.deleted_at IS NULL AND threads.archived_at IS NULL AND projects.deleted_at IS NULL AND messages.is_streaming = 0 AND ( messages.role = 'user' OR ( messages.role = 'assistant' AND messages.message_id IN ( SELECT turns.assistant_message_id FROM projection_turns AS turns WHERE turns.assistant_message_id IS NOT NULL ) ) ) AND messages.text LIKE ${pattern} ESCAPE '!' ) SELECT thread_id AS "threadId", project_id AS "projectId", source, match_text AS "matchText", message_created_at AS "messageCreatedAt" FROM ranked WHERE thread_match_rank = 1 ORDER BY match_rank ASC, thread_updated_at DESC, thread_id ASC LIMIT ${limit} `, }); const getActiveProjectRowByWorkspaceRoot = SqlSchema.findOneOption({ Request: WorkspaceRootLookupInput, Result: ProjectionProjectLookupRowSchema, execute: ({ workspaceRoot }) => sql` SELECT project_id AS "projectId", title, workspace_root AS "workspaceRoot", default_model_selection_json AS "defaultModelSelection", default_thread_env_mode AS "defaultThreadEnvMode", favicon_path AS "faviconPath", scripts_json AS "scripts", created_at AS "createdAt", updated_at AS "updatedAt", deleted_at AS "deletedAt" FROM projection_projects WHERE workspace_root = ${workspaceRoot} AND deleted_at IS NULL ORDER BY created_at ASC, project_id ASC LIMIT 1 `, }); const getActiveProjectRowById = SqlSchema.findOneOption({ Request: ProjectIdLookupInput, Result: ProjectionProjectLookupRowSchema, execute: ({ projectId }) => sql` SELECT project_id AS "projectId", title, workspace_root AS "workspaceRoot", default_model_selection_json AS "defaultModelSelection", default_thread_env_mode AS "defaultThreadEnvMode", favicon_path AS "faviconPath", scripts_json AS "scripts", created_at AS "createdAt", updated_at AS "updatedAt", deleted_at AS "deletedAt" FROM projection_projects WHERE project_id = ${projectId} AND deleted_at IS NULL LIMIT 1 `, }); const getFirstActiveThreadIdByProject = SqlSchema.findOneOption({ Request: ProjectIdLookupInput, Result: ProjectionThreadIdLookupRowSchema, execute: ({ projectId }) => sql` SELECT thread_id AS "threadId" FROM projection_threads WHERE project_id = ${projectId} AND deleted_at IS NULL AND archived_at IS NULL ORDER BY created_at ASC, thread_id ASC LIMIT 1 `, }); const getThreadCheckpointContextThreadRow = SqlSchema.findOneOption({ Request: ThreadIdLookupInput, Result: ProjectionThreadCheckpointContextThreadRowSchema, execute: ({ threadId }) => sql` SELECT threads.thread_id AS "threadId", threads.project_id AS "projectId", projects.workspace_root AS "workspaceRoot", threads.worktree_path AS "worktreePath" FROM projection_threads AS threads INNER JOIN projection_projects AS projects ON projects.project_id = threads.project_id WHERE threads.thread_id = ${threadId} AND threads.deleted_at IS NULL LIMIT 1 `, }); const getActiveThreadRowById = SqlSchema.findOneOption({ Request: ThreadIdLookupInput, Result: ProjectionThreadDbRowSchema, execute: ({ threadId }) => sql` SELECT thread_id AS "threadId", project_id AS "projectId", title, model_selection_json AS "modelSelection", runtime_mode AS "runtimeMode", interaction_mode AS "interactionMode", branch, worktree_path AS "worktreePath", linked_pull_request_json AS "linkedPullRequest", latest_turn_id AS "latestTurnId", created_at AS "createdAt", updated_at AS "updatedAt", archived_at AS "archivedAt", settled_override AS "settledOverride", settled_at AS "settledAt", snoozed_until AS "snoozedUntil", snoozed_at AS "snoozedAt", pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", pending_approval_count AS "pendingApprovalCount", pending_user_input_count AS "pendingUserInputCount", has_actionable_proposed_plan AS "hasActionableProposedPlan", deleted_at AS "deletedAt" FROM projection_threads WHERE thread_id = ${threadId} AND deleted_at IS NULL AND archived_at IS NULL LIMIT 1 `, }); const listThreadMessageRowsByThread = SqlSchema.findAll({ Request: ThreadIdLookupInput, Result: ProjectionThreadMessageDbRowSchema, execute: ({ threadId }) => sql` SELECT message_id AS "messageId", thread_id AS "threadId", turn_id AS "turnId", role, text, attachments_json AS "attachments", is_streaming AS "isStreaming", created_at AS "createdAt", updated_at AS "updatedAt" FROM projection_thread_messages WHERE thread_id = ${threadId} ORDER BY created_at ASC, message_id ASC `, }); const listThreadProposedPlanRowsByThread = SqlSchema.findAll({ Request: ThreadIdLookupInput, Result: ProjectionThreadProposedPlanDbRowSchema, execute: ({ threadId }) => sql` SELECT plan_id AS "planId", thread_id AS "threadId", turn_id AS "turnId", plan_markdown AS "planMarkdown", implemented_at AS "implementedAt", implementation_thread_id AS "implementationThreadId", created_at AS "createdAt", updated_at AS "updatedAt" FROM projection_thread_proposed_plans WHERE thread_id = ${threadId} ORDER BY created_at ASC, plan_id ASC `, }); const listThreadActivityRowsByThread = SqlSchema.findAll({ Request: ThreadIdLookupInput, Result: ProjectionThreadActivityDbRowSchema, execute: ({ threadId }) => sql` SELECT activity_id AS "activityId", thread_id AS "threadId", turn_id AS "turnId", tone, kind, summary, payload_json AS "payload", sequence, created_at AS "createdAt" FROM ( SELECT activity_id, thread_id, turn_id, tone, kind, summary, payload_json, sequence, created_at FROM projection_thread_activities WHERE thread_id = ${threadId} ORDER BY sequence DESC, created_at DESC, activity_id DESC LIMIT ${THREAD_DETAIL_ACTIVITY_LIMIT} ) AS recent_activities ORDER BY sequence ASC, created_at ASC, activity_id ASC `, }); const getThreadSessionRowByThread = SqlSchema.findOneOption({ Request: ThreadIdLookupInput, Result: ProjectionThreadSessionDbRowSchema, execute: ({ threadId }) => sql` SELECT thread_id AS "threadId", status, provider_name AS "providerName", provider_instance_id AS "providerInstanceId", runtime_mode AS "runtimeMode", active_turn_id AS "activeTurnId", last_error AS "lastError", updated_at AS "updatedAt" FROM projection_thread_sessions WHERE thread_id = ${threadId} LIMIT 1 `, }); const getLatestTurnRowByThread = SqlSchema.findOneOption({ Request: ThreadIdLookupInput, Result: ProjectionLatestTurnDbRowSchema, execute: ({ threadId }) => sql` SELECT turns.thread_id AS "threadId", turns.turn_id AS "turnId", turns.state, turns.requested_at AS "requestedAt", turns.started_at AS "startedAt", turns.completed_at AS "completedAt", turns.assistant_message_id AS "assistantMessageId", turns.source_proposed_plan_thread_id AS "sourceProposedPlanThreadId", turns.source_proposed_plan_id AS "sourceProposedPlanId" FROM projection_threads threads JOIN projection_turns turns ON turns.thread_id = threads.thread_id AND turns.turn_id = threads.latest_turn_id WHERE threads.thread_id = ${threadId} AND threads.deleted_at IS NULL AND threads.archived_at IS NULL LIMIT 1 `, }); const listCheckpointRowsByThread = SqlSchema.findAll({ Request: ThreadIdLookupInput, Result: ProjectionCheckpointDbRowSchema, execute: ({ threadId }) => sql` SELECT thread_id AS "threadId", turn_id AS "turnId", checkpoint_turn_count AS "checkpointTurnCount", checkpoint_ref AS "checkpointRef", checkpoint_status AS "status", checkpoint_files_json AS "files", assistant_message_id AS "assistantMessageId", completed_at AS "completedAt" FROM projection_turns WHERE thread_id = ${threadId} AND checkpoint_turn_count IS NOT NULL ORDER BY checkpoint_turn_count ASC `, }); // Resolves a page of recent turns for a windowed thread detail read. Walks // back from the exclusive (beforeAnchorAt, beforeTurnKey) keyset boundary // (sentinels "~"/"" mean unbounded, i.e. the first page) until it has seen // `userTurnLimit` user-anchored turns — turns whose pending message is a // user message; subagent/fan-out turns between them ride along — or hits the // `maxRawTurns` ceiling that bounds pathological fan-out. The `candidates` // CTE applies the keyset bound and LIMIT before the window functions run; // its ORDER BY uses raw columns so the migration-037 // (thread_id, requested_at, turn_id) index serves both range and order with // no temp B-tree — the scan is genuinely bounded by the LIMIT. (Raw // turn_id DESC places NULLs exactly where COALESCE-to-'' would, below every // real id.) The caller derives the continuation cursor from the oldest // returned row. // Highest thread-DETAIL event sequence for this thread that the projection // has applied (bounded by the global snapshot sequence read in the same // transaction). This is the thread-scoped watermark a windowed page carries // so clients can defer merging until their live subscription has caught up; // the global sequence is not waitable per-thread. The event_type filter // must match ws.ts's isThreadDetailEvent exactly: the subscription only // delivers these types, so a watermark counting any other event could // never be reached by the client and would park the page forever. Served // by the event store's (aggregate_kind, stream_id, sequence) index. const getThreadEventWatermarkRow = SqlSchema.findOneOption({ Request: Schema.Struct({ threadId: ThreadId, maxSequence: Schema.Number }), Result: Schema.Struct({ threadSequence: Schema.NullOr(Schema.Number) }), execute: ({ threadId, maxSequence }) => sql` SELECT MAX(sequence) AS "threadSequence" FROM orchestration_events WHERE aggregate_kind = 'thread' AND stream_id = ${threadId} AND sequence <= ${maxSequence} AND event_type IN ( 'thread.message-sent', 'thread.proposed-plan-upserted', 'thread.activity-appended', 'thread.turn-diff-completed', 'thread.reverted', 'thread.session-set' ) `, }); const listTurnWindowRows = SqlSchema.findAll({ Request: ThreadTurnWindowLookupInput, Result: ProjectionTurnWindowRowSchema, execute: ({ threadId, beforeAnchorAt, beforeTurnKey, userTurnLimit, maxRawTurns }) => sql` WITH candidates AS ( SELECT turns.requested_at AS anchor_at, COALESCE(turns.turn_id, '') AS turn_key, turns.pending_message_id FROM projection_turns AS turns WHERE turns.thread_id = ${threadId} AND ( turns.requested_at < ${beforeAnchorAt} OR ( turns.requested_at = ${beforeAnchorAt} AND COALESCE(turns.turn_id, '') < ${beforeTurnKey} ) ) ORDER BY turns.requested_at DESC, turns.turn_id DESC LIMIT ${maxRawTurns} ), walked AS ( SELECT candidates.anchor_at, candidates.turn_key, CASE WHEN messages.role = 'user' THEN 1 ELSE 0 END AS is_user_turn, SUM(CASE WHEN messages.role = 'user' THEN 1 ELSE 0 END) OVER ( ORDER BY candidates.anchor_at DESC, candidates.turn_key DESC ) AS user_turns_seen FROM candidates LEFT JOIN projection_thread_messages AS messages ON messages.message_id = candidates.pending_message_id ) SELECT anchor_at AS "anchorAt", turn_key AS "turnKey" FROM walked WHERE user_turns_seen < ${userTurnLimit} OR (user_turns_seen = ${userTurnLimit} AND is_user_turn = 1) ORDER BY anchor_at ASC, turn_key ASC `, }); // Windowed variants of the two heavy collections. Turn-linked rows are // bounded by the page's (anchor, turn key) keyset range over // projection_turns; rows with no turn linkage (user messages always, and // turnless activities like pre-turn context-window updates) are bounded by // the matching turn-anchor time range so they land on the same page as the // turns around them. Proposed plans and checkpoints stay unwindowed: they // are metadata-scale. const listThreadMessageRowsByThreadWindow = SqlSchema.findAll({ Request: ThreadTurnRangeLookupInput, Result: ProjectionThreadMessageDbRowSchema, execute: ({ threadId, minAnchorAt, minTurnKey, beforeAnchorAt, beforeTurnKey }) => sql` SELECT message_id AS "messageId", thread_id AS "threadId", turn_id AS "turnId", role, text, attachments_json AS "attachments", is_streaming AS "isStreaming", created_at AS "createdAt", updated_at AS "updatedAt" FROM projection_thread_messages WHERE thread_id = ${threadId} AND ( turn_id IN ( SELECT turn_id FROM projection_turns WHERE thread_id = ${threadId} AND turn_id IS NOT NULL AND ( requested_at > ${minAnchorAt} OR ( requested_at = ${minAnchorAt} AND turn_id >= ${minTurnKey} ) ) AND ( requested_at < ${beforeAnchorAt} OR ( requested_at = ${beforeAnchorAt} AND turn_id < ${beforeTurnKey} ) ) ) OR ( turn_id IS NULL AND created_at >= ${minAnchorAt} AND created_at < ${beforeAnchorAt} ) ) ORDER BY created_at ASC, message_id ASC `, }); // Blocking request payloads must remain available even if they predate the // recent activity window. Each CTE returns at most one unresolved row per // request, so the merge below stays bounded by actionable work. const listPinnedThreadActivityRowsByThread = SqlSchema.findAll({ Request: ThreadIdLookupInput, Result: ProjectionThreadActivityDbRowSchema, execute: ({ threadId }) => sql` WITH pending_approval_requests AS ( SELECT request_id, thread_id FROM projection_pending_approvals WHERE thread_id = ${threadId} AND status = 'pending' ), pending_approval_activities AS ( SELECT activity.activity_id, ROW_NUMBER() OVER ( PARTITION BY pending.request_id ORDER BY activity.created_at DESC, activity.activity_id DESC ) AS request_order FROM pending_approval_requests AS pending CROSS JOIN projection_thread_activities AS activity WHERE activity.thread_id = pending.thread_id AND activity.kind = 'approval.requested' AND json_extract(activity.payload_json, '$.requestId') = pending.request_id ), pending_user_input_thread AS ( SELECT thread_id FROM projection_threads WHERE thread_id = ${threadId} AND pending_user_input_count > 0 ), user_input_lifecycle AS ( SELECT activity.activity_id, activity.kind, ROW_NUMBER() OVER ( PARTITION BY json_extract(activity.payload_json, '$.requestId') ORDER BY activity.created_at DESC, activity.activity_id DESC ) AS request_order FROM pending_user_input_thread AS pending CROSS JOIN projection_thread_activities AS activity WHERE activity.thread_id = pending.thread_id AND ( activity.kind IN ('user-input.requested', 'user-input.resolved') OR ( activity.kind = 'provider.user-input.respond.failed' AND ( lower(COALESCE(json_extract(activity.payload_json, '$.detail'), '')) LIKE '%stale pending user-input request%' OR lower(COALESCE(json_extract(activity.payload_json, '$.detail'), '')) LIKE '%unknown pending user-input request%' OR lower(COALESCE(json_extract(activity.payload_json, '$.detail'), '')) LIKE '%unknown pending user input request%' OR lower(COALESCE(json_extract(activity.payload_json, '$.detail'), '')) LIKE '%unknown pending codex user input request%' ) ) ) AND json_extract(activity.payload_json, '$.requestId') IS NOT NULL ), pinned_activity_ids AS ( SELECT activity_id FROM pending_approval_activities WHERE request_order = 1 UNION ALL SELECT activity_id FROM user_input_lifecycle WHERE request_order = 1 AND kind = 'user-input.requested' ) SELECT activity.activity_id AS "activityId", activity.thread_id AS "threadId", activity.turn_id AS "turnId", activity.tone, activity.kind, activity.summary, activity.payload_json AS "payload", activity.sequence, activity.created_at AS "createdAt" FROM pinned_activity_ids AS pinned INNER JOIN projection_thread_activities AS activity ON activity.activity_id = pinned.activity_id ORDER BY activity.created_at ASC, activity.activity_id ASC `, }); const listThreadActivityRowsByThreadWindow = SqlSchema.findAll({ Request: ThreadTurnRangeLookupInput, Result: ProjectionThreadActivityDbRowSchema, execute: ({ threadId, minAnchorAt, minTurnKey, beforeAnchorAt, beforeTurnKey }) => sql` SELECT activity_id AS "activityId", thread_id AS "threadId", turn_id AS "turnId", tone, kind, summary, payload_json AS "payload", sequence, created_at AS "createdAt" FROM ( SELECT activity_id, thread_id, turn_id, tone, kind, summary, payload_json, sequence, created_at FROM projection_thread_activities WHERE thread_id = ${threadId} AND ( turn_id IN ( SELECT turn_id FROM projection_turns WHERE thread_id = ${threadId} AND turn_id IS NOT NULL AND ( requested_at > ${minAnchorAt} OR ( requested_at = ${minAnchorAt} AND turn_id >= ${minTurnKey} ) ) AND ( requested_at < ${beforeAnchorAt} OR ( requested_at = ${beforeAnchorAt} AND turn_id < ${beforeTurnKey} ) ) ) OR ( turn_id IS NULL AND created_at >= ${minAnchorAt} AND created_at < ${beforeAnchorAt} ) ) ORDER BY sequence DESC, created_at DESC, activity_id DESC LIMIT ${THREAD_DETAIL_ACTIVITY_LIMIT} ) AS recent_activities ORDER BY sequence ASC, created_at ASC, activity_id ASC `, }); const getFullThreadDiffContextRow = SqlSchema.findOneOption({ Request: FullThreadDiffContextLookupInput, Result: ProjectionFullThreadDiffContextRowSchema, execute: ({ threadId, checkpointTurnCount }) => sql` SELECT threads.thread_id AS "threadId", threads.project_id AS "projectId", projects.workspace_root AS "workspaceRoot", threads.worktree_path AS "worktreePath", ( SELECT MAX(turns.checkpoint_turn_count) FROM projection_turns AS turns WHERE turns.thread_id = threads.thread_id AND turns.checkpoint_turn_count IS NOT NULL ) AS "latestCheckpointTurnCount", ( SELECT turns.checkpoint_ref FROM projection_turns AS turns WHERE turns.thread_id = threads.thread_id AND turns.checkpoint_turn_count = ${checkpointTurnCount} LIMIT 1 ) AS "toCheckpointRef" FROM projection_threads AS threads INNER JOIN projection_projects AS projects ON projects.project_id = threads.project_id WHERE threads.thread_id = ${threadId} AND threads.deleted_at IS NULL LIMIT 1 `, }); const getSnapshot: ProjectionSnapshotQueryShape["getSnapshot"] = () => sql .withTransaction( Effect.all([ listProjectRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listProjects:query", "ProjectionSnapshotQuery.getSnapshot:listProjects:decodeRows", ), ), ), listThreadRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listThreads:query", "ProjectionSnapshotQuery.getSnapshot:listThreads:decodeRows", ), ), ), listThreadMessageRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listThreadMessages:query", "ProjectionSnapshotQuery.getSnapshot:listThreadMessages:decodeRows", ), ), ), listThreadProposedPlanRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listThreadProposedPlans:query", "ProjectionSnapshotQuery.getSnapshot:listThreadProposedPlans:decodeRows", ), ), ), listThreadActivityRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listThreadActivities:query", "ProjectionSnapshotQuery.getSnapshot:listThreadActivities:decodeRows", ), ), ), listThreadSessionRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listThreadSessions:query", "ProjectionSnapshotQuery.getSnapshot:listThreadSessions:decodeRows", ), ), ), listCheckpointRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listCheckpoints:query", "ProjectionSnapshotQuery.getSnapshot:listCheckpoints:decodeRows", ), ), ), listLatestTurnRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listLatestTurns:query", "ProjectionSnapshotQuery.getSnapshot:listLatestTurns:decodeRows", ), ), ), listProjectionStateRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshot:listProjectionState:query", "ProjectionSnapshotQuery.getSnapshot:listProjectionState:decodeRows", ), ), ), ]), ) .pipe( Effect.flatMap( ([ projectRows, threadRows, messageRows, proposedPlanRows, activityRows, sessionRows, checkpointRows, latestTurnRows, stateRows, ]) => Effect.gen(function* () { const messagesByThread = new Map>(); const proposedPlansByThread = new Map>(); const activitiesByThread = new Map>(); const checkpointsByThread = new Map>(); const sessionsByThread = new Map(); const latestTurnByThread = new Map(); let updatedAt: string | null = null; for (const row of projectRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of threadRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of stateRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of messageRows) { updatedAt = maxIso(updatedAt, row.updatedAt); const threadMessages = messagesByThread.get(row.threadId) ?? []; threadMessages.push({ id: row.messageId, role: row.role, text: row.text, ...(row.attachments !== null ? { attachments: row.attachments } : {}), turnId: row.turnId, streaming: row.isStreaming === 1, createdAt: row.createdAt, updatedAt: row.updatedAt, }); messagesByThread.set(row.threadId, threadMessages); } for (const row of proposedPlanRows) { updatedAt = maxIso(updatedAt, row.updatedAt); const threadProposedPlans = proposedPlansByThread.get(row.threadId) ?? []; threadProposedPlans.push({ id: row.planId, turnId: row.turnId, planMarkdown: row.planMarkdown, implementedAt: row.implementedAt, implementationThreadId: row.implementationThreadId, createdAt: row.createdAt, updatedAt: row.updatedAt, }); proposedPlansByThread.set(row.threadId, threadProposedPlans); } for (const row of activityRows) { updatedAt = maxIso(updatedAt, row.createdAt); const threadActivities = activitiesByThread.get(row.threadId) ?? []; threadActivities.push({ id: row.activityId, tone: row.tone, kind: row.kind, summary: row.summary, payload: row.payload, turnId: row.turnId, ...(row.sequence !== null ? { sequence: row.sequence } : {}), createdAt: row.createdAt, }); activitiesByThread.set(row.threadId, threadActivities); } for (const row of checkpointRows) { updatedAt = maxIso(updatedAt, row.completedAt); const threadCheckpoints = checkpointsByThread.get(row.threadId) ?? []; threadCheckpoints.push({ turnId: row.turnId, checkpointTurnCount: row.checkpointTurnCount, checkpointRef: row.checkpointRef, status: row.status, files: row.files, assistantMessageId: row.assistantMessageId, completedAt: row.completedAt, }); checkpointsByThread.set(row.threadId, threadCheckpoints); } for (const row of latestTurnRows) { updatedAt = maxIso(updatedAt, row.requestedAt); if (row.startedAt !== null) { updatedAt = maxIso(updatedAt, row.startedAt); } if (row.completedAt !== null) { updatedAt = maxIso(updatedAt, row.completedAt); } if (latestTurnByThread.has(row.threadId)) { continue; } latestTurnByThread.set(row.threadId, { turnId: row.turnId, state: row.state === "error" ? "error" : row.state === "interrupted" ? "interrupted" : row.state === "completed" ? "completed" : "running", requestedAt: row.requestedAt, startedAt: row.startedAt, completedAt: row.completedAt, assistantMessageId: row.assistantMessageId, ...(row.sourceProposedPlanThreadId !== null && row.sourceProposedPlanId !== null ? { sourceProposedPlan: { threadId: row.sourceProposedPlanThreadId, planId: row.sourceProposedPlanId, }, } : {}), }); } for (const row of sessionRows) { updatedAt = maxIso(updatedAt, row.updatedAt); sessionsByThread.set(row.threadId, { threadId: row.threadId, status: row.status, providerName: row.providerName, ...(row.providerInstanceId !== null ? { providerInstanceId: row.providerInstanceId } : {}), runtimeMode: row.runtimeMode, activeTurnId: row.activeTurnId, lastError: row.lastError, updatedAt: row.updatedAt, }); } const repositoryIdentities = yield* resolveRepositoryIdentitiesForProjects( projectRows, { includeDeleted: true }, ); const projects: ReadonlyArray = projectRows.map((row) => ({ id: row.projectId, title: row.title, workspaceRoot: row.workspaceRoot, repositoryIdentity: repositoryIdentities.get(row.projectId) ?? null, defaultModelSelection: row.defaultModelSelection, defaultThreadEnvMode: row.defaultThreadEnvMode, faviconPath: row.faviconPath ?? null, scripts: row.scripts, createdAt: row.createdAt, updatedAt: row.updatedAt, deletedAt: row.deletedAt, })); const threads: ReadonlyArray = threadRows.map((row) => ({ id: row.threadId, projectId: row.projectId, title: row.title, modelSelection: row.modelSelection, runtimeMode: row.runtimeMode, interactionMode: row.interactionMode, branch: row.branch, worktreePath: row.worktreePath, ...(row.linkedPullRequest === null ? {} : { linkedPullRequest: row.linkedPullRequest }), latestTurn: latestTurnByThread.get(row.threadId) ?? null, createdAt: row.createdAt, updatedAt: row.updatedAt, archivedAt: row.archivedAt, settledOverride: row.settledOverride, settledAt: row.settledAt, snoozedUntil: row.snoozedUntil, snoozedAt: row.snoozedAt, pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, titleRegeneration: mapTitleRegeneration(row), deletedAt: row.deletedAt, messages: messagesByThread.get(row.threadId) ?? [], proposedPlans: proposedPlansByThread.get(row.threadId) ?? [], activities: activitiesByThread.get(row.threadId) ?? [], checkpoints: checkpointsByThread.get(row.threadId) ?? [], session: sessionsByThread.get(row.threadId) ?? null, })); const snapshot = { snapshotSequence: computeSnapshotSequence(stateRows), projects, threads, updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", }; return yield* decodeReadModel(snapshot).pipe( Effect.mapError( toPersistenceDecodeError("ProjectionSnapshotQuery.getSnapshot:decodeReadModel"), ), ); }), ), Effect.mapError((error) => { if (isPersistenceError(error)) { return error; } return toPersistenceSqlError("ProjectionSnapshotQuery.getSnapshot:query")(error); }), ); const getCommandReadModel: ProjectionSnapshotQueryShape["getCommandReadModel"] = () => sql .withTransaction( Effect.all([ listProjectRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getCommandReadModel:listProjects:query", "ProjectionSnapshotQuery.getCommandReadModel:listProjects:decodeRows", ), ), ), listThreadRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getCommandReadModel:listThreads:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreads:decodeRows", ), ), ), listThreadProposedPlanRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getCommandReadModel:listThreadProposedPlans:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadProposedPlans:decodeRows", ), ), ), listThreadSessionRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getCommandReadModel:listThreadSessions:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadSessions:decodeRows", ), ), ), listLatestTurnRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getCommandReadModel:listLatestTurns:query", "ProjectionSnapshotQuery.getCommandReadModel:listLatestTurns:decodeRows", ), ), ), listProjectionStateRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getCommandReadModel:listProjectionState:query", "ProjectionSnapshotQuery.getCommandReadModel:listProjectionState:decodeRows", ), ), ), ]), ) .pipe( Effect.flatMap( ([projectRows, threadRows, proposedPlanRows, sessionRows, latestTurnRows, stateRows]) => Effect.sync(() => { let updatedAt: string | null = null; const projects: OrchestrationProject[] = []; const threads: OrchestrationThread[] = []; for (let index = 0; index < projectRows.length; index += 1) { const row = projectRows[index]; if (!row) { continue; } updatedAt = maxIso(updatedAt, row.updatedAt); projects.push({ id: row.projectId, title: row.title, workspaceRoot: row.workspaceRoot, defaultModelSelection: row.defaultModelSelection, defaultThreadEnvMode: row.defaultThreadEnvMode, faviconPath: row.faviconPath ?? null, scripts: row.scripts, createdAt: row.createdAt, updatedAt: row.updatedAt, deletedAt: row.deletedAt, }); } for (let index = 0; index < threadRows.length; index += 1) { const row = threadRows[index]; if (!row) { continue; } updatedAt = maxIso(updatedAt, row.updatedAt); } for (let index = 0; index < proposedPlanRows.length; index += 1) { const row = proposedPlanRows[index]; if (!row) { continue; } updatedAt = maxIso(updatedAt, row.updatedAt); } for (let index = 0; index < sessionRows.length; index += 1) { const row = sessionRows[index]; if (!row) { continue; } updatedAt = maxIso(updatedAt, row.updatedAt); } for (let index = 0; index < latestTurnRows.length; index += 1) { const row = latestTurnRows[index]; if (!row) { continue; } updatedAt = maxIso(updatedAt, row.requestedAt); if (row.startedAt !== null) { updatedAt = maxIso(updatedAt, row.startedAt); } if (row.completedAt !== null) { updatedAt = maxIso(updatedAt, row.completedAt); } } for (let index = 0; index < stateRows.length; index += 1) { const row = stateRows[index]; if (!row) { continue; } updatedAt = maxIso(updatedAt, row.updatedAt); } const latestTurnByThread = new Map(); for (let index = 0; index < latestTurnRows.length; index += 1) { const row = latestTurnRows[index]; if (!row) { continue; } latestTurnByThread.set(row.threadId, mapLatestTurn(row)); } const proposedPlansByThread = new Map>(); const sessionByThread = new Map(); for (let index = 0; index < sessionRows.length; index += 1) { const row = sessionRows[index]; if (!row) { continue; } sessionByThread.set(row.threadId, mapSessionRow(row)); } for (let index = 0; index < proposedPlanRows.length; index += 1) { const row = proposedPlanRows[index]; if (!row) { continue; } const threadProposedPlans = proposedPlansByThread.get(row.threadId) ?? []; threadProposedPlans.push(mapProposedPlanRow(row)); proposedPlansByThread.set(row.threadId, threadProposedPlans); } for (let index = 0; index < threadRows.length; index += 1) { const row = threadRows[index]; if (!row) { continue; } threads.push({ id: row.threadId, projectId: row.projectId, title: row.title, modelSelection: row.modelSelection, runtimeMode: row.runtimeMode, interactionMode: row.interactionMode, branch: row.branch, worktreePath: row.worktreePath, ...(row.linkedPullRequest === null ? {} : { linkedPullRequest: row.linkedPullRequest }), latestTurn: latestTurnByThread.get(row.threadId) ?? null, createdAt: row.createdAt, updatedAt: row.updatedAt, archivedAt: row.archivedAt, settledOverride: row.settledOverride, settledAt: row.settledAt, snoozedUntil: row.snoozedUntil, snoozedAt: row.snoozedAt, pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, titleRegeneration: mapTitleRegeneration(row), deletedAt: row.deletedAt, messages: [], proposedPlans: proposedPlansByThread.get(row.threadId) ?? [], activities: [], checkpoints: [], session: sessionByThread.get(row.threadId) ?? null, }); } return { snapshotSequence: computeSnapshotSequence(stateRows), projects, threads, updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", } satisfies OrchestrationReadModel; }), ), Effect.mapError((error) => { if (isPersistenceError(error)) { return error; } return toPersistenceSqlError("ProjectionSnapshotQuery.getCommandReadModel:query")(error); }), ); const getShellSnapshot: ProjectionSnapshotQueryShape["getShellSnapshot"] = () => sql .withTransaction( Effect.all([ listProjectRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getShellSnapshot:listProjects:query", "ProjectionSnapshotQuery.getShellSnapshot:listProjects:decodeRows", ), ), ), listActiveThreadRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getShellSnapshot:listThreads:query", "ProjectionSnapshotQuery.getShellSnapshot:listThreads:decodeRows", ), ), ), listActiveThreadSessionRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getShellSnapshot:listThreadSessions:query", "ProjectionSnapshotQuery.getShellSnapshot:listThreadSessions:decodeRows", ), ), ), listActiveLatestTurnRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getShellSnapshot:listLatestTurns:query", "ProjectionSnapshotQuery.getShellSnapshot:listLatestTurns:decodeRows", ), ), ), listProjectionStateRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getShellSnapshot:listProjectionState:query", "ProjectionSnapshotQuery.getShellSnapshot:listProjectionState:decodeRows", ), ), ), ]), ) .pipe( Effect.flatMap(([projectRows, threadRows, sessionRows, latestTurnRows, stateRows]) => Effect.gen(function* () { let updatedAt: string | null = null; for (const row of projectRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of threadRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of sessionRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of latestTurnRows) { updatedAt = maxIso(updatedAt, row.requestedAt); if (row.startedAt !== null) { updatedAt = maxIso(updatedAt, row.startedAt); } if (row.completedAt !== null) { updatedAt = maxIso(updatedAt, row.completedAt); } } for (const row of stateRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } const repositoryIdentities = yield* resolveRepositoryIdentitiesForProjects(projectRows); const latestTurnByThread = new Map( latestTurnRows.map((row) => [row.threadId, mapLatestTurn(row)] as const), ); const sessionByThread = new Map( sessionRows.map((row) => [row.threadId, mapSessionRow(row)] as const), ); const snapshot = { snapshotSequence: computeSnapshotSequence(stateRows), projects: Arr.filterMap(projectRows, (row) => row.deletedAt === null ? Result.succeed( mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null), ) : Result.failVoid, ), threads: Arr.filterMap(threadRows, (row) => row.deletedAt === null ? Result.succeed({ id: row.threadId, projectId: row.projectId, title: row.title, modelSelection: row.modelSelection, runtimeMode: row.runtimeMode, interactionMode: row.interactionMode, branch: row.branch, worktreePath: row.worktreePath, ...(row.linkedPullRequest === null ? {} : { linkedPullRequest: row.linkedPullRequest }), latestTurn: latestTurnByThread.get(row.threadId) ?? null, createdAt: row.createdAt, updatedAt: row.updatedAt, archivedAt: row.archivedAt, settledOverride: row.settledOverride, settledAt: row.settledAt, snoozedUntil: row.snoozedUntil, snoozedAt: row.snoozedAt, pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, titleRegeneration: mapTitleRegeneration(row), session: sessionByThread.get(row.threadId) ?? null, latestUserMessageAt: row.latestUserMessageAt, hasPendingApprovals: row.pendingApprovalCount > 0, hasPendingUserInput: row.pendingUserInputCount > 0, hasActionableProposedPlan: row.hasActionableProposedPlan > 0, backgroundLiveness: threadBackgroundLiveness.getThreadBackgroundLiveness( row.threadId, ), planProgress: threadPlanProgress.getThreadPlanProgress(row.threadId), } satisfies OrchestrationThreadShell) : Result.failVoid, ), updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", }; return yield* decodeShellSnapshot(snapshot).pipe( Effect.mapError( toPersistenceDecodeError( "ProjectionSnapshotQuery.getShellSnapshot:decodeShellSnapshot", ), ), ); }), ), Effect.mapError((error) => { if (isPersistenceError(error)) { return error; } return toPersistenceSqlError("ProjectionSnapshotQuery.getShellSnapshot:query")(error); }), ); const getArchivedShellSnapshot: ProjectionSnapshotQueryShape["getArchivedShellSnapshot"] = () => sql .withTransaction( Effect.all([ listProjectRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getArchivedShellSnapshot:listProjects:query", "ProjectionSnapshotQuery.getArchivedShellSnapshot:listProjects:decodeRows", ), ), ), listArchivedThreadRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getArchivedShellSnapshot:listThreads:query", "ProjectionSnapshotQuery.getArchivedShellSnapshot:listThreads:decodeRows", ), ), ), listArchivedThreadSessionRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getArchivedShellSnapshot:listThreadSessions:query", "ProjectionSnapshotQuery.getArchivedShellSnapshot:listThreadSessions:decodeRows", ), ), ), listArchivedLatestTurnRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getArchivedShellSnapshot:listLatestTurns:query", "ProjectionSnapshotQuery.getArchivedShellSnapshot:listLatestTurns:decodeRows", ), ), ), listProjectionStateRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getArchivedShellSnapshot:listProjectionState:query", "ProjectionSnapshotQuery.getArchivedShellSnapshot:listProjectionState:decodeRows", ), ), ), ]), ) .pipe( Effect.flatMap(([projectRows, threadRows, sessionRows, latestTurnRows, stateRows]) => Effect.gen(function* () { let updatedAt: string | null = null; for (const row of projectRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of threadRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of sessionRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } for (const row of latestTurnRows) { updatedAt = maxIso(updatedAt, row.requestedAt); if (row.startedAt !== null) { updatedAt = maxIso(updatedAt, row.startedAt); } if (row.completedAt !== null) { updatedAt = maxIso(updatedAt, row.completedAt); } } for (const row of stateRows) { updatedAt = maxIso(updatedAt, row.updatedAt); } const activeProjectIds = new Set(threadRows.map((row) => row.projectId)); const repositoryIdentities = yield* resolveRepositoryIdentitiesForProjects( projectRows.filter((row) => activeProjectIds.has(row.projectId)), ); const latestTurnByThread = new Map( latestTurnRows.map((row) => [row.threadId, mapLatestTurn(row)] as const), ); const sessionByThread = new Map( sessionRows.map((row) => [row.threadId, mapSessionRow(row)] as const), ); const snapshot = { snapshotSequence: computeSnapshotSequence(stateRows), projects: Arr.filterMap(projectRows, (row) => row.deletedAt === null && activeProjectIds.has(row.projectId) ? Result.succeed( mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null), ) : Result.failVoid, ), threads: threadRows.map( (row): OrchestrationThreadShell => ({ id: row.threadId, projectId: row.projectId, title: row.title, modelSelection: row.modelSelection, runtimeMode: row.runtimeMode, interactionMode: row.interactionMode, branch: row.branch, worktreePath: row.worktreePath, ...(row.linkedPullRequest === null ? {} : { linkedPullRequest: row.linkedPullRequest }), latestTurn: latestTurnByThread.get(row.threadId) ?? null, createdAt: row.createdAt, updatedAt: row.updatedAt, archivedAt: row.archivedAt, settledOverride: row.settledOverride, settledAt: row.settledAt, snoozedUntil: row.snoozedUntil, snoozedAt: row.snoozedAt, pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, titleRegeneration: mapTitleRegeneration(row), session: sessionByThread.get(row.threadId) ?? null, latestUserMessageAt: row.latestUserMessageAt, hasPendingApprovals: row.pendingApprovalCount > 0, hasPendingUserInput: row.pendingUserInputCount > 0, hasActionableProposedPlan: row.hasActionableProposedPlan > 0, backgroundLiveness: threadBackgroundLiveness.getThreadBackgroundLiveness( row.threadId, ), planProgress: threadPlanProgress.getThreadPlanProgress(row.threadId), }), ), updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", }; return yield* decodeShellSnapshot(snapshot).pipe( Effect.mapError( toPersistenceDecodeError( "ProjectionSnapshotQuery.getArchivedShellSnapshot:decodeShellSnapshot", ), ), ); }), ), Effect.mapError((error) => { if (isPersistenceError(error)) { return error; } return toPersistenceSqlError("ProjectionSnapshotQuery.getArchivedShellSnapshot:query")( error, ); }), ); const getSnapshotSequence: ProjectionSnapshotQueryShape["getSnapshotSequence"] = () => listProjectionStateRows(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getSnapshotSequence:query", "ProjectionSnapshotQuery.getSnapshotSequence:decodeRows", ), ), Effect.map((stateRows) => ({ snapshotSequence: computeSnapshotSequence(stateRows), })), ); const getCounts: ProjectionSnapshotQueryShape["getCounts"] = () => readProjectionCounts(undefined).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getCounts:query", "ProjectionSnapshotQuery.getCounts:decodeRow", ), ), Effect.map( (row): ProjectionSnapshotCounts => ({ projectCount: row.projectCount, threadCount: row.threadCount, }), ), ); const searchThreads: ProjectionSnapshotQueryShape["searchThreads"] = Effect.fn( "ProjectionSnapshotQuery.searchThreads", )(function* (input) { const escapedQuery = escapeLikePattern(input.query); const rows = yield* searchActiveThreadRows({ pattern: `%${escapedQuery}%`, limit: input.limit ?? 50, }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.searchThreads:query", "ProjectionSnapshotQuery.searchThreads:decodeRows", ), ), ); return { matches: rows.map((row) => ({ threadId: row.threadId, projectId: row.projectId, source: row.source, snippet: buildSearchSnippet(row.matchText, input.query), messageCreatedAt: row.messageCreatedAt, })), }; }); const getActiveProjectByWorkspaceRoot: ProjectionSnapshotQueryShape["getActiveProjectByWorkspaceRoot"] = (workspaceRoot) => getActiveProjectRowByWorkspaceRoot({ workspaceRoot }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getActiveProjectByWorkspaceRoot:query", "ProjectionSnapshotQuery.getActiveProjectByWorkspaceRoot:decodeRow", ), ), Effect.flatMap((option) => Option.isNone(option) ? Effect.succeed(Option.none()) : repositoryIdentityResolver.resolve(option.value.workspaceRoot).pipe( Effect.map((repositoryIdentity) => Option.some({ id: option.value.projectId, title: option.value.title, workspaceRoot: option.value.workspaceRoot, repositoryIdentity, defaultModelSelection: option.value.defaultModelSelection, defaultThreadEnvMode: option.value.defaultThreadEnvMode, faviconPath: option.value.faviconPath ?? null, scripts: option.value.scripts, createdAt: option.value.createdAt, updatedAt: option.value.updatedAt, deletedAt: option.value.deletedAt, } satisfies OrchestrationProject), ), ), ), ); const getProjectShellById: ProjectionSnapshotQueryShape["getProjectShellById"] = (projectId) => getActiveProjectRowById({ projectId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getProjectShellById:query", "ProjectionSnapshotQuery.getProjectShellById:decodeRow", ), ), Effect.flatMap((option) => Option.isNone(option) ? Effect.succeed(Option.none()) : repositoryIdentityResolver .resolve(option.value.workspaceRoot) .pipe( Effect.map((repositoryIdentity) => Option.some(mapProjectShellRow(option.value, repositoryIdentity)), ), ), ), ); const getFirstActiveThreadIdByProjectId: ProjectionSnapshotQueryShape["getFirstActiveThreadIdByProjectId"] = (projectId) => getFirstActiveThreadIdByProject({ projectId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getFirstActiveThreadIdByProjectId:query", "ProjectionSnapshotQuery.getFirstActiveThreadIdByProjectId:decodeRow", ), ), Effect.map(Option.map((row) => row.threadId)), ); const getThreadCheckpointContext: ProjectionSnapshotQueryShape["getThreadCheckpointContext"] = ( threadId, ) => Effect.gen(function* () { const threadRow = yield* getThreadCheckpointContextThreadRow({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadCheckpointContext:getThread:query", "ProjectionSnapshotQuery.getThreadCheckpointContext:getThread:decodeRow", ), ), ); if (Option.isNone(threadRow)) { return Option.none(); } const checkpointRows = yield* listCheckpointRowsByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadCheckpointContext:listCheckpoints:query", "ProjectionSnapshotQuery.getThreadCheckpointContext:listCheckpoints:decodeRows", ), ), ); return Option.some({ threadId: threadRow.value.threadId, projectId: threadRow.value.projectId, workspaceRoot: threadRow.value.workspaceRoot, worktreePath: threadRow.value.worktreePath, checkpoints: checkpointRows.map( (row): OrchestrationCheckpointSummary => ({ turnId: row.turnId, checkpointTurnCount: row.checkpointTurnCount, checkpointRef: row.checkpointRef, status: row.status, files: row.files, assistantMessageId: row.assistantMessageId, completedAt: row.completedAt, }), ), }); }); const getFullThreadDiffContext: NonNullable< ProjectionSnapshotQueryShape["getFullThreadDiffContext"] > = (threadId, toTurnCount) => Effect.gen(function* () { const row = yield* getFullThreadDiffContextRow({ threadId, checkpointTurnCount: toTurnCount, }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getFullThreadDiffContext:query", "ProjectionSnapshotQuery.getFullThreadDiffContext:decodeRow", ), ), ); if (Option.isNone(row)) { return Option.none(); } return Option.some({ threadId: row.value.threadId, projectId: row.value.projectId, workspaceRoot: row.value.workspaceRoot, worktreePath: row.value.worktreePath, latestCheckpointTurnCount: row.value.latestCheckpointTurnCount ?? 0, toCheckpointRef: row.value.toCheckpointRef, }); }); const getThreadShellById: ProjectionSnapshotQueryShape["getThreadShellById"] = (threadId) => Effect.gen(function* () { const [threadRow, latestTurnRow, sessionRow] = yield* Effect.all([ getActiveThreadRowById({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadShellById:getThread:query", "ProjectionSnapshotQuery.getThreadShellById:getThread:decodeRow", ), ), ), getLatestTurnRowByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadShellById:getLatestTurn:query", "ProjectionSnapshotQuery.getThreadShellById:getLatestTurn:decodeRow", ), ), ), getThreadSessionRowByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadShellById:getSession:query", "ProjectionSnapshotQuery.getThreadShellById:getSession:decodeRow", ), ), ), ]); if (Option.isNone(threadRow)) { return Option.none(); } return Option.some({ id: threadRow.value.threadId, projectId: threadRow.value.projectId, title: threadRow.value.title, modelSelection: threadRow.value.modelSelection, runtimeMode: threadRow.value.runtimeMode, interactionMode: threadRow.value.interactionMode, branch: threadRow.value.branch, worktreePath: threadRow.value.worktreePath, ...(threadRow.value.linkedPullRequest === null ? {} : { linkedPullRequest: threadRow.value.linkedPullRequest }), latestTurn: Option.isSome(latestTurnRow) ? mapLatestTurn(latestTurnRow.value) : null, createdAt: threadRow.value.createdAt, updatedAt: threadRow.value.updatedAt, archivedAt: threadRow.value.archivedAt, settledOverride: threadRow.value.settledOverride, settledAt: threadRow.value.settledAt, snoozedUntil: threadRow.value.snoozedUntil, snoozedAt: threadRow.value.snoozedAt, pinnedAt: threadRow.value.pinnedAt, pinOrderKey: threadRow.value.pinOrderKey ?? null, titleRegeneration: mapTitleRegeneration(threadRow.value), session: Option.isSome(sessionRow) ? mapSessionRow(sessionRow.value) : null, latestUserMessageAt: threadRow.value.latestUserMessageAt, hasPendingApprovals: threadRow.value.pendingApprovalCount > 0, hasPendingUserInput: threadRow.value.pendingUserInputCount > 0, hasActionableProposedPlan: threadRow.value.hasActionableProposedPlan > 0, backgroundLiveness: threadBackgroundLiveness.getThreadBackgroundLiveness( threadRow.value.threadId, ), planProgress: threadPlanProgress.getThreadPlanProgress(threadRow.value.threadId), } satisfies OrchestrationThreadShell); }); // Contiguous turn range bounding a windowed detail read; undefined loads the // full thread. Resolved from a window request inside the snapshot // transaction (see getThreadDetailSnapshot). interface ThreadDetailBounds { readonly minAnchorAt: string; readonly minTurnKey: string; readonly beforeAnchorAt: string; readonly beforeTurnKey: string; } const getThreadDetailByIdBounded = (threadId: ThreadId, bounds: ThreadDetailBounds | undefined) => Effect.gen(function* () { const [ threadRow, messageRows, proposedPlanRows, activityRows, pinnedActivityRows, checkpointRows, latestTurnRow, sessionRow, ] = yield* Effect.all([ getActiveThreadRowById({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:getThread:query", "ProjectionSnapshotQuery.getThreadDetailById:getThread:decodeRow", ), ), ), (bounds === undefined ? listThreadMessageRowsByThread({ threadId }) : listThreadMessageRowsByThreadWindow({ threadId, ...bounds }) ).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:listMessages:query", "ProjectionSnapshotQuery.getThreadDetailById:listMessages:decodeRows", ), ), ), listThreadProposedPlanRowsByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:listPlans:query", "ProjectionSnapshotQuery.getThreadDetailById:listPlans:decodeRows", ), ), ), (bounds === undefined ? listThreadActivityRowsByThread({ threadId }) : listThreadActivityRowsByThreadWindow({ threadId, ...bounds }) ).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:listActivities:query", "ProjectionSnapshotQuery.getThreadDetailById:listActivities:decodeRows", ), ), ), listPinnedThreadActivityRowsByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivities:query", "ProjectionSnapshotQuery.getThreadDetailById:listPinnedActivities:decodeRows", ), ), ), listCheckpointRowsByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:listCheckpoints:query", "ProjectionSnapshotQuery.getThreadDetailById:listCheckpoints:decodeRows", ), ), ), getLatestTurnRowByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:query", "ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:decodeRow", ), ), ), getThreadSessionRowByThread({ threadId }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailById:getSession:query", "ProjectionSnapshotQuery.getThreadDetailById:getSession:decodeRow", ), ), ), ]); if (Option.isNone(threadRow)) { return Option.none(); } const selectedActivityRows = [ ...new Map( [...activityRows, ...pinnedActivityRows].map((row) => [row.activityId, row] as const), ).values(), ].toSorted( (left, right) => (left.sequence ?? -1) - (right.sequence ?? -1) || left.createdAt.localeCompare(right.createdAt) || left.activityId.localeCompare(right.activityId), ); const thread = { id: threadRow.value.threadId, projectId: threadRow.value.projectId, title: threadRow.value.title, modelSelection: threadRow.value.modelSelection, runtimeMode: threadRow.value.runtimeMode, interactionMode: threadRow.value.interactionMode, branch: threadRow.value.branch, worktreePath: threadRow.value.worktreePath, ...(threadRow.value.linkedPullRequest === null ? {} : { linkedPullRequest: threadRow.value.linkedPullRequest }), latestTurn: Option.isSome(latestTurnRow) ? mapLatestTurn(latestTurnRow.value) : null, createdAt: threadRow.value.createdAt, updatedAt: threadRow.value.updatedAt, archivedAt: threadRow.value.archivedAt, settledOverride: threadRow.value.settledOverride, settledAt: threadRow.value.settledAt, snoozedUntil: threadRow.value.snoozedUntil, snoozedAt: threadRow.value.snoozedAt, pinnedAt: threadRow.value.pinnedAt, pinOrderKey: threadRow.value.pinOrderKey ?? null, titleRegeneration: mapTitleRegeneration(threadRow.value), deletedAt: null, messages: messageRows.map((row) => { const message = { id: row.messageId, role: row.role, text: row.text, turnId: row.turnId, streaming: row.isStreaming === 1, createdAt: row.createdAt, updatedAt: row.updatedAt, }; if (row.attachments !== null) { return Object.assign(message, { attachments: row.attachments }); } return message; }), proposedPlans: proposedPlanRows.map(mapProposedPlanRow), activities: selectedActivityRows.map((row) => { const activity = { id: row.activityId, tone: row.tone, kind: row.kind, summary: row.summary, payload: row.payload, turnId: row.turnId, createdAt: row.createdAt, }; if (row.sequence !== null) { return Object.assign(activity, { sequence: row.sequence }); } return activity; }), checkpoints: checkpointRows.map((row) => ({ turnId: row.turnId, checkpointTurnCount: row.checkpointTurnCount, checkpointRef: row.checkpointRef, status: row.status, files: row.files, assistantMessageId: row.assistantMessageId, completedAt: row.completedAt, })), session: Option.isSome(sessionRow) ? mapSessionRow(sessionRow.value) : null, }; return Option.some( yield* decodeThread(thread).pipe( Effect.mapError( toPersistenceDecodeError("ProjectionSnapshotQuery.getThreadDetailById:decodeThread"), ), ), ); }); const getThreadDetailById: ProjectionSnapshotQueryShape["getThreadDetailById"] = (threadId) => getThreadDetailByIdBounded(threadId, undefined); // Bounds pathological fan-out: one user turn that spawned hundreds of // subagent turns still pages in bounded chunks, at the cost of splitting the // fan-out group across pages (the cursor continues the same group). Also // structurally bounds the window scan via the candidates CTE's LIMIT. const THREAD_DETAIL_MAX_RAW_TURNS_PER_PAGE = 150; // Sentinels for unbounded keyset ends; "~" sorts after any ISO timestamp. const ANCHOR_UNBOUNDED = "~"; const getThreadDetailSnapshot: ProjectionSnapshotQueryShape["getThreadDetailSnapshot"] = ( threadId, window, ) => // Read the thread detail and the snapshot sequence within a single // transaction so the sequence is consistent with the returned state; a // projector update landing between two separate reads could otherwise return // a sequence ahead of the thread detail, causing the client to resume from // too far and drop events. Window resolution runs inside the same // transaction so the page boundary is consistent with the returned rows. sql .withTransaction( Effect.gen(function* () { if (window?.turnLimit === undefined) { const thread = yield* getThreadDetailById(threadId); if (Option.isNone(thread)) { return Option.none(); } const { snapshotSequence } = yield* getSnapshotSequence(); return Option.some({ snapshotSequence, thread: thread.value }); } // A malformed or foreign-thread cursor falls back to the first page // rather than failing: the client's stale cursor after a revert or // reconnect should degrade to "reload recent history", not error. const decodedCursor = window.beforeCursor === undefined ? null : decodeThreadDetailPageCursor(window.beforeCursor); const cursor = decodedCursor?.threadId === threadId ? decodedCursor : null; const windowRows = yield* listTurnWindowRows({ threadId, beforeAnchorAt: cursor?.beforeAnchorAt ?? ANCHOR_UNBOUNDED, beforeTurnKey: cursor?.beforeTurnId ?? "", userTurnLimit: window.turnLimit, maxRawTurns: THREAD_DETAIL_MAX_RAW_TURNS_PER_PAGE, }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailSnapshot:listTurnWindow:query", "ProjectionSnapshotQuery.getThreadDetailSnapshot:listTurnWindow:decodeRows", ), ), ); const oldest = windowRows[0]; // An empty window (no turns before the cursor, or a thread with no // turns at all) still returns thread metadata with empty collections // for turn-linked rows; turnless rows are bounded to the same empty // range. The first page of a turnless thread stays unwindowed so // pre-turn content (e.g. a just-created thread) is not hidden. const bounds: ThreadDetailBounds | undefined = oldest === undefined && cursor === null ? undefined : { minAnchorAt: oldest?.anchorAt ?? "", minTurnKey: oldest?.turnKey ?? "", beforeAnchorAt: cursor?.beforeAnchorAt ?? ANCHOR_UNBOUNDED, beforeTurnKey: cursor?.beforeTurnId ?? "", }; // Empty window behind a cursor: nothing older remains. const emptyBounds = oldest === undefined && cursor !== null ? { minAnchorAt: "", minTurnKey: "", beforeAnchorAt: "", beforeTurnKey: "" } : undefined; const thread = yield* getThreadDetailByIdBounded(threadId, emptyBounds ?? bounds); if (Option.isNone(thread)) { return Option.none(); } const hasMore = oldest !== undefined && (yield* listTurnWindowRows({ threadId, beforeAnchorAt: oldest.anchorAt, beforeTurnKey: oldest.turnKey, userTurnLimit: 1, maxRawTurns: 1, }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailSnapshot:probeOlder:query", "ProjectionSnapshotQuery.getThreadDetailSnapshot:probeOlder:decodeRows", ), ), )).length > 0; const { snapshotSequence } = yield* getSnapshotSequence(); const watermarkRow = yield* getThreadEventWatermarkRow({ threadId, maxSequence: snapshotSequence, }).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "ProjectionSnapshotQuery.getThreadDetailSnapshot:threadWatermark:query", "ProjectionSnapshotQuery.getThreadDetailSnapshot:threadWatermark:decodeRow", ), ), ); const threadSequence = Option.match(watermarkRow, { onNone: () => 0, onSome: (row) => row.threadSequence ?? 0, }); return Option.some({ snapshotSequence, thread: thread.value, page: { beforeCursor: hasMore && oldest !== undefined ? encodeThreadDetailPageCursor({ threadId, beforeAnchorAt: oldest.anchorAt, beforeTurnId: oldest.turnKey, }) : null, hasMore, snapshotSequence, threadSequence, }, }); }), ) .pipe( Effect.mapError((error) => isPersistenceError(error) ? error : toPersistenceSqlError("ProjectionSnapshotQuery.getThreadDetailSnapshot:transaction")( error, ), ), ); return { getCommandReadModel, getSnapshot, getShellSnapshot, getArchivedShellSnapshot, searchThreads, getSnapshotSequence, getCounts, getActiveProjectByWorkspaceRoot, getProjectShellById, getFirstActiveThreadIdByProjectId, getThreadCheckpointContext, getFullThreadDiffContext, getThreadShellById, getThreadDetailById, getThreadDetailSnapshot, } satisfies ProjectionSnapshotQueryShape; }); export const OrchestrationProjectionSnapshotQueryLive = Layer.effect( ProjectionSnapshotQuery, makeProjectionSnapshotQuery, );