import * as SecretResolver from "@t3tools/secret-provider-core/server/SecretResolver"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { AuthSessionId, AuthSourceControlWriteScope, EnvironmentId, GitManagerError, ProjectId, ThreadId, ProviderDriverKind, ProviderInstanceId, WS_METHODS, WsRpcGroup, WsServerRefreshProvidersRpc, WsSubscribeServerConfigRpc, type OrchestrationV2StoredEvent, type OrchestrationV2ThreadShell, type OrchestrationProjectShell, type ServerConfig, type ServerConfigStreamEvent, type ServerProvider, } from "@t3tools/contracts"; import * as Deferred from "effect/Deferred"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Cause from "effect/Cause"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as PubSub from "effect/PubSub"; import * as Queue from "effect/Queue"; import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; import { Headers } from "effect/http"; import { RpcGroup, RpcMessage, RpcServer } from "effect/rpc"; import * as SqlClient from "effect/sql/SqlClient"; import * as Statement from "effect/sql/Statement"; import * as PreviewAutomationBroker from "./mcp/PreviewAutomationBroker.ts"; import * as ServerBrowser from "./preview/ServerBrowser.ts"; import * as AcpRegistryRuntimeCoordinator from "@t3tools/provider-acp-registry/server/AcpRegistryRuntimeCoordinator"; import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport"; import * as AgentSessionImporter from "./project/AgentSessionImporter.ts"; import * as AgentSessionScanner from "./project/AgentSessionScanner.ts"; import * as AnalyticsService from "./telemetry/AnalyticsService.ts"; import * as AntigravityInstallation from "./provider/AntigravityInstallation.ts"; import * as BackgroundPolicy from "./background/BackgroundPolicy.ts"; import * as CheckpointDiffQuery from "./checkpointing/CheckpointDiffQuery.ts"; import * as NativeChildWorkQuery from "./orchestration-v2/NativeChildWorkQuery.ts"; import * as CommandOutputHub from "./orchestration-v2/CommandOutputHub.ts"; import * as CodexInstallation from "./provider/CodexInstallation.ts"; import * as DeviceService from "./device/DeviceService.ts"; import * as DocumentText from "./provider/DocumentText.ts"; import * as DirectEndpoints from "./environment/DirectEndpoints.ts"; import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; import * as EnvironmentTheme from "./environmentTheme.ts"; import * as GitWorkflowService from "./git/GitWorkflowService.ts"; import * as HostResources from "./resourceTelemetry/HostResources.ts"; import * as ManagedProjectFolders from "./project/ManagedProjectFolders.ts"; import * as ScratchThreadPromotion from "./project/ScratchThreadPromotion.ts"; import * as McpAppRequests from "./mcpApps/McpAppRequests.ts"; import * as ModelManifest from "./provider/ModelManifest.ts"; import * as Orchestrator from "./orchestration-v2/Orchestrator.ts"; import * as OtlpEndpointCheck from "./observability/OtlpEndpointCheck.ts"; import * as PairingGrantStore from "./auth/PairingGrantStore.ts"; import * as RpcAuthorization from "./auth/RpcAuthorization.ts"; import * as PeerLinkRequests from "./peer/PeerLinkRequests.ts"; import * as PeerLinks from "./peer/PeerLinks.ts"; import * as PortScanner from "./preview/PortScanner.ts"; import * as PreviewManager from "./preview/Manager.ts"; import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts"; import * as ProjectCloneTracker from "./project/ProjectCloneTracker.ts"; import * as ProjectSetupScriptRunner from "./project/ProjectSetupScriptRunner.ts"; import * as ProviderAuthService from "./provider/ProviderAuthService.ts"; import * as ProviderInstanceRegistry from "./provider/ProviderInstanceRegistry.ts"; import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; import * as ProviderMaintenanceRunner from "./provider/providerMaintenanceRunner.ts"; import * as ProviderRegistry from "./provider/ProviderRegistry.ts"; import * as ProviderSessionManager from "./orchestration-v2/ProviderSessionManager.ts"; import * as PullRequestReviewGuideService from "./pullRequest/PullRequestReviewGuideService.ts"; import * as PullRequestService from "./pullRequest/PullRequestService.ts"; import * as PullRequestSyncReactor from "./orchestration-v2/PullRequestSyncReactor.ts"; import * as RelayClient from "@t3tools/shared/relayClient"; import * as RemoteOpenTargets from "./environment/RemoteOpenTargets.ts"; import * as RepositoryIdentityResolver from "./project/RepositoryIdentityResolver.ts"; import * as ResourceTelemetry from "./resourceTelemetry/ResourceTelemetry.ts"; import * as ReviewService from "./review/ReviewService.ts"; import * as ScheduledTasks from "./scheduledTasks/ScheduledTaskService.ts"; import * as SecretRequests from "./secrets/SecretRequests.ts"; import * as GuestShareService from "./guest/GuestShareService.ts"; import * as ServerEnvironment from "./environment/ServerEnvironment.ts"; import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts"; import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts"; import * as ServerSelfUpdate from "./cloud/selfUpdate.ts"; import * as NpmRelease from "./cloud/npmRelease.ts"; import * as SessionStore from "./auth/SessionStore.ts"; import * as SourceControlDiscovery from "./sourceControl/SourceControlDiscovery.ts"; import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts"; import * as SourceControlSignIn from "./sourceControl/SourceControlSignIn.ts"; import * as StorageCleanup from "./storageCleanup.ts"; import * as DatabaseStorage from "./persistence/DatabaseStorage.ts"; import * as TerminalManager from "./terminal/Manager.ts"; import * as ThreadLaunchService from "./orchestration-v2/ThreadLaunchService.ts"; import * as ThreadSearch from "./orchestration-v2/ThreadSearch.ts"; import * as UsageLimitSources from "./usage/UsageLimitSources.ts"; import * as UsageService from "./usage/UsageService.ts"; import * as VcsProvisioningService from "./vcs/VcsProvisioningService.ts"; import * as VcsStatusBroadcaster from "./vcs/VcsStatusBroadcaster.ts"; import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts"; import * as WorkspaceFileSystem from "./workspace/WorkspaceFileSystem.ts"; import * as WorktreeSetupTracker from "./project/WorktreeSetupTracker.ts"; import * as ProjectFaviconResolver from "./project/ProjectFaviconResolver.ts"; import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts"; import * as TriageService from "./triage/TriageService.ts"; import * as WorkspacePaths from "./workspace/WorkspacePaths.ts"; import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; import * as ServerConfigModule from "./config.ts"; import * as Keybindings from "./keybindings.ts"; import * as ServerSettings from "./serverSettings.ts"; import * as ManagedPolicy from "./policy/ManagedPolicy.ts"; import * as ThreadManagement from "./orchestration-v2/ThreadManagementService.ts"; import * as ProjectStore from "./orchestration-v2/ProjectStore.ts"; import * as OrchestrationEventStore from "./persistence/OrchestrationEventStore.ts"; import * as SqlitePersistence from "./persistence/Sqlite.ts"; import * as ExternalLauncher from "./process/externalLauncher.ts"; import * as ProjectEnrichment from "./project/ProjectEnrichmentService.ts"; import * as ProjectService from "./project/ProjectService.ts"; import { layerWsRpc, resolveOpenDiscoveryForConfig, subscribeOrchestrationV2Shell, subscribeOrchestrationV2Thread, WS_RPC_SERVER_OPTIONS, withLateConfigDiscovery, } from "./ws.ts"; const snapshotConfig = (fields: Partial) => ({ availableEditors: [], settings: {}, ...fields }) as unknown as ServerConfig; const settingsUpdated = (settings: object): ServerConfigStreamEvent => ({ version: 1, type: "settingsUpdated", payload: { settings: settings as ServerConfig["settings"] }, }); it.effect("resends late editors without rolling back updates already sent", () => Effect.gen(function* () { const settingsSent = yield* Deferred.make(); const events = yield* withLateConfigDiscovery( snapshotConfig({ settings: { enableProviderUpdateChecks: true } as never }), Stream.make(settingsUpdated({ enableProviderUpdateChecks: false })), { resolveAvailableEditors: () => Effect.succeed(["file-manager"]), resolveFileManagerRevealKind: () => Deferred.await(settingsSent).pipe(Effect.as("file-explorer" as const)), }, { resolveTargets: () => Effect.succeed([]) }, { resolve: () => Effect.succeed([]) }, ).pipe( Stream.tap((event) => event.type === "settingsUpdated" ? Deferred.succeed(settingsSent, undefined) : Effect.void, ), Stream.runCollect, ); const [first, second] = Array.from(events); assert.equal(events.length, 2); assert.equal(first?.type, "settingsUpdated"); assert.equal(second?.type, "snapshot"); if (second?.type === "snapshot") { assert.deepEqual(second.config.availableEditors, ["file-manager"]); assert.equal(second.config.shellRevealInFileManagerKind, "file-explorer"); assert.deepEqual(second.config.settings, { enableProviderUpdateChecks: false } as never); } }), ); it.effect("resends a reveal kind and remote targets that outlast the snapshot timeout", () => Effect.gen(function* () { const targets = [{ kind: "mdns" as const, host: "test.local" }]; const finishDiscovery = yield* Deferred.make(); const collecting = yield* withLateConfigDiscovery( snapshotConfig({ availableEditors: ["file-manager"] }), Stream.empty, { resolveAvailableEditors: () => Effect.succeed(["file-manager"]), resolveFileManagerRevealKind: () => Effect.succeed("file-explorer"), }, { resolveTargets: () => Deferred.await(finishDiscovery) }, { resolve: () => Effect.succeed([]) }, ).pipe(Stream.runCollect, Effect.forkScoped({ startImmediately: true })); yield* TestClock.adjust("6 seconds"); yield* Deferred.succeed(finishDiscovery, targets); const events = yield* Fiber.join(collecting); const [late, remote] = Array.from(events); assert.equal(events.length, 2); assert.equal(late?.type, "snapshot"); if (late?.type === "snapshot") { assert.equal(late.config.shellRevealInFileManagerKind, "file-explorer"); } assert.equal(remote?.type, "snapshot"); if (remote?.type === "snapshot") { assert.deepEqual(remote.config.remoteOpenTargets, targets); assert.equal(remote.config.shellRevealInFileManagerKind, "file-explorer"); } }).pipe(Effect.scoped), ); it.effect("runs editor, remote open target and direct endpoint discovery side by side", () => Effect.gen(function* () { // Each discovery finishes only once the other two have started, so a // config that ran them one after another would never resolve. const editorsStarted = yield* Deferred.make(); const targetsStarted = yield* Deferred.make(); const endpointsStarted = yield* Deferred.make(); const startThenAwait = (started: Deferred.Deferred, value: A) => Deferred.succeed(started, undefined).pipe( Effect.andThen( Effect.forEach([editorsStarted, targetsStarted, endpointsStarted], Deferred.await), ), Effect.as(value), ); const discovery = yield* resolveOpenDiscoveryForConfig({ editors: startThenAwait(editorsStarted, ["file-manager" as const]), fileManagerRevealKind: Effect.succeed("finder" as const), remoteOpenTargets: startThenAwait(targetsStarted, []), directEndpoints: startThenAwait(endpointsStarted, []), }); assert.deepEqual(discovery, { availableEditors: ["file-manager"], shellRevealInFileManager: true, shellRevealInFileManagerKind: "finder", remoteOpenTargets: [], directEndpoints: [], }); }), ); const ReplayTestLayer = OrchestrationEventStore.layer.pipe( Layer.provideMerge(SqlitePersistence.layerMemory), ); it.live.each(["thread", "shell"] as const)( "%s resume skips unknown types, synchronizes, and receives the live tail", (scope) => Effect.gen(function* () { const store = yield* OrchestrationEventStore.OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; const now = "2026-01-05T00:00:00.000Z"; const threadId = ThreadId.make("thread:replay"); const projectId = ProjectId.make("project:replay"); const project = { id: projectId, title: "Replay", workspaceRoot: "/tmp/replay", defaultModelSelection: null, scripts: [], createdAt: now, updatedAt: now, } satisfies OrchestrationProjectShell; const insert = (version: number, unknown = false) => sql<{ sequence: number }>` INSERT INTO orchestration_events ( event_id, aggregate_kind, stream_id, stream_version, event_type, occurred_at, actor_kind, payload_json, metadata_json, application_event_version ) VALUES ( ${`replay:${version}`}, ${scope === "thread" ? "thread" : "project"}, ${scope === "thread" ? threadId : projectId}, ${version}, ${unknown ? "future.event" : scope === "thread" ? "provider-session.detached" : "project.meta-updated"}, ${now}, 'server', ${JSON.stringify( scope === "thread" ? { providerSessionId: "session:replay", detachedAt: now } : { projectId, updatedAt: now }, )}, '{}', 2 ) RETURNING sequence `; const [before] = yield* insert(0); yield* insert(1, true); const [after] = yield* insert(2); const [highWater] = yield* insert(3, true); const updates = yield* PubSub.unbounded(); let liveSequence = 0; const consume = ( stream: Stream.Stream, ) => stream.pipe( Stream.tap((item) => Effect.gen(function* () { if (item.kind !== "synchronized") return; const [live] = yield* insert(4); liveSequence = live!.sequence; const events = yield* store .readApplicationEvents({ afterSequence: highWater!.sequence, throughSequence: liveSequence, }) .pipe(Stream.runCollect); yield* store.publishCommitted(events); }), ), Stream.takeUntil((item) => item.sequence === liveSequence), Stream.map((item) => ({ kind: item.kind as string, sequence: item.sequence })), Stream.runCollect, ); const subscription = Effect.gen(function* () { return scope === "thread" ? yield* subscribeOrchestrationV2Thread({ threadId, afterSequence: before!.sequence, requestCompletionMarker: true, }).pipe(Effect.flatMap(consume)) : yield* subscribeOrchestrationV2Shell({ afterSequence: before!.sequence, requestCompletionMarker: true, }).pipe(Effect.flatMap(consume)); }); const items = yield* subscription.pipe( Effect.provideService(ThreadManagement.ThreadManagementService, { ensureLegacyTranscript: () => Effect.void, getShellSnapshot: () => Effect.die("shell resume must not load a full snapshot"), listActiveSubagentThreadIds: () => Effect.succeed([]), streamStoredEventsFrom: ( input: Parameters< ThreadManagement.ThreadManagementService["Service"]["streamStoredEventsFrom"] >[0], ) => { assert.equal(input?.afterSequence, highWater!.sequence); return store .streamApplicationEvents({ afterSequence: input?.afterSequence ?? 0 }) .pipe( Stream.filter((event): event is OrchestrationV2StoredEvent => "event" in event), ); }, } as unknown as ThreadManagement.ThreadManagementService["Service"]), Effect.provideService(ProjectStore.ProjectStoreV2, { listShells: () => Effect.succeed([]), } as unknown as ProjectStore.ProjectStoreV2["Service"]), Effect.provideService(ProjectService.ProjectService, { getShell: () => Effect.succeedSome(project), } as unknown as ProjectService.ProjectService["Service"]), Effect.provideService(ProjectEnrichment.ProjectEnrichmentService, { subscribeChanges: PubSub.subscribe(updates), } as ProjectEnrichment.ProjectEnrichmentService["Service"]), ); assert.deepEqual( items.map((item) => item.kind), scope === "thread" ? ["event", "synchronized", "event"] : ["project.updated", "synchronized", "project.updated"], ); assert.deepEqual( items.flatMap((item) => (item.sequence === undefined ? [] : [item.sequence])), [after!.sequence, liveSequence], ); }).pipe(Effect.scoped, Effect.provide(ReplayTestLayer)), ); it.effect("coalesces streaming replay before synchronization and the live tail", () => Effect.gen(function* () { const store = yield* OrchestrationEventStore.OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; const threadId = ThreadId.make("thread:streaming-replay"); const now = "2026-10-27T00:00:00.000Z"; const insert = (version: number, text: string, streaming: boolean) => sql<{ sequence: number; }>` INSERT INTO orchestration_events ( event_id, aggregate_kind, stream_id, stream_version, event_type, occurred_at, actor_kind, payload_json, metadata_json, application_event_version ) VALUES ( ${`streaming-replay:${version}`}, 'thread', ${threadId}, ${version}, 'message.updated', ${now}, 'server', ${JSON.stringify({ createdBy: "agent", creationSource: "web", id: "message:streaming-replay", threadId, runId: null, nodeId: null, role: "assistant", text, attachments: [], streaming, createdAt: now, updatedAt: now, })}, '{}', 2 ) RETURNING sequence `; yield* insert(0, "H", true); yield* insert(1, "He", true); const [latest] = yield* insert(2, "Hello", true); const [completed] = yield* insert(3, "Hello!", false); const items = yield* subscribeOrchestrationV2Thread({ threadId, afterSequence: 0, requestCompletionMarker: true, }).pipe( Effect.flatMap(Stream.runCollect), Effect.provideService(ThreadManagement.ThreadManagementService, { ensureLegacyTranscript: () => Effect.void, streamStoredEventsFrom: () => Stream.unwrap( insert(4, "Next", false).pipe( Effect.map(() => store.readAgentEvents({ threadId, afterSequence: completed!.sequence }), ), ), ), } as unknown as ThreadManagement.ThreadManagementService["Service"]), ); assert.deepEqual( items.map((item) => item.kind === "event" ? [item.sequence, item.event.type === "message.updated" ? item.event.payload.text : null] : [item.kind], ), [ [latest!.sequence, "Hello"], [completed!.sequence, "Hello!"], ["synchronized"], [completed!.sequence + 1, "Next"], ], ); }).pipe(Effect.scoped, Effect.provide(ReplayTestLayer)), ); it.effect("shell groups read distinct threads once and keep each event's own sequence", () => Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const now = "2026-10-27T00:00:00.000Z"; const insert = (aggregateKind: "thread" | "project", streamId: string, version: number) => sql<{ sequence: number }>` INSERT INTO orchestration_events ( event_id, aggregate_kind, stream_id, stream_version, event_type, occurred_at, actor_kind, payload_json, metadata_json, application_event_version ) VALUES ( ${`shell-group:${streamId}:${version}`}, ${aggregateKind}, ${streamId}, ${version}, ${aggregateKind === "thread" ? "provider-session.detached" : "project.meta-updated"}, ${now}, 'server', ${JSON.stringify( aggregateKind === "thread" ? { providerSessionId: "session:shell-group", detachedAt: now } : { projectId: streamId, updatedAt: now }, )}, '{}', 2 ) RETURNING sequence `.pipe(Effect.map(([row]) => row!.sequence)); const shell = (id: string, archivedAt: string | null = null) => ({ id: ThreadId.make(id), archivedAt }) as unknown as OrchestrationV2ThreadShell; const project = { id: ProjectId.make("project:group"), } as unknown as OrchestrationProjectShell; const after = yield* insert("project", "project:before", 0); yield* insert("thread", "thread:active", 0); const archived = yield* insert("thread", "thread:archived", 0); const projectUpdated = yield* insert("project", project.id, 0); const active = yield* insert("thread", "thread:active", 1); const missing = yield* insert("thread", "thread:missing", 0); const shellReads: Array | undefined> = []; let batchFails = false; const collect = (afterSequence: number) => subscribeOrchestrationV2Shell({ afterSequence, requestCompletionMarker: true }).pipe( Effect.flatMap((stream) => stream.pipe( Stream.takeUntil((item) => item.kind === "synchronized"), Stream.runCollect, ), ), Effect.provideService(ThreadManagement.ThreadManagementService, { listActiveSubagentThreadIds: () => Effect.succeed([]), getShellSnapshot: (options?: { readonly threadIds?: ReadonlyArray }) => Effect.suspend(() => { shellReads.push(options?.threadIds); return batchFails ? Effect.fail("batch failed") : Effect.succeed({ schemaVersion: 1, snapshotSequence: Number.MAX_SAFE_INTEGER, threads: [shell("thread:active")], archivedThreads: [shell("thread:archived", now)], }); }), getThreadShell: (threadId: string) => Effect.sync(() => { shellReads.push([`scalar:${threadId}`]); return threadId === "thread:missing" ? null : shell(threadId, threadId === "thread:archived" ? now : null); }), } as unknown as ThreadManagement.ThreadManagementService["Service"]), Effect.provideService(ProjectStore.ProjectStoreV2, { listShells: () => Effect.succeed([]), } as unknown as ProjectStore.ProjectStoreV2["Service"]), Effect.provideService(ProjectService.ProjectService, { getShell: () => Effect.succeedSome(project), } as unknown as ProjectService.ProjectService["Service"]), Effect.provideServiceEffect( ProjectEnrichment.ProjectEnrichmentService, PubSub.unbounded().pipe( Effect.map( (changes) => ({ subscribeChanges: PubSub.subscribe(changes), }) as unknown as ProjectEnrichment.ProjectEnrichmentService["Service"], ), ), ), ); const collectSummary = (afterSequence: number) => collect(afterSequence).pipe( Effect.map((items) => items.flatMap((item): ReadonlyArray> => item.kind === "thread.updated" ? [[item.kind, item.sequence, item.thread.id]] : item.kind === "thread.removed" ? [[item.kind, item.sequence, item.threadId]] : item.kind === "project.updated" ? [[item.kind, item.sequence, item.project.id]] : [], ), ), ); const expectedGroup = [ ["thread.removed", archived, "thread:archived"], ["project.updated", projectUpdated, project.id], ["thread.updated", active, "thread:active"], ["thread.removed", missing, "thread:missing"], ]; assert.deepEqual(yield* collectSummary(after), expectedGroup); assert.deepEqual(shellReads, [["thread:archived", "thread:active", "thread:missing"]]); shellReads.length = 0; assert.deepEqual(yield* collectSummary(active), [ ["thread.removed", missing, "thread:missing"], ]); assert.deepEqual(shellReads, [["scalar:thread:missing"]]); shellReads.length = 0; batchFails = true; assert.deepEqual(yield* collectSummary(after), expectedGroup); assert.deepEqual(shellReads, [ ["thread:archived", "thread:active", "thread:missing"], ["scalar:thread:archived"], ["scalar:thread:active"], ["scalar:thread:missing"], ]); }).pipe(Effect.scoped, Effect.provide(ReplayTestLayer)), ); it.effect("shell snapshots omit idle children and older settled threads only on request", () => Effect.gen(function* () { const shell = ( id: string, parent: string | null, activityRunStatus: "running" | null, settledDaysAgo: number | null = null, ) => ({ id: ThreadId.make(id), projectId: "project", archivedAt: null, status: activityRunStatus ?? "completed", activityRunStatus, pendingRuntimeRequest: null, pendingBackgroundTasks: [], settledOverride: settledDaysAgo === null ? null : "settled", settledAt: settledDaysAgo === null ? null : DateTime.makeUnsafe(-settledDaysAgo * 86_400_000), lineage: { parentThreadId: parent === null ? null : ThreadId.make(parent), relationshipToParent: parent === null ? null : "subagent", rootThreadId: ThreadId.make(parent ?? id), }, }) as unknown as OrchestrationV2ThreadShell; const threads = [ shell("parent", null, null), shell("idle", "parent", null), shell("live", "parent", "running"), ...Array.from({ length: 25 }, (_, index) => shell(`recent-${index}`, null, null, 1)), shell("old", null, null, 30), ]; const firstSnapshot = (input: { readonly omitIdleSubagents?: boolean; readonly omitOlderSettled?: boolean; }) => subscribeOrchestrationV2Shell(input).pipe( Effect.flatMap((stream) => stream.pipe(Stream.take(1), Stream.runCollect)), Effect.map(([item]) => (item?.kind === "snapshot" ? item.snapshot : null)), Effect.provideService(ThreadManagement.ThreadManagementService, { listActiveShellIndex: () => Effect.succeed( threads.map((thread) => ({ ...thread, pinnedAt: null, snoozedUntil: null, mayBeLive: thread.activityRunStatus !== null, })), ), readShellSnapshot: (options?: { readonly threadIds?: ReadonlyArray }) => Effect.succeed( Effect.succeed({ schemaVersion: 1, snapshotSequence: 0, threads: threads.filter( (thread) => options?.threadIds?.includes(thread.id) ?? true, ), archivedThreads: [], }), ), } as unknown as ThreadManagement.ThreadManagementService["Service"]), Effect.provideService(ProjectStore.ProjectStoreV2, { listShells: () => Effect.succeed([]), } as unknown as ProjectStore.ProjectStoreV2["Service"]), Effect.provideService(ProjectService.ProjectService, { getShell: () => Effect.succeedNone, } as unknown as ProjectService.ProjectService["Service"]), Effect.provideServiceEffect( ProjectEnrichment.ProjectEnrichmentService, PubSub.unbounded().pipe( Effect.map( (changes) => ({ subscribeChanges: PubSub.subscribe(changes), }) as unknown as ProjectEnrichment.ProjectEnrichmentService["Service"], ), ), ), ); const recent = threads.slice(3, 28).map((thread) => thread.id); const full = yield* firstSnapshot({}); assert.deepEqual( full?.threads.map((thread) => thread.id), threads.map((thread) => thread.id), ); assert.isUndefined(full?.olderSettled); const withoutChildren = yield* firstSnapshot({ omitIdleSubagents: true }); assert.deepEqual( withoutChildren?.threads.map((thread) => thread.id), ["parent", "live", ...recent, "old"], ); const withoutOld = yield* firstSnapshot({ omitIdleSubagents: true, omitOlderSettled: true }); assert.deepEqual( withoutOld?.threads.map((thread) => thread.id), ["parent", "live", ...recent], ); assert.deepEqual(withoutOld?.olderSettled?.counts, [ { projectId: ProjectId.make("project"), count: 1 }, ]); }).pipe(Effect.scoped, Effect.provide(ReplayTestLayer)), ); // Build the production RPC layer; unrelated capabilities fail if this test calls them. const layerUnusedConfigRpcServices = Layer.mergeAll( Layer.mock(DocumentText.DocumentText)({}), Layer.mock(ServerSecretStore.ServerSecretStore)({}), Layer.mock(WorkspacePaths.WorkspacePaths)({}), Layer.mock(TraceDiagnostics.TraceDiagnostics)({}), Layer.mock(TriageService.TriageService)({}), Layer.mock(ProjectFaviconResolver.ProjectFaviconResolver)({}), Layer.mock(ProjectEnrichment.ProjectEnrichmentService)({}), Layer.mock(AcpRegistryRuntimeCoordinator.AcpRegistryRuntimeCoordinator)({}), Layer.mock(AcpRegistrySupport.AcpRegistryCatalog)({}), Layer.mock(AgentSessionImporter.AgentSessionImporter)({}), Layer.mock(AgentSessionScanner.AgentSessionScanner)({}), Layer.mock(AnalyticsService.AnalyticsService)({}), Layer.mock(AntigravityInstallation.AntigravityInstallation)({ managedDirectory: "/unused" }), Layer.mock(BackgroundPolicy.BackgroundPolicy)({}), Layer.mock(CheckpointDiffQuery.CheckpointDiffQuery)({}), Layer.mock(NativeChildWorkQuery.NativeChildWorkQuery)({}), Layer.mock(CommandOutputHub.CommandOutputHub)({}), Layer.mock(CodexInstallation.CodexInstallation)({ managedDirectory: "/unused" }), Layer.mock(DeviceService.DeviceService)({}), Layer.mock(EnvironmentTheme.EnvironmentThemeService)({}), Layer.mock(GitWorkflowService.GitWorkflowService)({}), Layer.mock(HostResources.HostResources)({}), Layer.mock(McpAppRequests.McpAppRequests)({}), Layer.mock(ModelManifest.ModelManifest)({}), Layer.mock(OrchestrationEventStore.OrchestrationEventStore)({}), Layer.mock(Orchestrator.OrchestratorV2)({}), Layer.mock(OtlpEndpointCheck.OtlpEndpointCheck)({}), Layer.mock(PairingGrantStore.PairingGrantStore)({}), Layer.mock(PeerLinks.PeerLinks)({}), Layer.mock(PeerLinkRequests.PeerLinkRequests)({}), Layer.mock(PortScanner.PortDiscovery)({ trackAgent: () => () => undefined }), Layer.mock(PreviewManager.PreviewManager)({}), Layer.mock(ProcessDiagnostics.ProcessDiagnostics)({}), Layer.mock(ProcessResourceMonitor.ProcessResourceMonitor)({}), Layer.mock(ProjectCloneTracker.ProjectCloneTracker)({}), Layer.mock(ProjectService.ProjectService)({}), Layer.mock(ProjectSetupScriptRunner.ProjectSetupScriptRunner)({}), Layer.mock(ProjectStore.ProjectStoreV2)({}), Layer.mock(ProviderAuthService.ProviderAuthService)({}), Layer.mock(ProviderInstanceRegistry.ProviderInstanceRegistry)({}), Layer.mock(ProviderLatestVersions.ProviderLatestVersions)({}), Layer.mock(ProviderMaintenanceRunner.ProviderMaintenanceRunner)({}), Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({}), Layer.mock(PullRequestReviewGuideService.PullRequestReviewGuideService)({}), Layer.mock(PullRequestService.PullRequestService)({}), Layer.mock(PullRequestSyncReactor.PullRequestSyncReactor)({}), Layer.mock(RelayClient.RelayClient)({}), Layer.mock(RepositoryIdentityResolver.RepositoryIdentityResolver)({}), Layer.mock(ResourceTelemetry.ResourceTelemetry)({}), Layer.mock(ReviewService.ReviewService)({}), Layer.mock(ScheduledTasks.ScheduledTaskService)({}), Layer.mock(SecretRequests.SecretRequests)({}), Layer.mock(SecretResolver.SecretResolver)({}), Layer.mock(GuestShareService.GuestShareService)({}), Layer.mock(ServerLifecycleEvents.ServerLifecycleEvents)({}), Layer.mock(ServerRuntimeStartup.ServerRuntimeStartup)({}), Layer.mock(ServerSelfUpdate.ServerSelfUpdate)({}), Layer.mock(NpmRelease.NpmRelease)({}), Layer.mock(SessionStore.SessionStore)({ cookieName: "config-test", legacyCookieName: undefined }), Layer.mock(SourceControlDiscovery.SourceControlDiscovery)({}), Layer.mock(SourceControlRepositoryService.SourceControlRepositoryService)({}), Layer.mock(SourceControlSignIn.SourceControlSignIn)({}), Layer.mock(StorageCleanup.StorageCleanup)({}), Layer.mock(DatabaseStorage.DatabaseStorage)({}), Layer.mock(TerminalManager.TerminalManager)({}), Layer.mock(ThreadLaunchService.ThreadLaunchService)({}), Layer.mock(ThreadManagement.ThreadManagementService)({}), Layer.mock(ThreadSearch.ThreadSearch)({}), Layer.mock(UsageService.UsageService)({}), Layer.mock(VcsProvisioningService.VcsProvisioningService)({}), Layer.mock(VcsStatusBroadcaster.VcsStatusBroadcaster)({}), Layer.mock(WorkspaceEntries.WorkspaceEntries)({}), Layer.mock(WorkspaceFileSystem.WorkspaceFileSystem)({}), Layer.mock(WorktreeSetupTracker.WorktreeSetupTracker)({}), ); const layerConfigSources = Layer.mergeAll( Keybindings.layer, ServerSettings.layer.pipe( Layer.provide(Layer.merge(ServerSecretStore.layer, ManagedPolicy.layerTest())), ), ).pipe( Layer.provideMerge(Layer.fresh(SqlitePersistence.layerMemory)), Layer.provideMerge( Layer.fresh( ServerConfigModule.layerTest(process.cwd(), { prefix: "t3-config-subscription-test-" }), ), ), Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)), ); it.effect.each([ "pending", "cached", "direct-pending", "direct-cached", "direct-failed", "direct-interrupted", ] as const)( "%s endpoint discovery preserves initial config and updates through discovery and ACK", (discovery) => Effect.gen(function* () { const keybindings = yield* Keybindings.Keybindings; const settings = yield* ServerSettings.ServerSettingsService; const keybindingsSubscribed = yield* Deferred.make(); const settingsSubscribed = yield* Deferred.make(); const keybindingsChanges = keybindings.subscribeChanges.pipe( Effect.tap(() => Deferred.succeed(keybindingsSubscribed, undefined)), ); const settingsChanges = settings.subscribeChanges.pipe( Effect.tap(() => Deferred.succeed(settingsSubscribed, undefined)), ); const initialKeybindings = (yield* keybindings.getSnapshot).keybindings; const discoveryStarted = yield* Deferred.make(); const finishDiscovery = yield* Deferred.make(); const remoteDiscoveryStarted = yield* Deferred.make(); const finishRemoteDiscovery = yield* Deferred.make(); const targets = [{ kind: "mdns" as const, host: "test.local" }]; const direct = discovery.startsWith("direct-"); const bound = [{ kind: "lan" as const, httpBaseUrl: "http://192.168.1.10:3773/" }]; const endpoints = [ ...bound, { kind: "tailnet" as const, httpBaseUrl: "https://test.ts.net/" }, ]; const directStarted = yield* Deferred.make(); const finishDirect = yield* Deferred.make(); const directStopped = yield* Deferred.make(); const responses = yield* Queue.unbounded(); const configRpcServices = Layer.mergeAll( layerUnusedConfigRpcServices, Layer.mock(ExternalLauncher.ExternalLauncher)({ resolveAvailableEditors: () => Deferred.succeed(discoveryStarted, undefined).pipe( Effect.andThen(Deferred.await(finishDiscovery)), Effect.as([]), ), // @effect-diagnostics-next-line effectSucceedWithVoid:off - This optional result requires undefined, not the wider void type. resolveFileManagerRevealKind: () => Effect.succeed(undefined), }), Layer.mock(ProviderRegistry.ProviderRegistry)({ getProviders: Effect.succeed([]), streamChanges: Stream.never, }), Layer.mock(UsageLimitSources.UsageLimitSources)({ streamChanges: Stream.make([]) }), Layer.mock(RemoteOpenTargets.RemoteOpenTargets)({ resolveTargets: () => Deferred.succeed(remoteDiscoveryStarted, undefined).pipe( Effect.andThen( discovery === "cached" || direct ? Effect.void : Deferred.await(finishRemoteDiscovery), ), Effect.as(targets), ), }), Layer.mock(DirectEndpoints.DirectEndpoints)({ resolveAvailable: () => Effect.succeed(discovery === "direct-cached" ? endpoints : bound), resolve: () => direct ? Deferred.succeed(directStarted, undefined).pipe( Effect.andThen( discovery === "direct-cached" ? Effect.void : Deferred.await(finishDirect), ), Effect.andThen( discovery === "direct-failed" ? Effect.die("probe failed") : Effect.succeed(endpoints), ), Effect.onExit(() => Deferred.succeed(directStopped, undefined)), ) : Effect.succeed(bound), }), Layer.mock(ManagedProjectFolders.ManagedProjectFolders)({ scratchRoot: Effect.succeedNone, namedProjectsRoot: process.cwd(), }), Layer.mock(ScratchThreadPromotion.ScratchThreadPromotion)({}), Layer.mock(ServerEnvironment.ServerEnvironment)({ getDescriptor: Effect.succeed({ environmentId: EnvironmentId.make("config-test"), label: "Config test", platform: { os: "linux", arch: "x64" }, serverVersion: "0.0.0", capabilities: { repositoryIdentity: false }, }), }), Layer.mock(EnvironmentAuth.EnvironmentAuth)({ getDescriptor: () => Effect.succeed({ policy: "loopback-browser", bootstrapMethods: [], sessionMethods: ["browser-session-cookie"], sessionCookieName: "config-test-session", }), }), // Signal subscription on either accessor so the test also fails, rather // than hangs, against a handler that subscribes after the snapshot. Layer.succeed(Keybindings.Keybindings, { ...keybindings, streamChanges: Stream.unwrap(keybindingsChanges), subscribeChanges: keybindingsChanges, }), Layer.succeed(ServerSettings.ServerSettingsService, { ...settings, streamChanges: Stream.unwrap(settingsChanges), subscribeChanges: settingsChanges, }), ); const server = yield* RpcServer.makeNoSerialization( RpcGroup.make(WsSubscribeServerConfigRpc), { ...WS_RPC_SERVER_OPTIONS, onFromServer: (response) => response._tag === "Chunk" ? Effect.forEach(response.values, (event) => Queue.offer(responses, event), { discard: true, }) : Effect.void, }, ).pipe( Effect.provide( layerWsRpc( { sessionId: AuthSessionId.make("config-test"), subject: "config-test", method: "browser-session-cookie", scopes: [], }, {}, yield* ServerBrowser.ServerBrowser, () => Effect.void, ).pipe(Layer.provide(configRpcServices)), ), ); const requestId = RpcMessage.RequestId("config"); const acknowledge = server.write(0, { _tag: "Ack", requestId }); yield* server.write(0, { _tag: "Request", id: requestId, tag: WS_METHODS.subscribeServerConfig, payload: {}, headers: Headers.empty, }); yield* Deferred.await(discoveryStarted); assert.isTrue(yield* Deferred.isDone(remoteDiscoveryStarted)); const duringDiscovery = yield* settings.updateSettings({ defaultAutoPull: true }); yield* Deferred.succeed(finishDiscovery, undefined); const snapshot = yield* Queue.take(responses); assert.equal(snapshot.type, "snapshot"); if (snapshot.type !== "snapshot") return yield* Effect.die("Missing initial snapshot"); assert.deepEqual( snapshot.config.remoteOpenTargets, discovery === "cached" || direct ? targets : [], ); assert.deepEqual( snapshot.config.directEndpoints, discovery === "direct-cached" ? endpoints : bound, ); assert.isFalse(snapshot.config.settings.defaultAutoPull); assert.deepEqual(snapshot.config.keybindings, initialKeybindings); const duringAck = yield* keybindings.upsertKeybindingRule({ key: "mod+alt+y", command: "terminal.toggle", }); yield* acknowledge; yield* Deferred.await(keybindingsSubscribed); yield* Deferred.await(settingsSubscribed); // Later updates expose a dropped buffered event as a value mismatch, not a timeout. const latestSettings = yield* settings.updateSettings({ enableProviderUpdateChecks: false }); const latestKeybindings = yield* keybindings.upsertKeybindingRule({ key: "mod+alt+z", command: "terminal.split", }); const expectedSettings = [duringDiscovery, latestSettings].map( ServerSettings.redactServerSettingsForClient, ); const expectedKeybindings = [duringAck.keybindings, latestKeybindings.keybindings]; // The two sources can interleave, but each must retain its own publication order. for (let i = 0; i < 4; i++) { const event = yield* Queue.take(responses); if (event.type === "settingsUpdated") { assert.deepEqual(event.payload.settings, expectedSettings.shift()); } else { assert.equal(event.type, "keybindingsUpdated"); if (event.type !== "keybindingsUpdated") return yield* Effect.die("Unexpected config event"); assert.deepEqual(event.payload.keybindings, expectedKeybindings.shift()); assert.deepEqual(event.payload.issues, []); } yield* acknowledge; } assert.isEmpty(expectedSettings); assert.isEmpty(expectedKeybindings); if (discovery === "pending") { yield* Deferred.await(remoteDiscoveryStarted); yield* Deferred.succeed(finishRemoteDiscovery, undefined); const resolved = yield* Queue.take(responses); assert.equal(resolved.type, "snapshot"); if (resolved.type !== "snapshot") return yield* Effect.die("Missing remote targets"); assert.deepEqual(resolved.config.remoteOpenTargets, targets); assert.deepEqual( resolved.config.settings, ServerSettings.redactServerSettingsForClient(latestSettings), ); assert.deepEqual(resolved.config.keybindings, latestKeybindings.keybindings); yield* acknowledge; } if (direct) { yield* Deferred.await(directStarted); if (discovery === "direct-pending") { yield* Deferred.succeed(finishDirect, undefined); const resolved = yield* Queue.take(responses); assert.equal(resolved.type, "snapshot"); if (resolved.type !== "snapshot") return yield* Effect.die("Missing direct endpoints"); assert.deepEqual(resolved.config.directEndpoints, endpoints); assert.deepEqual( resolved.config.settings, ServerSettings.redactServerSettingsForClient(latestSettings), ); assert.deepEqual(resolved.config.keybindings, latestKeybindings.keybindings); yield* acknowledge; } else if (discovery === "direct-failed") { yield* Deferred.succeed(finishDirect, undefined); yield* Deferred.await(directStopped); yield* settings.updateSettings({ defaultAutoPull: false }); const event = yield* Queue.take(responses); assert.equal(event.type, "settingsUpdated"); yield* acknowledge; } } yield* server.write(0, { _tag: "Interrupt", requestId, interruptors: [] }); if (direct) yield* Deferred.await(directStopped); }).pipe( Effect.provide( Layer.mergeAll( Layer.fresh(layerConfigSources), Layer.mock(PreviewAutomationBroker.PreviewAutomationBroker)({}), Layer.mock(ServerBrowser.ServerBrowser)({}), ), ), Effect.scoped, ), ); it.effect("on-demand clients get provider config without workspace snapshots", () => Effect.gen(function* () { const instanceId = ProviderInstanceId.make("claudeAgent"); const snapshotFor = (cwd: string, checkedAt: string) => ({ cwd, checkedAt, slashCommands: [{ name: `${cwd.slice(1)}-command` }], skills: [], }); const provider: ServerProvider = { instanceId, driver: ProviderDriverKind.make("claudeAgent"), enabled: true, installed: true, version: "1.0.0", status: "ready", auth: { status: "authenticated" }, checkedAt: "2026-10-25T00:00:00.000Z", models: [], slashCommands: [{ name: "machine-command" }], skills: [], workspaceSnapshots: [ snapshotFor("/a", "2026-10-25T00:00:00.000Z"), snapshotFor("/b", "2026-10-25T00:00:00.000Z"), ], }; const providerChanges = yield* Queue.unbounded>(); const pulled = yield* Queue.unbounded(); const responses = yield* Queue.unbounded(); const configRpcServices = Layer.mergeAll( layerUnusedConfigRpcServices, Layer.mock(ExternalLauncher.ExternalLauncher)({ resolveAvailableEditors: () => Effect.succeed([]), // @effect-diagnostics-next-line effectSucceedWithVoid:off - This optional result requires undefined, not the wider void type. resolveFileManagerRevealKind: () => Effect.succeed(undefined), }), Layer.mock(ProviderRegistry.ProviderRegistry)({ getProviders: Effect.succeed([provider]), streamChanges: Stream.fromQueue(providerChanges).pipe( Stream.tap(() => Queue.offer(pulled, undefined)), ), refreshWorkspaceSnapshot: () => Effect.succeed([provider]), }), Layer.mock(UsageLimitSources.UsageLimitSources)({ current: Effect.succeed([]), streamChanges: Stream.make([]), }), Layer.mock(RemoteOpenTargets.RemoteOpenTargets)({ resolveTargets: () => Effect.succeed([]), }), Layer.mock(DirectEndpoints.DirectEndpoints)({ resolve: () => Effect.succeed([]), resolveAvailable: () => Effect.succeed([]), }), Layer.mock(ManagedProjectFolders.ManagedProjectFolders)({ scratchRoot: Effect.succeedNone, namedProjectsRoot: process.cwd(), }), Layer.mock(ScratchThreadPromotion.ScratchThreadPromotion)({}), Layer.mock(ServerEnvironment.ServerEnvironment)({ getDescriptor: Effect.succeed({ environmentId: EnvironmentId.make("config-test"), label: "Config test", platform: { os: "linux", arch: "x64" }, serverVersion: "0.0.0", capabilities: { repositoryIdentity: false }, }), }), Layer.mock(EnvironmentAuth.EnvironmentAuth)({ getDescriptor: () => Effect.succeed({ policy: "loopback-browser", bootstrapMethods: [], sessionMethods: ["browser-session-cookie"], sessionCookieName: "config-test-session", }), }), ); const server = yield* RpcServer.makeNoSerialization( RpcGroup.make(WsSubscribeServerConfigRpc, WsServerRefreshProvidersRpc), { ...WS_RPC_SERVER_OPTIONS, onFromServer: (response) => response._tag === "Chunk" ? Effect.forEach(response.values, (value) => Queue.offer(responses, value), { discard: true, }) : response._tag === "Exit" && response.exit._tag === "Success" ? Queue.offer(responses, response.exit.value) : Effect.void, }, ).pipe( Effect.provide( layerWsRpc( { sessionId: AuthSessionId.make("config-test"), subject: "config-test", method: "browser-session-cookie", scopes: [], }, {}, yield* ServerBrowser.ServerBrowser, () => Effect.void, ).pipe(Layer.provide(configRpcServices)), ), ); const configRequest = RpcMessage.RequestId("config"); yield* server.write(0, { _tag: "Request", id: configRequest, tag: WS_METHODS.subscribeServerConfig, payload: { workspaceSnapshotsOnDemand: true }, headers: Headers.empty, }); const snapshot = (yield* Queue.take(responses)) as ServerConfigStreamEvent; if (snapshot.type !== "snapshot") return yield* Effect.die("Missing initial snapshot"); assert.isUndefined(snapshot.config.providers[0]?.workspaceSnapshots); yield* server.write(0, { _tag: "Ack", requestId: configRequest }); // A rescan that only touches a snapshot sends nothing; the next real change does. yield* Queue.offer(providerChanges, [ { ...provider, workspaceSnapshots: [snapshotFor("/a", "2026-10-25T00:05:00.000Z")] }, ]); yield* Queue.take(pulled); yield* TestClock.adjust("1 second"); yield* Queue.offer(providerChanges, [{ ...provider, status: "warning" }]); yield* Queue.take(pulled); yield* TestClock.adjust("1 second"); const update = (yield* Queue.take(responses)) as ServerConfigStreamEvent; if (update.type !== "providerStatuses") return yield* Effect.die("Expected provider update"); assert.equal(update.payload.providers[0]?.status, "warning"); assert.isUndefined(update.payload.providers[0]?.workspaceSnapshots); yield* server.write(0, { _tag: "Request", id: RpcMessage.RequestId("refresh"), tag: WS_METHODS.serverRefreshProviders, payload: { instanceId, cwd: "/b", workspaceSnapshotsOnDemand: true }, headers: Headers.empty, }); const refreshed = (yield* Queue.take(responses)) as { readonly providers: ReadonlyArray; }; assert.deepEqual( refreshed.providers.map((entry) => [entry.workspaceSnapshots, entry.slashCommands]), [[[snapshotFor("/b", "2026-10-25T00:00:00.000Z")], []]], ); yield* server.write(0, { _tag: "Interrupt", requestId: configRequest, interruptors: [] }); }).pipe( Effect.provide( Layer.mergeAll( Layer.fresh(layerConfigSources), Layer.mock(PreviewAutomationBroker.PreviewAutomationBroker)({}), Layer.mock(ServerBrowser.ServerBrowser)({}), ), ), Effect.scoped, ), ); it.effect("a git action that fails partway still refreshes the repository status", () => Effect.gen(function* () { const failure = new GitManagerError({ operation: "test", cwd: "/repo", detail: "push failed" }); const refreshed = yield* Deferred.make(); const exits = yield* Queue.unbounded(); const services = Layer.mergeAll( layerUnusedConfigRpcServices, Layer.mock(GitWorkflowService.GitWorkflowService)({ runStackedAction: () => Effect.fail(failure), }), Layer.mock(VcsStatusBroadcaster.VcsStatusBroadcaster)({ refreshStatus: (cwd) => Deferred.succeed(refreshed, cwd).pipe(Effect.andThen(Effect.fail(failure))), }), Layer.mock(ExternalLauncher.ExternalLauncher)({}), Layer.mock(ProviderRegistry.ProviderRegistry)({}), Layer.mock(UsageLimitSources.UsageLimitSources)({}), Layer.mock(RemoteOpenTargets.RemoteOpenTargets)({}), Layer.mock(DirectEndpoints.DirectEndpoints)({}), Layer.mock(ManagedProjectFolders.ManagedProjectFolders)({ namedProjectsRoot: "/unused" }), Layer.mock(ScratchThreadPromotion.ScratchThreadPromotion)({}), Layer.mock(ServerEnvironment.ServerEnvironment)({}), Layer.mock(EnvironmentAuth.EnvironmentAuth)({}), ); const server = yield* RpcServer.makeNoSerialization(WsRpcGroup, { ...WS_RPC_SERVER_OPTIONS, onFromServer: (response) => response._tag === "Exit" ? Queue.offer(exits, response.exit._tag) : Effect.void, }).pipe( Effect.provide( layerWsRpc( { sessionId: AuthSessionId.make("git-test"), subject: "git-test", method: "browser-session-cookie", scopes: [AuthSourceControlWriteScope], }, {}, yield* ServerBrowser.ServerBrowser, () => Effect.void, ).pipe( Layer.provide(services), Layer.merge(RpcAuthorization.layer([AuthSourceControlWriteScope])), ), ), ); yield* server.write(0, { _tag: "Request", id: RpcMessage.RequestId("git"), tag: WS_METHODS.gitRunStackedAction, payload: { actionId: "action", cwd: "/repo", action: "commit_push" }, headers: Headers.empty, }); assert.equal(yield* Queue.take(exits), "Failure"); assert.equal(yield* Deferred.await(refreshed), "/repo"); }).pipe( Effect.provide( Layer.mergeAll( Layer.fresh(layerConfigSources), Layer.mock(PreviewAutomationBroker.PreviewAutomationBroker)({}), Layer.mock(ServerBrowser.ServerBrowser)({}), ), ), Effect.scoped, ), ); // Pruned history cannot be replayed: a cursor below the boundary must resync from a snapshot. it.effect.each(["thread", "shell"] as const)( "%s resume from a cursor below the pruned boundary loads a snapshot", (scope) => Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const now = "2026-10-13T00:00:00.000Z"; const threadId = ThreadId.make("thread:pruned"); const insert = (version: number) => sql<{ sequence: number }>` INSERT INTO orchestration_events ( event_id, aggregate_kind, stream_id, stream_version, event_type, occurred_at, actor_kind, payload_json, metadata_json, application_event_version ) VALUES ( ${`pruned:${version}`}, 'thread', ${threadId}, ${version}, 'provider-session.detached', ${now}, 'server', ${JSON.stringify({ providerSessionId: "session:pruned", detachedAt: now })}, '{}', 2 ) RETURNING sequence `; const [first] = yield* insert(0); const [second] = yield* insert(1); yield* insert(2); yield* sql` INSERT INTO orchestration_v2_projection_metadata ( projection_name, schema_version, last_sequence, updated_at ) VALUES (${OrchestrationEventStore.EVENT_LOG_PRUNED_THROUGH}, 2, ${second!.sequence}, ${now}) `; const subscription = Effect.gen(function* () { const stream = scope === "thread" ? yield* subscribeOrchestrationV2Thread({ threadId, afterSequence: first!.sequence }) : yield* subscribeOrchestrationV2Shell({ afterSequence: first!.sequence }); return stream; }); const exit = yield* subscription.pipe( Effect.provideService(ThreadManagement.ThreadManagementService, { ensureLegacyTranscript: () => Effect.void, getThreadSnapshot: () => Effect.die("snapshot"), listActiveShellIndex: () => Effect.die("snapshot"), readShellSnapshot: () => Effect.die("snapshot"), listActiveSubagentThreadIds: () => Effect.die("replay"), } as unknown as ThreadManagement.ThreadManagementService["Service"]), Effect.provideService(ProjectStore.ProjectStoreV2, { listShells: () => Effect.succeed([]), } as unknown as ProjectStore.ProjectStoreV2["Service"]), Effect.provideService( ProjectService.ProjectService, {} as ProjectService.ProjectService["Service"], ), Effect.provideService(ProjectEnrichment.ProjectEnrichmentService, { subscribeChanges: PubSub.subscribe(yield* PubSub.unbounded()), } as ProjectEnrichment.ProjectEnrichmentService["Service"]), Effect.exit, ); assert.deepEqual(Exit.isFailure(exit) ? Cause.squash(exit.cause) : exit, "snapshot"); }).pipe(Effect.scoped, Effect.provide(ReplayTestLayer)), ); // A prune pass can delete replayed rows after the boundary check; the read must not be trusted. it.effect.each(["thread", "shell"] as const)( "%s resume loads a snapshot when pruning advances during the replay read", (scope) => Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const now = "2026-10-13T00:00:00.000Z"; const threadId = ThreadId.make("thread:prune-race"); const insert = (version: number) => sql<{ sequence: number }>` INSERT INTO orchestration_events ( event_id, aggregate_kind, stream_id, stream_version, event_type, occurred_at, actor_kind, payload_json, metadata_json, application_event_version ) VALUES ( ${`prune-race:${version}`}, 'thread', ${threadId}, ${version}, 'provider-session.detached', ${now}, 'server', ${JSON.stringify({ providerSessionId: "session:race", detachedAt: now })}, '{}', 2 ) RETURNING sequence `; const [first] = yield* insert(0); const [second] = yield* insert(1); yield* insert(2); const readStarted = yield* Deferred.make(); const releaseRead = yield* Deferred.make(); let gated = false; const gateReplayRead: Statement.Transformer = (statement) => { const [query] = statement.compile(); // The paged replay read (readApplicationRows), not the stats or high-water queries. if (!gated && /^\s*SELECT\s+sequence,\s+event_id,/.test(query)) { gated = true; return Deferred.succeed(readStarted, undefined).pipe( Effect.andThen(Deferred.await(releaseRead)), Effect.as(statement), ); } return Effect.succeed(statement); }; const subscription = Effect.gen(function* () { const stream = scope === "thread" ? yield* subscribeOrchestrationV2Thread({ threadId, afterSequence: first!.sequence }) : yield* subscribeOrchestrationV2Shell({ afterSequence: first!.sequence }); return stream; }).pipe( Effect.provideService(Statement.CurrentTransformer, gateReplayRead), Effect.provideService(ThreadManagement.ThreadManagementService, { ensureLegacyTranscript: () => Effect.void, getThreadSnapshot: () => Effect.die("snapshot"), listActiveShellIndex: () => Effect.die("snapshot"), readShellSnapshot: () => Effect.die("snapshot"), listActiveSubagentThreadIds: () => Effect.succeed([]), } as unknown as ThreadManagement.ThreadManagementService["Service"]), Effect.provideService(ProjectStore.ProjectStoreV2, { listShells: () => Effect.succeed([]), } as unknown as ProjectStore.ProjectStoreV2["Service"]), Effect.provideService( ProjectService.ProjectService, {} as ProjectService.ProjectService["Service"], ), Effect.provideService(ProjectEnrichment.ProjectEnrichmentService, { subscribeChanges: PubSub.subscribe(yield* PubSub.unbounded()), } as ProjectEnrichment.ProjectEnrichmentService["Service"]), Effect.exit, Effect.forkChild, ); const fiber = yield* subscription; yield* Deferred.await(readStarted); // A pass publishes its boundary, then deletes the rows it covers. yield* sql` INSERT INTO orchestration_v2_projection_metadata ( projection_name, schema_version, last_sequence, updated_at ) VALUES (${OrchestrationEventStore.EVENT_LOG_PRUNED_THROUGH}, 2, ${second!.sequence}, ${now}) `; yield* sql`DELETE FROM orchestration_events WHERE sequence = ${second!.sequence}`; yield* Deferred.succeed(releaseRead, undefined); const exit = yield* Fiber.join(fiber); assert.deepEqual(Exit.isFailure(exit) ? Cause.squash(exit.cause) : exit, "snapshot"); }).pipe(Effect.scoped, Effect.provide(ReplayTestLayer)), ); // A thread idle since before the boundary missed nothing; resuming must not resend its body. it.effect("thread resume at its own high-water below the pruned boundary skips the snapshot", () => Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const now = "2026-10-13T00:00:00.000Z"; const threadId = ThreadId.make("thread:idle"); const [last] = yield* sql<{ sequence: number }>` INSERT INTO orchestration_events ( event_id, aggregate_kind, stream_id, stream_version, event_type, occurred_at, actor_kind, payload_json, metadata_json, application_event_version ) VALUES ( 'idle:0', 'thread', ${threadId}, 0, 'provider-session.detached', ${now}, 'server', ${JSON.stringify({ providerSessionId: "session:idle", detachedAt: now })}, '{}', 2 ) RETURNING sequence `; yield* sql` INSERT INTO orchestration_v2_projection_metadata ( projection_name, schema_version, last_sequence, updated_at ) VALUES (${OrchestrationEventStore.EVENT_LOG_PRUNED_THROUGH}, 2, ${last!.sequence + 10}, ${now}) `; const exit = yield* subscribeOrchestrationV2Thread({ threadId, afterSequence: last!.sequence, }).pipe( Effect.provideService(ThreadManagement.ThreadManagementService, { ensureLegacyTranscript: () => Effect.void, getThreadSnapshot: () => Effect.die("snapshot"), streamStoredEventsFrom: () => Stream.empty, } as unknown as ThreadManagement.ThreadManagementService["Service"]), Effect.exit, ); // A snapshot load dies with "snapshot", so success means the resume replayed nothing. assert.isTrue(Exit.isSuccess(exit)); }).pipe(Effect.scoped, Effect.provide(ReplayTestLayer)), );