From f44c0a7e221dd143719d3029370339f091d0993b Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 01:56:01 -0700 Subject: [PATCH 1/9] Switch Effect imports to explicit subpaths - Replace barrel imports across server and test code - Add diagnostics overrides for Node-only scripts and probes - Tighten auth/session encoding and deterministic test fixtures --- apps/desktop/tsconfig.json | 14 +- .../OrchestrationEngineHarness.integration.ts | 27 +-- .../TestProviderAdapter.integration.ts | 14 +- .../orchestrationEngine.integration.test.ts | 17 +- .../providerService.integration.test.ts | 7 +- apps/server/scripts/acp-mock-agent.ts | 1 + apps/server/scripts/cli.ts | 14 +- .../cursor-acp-model-mismatch-probe.ts | 12 +- apps/server/src/atomicWrite.ts | 4 +- apps/server/src/attachmentPaths.ts | 11 +- apps/server/src/attachmentStore.test.ts | 1 + apps/server/src/attachmentStore.ts | 1 + .../src/auth/Layers/AuthControlPlane.test.ts | 3 +- .../src/auth/Layers/AuthControlPlane.ts | 4 +- .../Layers/BootstrapCredentialService.test.ts | 4 +- .../auth/Layers/BootstrapCredentialService.ts | 10 +- .../server/src/auth/Layers/ServerAuth.test.ts | 3 +- apps/server/src/auth/Layers/ServerAuth.ts | 5 +- .../src/auth/Layers/ServerAuthPolicy.test.ts | 3 +- .../src/auth/Layers/ServerAuthPolicy.ts | 3 +- .../src/auth/Layers/ServerSecretStore.test.ts | 7 +- .../src/auth/Layers/ServerSecretStore.ts | 6 +- .../Layers/SessionCredentialService.test.ts | 4 +- .../auth/Layers/SessionCredentialService.ts | 29 ++- .../src/auth/Services/AuthControlPlane.ts | 6 +- .../Services/BootstrapCredentialService.ts | 8 +- apps/server/src/auth/Services/ServerAuth.ts | 6 +- .../src/auth/Services/ServerAuthPolicy.ts | 4 +- .../src/auth/Services/ServerSecretStore.ts | 5 +- .../auth/Services/SessionCredentialService.ts | 8 +- apps/server/src/auth/http.ts | 4 +- apps/server/src/bin.test.ts | 7 +- apps/server/src/bootstrap.test.ts | 4 +- apps/server/src/bootstrap.ts | 8 +- apps/server/src/checkpointing/Errors.ts | 2 +- .../Layers/CheckpointDiffQuery.test.ts | 4 +- .../Layers/CheckpointDiffQuery.ts | 5 +- .../Layers/CheckpointStore.test.ts | 7 +- .../checkpointing/Layers/CheckpointStore.ts | 5 +- .../Services/CheckpointDiffQuery.ts | 4 +- .../checkpointing/Services/CheckpointStore.ts | 4 +- apps/server/src/checkpointing/Utils.ts | 2 +- apps/server/src/cli/auth.ts | 6 +- apps/server/src/cli/config.test.ts | 23 +- apps/server/src/cli/config.ts | 22 +- apps/server/src/cli/project.ts | 48 ++-- apps/server/src/cli/server.ts | 2 +- apps/server/src/cliAuthFormat.test.ts | 16 +- apps/server/src/cliAuthFormat.ts | 2 +- apps/server/src/config.ts | 8 +- .../Layers/ServerEnvironment.test.ts | 7 +- .../environment/Layers/ServerEnvironment.ts | 6 +- .../Layers/ServerEnvironmentLabel.test.ts | 3 +- .../Layers/ServerEnvironmentLabel.ts | 3 +- .../environment/Services/ServerEnvironment.ts | 4 +- apps/server/src/git/GitManager.test.ts | 62 ++++- apps/server/src/git/GitManager.ts | 28 +-- .../server/src/git/GitWorkflowService.test.ts | 3 +- apps/server/src/git/GitWorkflowService.ts | 4 +- apps/server/src/git/Utils.ts | 1 + apps/server/src/http.ts | 6 +- apps/server/src/keybindings.test.ts | 9 +- apps/server/src/keybindings.ts | 44 ++-- apps/server/src/observability/Attributes.ts | 3 +- .../src/observability/Layers/Observability.ts | 5 +- apps/server/src/observability/Metrics.test.ts | 5 +- apps/server/src/observability/Metrics.ts | 6 +- .../observability/RpcInstrumentation.test.ts | 8 +- .../src/observability/RpcInstrumentation.ts | 8 +- .../Services/BrowserTraceCollector.ts | 4 +- apps/server/src/open.test.ts | 7 +- apps/server/src/open.ts | 5 +- apps/server/src/orchestration/Errors.ts | 3 +- .../Layers/CheckpointReactor.test.ts | 82 ++++--- .../orchestration/Layers/CheckpointReactor.ts | 17 +- .../Layers/OrchestrationEngine.test.ts | 11 +- .../Layers/OrchestrationEngine.ts | 46 ++-- .../Layers/OrchestrationReactor.test.ts | 6 +- .../Layers/OrchestrationReactor.ts | 3 +- .../Layers/ProjectionPipeline.test.ts | 39 +-- .../Layers/ProjectionPipeline.ts | 7 +- .../Layers/ProjectionSnapshotQuery.test.ts | 3 +- .../Layers/ProjectionSnapshotQuery.ts | 12 +- .../Layers/ProviderCommandReactor.test.ts | 78 +++--- .../Layers/ProviderCommandReactor.ts | 10 +- .../Layers/ProviderRuntimeIngestion.test.ts | 130 +++++----- .../Layers/ProviderRuntimeIngestion.ts | 8 +- .../orchestration/Layers/RuntimeReceiptBus.ts | 5 +- .../Layers/ThreadDeletionReactor.test.ts | 4 +- .../Layers/ThreadDeletionReactor.ts | 5 +- apps/server/src/orchestration/Normalizer.ts | 4 +- .../Services/CheckpointReactor.ts | 5 +- .../Services/OrchestrationEngine.ts | 5 +- .../Services/OrchestrationReactor.ts | 5 +- .../Services/ProjectionPipeline.ts | 4 +- .../Services/ProjectionSnapshotQuery.ts | 6 +- .../Services/ProviderCommandReactor.ts | 5 +- .../Services/ProviderRuntimeIngestion.ts | 5 +- .../Services/RuntimeReceiptBus.ts | 6 +- .../Services/ThreadDeletionReactor.ts | 5 +- .../orchestration/commandInvariants.test.ts | 4 +- .../src/orchestration/commandInvariants.ts | 2 +- .../src/orchestration/decider.delete.test.ts | 4 +- .../decider.projectScripts.test.ts | 12 +- apps/server/src/orchestration/decider.ts | 3 +- apps/server/src/orchestration/http.ts | 2 +- .../src/orchestration/projector.test.ts | 12 +- apps/server/src/orchestration/projector.ts | 3 +- apps/server/src/orchestration/runtimeLayer.ts | 2 +- apps/server/src/os-jank.test.ts | 185 --------------- apps/server/src/os-jank.ts | 15 +- apps/server/src/pathExpansion.test.ts | 1 + apps/server/src/pathExpansion.ts | 1 + apps/server/src/persistence/Errors.ts | 3 +- .../persistence/Layers/AuthPairingLinks.ts | 4 +- .../src/persistence/Layers/AuthSessions.ts | 5 +- .../Layers/OrchestrationCommandReceipts.ts | 3 +- .../Layers/OrchestrationEventStore.test.ts | 9 +- .../Layers/OrchestrationEventStore.ts | 5 +- .../Layers/ProjectionCheckpoints.ts | 6 +- .../Layers/ProjectionPendingApprovals.ts | 3 +- .../persistence/Layers/ProjectionProjects.ts | 5 +- .../Layers/ProjectionRepositories.test.ts | 10 +- .../src/persistence/Layers/ProjectionState.ts | 4 +- .../Layers/ProjectionThreadActivities.ts | 5 +- .../Layers/ProjectionThreadMessages.test.ts | 3 +- .../Layers/ProjectionThreadMessages.ts | 6 +- .../Layers/ProjectionThreadProposedPlans.ts | 3 +- .../Layers/ProjectionThreadSessions.ts | 3 +- .../persistence/Layers/ProjectionThreads.ts | 5 +- .../src/persistence/Layers/ProjectionTurns.ts | 6 +- .../Layers/ProviderSessionRuntime.ts | 6 +- apps/server/src/persistence/Layers/Sqlite.ts | 5 +- .../016_CanonicalizeModelSelections.test.ts | 17 +- ...19_ProjectionSnapshotLookupIndexes.test.ts | 3 +- ...ckfillProjectionThreadShellSummary.test.ts | 3 +- ...pInvalidProjectionPendingApprovals.test.ts | 3 +- ..._CanonicalizeModelSelectionOptions.test.ts | 3 +- .../027_028_ProviderInstanceIdColumns.test.ts | 3 +- ...jectionThreadDetailOrderingIndexes.test.ts | 3 +- .../src/persistence/NodeSqliteClient.test.ts | 2 +- .../persistence/Services/AuthPairingLinks.ts | 6 +- .../src/persistence/Services/AuthSessions.ts | 6 +- .../Services/OrchestrationCommandReceipts.ts | 6 +- .../Services/OrchestrationEventStore.ts | 5 +- .../Services/ProjectionCheckpoints.ts | 6 +- .../Services/ProjectionPendingApprovals.ts | 6 +- .../Services/ProjectionProjects.ts | 6 +- .../persistence/Services/ProjectionState.ts | 6 +- .../Services/ProjectionThreadActivities.ts | 5 +- .../Services/ProjectionThreadMessages.ts | 7 +- .../Services/ProjectionThreadProposedPlans.ts | 5 +- .../Services/ProjectionThreadSessions.ts | 6 +- .../persistence/Services/ProjectionThreads.ts | 6 +- .../persistence/Services/ProjectionTurns.ts | 6 +- .../Services/ProviderSessionRuntime.ts | 6 +- apps/server/src/processRunner.ts | 15 +- .../Layers/ProjectFaviconResolver.test.ts | 5 +- .../project/Layers/ProjectFaviconResolver.ts | 5 +- .../Layers/ProjectSetupScriptRunner.test.ts | 4 +- .../Layers/ProjectSetupScriptRunner.ts | 30 ++- .../Layers/RepositoryIdentityResolver.test.ts | 6 +- .../Layers/RepositoryIdentityResolver.ts | 6 +- .../Services/ProjectFaviconResolver.ts | 4 +- .../Services/ProjectSetupScriptRunner.ts | 13 +- .../Services/RepositoryIdentityResolver.ts | 4 +- .../src/provider/Drivers/ClaudeDriver.ts | 8 +- .../src/provider/Drivers/ClaudeHome.test.ts | 3 +- .../server/src/provider/Drivers/ClaudeHome.ts | 3 +- .../src/provider/Drivers/CodexDriver.ts | 7 +- .../provider/Drivers/CodexHomeLayout.test.ts | 5 +- .../src/provider/Drivers/CodexHomeLayout.ts | 5 +- .../src/provider/Drivers/CursorDriver.ts | 7 +- .../src/provider/Drivers/OpenCodeDriver.ts | 7 +- apps/server/src/provider/Errors.ts | 2 +- .../src/provider/Layers/ClaudeAdapter.test.ts | 13 +- .../src/provider/Layers/ClaudeAdapter.ts | 101 +++++--- .../src/provider/Layers/ClaudeProvider.ts | 10 +- .../src/provider/Layers/CodexAdapter.test.ts | 46 ++-- .../src/provider/Layers/CodexAdapter.ts | 9 +- .../src/provider/Layers/CodexProvider.ts | 11 +- .../Layers/CodexSessionRuntime.test.ts | 3 +- .../provider/Layers/CodexSessionRuntime.ts | 44 ++-- .../src/provider/Layers/CursorAdapter.test.ts | 20 +- .../src/provider/Layers/CursorAdapter.ts | 44 ++-- .../provider/Layers/CursorProvider.test.ts | 18 +- .../src/provider/Layers/CursorProvider.ts | 19 +- .../provider/Layers/EventNdjsonLogger.test.ts | 3 +- .../src/provider/Layers/EventNdjsonLogger.ts | 7 +- .../provider/Layers/OpenCodeAdapter.test.ts | 20 +- .../src/provider/Layers/OpenCodeAdapter.ts | 14 +- .../provider/Layers/OpenCodeProvider.test.ts | 4 +- .../src/provider/Layers/OpenCodeProvider.ts | 9 +- .../Layers/ProviderAdapterRegistry.test.ts | 5 +- .../Layers/ProviderAdapterRegistry.ts | 3 +- .../provider/Layers/ProviderEventLoggers.ts | 4 +- .../ProviderInstanceRegistryHydration.ts | 4 +- .../ProviderInstanceRegistryLive.test.ts | 3 +- .../Layers/ProviderInstanceRegistryLive.ts | 11 +- .../provider/Layers/ProviderRegistry.test.ts | 49 ++-- .../src/provider/Layers/ProviderRegistry.ts | 10 +- .../provider/Layers/ProviderService.test.ts | 51 ++-- .../src/provider/Layers/ProviderService.ts | 57 +++-- .../Layers/ProviderSessionDirectory.test.ts | 7 +- .../Layers/ProviderSessionDirectory.ts | 8 +- .../Layers/ProviderSessionReaper.test.ts | 40 ++-- .../provider/Layers/ProviderSessionReaper.ts | 9 +- .../Layers/scopedSafeTeardown.test.ts | 4 +- .../src/provider/Layers/scopedSafeTeardown.ts | 4 +- apps/server/src/provider/ProviderDriver.ts | 4 +- .../src/provider/Services/ProviderAdapter.ts | 4 +- .../Services/ProviderAdapterRegistry.ts | 7 +- .../Services/ProviderInstanceRegistry.ts | 7 +- .../ProviderInstanceRegistryMutator.ts | 4 +- .../src/provider/Services/ProviderRegistry.ts | 5 +- .../src/provider/Services/ProviderService.ts | 5 +- .../Services/ProviderSessionDirectory.ts | 5 +- .../Services/ProviderSessionReaper.ts | 5 +- .../src/provider/Services/ServerProvider.ts | 3 +- .../src/provider/acp/AcpAdapterSupport.ts | 2 +- .../provider/acp/AcpJsonRpcConnection.test.ts | 4 +- .../src/provider/acp/AcpNativeLogging.ts | 9 +- .../src/provider/acp/AcpSessionRuntime.ts | 20 +- .../provider/acp/CursorAcpCliProbe.test.ts | 19 +- .../src/provider/acp/CursorAcpExtension.ts | 2 +- .../src/provider/acp/CursorAcpSupport.test.ts | 10 +- .../src/provider/acp/CursorAcpSupport.ts | 4 +- .../src/provider/builtInProviderCatalog.ts | 2 +- .../makeManagedServerProvider.test.ts | 7 +- .../src/provider/makeManagedServerProvider.ts | 9 +- apps/server/src/provider/opencodeRuntime.ts | 35 ++- .../src/provider/providerMaintenance.test.ts | 4 +- .../src/provider/providerMaintenance.ts | 6 +- .../providerMaintenanceCommandCoordinator.ts | 3 +- .../providerMaintenanceRunner.test.ts | 10 +- .../src/provider/providerMaintenanceRunner.ts | 26 +- apps/server/src/provider/providerSnapshot.ts | 14 +- .../src/provider/providerStatusCache.test.ts | 3 +- .../src/provider/providerStatusCache.ts | 6 +- .../testUtils/providerAdapterRegistryMock.ts | 6 +- .../provider/unavailableProviderSnapshot.ts | 8 +- apps/server/src/server.test.ts | 110 +++++---- apps/server/src/server.ts | 11 +- apps/server/src/serverLifecycleEvents.test.ts | 5 +- apps/server/src/serverLifecycleEvents.ts | 7 +- apps/server/src/serverLogger.ts | 5 +- apps/server/src/serverRuntimeStartup.test.ts | 7 +- apps/server/src/serverRuntimeStartup.ts | 33 ++- apps/server/src/serverRuntimeState.ts | 23 +- apps/server/src/serverSettings.test.ts | 7 +- apps/server/src/serverSettings.ts | 36 ++- .../src/sourceControl/AzureDevOpsCli.test.ts | 12 +- .../src/sourceControl/AzureDevOpsCli.ts | 7 +- .../AzureDevOpsSourceControlProvider.test.ts | 4 +- .../AzureDevOpsSourceControlProvider.ts | 13 +- .../src/sourceControl/BitbucketApi.test.ts | 9 +- apps/server/src/sourceControl/BitbucketApi.ts | 8 +- .../BitbucketSourceControlProvider.test.ts | 4 +- .../BitbucketSourceControlProvider.ts | 4 +- .../src/sourceControl/GitHubCli.test.ts | 9 +- apps/server/src/sourceControl/GitHubCli.ts | 7 +- .../GitHubSourceControlProvider.test.ts | 5 +- .../GitHubSourceControlProvider.ts | 6 +- .../src/sourceControl/GitLabCli.test.ts | 17 +- apps/server/src/sourceControl/GitLabCli.ts | 9 +- .../GitLabSourceControlProvider.test.ts | 4 +- .../GitLabSourceControlProvider.ts | 4 +- .../SourceControlDiscovery.test.ts | 4 +- .../sourceControl/SourceControlDiscovery.ts | 5 +- .../sourceControl/SourceControlProvider.ts | 3 +- .../SourceControlProviderDiscovery.ts | 3 +- .../SourceControlProviderRegistry.test.ts | 5 +- .../SourceControlProviderRegistry.ts | 7 +- .../SourceControlRepositoryService.test.ts | 4 +- .../SourceControlRepositoryService.ts | 7 +- .../sourceControl/azureDevOpsPullRequests.ts | 7 +- .../sourceControl/bitbucketPullRequests.ts | 4 +- .../src/sourceControl/gitHubPullRequests.ts | 7 +- .../src/sourceControl/gitLabMergeRequests.ts | 7 +- apps/server/src/startupAccess.ts | 2 +- .../src/stream/collectUint8StreamText.test.ts | 3 +- .../src/stream/collectUint8StreamText.ts | 3 +- apps/server/src/telemetry/Identify.ts | 6 +- .../telemetry/Layers/AnalyticsService.test.ts | 4 +- .../src/telemetry/Layers/AnalyticsService.ts | 6 +- .../telemetry/Services/AnalyticsService.ts | 4 +- apps/server/src/terminal/Layers/BunPTY.ts | 3 +- .../src/terminal/Layers/Manager.test.ts | 67 +++--- apps/server/src/terminal/Layers/Manager.ts | 101 ++++---- .../src/terminal/Layers/NodePTY.test.ts | 4 +- apps/server/src/terminal/Layers/NodePTY.ts | 5 +- apps/server/src/terminal/Services/Manager.ts | 3 +- apps/server/src/terminal/Services/PTY.ts | 4 +- .../ClaudeTextGeneration.test.ts | 7 +- .../textGeneration/ClaudeTextGeneration.ts | 13 +- .../CodexTextGeneration.test.ts | 20 +- .../src/textGeneration/CodexTextGeneration.ts | 11 +- .../CursorTextGeneration.test.ts | 25 +- .../textGeneration/CursorTextGeneration.ts | 5 +- .../OpenCodeTextGeneration.test.ts | 5 +- .../textGeneration/OpenCodeTextGeneration.ts | 6 +- .../src/textGeneration/TextGeneration.test.ts | 5 +- .../src/textGeneration/TextGeneration.ts | 4 +- .../textGeneration/TextGenerationPolicy.ts | 2 +- .../textGeneration/TextGenerationPrompts.ts | 2 +- .../src/textGeneration/TextGenerationUtils.ts | 2 +- apps/server/src/vcs/GitVcsDriver.test.ts | 6 +- apps/server/src/vcs/GitVcsDriver.ts | 6 +- apps/server/src/vcs/GitVcsDriverCore.test.ts | 7 +- apps/server/src/vcs/GitVcsDriverCore.ts | 34 ++- apps/server/src/vcs/VcsDriver.ts | 3 +- apps/server/src/vcs/VcsDriverRegistry.test.ts | 3 +- apps/server/src/vcs/VcsDriverRegistry.ts | 7 +- apps/server/src/vcs/VcsProcess.ts | 9 +- apps/server/src/vcs/VcsProjectConfig.test.ts | 6 +- apps/server/src/vcs/VcsProjectConfig.ts | 7 +- .../src/vcs/VcsProvisioningService.test.ts | 5 +- apps/server/src/vcs/VcsProvisioningService.ts | 4 +- .../src/vcs/VcsStatusBroadcaster.test.ts | 15 +- apps/server/src/vcs/VcsStatusBroadcaster.ts | 50 ++-- .../vcs/testing/VcsDriverContractHarness.ts | 18 +- .../workspace/Layers/WorkspaceEntries.test.ts | 45 +++- .../src/workspace/Layers/WorkspaceEntries.ts | 9 +- .../Layers/WorkspaceFileSystem.test.ts | 5 +- .../workspace/Layers/WorkspaceFileSystem.ts | 5 +- .../workspace/Layers/WorkspacePaths.test.ts | 5 +- .../src/workspace/Layers/WorkspacePaths.ts | 5 +- .../workspace/Services/WorkspaceEntries.ts | 5 +- .../workspace/Services/WorkspaceFileSystem.ts | 5 +- .../src/workspace/Services/WorkspacePaths.ts | 5 +- apps/server/src/ws.ts | 19 +- apps/server/tsconfig.json | 16 +- .../web/src/components/BranchToolbar.logic.ts | 2 +- apps/web/src/components/ChatView.browser.tsx | 2 +- apps/web/src/components/ChatView.logic.ts | 2 +- apps/web/src/components/CommandPalette.tsx | 2 +- apps/web/src/components/GitActionsControl.tsx | 2 +- .../settings/ConnectionsSettings.tsx | 2 +- .../settings/DiagnosticsSettings.tsx | 3 +- .../settings/ProviderSettingsForm.tsx | 2 +- .../settings/SettingsPanels.browser.tsx | 3 +- .../components/settings/SettingsPanels.tsx | 2 +- .../settings/SourceControlSettings.tsx | 2 +- .../components/settings/providerDriverMeta.ts | 2 +- apps/web/src/components/ui/sidebar.tsx | 2 +- apps/web/src/environments/primary/auth.ts | 3 +- apps/web/src/hooks/useSettings.ts | 2 +- apps/web/src/lib/gitStatusState.ts | 2 +- apps/web/src/lib/processDiagnosticsState.ts | 4 +- apps/web/src/lib/projectScriptKeybindings.ts | 2 +- apps/web/src/lib/providerReactQuery.ts | 3 +- .../src/lib/sourceControlDiscoveryState.ts | 2 +- apps/web/src/lib/traceDiagnosticsState.ts | 4 +- apps/web/src/observability/clientTracing.ts | 6 +- apps/web/src/projectScripts.ts | 2 +- apps/web/src/rpc/protocol.ts | 5 +- apps/web/src/rpc/wsRpcClient.ts | 3 +- apps/web/src/rpc/wsTransport.test.ts | 2 +- apps/web/src/rpc/wsTransport.ts | 20 +- apps/web/src/store.ts | 2 +- apps/web/test/wsRpcHarness.ts | 6 +- apps/web/tsconfig.json | 16 +- bun.lock | 1 + packages/client-runtime/tsconfig.json | 15 +- packages/contracts/src/model.ts | 4 +- packages/contracts/src/terminal.test.ts | 16 +- packages/contracts/src/terminal.ts | 7 +- packages/contracts/tsconfig.json | 15 +- .../examples/cursor-acp-client.example.ts | 6 +- packages/effect-acp/tsconfig.json | 15 +- .../test/examples/codex-app-server-probe.ts | 6 +- .../effect-codex-app-server/tsconfig.json | 15 +- packages/shared/package.json | 1 + packages/shared/src/logging.ts | 1 + packages/shared/src/observability.test.ts | 222 +++++++++--------- packages/shared/src/shell.ts | 1 + packages/shared/tsconfig.json | 15 +- packages/ssh/tsconfig.json | 15 +- packages/tailscale/tsconfig.json | 14 +- scripts/apply-web-brand-assets.ts | 5 +- scripts/build-desktop-artifact.test.ts | 4 +- scripts/build-desktop-artifact.ts | 22 +- scripts/dev-runner.test.ts | 3 +- scripts/dev-runner.ts | 10 +- scripts/merge-update-manifests.test.ts | 4 +- scripts/merge-update-manifests.ts | 6 +- scripts/mock-update-server.test.ts | 7 +- scripts/mock-update-server.ts | 10 +- scripts/notify-discord-release.ts | 8 +- scripts/release-smoke.ts | 5 +- scripts/resolve-nightly-release.ts | 10 +- scripts/resolve-previous-release-tag.ts | 8 +- scripts/tsconfig.json | 7 +- .../update-release-package-versions.test.ts | 8 +- scripts/update-release-package-versions.ts | 9 +- tsconfig.base.json | 35 ++- 396 files changed, 2694 insertions(+), 1708 deletions(-) delete mode 100644 apps/server/src/os-jank.test.ts diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 6fee28207ab..ff3e4cd0f38 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -3,19 +3,7 @@ "compilerOptions": { "composite": true, "types": ["node", "electron"], - "lib": ["ESNext", "DOM", "esnext.disposable"], - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node", "effect"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] + "lib": ["ESNext", "DOM", "esnext.disposable"] }, "include": ["src", "tsdown.config.ts"] } diff --git a/apps/server/integration/OrchestrationEngineHarness.integration.ts b/apps/server/integration/OrchestrationEngineHarness.integration.ts index 8945294555a..57546f01d94 100644 --- a/apps/server/integration/OrchestrationEngineHarness.integration.ts +++ b/apps/server/integration/OrchestrationEngineHarness.integration.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import { execFileSync } from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices"; @@ -8,20 +9,18 @@ import { type OrchestrationEvent, type OrchestrationThread, } from "@t3tools/contracts"; -import { - Effect, - Exit, - FileSystem, - Layer, - ManagedRuntime, - Option, - Path, - Ref, - Schedule, - Schema, - Scope, - Stream, -} from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Ref from "effect/Ref"; +import * as Schedule from "effect/Schedule"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { CheckpointStoreLive } from "../src/checkpointing/Layers/CheckpointStore.ts"; import { CheckpointStore } from "../src/checkpointing/Services/CheckpointStore.ts"; diff --git a/apps/server/integration/TestProviderAdapter.integration.ts b/apps/server/integration/TestProviderAdapter.integration.ts index 9ed58c0a5b8..28873c51f97 100644 --- a/apps/server/integration/TestProviderAdapter.integration.ts +++ b/apps/server/integration/TestProviderAdapter.integration.ts @@ -1,5 +1,3 @@ -import { randomUUID } from "node:crypto"; - import { ApprovalRequestId, EventId, @@ -12,7 +10,10 @@ import { TurnId, ProviderDriverKind, } from "@t3tools/contracts"; -import { Effect, Queue, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Queue from "effect/Queue"; +import * as Random from "effect/Random"; +import * as Stream from "effect/Stream"; import { ProviderAdapterSessionNotFoundError, @@ -202,7 +203,7 @@ interface MakeTestProviderAdapterHarnessOptions { } function nowIso(): string { - return new Date().toISOString(); + return "2026-01-01T00:00:00.000Z"; } function sessionNotFound( @@ -308,10 +309,9 @@ export const makeTestProviderAdapterHarness = (options?: MakeTestProviderAdapter for (const fixtureEvent of response.events) { const rawEvent: Record = { ...(fixtureEvent as Record), - eventId: randomUUID(), + eventId: yield* Random.nextUUIDv4, provider, sessionId: RuntimeSessionId.make(String(input.threadId)), - createdAt: nowIso(), }; rawEvent.threadId = state.snapshot.threadId; if (Object.hasOwn(rawEvent, "turnId")) { @@ -366,7 +366,7 @@ export const makeTestProviderAdapterHarness = (options?: MakeTestProviderAdapter if (deferredTurnCompletedEvents.length === 0) { yield* emit({ type: "turn.completed", - eventId: EventId.make(randomUUID()), + eventId: EventId.make(yield* Random.nextUUIDv4), provider, createdAt: nowIso(), threadId: state.snapshot.threadId, diff --git a/apps/server/integration/orchestrationEngine.integration.test.ts b/apps/server/integration/orchestrationEngine.integration.test.ts index 6bd94819839..e79897c740e 100644 --- a/apps/server/integration/orchestrationEngine.integration.test.ts +++ b/apps/server/integration/orchestrationEngine.integration.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import path from "node:path"; @@ -17,7 +18,10 @@ import { ProviderInstanceId, } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; -import { Effect, Option, Schema } from "effect"; +import * as Clock from "effect/Clock"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts"; import { @@ -47,7 +51,7 @@ const CODEX_PROVIDER = ProviderDriverKind.make("codex"); const CLAUDE_AGENT_PROVIDER = ProviderDriverKind.make("claudeAgent"); function nowIso() { - return new Date().toISOString(); + return "2026-05-01T00:00:00.000Z"; } class IntegrationWaitTimeoutError extends Schema.TaggedErrorClass()( @@ -64,14 +68,14 @@ function waitForSync( timeoutMs = 10_000, ): Effect.Effect { return Effect.gen(function* () { - const deadline = Date.now() + timeoutMs; + const deadline = (yield* Clock.currentTimeMillis) + timeoutMs; while (true) { const value = read(); if (predicate(value)) { return value; } - if (Date.now() >= deadline) { + if ((yield* Clock.currentTimeMillis) >= deadline) { return yield* Effect.die(new IntegrationWaitTimeoutError({ description })); } yield* Effect.sleep(10); @@ -156,6 +160,7 @@ const startTurn = (input: { readonly messageId: string; readonly text: string; readonly modelSelection?: ModelSelection; + readonly createdAt?: string; }) => input.harness.engine.dispatch({ type: "thread.turn.start", @@ -174,7 +179,7 @@ const startTurn = (input: { : {}), interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", - createdAt: nowIso(), + createdAt: input.createdAt ?? nowIso(), }); it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () => @@ -755,6 +760,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git commandId: "cmd-turn-start-revert-1", messageId: "msg-user-revert-1", text: "First edit", + createdAt: "2026-02-24T10:04:59.900Z", }); yield* harness.waitForThread( @@ -813,6 +819,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git commandId: "cmd-turn-start-revert-2", messageId: "msg-user-revert-2", text: "Second edit", + createdAt: "2026-02-24T10:05:00.900Z", }); yield* harness.waitForThread( diff --git a/apps/server/integration/providerService.integration.test.ts b/apps/server/integration/providerService.integration.test.ts index 60fe01d4c20..57e93c5acdd 100644 --- a/apps/server/integration/providerService.integration.test.ts +++ b/apps/server/integration/providerService.integration.test.ts @@ -3,7 +3,12 @@ import { ProviderDriverKind, ProviderInstanceId, ThreadId } from "@t3tools/contr import { DEFAULT_SERVER_SETTINGS } from "@t3tools/contracts/settings"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, assert } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path, Queue, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Queue from "effect/Queue"; +import * as Stream from "effect/Stream"; import { ProviderAdapterRegistry } from "../src/provider/Services/ProviderAdapterRegistry.ts"; import { makeAdapterRegistryMock } from "../src/provider/testUtils/providerAdapterRegistryMock.ts"; diff --git a/apps/server/scripts/acp-mock-agent.ts b/apps/server/scripts/acp-mock-agent.ts index 26ffa084a83..e704b8d8a25 100644 --- a/apps/server/scripts/acp-mock-agent.ts +++ b/apps/server/scripts/acp-mock-agent.ts @@ -1,4 +1,5 @@ #!/usr/bin/env bun +// @effect-diagnostics nodeBuiltinImport:off import { appendFileSync } from "node:fs"; import * as Effect from "effect/Effect"; diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts index efaa2b3b6cf..fc9b621691b 100644 --- a/apps/server/scripts/cli.ts +++ b/apps/server/scripts/cli.ts @@ -1,8 +1,13 @@ #!/usr/bin/env node - import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Data, Effect, FileSystem, Logger, Option, Path } from "effect"; +import * as Data from "effect/Data"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Logger from "effect/Logger"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { Command, Flag } from "effect/unstable/cli"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; @@ -228,7 +233,10 @@ const publishCmd = Command.make( const original = yield* fs.readFileString(packageJsonPath); yield* fs.writeFileString(backupPath, original); - yield* fs.writeFileString(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`); + yield* fs.writeFileString( + packageJsonPath, + `${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(pkg)}\n`, + ); yield* Effect.log("[cli] Prepared package.json for publish"); const iconBackups = yield* applyPublishIconOverrides(repoRoot, serverDir); diff --git a/apps/server/scripts/cursor-acp-model-mismatch-probe.ts b/apps/server/scripts/cursor-acp-model-mismatch-probe.ts index f3152ab1786..04c2321870e 100644 --- a/apps/server/scripts/cursor-acp-model-mismatch-probe.ts +++ b/apps/server/scripts/cursor-acp-model-mismatch-probe.ts @@ -1,6 +1,8 @@ +// @effect-diagnostics nodeBuiltinImport:off import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; import process from "node:process"; import readline from "node:readline"; +import * as NodeTimers from "node:timers"; type JsonPrimitive = null | boolean | number | string; type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; @@ -114,7 +116,8 @@ function matchesKeyword(option: SessionConfigOption, keyword: string): boolean { function sleep(ms: number) { return new Promise((resolve) => { - setTimeout(resolve, ms); + // @effect-diagnostics-next-line globalTimers:off - Standalone Node probe script, not an Effect runtime test. + NodeTimers.setTimeout(resolve, ms); }); } @@ -177,7 +180,8 @@ class JsonRpcChild { const id = this.nextId++; const responsePromise = new Promise((resolve, reject) => { - const timeout = setTimeout(() => { + // @effect-diagnostics-next-line globalTimers:off - Standalone Node probe script request timeout. + const timeout = NodeTimers.setTimeout(() => { this.pending.delete(id); reject(new Error(`Timed out waiting for ${method} response after ${timeoutMs}ms.`)); }, timeoutMs); @@ -185,11 +189,11 @@ class JsonRpcChild { this.pending.set(id, { method, resolve: (value) => { - clearTimeout(timeout); + NodeTimers.clearTimeout(timeout); resolve(value); }, reject: (error) => { - clearTimeout(timeout); + NodeTimers.clearTimeout(timeout); reject(error); }, }); diff --git a/apps/server/src/atomicWrite.ts b/apps/server/src/atomicWrite.ts index 431b2f4a01c..764b6781919 100644 --- a/apps/server/src/atomicWrite.ts +++ b/apps/server/src/atomicWrite.ts @@ -1,4 +1,6 @@ -import { Effect, FileSystem, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import * as Random from "effect/Random"; export const writeFileStringAtomically = (input: { diff --git a/apps/server/src/attachmentPaths.ts b/apps/server/src/attachmentPaths.ts index cd7e1faec85..8c6999a7341 100644 --- a/apps/server/src/attachmentPaths.ts +++ b/apps/server/src/attachmentPaths.ts @@ -1,9 +1,10 @@ -import path from "node:path"; +// @effect-diagnostics nodeBuiltinImport:off +import NodePath from "node:path"; export const ATTACHMENTS_ROUTE_PREFIX = "/attachments"; export function normalizeAttachmentRelativePath(rawRelativePath: string): string | null { - const normalized = path.normalize(rawRelativePath).replace(/^[/\\]+/, ""); + const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, ""); if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) { return null; } @@ -19,9 +20,9 @@ export function resolveAttachmentRelativePath(input: { return null; } - const attachmentsRoot = path.resolve(input.attachmentsDir); - const filePath = path.resolve(path.join(attachmentsRoot, normalizedRelativePath)); - if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) { + const attachmentsRoot = NodePath.resolve(input.attachmentsDir); + const filePath = NodePath.resolve(NodePath.join(attachmentsRoot, normalizedRelativePath)); + if (!filePath.startsWith(`${attachmentsRoot}${NodePath.sep}`)) { return null; } return filePath; diff --git a/apps/server/src/attachmentStore.test.ts b/apps/server/src/attachmentStore.test.ts index e92c3d219d3..842667e61ba 100644 --- a/apps/server/src/attachmentStore.test.ts +++ b/apps/server/src/attachmentStore.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import os from "node:os"; import path from "node:path"; diff --git a/apps/server/src/attachmentStore.ts b/apps/server/src/attachmentStore.ts index aa85b8c51a3..1e8dd93f603 100644 --- a/apps/server/src/attachmentStore.ts +++ b/apps/server/src/attachmentStore.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import { randomUUID } from "node:crypto"; import { existsSync } from "node:fs"; diff --git a/apps/server/src/auth/Layers/AuthControlPlane.test.ts b/apps/server/src/auth/Layers/AuthControlPlane.test.ts index 280fbc1604f..23ea4e97958 100644 --- a/apps/server/src/auth/Layers/AuthControlPlane.test.ts +++ b/apps/server/src/auth/Layers/AuthControlPlane.test.ts @@ -1,6 +1,7 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import type { ServerConfigShape } from "../../config.ts"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/AuthControlPlane.ts b/apps/server/src/auth/Layers/AuthControlPlane.ts index 1bf4909e82c..aea07fd286f 100644 --- a/apps/server/src/auth/Layers/AuthControlPlane.ts +++ b/apps/server/src/auth/Layers/AuthControlPlane.ts @@ -1,5 +1,7 @@ import type { AuthClientSession, AuthPairingLink } from "@t3tools/contracts"; -import { DateTime, Effect, Layer } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { BootstrapCredentialServiceLive } from "./BootstrapCredentialService.ts"; import { ServerSecretStoreLive } from "./ServerSecretStore.ts"; diff --git a/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts b/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts index ec110ee96fb..08f57874b23 100644 --- a/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts +++ b/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts @@ -1,6 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Duration, Effect, Layer } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { TestClock } from "effect/testing"; import type { ServerConfigShape } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/BootstrapCredentialService.ts b/apps/server/src/auth/Layers/BootstrapCredentialService.ts index 5539f62c708..77d865eca68 100644 --- a/apps/server/src/auth/Layers/BootstrapCredentialService.ts +++ b/apps/server/src/auth/Layers/BootstrapCredentialService.ts @@ -1,6 +1,12 @@ import type { AuthPairingLink } from "@t3tools/contracts"; -import { DateTime, Duration, Effect, Layer, PubSub, Ref, Stream } from "effect"; -import { Option } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Stream from "effect/Stream"; +import * as Option from "effect/Option"; import { ServerConfig } from "../../config.ts"; import { AuthPairingLinkRepositoryLive } from "../../persistence/Layers/AuthPairingLinks.ts"; diff --git a/apps/server/src/auth/Layers/ServerAuth.test.ts b/apps/server/src/auth/Layers/ServerAuth.test.ts index 0c3d71cc9fd..748f4ded731 100644 --- a/apps/server/src/auth/Layers/ServerAuth.test.ts +++ b/apps/server/src/auth/Layers/ServerAuth.test.ts @@ -1,6 +1,7 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import type { ServerConfigShape } from "../../config.ts"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/ServerAuth.ts b/apps/server/src/auth/Layers/ServerAuth.ts index cb1c6fa4c41..238475aca37 100644 --- a/apps/server/src/auth/Layers/ServerAuth.ts +++ b/apps/server/src/auth/Layers/ServerAuth.ts @@ -6,7 +6,10 @@ import { type AuthSessionState, type AuthWebSocketTokenResult, } from "@t3tools/contracts"; -import { DateTime, Effect, Layer, Option } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; import { AuthControlPlane } from "../Services/AuthControlPlane.ts"; diff --git a/apps/server/src/auth/Layers/ServerAuthPolicy.test.ts b/apps/server/src/auth/Layers/ServerAuthPolicy.test.ts index 13ca0233ee1..fcc56686d8f 100644 --- a/apps/server/src/auth/Layers/ServerAuthPolicy.test.ts +++ b/apps/server/src/auth/Layers/ServerAuthPolicy.test.ts @@ -1,6 +1,7 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import type { ServerConfigShape } from "../../config.ts"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/ServerAuthPolicy.ts b/apps/server/src/auth/Layers/ServerAuthPolicy.ts index 43735b47618..2c000bc1883 100644 --- a/apps/server/src/auth/Layers/ServerAuthPolicy.ts +++ b/apps/server/src/auth/Layers/ServerAuthPolicy.ts @@ -1,5 +1,6 @@ import type { ServerAuthDescriptor } from "@t3tools/contracts"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { ServerConfig } from "../../config.ts"; import { ServerAuthPolicy, type ServerAuthPolicyShape } from "../Services/ServerAuthPolicy.ts"; diff --git a/apps/server/src/auth/Layers/ServerSecretStore.test.ts b/apps/server/src/auth/Layers/ServerSecretStore.test.ts index 7e6352eec25..30b5a9d35ed 100644 --- a/apps/server/src/auth/Layers/ServerSecretStore.test.ts +++ b/apps/server/src/auth/Layers/ServerSecretStore.test.ts @@ -1,6 +1,11 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Cause, Deferred, Effect, FileSystem, Layer, Ref } from "effect"; +import * as Cause from "effect/Cause"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Ref from "effect/Ref"; import * as PlatformError from "effect/PlatformError"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/ServerSecretStore.ts b/apps/server/src/auth/Layers/ServerSecretStore.ts index c8acf11babe..d80242280bd 100644 --- a/apps/server/src/auth/Layers/ServerSecretStore.ts +++ b/apps/server/src/auth/Layers/ServerSecretStore.ts @@ -1,6 +1,10 @@ import * as Crypto from "node:crypto"; -import { Effect, FileSystem, Layer, Path, Predicate } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Predicate from "effect/Predicate"; import * as PlatformError from "effect/PlatformError"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/SessionCredentialService.test.ts b/apps/server/src/auth/Layers/SessionCredentialService.test.ts index bafd9c85c2c..71cd092ea8c 100644 --- a/apps/server/src/auth/Layers/SessionCredentialService.test.ts +++ b/apps/server/src/auth/Layers/SessionCredentialService.test.ts @@ -1,6 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Duration, Effect, Layer } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { TestClock } from "effect/testing"; import type { ServerConfigShape } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/SessionCredentialService.ts b/apps/server/src/auth/Layers/SessionCredentialService.ts index 5ff4bbffff2..cbc491a6b05 100644 --- a/apps/server/src/auth/Layers/SessionCredentialService.ts +++ b/apps/server/src/auth/Layers/SessionCredentialService.ts @@ -1,6 +1,14 @@ import { AuthSessionId, type AuthClientMetadata, type AuthClientSession } from "@t3tools/contracts"; -import { Clock, DateTime, Duration, Effect, Layer, PubSub, Ref, Schema, Stream } from "effect"; -import { Option } from "effect"; +import * as Clock from "effect/Clock"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; +import * as Option from "effect/Option"; import { ServerConfig } from "../../config.ts"; import { AuthSessionRepositoryLive } from "../../persistence/Layers/AuthSessions.ts"; @@ -192,6 +200,7 @@ export const makeSessionCredentialService = Effect.gen(function* () { ), ); + const encodeClaims = Schema.encodeEffect(Schema.fromJsonString(SessionClaims)); const issue: SessionCredentialServiceShape["issue"] = (input) => Effect.gen(function* () { const sessionId = AuthSessionId.make(crypto.randomUUID()); @@ -209,7 +218,13 @@ export const makeSessionCredentialService = Effect.gen(function* () { iat: issuedAt.epochMilliseconds, exp: expiresAt.epochMilliseconds, }; - const encodedPayload = base64UrlEncode(JSON.stringify(claims)); + + const encodedPayload = yield* encodeClaims(claims).pipe( + Effect.map(base64UrlEncode), + Effect.mapError( + (cause) => new SessionCredentialError({ message: "Failed to encode claims", cause }), + ), + ); const signature = signPayload(encodedPayload, signingSecret); const client = input?.client ?? createDefaultClientMetadata(); yield* authSessions.create({ @@ -317,6 +332,7 @@ export const makeSessionCredentialService = Effect.gen(function* () { ), ); + const encodeWsClaims = Schema.encodeEffect(Schema.fromJsonString(WebSocketClaims)); const issueWebSocketToken: SessionCredentialServiceShape["issueWebSocketToken"] = ( sessionId, input, @@ -333,7 +349,12 @@ export const makeSessionCredentialService = Effect.gen(function* () { iat: issuedAt.epochMilliseconds, exp: expiresAt.epochMilliseconds, }; - const encodedPayload = base64UrlEncode(JSON.stringify(claims)); + const encodedPayload = yield* encodeWsClaims(claims).pipe( + Effect.map(base64UrlEncode), + Effect.mapError( + (cause) => new SessionCredentialError({ message: "Failed to encode claims", cause }), + ), + ); const signature = signPayload(encodedPayload, signingSecret); return { token: `${encodedPayload}.${signature}`, diff --git a/apps/server/src/auth/Services/AuthControlPlane.ts b/apps/server/src/auth/Services/AuthControlPlane.ts index 4b3cf474fea..b5e67639a65 100644 --- a/apps/server/src/auth/Services/AuthControlPlane.ts +++ b/apps/server/src/auth/Services/AuthControlPlane.ts @@ -4,7 +4,11 @@ import type { AuthPairingLink, AuthSessionId, } from "@t3tools/contracts"; -import { Data, DateTime, Duration, Effect, Context } from "effect"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Context from "effect/Context"; import type { SessionRole } from "./SessionCredentialService.ts"; export const DEFAULT_SESSION_SUBJECT = "cli-issued-session"; diff --git a/apps/server/src/auth/Services/BootstrapCredentialService.ts b/apps/server/src/auth/Services/BootstrapCredentialService.ts index bcb6119a22e..70dd1d5aead 100644 --- a/apps/server/src/auth/Services/BootstrapCredentialService.ts +++ b/apps/server/src/auth/Services/BootstrapCredentialService.ts @@ -1,6 +1,10 @@ import type { AuthPairingLink, ServerAuthBootstrapMethod } from "@t3tools/contracts"; -import { Data, DateTime, Duration, Context } from "effect"; -import type { Effect, Stream } from "effect"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; export type BootstrapCredentialRole = "owner" | "client"; diff --git a/apps/server/src/auth/Services/ServerAuth.ts b/apps/server/src/auth/Services/ServerAuth.ts index 07be98269bf..3ea93b77f94 100644 --- a/apps/server/src/auth/Services/ServerAuth.ts +++ b/apps/server/src/auth/Services/ServerAuth.ts @@ -12,8 +12,10 @@ import type { ServerAuthSessionMethod, AuthWebSocketTokenResult, } from "@t3tools/contracts"; -import { Data, DateTime, Context } from "effect"; -import type { Effect } from "effect"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; import type { SessionRole } from "./SessionCredentialService.ts"; diff --git a/apps/server/src/auth/Services/ServerAuthPolicy.ts b/apps/server/src/auth/Services/ServerAuthPolicy.ts index 530d776998c..5d9ef68cf95 100644 --- a/apps/server/src/auth/Services/ServerAuthPolicy.ts +++ b/apps/server/src/auth/Services/ServerAuthPolicy.ts @@ -1,6 +1,6 @@ import type { ServerAuthDescriptor } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; export interface ServerAuthPolicyShape { readonly getDescriptor: () => Effect.Effect; diff --git a/apps/server/src/auth/Services/ServerSecretStore.ts b/apps/server/src/auth/Services/ServerSecretStore.ts index 7d97b4c3a10..f5c6f6dfef4 100644 --- a/apps/server/src/auth/Services/ServerSecretStore.ts +++ b/apps/server/src/auth/Services/ServerSecretStore.ts @@ -1,5 +1,6 @@ -import { Data, Context } from "effect"; -import type { Effect } from "effect"; +import * as Data from "effect/Data"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; export class SecretStoreError extends Data.TaggedError("SecretStoreError")<{ readonly message: string; diff --git a/apps/server/src/auth/Services/SessionCredentialService.ts b/apps/server/src/auth/Services/SessionCredentialService.ts index 3d72b5a636c..7dc049c910e 100644 --- a/apps/server/src/auth/Services/SessionCredentialService.ts +++ b/apps/server/src/auth/Services/SessionCredentialService.ts @@ -4,8 +4,12 @@ import type { AuthSessionId, ServerAuthSessionMethod, } from "@t3tools/contracts"; -import { Data, DateTime, Duration, Context } from "effect"; -import type { Effect, Stream } from "effect"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; export type SessionRole = "owner" | "client"; diff --git a/apps/server/src/auth/http.ts b/apps/server/src/auth/http.ts index 76c14646e98..fff1227a4fc 100644 --- a/apps/server/src/auth/http.ts +++ b/apps/server/src/auth/http.ts @@ -6,7 +6,9 @@ import { AuthRevokePairingLinkInput, type AuthWebSocketTokenResult, } from "@t3tools/contracts"; -import { DateTime, Effect, Schema } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { AuthError, ServerAuth } from "./Services/ServerAuth.ts"; diff --git a/apps/server/src/bin.test.ts b/apps/server/src/bin.test.ts index fa4572cc909..6e5240eb34c 100644 --- a/apps/server/src/bin.test.ts +++ b/apps/server/src/bin.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics-next-line nodeBuiltinImport:off - NodeHttpServer.layer takes `NodeHttp.createServer` as arg import * as NodeHttp from "node:http"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; @@ -138,7 +139,7 @@ const withLiveProjectCliServer = (baseDir: string, run: () => Effect.Ef } yield* persistServerRuntimeState({ path: config.serverRuntimeStatePath, - state: makePersistedServerRuntimeState({ + state: yield* makePersistedServerRuntimeState({ config, port: address.port, }), @@ -179,6 +180,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { const createdOutput = yield* captureStdout( runCli(["auth", "pairing", "create", "--base-dir", baseDir, "--json"]), ); + // @effect-diagnostics-next-line preferSchemaOverJson:off const created = JSON.parse(createdOutput.output) as { readonly id: string; readonly credential: string; @@ -186,6 +188,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { const listedOutput = yield* captureStdout( runCli(["auth", "pairing", "list", "--base-dir", baseDir, "--json"]), ); + // @effect-diagnostics-next-line preferSchemaOverJson:off const listed = JSON.parse(listedOutput.output) as ReadonlyArray<{ readonly id: string; readonly credential?: string; @@ -207,6 +210,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { const issuedOutput = yield* captureStdout( runCli(["auth", "session", "issue", "--base-dir", baseDir, "--json"]), ); + // @effect-diagnostics-next-line preferSchemaOverJson:off const issued = JSON.parse(issuedOutput.output) as { readonly sessionId: string; readonly token: string; @@ -215,6 +219,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { const listedOutput = yield* captureStdout( runCli(["auth", "session", "list", "--base-dir", baseDir, "--json"]), ); + // @effect-diagnostics-next-line preferSchemaOverJson:off const listed = JSON.parse(listedOutput.output) as ReadonlyArray<{ readonly sessionId: string; readonly token?: string; diff --git a/apps/server/src/bootstrap.test.ts b/apps/server/src/bootstrap.test.ts index e4cdfab1dbb..17128ddad0a 100644 --- a/apps/server/src/bootstrap.test.ts +++ b/apps/server/src/bootstrap.test.ts @@ -1,9 +1,11 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as NFS from "node:fs"; import * as path from "node:path"; import { execFileSync, spawn } from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; -import { FileSystem, Schema } from "effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Schema from "effect/Schema"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; diff --git a/apps/server/src/bootstrap.ts b/apps/server/src/bootstrap.ts index 0fb13522686..9ad6328798d 100644 --- a/apps/server/src/bootstrap.ts +++ b/apps/server/src/bootstrap.ts @@ -1,9 +1,15 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as NFS from "node:fs"; import * as Net from "node:net"; import * as readline from "node:readline"; import type { Readable } from "node:stream"; -import { Data, Effect, Option, Predicate, Result, Schema } from "effect"; +import * as Data from "effect/Data"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as Predicate from "effect/Predicate"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; import { decodeJsonResult } from "@t3tools/shared/schemaJson"; class BootstrapError extends Data.TaggedError("BootstrapError")<{ diff --git a/apps/server/src/checkpointing/Errors.ts b/apps/server/src/checkpointing/Errors.ts index c6875e585cf..1d8e113a4eb 100644 --- a/apps/server/src/checkpointing/Errors.ts +++ b/apps/server/src/checkpointing/Errors.ts @@ -1,4 +1,4 @@ -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import type { ProjectionRepositoryError } from "../persistence/Errors.ts"; import type { VcsError } from "@t3tools/contracts"; diff --git a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts b/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts index 041bc402034..7e012312dca 100644 --- a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts +++ b/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts @@ -1,5 +1,7 @@ import { CheckpointRef, ProjectId, ThreadId, TurnId } from "@t3tools/contracts"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { describe, expect, it } from "vitest"; import { diff --git a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts b/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts index c6b15134711..a280c087824 100644 --- a/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts +++ b/apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts @@ -4,7 +4,10 @@ import { type OrchestrationGetFullThreadDiffResult, type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType, } from "@t3tools/contracts"; -import { Effect, Layer, Option, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; import { CheckpointInvariantError, CheckpointUnavailableError } from "../Errors.ts"; diff --git a/apps/server/src/checkpointing/Layers/CheckpointStore.test.ts b/apps/server/src/checkpointing/Layers/CheckpointStore.test.ts index 204fd565740..2d4fd8a7df2 100644 --- a/apps/server/src/checkpointing/Layers/CheckpointStore.test.ts +++ b/apps/server/src/checkpointing/Layers/CheckpointStore.test.ts @@ -1,8 +1,13 @@ +// @effect-diagnostics nodeBuiltinImport:off import path from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as PlatformError from "effect/PlatformError"; +import * as Scope from "effect/Scope"; import { describe, expect } from "vitest"; import { checkpointRefForThreadTurn } from "../Utils.ts"; diff --git a/apps/server/src/checkpointing/Layers/CheckpointStore.ts b/apps/server/src/checkpointing/Layers/CheckpointStore.ts index 7efe4cc0a56..1dccd5e0cce 100644 --- a/apps/server/src/checkpointing/Layers/CheckpointStore.ts +++ b/apps/server/src/checkpointing/Layers/CheckpointStore.ts @@ -11,7 +11,10 @@ */ import { randomUUID } from "node:crypto"; -import { Effect, Layer, FileSystem, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import { CheckpointInvariantError } from "../Errors.ts"; import { VcsProcessExitError } from "@t3tools/contracts"; diff --git a/apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts b/apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts index d865256ac59..4bb8b111827 100644 --- a/apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts +++ b/apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts @@ -12,8 +12,8 @@ import type { OrchestrationGetTurnDiffInput, OrchestrationGetTurnDiffResult, } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { CheckpointServiceError } from "../Errors.ts"; diff --git a/apps/server/src/checkpointing/Services/CheckpointStore.ts b/apps/server/src/checkpointing/Services/CheckpointStore.ts index be6bdbccfaa..a7c4c3dbef0 100644 --- a/apps/server/src/checkpointing/Services/CheckpointStore.ts +++ b/apps/server/src/checkpointing/Services/CheckpointStore.ts @@ -10,8 +10,8 @@ * * @module CheckpointStore */ -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { CheckpointStoreError } from "../Errors.ts"; import { CheckpointRef } from "@t3tools/contracts"; diff --git a/apps/server/src/checkpointing/Utils.ts b/apps/server/src/checkpointing/Utils.ts index c709aa3735c..50d0163af5a 100644 --- a/apps/server/src/checkpointing/Utils.ts +++ b/apps/server/src/checkpointing/Utils.ts @@ -1,4 +1,4 @@ -import { Encoding } from "effect"; +import * as Encoding from "effect/Encoding"; import { CheckpointRef, ProjectId, type ThreadId } from "@t3tools/contracts"; export const CHECKPOINT_REFS_PREFIX = "refs/t3/checkpoints"; diff --git a/apps/server/src/cli/auth.ts b/apps/server/src/cli/auth.ts index dabae1b0f88..d54731b4a24 100644 --- a/apps/server/src/cli/auth.ts +++ b/apps/server/src/cli/auth.ts @@ -1,5 +1,9 @@ import { AuthSessionId } from "@t3tools/contracts"; -import { Console, Effect, Layer, Option, References } from "effect"; +import * as Console from "effect/Console"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as References from "effect/References"; import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli"; import { AuthControlPlaneRuntimeLive } from "../auth/Layers/AuthControlPlane.ts"; diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index fbadf2cf4ea..5bdf2d1f59b 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -1,7 +1,13 @@ -import os from "node:os"; +import NodeOS from "node:os"; import { assert, expect, it } from "@effect/vitest"; -import { ConfigProvider, Effect, FileSystem, Layer, Option, Path, Schema } from "effect"; +import * as ConfigProvider from "effect/ConfigProvider"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { DesktopBackendBootstrap, @@ -53,7 +59,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { it.effect("falls back to effect/config values when flags are omitted", () => Effect.gen(function* () { const { join } = yield* Path.Path; - const baseDir = join(os.tmpdir(), "t3-cli-config-env-base"); + const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-env-base"); const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:5173")); const resolved = yield* resolveServerConfig( { @@ -119,7 +125,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { it.effect("uses CLI flags when provided", () => Effect.gen(function* () { const { join } = yield* Path.Path; - const baseDir = join(os.tmpdir(), "t3-cli-config-flags-base"); + const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-flags-base"); const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:4173")); const resolved = yield* resolveServerConfig( { @@ -147,7 +153,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { T3CODE_MODE: "desktop", T3CODE_PORT: "4001", T3CODE_HOST: "0.0.0.0", - T3CODE_HOME: join(os.tmpdir(), "ignored-base"), + T3CODE_HOME: join(NodeOS.tmpdir(), "ignored-base"), VITE_DEV_SERVER_URL: "http://127.0.0.1:5173", T3CODE_NO_BROWSER: "false", T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false", @@ -185,7 +191,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { it.effect("preserves explicit false CLI boolean flags over env and bootstrap values", () => Effect.gen(function* () { const { join } = yield* Path.Path; - const baseDir = join(os.tmpdir(), "t3-cli-config-false-flags"); + const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-false-flags"); const fd = yield* openBootstrapFd( makeDesktopBootstrap({ noBrowser: true, @@ -378,7 +384,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { it.effect("applies flag then env precedence over bootstrap envelope values", () => Effect.gen(function* () { const { join } = yield* Path.Path; - const baseDir = join(os.tmpdir(), "t3-cli-config-env-wins"); + const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-env-wins"); const fd = yield* openBootstrapFd( makeDesktopBootstrap({ port: 4888, @@ -459,6 +465,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }); yield* fs.writeFileString( derivedPaths.settingsPath, + // @effect-diagnostics-next-line preferSchemaOverJson:off `${JSON.stringify({ observability: { otlpTracesUrl: "http://localhost:4318/v1/traces", @@ -521,7 +528,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { it.effect("forces noBrowser and disables auto-bootstrap for headless startup presentation", () => Effect.gen(function* () { const { join } = yield* Path.Path; - const baseDir = join(os.tmpdir(), "t3-cli-config-headless-base"); + const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-headless-base"); const derivedPaths = yield* deriveServerPaths(baseDir, undefined); const resolved = yield* resolveServerConfig( diff --git a/apps/server/src/cli/config.ts b/apps/server/src/cli/config.ts index 0af25ab45c8..c374bc8f182 100644 --- a/apps/server/src/cli/config.ts +++ b/apps/server/src/cli/config.ts @@ -1,18 +1,16 @@ import { NetService } from "@t3tools/shared/Net"; import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings"; import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts"; -import { - Config, - Duration, - Effect, - FileSystem, - LogLevel, - Option, - Path, - Schema, - SchemaIssue, - SchemaTransformation, -} from "effect"; +import * as Config from "effect/Config"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as LogLevel from "effect/LogLevel"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as SchemaIssue from "effect/SchemaIssue"; +import * as SchemaTransformation from "effect/SchemaTransformation"; import { Argument, Flag } from "effect/unstable/cli"; import { readBootstrapEnvelope } from "../bootstrap.ts"; diff --git a/apps/server/src/cli/project.ts b/apps/server/src/cli/project.ts index 956bd386e38..39772fcd061 100644 --- a/apps/server/src/cli/project.ts +++ b/apps/server/src/cli/project.ts @@ -4,18 +4,18 @@ import { ProjectId, type ClientOrchestrationCommand, } from "@t3tools/contracts"; -import { - Console, - Duration, - Effect, - Exit, - FileSystem, - Layer, - Option, - Path, - References, - Schema, -} from "effect"; +import * as Console from "effect/Console"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as References from "effect/References"; +import * as Schema from "effect/Schema"; import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli"; import { FetchHttpClient, @@ -54,6 +54,10 @@ type ProjectCliDispatchCommand = Extract< { type: "project.create" | "project.meta.update" | "project.delete" } >; +class ProjectCommandError extends Data.TaggedError("ProjectCommandError")<{ + readonly message: string; +}> {} + const ProjectCliRuntimeLive = Layer.mergeAll( WorkspacePathsLive, OrchestrationLayerLive.pipe( @@ -124,7 +128,7 @@ const resolveProjectTitle = Effect.fn("resolveProjectTitle")(function* ( if (trimmed.length > 0) { return trimmed; } - return yield* Effect.fail(new Error("Project title cannot be empty.")); + return yield* new ProjectCommandError({ message: "Project title cannot be empty." }); } const path = yield* Path.Path; @@ -138,7 +142,7 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* ( }) { const trimmedIdentifier = input.identifier.trim(); if (trimmedIdentifier.length === 0) { - return yield* Effect.fail(new Error("Project identifier cannot be empty.")); + return yield* new ProjectCommandError({ message: "Project identifier cannot be empty." }); } const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null); @@ -165,7 +169,9 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* ( const resolved = exactWorkspaceMatch; if (!resolved) { - return yield* Effect.fail(new Error(`No active project found for '${trimmedIdentifier}'.`)); + return yield* new ProjectCommandError({ + message: `No active project found for '${trimmedIdentifier}'.`, + }); } return { @@ -185,7 +191,7 @@ const fetchLiveOrchestrationSnapshot = (origin: string, bearerToken: string) => "2xx": decodeOrchestrationReadModelResponse, orElse: (response) => readErrorMessageFromResponse(response).pipe( - Effect.flatMap((message) => Effect.fail(new Error(message))), + Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))), ), }), ); @@ -206,7 +212,7 @@ const dispatchLiveOrchestrationCommand = ( "2xx": () => Effect.void, orElse: (response) => readErrorMessageFromResponse(response).pipe( - Effect.flatMap((message) => Effect.fail(new Error(message))), + Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))), ), }), ), @@ -331,9 +337,9 @@ const projectAddCommand = Command.make("add", { (project) => project.deletedAt === null && project.workspaceRoot === workspaceRoot, ); if (existingProject) { - return yield* Effect.fail( - new Error(`An active project already exists for '${workspaceRoot}'.`), - ); + return yield* new ProjectCommandError({ + message: `An active project already exists for '${workspaceRoot}'.`, + }); } const title = yield* resolveProjectTitle(workspaceRoot, Option.getOrUndefined(flags.title)); @@ -345,7 +351,7 @@ const projectAddCommand = Command.make("add", { title, workspaceRoot, defaultModelSelection: getAutoBootstrapDefaultModelSelection(), - createdAt: new Date().toISOString(), + createdAt: DateTime.formatIso(yield* DateTime.now), }); return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; }), diff --git a/apps/server/src/cli/server.ts b/apps/server/src/cli/server.ts index e24a960650e..bc4b3f45706 100644 --- a/apps/server/src/cli/server.ts +++ b/apps/server/src/cli/server.ts @@ -1,4 +1,4 @@ -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { Command, GlobalFlag } from "effect/unstable/cli"; import { ServerConfig, type StartupPresentation } from "../config.ts"; diff --git a/apps/server/src/cliAuthFormat.test.ts b/apps/server/src/cliAuthFormat.test.ts index 017ced97e83..3ffaf6fba74 100644 --- a/apps/server/src/cliAuthFormat.test.ts +++ b/apps/server/src/cliAuthFormat.test.ts @@ -1,5 +1,5 @@ import { expect, it } from "@effect/vitest"; -import { DateTime } from "effect"; +import * as DateTime from "effect/DateTime"; import { formatIssuedPairingCredential, @@ -15,8 +15,8 @@ it("formats issued pairing credentials with the secret and optional pair URL", ( credential: "secret-pairing-token", role: "client", subject: "one-time-token", - createdAt: DateTime.fromDateUnsafe(new Date("2026-04-08T09:00:00.000Z")), - expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), + createdAt: DateTime.makeUnsafe("2026-04-08T09:00:00.000Z"), + expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), }, { baseUrl: "https://example.com", json: false }, ); @@ -34,8 +34,8 @@ it("formats pairing listings without exposing the secret token", () => { subject: "one-time-token", label: "Phone", role: "client", - createdAt: DateTime.fromDateUnsafe(new Date("2026-04-08T09:00:00.000Z")), - expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), + createdAt: DateTime.makeUnsafe("2026-04-08T09:00:00.000Z"), + expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), }, ], { json: false }, @@ -57,7 +57,7 @@ it("formats issued sessions with the bearer token but omits tokens from listings label: "deploy-bot", deviceType: "bot", }, - expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), + expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), }, { json: false }, ); @@ -75,8 +75,8 @@ it("formats issued sessions with the bearer token but omits tokens from listings }, connected: false, current: false, - issuedAt: DateTime.fromDateUnsafe(new Date("2026-04-08T09:00:00.000Z")), - expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), + issuedAt: DateTime.makeUnsafe("2026-04-08T09:00:00.000Z"), + expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), lastConnectedAt: null, }, ], diff --git a/apps/server/src/cliAuthFormat.ts b/apps/server/src/cliAuthFormat.ts index 44356c5a8a9..4078860ff94 100644 --- a/apps/server/src/cliAuthFormat.ts +++ b/apps/server/src/cliAuthFormat.ts @@ -1,5 +1,5 @@ import type { AuthClientMetadata, AuthClientSession, AuthPairingLink } from "@t3tools/contracts"; -import { DateTime } from "effect"; +import * as DateTime from "effect/DateTime"; import type { IssuedBearerSession, IssuedPairingLink } from "./auth/Services/AuthControlPlane.ts"; diff --git a/apps/server/src/config.ts b/apps/server/src/config.ts index 4d8e1cb7e79..b0a23cb273c 100644 --- a/apps/server/src/config.ts +++ b/apps/server/src/config.ts @@ -6,7 +6,13 @@ * * @module ServerConfig */ -import { Effect, FileSystem, Layer, LogLevel, Path, Schema, Context } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as LogLevel from "effect/LogLevel"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; export const DEFAULT_PORT = 3773; diff --git a/apps/server/src/environment/Layers/ServerEnvironment.test.ts b/apps/server/src/environment/Layers/ServerEnvironment.test.ts index 6b9ab90481c..6904c53c847 100644 --- a/apps/server/src/environment/Layers/ServerEnvironment.test.ts +++ b/apps/server/src/environment/Layers/ServerEnvironment.test.ts @@ -1,7 +1,12 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as nodePath from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Effect, Exit, FileSystem, Layer, PlatformError } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as PlatformError from "effect/PlatformError"; import { deriveServerPaths, ServerConfig, type ServerConfigShape } from "../../config.ts"; import { ServerEnvironment } from "../Services/ServerEnvironment.ts"; diff --git a/apps/server/src/environment/Layers/ServerEnvironment.ts b/apps/server/src/environment/Layers/ServerEnvironment.ts index 9208a6d8ed5..87b2f3416f8 100644 --- a/apps/server/src/environment/Layers/ServerEnvironment.ts +++ b/apps/server/src/environment/Layers/ServerEnvironment.ts @@ -1,5 +1,9 @@ import { EnvironmentId, type ExecutionEnvironmentDescriptor } from "@t3tools/contracts"; -import { Effect, FileSystem, Layer, Path, Random } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Random from "effect/Random"; import { ServerConfig } from "../../config.ts"; import { ServerEnvironment, type ServerEnvironmentShape } from "../Services/ServerEnvironment.ts"; diff --git a/apps/server/src/environment/Layers/ServerEnvironmentLabel.test.ts b/apps/server/src/environment/Layers/ServerEnvironmentLabel.test.ts index 3d44713510b..26637b05ff3 100644 --- a/apps/server/src/environment/Layers/ServerEnvironmentLabel.test.ts +++ b/apps/server/src/environment/Layers/ServerEnvironmentLabel.test.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect, it } from "@effect/vitest"; -import { Effect, FileSystem } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import { vi } from "vitest"; vi.mock("../../processRunner.ts", () => ({ diff --git a/apps/server/src/environment/Layers/ServerEnvironmentLabel.ts b/apps/server/src/environment/Layers/ServerEnvironmentLabel.ts index dc776583262..16fa9295218 100644 --- a/apps/server/src/environment/Layers/ServerEnvironmentLabel.ts +++ b/apps/server/src/environment/Layers/ServerEnvironmentLabel.ts @@ -1,6 +1,7 @@ import * as OS from "node:os"; -import { Effect, FileSystem } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import { runProcess } from "../../processRunner.ts"; diff --git a/apps/server/src/environment/Services/ServerEnvironment.ts b/apps/server/src/environment/Services/ServerEnvironment.ts index 48586803332..1e6dea0d05f 100644 --- a/apps/server/src/environment/Services/ServerEnvironment.ts +++ b/apps/server/src/environment/Services/ServerEnvironment.ts @@ -1,6 +1,6 @@ import type { EnvironmentId, ExecutionEnvironmentDescriptor } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; export interface ServerEnvironmentShape { readonly getEnvironmentId: Effect.Effect; diff --git a/apps/server/src/git/GitManager.test.ts b/apps/server/src/git/GitManager.test.ts index 4dba7f9842b..530e0488cf3 100644 --- a/apps/server/src/git/GitManager.test.ts +++ b/apps/server/src/git/GitManager.test.ts @@ -1,10 +1,15 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as PlatformError from "effect/PlatformError"; +import * as Scope from "effect/Scope"; import { ChildProcessSpawner } from "effect/unstable/process"; import { expect } from "vitest"; import type { @@ -32,6 +37,7 @@ import { ServerConfig } from "../config.ts"; import { ServerSettingsService } from "../serverSettings.ts"; import { ProjectSetupScriptRunner, + ProjectSetupScriptRunnerError, type ProjectSetupScriptRunnerInput, type ProjectSetupScriptRunnerShape, } from "../project/Services/ProjectSetupScriptRunner.ts"; @@ -713,6 +719,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 13, @@ -754,6 +761,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 14, @@ -792,6 +800,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 0, @@ -843,6 +852,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 16, @@ -955,6 +965,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { }; const { manager, ghCalls } = yield* makeManager({ ghScenario: { + // @effect-diagnostics-next-line preferSchemaOverJson:off prListSequence: [JSON.stringify([existingPr]), JSON.stringify([existingPr])], }, }); @@ -981,6 +992,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 1661, @@ -1034,8 +1046,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 488, @@ -1110,6 +1125,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListByHeadSelector: { + // @effect-diagnostics-next-line preferSchemaOverJson:off "effect-atom": JSON.stringify([ { number: 1618, @@ -1121,6 +1137,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { updatedAt: "2026-03-01T10:00:00Z", }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "upstream/effect-atom": JSON.stringify([ { number: 1518, @@ -1132,8 +1149,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { updatedAt: "2026-04-01T10:00:00Z", }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "pingdotgg:effect-atom": JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "my-org/upstream:effect-atom": JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "pingdotgg:upstream/effect-atom": JSON.stringify([ { number: 1518, @@ -1145,6 +1165,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { updatedAt: "2026-04-01T10:00:00Z", }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "my-org/upstream:upstream/effect-atom": JSON.stringify([ { number: 1518, @@ -1194,6 +1215,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 22, @@ -1231,6 +1253,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 23, @@ -1262,6 +1285,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 45, @@ -1612,6 +1636,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { ghScenario: { prListSequence: [ "[]", + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 77, @@ -1747,6 +1772,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { ghScenario: { prListSequence: [ "[]", + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 303, @@ -1793,6 +1819,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { ghScenario: { prListSequence: [ "[]", + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 404, @@ -1833,6 +1860,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 42, @@ -1886,7 +1914,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 142, @@ -1963,6 +1993,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListByHeadSelector: { + // @effect-diagnostics-next-line preferSchemaOverJson:off "effect-atom": JSON.stringify([ { number: 1618, @@ -1972,6 +2003,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { headRefName: "effect-atom", }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "upstream/effect-atom": JSON.stringify([ { number: 1518, @@ -1981,8 +2013,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { headRefName: "upstream/effect-atom", }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "pingdotgg:effect-atom": JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "my-org/upstream:effect-atom": JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "pingdotgg:upstream/effect-atom": JSON.stringify([ { number: 1518, @@ -1992,6 +2027,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { headRefName: "upstream/effect-atom", }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "my-org/upstream:upstream/effect-atom": JSON.stringify([ { number: 1518, @@ -2041,7 +2077,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListByHeadSelector: { + // @effect-diagnostics-next-line preferSchemaOverJson:off "t3code/pr-142/statemachine": JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off statemachine: JSON.stringify([ { number: 41, @@ -2051,6 +2089,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { headRefName: "statemachine", }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "octocat:statemachine": JSON.stringify([ { number: 142, @@ -2068,6 +2107,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { }, }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "fork-seed:statemachine": JSON.stringify([]), }, }, @@ -2112,6 +2152,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListByHeadSelector: { + // @effect-diagnostics-next-line preferSchemaOverJson:off "octocat:statemachine": JSON.stringify([ { number: 142, @@ -2129,8 +2170,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { }, }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "fork-seed:statemachine": JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off "t3code/pr-142/statemachine": JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off statemachine: JSON.stringify([]), }, }, @@ -2170,6 +2214,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { ghScenario: { prListSequence: [ "[]", + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 88, @@ -2215,6 +2260,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager, ghCalls } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 1661, @@ -2232,6 +2278,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { }, }, ]), + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 188, @@ -2294,7 +2341,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { ghScenario: { prListSequenceByHeadSelector: { "octocat:statemachine": [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 188, @@ -2313,7 +2362,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { }, ]), ], + // @effect-diagnostics-next-line preferSchemaOverJson:off "fork-seed:statemachine": [JSON.stringify([])], + // @effect-diagnostics-next-line preferSchemaOverJson:off statemachine: [JSON.stringify([])], }, }, @@ -2870,7 +2921,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { yield* runGit(repoDir, ["add", "existing.txt"]); yield* runGit(repoDir, ["commit", "-m", "Existing worktree branch"]); yield* runGit(repoDir, ["checkout", "main"]); - const worktreePath = path.join(repoDir, "..", `pr-existing-${Date.now()}`); + const worktreePath = path.join(repoDir, "..", `pr-existing-${path.basename(repoDir)}`); yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-existing-worktree"]); const setupCalls: ProjectSetupScriptRunnerInput[] = []; @@ -3044,7 +3095,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { yield* runGit(repoDir, ["commit", "-m", "Reused fork PR branch"]); yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-reused-fork"]); yield* runGit(repoDir, ["checkout", "main"]); - const worktreePath = path.join(repoDir, "..", `pr-reused-fork-${Date.now()}`); + const worktreePath = path.join(repoDir, "..", `pr-reused-fork-${path.basename(repoDir)}`); yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-reused-fork"]); yield* runGit(worktreePath, ["branch", "--unset-upstream"], true); @@ -3112,7 +3163,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { }, }, setupScriptRunner: { - runForThread: () => Effect.fail(new Error("terminal start failed")), + runForThread: () => + Effect.fail(new ProjectSetupScriptRunnerError({ message: "terminal start failed" })), }, }); @@ -3289,7 +3341,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { const { manager } = yield* makeManager({ ghScenario: { prListSequence: [ + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([]), + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 201, diff --git a/apps/server/src/git/GitManager.ts b/apps/server/src/git/GitManager.ts index b77ee5169b1..8dfb957b89d 100644 --- a/apps/server/src/git/GitManager.ts +++ b/apps/server/src/git/GitManager.ts @@ -1,20 +1,18 @@ import { randomUUID } from "node:crypto"; -import { - Array as Arr, - Cache, - Context, - DateTime, - Duration, - Effect, - Exit, - FileSystem, - Layer, - Option, - Order, - Path, - Ref, -} from "effect"; +import * as Arr from "effect/Array"; +import * as Cache from "effect/Cache"; +import * as Context from "effect/Context"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Order from "effect/Order"; +import * as Path from "effect/Path"; +import * as Ref from "effect/Ref"; import { GitActionProgressEvent, GitActionProgressPhase, diff --git a/apps/server/src/git/GitWorkflowService.test.ts b/apps/server/src/git/GitWorkflowService.test.ts index dd7273b40c9..9a34680496f 100644 --- a/apps/server/src/git/GitWorkflowService.test.ts +++ b/apps/server/src/git/GitWorkflowService.test.ts @@ -1,5 +1,6 @@ import { assert, describe, it, vi } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as GitManager from "./GitManager.ts"; import * as GitWorkflowService from "./GitWorkflowService.ts"; diff --git a/apps/server/src/git/GitWorkflowService.ts b/apps/server/src/git/GitWorkflowService.ts index 70ab6eecf1f..74064450fcb 100644 --- a/apps/server/src/git/GitWorkflowService.ts +++ b/apps/server/src/git/GitWorkflowService.ts @@ -1,4 +1,6 @@ -import { Context, Effect, Layer } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { GitManagerError, diff --git a/apps/server/src/git/Utils.ts b/apps/server/src/git/Utils.ts index 6faf3e99c77..b414daaa0a4 100644 --- a/apps/server/src/git/Utils.ts +++ b/apps/server/src/git/Utils.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import { existsSync } from "node:fs"; import { join } from "node:path"; diff --git a/apps/server/src/http.ts b/apps/server/src/http.ts index 23ee0f54cf8..fcd1c380e49 100644 --- a/apps/server/src/http.ts +++ b/apps/server/src/http.ts @@ -1,6 +1,10 @@ import Mime from "@effect/platform-node/Mime"; import { decodeOtlpTraceRecords } from "@t3tools/shared/observability"; -import { Data, Effect, FileSystem, Option, Path } from "effect"; +import * as Data from "effect/Data"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; import { cast } from "effect/Function"; import { HttpBody, diff --git a/apps/server/src/keybindings.test.ts b/apps/server/src/keybindings.test.ts index efada98bf54..cf6dac2bc76 100644 --- a/apps/server/src/keybindings.test.ts +++ b/apps/server/src/keybindings.test.ts @@ -2,7 +2,13 @@ import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/c import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { assertFailure } from "@effect/vitest/utils"; -import { Cause, Effect, FileSystem, Layer, Logger, Path, Schema } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { ServerConfig } from "./config.ts"; import { @@ -229,6 +235,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString( keybindingsConfigPath, + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { key: "mod+j", command: "terminal.toggle" }, { key: "mod+shift+d+o", command: "terminal.new" }, diff --git a/apps/server/src/keybindings.ts b/apps/server/src/keybindings.ts index 95bf5c3d9a4..83d303c0fad 100644 --- a/apps/server/src/keybindings.ts +++ b/apps/server/src/keybindings.ts @@ -19,29 +19,27 @@ import { type ServerUpsertKeybindingInput, type ServerConfigIssue, } from "@t3tools/contracts"; -import { - Array, - Cache, - Cause, - Deferred, - Duration, - Effect, - Exit, - FileSystem, - Path, - Layer, - Option, - Predicate, - PubSub, - Schema, - SchemaGetter, - SchemaIssue, - SchemaTransformation, - Ref, - Context, - Scope, - Stream, -} from "effect"; +import * as Array from "effect/Array"; +import * as Cache from "effect/Cache"; +import * as Cause from "effect/Cause"; +import * as Deferred from "effect/Deferred"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Predicate from "effect/Predicate"; +import * as PubSub from "effect/PubSub"; +import * as Schema from "effect/Schema"; +import * as SchemaGetter from "effect/SchemaGetter"; +import * as SchemaIssue from "effect/SchemaIssue"; +import * as SchemaTransformation from "effect/SchemaTransformation"; +import * as Ref from "effect/Ref"; +import * as Context from "effect/Context"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import * as Semaphore from "effect/Semaphore"; import { ServerConfig } from "./config.ts"; import { writeFileStringAtomically } from "./atomicWrite.ts"; diff --git a/apps/server/src/observability/Attributes.ts b/apps/server/src/observability/Attributes.ts index 1da76d3b325..168fad18897 100644 --- a/apps/server/src/observability/Attributes.ts +++ b/apps/server/src/observability/Attributes.ts @@ -1,4 +1,5 @@ -import { Cause, Exit } from "effect"; +import * as Cause from "effect/Cause"; +import * as Exit from "effect/Exit"; export type MetricAttributeValue = string; export type MetricAttributes = Readonly>; diff --git a/apps/server/src/observability/Layers/Observability.ts b/apps/server/src/observability/Layers/Observability.ts index ae3c1ecb276..94368a45296 100644 --- a/apps/server/src/observability/Layers/Observability.ts +++ b/apps/server/src/observability/Layers/Observability.ts @@ -1,5 +1,8 @@ import { makeLocalFileTracer, makeTraceSink } from "@t3tools/shared/observability"; -import { Effect, Layer, References, Tracer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as References from "effect/References"; +import * as Tracer from "effect/Tracer"; import { OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/observability/Metrics.test.ts b/apps/server/src/observability/Metrics.test.ts index 57bfebaeceb..899a1e28bc4 100644 --- a/apps/server/src/observability/Metrics.test.ts +++ b/apps/server/src/observability/Metrics.test.ts @@ -1,6 +1,9 @@ import { assert, describe, it } from "@effect/vitest"; import { ProviderDriverKind } from "@t3tools/contracts"; -import { Duration, Effect, Fiber, Metric } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as Metric from "effect/Metric"; import { TestClock } from "effect/testing"; import { withMetrics } from "./Metrics.ts"; diff --git a/apps/server/src/observability/Metrics.ts b/apps/server/src/observability/Metrics.ts index 976bf7ccdb7..886833d6e2c 100644 --- a/apps/server/src/observability/Metrics.ts +++ b/apps/server/src/observability/Metrics.ts @@ -1,4 +1,8 @@ -import { Clock, Duration, Effect, Exit, Metric } from "effect"; +import * as Clock from "effect/Clock"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Metric from "effect/Metric"; import { dual } from "effect/Function"; import { diff --git a/apps/server/src/observability/RpcInstrumentation.test.ts b/apps/server/src/observability/RpcInstrumentation.test.ts index b0aa7c874f4..627357e2955 100644 --- a/apps/server/src/observability/RpcInstrumentation.test.ts +++ b/apps/server/src/observability/RpcInstrumentation.test.ts @@ -1,6 +1,12 @@ import { assert, describe, it } from "@effect/vitest"; import { WS_METHODS } from "@t3tools/contracts"; -import { Duration, Effect, Exit, Fiber, Metric, Stream, Tracer } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as Metric from "effect/Metric"; +import * as Stream from "effect/Stream"; +import * as Tracer from "effect/Tracer"; import { TestClock } from "effect/testing"; import { diff --git a/apps/server/src/observability/RpcInstrumentation.ts b/apps/server/src/observability/RpcInstrumentation.ts index c03e1c2b8a8..e0ea5859af0 100644 --- a/apps/server/src/observability/RpcInstrumentation.ts +++ b/apps/server/src/observability/RpcInstrumentation.ts @@ -1,5 +1,11 @@ import { WS_METHODS } from "@t3tools/contracts"; -import { Clock, Duration, Effect, Exit, Metric, References, Stream } from "effect"; +import * as Clock from "effect/Clock"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Metric from "effect/Metric"; +import * as References from "effect/References"; +import * as Stream from "effect/Stream"; import { outcomeFromExit } from "./Attributes.ts"; import { metricAttributes, rpcRequestDuration, rpcRequestsTotal, withMetrics } from "./Metrics.ts"; diff --git a/apps/server/src/observability/Services/BrowserTraceCollector.ts b/apps/server/src/observability/Services/BrowserTraceCollector.ts index 1018d536044..b704804c963 100644 --- a/apps/server/src/observability/Services/BrowserTraceCollector.ts +++ b/apps/server/src/observability/Services/BrowserTraceCollector.ts @@ -1,6 +1,6 @@ import type { TraceRecord } from "@t3tools/shared/observability"; -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; export interface BrowserTraceCollectorShape { readonly record: (records: ReadonlyArray) => Effect.Effect; diff --git a/apps/server/src/open.test.ts b/apps/server/src/open.test.ts index 1efa36e957c..f600a050288 100644 --- a/apps/server/src/open.test.ts +++ b/apps/server/src/open.test.ts @@ -1,7 +1,10 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { assertSuccess } from "@effect/vitest/utils"; -import { FileSystem, Path, Effect } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Random from "effect/Random"; import { isCommandAvailable, @@ -488,7 +491,7 @@ it.layer(NodeServices.layer)("launchDetached", (it) => { it.effect("rejects when command does not exist", () => Effect.gen(function* () { const result = yield* launchDetached({ - command: `t3code-no-such-command-${Date.now()}`, + command: `t3code-no-such-command-${yield* Random.nextUUIDv4}`, args: [], }).pipe(Effect.result); assert.equal(result._tag, "Failure"); diff --git a/apps/server/src/open.ts b/apps/server/src/open.ts index 98dfcaf4caa..4b2e6ef2716 100644 --- a/apps/server/src/open.ts +++ b/apps/server/src/open.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off /** * Open - Browser/editor launch service interface. * @@ -10,7 +11,9 @@ import { spawn } from "node:child_process"; import { EDITORS, OpenError, type EditorId } from "@t3tools/contracts"; import { isCommandAvailable, type CommandAvailabilityOptions } from "@t3tools/shared/shell"; -import { Context, Effect, Layer } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; // ============================== // Definitions diff --git a/apps/server/src/orchestration/Errors.ts b/apps/server/src/orchestration/Errors.ts index 1ea038e1d13..888fd4b3c0d 100644 --- a/apps/server/src/orchestration/Errors.ts +++ b/apps/server/src/orchestration/Errors.ts @@ -1,4 +1,5 @@ -import { SchemaIssue, Schema } from "effect"; +import * as SchemaIssue from "effect/SchemaIssue"; +import * as Schema from "effect/Schema"; import type { ProjectionRepositoryError } from "../persistence/Errors.ts"; diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts index 862e4de3cbe..ecc78d0b484 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -19,7 +20,14 @@ import { TurnId, } from "@t3tools/contracts"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect"; +import * as Clock from "effect/Clock"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as PubSub from "effect/PubSub"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { afterEach, describe, expect, it, vi } from "vitest"; import { CheckpointStoreLive } from "../../checkpointing/Layers/CheckpointStore.ts"; @@ -73,7 +81,7 @@ function createProviderServiceHarness( sessionCwd = cwd, providerName: ProviderSession["provider"] = ProviderDriverKind.make("codex"), ) { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const runtimeEventPubSub = Effect.runSync(PubSub.unbounded()); const rollbackConversation = vi.fn( (_input: { readonly threadId: ThreadId; readonly numTurns: number }) => Effect.void, @@ -148,7 +156,7 @@ async function waitForThread( }) => boolean, timeoutMs = 15_000, ) { - const deadline = Date.now() + timeoutMs; + const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; const poll = async (): Promise<{ latestTurn: { turnId: string } | null; checkpoints: ReadonlyArray<{ checkpointTurnCount: number }>; @@ -159,10 +167,10 @@ async function waitForThread( if (thread && predicate(thread)) { return thread; } - if (Date.now() >= deadline) { + if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error("Timed out waiting for thread state."); } - await new Promise((resolve) => setTimeout(resolve, 10)); + await Effect.runPromise(Effect.yieldNow); return poll(); }; return poll(); @@ -173,7 +181,7 @@ async function waitForEvent( predicate: (event: { type: string }) => boolean, timeoutMs = 15_000, ) { - const deadline = Date.now() + timeoutMs; + const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; const poll = async () => { const events = await Effect.runPromise( Stream.runCollect(engine.readEvents(0)).pipe(Effect.map((chunk) => Array.from(chunk))), @@ -181,10 +189,10 @@ async function waitForEvent( if (events.some(predicate)) { return events; } - if (Date.now() >= deadline) { + if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error("Timed out waiting for orchestration event."); } - await new Promise((resolve) => setTimeout(resolve, 10)); + await Effect.runPromise(Effect.yieldNow); return poll(); }; return poll(); @@ -223,15 +231,15 @@ function gitShowFileAtRef(cwd: string, ref: string, filePath: string): string { } async function waitForGitRefExists(cwd: string, ref: string, timeoutMs = 15_000) { - const deadline = Date.now() + timeoutMs; + const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; const poll = async (): Promise => { if (gitRefExists(cwd, ref)) { return; } - if (Date.now() >= deadline) { + if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error(`Timed out waiting for git ref '${ref}'.`); } - await new Promise((resolve) => setTimeout(resolve, 10)); + await Effect.runPromise(Effect.yieldNow); return poll(); }; return poll(); @@ -342,7 +350,7 @@ describe("CheckpointReactor", () => { await Effect.runPromise(reactor.start().pipe(Scope.provide(scope))); const drain = () => Effect.runPromise(reactor.drain); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( engine.dispatch({ type: "project.create", @@ -410,7 +418,7 @@ describe("CheckpointReactor", () => { it("captures pre-turn baseline on turn.started and post-turn checkpoint on turn.completed", async () => { const harness = await createHarness({ seedFilesystemCheckpoints: false }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -435,7 +443,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-started-1"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-1"), }); @@ -450,7 +458,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-completed-1"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-1"), payload: { state: "completed" }, @@ -495,7 +503,7 @@ describe("CheckpointReactor", () => { type: "turn.completed", eventId: EventId.make("evt-turn-completed-refresh-local-status"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-refresh-local-status"), payload: { state: "completed" }, @@ -508,7 +516,7 @@ describe("CheckpointReactor", () => { it("ignores auxiliary thread turn completion while primary turn is active", async () => { const harness = await createHarness({ seedFilesystemCheckpoints: false }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -533,7 +541,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-started-main"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-main"), }); @@ -549,7 +557,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-completed-aux"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-aux"), payload: { state: "completed" }, @@ -565,7 +573,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-completed-main"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-main"), payload: { state: "completed" }, @@ -583,7 +591,7 @@ describe("CheckpointReactor", () => { seedFilesystemCheckpoints: false, providerName: ProviderDriverKind.make("claudeAgent"), }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -607,7 +615,7 @@ describe("CheckpointReactor", () => { type: "turn.started", eventId: EventId.make("evt-turn-started-claude-1"), provider: ProviderDriverKind.make("claudeAgent"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-claude-1"), }); @@ -621,7 +629,7 @@ describe("CheckpointReactor", () => { type: "turn.completed", eventId: EventId.make("evt-turn-completed-claude-1"), provider: ProviderDriverKind.make("claudeAgent"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-claude-1"), payload: { state: "completed" }, @@ -641,7 +649,7 @@ describe("CheckpointReactor", () => { it("appends capture failure activity when turn diff summary cannot be derived", async () => { const harness = await createHarness({ seedFilesystemCheckpoints: false }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -666,7 +674,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-completed-missing-baseline"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-missing-baseline"), payload: { state: "completed" }, @@ -706,7 +714,7 @@ describe("CheckpointReactor", () => { }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", }), ); @@ -729,7 +737,7 @@ describe("CheckpointReactor", () => { seedFilesystemCheckpoints: false, threadWorktreePath: null, }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -755,7 +763,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-completed-missing-provider-cwd"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-missing-cwd"), payload: { state: "completed" }, @@ -776,7 +784,7 @@ describe("CheckpointReactor", () => { it("ignores non-v2 checkpoint.captured runtime events", async () => { const harness = await createHarness(); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -801,7 +809,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-checkpoint-captured-3"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-3"), turnCount: 3, @@ -826,7 +834,7 @@ describe("CheckpointReactor", () => { seedFilesystemCheckpoints: false, providerSessionCwd: nonRepositorySessionCwd, }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -851,7 +859,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-runtime-capture-failure"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-runtime-failure"), payload: { state: "completed" }, @@ -862,7 +870,7 @@ describe("CheckpointReactor", () => { eventId: EventId.make("evt-turn-started-after-runtime-failure"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: ThreadId.make("thread-1"), turnId: asTurnId("turn-after-runtime-failure"), }); @@ -878,7 +886,7 @@ describe("CheckpointReactor", () => { it("executes provider revert and emits thread.reverted for checkpoint revert requests", async () => { const harness = await createHarness(); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -959,7 +967,7 @@ describe("CheckpointReactor", () => { it("executes provider revert and emits thread.reverted for claude sessions", async () => { const harness = await createHarness({ providerName: ProviderDriverKind.make("claudeAgent") }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1028,7 +1036,7 @@ describe("CheckpointReactor", () => { it("processes consecutive revert requests with deterministic rollback sequencing", async () => { const harness = await createHarness(); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1111,7 +1119,7 @@ describe("CheckpointReactor", () => { it("appends an error activity when revert is requested without an active session", async () => { const harness = await createHarness({ hasSession: false }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts index 001b757cf80..4e1606928bf 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts @@ -8,7 +8,12 @@ import { type OrchestrationEvent, type ProviderRuntimeEvent, } from "@t3tools/contracts"; -import { Cause, Effect, Layer, Option, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Stream from "effect/Stream"; import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; import { parseTurnDiffFilesFromUnifiedDiff } from "../../checkpointing/Diffs.ts"; @@ -28,6 +33,8 @@ import { isGitRepository } from "../../git/Utils.ts"; import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; import { WorkspaceEntries } from "../../workspace/Services/WorkspaceEntries.ts"; +const nowIsoSync = () => Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); + type ReactorInput = | { readonly source: "runtime"; @@ -584,7 +591,7 @@ const make = Effect.gen(function* () { const handleRevertRequested = Effect.fn("handleRevertRequested")(function* ( event: Extract, ) { - const now = new Date().toISOString(); + const now = DateTime.formatIso(yield* DateTime.now); const thread = yield* resolveThreadDetail(event.payload.threadId); if (!thread) { @@ -721,7 +728,7 @@ const make = Effect.gen(function* () { threadId: event.payload.threadId, turnCount: event.payload.turnCount, detail: error.message, - createdAt: new Date().toISOString(), + createdAt: nowIsoSync(), }), ), ); @@ -740,7 +747,7 @@ const make = Effect.gen(function* () { threadId: event.payload.threadId, turnId: event.payload.turnId, detail: error.message, - createdAt: new Date().toISOString(), + createdAt: nowIsoSync(), }).pipe(Effect.catch(() => Effect.void)), ), ); @@ -764,7 +771,7 @@ const make = Effect.gen(function* () { threadId: event.threadId, turnId, detail: error.message, - createdAt: new Date().toISOString(), + createdAt: nowIsoSync(), }).pipe(Effect.catch(() => Effect.void)), ), ); diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts index 90d849fd826..d74c2c5ed42 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts @@ -8,9 +8,14 @@ import { TurnId, type OrchestrationEvent, ProviderInstanceId, - type OrchestrationReadModel, } from "@t3tools/contracts"; -import { Effect, Layer, ManagedRuntime, Metric, Option, Queue, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as Metric from "effect/Metric"; +import * as Option from "effect/Option"; +import * as Queue from "effect/Queue"; +import * as Stream from "effect/Stream"; import { describe, expect, it } from "vitest"; import { PersistenceSqlError } from "../../persistence/Errors.ts"; @@ -69,7 +74,7 @@ async function createOrchestrationSystem() { } function now() { - return new Date().toISOString(); + return "2026-01-01T00:00:00.000Z"; } const hasMetricSnapshot = ( diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts index 6c591416486..88561c17658 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts @@ -5,20 +5,20 @@ import type { ThreadId, } from "@t3tools/contracts"; import { OrchestrationCommand } from "@t3tools/contracts"; -import { - Cause, - Deferred, - Duration, - Effect, - Exit, - Layer, - Metric, - Option, - PubSub, - Queue, - Schema, - Stream, -} from "effect"; +import * as Cause from "effect/Cause"; +import * as Clock from "effect/Clock"; +import * as DateTime from "effect/DateTime"; +import * as Deferred from "effect/Deferred"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Metric from "effect/Metric"; +import * as Option from "effect/Option"; +import * as PubSub from "effect/PubSub"; +import * as Queue from "effect/Queue"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { @@ -78,7 +78,8 @@ const makeOrchestrationEngine = Effect.gen(function* () { const projectionPipeline = yield* OrchestrationProjectionPipeline; const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; - let commandReadModel = createEmptyReadModel(new Date().toISOString()); + const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); + let commandReadModel = createEmptyReadModel(yield* nowIso); const commandQueue = yield* Queue.unbounded(); const eventPubSub = yield* PubSub.unbounded(); @@ -97,7 +98,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { const processEnvelope = (envelope: CommandEnvelope): Effect.Effect => { const dispatchStartSequence = commandReadModel.snapshotSequence; - const processingStartedAtMs = Date.now(); + let processingStartedAtMs = 0; const aggregateRef = commandToAggregateRef(envelope.command); const baseMetricAttributes = { commandType: envelope.command.type, @@ -120,6 +121,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { return Effect.exit( Effect.gen(function* () { + processingStartedAtMs = yield* Clock.currentTimeMillis; yield* Effect.annotateCurrentSpan({ "orchestration.command_id": envelope.command.commandId, "orchestration.command_type": envelope.command.type, @@ -205,7 +207,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { ackEventType: event.type, }), ), - Duration.millis(Math.max(0, Date.now() - envelope.startedAtMs)), + Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - envelope.startedAtMs)), ); } } @@ -224,7 +226,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { orchestrationCommandDuration, metricAttributes(baseMetricAttributes), ), - Duration.millis(Math.max(0, Date.now() - processingStartedAtMs)), + Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - processingStartedAtMs)), ); yield* Metric.update( Metric.withAttributes( @@ -263,7 +265,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { commandId: envelope.command.commandId, aggregateKind: aggregateRef.aggregateKind, aggregateId: aggregateRef.aggregateId, - acceptedAt: new Date().toISOString(), + acceptedAt: yield* nowIso, resultSequence: commandReadModel.snapshotSequence, status: "rejected", error: error.message, @@ -293,7 +295,11 @@ const makeOrchestrationEngine = Effect.gen(function* () { const dispatch: OrchestrationEngineShape["dispatch"] = (command) => Effect.gen(function* () { const result = yield* Deferred.make<{ sequence: number }, OrchestrationDispatchError>(); - yield* Queue.offer(commandQueue, { command, result, startedAtMs: Date.now() }); + yield* Queue.offer(commandQueue, { + command, + result, + startedAtMs: yield* Clock.currentTimeMillis, + }); return yield* Deferred.await(result); }); diff --git a/apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts b/apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts index 8be139e3a0f..6155af8858a 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts @@ -1,4 +1,8 @@ -import { Effect, Exit, Layer, ManagedRuntime, Scope } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as Scope from "effect/Scope"; import { afterEach, describe, expect, it } from "vitest"; import { CheckpointReactor } from "../Services/CheckpointReactor.ts"; diff --git a/apps/server/src/orchestration/Layers/OrchestrationReactor.ts b/apps/server/src/orchestration/Layers/OrchestrationReactor.ts index 258294830e0..5e432d9884f 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationReactor.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationReactor.ts @@ -1,4 +1,5 @@ -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { OrchestrationReactor, diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts index 7f364c717a7..369eea0f7a0 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts @@ -11,7 +11,10 @@ import { } from "@t3tools/contracts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts"; @@ -55,7 +58,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* eventStore.append({ type: "project.created", @@ -180,7 +183,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-base-")))( const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* eventStore.append({ type: "thread.message-sent", @@ -224,6 +227,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-base-")))( WHERE message_id = 'message-attachments' `; assert.equal(rows.length, 1); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ { type: "image", @@ -246,7 +250,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* eventStore.append({ type: "thread.message-sent", @@ -297,6 +301,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta WHERE message_id = 'message-attachments-safe' `; assert.equal(rows.length, 1); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ { type: "image", @@ -326,8 +331,8 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); - const later = new Date(Date.now() + 1_000).toISOString(); + const now = "2026-01-01T00:00:00.000Z"; + const later = "2026-01-01T00:00:01.000Z"; yield* eventStore.append({ type: "project.created", @@ -441,6 +446,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { WHERE message_id = 'message-clear-attachments' `; assert.equal(rows.length, 1); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), []); }), ); @@ -454,8 +460,8 @@ it.layer( const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); - const later = new Date(Date.now() + 1_000).toISOString(); + const now = "2026-01-01T00:00:00.000Z"; + const later = "2026-01-01T00:00:01.000Z"; yield* eventStore.append({ type: "project.created", @@ -576,6 +582,7 @@ it.layer( WHERE message_id = 'message-overwrite' `; assert.equal(rows.length, 1); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ { type: "image", @@ -598,7 +605,7 @@ it.layer( const eventStore = yield* OrchestrationEventStore; const path = yield* Path.Path; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const appendAndProject = (event: Parameters[0]) => eventStore @@ -722,7 +729,7 @@ it.layer( const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const { attachmentsDir } = yield* ServerConfig; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const threadId = ThreadId.make("Thread Revert.Files"); const keepAttachmentId = "thread-revert-files-00000000-0000-4000-8000-000000000001"; const removeAttachmentId = "thread-revert-files-00000000-0000-4000-8000-000000000002"; @@ -931,7 +938,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const { attachmentsDir } = yield* ServerConfig; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const threadId = ThreadId.make("Thread Delete.Files"); const attachmentId = "thread-delete-files-00000000-0000-4000-8000-000000000001"; const otherThreadAttachmentId = @@ -1063,7 +1070,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta const path = yield* Path.Path; const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const { attachmentsDir: attachmentsRootDir, stateDir } = yield* ServerConfig; const attachmentsSentinelPath = path.join(attachmentsRootDir, "sentinel.txt"); const stateDirSentinelPath = path.join(stateDir, "state-sentinel.txt"); @@ -1103,7 +1110,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* eventStore.append({ type: "project.created", @@ -1230,7 +1237,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { const projectionPipeline = yield* OrchestrationProjectionPipeline; const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* eventStore.append({ type: "project.created", @@ -2187,7 +2194,7 @@ engineLayer("OrchestrationProjectionPipeline via engine dispatch", (it) => { Effect.gen(function* () { const engine = yield* OrchestrationEngineService; const sql = yield* SqlClient.SqlClient; - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; yield* engine.dispatch({ type: "project.create", @@ -2225,7 +2232,7 @@ engineLayer("OrchestrationProjectionPipeline via engine dispatch", (it) => { Effect.gen(function* () { const engine = yield* OrchestrationEngineService; const sql = yield* SqlClient.SqlClient; - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; yield* engine.dispatch({ type: "project.create", diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index 3ef8b38d642..1161ff6a7d7 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -4,7 +4,12 @@ import { type OrchestrationEvent, ThreadId, } from "@t3tools/contracts"; -import { Effect, FileSystem, Layer, Option, Path, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Stream from "effect/Stream"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { toPersistenceSqlError, type ProjectionRepositoryError } from "../../persistence/Errors.ts"; diff --git a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts index 4538ab4b6b5..7f6f973ed89 100644 --- a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts +++ b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts @@ -8,7 +8,8 @@ import { ProviderInstanceId, } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; diff --git a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts index 27b067e8f57..9a539d77aec 100644 --- a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts +++ b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts @@ -23,7 +23,11 @@ import { ProjectId, ThreadId, } from "@t3tools/contracts"; -import { Effect, Layer, Option, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +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"; @@ -1005,7 +1009,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { snapshotSequence: computeSnapshotSequence(stateRows), projects, threads, - updatedAt: updatedAt ?? new Date(0).toISOString(), + updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", }; return yield* decodeReadModel(snapshot).pipe( @@ -1204,7 +1208,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { snapshotSequence: computeSnapshotSequence(stateRows), projects, threads, - updatedAt: updatedAt ?? new Date(0).toISOString(), + updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", } satisfies OrchestrationReadModel; }), ), @@ -1326,7 +1330,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { hasActionableProposedPlan: row.hasActionableProposedPlan > 0, }), ), - updatedAt: updatedAt ?? new Date(0).toISOString(), + updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", }; return yield* decodeShellSnapshot(snapshot).pipe( diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index f641eef0370..571164fad93 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -20,7 +21,13 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as PubSub from "effect/PubSub"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { afterEach, describe, expect, it, vi } from "vitest"; import { deriveServerPaths, ServerConfig } from "../../config.ts"; @@ -47,6 +54,7 @@ import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts"; import { ProviderCommandReactor } from "../Services/ProviderCommandReactor.ts"; import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts"; import * as NodeServices from "@effect/platform-node/NodeServices"; +import * as Clock from "effect/Clock"; import { ServerSettingsService } from "../../serverSettings.ts"; import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; import { GitWorkflowService, type GitWorkflowServiceShape } from "../../git/GitWorkflowService.ts"; @@ -63,15 +71,15 @@ async function waitFor( predicate: () => boolean | Promise, timeoutMs = 2000, ): Promise { - const deadline = Date.now() + timeoutMs; + const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; const poll = async (): Promise => { if (await predicate()) { return; } - if (Date.now() >= deadline) { + if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error("Timed out waiting for expectation."); } - await new Promise((resolve) => setTimeout(resolve, 10)); + await Effect.runPromise(Effect.yieldNow); return poll(); }; @@ -135,7 +143,7 @@ describe("ProviderCommandReactor", () => { readonly threadModelSelection?: ModelSelection; readonly sessionModelSwitch?: "unsupported" | "in-session"; }) { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const baseDir = input?.baseDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "t3code-reactor-")); createdBaseDirs.add(baseDir); const { stateDir } = deriveServerPathsSync(baseDir, undefined); @@ -408,7 +416,7 @@ describe("ProviderCommandReactor", () => { it("reacts to thread.turn.start by ensuring session and sending provider turn", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -447,7 +455,7 @@ describe("ProviderCommandReactor", () => { it("generates a thread title on the first turn", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const seededTitle = "Please investigate reconnect failures after restar..."; harness.generateThreadTitle.mockReturnValue(Effect.succeed({ title: "Generated title" })); @@ -497,7 +505,7 @@ describe("ProviderCommandReactor", () => { it("does not overwrite an existing custom thread title on the first turn", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const seededTitle = "Please investigate reconnect failures after restar..."; await Effect.runPromise( @@ -537,7 +545,7 @@ describe("ProviderCommandReactor", () => { it("matches the client-seeded title even when the outgoing prompt is reformatted", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const seededTitle = "Fix reconnect spinner on resume"; harness.generateThreadTitle.mockReturnValue( Effect.succeed({ @@ -588,7 +596,7 @@ describe("ProviderCommandReactor", () => { it("generates a worktree branch name for the first turn", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -642,7 +650,7 @@ describe("ProviderCommandReactor", () => { it("forwards codex model options through session start and turn send", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -689,7 +697,7 @@ describe("ProviderCommandReactor", () => { model: "claude-sonnet-4-6", }, }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -739,7 +747,7 @@ describe("ProviderCommandReactor", () => { model: "claude-opus-4-6", }, }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -784,7 +792,7 @@ describe("ProviderCommandReactor", () => { it("forwards plan interaction mode to the provider turn request", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -822,7 +830,7 @@ describe("ProviderCommandReactor", () => { it("preserves the active session model when in-session model switching is unsupported", async () => { const harness = await createHarness({ sessionModelSwitch: "unsupported" }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -875,7 +883,7 @@ describe("ProviderCommandReactor", () => { const harness = await createHarness({ threadModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex" }, }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -921,7 +929,7 @@ describe("ProviderCommandReactor", () => { it("reuses the same provider session when runtime mode is unchanged", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -967,7 +975,7 @@ describe("ProviderCommandReactor", () => { it("restarts an existing Codex thread on a compatible requested instance", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1009,7 +1017,7 @@ describe("ProviderCommandReactor", () => { }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", }), ); @@ -1034,7 +1042,7 @@ describe("ProviderCommandReactor", () => { model: "claude-sonnet-4-6", }, }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1107,7 +1115,7 @@ describe("ProviderCommandReactor", () => { model: "claude-sonnet-4-6", }, }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1170,7 +1178,7 @@ describe("ProviderCommandReactor", () => { it("restarts the provider session when runtime mode is updated on the thread", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1260,7 +1268,7 @@ describe("ProviderCommandReactor", () => { model: "claude-opus-4-6", }, }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1303,7 +1311,7 @@ describe("ProviderCommandReactor", () => { it("does not stop the active session when restart fails before rebind", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1336,7 +1344,7 @@ describe("ProviderCommandReactor", () => { await waitFor(() => harness.sendTurn.mock.calls.length === 1); harness.startSession.mockImplementationOnce( - (_: unknown, __: unknown) => Effect.fail(new Error("simulated restart failure")) as never, + (_: unknown, __: unknown) => Effect.fail("simulated restart failure") as never, ); await Effect.runPromise( @@ -1368,7 +1376,7 @@ describe("ProviderCommandReactor", () => { it("rejects provider changes after a thread is already bound to a session provider", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1440,7 +1448,7 @@ describe("ProviderCommandReactor", () => { it("rejects cross-driver provider changes after the existing thread session has stopped", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1506,7 +1514,7 @@ describe("ProviderCommandReactor", () => { it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1544,7 +1552,7 @@ describe("ProviderCommandReactor", () => { it("starts a fresh session when only projected session state exists", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1599,7 +1607,7 @@ describe("ProviderCommandReactor", () => { it("rejects active runtime sessions that are missing provider instance ids", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1670,7 +1678,7 @@ describe("ProviderCommandReactor", () => { it("reacts to thread.approval.respond by forwarding provider approval response", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1711,7 +1719,7 @@ describe("ProviderCommandReactor", () => { it("reacts to thread.user-input.respond by forwarding structured user input answers", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1756,7 +1764,7 @@ describe("ProviderCommandReactor", () => { it("surfaces stale provider approval request failures without faking approval resolution", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.respondToRequest.mockImplementation(() => Effect.fail( new ProviderAdapterRequestError({ @@ -1851,7 +1859,7 @@ describe("ProviderCommandReactor", () => { it("surfaces stale provider user-input failures without faking user-input resolution", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.respondToUserInput.mockImplementation(() => Effect.fail( new ProviderAdapterRequestError({ @@ -1960,7 +1968,7 @@ describe("ProviderCommandReactor", () => { it("reacts to thread.session.stop by stopping provider session and clearing thread session state", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts index ff583c0f19d..c037ba758b6 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts @@ -13,7 +13,15 @@ import { type TurnId, } from "@t3tools/contracts"; import { isTemporaryWorktreeBranch, WORKTREE_BRANCH_PREFIX } from "@t3tools/shared/git"; -import { Cache, Cause, Duration, Effect, Equal, Layer, Option, Schema, Stream } from "effect"; +import * as Cache from "effect/Cache"; +import * as Cause from "effect/Cause"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Equal from "effect/Equal"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; import { resolveThreadWorkspaceCwd } from "../../checkpointing/Utils.ts"; diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts index 2fe0e406d66..3b2411cba2a 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -21,7 +22,14 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect"; +import * as Clock from "effect/Clock"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as PubSub from "effect/PubSub"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { afterEach, describe, expect, it } from "vitest"; import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts"; @@ -165,17 +173,17 @@ async function waitForThread( timeoutMs = 2000, threadId: ThreadId = asThreadId("thread-1"), ) { - const deadline = Date.now() + timeoutMs; + const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; const poll = async (): Promise => { const snapshot = await readModel(); const thread = snapshot.threads.find((entry) => entry.id === threadId); if (thread && predicate(thread)) { return thread; } - if (Date.now() >= deadline) { + if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error("Timed out waiting for thread state"); } - await new Promise((resolve) => setTimeout(resolve, 10)); + await Effect.runPromise(Effect.yieldNow); return poll(); }; return poll(); @@ -242,7 +250,7 @@ describe("ProviderRuntimeIngestion", () => { await Effect.runPromise(ingestion.start().pipe(Scope.provide(scope))); const drain = () => Effect.runPromise(ingestion.drain); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( engine.dispatch({ type: "project.create", @@ -312,7 +320,7 @@ describe("ProviderRuntimeIngestion", () => { it("maps turn started/completed events into thread session updates", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -333,7 +341,7 @@ describe("ProviderRuntimeIngestion", () => { eventId: asEventId("evt-turn-completed"), provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", turnId: asTurnId("turn-1"), payload: { state: "failed", @@ -354,7 +362,7 @@ describe("ProviderRuntimeIngestion", () => { it("applies provider session.state.changed transitions directly", async () => { const harness = await createHarness(); - const waitingAt = new Date().toISOString(); + const waitingAt = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "session.state.changed", @@ -380,7 +388,7 @@ describe("ProviderRuntimeIngestion", () => { eventId: asEventId("evt-session-state-error"), provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", payload: { state: "error", reason: "provider crashed", @@ -402,7 +410,7 @@ describe("ProviderRuntimeIngestion", () => { eventId: asEventId("evt-session-state-stopped"), provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", payload: { state: "stopped", }, @@ -423,7 +431,7 @@ describe("ProviderRuntimeIngestion", () => { eventId: asEventId("evt-session-state-ready"), provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", payload: { state: "ready", }, @@ -442,7 +450,7 @@ describe("ProviderRuntimeIngestion", () => { it("does not clear active turn when session/thread started arrives mid-turn", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -464,14 +472,14 @@ describe("ProviderRuntimeIngestion", () => { type: "thread.started", eventId: asEventId("evt-thread-started-midturn-lifecycle"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), }); harness.emit({ type: "session.started", eventId: asEventId("evt-session-started-midturn-lifecycle"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), }); @@ -485,7 +493,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.completed", eventId: asEventId("evt-turn-completed-midturn-lifecycle"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-midturn-lifecycle"), status: "completed", @@ -499,7 +507,7 @@ describe("ProviderRuntimeIngestion", () => { it("accepts claude turn lifecycle when seeded thread id is a synthetic placeholder", async () => { const harness = await createHarness(); - const seededAt = new Date().toISOString(); + const seededAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -523,7 +531,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.started", eventId: asEventId("evt-turn-started-claude-placeholder"), provider: ProviderDriverKind.make("claudeAgent"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-claude-placeholder"), }); @@ -539,7 +547,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.completed", eventId: asEventId("evt-turn-completed-claude-placeholder"), provider: ProviderDriverKind.make("claudeAgent"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-claude-placeholder"), status: "completed", @@ -553,7 +561,7 @@ describe("ProviderRuntimeIngestion", () => { it("ignores auxiliary turn completions from a different provider thread", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -574,7 +582,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.completed", eventId: asEventId("evt-turn-completed-aux"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-aux"), status: "completed", @@ -590,7 +598,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.completed", eventId: asEventId("evt-turn-completed-primary"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-primary"), status: "completed", @@ -604,7 +612,7 @@ describe("ProviderRuntimeIngestion", () => { it("ignores non-active turn completion when runtime omits thread id", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -626,7 +634,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.completed", eventId: asEventId("evt-turn-completed-guarded-other"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-guarded-other"), status: "completed", @@ -642,7 +650,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.completed", eventId: asEventId("evt-turn-completed-guarded-main"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-guarded-main"), status: "completed", @@ -656,7 +664,7 @@ describe("ProviderRuntimeIngestion", () => { it("maps canonical content delta/item completed into finalized assistant messages", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "content.delta", @@ -713,7 +721,7 @@ describe("ProviderRuntimeIngestion", () => { it("uses assistant item completion detail when no assistant deltas were streamed", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "item.completed", @@ -745,7 +753,7 @@ describe("ProviderRuntimeIngestion", () => { it("preserves completed tool metadata on projected tool activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "item.completed", @@ -801,7 +809,7 @@ describe("ProviderRuntimeIngestion", () => { it("normalizes command execution activities to ran-command summaries", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "item.completed", @@ -843,7 +851,7 @@ describe("ProviderRuntimeIngestion", () => { it("uses structured read-file paths when available", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "item.completed", @@ -885,7 +893,7 @@ describe("ProviderRuntimeIngestion", () => { it("projects completed plan items into first-class proposed plans", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.proposed.completed", @@ -919,7 +927,7 @@ describe("ProviderRuntimeIngestion", () => { const targetThreadId = asThreadId("thread-implement"); const sourceTurnId = asTurnId("turn-plan-source"); const targetTurnId = asTurnId("turn-plan-implement"); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1050,7 +1058,7 @@ describe("ProviderRuntimeIngestion", () => { }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", }), ); @@ -1075,7 +1083,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.started", eventId: asEventId("evt-plan-target-started"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: targetThreadId, turnId: targetTurnId, }); @@ -1106,7 +1114,7 @@ describe("ProviderRuntimeIngestion", () => { const sourceTurnId = asTurnId("turn-plan-source"); const activeTurnId = asTurnId("turn-already-running"); const staleTurnId = asTurnId("turn-stale-start"); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1219,7 +1227,7 @@ describe("ProviderRuntimeIngestion", () => { }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", }), ); @@ -1227,7 +1235,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.started", eventId: asEventId("evt-turn-started-stale-plan-implementation"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: targetThreadId, turnId: staleTurnId, }); @@ -1259,7 +1267,7 @@ describe("ProviderRuntimeIngestion", () => { const sourceTurnId = asTurnId("turn-plan-source"); const expectedTurnId = asTurnId("turn-plan-implement"); const replayedTurnId = asTurnId("turn-replayed"); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -1381,7 +1389,7 @@ describe("ProviderRuntimeIngestion", () => { }, interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", }), ); @@ -1399,7 +1407,7 @@ describe("ProviderRuntimeIngestion", () => { type: "turn.started", eventId: asEventId("evt-turn-started-unrelated-plan-implementation"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: targetThreadId, turnId: replayedTurnId, }); @@ -1420,7 +1428,7 @@ describe("ProviderRuntimeIngestion", () => { it("finalizes buffered proposed-plan deltas into a first-class proposed plan on turn completion", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -1486,7 +1494,7 @@ describe("ProviderRuntimeIngestion", () => { it("buffers assistant deltas by default until completion", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -1554,7 +1562,7 @@ describe("ProviderRuntimeIngestion", () => { it("flushes and completes buffered assistant text when an approval request opens", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -1614,7 +1622,7 @@ describe("ProviderRuntimeIngestion", () => { it("flushes and completes buffered assistant text when user input is requested", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -1980,7 +1988,7 @@ describe("ProviderRuntimeIngestion", () => { it("streams assistant deltas when thread.turn.start requests streaming mode", async () => { const harness = await createHarness({ serverSettings: { enableAssistantStreaming: true } }); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; await Effect.runPromise( harness.engine.dispatch({ @@ -2072,7 +2080,7 @@ describe("ProviderRuntimeIngestion", () => { it("spills oversized buffered deltas and still finalizes full assistant text", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const oversizedText = "x".repeat(40_000); harness.emit({ @@ -2133,7 +2141,7 @@ describe("ProviderRuntimeIngestion", () => { it("does not duplicate assistant completion when item.completed is followed by turn.completed", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -2219,7 +2227,7 @@ describe("ProviderRuntimeIngestion", () => { it("maps canonical request events into approval activities with requestKind", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "request.opened", @@ -2285,7 +2293,7 @@ describe("ProviderRuntimeIngestion", () => { it("maps runtime.error into errored session state", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "runtime.error", @@ -2312,7 +2320,7 @@ describe("ProviderRuntimeIngestion", () => { it("records runtime.error activities from the typed payload message", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "runtime.error", @@ -2343,7 +2351,7 @@ describe("ProviderRuntimeIngestion", () => { it("keeps the session running when a runtime.warning arrives during an active turn", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "turn.started", @@ -2387,7 +2395,7 @@ describe("ProviderRuntimeIngestion", () => { it("maps session/thread lifecycle and item.started into session/activity projections", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "session.started", @@ -2439,7 +2447,7 @@ describe("ProviderRuntimeIngestion", () => { it("consumes P1 runtime events into thread metadata, diff checkpoints, and activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "thread.metadata.updated", @@ -2573,7 +2581,7 @@ describe("ProviderRuntimeIngestion", () => { it("projects context window updates into normalized thread activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "thread.token-usage.updated", @@ -2625,7 +2633,7 @@ describe("ProviderRuntimeIngestion", () => { it("projects Codex camelCase token usage payloads into normalized thread activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "thread.token-usage.updated", @@ -2678,7 +2686,7 @@ describe("ProviderRuntimeIngestion", () => { it("projects Claude usage snapshots with context window into normalized thread activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "thread.token-usage.updated", @@ -2722,7 +2730,7 @@ describe("ProviderRuntimeIngestion", () => { it("projects compacted thread state into context compaction activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "thread.state.changed", @@ -2752,7 +2760,7 @@ describe("ProviderRuntimeIngestion", () => { it("projects Codex task lifecycle chunks into thread activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "task.started", @@ -2855,7 +2863,7 @@ describe("ProviderRuntimeIngestion", () => { it("projects structured user input request and resolution as thread activities", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "user-input.requested", @@ -2886,7 +2894,7 @@ describe("ProviderRuntimeIngestion", () => { type: "user-input.resolved", eventId: asEventId("evt-user-input-resolved"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-user-input"), requestId: ApprovalRequestId.make("req-user-input-1"), @@ -2928,7 +2936,7 @@ describe("ProviderRuntimeIngestion", () => { it("continues processing runtime events after a single event handler failure", async () => { const harness = await createHarness(); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; harness.emit({ type: "content.delta", @@ -2948,7 +2956,7 @@ describe("ProviderRuntimeIngestion", () => { type: "runtime.error", eventId: asEventId("evt-runtime-error-after-failure"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-after-failure"), payload: { diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts index 2e86623f8dc..2c07ac91b1e 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts @@ -17,7 +17,13 @@ import { type OrchestrationThreadActivity, type ProviderRuntimeEvent, } from "@t3tools/contracts"; -import { Cache, Cause, Duration, Effect, Layer, Option, Stream } from "effect"; +import * as Cache from "effect/Cache"; +import * as Cause from "effect/Cause"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Stream from "effect/Stream"; import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; import { ProviderService } from "../../provider/Services/ProviderService.ts"; diff --git a/apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts b/apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts index 5c3148c9434..586281dc9c6 100644 --- a/apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts +++ b/apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts @@ -8,7 +8,10 @@ * * @module RuntimeReceiptBus */ -import { Effect, Layer, PubSub, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Stream from "effect/Stream"; import { RuntimeReceiptBus, diff --git a/apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts b/apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts index 4fdac41175e..701835da16c 100644 --- a/apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts @@ -1,5 +1,7 @@ import { ThreadId } from "@t3tools/contracts"; -import { Cause, Effect, Exit } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; import { describe, expect, it } from "vitest"; import { logCleanupCauseUnlessInterrupted } from "./ThreadDeletionReactor.ts"; diff --git a/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts b/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts index db3d14fa6db..4bbf5ca2149 100644 --- a/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts +++ b/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts @@ -1,6 +1,9 @@ import type { OrchestrationEvent } from "@t3tools/contracts"; import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; -import { Cause, Effect, Layer, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Stream from "effect/Stream"; import { ProviderService } from "../../provider/Services/ProviderService.ts"; import { TerminalManager } from "../../terminal/Services/Manager.ts"; diff --git a/apps/server/src/orchestration/Normalizer.ts b/apps/server/src/orchestration/Normalizer.ts index 811d9b8a1c7..95d29e3d6d2 100644 --- a/apps/server/src/orchestration/Normalizer.ts +++ b/apps/server/src/orchestration/Normalizer.ts @@ -1,4 +1,6 @@ -import { Effect, FileSystem, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import { type ClientOrchestrationCommand, type OrchestrationCommand, diff --git a/apps/server/src/orchestration/Services/CheckpointReactor.ts b/apps/server/src/orchestration/Services/CheckpointReactor.ts index 9e9c83beb47..bd3ee3e88f9 100644 --- a/apps/server/src/orchestration/Services/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Services/CheckpointReactor.ts @@ -6,8 +6,9 @@ * * @module CheckpointReactor */ -import { Context } from "effect"; -import type { Effect, Scope } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Scope from "effect/Scope"; /** * CheckpointReactorShape - Service API for checkpoint reactor lifecycle. diff --git a/apps/server/src/orchestration/Services/OrchestrationEngine.ts b/apps/server/src/orchestration/Services/OrchestrationEngine.ts index 39270bb0c4a..acb2b7b042d 100644 --- a/apps/server/src/orchestration/Services/OrchestrationEngine.ts +++ b/apps/server/src/orchestration/Services/OrchestrationEngine.ts @@ -11,8 +11,9 @@ * @module OrchestrationEngineService */ import type { OrchestrationCommand, OrchestrationEvent } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect, Stream } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; import type { OrchestrationDispatchError } from "../Errors.ts"; import type { OrchestrationEventStoreError } from "../../persistence/Errors.ts"; diff --git a/apps/server/src/orchestration/Services/OrchestrationReactor.ts b/apps/server/src/orchestration/Services/OrchestrationReactor.ts index a3edeaac61d..eb2d95954bb 100644 --- a/apps/server/src/orchestration/Services/OrchestrationReactor.ts +++ b/apps/server/src/orchestration/Services/OrchestrationReactor.ts @@ -6,8 +6,9 @@ * * @module OrchestrationReactor */ -import { Context } from "effect"; -import type { Effect, Scope } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Scope from "effect/Scope"; /** * OrchestrationReactorShape - Service API for orchestration reactor lifecycle. diff --git a/apps/server/src/orchestration/Services/ProjectionPipeline.ts b/apps/server/src/orchestration/Services/ProjectionPipeline.ts index 349f3430ad6..bb4736ca57a 100644 --- a/apps/server/src/orchestration/Services/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Services/ProjectionPipeline.ts @@ -7,8 +7,8 @@ * @module OrchestrationProjectionPipeline */ import type { OrchestrationEvent } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../../persistence/Errors.ts"; diff --git a/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts b/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts index 9d64307d3dd..2df890d547e 100644 --- a/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts +++ b/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts @@ -17,9 +17,9 @@ import type { ProjectId, ThreadId, } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Option } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Option from "effect/Option"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../../persistence/Errors.ts"; diff --git a/apps/server/src/orchestration/Services/ProviderCommandReactor.ts b/apps/server/src/orchestration/Services/ProviderCommandReactor.ts index c8b8580682a..65aa9949fe1 100644 --- a/apps/server/src/orchestration/Services/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Services/ProviderCommandReactor.ts @@ -6,8 +6,9 @@ * * @module ProviderCommandReactor */ -import { Context } from "effect"; -import type { Effect, Scope } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Scope from "effect/Scope"; /** * ProviderCommandReactorShape - Service API for provider command reactors. diff --git a/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts index 831ce3a6f0c..b6fa2711b94 100644 --- a/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts +++ b/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts @@ -6,8 +6,9 @@ * * @module ProviderRuntimeIngestionService */ -import { Context } from "effect"; -import type { Effect, Scope } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Scope from "effect/Scope"; /** * ProviderRuntimeIngestionShape - Service API for runtime ingestion lifecycle. diff --git a/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts b/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts index 53038006dea..0b880ee6999 100644 --- a/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts +++ b/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts @@ -15,8 +15,10 @@ * @module RuntimeReceiptBus */ import { CheckpointRef, IsoDateTime, NonNegativeInt, ThreadId, TurnId } from "@t3tools/contracts"; -import { Schema, Context } from "effect"; -import type { Effect, Stream } from "effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; export const CheckpointBaselineCapturedReceipt = Schema.Struct({ type: Schema.Literal("checkpoint.baseline.captured"), diff --git a/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts b/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts index 6cf1f0bba8d..7c6718965a6 100644 --- a/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts +++ b/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts @@ -6,8 +6,9 @@ * * @module ThreadDeletionReactor */ -import { Context } from "effect"; -import type { Effect, Scope } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Scope from "effect/Scope"; /** * ThreadDeletionReactorShape - Service API for thread deletion cleanup. diff --git a/apps/server/src/orchestration/commandInvariants.test.ts b/apps/server/src/orchestration/commandInvariants.test.ts index 1e33a355d06..d52f0535fbb 100644 --- a/apps/server/src/orchestration/commandInvariants.test.ts +++ b/apps/server/src/orchestration/commandInvariants.test.ts @@ -9,7 +9,7 @@ import { type OrchestrationReadModel, ProviderInstanceId, } from "@t3tools/contracts"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { findThreadById, @@ -19,7 +19,7 @@ import { requireThreadAbsent, } from "./commandInvariants.ts"; -const now = new Date().toISOString(); +const now = "2026-01-01T00:00:00.000Z"; const readModel: OrchestrationReadModel = { snapshotSequence: 2, diff --git a/apps/server/src/orchestration/commandInvariants.ts b/apps/server/src/orchestration/commandInvariants.ts index 009fdb190e5..f5ab794bce7 100644 --- a/apps/server/src/orchestration/commandInvariants.ts +++ b/apps/server/src/orchestration/commandInvariants.ts @@ -6,7 +6,7 @@ import type { ProjectId, ThreadId, } from "@t3tools/contracts"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { OrchestrationCommandInvariantError } from "./Errors.ts"; diff --git a/apps/server/src/orchestration/decider.delete.test.ts b/apps/server/src/orchestration/decider.delete.test.ts index 548fcc0b68e..dcdaaecc1fb 100644 --- a/apps/server/src/orchestration/decider.delete.test.ts +++ b/apps/server/src/orchestration/decider.delete.test.ts @@ -9,7 +9,7 @@ import { type OrchestrationReadModel, ProviderInstanceId, } from "@t3tools/contracts"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { describe, expect, it } from "vitest"; import { decideOrchestrationCommand } from "./decider.ts"; @@ -21,7 +21,7 @@ const asProjectId = (value: string): ProjectId => ProjectId.make(value); const asThreadId = (value: string): ThreadId => ThreadId.make(value); async function seedReadModel(): Promise { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const initial = createEmptyReadModel(now); const withProject = await Effect.runPromise( projectEvent(initial, { diff --git a/apps/server/src/orchestration/decider.projectScripts.test.ts b/apps/server/src/orchestration/decider.projectScripts.test.ts index 23566099196..c5b7086eb12 100644 --- a/apps/server/src/orchestration/decider.projectScripts.test.ts +++ b/apps/server/src/orchestration/decider.projectScripts.test.ts @@ -9,7 +9,7 @@ import { } from "@t3tools/contracts"; import { createModelSelection } from "@t3tools/shared/model"; import { describe, expect, it } from "vitest"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { decideOrchestrationCommand } from "./decider.ts"; import { createEmptyReadModel, projectEvent } from "./projector.ts"; @@ -20,7 +20,7 @@ const asMessageId = (value: string): MessageId => MessageId.make(value); describe("decider project scripts", () => { it("emits empty scripts on project.create", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const readModel = createEmptyReadModel(now); const result = await Effect.runPromise( @@ -43,7 +43,7 @@ describe("decider project scripts", () => { }); it("propagates scripts in project.meta.update payload", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const initial = createEmptyReadModel(now); const readModel = await Effect.runPromise( projectEvent(initial, { @@ -97,7 +97,7 @@ describe("decider project scripts", () => { }); it("emits user message and turn-start-requested events for thread.turn.start", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const initial = createEmptyReadModel(now); const withProject = await Effect.runPromise( projectEvent(initial, { @@ -198,7 +198,7 @@ describe("decider project scripts", () => { }); it("emits thread.runtime-mode-set from thread.runtime-mode.set", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const initial = createEmptyReadModel(now); const withProject = await Effect.runPromise( projectEvent(initial, { @@ -280,7 +280,7 @@ describe("decider project scripts", () => { }); it("emits thread.interaction-mode-set from thread.interaction-mode.set", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const initial = createEmptyReadModel(now); const withProject = await Effect.runPromise( projectEvent(initial, { diff --git a/apps/server/src/orchestration/decider.ts b/apps/server/src/orchestration/decider.ts index 05ae5b0eb00..3a6e42997a8 100644 --- a/apps/server/src/orchestration/decider.ts +++ b/apps/server/src/orchestration/decider.ts @@ -3,7 +3,8 @@ import type { OrchestrationEvent, OrchestrationReadModel, } from "@t3tools/contracts"; -import { DateTime, Effect } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; import { OrchestrationCommandInvariantError } from "./Errors.ts"; import { diff --git a/apps/server/src/orchestration/http.ts b/apps/server/src/orchestration/http.ts index 959d841f673..f24f808c6f8 100644 --- a/apps/server/src/orchestration/http.ts +++ b/apps/server/src/orchestration/http.ts @@ -4,7 +4,7 @@ import { OrchestrationGetSnapshotError, type OrchestrationReadModel, } from "@t3tools/contracts"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { ServerAuth } from "../auth/Services/ServerAuth.ts"; diff --git a/apps/server/src/orchestration/projector.test.ts b/apps/server/src/orchestration/projector.test.ts index b50bc2da598..01dcb9abeac 100644 --- a/apps/server/src/orchestration/projector.test.ts +++ b/apps/server/src/orchestration/projector.test.ts @@ -6,7 +6,7 @@ import { ThreadId, type OrchestrationEvent, } from "@t3tools/contracts"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { describe, expect, it } from "vitest"; import { createEmptyReadModel, projectEvent } from "./projector.ts"; @@ -40,7 +40,7 @@ function makeEvent(input: { describe("orchestration projector", () => { it("applies thread.created events", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const model = createEmptyReadModel(now); const next = await Effect.runPromise( @@ -100,7 +100,7 @@ describe("orchestration projector", () => { }); it("fails when event payload cannot be decoded by runtime schema", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const model = createEmptyReadModel(now); await expect( @@ -134,8 +134,8 @@ describe("orchestration projector", () => { }); it("applies thread.archived and thread.unarchived events", async () => { - const now = new Date().toISOString(); - const later = new Date(Date.parse(now) + 1_000).toISOString(); + const now = "2026-01-01T00:00:00.000Z"; + const later = "2026-01-01T00:00:01.000Z"; const created = await Effect.runPromise( projectEvent( createEmptyReadModel(now), @@ -206,7 +206,7 @@ describe("orchestration projector", () => { }); it("keeps projector forward-compatible for unhandled event types", async () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const model = createEmptyReadModel(now); const next = await Effect.runPromise( diff --git a/apps/server/src/orchestration/projector.ts b/apps/server/src/orchestration/projector.ts index deb8a6d44d7..bf0d7b85df5 100644 --- a/apps/server/src/orchestration/projector.ts +++ b/apps/server/src/orchestration/projector.ts @@ -5,7 +5,8 @@ import { OrchestrationSession, OrchestrationThread, } from "@t3tools/contracts"; -import { Effect, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; import { toProjectorDecodeError, type OrchestrationProjectorDecodeError } from "./Errors.ts"; import { diff --git a/apps/server/src/orchestration/runtimeLayer.ts b/apps/server/src/orchestration/runtimeLayer.ts index 1b964709aec..a2ed5875950 100644 --- a/apps/server/src/orchestration/runtimeLayer.ts +++ b/apps/server/src/orchestration/runtimeLayer.ts @@ -1,4 +1,4 @@ -import { Layer } from "effect"; +import * as Layer from "effect/Layer"; import { OrchestrationCommandReceiptRepositoryLive } from "../persistence/Layers/OrchestrationCommandReceipts.ts"; import { OrchestrationEventStoreLive } from "../persistence/Layers/OrchestrationEventStore.ts"; diff --git a/apps/server/src/os-jank.test.ts b/apps/server/src/os-jank.test.ts deleted file mode 100644 index c49a4120a54..00000000000 --- a/apps/server/src/os-jank.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; - -import { fixPath } from "./os-jank.ts"; - -describe("fixPath", () => { - it("hydrates PATH on linux using the resolved login shell", () => { - const env: NodeJS.ProcessEnv = { - SHELL: "/bin/zsh", - PATH: "/Users/test/.local/bin:/usr/bin", - }; - const readPath = vi.fn(() => "/opt/homebrew/bin:/usr/bin"); - - fixPath({ - env, - platform: "linux", - readPath, - }); - - expect(readPath).toHaveBeenCalledWith("/bin/zsh"); - expect(env.PATH).toBe("/opt/homebrew/bin:/usr/bin:/Users/test/.local/bin"); - }); - - it("falls back to launchctl PATH on macOS when shell probing fails", () => { - const env: NodeJS.ProcessEnv = { - SHELL: "/opt/homebrew/bin/nu", - PATH: "/usr/bin", - }; - const readPath = vi - .fn() - .mockImplementationOnce(() => { - throw new Error("unknown flag"); - }) - .mockImplementationOnce(() => undefined); - const readLaunchctlPath = vi.fn(() => "/opt/homebrew/bin:/usr/bin"); - const logWarning = vi.fn(); - - fixPath({ - env, - platform: "darwin", - readPath, - readLaunchctlPath, - userShell: "/bin/zsh", - logWarning, - }); - - expect(readPath).toHaveBeenNthCalledWith(1, "/opt/homebrew/bin/nu"); - expect(readPath).toHaveBeenNthCalledWith(2, "/bin/zsh"); - expect(readLaunchctlPath).toHaveBeenCalledTimes(1); - expect(logWarning).toHaveBeenCalledWith( - "Failed to read PATH from login shell /opt/homebrew/bin/nu.", - expect.any(Error), - ); - expect(env.PATH).toBe("/opt/homebrew/bin:/usr/bin"); - }); - - it("repairs PATH on Windows by merging PowerShell PATH with inherited PATH", () => { - const env: NodeJS.ProcessEnv = { - PATH: "C:\\Windows\\System32", - APPDATA: "C:\\Users\\testuser\\AppData\\Roaming", - LOCALAPPDATA: "C:\\Users\\testuser\\AppData\\Local", - USERPROFILE: "C:\\Users\\testuser", - }; - const readWindowsEnvironment = vi.fn(() => ({ - PATH: "C:\\Custom\\Bin;C:\\Windows\\System32", - })); - const isWindowsCommandAvailable = vi.fn(() => true); - - fixPath({ - env, - platform: "win32", - readWindowsEnvironment, - isWindowsCommandAvailable, - }); - - expect(readWindowsEnvironment).toHaveBeenCalledWith(["PATH"], { loadProfile: false }); - expect(env.PATH).toBe( - [ - "C:\\Users\\testuser\\AppData\\Roaming\\npm", - "C:\\Users\\testuser\\AppData\\Local\\Programs\\nodejs", - "C:\\Users\\testuser\\AppData\\Local\\Volta\\bin", - "C:\\Users\\testuser\\AppData\\Local\\pnpm", - "C:\\Users\\testuser\\.bun\\bin", - "C:\\Users\\testuser\\scoop\\shims", - "C:\\Custom\\Bin", - "C:\\Windows\\System32", - ].join(";"), - ); - }); - - it("applies profile-derived fnm variables on Windows when node is missing", () => { - const env: NodeJS.ProcessEnv = { - PATH: "C:\\Windows\\System32", - APPDATA: "C:\\Users\\testuser\\AppData\\Roaming", - LOCALAPPDATA: "C:\\Users\\testuser\\AppData\\Local", - USERPROFILE: "C:\\Users\\testuser", - }; - const readWindowsEnvironment = vi.fn( - (_names: ReadonlyArray, options?: { loadProfile?: boolean }) => - options?.loadProfile - ? { - PATH: "C:\\Profile\\Node;C:\\Windows\\System32", - FNM_DIR: "C:\\Users\\testuser\\AppData\\Roaming\\fnm", - FNM_MULTISHELL_PATH: "C:\\Users\\testuser\\AppData\\Local\\fnm_multishells\\123", - } - : { PATH: "C:\\Custom\\Bin;C:\\Windows\\System32" }, - ); - const isWindowsCommandAvailable = vi.fn().mockReturnValueOnce(false).mockReturnValueOnce(true); - - fixPath({ - env, - platform: "win32", - readWindowsEnvironment, - isWindowsCommandAvailable, - }); - - expect(env.PATH).toBe( - [ - "C:\\Profile\\Node", - "C:\\Windows\\System32", - "C:\\Users\\testuser\\AppData\\Roaming\\npm", - "C:\\Users\\testuser\\AppData\\Local\\Programs\\nodejs", - "C:\\Users\\testuser\\AppData\\Local\\Volta\\bin", - "C:\\Users\\testuser\\AppData\\Local\\pnpm", - "C:\\Users\\testuser\\.bun\\bin", - "C:\\Users\\testuser\\scoop\\shims", - "C:\\Custom\\Bin", - ].join(";"), - ); - expect(env.FNM_DIR).toBe("C:\\Users\\testuser\\AppData\\Roaming\\fnm"); - expect(env.FNM_MULTISHELL_PATH).toBe( - "C:\\Users\\testuser\\AppData\\Local\\fnm_multishells\\123", - ); - }); - - it("preserves baseline PATH on Windows when the profile probe fails", () => { - const env: NodeJS.ProcessEnv = { - PATH: "C:\\Windows\\System32", - APPDATA: "C:\\Users\\testuser\\AppData\\Roaming", - USERPROFILE: "C:\\Users\\testuser", - }; - const readWindowsEnvironment = vi.fn( - (_names: ReadonlyArray, options?: { loadProfile?: boolean }) => { - if (options?.loadProfile) { - throw new Error("profile load failed"); - } - return { PATH: "C:\\Custom\\Bin;C:\\Windows\\System32" }; - }, - ); - const isWindowsCommandAvailable = vi.fn(() => false); - - fixPath({ - env, - platform: "win32", - readWindowsEnvironment, - isWindowsCommandAvailable, - }); - - expect(env.PATH).toBe( - [ - "C:\\Users\\testuser\\AppData\\Roaming\\npm", - "C:\\Users\\testuser\\.bun\\bin", - "C:\\Users\\testuser\\scoop\\shims", - "C:\\Custom\\Bin", - "C:\\Windows\\System32", - ].join(";"), - ); - }); - - it("does nothing on unsupported platforms", () => { - const env: NodeJS.ProcessEnv = { - SHELL: "C:/Program Files/Git/bin/bash.exe", - PATH: "C:\\Windows\\System32", - }; - const readPath = vi.fn(() => "/usr/local/bin:/usr/bin"); - - fixPath({ - env, - platform: "freebsd", - readPath, - }); - - expect(readPath).not.toHaveBeenCalled(); - expect(env.PATH).toBe("C:\\Windows\\System32"); - }); -}); diff --git a/apps/server/src/os-jank.ts b/apps/server/src/os-jank.ts index 47574c14c12..93a40ae7e19 100644 --- a/apps/server/src/os-jank.ts +++ b/apps/server/src/os-jank.ts @@ -1,5 +1,6 @@ -import * as OS from "node:os"; -import { Effect, Path } from "effect"; +import * as NodeOS from "node:os"; +import * as Effect from "effect/Effect"; +import * as Path from "effect/Path"; import { readPathFromLoginShell, readEnvironmentFromWindowsShell, @@ -17,7 +18,9 @@ type WindowsCommandAvailabilityChecker = ( ) => boolean; function logPathHydrationWarning(message: string, error?: unknown): void { - console.warn(`[server] ${message}`, error instanceof Error ? error.message : (error ?? "")); + process.stderr.write( + `[server] ${message} ${error instanceof Error ? error.message : (error ?? "")}\n`, + ); } export function fixPath( @@ -84,10 +87,10 @@ export function fixPath( export const expandHomePath = Effect.fn(function* (input: string) { const { join } = yield* Path.Path; if (input === "~") { - return OS.homedir(); + return NodeOS.homedir(); } if (input.startsWith("~/") || input.startsWith("~\\")) { - return join(OS.homedir(), input.slice(2)); + return join(NodeOS.homedir(), input.slice(2)); } return input; }); @@ -95,7 +98,7 @@ export const expandHomePath = Effect.fn(function* (input: string) { export const resolveBaseDir = Effect.fn(function* (raw: string | undefined) { const { join, resolve } = yield* Path.Path; if (!raw || raw.trim().length === 0) { - return join(OS.homedir(), ".t3"); + return join(NodeOS.homedir(), ".t3"); } return resolve(yield* expandHomePath(raw.trim())); }); diff --git a/apps/server/src/pathExpansion.test.ts b/apps/server/src/pathExpansion.test.ts index 40ca25e6c8f..c195490eed9 100644 --- a/apps/server/src/pathExpansion.test.ts +++ b/apps/server/src/pathExpansion.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import { homedir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; diff --git a/apps/server/src/pathExpansion.ts b/apps/server/src/pathExpansion.ts index 18060c3e554..170d83c54d0 100644 --- a/apps/server/src/pathExpansion.ts +++ b/apps/server/src/pathExpansion.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import { homedir } from "node:os"; import { join } from "node:path"; diff --git a/apps/server/src/persistence/Errors.ts b/apps/server/src/persistence/Errors.ts index eb05bf5ae95..b122062844e 100644 --- a/apps/server/src/persistence/Errors.ts +++ b/apps/server/src/persistence/Errors.ts @@ -1,4 +1,5 @@ -import { Schema, SchemaIssue } from "effect"; +import * as Schema from "effect/Schema"; +import * as SchemaIssue from "effect/SchemaIssue"; // =============================== // Core Persistence Errors diff --git a/apps/server/src/persistence/Layers/AuthPairingLinks.ts b/apps/server/src/persistence/Layers/AuthPairingLinks.ts index 9767f24993a..15c572ad0a1 100644 --- a/apps/server/src/persistence/Layers/AuthPairingLinks.ts +++ b/apps/server/src/persistence/Layers/AuthPairingLinks.ts @@ -1,6 +1,8 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; import { toPersistenceDecodeError, diff --git a/apps/server/src/persistence/Layers/AuthSessions.ts b/apps/server/src/persistence/Layers/AuthSessions.ts index 66e02ed2a72..64b8146f927 100644 --- a/apps/server/src/persistence/Layers/AuthSessions.ts +++ b/apps/server/src/persistence/Layers/AuthSessions.ts @@ -1,7 +1,10 @@ import { AuthSessionId } from "@t3tools/contracts"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Option, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { toPersistenceDecodeError, diff --git a/apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts b/apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts index 33f28567428..989f5d78f8d 100644 --- a/apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts +++ b/apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts @@ -1,6 +1,7 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { toPersistenceSqlError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts b/apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts index 80526986a70..f0d871a9750 100644 --- a/apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts +++ b/apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts @@ -1,6 +1,9 @@ import { CommandId, EventId, ProjectId } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; -import { Effect, Layer, Schema, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { PersistenceDecodeError } from "../Errors.ts"; @@ -17,7 +20,7 @@ layer("OrchestrationEventStore", (it) => { Effect.gen(function* () { const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const appended = yield* eventStore.append({ type: "project.created", @@ -69,7 +72,7 @@ layer("OrchestrationEventStore", (it) => { Effect.gen(function* () { const eventStore = yield* OrchestrationEventStore; const sql = yield* SqlClient.SqlClient; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* sql` INSERT INTO orchestration_events ( diff --git a/apps/server/src/persistence/Layers/OrchestrationEventStore.ts b/apps/server/src/persistence/Layers/OrchestrationEventStore.ts index 4d81cf5e8d7..18d0e9aa578 100644 --- a/apps/server/src/persistence/Layers/OrchestrationEventStore.ts +++ b/apps/server/src/persistence/Layers/OrchestrationEventStore.ts @@ -13,7 +13,10 @@ import { } from "@t3tools/contracts"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Schema, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { toPersistenceDecodeError, diff --git a/apps/server/src/persistence/Layers/ProjectionCheckpoints.ts b/apps/server/src/persistence/Layers/ProjectionCheckpoints.ts index 26ef8fed1b9..cfa5658d0e9 100644 --- a/apps/server/src/persistence/Layers/ProjectionCheckpoints.ts +++ b/apps/server/src/persistence/Layers/ProjectionCheckpoints.ts @@ -1,7 +1,11 @@ import { OrchestrationCheckpointFile } from "@t3tools/contracts"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Option, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Struct from "effect/Struct"; import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; import { diff --git a/apps/server/src/persistence/Layers/ProjectionPendingApprovals.ts b/apps/server/src/persistence/Layers/ProjectionPendingApprovals.ts index 25443a70dca..253f6e13b97 100644 --- a/apps/server/src/persistence/Layers/ProjectionPendingApprovals.ts +++ b/apps/server/src/persistence/Layers/ProjectionPendingApprovals.ts @@ -1,6 +1,7 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { toPersistenceSqlError } from "../Errors.ts"; import { diff --git a/apps/server/src/persistence/Layers/ProjectionProjects.ts b/apps/server/src/persistence/Layers/ProjectionProjects.ts index 7ff19f55aea..c1ca6d3104e 100644 --- a/apps/server/src/persistence/Layers/ProjectionProjects.ts +++ b/apps/server/src/persistence/Layers/ProjectionProjects.ts @@ -1,6 +1,9 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import * as Struct from "effect/Struct"; import { ModelSelection, ProjectScript } from "@t3tools/contracts"; import { toPersistenceSqlError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Layers/ProjectionRepositories.test.ts b/apps/server/src/persistence/Layers/ProjectionRepositories.test.ts index fc5f7a5c155..a2069e62a14 100644 --- a/apps/server/src/persistence/Layers/ProjectionRepositories.test.ts +++ b/apps/server/src/persistence/Layers/ProjectionRepositories.test.ts @@ -1,6 +1,8 @@ import { ProjectId, ThreadId, ProviderInstanceId } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { SqlitePersistenceMemory } from "./Sqlite.ts"; @@ -46,11 +48,12 @@ projectionRepositoriesLayer("Projection repositories", (it) => { `; const row = rows[0]; if (!row) { - return yield* Effect.fail(new Error("Expected projection_projects row to exist.")); + return yield* Effect.die("Expected projection_projects row to exist."); } assert.strictEqual( row.defaultModelSelection, + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", @@ -104,11 +107,12 @@ projectionRepositoriesLayer("Projection repositories", (it) => { `; const row = rows[0]; if (!row) { - return yield* Effect.fail(new Error("Expected projection_threads row to exist.")); + return yield* Effect.die("Expected projection_threads row to exist."); } assert.strictEqual( row.modelSelection, + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ instanceId: ProviderInstanceId.make("claudeAgent"), model: "claude-opus-4-6", diff --git a/apps/server/src/persistence/Layers/ProjectionState.ts b/apps/server/src/persistence/Layers/ProjectionState.ts index 7c2b8110c70..f630fe51e03 100644 --- a/apps/server/src/persistence/Layers/ProjectionState.ts +++ b/apps/server/src/persistence/Layers/ProjectionState.ts @@ -1,7 +1,9 @@ import { NonNegativeInt } from "@t3tools/contracts"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; import { toPersistenceSqlError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Layers/ProjectionThreadActivities.ts b/apps/server/src/persistence/Layers/ProjectionThreadActivities.ts index 8e88cfa7853..2f4815f9654 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreadActivities.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreadActivities.ts @@ -1,7 +1,10 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import { NonNegativeInt } from "@t3tools/contracts"; -import { Effect, Layer, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import * as Struct from "effect/Struct"; import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Layers/ProjectionThreadMessages.test.ts b/apps/server/src/persistence/Layers/ProjectionThreadMessages.test.ts index 8bbe723bf37..b1f394a9e57 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreadMessages.test.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreadMessages.test.ts @@ -1,6 +1,7 @@ import { MessageId, ThreadId } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { ProjectionThreadMessageRepository } from "../Services/ProjectionThreadMessages.ts"; import { ProjectionThreadMessageRepositoryLive } from "./ProjectionThreadMessages.ts"; diff --git a/apps/server/src/persistence/Layers/ProjectionThreadMessages.ts b/apps/server/src/persistence/Layers/ProjectionThreadMessages.ts index 13b7086cecd..71919166886 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreadMessages.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreadMessages.ts @@ -1,6 +1,10 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Option, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Struct from "effect/Struct"; import { ChatAttachment } from "@t3tools/contracts"; import { toPersistenceSqlError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Layers/ProjectionThreadProposedPlans.ts b/apps/server/src/persistence/Layers/ProjectionThreadProposedPlans.ts index ccd322feb23..63aed1a1670 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreadProposedPlans.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreadProposedPlans.ts @@ -1,4 +1,5 @@ -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; diff --git a/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts b/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts index 80d241436f8..dcb750983a0 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreadSessions.ts @@ -1,6 +1,7 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { toPersistenceSqlError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Layers/ProjectionThreads.ts b/apps/server/src/persistence/Layers/ProjectionThreads.ts index 57fb88c371c..1baeb375c15 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreads.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreads.ts @@ -1,6 +1,9 @@ import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import * as Struct from "effect/Struct"; import { toPersistenceSqlError } from "../Errors.ts"; import { diff --git a/apps/server/src/persistence/Layers/ProjectionTurns.ts b/apps/server/src/persistence/Layers/ProjectionTurns.ts index 9b6c9c57710..bd57a4eaa30 100644 --- a/apps/server/src/persistence/Layers/ProjectionTurns.ts +++ b/apps/server/src/persistence/Layers/ProjectionTurns.ts @@ -1,7 +1,11 @@ import { OrchestrationCheckpointFile } from "@t3tools/contracts"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Option, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Struct from "effect/Struct"; import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; import { diff --git a/apps/server/src/persistence/Layers/ProviderSessionRuntime.ts b/apps/server/src/persistence/Layers/ProviderSessionRuntime.ts index 778e0c6d2ee..9ee5c82bb53 100644 --- a/apps/server/src/persistence/Layers/ProviderSessionRuntime.ts +++ b/apps/server/src/persistence/Layers/ProviderSessionRuntime.ts @@ -1,7 +1,11 @@ import { ThreadId } from "@t3tools/contracts"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; -import { Effect, Layer, Option, Schema, Struct } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Struct from "effect/Struct"; import { toPersistenceDecodeError, diff --git a/apps/server/src/persistence/Layers/Sqlite.ts b/apps/server/src/persistence/Layers/Sqlite.ts index 58556099db1..3bc1ec4d2d2 100644 --- a/apps/server/src/persistence/Layers/Sqlite.ts +++ b/apps/server/src/persistence/Layers/Sqlite.ts @@ -1,4 +1,7 @@ -import { Effect, Layer, FileSystem, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; diff --git a/apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts b/apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts index d74e9fe08cd..1e64519ff4f 100644 --- a/apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts +++ b/apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; @@ -264,7 +265,7 @@ layer("016_CanonicalizeModelSelections", (it) => { FROM orchestration_events ORDER BY rowid ASC `; - + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(eventRows[0]!.payloadJson), { projectId: "project-1", title: "Project", @@ -280,7 +281,7 @@ layer("016_CanonicalizeModelSelections", (it) => { createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); - + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(eventRows[1]!.payloadJson), { projectId: "project-2", title: "Fallback Project", @@ -296,7 +297,7 @@ layer("016_CanonicalizeModelSelections", (it) => { createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); - + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(eventRows[2]!.payloadJson), { projectId: "project-3", title: "Null Model Project", @@ -306,7 +307,7 @@ layer("016_CanonicalizeModelSelections", (it) => { createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); - + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(eventRows[3]!.payloadJson), { threadId: "thread-1", projectId: "project-1", @@ -326,7 +327,7 @@ layer("016_CanonicalizeModelSelections", (it) => { createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); - + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(eventRows[4]!.payloadJson), { threadId: "thread-2", projectId: "project-1", @@ -345,7 +346,7 @@ layer("016_CanonicalizeModelSelections", (it) => { createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); - + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(eventRows[5]!.payloadJson), { threadId: "thread-1", turnId: "turn-1", @@ -359,7 +360,7 @@ layer("016_CanonicalizeModelSelections", (it) => { }, deliveryMode: "buffered", }); - + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(eventRows[6]!.payloadJson), { threadId: "thread-3", projectId: "project-1", diff --git a/apps/server/src/persistence/Migrations/019_ProjectionSnapshotLookupIndexes.test.ts b/apps/server/src/persistence/Migrations/019_ProjectionSnapshotLookupIndexes.test.ts index 6207a9bcb6a..2011613a9f9 100644 --- a/apps/server/src/persistence/Migrations/019_ProjectionSnapshotLookupIndexes.test.ts +++ b/apps/server/src/persistence/Migrations/019_ProjectionSnapshotLookupIndexes.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; diff --git a/apps/server/src/persistence/Migrations/024_BackfillProjectionThreadShellSummary.test.ts b/apps/server/src/persistence/Migrations/024_BackfillProjectionThreadShellSummary.test.ts index cc911d2469c..71dfe6fd004 100644 --- a/apps/server/src/persistence/Migrations/024_BackfillProjectionThreadShellSummary.test.ts +++ b/apps/server/src/persistence/Migrations/024_BackfillProjectionThreadShellSummary.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; diff --git a/apps/server/src/persistence/Migrations/025_CleanupInvalidProjectionPendingApprovals.test.ts b/apps/server/src/persistence/Migrations/025_CleanupInvalidProjectionPendingApprovals.test.ts index 060cd471b30..752b1676efa 100644 --- a/apps/server/src/persistence/Migrations/025_CleanupInvalidProjectionPendingApprovals.test.ts +++ b/apps/server/src/persistence/Migrations/025_CleanupInvalidProjectionPendingApprovals.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; diff --git a/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts b/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts index ffc42521c90..5160b4ab34b 100644 --- a/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts +++ b/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; diff --git a/apps/server/src/persistence/Migrations/027_028_ProviderInstanceIdColumns.test.ts b/apps/server/src/persistence/Migrations/027_028_ProviderInstanceIdColumns.test.ts index 3233f5043af..5c0d7e2a7a8 100644 --- a/apps/server/src/persistence/Migrations/027_028_ProviderInstanceIdColumns.test.ts +++ b/apps/server/src/persistence/Migrations/027_028_ProviderInstanceIdColumns.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; diff --git a/apps/server/src/persistence/Migrations/029_ProjectionThreadDetailOrderingIndexes.test.ts b/apps/server/src/persistence/Migrations/029_ProjectionThreadDetailOrderingIndexes.test.ts index 6565e81e5c9..4b0aa186cb4 100644 --- a/apps/server/src/persistence/Migrations/029_ProjectionThreadDetailOrderingIndexes.test.ts +++ b/apps/server/src/persistence/Migrations/029_ProjectionThreadDetailOrderingIndexes.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { runMigrations } from "../Migrations.ts"; diff --git a/apps/server/src/persistence/NodeSqliteClient.test.ts b/apps/server/src/persistence/NodeSqliteClient.test.ts index a055d409ba2..43023abf60a 100644 --- a/apps/server/src/persistence/NodeSqliteClient.test.ts +++ b/apps/server/src/persistence/NodeSqliteClient.test.ts @@ -1,5 +1,5 @@ import { assert, it } from "@effect/vitest"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqliteClient from "./NodeSqliteClient.ts"; diff --git a/apps/server/src/persistence/Services/AuthPairingLinks.ts b/apps/server/src/persistence/Services/AuthPairingLinks.ts index d03e67cbb5b..c81ce51a3f4 100644 --- a/apps/server/src/persistence/Services/AuthPairingLinks.ts +++ b/apps/server/src/persistence/Services/AuthPairingLinks.ts @@ -1,5 +1,7 @@ -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { AuthPairingLinkRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/AuthSessions.ts b/apps/server/src/persistence/Services/AuthSessions.ts index 567d3cca5c1..a92573f9f9e 100644 --- a/apps/server/src/persistence/Services/AuthSessions.ts +++ b/apps/server/src/persistence/Services/AuthSessions.ts @@ -1,6 +1,8 @@ import { AuthClientMetadataDeviceType, AuthSessionId } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { AuthSessionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts b/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts index da8755ae71c..1498984827e 100644 --- a/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts +++ b/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts @@ -15,8 +15,10 @@ import { ProjectId, ThreadId, } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { OrchestrationCommandReceiptRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/OrchestrationEventStore.ts b/apps/server/src/persistence/Services/OrchestrationEventStore.ts index beecdcdc656..8b465e7713e 100644 --- a/apps/server/src/persistence/Services/OrchestrationEventStore.ts +++ b/apps/server/src/persistence/Services/OrchestrationEventStore.ts @@ -10,8 +10,9 @@ * @module OrchestrationEventStore */ import { OrchestrationEvent } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect, Stream } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; import type { OrchestrationEventStoreError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionCheckpoints.ts b/apps/server/src/persistence/Services/ProjectionCheckpoints.ts index 617f964f333..7796ebec2a8 100644 --- a/apps/server/src/persistence/Services/ProjectionCheckpoints.ts +++ b/apps/server/src/persistence/Services/ProjectionCheckpoints.ts @@ -16,8 +16,10 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Option, Context, Schema } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Context from "effect/Context"; +import * as Schema from "effect/Schema"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts b/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts index 82c43c0bb03..967e6da9d3a 100644 --- a/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts +++ b/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts @@ -14,8 +14,10 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionProjects.ts b/apps/server/src/persistence/Services/ProjectionProjects.ts index 0970bb2ead4..5632205a269 100644 --- a/apps/server/src/persistence/Services/ProjectionProjects.ts +++ b/apps/server/src/persistence/Services/ProjectionProjects.ts @@ -7,8 +7,10 @@ * @module ProjectionProjectRepository */ import { IsoDateTime, ModelSelection, ProjectId, ProjectScript } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionState.ts b/apps/server/src/persistence/Services/ProjectionState.ts index 0b75b817d25..ba1ca151736 100644 --- a/apps/server/src/persistence/Services/ProjectionState.ts +++ b/apps/server/src/persistence/Services/ProjectionState.ts @@ -7,8 +7,10 @@ * @module ProjectionStateRepository */ import { IsoDateTime, NonNegativeInt } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionThreadActivities.ts b/apps/server/src/persistence/Services/ProjectionThreadActivities.ts index 87daa3c636b..47cb6073c47 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadActivities.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadActivities.ts @@ -14,8 +14,9 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionThreadMessages.ts b/apps/server/src/persistence/Services/ProjectionThreadMessages.ts index 56f8f92dbfe..d50ff320256 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadMessages.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadMessages.ts @@ -14,9 +14,10 @@ import { TurnId, IsoDateTime, } from "@t3tools/contracts"; -import { Schema, Context } from "effect"; -import type { Option } from "effect"; -import type { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Option from "effect/Option"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts b/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts index a68bedb8c37..b4bc2bcc328 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts @@ -5,8 +5,9 @@ import { TrimmedNonEmptyString, TurnId, } from "@t3tools/contracts"; -import { Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionThreadSessions.ts b/apps/server/src/persistence/Services/ProjectionThreadSessions.ts index 5d2c3c87d1d..7cecac33eb6 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadSessions.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadSessions.ts @@ -14,8 +14,10 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionThreads.ts b/apps/server/src/persistence/Services/ProjectionThreads.ts index 7afdab2d58b..44fdc147a4a 100644 --- a/apps/server/src/persistence/Services/ProjectionThreads.ts +++ b/apps/server/src/persistence/Services/ProjectionThreads.ts @@ -16,8 +16,10 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProjectionTurns.ts b/apps/server/src/persistence/Services/ProjectionTurns.ts index 8e37ae09bd9..f3d5d5e4706 100644 --- a/apps/server/src/persistence/Services/ProjectionTurns.ts +++ b/apps/server/src/persistence/Services/ProjectionTurns.ts @@ -17,8 +17,10 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectionRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/persistence/Services/ProviderSessionRuntime.ts b/apps/server/src/persistence/Services/ProviderSessionRuntime.ts index 6ba15b32d96..125f4fa5bbf 100644 --- a/apps/server/src/persistence/Services/ProviderSessionRuntime.ts +++ b/apps/server/src/persistence/Services/ProviderSessionRuntime.ts @@ -12,8 +12,10 @@ import { RuntimeMode, ThreadId, } from "@t3tools/contracts"; -import { Option, Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProviderSessionRuntimeRepositoryError } from "../Errors.ts"; diff --git a/apps/server/src/processRunner.ts b/apps/server/src/processRunner.ts index 03b164fc241..aeeec53e019 100644 --- a/apps/server/src/processRunner.ts +++ b/apps/server/src/processRunner.ts @@ -1,5 +1,6 @@ +// @effect-diagnostics nodeBuiltinImport:off import { type ChildProcess as ChildProcessHandle, spawn, spawnSync } from "node:child_process"; - +import * as NodeTimers from "node:timers"; export interface ProcessRunOptions { cwd?: string | undefined; timeoutMs?: number | undefined; @@ -163,12 +164,14 @@ export async function runProcess( let stderrTruncated = false; let timedOut = false; let settled = false; - let forceKillTimer: ReturnType | null = null; + let forceKillTimer: ReturnType | null = null; - const timeoutTimer = setTimeout(() => { + // @effect-diagnostics-next-line globalTimers:off - Promise/child_process boundary; moving this runner to Effect timers is a separate refactor. + const timeoutTimer = NodeTimers.setTimeout(() => { timedOut = true; killChild(child, "SIGTERM"); - forceKillTimer = setTimeout(() => { + // @effect-diagnostics-next-line globalTimers:off - Promise/child_process boundary; see timeout timer above. + forceKillTimer = NodeTimers.setTimeout(() => { killChild(child, "SIGKILL"); }, 1_000); }, timeoutMs); @@ -176,9 +179,9 @@ export async function runProcess( const finalize = (callback: () => void): void => { if (settled) return; settled = true; - clearTimeout(timeoutTimer); + NodeTimers.clearTimeout(timeoutTimer); if (forceKillTimer) { - clearTimeout(forceKillTimer); + NodeTimers.clearTimeout(forceKillTimer); } callback(); }; diff --git a/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts b/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts index 29b30739b9c..c983aca4ba7 100644 --- a/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts +++ b/apps/server/src/project/Layers/ProjectFaviconResolver.test.ts @@ -1,6 +1,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, describe, expect } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { ProjectFaviconResolver } from "../Services/ProjectFaviconResolver.ts"; import { ProjectFaviconResolverLive } from "./ProjectFaviconResolver.ts"; diff --git a/apps/server/src/project/Layers/ProjectFaviconResolver.ts b/apps/server/src/project/Layers/ProjectFaviconResolver.ts index f2d44a76096..ed5412bf138 100644 --- a/apps/server/src/project/Layers/ProjectFaviconResolver.ts +++ b/apps/server/src/project/Layers/ProjectFaviconResolver.ts @@ -1,4 +1,7 @@ -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { ProjectFaviconResolver, diff --git a/apps/server/src/project/Layers/ProjectSetupScriptRunner.test.ts b/apps/server/src/project/Layers/ProjectSetupScriptRunner.test.ts index 28c74aa7fb8..72e3b5e6d5d 100644 --- a/apps/server/src/project/Layers/ProjectSetupScriptRunner.test.ts +++ b/apps/server/src/project/Layers/ProjectSetupScriptRunner.test.ts @@ -1,5 +1,7 @@ import { ProjectId, type OrchestrationProject } from "@t3tools/contracts"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { describe, expect, it, vi } from "vitest"; import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; diff --git a/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts b/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts index 43c916d52ee..61cd043b43b 100644 --- a/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts +++ b/apps/server/src/project/Layers/ProjectSetupScriptRunner.ts @@ -1,12 +1,15 @@ import { ProjectId } from "@t3tools/contracts"; import { projectScriptRuntimeEnv, setupProjectScript } from "@t3tools/shared/projectScripts"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; import { TerminalManager } from "../../terminal/Services/Manager.ts"; import { type ProjectSetupScriptRunnerShape, ProjectSetupScriptRunner, + ProjectSetupScriptRunnerError, } from "../Services/ProjectSetupScriptRunner.ts"; const makeProjectSetupScriptRunner = Effect.gen(function* () { @@ -29,7 +32,9 @@ const makeProjectSetupScriptRunner = Effect.gen(function* () { null; if (!project) { - return yield* Effect.fail(new Error("Project was not found for setup script execution.")); + return yield* new ProjectSetupScriptRunnerError({ + message: "Project was not found for setup script execution.", + }); } const script = setupProjectScript(project.scripts); @@ -66,7 +71,26 @@ const makeProjectSetupScriptRunner = Effect.gen(function* () { terminalId, cwd, } as const; - }); + }).pipe( + Effect.mapError((cause) => { + if ( + typeof cause === "object" && + cause !== null && + "_tag" in cause && + cause._tag === "ProjectSetupScriptRunnerError" + ) { + return cause as ProjectSetupScriptRunnerError; + } + const message = + typeof cause === "object" && + cause !== null && + "message" in cause && + typeof cause.message === "string" + ? cause.message + : String(cause); + return new ProjectSetupScriptRunnerError({ message }); + }), + ); return { runForThread, diff --git a/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts b/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts index 98257b97e5a..1092ce36989 100644 --- a/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts +++ b/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts @@ -1,8 +1,12 @@ +// @effect-diagnostics nodeBuiltinImport:off import { realpathSync } from "node:fs"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; -import { Duration, Effect, FileSystem, Layer } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; import { TestClock } from "effect/testing"; import { runProcess } from "../../processRunner.ts"; diff --git a/apps/server/src/project/Layers/RepositoryIdentityResolver.ts b/apps/server/src/project/Layers/RepositoryIdentityResolver.ts index ae35c29ce21..8e3239a2acf 100644 --- a/apps/server/src/project/Layers/RepositoryIdentityResolver.ts +++ b/apps/server/src/project/Layers/RepositoryIdentityResolver.ts @@ -1,5 +1,9 @@ import type { RepositoryIdentity } from "@t3tools/contracts"; -import { Cache, Duration, Effect, Exit, Layer } from "effect"; +import * as Cache from "effect/Cache"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; import { detectSourceControlProviderFromGitRemoteUrl, normalizeGitRemoteUrl, diff --git a/apps/server/src/project/Services/ProjectFaviconResolver.ts b/apps/server/src/project/Services/ProjectFaviconResolver.ts index c05dfe8f1fd..ad1b466e2c7 100644 --- a/apps/server/src/project/Services/ProjectFaviconResolver.ts +++ b/apps/server/src/project/Services/ProjectFaviconResolver.ts @@ -6,8 +6,8 @@ * * @module ProjectFaviconResolver */ -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; /** * ProjectFaviconResolverShape - Service API for project favicon lookup. diff --git a/apps/server/src/project/Services/ProjectSetupScriptRunner.ts b/apps/server/src/project/Services/ProjectSetupScriptRunner.ts index acba335a6e7..184c75b5019 100644 --- a/apps/server/src/project/Services/ProjectSetupScriptRunner.ts +++ b/apps/server/src/project/Services/ProjectSetupScriptRunner.ts @@ -1,5 +1,6 @@ -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import * as Data from "effect/Data"; +import type * as Effect from "effect/Effect"; export interface ProjectSetupScriptRunnerResultNoScript { readonly status: "no-script"; @@ -25,10 +26,16 @@ export interface ProjectSetupScriptRunnerInput { readonly preferredTerminalId?: string; } +export class ProjectSetupScriptRunnerError extends Data.TaggedError( + "ProjectSetupScriptRunnerError", +)<{ + readonly message: string; +}> {} + export interface ProjectSetupScriptRunnerShape { readonly runForThread: ( input: ProjectSetupScriptRunnerInput, - ) => Effect.Effect; + ) => Effect.Effect; } export class ProjectSetupScriptRunner extends Context.Service< diff --git a/apps/server/src/project/Services/RepositoryIdentityResolver.ts b/apps/server/src/project/Services/RepositoryIdentityResolver.ts index d3eea846199..ef0b128c6f7 100644 --- a/apps/server/src/project/Services/RepositoryIdentityResolver.ts +++ b/apps/server/src/project/Services/RepositoryIdentityResolver.ts @@ -1,6 +1,6 @@ import type { RepositoryIdentity } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; export interface RepositoryIdentityResolverShape { readonly resolve: (cwd: string) => Effect.Effect; diff --git a/apps/server/src/provider/Drivers/ClaudeDriver.ts b/apps/server/src/provider/Drivers/ClaudeDriver.ts index eb287ad7e22..87b08d01553 100644 --- a/apps/server/src/provider/Drivers/ClaudeDriver.ts +++ b/apps/server/src/provider/Drivers/ClaudeDriver.ts @@ -13,7 +13,13 @@ * @module provider/Drivers/ClaudeDriver */ import { ClaudeSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; -import { Cache, Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; +import * as Cache from "effect/Cache"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { HttpClient } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; diff --git a/apps/server/src/provider/Drivers/ClaudeHome.test.ts b/apps/server/src/provider/Drivers/ClaudeHome.test.ts index 84c9c331cc8..87298f80125 100644 --- a/apps/server/src/provider/Drivers/ClaudeHome.test.ts +++ b/apps/server/src/provider/Drivers/ClaudeHome.test.ts @@ -2,7 +2,8 @@ import * as NodeOS from "node:os"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, expect, it } from "@effect/vitest"; -import { Effect, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as Path from "effect/Path"; import { makeClaudeCapabilitiesCacheKey, diff --git a/apps/server/src/provider/Drivers/ClaudeHome.ts b/apps/server/src/provider/Drivers/ClaudeHome.ts index c959096677e..9a4d1ce9cdf 100644 --- a/apps/server/src/provider/Drivers/ClaudeHome.ts +++ b/apps/server/src/provider/Drivers/ClaudeHome.ts @@ -1,7 +1,8 @@ import * as NodeOS from "node:os"; import type { ClaudeSettings } from "@t3tools/contracts"; -import { Effect, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as Path from "effect/Path"; import { expandHomePath } from "../../pathExpansion.ts"; diff --git a/apps/server/src/provider/Drivers/CodexDriver.ts b/apps/server/src/provider/Drivers/CodexDriver.ts index cc849acc382..ce00d1340ff 100644 --- a/apps/server/src/provider/Drivers/CodexDriver.ts +++ b/apps/server/src/provider/Drivers/CodexDriver.ts @@ -22,7 +22,12 @@ * @module provider/Drivers/CodexDriver */ import { CodexSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; -import { Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { HttpClient } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; diff --git a/apps/server/src/provider/Drivers/CodexHomeLayout.test.ts b/apps/server/src/provider/Drivers/CodexHomeLayout.test.ts index b84a43c4032..78573105e6d 100644 --- a/apps/server/src/provider/Drivers/CodexHomeLayout.test.ts +++ b/apps/server/src/provider/Drivers/CodexHomeLayout.test.ts @@ -1,6 +1,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, expect, it } from "@effect/vitest"; -import { Effect, FileSystem, Path, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { CodexSettings } from "@t3tools/contracts"; import { diff --git a/apps/server/src/provider/Drivers/CodexHomeLayout.ts b/apps/server/src/provider/Drivers/CodexHomeLayout.ts index 0b6cd6b8918..370cf001cb5 100644 --- a/apps/server/src/provider/Drivers/CodexHomeLayout.ts +++ b/apps/server/src/provider/Drivers/CodexHomeLayout.ts @@ -1,7 +1,10 @@ import * as NodeOS from "node:os"; import { ProviderDriverKind, type CodexSettings } from "@t3tools/contracts"; -import { Effect, FileSystem, Path, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import * as PlatformError from "effect/PlatformError"; import { expandHomePath } from "../../pathExpansion.ts"; diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/apps/server/src/provider/Drivers/CursorDriver.ts index 72a6bae1f79..135d8637062 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.ts @@ -13,7 +13,12 @@ * @module provider/Drivers/CursorDriver */ import { CursorSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; -import { Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { HttpClient } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; diff --git a/apps/server/src/provider/Drivers/OpenCodeDriver.ts b/apps/server/src/provider/Drivers/OpenCodeDriver.ts index 0452a3a733f..a6e184b2d4b 100644 --- a/apps/server/src/provider/Drivers/OpenCodeDriver.ts +++ b/apps/server/src/provider/Drivers/OpenCodeDriver.ts @@ -13,7 +13,12 @@ * @module provider/Drivers/OpenCodeDriver */ import { OpenCodeSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; -import { Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { HttpClient } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; diff --git a/apps/server/src/provider/Errors.ts b/apps/server/src/provider/Errors.ts index 2bd926512ed..bccc7d5679c 100644 --- a/apps/server/src/provider/Errors.ts +++ b/apps/server/src/provider/Errors.ts @@ -1,4 +1,4 @@ -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import type { CheckpointServiceError } from "../checkpointing/Errors.ts"; diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 4c71a016f32..f095337a41c 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -22,7 +23,14 @@ import { } from "@t3tools/contracts"; import { createModelSelection } from "@t3tools/shared/model"; import { assert, describe, it } from "@effect/vitest"; -import { Context, Effect, Fiber, Layer, Random, Schema, Stream } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as Layer from "effect/Layer"; +import * as Random from "effect/Random"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; +import { TestClock } from "effect/testing"; import { attachmentRelativePath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; @@ -1463,7 +1471,8 @@ describe("ClaudeAdapterLive", () => { yield* Effect.yieldNow; yield* Effect.yieldNow; yield* Effect.yieldNow; - yield* Effect.promise(() => new Promise((resolve) => setTimeout(resolve, 50))); + yield* TestClock.adjust("50 millis"); + yield* Effect.yieldNow; runtimeEventsFiber.interruptUnsafe(); diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 556504d6cf4..9e5cb99a937 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -51,20 +51,19 @@ import { getProviderOptionDescriptors, resolvePromptInjectedEffort, } from "@t3tools/shared/model"; -import { - Cause, - DateTime, - Deferred, - Effect, - Exit, - FileSystem, - Fiber, - Path, - Queue, - Random, - Ref, - Stream, -} from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Fiber from "effect/Fiber"; +import * as Path from "effect/Path"; +import * as Queue from "effect/Queue"; +import * as Random from "effect/Random"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { resolveAttachmentPath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; @@ -222,11 +221,22 @@ function toMessage(cause: unknown, fallback: string): string { return fallback; } -function toError(cause: unknown, fallback: string): Error { - return cause instanceof Error ? cause : new Error(toMessage(cause, fallback)); +function toProcessError( + cause: unknown, + fallback: string, + threadId: ThreadId, +): ProviderAdapterProcessError { + return new ProviderAdapterProcessError({ + provider: PROVIDER, + threadId, + detail: toMessage(cause, fallback), + cause, + }); } -function normalizeClaudeStreamMessages(cause: Cause.Cause): ReadonlyArray { +function normalizeClaudeStreamMessages( + cause: Cause.Cause<{ readonly message: string }>, +): ReadonlyArray { const errors = Cause.prettyErrors(cause) .map((error) => error.message.trim()) .filter((message) => message.length > 0); @@ -252,18 +262,23 @@ function isClaudeInterruptedMessage(message: string): boolean { ); } -function isClaudeInterruptedCause(cause: Cause.Cause): boolean { +function isClaudeInterruptedCause(cause: Cause.Cause<{ readonly message: string }>): boolean { return ( Cause.hasInterruptsOnly(cause) || normalizeClaudeStreamMessages(cause).some(isClaudeInterruptedMessage) ); } -function messageFromClaudeStreamCause(cause: Cause.Cause, fallback: string): string { +function messageFromClaudeStreamCause( + cause: Cause.Cause<{ readonly message: string }>, + fallback: string, +): string { return normalizeClaudeStreamMessages(cause)[0] ?? fallback; } -function interruptionMessageFromClaudeCause(cause: Cause.Cause): string { +function interruptionMessageFromClaudeCause( + cause: Cause.Cause<{ readonly message: string }>, +): string { const message = messageFromClaudeStreamCause(cause, "Claude runtime interrupted."); return isClaudeInterruptedMessage(message) ? "Claude runtime interrupted." : message; } @@ -530,7 +545,7 @@ function summarizeToolRequest(toolName: string, input: Record): } } - const serialized = JSON.stringify(input); + const serialized = Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(input); if (serialized.length <= 400) { return `${toolName}: ${serialized}`; } @@ -796,7 +811,7 @@ function exitPlanCaptureKey(input: { function tryParseJsonRecord(value: string): Record | undefined { try { - const parsed = JSON.parse(value); + const parsed = Schema.decodeUnknownSync(Schema.UnknownFromJsonString)(value); return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record) : undefined; @@ -807,7 +822,7 @@ function tryParseJsonRecord(value: string): Record | undefined function toolInputFingerprint(input: Record): string | undefined { try { - return JSON.stringify(input); + return Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(input); } catch { return undefined; } @@ -1020,7 +1035,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( return; } - const observedAt = new Date().toISOString(); + const observedAt = yield* nowIso; const itemId = sdkNativeItemId(message); yield* nativeEventLogger.write( @@ -1030,7 +1045,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( id: "uuid" in message && typeof message.uuid === "string" ? message.uuid - : crypto.randomUUID(), + : yield* Random.nextUUIDv4, kind: "notification", provider: PROVIDER, createdAt: observedAt, @@ -2353,9 +2368,11 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( } }); - const runSdkStream = (context: ClaudeSessionContext): Effect.Effect => + const runSdkStream = ( + context: ClaudeSessionContext, + ): Effect.Effect => Stream.fromAsyncIterable(context.query, (cause) => - toError(cause, "Claude runtime stream failed."), + toProcessError(cause, "Claude runtime stream failed.", context.session.threadId), ).pipe( Stream.takeWhile(() => !context.stopped), Stream.runForEach((message) => handleSdkMessage(context, message)), @@ -2363,7 +2380,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( const handleStreamExit = Effect.fn("handleStreamExit")(function* ( context: ClaudeSessionContext, - exit: Exit.Exit, + exit: Exit.Exit, ) { if (context.stopped) { return; @@ -2432,12 +2449,20 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( yield* Fiber.interrupt(streamFiber); } - // @effect-diagnostics-next-line tryCatchInEffectGen:off - try { - context.query.close(); - } catch (cause) { - yield* emitRuntimeError(context, "Failed to close Claude runtime query.", cause); - } + yield* Effect.try({ + try: () => context.query.close(), + catch: (cause) => + new ProviderAdapterProcessError({ + provider: PROVIDER, + threadId: context.session.threadId, + detail: toMessage(cause, "Failed to close Claude runtime query."), + cause, + }), + }).pipe( + Effect.catch((cause) => + emitRuntimeError(context, "Failed to close Claude runtime query.", cause), + ), + ); const updatedAt = yield* nowIso; context.session = { @@ -2916,8 +2941,12 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( "claude.query.include_partial_messages": true, "claude.query.additional_directories": input.cwd ? [input.cwd] : [], "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES], - "claude.query.settings_json": JSON.stringify(settings), - "claude.query.extra_args_json": JSON.stringify(extraArgs), + "claude.query.settings_json": Schema.encodeUnknownSync(Schema.UnknownFromJsonString)( + settings, + ), + "claude.query.extra_args_json": Schema.encodeUnknownSync(Schema.UnknownFromJsonString)( + extraArgs, + ), "claude.query.path_to_executable": claudeBinaryPath, }); diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts index 43505967002..aacdb12b709 100644 --- a/apps/server/src/provider/Layers/ClaudeProvider.ts +++ b/apps/server/src/provider/Layers/ClaudeProvider.ts @@ -6,7 +6,11 @@ import { type ServerProviderModel, type ServerProviderSlashCommand, } from "@t3tools/contracts"; -import { Effect, Option, Path, Result } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Result from "effect/Result"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { createModelCapabilities, @@ -521,7 +525,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( never, ChildProcessSpawner.ChildProcessSpawner | Path.Path > { - const checkedAt = new Date().toISOString(); + const checkedAt = DateTime.formatIso(yield* DateTime.now); const allModels = providerModelsFromSettings( BUILT_IN_MODELS, PROVIDER, @@ -665,7 +669,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( }); export const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): ServerProviderDraft => { - const checkedAt = new Date().toISOString(); + const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); const models = providerModelsFromSettings( BUILT_IN_MODELS, PROVIDER, diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index 486e149166b..80ba9323461 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; @@ -21,7 +22,16 @@ import { createModelSelection } from "@t3tools/shared/model"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, vi } from "@effect/vitest"; -import { Context, Effect, Exit, Fiber, Layer, Option, Queue, Schema, Scope, Stream } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Queue from "effect/Queue"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import * as CodexErrors from "effect-codex-app-server/errors"; import { ServerConfig } from "../../config.ts"; @@ -49,7 +59,7 @@ const asItemId = (value: string): ProviderItemId => ProviderItemId.make(value); class FakeCodexRuntime implements CodexSessionRuntimeShape { private readonly eventQueue = Effect.runSync(Queue.unbounded()); - private readonly now = new Date().toISOString(); + private readonly now = "2026-01-01T00:00:00.000Z"; public readonly startImpl = vi.fn(() => Promise.resolve({ @@ -446,7 +456,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { id: asEventId("evt-msg-complete"), kind: "notification", provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "item/completed", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), @@ -489,7 +499,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { id: asEventId("evt-plan-complete"), kind: "notification", provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "item/completed", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), @@ -531,7 +541,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { id: asEventId("evt-plan-delta"), kind: "notification", provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "item/plan/delta", threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), @@ -569,7 +579,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "session", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "session/closed", message: "Session stopped", }; @@ -600,7 +610,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "error", turnId: asTurnId("turn-1"), payload: { @@ -638,7 +648,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "process/stderr", turnId: asTurnId("turn-1"), message: "The filename or extension is too long. (os error 206)", @@ -672,7 +682,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "thread/realtime/started", payload: { threadId: "thread-1", @@ -706,7 +716,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "process/stderr", turnId: asTurnId("turn-1"), message: @@ -742,7 +752,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "serverRequest/resolved", requestKind: "command", requestId: ApprovalRequestId.make("req-1"), @@ -777,7 +787,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "serverRequest/resolved", requestKind: "file-read", requestId: ApprovalRequestId.make("req-file-read-1"), @@ -812,7 +822,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "item/tool/requestUserInput/answered", payload: { answers: { @@ -852,7 +862,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "windowsSandbox/setupCompleted", message: "Sandbox setup failed", payload: { @@ -897,7 +907,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "request", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "item/tool/requestUserInput", requestId: ApprovalRequestId.make("req-user-input-1"), payload: { @@ -924,7 +934,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "item/tool/requestUserInput/answered", requestId: ApprovalRequestId.make("req-user-input-1"), payload: { @@ -965,7 +975,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-1"), turnId: asTurnId("turn-1"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "thread/tokenUsage/updated", payload: { threadId: "thread-1", @@ -1147,7 +1157,7 @@ it.effect("flushes managed native logs when the adapter layer shuts down", () => kind: "notification", provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-logger"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", method: "process/stderr", message: "native flush test", } satisfies ProviderEvent); diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts index 7486f2b9bb8..fbe5ae62ee9 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.ts @@ -24,7 +24,14 @@ import { ThreadId, ProviderSendTurnInput, } from "@t3tools/contracts"; -import { Effect, Exit, Fiber, FileSystem, Queue, Schema, Scope, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as FileSystem from "effect/FileSystem"; +import * as Queue from "effect/Queue"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as CodexErrors from "effect-codex-app-server/errors"; import * as EffectCodexSchema from "effect-codex-app-server/schema"; diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts index dd2c04d086b..683960e1d8a 100644 --- a/apps/server/src/provider/Layers/CodexProvider.ts +++ b/apps/server/src/provider/Layers/CodexProvider.ts @@ -1,4 +1,11 @@ -import { DateTime, Duration, Effect, Layer, Option, Result, Schema, Types } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; +import * as Types from "effect/Types"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as CodexClient from "effect-codex-app-server/client"; import * as CodexSchema from "effect-codex-app-server/schema"; @@ -326,7 +333,7 @@ const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvi })); const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProviderDraft => { - const checkedAt = new Date().toISOString(); + const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); const models = emptyCodexModelsFromSettings(codexSettings); if (!codexSettings.enabled) { diff --git a/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts b/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts index 780f9731fd2..f56e29c63b3 100644 --- a/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts +++ b/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; -import { Effect, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; import { describe, it } from "vitest"; import { ThreadId } from "@t3tools/contracts"; import * as CodexErrors from "effect-codex-app-server/errors"; diff --git a/apps/server/src/provider/Layers/CodexSessionRuntime.ts b/apps/server/src/provider/Layers/CodexSessionRuntime.ts index 802a2e808a1..546ac4e74d2 100644 --- a/apps/server/src/provider/Layers/CodexSessionRuntime.ts +++ b/apps/server/src/provider/Layers/CodexSessionRuntime.ts @@ -17,7 +17,17 @@ import { TurnId, } from "@t3tools/contracts"; import { normalizeModelSlug } from "@t3tools/shared/model"; -import { Deferred, Effect, Exit, Layer, Queue, Ref, Scope, Random, Schema, Stream } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Queue from "effect/Queue"; +import * as Ref from "effect/Ref"; +import * as Scope from "effect/Scope"; +import * as Random from "effect/Random"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import * as SchemaIssue from "effect/SchemaIssue"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import * as CodexClient from "effect-codex-app-server/client"; @@ -653,11 +663,14 @@ function updateSession( sessionRef: Ref.Ref, updates: Partial, ): Effect.Effect { - return Ref.update(sessionRef, (session) => ({ - ...session, - ...updates, - updatedAt: new Date().toISOString(), - })); + return Effect.gen(function* () { + const updatedAt = DateTime.formatIso(yield* DateTime.now); + yield* Ref.update(sessionRef, (session) => ({ + ...session, + ...updates, + updatedAt, + })); + }); } function parseThreadSnapshot( @@ -724,7 +737,9 @@ export const makeCodexSessionRuntime = ( Effect.provide(clientContext), ); const serverNotifications = yield* Queue.unbounded(); + const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); + const sessionCreatedAt = yield* nowIso; const initialSession = { provider: PROVIDER, ...(options.providerInstanceId ? { providerInstanceId: options.providerInstanceId } : {}), @@ -734,22 +749,23 @@ export const makeCodexSessionRuntime = ( ...(options.model ? { model: options.model } : {}), threadId: options.threadId, ...(options.resumeCursor !== undefined ? { resumeCursor: options.resumeCursor } : {}), - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), + createdAt: sessionCreatedAt, + updatedAt: sessionCreatedAt, } satisfies ProviderSession; const sessionRef = yield* Ref.make(initialSession); const offerEvent = (event: ProviderEvent) => Queue.offer(events, event).pipe(Effect.asVoid); const emitEvent = (event: Omit) => - Effect.flatMap(Random.nextUUIDv4, (id) => - offerEvent({ + Effect.gen(function* () { + const id = yield* Random.nextUUIDv4; + return yield* offerEvent({ id: EventId.make(id), provider: PROVIDER, ...(options.providerInstanceId ? { providerInstanceId: options.providerInstanceId } : {}), - createdAt: new Date().toISOString(), + createdAt: yield* nowIso, ...event, - }), - ); + }); + }); const emitSessionEvent = (method: string, message: string) => emitEvent({ kind: "session", @@ -1177,7 +1193,7 @@ export const makeCodexSessionRuntime = ( cwd: opened.cwd, model: opened.model, resumeCursor: { threadId: providerThreadId }, - updatedAt: new Date().toISOString(), + updatedAt: yield* nowIso, } satisfies ProviderSession; yield* Ref.set(sessionRef, session); yield* emitSessionEvent("session/ready", "Codex App Server session ready."); diff --git a/apps/server/src/provider/Layers/CursorAdapter.test.ts b/apps/server/src/provider/Layers/CursorAdapter.test.ts index 3c676b22ee5..f67b995751f 100644 --- a/apps/server/src/provider/Layers/CursorAdapter.test.ts +++ b/apps/server/src/provider/Layers/CursorAdapter.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as path from "node:path"; import * as os from "node:os"; import { chmod, mkdtemp, readFile, writeFile } from "node:fs/promises"; @@ -5,7 +6,13 @@ import { fileURLToPath } from "node:url"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; -import { Context, Deferred, Effect, Fiber, Layer, Schema, Stream } from "effect"; +import * as Context from "effect/Context"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { createModelSelection } from "@t3tools/shared/model"; import { @@ -98,7 +105,7 @@ async function waitForFileContent(filePath: string, attempts = 40) { return raw; } } catch {} - await new Promise((resolve) => setTimeout(resolve, 50)); + await Effect.runPromise(Effect.yieldNow); } throw new Error(`Timed out waiting for file content at ${filePath}`); } @@ -113,10 +120,11 @@ async function waitForFileContent(filePath: string, attempts = 40) { // tests assumed. const makeResolveCursorSettings = Effect.gen(function* () { const serverSettings = yield* ServerSettingsService; - // @effect-diagnostics effect/returnEffectInGen:off - return serverSettings.getSettings.pipe( - Effect.map((snapshot) => snapshot.providers.cursor), - Effect.orDie, + return yield* Effect.succeed( + serverSettings.getSettings.pipe( + Effect.map((snapshot) => snapshot.providers.cursor), + Effect.orDie, + ), ); }); diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts index 34d1221b022..5a350582349 100644 --- a/apps/server/src/provider/Layers/CursorAdapter.ts +++ b/apps/server/src/provider/Layers/CursorAdapter.ts @@ -3,7 +3,6 @@ * * @module CursorAdapterLive */ -import * as nodePath from "node:path"; import { ApprovalRequestId, @@ -22,21 +21,21 @@ import { type ThreadId, TurnId, } from "@t3tools/contracts"; -import { - DateTime, - Deferred, - Effect, - Exit, - Fiber, - FileSystem, - Option, - PubSub, - Random, - Scope, - Semaphore, - Stream, - SynchronizedRef, -} from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as PubSub from "effect/PubSub"; +import * as Random from "effect/Random"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Semaphore from "effect/Semaphore"; +import * as Stream from "effect/Stream"; +import * as SynchronizedRef from "effect/SynchronizedRef"; import { ChildProcessSpawner } from "effect/unstable/process"; import type * as EffectAcpSchema from "effect-acp/schema"; @@ -306,6 +305,7 @@ export function makeCursorAdapter( return Effect.gen(function* () { const boundInstanceId = options?.instanceId ?? ProviderInstanceId.make("cursor"); const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; const serverConfig = yield* Effect.service(ServerConfig); const nativeEventLogger = @@ -358,12 +358,12 @@ export function makeCursorAdapter( ) => Effect.gen(function* () { if (!nativeEventLogger) return; - const observedAt = new Date().toISOString(); + const observedAt = yield* nowIso; yield* nativeEventLogger.write( { observedAt, event: { - id: crypto.randomUUID(), + id: yield* Random.nextUUIDv4, kind: "notification", provider: PROVIDER, createdAt: observedAt, @@ -390,7 +390,7 @@ export function makeCursorAdapter( method: string, ) => Effect.gen(function* () { - const fingerprint = `${ctx.activeTurnId ?? "no-turn"}:${JSON.stringify(payload)}`; + const fingerprint = `${ctx.activeTurnId ?? "no-turn"}:${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(payload)}`; if (ctx.lastPlanFingerprint === fingerprint) { return; } @@ -460,7 +460,7 @@ export function makeCursorAdapter( }); } - const cwd = nodePath.resolve(input.cwd.trim()); + const cwd = path.resolve(input.cwd.trim()); const cursorModelSelection = input.modelSelection?.instanceId === boundInstanceId ? input.modelSelection : undefined; const existing = sessions.get(input.threadId); @@ -638,7 +638,9 @@ export function makeCursorAdapter( turnId: ctx?.activeTurnId, requestId: runtimeRequestId, permissionRequest, - detail: permissionRequest.detail ?? JSON.stringify(params).slice(0, 2000), + detail: + permissionRequest.detail ?? + Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(params).slice(0, 2000), args: params, source: "acp.jsonrpc", method: "session/request_permission", diff --git a/apps/server/src/provider/Layers/CursorProvider.test.ts b/apps/server/src/provider/Layers/CursorProvider.test.ts index 33ef20acf21..90b36e89004 100644 --- a/apps/server/src/provider/Layers/CursorProvider.test.ts +++ b/apps/server/src/provider/Layers/CursorProvider.test.ts @@ -1,7 +1,9 @@ import * as NodeOS from "node:os"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Effect, FileSystem, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import { describe, expect, it } from "vitest"; import type * as EffectAcpSchema from "effect-acp/schema"; import type { CursorSettings, ServerProviderModel } from "@t3tools/contracts"; @@ -68,12 +70,16 @@ const makeMockAgentWrapper = Effect.fn("makeMockAgentWrapper")(function* ( prefix: "cursor-provider-mock-", }); const wrapperPath = path.join(dir, "fake-agent.sh"); + // @effect-diagnostics-next-line preferSchemaOverJson:off + const bunCommand = JSON.stringify("bun"); + // @effect-diagnostics-next-line preferSchemaOverJson:off + const mockAgentPathJson = JSON.stringify(mockAgentPath); const envExports = Object.entries(extraEnv ?? {}) .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`) .join("\n"); const script = `#!/bin/sh ${envExports} -exec ${JSON.stringify("bun")} ${JSON.stringify(mockAgentPath)} "$@" +exec ${bunCommand} ${mockAgentPathJson} "$@" `; yield* fileSystem.writeFileString(wrapperPath, script); yield* fileSystem.chmod(wrapperPath, 0o755); @@ -89,13 +95,17 @@ const makeMockAgentWithAboutWrapper = Effect.fn("makeMockAgentWithAboutWrapper") prefix: "cursor-provider-about-mock-", }); const wrapperPath = path.join(dir, "fake-agent.sh"); + // @effect-diagnostics-next-line preferSchemaOverJson:off + const bunCommand = JSON.stringify("bun"); + // @effect-diagnostics-next-line preferSchemaOverJson:off + const mockAgentPathJson = JSON.stringify(mockAgentPath); const script = `#!/bin/sh if [ "$1" = "about" ]; then printf 'CLI Version 2026.04.09-f2b0fcd\\n' printf 'User Email cursor@example.com\\n' exit 0 fi -exec ${JSON.stringify("bun")} ${JSON.stringify(mockAgentPath)} "$@" +exec ${bunCommand} ${mockAgentPathJson} "$@" `; yield* fileSystem.writeFileString(wrapperPath, script); yield* fileSystem.chmod(wrapperPath, 0o755); @@ -118,7 +128,7 @@ const waitForFileContent = Effect.fn("waitForFileContent")(function* ( } yield* Effect.sleep("50 millis"); } - return yield* Effect.fail(new Error(`Timed out waiting for file content at ${filePath}`)); + return yield* Effect.die(`Timed out waiting for file content at ${filePath}`); }); const makeProviderStatusEnvFixture = Effect.fn("makeProviderStatusEnvFixture")(function* () { diff --git a/apps/server/src/provider/Layers/CursorProvider.ts b/apps/server/src/provider/Layers/CursorProvider.ts index 4a8b1cdcf94..b7ce0cf63c2 100644 --- a/apps/server/src/provider/Layers/CursorProvider.ts +++ b/apps/server/src/provider/Layers/CursorProvider.ts @@ -1,5 +1,4 @@ -import * as nodeOs from "node:os"; - +import * as NodeOs from "node:os"; import type { CursorSettings, ModelCapabilities, @@ -11,7 +10,15 @@ import type { } from "@t3tools/contracts"; import { ProviderDriverKind } from "@t3tools/contracts"; import type * as EffectAcpSchema from "effect-acp/schema"; -import { Cause, Effect, Exit, FileSystem, Layer, Option, Path, Result } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Result from "effect/Result"; import { HttpClient } from "effect/unstable/http"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { @@ -59,7 +66,7 @@ export const CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES = { export function buildInitialCursorProviderSnapshot( cursorSettings: CursorSettings, ): ServerProviderDraft { - const checkedAt = new Date().toISOString(); + const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); const models = getCursorFallbackModels(cursorSettings); if (!cursorSettings.enabled) { @@ -854,7 +861,7 @@ function isCursorAboutJsonFormatUnsupported(result: CommandResult): boolean { const readCursorCliConfigChannel = Effect.fn("readCursorCliConfigChannel")(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; - const configPath = path.join(nodeOs.homedir(), ".cursor", "cli-config.json"); + const configPath = path.join(NodeOs.homedir(), ".cursor", "cli-config.json"); const raw = yield* fileSystem.readFileString(configPath).pipe(Effect.orElseSucceed(() => "")); return parseCursorCliConfigChannel(raw); }); @@ -1085,7 +1092,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")( never, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path > { - const checkedAt = new Date().toISOString(); + const checkedAt = DateTime.formatIso(yield* DateTime.now); const fallbackModels = getCursorFallbackModels(cursorSettings); if (!cursorSettings.enabled) { diff --git a/apps/server/src/provider/Layers/EventNdjsonLogger.test.ts b/apps/server/src/provider/Layers/EventNdjsonLogger.test.ts index aa7e5a26927..0b1f99d3c11 100644 --- a/apps/server/src/provider/Layers/EventNdjsonLogger.test.ts +++ b/apps/server/src/provider/Layers/EventNdjsonLogger.test.ts @@ -1,10 +1,11 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { ThreadId } from "@t3tools/contracts"; import { assert, describe, it } from "@effect/vitest"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; diff --git a/apps/server/src/provider/Layers/EventNdjsonLogger.ts b/apps/server/src/provider/Layers/EventNdjsonLogger.ts index 2a512efe660..3b36cc20c90 100644 --- a/apps/server/src/provider/Layers/EventNdjsonLogger.ts +++ b/apps/server/src/provider/Layers/EventNdjsonLogger.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off /** * Provider event logger helper. * @@ -10,7 +11,11 @@ import path from "node:path"; import type { ThreadId } from "@t3tools/contracts"; import { RotatingFileSink } from "@t3tools/shared/logging"; -import { Effect, Exit, Logger, Scope, SynchronizedRef } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Logger from "effect/Logger"; +import * as Scope from "effect/Scope"; +import * as SynchronizedRef from "effect/SynchronizedRef"; import { toSafeThreadAttachmentSegment } from "../../attachmentStore.ts"; diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts index 76b9da7b32c..96c1f7fe7ab 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts @@ -1,8 +1,16 @@ import assert from "node:assert/strict"; - import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Context, Effect, Exit, Fiber, Layer, Option, Schema, Scope, Stream } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; +import { TestClock } from "effect/testing"; import { beforeEach } from "vitest"; import { @@ -216,8 +224,8 @@ beforeEach(() => { runtimeMock.reset(); }); -const sleep = (ms: number) => - Effect.promise(() => new Promise((resolve) => setTimeout(resolve, ms))); +const advanceTestClock = (ms: number) => + TestClock.adjust(`${ms} millis`).pipe(Effect.andThen(Effect.yieldNow)); it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { it.effect("reuses a configured OpenCode server URL instead of spawning a local server", () => @@ -648,7 +656,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { threadId: asThreadId("thread-native-log"), runtimeMode: "full-access", }); - yield* sleep(10); + yield* advanceTestClock(10); return started; }).pipe(Effect.provide(adapterLayer)); @@ -736,7 +744,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { threadId: asThreadId("thread-native-log-failure"), runtimeMode: "full-access", }); - yield* sleep(10); + yield* advanceTestClock(10); return { sessions: yield* adapter.listSessions(), closeCallsDuringRun: [...runtimeMock.state.closeCalls], diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.ts index a7b179ab1c3..d3561507f9f 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.ts @@ -12,7 +12,15 @@ import { TurnId, type UserInputQuestion, } from "@t3tools/contracts"; -import { Cause, Effect, Exit, Queue, Random, Ref, Scope, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Queue from "effect/Queue"; +import * as Random from "effect/Random"; +import * as Ref from "effect/Ref"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import type { OpencodeClient, Part, PermissionRequest, QuestionRequest } from "@opencode-ai/sdk/v2"; import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; @@ -96,7 +104,7 @@ export interface OpenCodeAdapterLiveOptions { } function nowIso(): string { - return new Date().toISOString(); + return Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); } /** @@ -351,7 +359,7 @@ function isoFromEpochMs(value: number | undefined): string | undefined { if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) { return undefined; } - return new Date(value).toISOString(); + return DateTime.formatIso(DateTime.makeUnsafe(value)); } function messageRoleForPart( diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.test.ts b/apps/server/src/provider/Layers/OpenCodeProvider.test.ts index 7abe0be9816..4627debfb99 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.test.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.test.ts @@ -2,7 +2,9 @@ import assert from "node:assert/strict"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, Layer, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; import { beforeEach } from "vitest"; import { OpenCodeSettings } from "@t3tools/contracts"; diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.ts b/apps/server/src/provider/Layers/OpenCodeProvider.ts index 6431282d63b..5abbd7daf62 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.ts @@ -4,7 +4,10 @@ import { type OpenCodeSettings, type ServerProviderModel, } from "@t3tools/contracts"; -import { Cause, Data, Effect } from "effect"; +import * as Cause from "effect/Cause"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; import { createModelCapabilities } from "@t3tools/shared/model"; import { @@ -250,7 +253,7 @@ function flattenOpenCodeModels(input: OpenCodeInventory): ReadonlyArray { - const checkedAt = new Date().toISOString(); + const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); const models = providerModelsFromSettings( [], PROVIDER, @@ -298,7 +301,7 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu environment: NodeJS.ProcessEnv = process.env, ): Effect.fn.Return { const openCodeRuntime = yield* OpenCodeRuntime; - const checkedAt = new Date().toISOString(); + const checkedAt = DateTime.formatIso(yield* DateTime.now); const customModels = openCodeSettings.customModels; const isExternalServer = openCodeSettings.serverUrl.trim().length > 0; diff --git a/apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts b/apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts index a37eeae6b8f..7fb545b2bed 100644 --- a/apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts +++ b/apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts @@ -5,7 +5,10 @@ import { } from "@t3tools/contracts"; import { it, assert, vi } from "@effect/vitest"; -import { Effect, Layer, PubSub, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Stream from "effect/Stream"; import type { ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts"; import type { CodexAdapterShape } from "../Services/CodexAdapter.ts"; diff --git a/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts b/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts index f2eeaa1aae8..b492399b10b 100644 --- a/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts +++ b/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts @@ -20,7 +20,8 @@ import { ProviderInstanceId, type ProviderDriverKind, } from "@t3tools/contracts"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { ProviderUnsupportedError } from "../Errors.ts"; import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts"; diff --git a/apps/server/src/provider/Layers/ProviderEventLoggers.ts b/apps/server/src/provider/Layers/ProviderEventLoggers.ts index 4a15fdd6852..14e9a1075d5 100644 --- a/apps/server/src/provider/Layers/ProviderEventLoggers.ts +++ b/apps/server/src/provider/Layers/ProviderEventLoggers.ts @@ -27,7 +27,9 @@ * * @module provider/Layers/ProviderEventLoggers */ -import { Context, Effect, Layer } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { ServerConfig } from "../../config.ts"; import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; diff --git a/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts b/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts index b6c44306f93..4e43e04cb7c 100644 --- a/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts +++ b/apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts @@ -47,7 +47,9 @@ import { type ProviderInstanceConfigMap, ServerSettings, } from "@t3tools/contracts"; -import { Effect, Layer, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Stream from "effect/Stream"; import { ServerSettingsService } from "../../serverSettings.ts"; import { BUILT_IN_DRIVERS, type BuiltInDriversEnv } from "../builtInDrivers.ts"; diff --git a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts index 0eeef1fe957..86f99c97326 100644 --- a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts +++ b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts @@ -33,7 +33,8 @@ import { type ProviderInstanceConfigMap, ProviderInstanceId, } from "@t3tools/contracts"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts index 63f687f55b6..59db5709d18 100644 --- a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts +++ b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts @@ -40,7 +40,16 @@ import { type ProviderDriverKind, type ServerProvider, } from "@t3tools/contracts"; -import { Context, Effect, Equal, Exit, Layer, PubSub, Ref, Schema, Scope, Stream } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Equal from "effect/Equal"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { buildUnavailableProviderSnapshot } from "../unavailableProviderSnapshot.ts"; import { diff --git a/apps/server/src/provider/Layers/ProviderRegistry.test.ts b/apps/server/src/provider/Layers/ProviderRegistry.test.ts index 862da8019b9..a903974f4f0 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.test.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.test.ts @@ -1,6 +1,15 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { describe, it, assert, live } from "@effect/vitest"; -import { Effect, Exit, Layer, PubSub, Ref, Schema, Scope, Sink, Stream } from "effect"; +import { describe, it, assert } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Sink from "effect/Sink"; +import * as Stream from "effect/Stream"; +import { TestClock } from "effect/testing"; import * as CodexErrors from "effect-codex-app-server/errors"; import { ClaudeSettings, @@ -696,7 +705,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T attempt < 50 && cachedProvider?.checkedAt !== refreshedProvider.checkedAt; attempt += 1 ) { - yield* Effect.sleep("10 millis"); + yield* TestClock.adjust("10 millis"); + yield* Effect.yieldNow; cachedProvider = yield* readProviderStatusCache(filePath); } @@ -840,8 +850,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T const changes = yield* PubSub.unbounded(); const instancesRef = yield* Ref.make>([codexInstance]); const failNextList = yield* Ref.make(false); - const wait = (millis: number) => - Effect.promise(() => new Promise((resolve) => setTimeout(resolve, millis))); + const wait = () => Effect.yieldNow; const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, { getInstance: (instanceId) => Ref.get(instancesRef).pipe( @@ -892,7 +901,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T !providers.some((provider) => provider.instanceId === claudeInstanceId); attempt += 1 ) { - yield* wait(10); + yield* wait(); providers = yield* registry.getProviders; } @@ -917,7 +926,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T // assertions below fail. it.effect("propagates real Codex probe failures to the aggregator at boot", () => Effect.gen(function* () { - const missingBinary = `t3code_codex_missing_${process.pid}_${Date.now()}`; + const missingBinary = `t3code_codex_missing_`; const serverSettings = yield* makeMutableServerSettingsService( Schema.decodeSync(ServerSettings)( deepMerge(DEFAULT_SERVER_SETTINGS, { @@ -1013,20 +1022,10 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T // rebuilt instance's refresh (previous bug mode), the aggregator // keeps the old snapshot and this test fails. // - // `live` (imported from `@effect/vitest`) is used instead of - // `it.effect` so real timers coordinate the fibres that drive the - // settings → reconcile → sync pipeline. Under `it.effect`'s - // TestClock, `Effect.sleep` blocks until `TestClock.adjust`, which - // would require this test to reach into the internals of the - // reconcile pipeline to advance it step by step. - // - // The nested `it` handed to `it.layer(…, (it) => …)` is the - // `MethodsNonLive` variant and therefore lacks `.live`; the - // top-level `live` export from `@effect/vitest` is the equivalent. - live("re-probes when settings change the codex binaryPath", () => + it.effect("re-probes when settings change the codex binaryPath", () => Effect.gen(function* () { - const firstMissing = `t3code_codex_first_${process.pid}_${Date.now()}`; - const secondMissing = `t3code_codex_second_${process.pid}_${Date.now()}`; + const firstMissing = `t3code_codex_first_`; + const secondMissing = `t3code_codex_second_`; const serverSettings = yield* makeMutableServerSettingsService( Schema.decodeSync(ServerSettings)( deepMerge(DEFAULT_SERVER_SETTINGS, { @@ -1093,11 +1092,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T }, }); - // Poll with real timers (via `it.live`) until `checkedAt` - // advances or we hit a generous 3-second ceiling. Anything - // slower than that is a regression — the real probe fails - // fast on ENOENT, and the reconcile + sync pipeline is - // purely in-process. + // Poll with TestClock until `checkedAt` advances or we hit a + // generous virtual 3-second ceiling. const refreshed = yield* Effect.gen(function* () { for (let attempts = 0; attempts < 60; attempts += 1) { const providers = yield* registry.getProviders; @@ -1105,7 +1101,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T if (codex !== undefined && codex.checkedAt !== initialCheckedAt) { return providers; } - yield* Effect.sleep("50 millis"); + yield* TestClock.adjust("50 millis"); + yield* Effect.yieldNow; } return yield* registry.getProviders; }); diff --git a/apps/server/src/provider/Layers/ProviderRegistry.ts b/apps/server/src/provider/Layers/ProviderRegistry.ts index 9018b6624ed..22a120f0a52 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.ts @@ -29,7 +29,15 @@ import { type ServerProvider, type ServerProviderUpdateState, } from "@t3tools/contracts"; -import { Cause, Effect, Equal, FileSystem, Layer, Path, PubSub, Ref, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Equal from "effect/Equal"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Stream from "effect/Stream"; import * as Semaphore from "effect/Semaphore"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index 7e771251437..ca84cb06986 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -21,7 +22,17 @@ import { import { createModelSelection } from "@t3tools/shared/model"; import { it, assert, vi } from "@effect/vitest"; -import { Effect, Exit, Fiber, Layer, Metric, Option, PubSub, Ref, Scope, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as Layer from "effect/Layer"; +import * as Metric from "effect/Metric"; +import * as Option from "effect/Option"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; +import { TestClock } from "effect/testing"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { @@ -83,7 +94,7 @@ function makeFakeCodexAdapter(provider: ProviderDriverKind = CODEX_DRIVER) { const startSession = vi.fn((input: ProviderSessionStartInput) => Effect.sync(() => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const session: ProviderSession = { provider, ...(input.providerInstanceId !== undefined @@ -246,8 +257,8 @@ function makeFakeCodexAdapter(provider: ProviderDriverKind = CODEX_DRIVER) { }; } -const sleep = (ms: number) => - Effect.promise(() => new Promise((resolve) => setTimeout(resolve, ms))); +const advanceTestClock = (ms: number) => + TestClock.adjust(`${ms} millis`).pipe(Effect.andThen(Effect.yieldNow)); const hasMetricSnapshot = ( snapshots: ReadonlyArray, @@ -572,18 +583,18 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se yield* Effect.gen(function* () { yield* ProviderService; - yield* sleep(10); + yield* advanceTestClock(10); codex.emit({ eventId: asEventId("evt-canonical-thread-segment"), provider: ProviderDriverKind.make("codex"), threadId: asThreadId("thread-canonical-thread-segment"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", type: "turn.completed", payload: { state: "completed", }, }); - yield* sleep(20); + yield* advanceTestClock(20); }).pipe(Effect.provide(providerLayer)); assert.equal(canonicalEvents.length, 1); @@ -704,7 +715,7 @@ it.effect( ...existing, status: "ready", resumeCursor: updatedResumeCursor, - updatedAt: new Date(Date.now() + 1_000).toISOString(), + updatedAt: "2026-01-01T00:00:01.000Z", })); return session; }).pipe(Effect.provide(firstProviderLayer)); @@ -1403,20 +1414,20 @@ fanout.layer("ProviderServiceLive fanout", (it) => { const consumer = yield* Stream.runForEach(provider.streamEvents, (event) => Ref.update(eventsRef, (current) => [...current, event]), ).pipe(Effect.forkChild); - yield* sleep(50); + yield* advanceTestClock(50); const completedEvent: LegacyProviderRuntimeEvent = { type: "turn.completed", eventId: asEventId("evt-1"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: session.threadId, turnId: asTurnId("turn-1"), status: "completed", }; fanout.codex.emit(completedEvent); - yield* sleep(50); + yield* advanceTestClock(50); const events = yield* Ref.get(eventsRef); yield* Fiber.interrupt(consumer); @@ -1450,13 +1461,13 @@ fanout.layer("ProviderServiceLive fanout", (it) => { Stream.runForEach((event) => Ref.update(receivedRef, (current) => [...current, event])), Effect.forkChild, ); - yield* sleep(50); + yield* advanceTestClock(50); fanout.codex.emit({ type: "tool.started", eventId: asEventId("evt-seq-1"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: session.threadId, turnId: asTurnId("turn-1"), toolKind: "command", @@ -1466,7 +1477,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { type: "tool.completed", eventId: asEventId("evt-seq-2"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: session.threadId, turnId: asTurnId("turn-1"), toolKind: "command", @@ -1476,7 +1487,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { type: "turn.completed", eventId: asEventId("evt-seq-3"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: session.threadId, turnId: asTurnId("turn-1"), status: "completed", @@ -1515,14 +1526,14 @@ fanout.layer("ProviderServiceLive fanout", (it) => { Stream.runForEach(() => Effect.fail("listener crash")), Effect.forkChild, ); - yield* sleep(50); + yield* advanceTestClock(50); const events: ReadonlyArray = [ { type: "tool.completed", eventId: asEventId("evt-ordered-1"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: session.threadId, turnId: asTurnId("turn-1"), toolKind: "command", @@ -1533,7 +1544,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { type: "message.delta", eventId: asEventId("evt-ordered-2"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: session.threadId, turnId: asTurnId("turn-1"), delta: "hello", @@ -1542,7 +1553,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { type: "turn.completed", eventId: asEventId("evt-ordered-3"), provider: ProviderDriverKind.make("codex"), - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", threadId: session.threadId, turnId: asTurnId("turn-1"), status: "completed", @@ -1758,7 +1769,7 @@ validation.layer("ProviderServiceLive validation", (it) => { validation.codex.startSession.mockImplementationOnce((input: ProviderSessionStartInput) => Effect.sync(() => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; return { provider: ProviderDriverKind.make("codex"), status: "ready", diff --git a/apps/server/src/provider/Layers/ProviderService.ts b/apps/server/src/provider/Layers/ProviderService.ts index 05b4e72ec18..1f96742b787 100644 --- a/apps/server/src/provider/Layers/ProviderService.ts +++ b/apps/server/src/provider/Layers/ProviderService.ts @@ -24,7 +24,16 @@ import { type ProviderRuntimeEvent, type ProviderSession, } from "@t3tools/contracts"; -import { Cause, Effect, Layer, Option, PubSub, Ref, Schema, SchemaIssue, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as SchemaIssue from "effect/SchemaIssue"; +import * as Stream from "effect/Stream"; import { increment, @@ -199,6 +208,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( const registry = yield* ProviderAdapterRegistry; const directory = yield* ProviderSessionDirectory; const runtimeEventPubSub = yield* PubSub.unbounded(); + const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); const publishRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect => Effect.succeed(event).pipe( @@ -655,7 +665,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}), activeTurnId: turn.turnId, lastRuntimeEvent: "provider.sendTurn", - lastRuntimeEventAt: new Date().toISOString(), + lastRuntimeEventAt: yield* nowIso, }, }); yield* analytics.record("provider.turn.sent", { @@ -977,27 +987,34 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( ), ).pipe(Effect.map((sessionsByAdapter) => sessionsByAdapter.flatMap((sessions) => sessions))); yield* Effect.forEach(activeSessions, (session) => - upsertSessionBinding(session, session.threadId, { - lastRuntimeEvent: "provider.stopAll", - lastRuntimeEventAt: new Date().toISOString(), - }), + Effect.flatMap(nowIso, (lastRuntimeEventAt) => + upsertSessionBinding(session, session.threadId, { + lastRuntimeEvent: "provider.stopAll", + lastRuntimeEventAt, + }), + ), ).pipe(Effect.asVoid); yield* Effect.forEach(currentAdapters, ([, adapter]) => adapter.stopAll()).pipe(Effect.asVoid); const bindings = yield* directory.listBindings().pipe(Effect.orElseSucceed(() => [])); - yield* Effect.forEach(bindings, (binding) => { - const providerInstanceId = dieOnMissingBindingInstanceId("ProviderService.stopAll", binding); - return directory.upsert({ - threadId: binding.threadId, - provider: binding.provider, - providerInstanceId, - status: "stopped", - runtimePayload: { - activeTurnId: null, - lastRuntimeEvent: "provider.stopAll", - lastRuntimeEventAt: new Date().toISOString(), - }, - }); - }).pipe(Effect.asVoid); + yield* Effect.forEach(bindings, (binding) => + Effect.gen(function* () { + const providerInstanceId = dieOnMissingBindingInstanceId( + "ProviderService.stopAll", + binding, + ); + return yield* directory.upsert({ + threadId: binding.threadId, + provider: binding.provider, + providerInstanceId, + status: "stopped", + runtimePayload: { + activeTurnId: null, + lastRuntimeEvent: "provider.stopAll", + lastRuntimeEventAt: yield* nowIso, + }, + }); + }), + ).pipe(Effect.asVoid); yield* analytics.record("provider.sessions.stopped_all", { sessionCount: threadIds.length, }); diff --git a/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts b/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts index 28a168c43ad..f9793ca9d1f 100644 --- a/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts +++ b/apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -6,7 +7,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { ProviderDriverKind, ThreadId } from "@t3tools/contracts"; import { it, assert } from "@effect/vitest"; import { assertSome } from "@effect/vitest/utils"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { @@ -209,7 +212,7 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL adapterKey: "claudeAgent", runtimeMode: "full-access", status: "running", - lastSeenAt: new Date().toISOString(), + lastSeenAt: "2026-01-01T00:00:00.000Z", resumeCursor: null, runtimePayload: null, }); diff --git a/apps/server/src/provider/Layers/ProviderSessionDirectory.ts b/apps/server/src/provider/Layers/ProviderSessionDirectory.ts index e1479082f2f..34f43a84612 100644 --- a/apps/server/src/provider/Layers/ProviderSessionDirectory.ts +++ b/apps/server/src/provider/Layers/ProviderSessionDirectory.ts @@ -1,5 +1,9 @@ import { defaultInstanceIdForDriver, ProviderDriverKind, type ThreadId } from "@t3tools/contracts"; -import { Effect, Layer, Option, Schema } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import type { ProviderSessionRuntime } from "../../persistence/Services/ProviderSessionRuntime.ts"; import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts"; @@ -110,7 +114,7 @@ const makeProviderSessionDirectory = Effect.gen(function* () { }); } - const now = new Date().toISOString(); + const now = DateTime.formatIso(yield* DateTime.now); const providerChanged = existingRuntime !== undefined && existingRuntime.providerName !== binding.provider; const providerInstanceId = diff --git a/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts b/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts index b2b9af2c1ec..57903e3776c 100644 --- a/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts +++ b/apps/server/src/provider/Layers/ProviderSessionReaper.test.ts @@ -6,7 +6,15 @@ import { ProviderDriverKind, ProviderInstanceId, } from "@t3tools/contracts"; -import { Effect, Exit, Layer, ManagedRuntime, Option, Scope, Stream } from "effect"; +import * as Clock from "effect/Clock"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as Option from "effect/Option"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { afterEach, describe, expect, it, vi } from "vitest"; import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; @@ -28,21 +36,25 @@ async function waitFor( predicate: () => boolean | Promise, timeoutMs = 2_000, ): Promise { - const deadline = Date.now() + timeoutMs; + const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; const poll = async (): Promise => { if (await predicate()) { return; } - if (Date.now() >= deadline) { + if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error("Timed out waiting for expectation."); } - await new Promise((resolve) => setTimeout(resolve, 10)); + await Effect.runPromise(Effect.yieldNow); return poll(); }; return poll(); } +const drainFibers = Effect.forEach(Array.from({ length: 10 }), () => Effect.yieldNow, { + discard: true, +}); + const unsupported = () => Effect.die(new Error("Unsupported provider call in test")) as never; function makeReadModel( @@ -59,7 +71,7 @@ function makeReadModel( } | null; }>, ) { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const projectId = ProjectId.make("project-provider-session-reaper"); return { @@ -207,7 +219,7 @@ describe("ProviderSessionReaper", () => { it("reaps stale persisted sessions without active turns", async () => { const threadId = ThreadId.make("thread-reaper-stale"); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const harness = await createHarness({ readModel: makeReadModel([ { @@ -255,7 +267,7 @@ describe("ProviderSessionReaper", () => { it("skips stale sessions when the thread still has an active turn", async () => { const threadId = ThreadId.make("thread-reaper-active-turn"); const turnId = TurnId.make("turn-reaper-active"); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const harness = await createHarness({ readModel: makeReadModel([ { @@ -293,7 +305,7 @@ describe("ProviderSessionReaper", () => { const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); scope = await Effect.runPromise(Scope.make("sequential")); await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); - await new Promise((resolve) => setTimeout(resolve, 50)); + await Effect.runPromise(drainFibers); expect(harness.stopSession).not.toHaveBeenCalled(); const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); @@ -302,7 +314,7 @@ describe("ProviderSessionReaper", () => { it("does not reap sessions that are still within the inactivity threshold", async () => { const threadId = ThreadId.make("thread-reaper-fresh"); - const now = new Date().toISOString(); + const now = DateTime.formatIso(await Effect.runPromise(DateTime.now)); const harness = await createHarness({ readModel: makeReadModel([ { @@ -340,7 +352,7 @@ describe("ProviderSessionReaper", () => { const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); scope = await Effect.runPromise(Scope.make("sequential")); await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); - await new Promise((resolve) => setTimeout(resolve, 50)); + await Effect.runPromise(drainFibers); expect(harness.stopSession).not.toHaveBeenCalled(); const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); @@ -349,7 +361,7 @@ describe("ProviderSessionReaper", () => { it("skips persisted sessions that are already marked stopped", async () => { const threadId = ThreadId.make("thread-reaper-stopped"); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const harness = await createHarness({ readModel: makeReadModel([ { @@ -387,7 +399,7 @@ describe("ProviderSessionReaper", () => { const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); scope = await Effect.runPromise(Scope.make("sequential")); await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); - await new Promise((resolve) => setTimeout(resolve, 50)); + await Effect.runPromise(drainFibers); expect(harness.stopSession).not.toHaveBeenCalled(); const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); @@ -397,7 +409,7 @@ describe("ProviderSessionReaper", () => { it("continues reaping other sessions when one stop attempt fails", async () => { const failedThreadId = ThreadId.make("thread-reaper-stop-failure"); const reapedThreadId = ThreadId.make("thread-reaper-stop-success"); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const harness = await createHarness({ readModel: makeReadModel([ { @@ -483,7 +495,7 @@ describe("ProviderSessionReaper", () => { it("continues reaping other sessions when one stop attempt defects", async () => { const defectThreadId = ThreadId.make("thread-reaper-stop-defect"); const reapedThreadId = ThreadId.make("thread-reaper-stop-after-defect"); - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const harness = await createHarness({ readModel: makeReadModel([ { diff --git a/apps/server/src/provider/Layers/ProviderSessionReaper.ts b/apps/server/src/provider/Layers/ProviderSessionReaper.ts index 916e5fcea4a..ca396b40596 100644 --- a/apps/server/src/provider/Layers/ProviderSessionReaper.ts +++ b/apps/server/src/provider/Layers/ProviderSessionReaper.ts @@ -1,4 +1,9 @@ -import { Duration, Effect, Layer, Option, Schedule } from "effect"; +import * as Clock from "effect/Clock"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schedule from "effect/Schedule"; import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts"; @@ -30,7 +35,7 @@ const makeProviderSessionReaper = (options?: ProviderSessionReaperLiveOptions) = const sweep = Effect.gen(function* () { const bindings = yield* directory.listBindings(); - const now = Date.now(); + const now = yield* Clock.currentTimeMillis; let reapedCount = 0; for (const binding of bindings) { diff --git a/apps/server/src/provider/Layers/scopedSafeTeardown.test.ts b/apps/server/src/provider/Layers/scopedSafeTeardown.test.ts index ebbc379b7db..5bd9325053a 100644 --- a/apps/server/src/provider/Layers/scopedSafeTeardown.test.ts +++ b/apps/server/src/provider/Layers/scopedSafeTeardown.test.ts @@ -1,5 +1,7 @@ import { it } from "@effect/vitest"; -import { Cause, Effect, Exit } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; import { describe, expect } from "vitest"; import { scopedSafeTeardown } from "./scopedSafeTeardown.ts"; diff --git a/apps/server/src/provider/Layers/scopedSafeTeardown.ts b/apps/server/src/provider/Layers/scopedSafeTeardown.ts index 688374590e7..8a1bc829cbf 100644 --- a/apps/server/src/provider/Layers/scopedSafeTeardown.ts +++ b/apps/server/src/provider/Layers/scopedSafeTeardown.ts @@ -34,7 +34,9 @@ * * @module provider/Layers/scopedSafeTeardown */ -import { Effect, Exit, Scope } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Scope from "effect/Scope"; /** * Run `effect` with a freshly made `Scope.Scope`, guaranteeing that diff --git a/apps/server/src/provider/ProviderDriver.ts b/apps/server/src/provider/ProviderDriver.ts index e7d3e40cd8c..3a57f374de4 100644 --- a/apps/server/src/provider/ProviderDriver.ts +++ b/apps/server/src/provider/ProviderDriver.ts @@ -26,7 +26,9 @@ import type { ProviderInstanceEnvironment, ProviderInstanceId, } from "@t3tools/contracts"; -import type { Effect, Schema, Scope } from "effect"; +import type * as Effect from "effect/Effect"; +import type * as Schema from "effect/Schema"; +import type * as Scope from "effect/Scope"; import type { TextGenerationShape } from "../textGeneration/TextGeneration.ts"; import type { ProviderAdapterError, ProviderDriverError } from "./Errors.ts"; diff --git a/apps/server/src/provider/Services/ProviderAdapter.ts b/apps/server/src/provider/Services/ProviderAdapter.ts index dd1be738721..01eeae7b7bd 100644 --- a/apps/server/src/provider/Services/ProviderAdapter.ts +++ b/apps/server/src/provider/Services/ProviderAdapter.ts @@ -20,8 +20,8 @@ import type { ProviderTurnStartResult, TurnId, } from "@t3tools/contracts"; -import type { Effect } from "effect"; -import type { Stream } from "effect"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; export type ProviderSessionModelSwitchMode = "in-session" | "unsupported"; diff --git a/apps/server/src/provider/Services/ProviderAdapterRegistry.ts b/apps/server/src/provider/Services/ProviderAdapterRegistry.ts index 1161487d5a3..5b755c42eed 100644 --- a/apps/server/src/provider/Services/ProviderAdapterRegistry.ts +++ b/apps/server/src/provider/Services/ProviderAdapterRegistry.ts @@ -20,8 +20,11 @@ * @module ProviderAdapterRegistry */ import type { ProviderDriverKind, ProviderInstanceId } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect, PubSub, Scope, Stream } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as PubSub from "effect/PubSub"; +import type * as Scope from "effect/Scope"; +import type * as Stream from "effect/Stream"; import type { ProviderAdapterError, ProviderUnsupportedError } from "../Errors.ts"; import type { ProviderAdapterShape } from "./ProviderAdapter.ts"; diff --git a/apps/server/src/provider/Services/ProviderInstanceRegistry.ts b/apps/server/src/provider/Services/ProviderInstanceRegistry.ts index f642475a243..cfea1142666 100644 --- a/apps/server/src/provider/Services/ProviderInstanceRegistry.ts +++ b/apps/server/src/provider/Services/ProviderInstanceRegistry.ts @@ -18,8 +18,11 @@ * @module provider/Services/ProviderInstanceRegistry */ import type { ProviderInstanceId, ServerProvider } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect, PubSub, Scope, Stream } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as PubSub from "effect/PubSub"; +import type * as Scope from "effect/Scope"; +import type * as Stream from "effect/Stream"; import type { ProviderInstance } from "../ProviderDriver.ts"; diff --git a/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts b/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts index ff861f961c7..98d45080237 100644 --- a/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts +++ b/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts @@ -29,8 +29,8 @@ * @module provider/Services/ProviderInstanceRegistryMutator */ import type { ProviderInstanceConfigMap } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; export interface ProviderInstanceRegistryMutatorShape { /** diff --git a/apps/server/src/provider/Services/ProviderRegistry.ts b/apps/server/src/provider/Services/ProviderRegistry.ts index b131c1c5b8d..b7426b30338 100644 --- a/apps/server/src/provider/Services/ProviderRegistry.ts +++ b/apps/server/src/provider/Services/ProviderRegistry.ts @@ -12,8 +12,9 @@ import type { ServerProvider, ServerProviderUpdateState, } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect, Stream } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; import type { ProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; export type ProviderMaintenanceActionKind = "update"; diff --git a/apps/server/src/provider/Services/ProviderService.ts b/apps/server/src/provider/Services/ProviderService.ts index 17a64689b49..4d4cb4fa01a 100644 --- a/apps/server/src/provider/Services/ProviderService.ts +++ b/apps/server/src/provider/Services/ProviderService.ts @@ -24,8 +24,9 @@ import type { ThreadId, ProviderTurnStartResult, } from "@t3tools/contracts"; -import { Context } from "effect"; -import type { Effect, Stream } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; import type { ProviderServiceError } from "../Errors.ts"; import type { ProviderAdapterCapabilities } from "./ProviderAdapter.ts"; diff --git a/apps/server/src/provider/Services/ProviderSessionDirectory.ts b/apps/server/src/provider/Services/ProviderSessionDirectory.ts index 99ffb800f90..f2dd4323f7a 100644 --- a/apps/server/src/provider/Services/ProviderSessionDirectory.ts +++ b/apps/server/src/provider/Services/ProviderSessionDirectory.ts @@ -5,8 +5,9 @@ import type { RuntimeMode, ThreadId, } from "@t3tools/contracts"; -import { Option, Context } from "effect"; -import type { Effect } from "effect"; +import * as Option from "effect/Option"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProviderSessionDirectoryPersistenceError, diff --git a/apps/server/src/provider/Services/ProviderSessionReaper.ts b/apps/server/src/provider/Services/ProviderSessionReaper.ts index b13b6f7e0c7..7c4627eca89 100644 --- a/apps/server/src/provider/Services/ProviderSessionReaper.ts +++ b/apps/server/src/provider/Services/ProviderSessionReaper.ts @@ -1,5 +1,6 @@ -import { Context } from "effect"; -import type { Effect, Scope } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type * as Scope from "effect/Scope"; export interface ProviderSessionReaperShape { /** diff --git a/apps/server/src/provider/Services/ServerProvider.ts b/apps/server/src/provider/Services/ServerProvider.ts index fb52080d50b..12162512927 100644 --- a/apps/server/src/provider/Services/ServerProvider.ts +++ b/apps/server/src/provider/Services/ServerProvider.ts @@ -1,5 +1,6 @@ import type { ServerProvider } from "@t3tools/contracts"; -import type { Effect, Stream } from "effect"; +import type * as Effect from "effect/Effect"; +import type * as Stream from "effect/Stream"; import type { ProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; export interface ServerProviderShape { diff --git a/apps/server/src/provider/acp/AcpAdapterSupport.ts b/apps/server/src/provider/acp/AcpAdapterSupport.ts index 499ebd2e707..35a418fd99a 100644 --- a/apps/server/src/provider/acp/AcpAdapterSupport.ts +++ b/apps/server/src/provider/acp/AcpAdapterSupport.ts @@ -3,7 +3,7 @@ import { type ProviderDriverKind, type ThreadId, } from "@t3tools/contracts"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import * as EffectAcpErrors from "effect-acp/errors"; import { diff --git a/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts b/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts index 4820d5c2e58..1b8f7be5d7d 100644 --- a/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts +++ b/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as path from "node:path"; import * as os from "node:os"; import { fileURLToPath } from "node:url"; @@ -5,7 +6,8 @@ import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Stream from "effect/Stream"; import { describe, expect } from "vitest"; import { AcpSessionRuntime, type AcpSessionRequestLogEvent } from "./AcpSessionRuntime.ts"; diff --git a/apps/server/src/provider/acp/AcpNativeLogging.ts b/apps/server/src/provider/acp/AcpNativeLogging.ts index 3d44aac0a47..f00ffcb6655 100644 --- a/apps/server/src/provider/acp/AcpNativeLogging.ts +++ b/apps/server/src/provider/acp/AcpNativeLogging.ts @@ -1,5 +1,8 @@ import type { ProviderDriverKind, ThreadId } from "@t3tools/contracts"; -import { Cause, Effect } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Random from "effect/Random"; import type * as EffectAcpProtocol from "effect-acp/protocol"; import type { EventNdjsonLogger } from "../Layers/EventNdjsonLogger.ts"; @@ -14,12 +17,12 @@ function writeNativeAcpLog(input: { }): Effect.Effect { return Effect.gen(function* () { if (!input.nativeEventLogger) return; - const observedAt = new Date().toISOString(); + const observedAt = DateTime.formatIso(yield* DateTime.now); yield* input.nativeEventLogger.write( { observedAt, event: { - id: crypto.randomUUID(), + id: yield* Random.nextUUIDv4, kind: input.kind, provider: input.provider, createdAt: observedAt, diff --git a/apps/server/src/provider/acp/AcpSessionRuntime.ts b/apps/server/src/provider/acp/AcpSessionRuntime.ts index b4cf6656086..250ea140373 100644 --- a/apps/server/src/provider/acp/AcpSessionRuntime.ts +++ b/apps/server/src/provider/acp/AcpSessionRuntime.ts @@ -1,4 +1,14 @@ -import { Cause, Deferred, Effect, Exit, Layer, Queue, Ref, Scope, Context, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Queue from "effect/Queue"; +import * as Ref from "effect/Ref"; +import * as Scope from "effect/Scope"; +import * as Context from "effect/Context"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import * as EffectAcpClient from "effect-acp/client"; import * as EffectAcpErrors from "effect-acp/errors"; @@ -248,7 +258,7 @@ const makeAcpSessionRuntime = ( } return yield* new EffectAcpErrors.AcpTransportError({ detail: "ACP session runtime has not been started", - cause: new Error("ACP session runtime has not been started"), + cause: "ACP session runtime has not been started", }); }); @@ -267,7 +277,7 @@ const makeAcpSessionRuntime = ( } return yield* new EffectAcpErrors.AcpRequestError({ code: -32602, - errorMessage: `Invalid value ${JSON.stringify(value)} for session config option "${configOption.id}": expected boolean`, + errorMessage: `Invalid value ${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(value)} for session config option "${configOption.id}": expected boolean`, data: { configId: configOption.id, expectedType: "boolean", @@ -278,7 +288,7 @@ const makeAcpSessionRuntime = ( if (typeof value !== "string") { return yield* new EffectAcpErrors.AcpRequestError({ code: -32602, - errorMessage: `Invalid value ${JSON.stringify(value)} for session config option "${configOption.id}": expected string`, + errorMessage: `Invalid value ${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(value)} for session config option "${configOption.id}": expected string`, data: { configId: configOption.id, expectedType: "string", @@ -292,7 +302,7 @@ const makeAcpSessionRuntime = ( } return yield* new EffectAcpErrors.AcpRequestError({ code: -32602, - errorMessage: `Invalid value ${JSON.stringify(value)} for session config option "${configOption.id}": expected one of ${allowedValues.join(", ")}`, + errorMessage: `Invalid value ${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(value)} for session config option "${configOption.id}": expected one of ${allowedValues.join(", ")}`, data: { configId: configOption.id, allowedValues, diff --git a/apps/server/src/provider/acp/CursorAcpCliProbe.test.ts b/apps/server/src/provider/acp/CursorAcpCliProbe.test.ts index 7744e24ac97..30fbb1842fc 100644 --- a/apps/server/src/provider/acp/CursorAcpCliProbe.test.ts +++ b/apps/server/src/provider/acp/CursorAcpCliProbe.test.ts @@ -4,7 +4,8 @@ */ import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect } from "effect"; +import * as Console from "effect/Console"; +import * as Effect from "effect/Effect"; import { describe, expect } from "vitest"; import type * as EffectAcpSchema from "effect-acp/schema"; @@ -44,12 +45,14 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", const runtime = yield* AcpSessionRuntime; const started = yield* runtime.start(); const result = started.sessionSetupResult; - console.log("session/new result:", JSON.stringify(result, null, 2)); + // @effect-diagnostics-next-line preferSchemaOverJson:off + yield* Console.log("session/new result:", JSON.stringify(result, null, 2)); expect(typeof started.sessionId).toBe("string"); const configOptions = result.configOptions; - console.log("session/new configOptions:", JSON.stringify(configOptions, null, 2)); + // @effect-diagnostics-next-line preferSchemaOverJson:off + yield* Console.log("session/new configOptions:", JSON.stringify(configOptions, null, 2)); if (Array.isArray(configOptions)) { const modelConfig = configOptions.find((opt) => opt.category === "model"); @@ -59,9 +62,11 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", opt.category === "model_option" || opt.category === "model_config", ); - console.log("Model config option:", JSON.stringify(modelConfig, null, 2)); - console.log( + // @effect-diagnostics-next-line preferSchemaOverJson:off + yield* Console.log("Model config option:", JSON.stringify(modelConfig, null, 2)); + yield* Console.log( "Parameterized model config options:", + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify(parameterizedOptions, null, 2), ); expect(modelConfig).toBeDefined(); @@ -107,8 +112,8 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", const setResult: EffectAcpSchema.SetSessionConfigOptionResponse = yield* runtime.setConfigOption(modelConfigId, "gpt-5.4"); - - console.log("session/set_config_option result:", JSON.stringify(setResult, null, 2)); + // @effect-diagnostics-next-line preferSchemaOverJson:off + yield* Console.log("session/set_config_option result:", JSON.stringify(setResult, null, 2)); if (Array.isArray(setResult.configOptions)) { const modelConfig = setResult.configOptions.find((opt) => opt.category === "model"); diff --git a/apps/server/src/provider/acp/CursorAcpExtension.ts b/apps/server/src/provider/acp/CursorAcpExtension.ts index dff65535c9d..4ab36636fb9 100644 --- a/apps/server/src/provider/acp/CursorAcpExtension.ts +++ b/apps/server/src/provider/acp/CursorAcpExtension.ts @@ -3,7 +3,7 @@ * Additional reference provided by the Cursor team: https://anysphere.enterprise.slack.com/files/U068SSJE141/F0APT1HSZRP/cursor-acp-extension-method-schemas.md */ import type { UserInputQuestion } from "@t3tools/contracts"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; const CursorAskQuestionOption = Schema.Struct({ id: Schema.String, diff --git a/apps/server/src/provider/acp/CursorAcpSupport.test.ts b/apps/server/src/provider/acp/CursorAcpSupport.test.ts index 30941acbfd5..89422d865ad 100644 --- a/apps/server/src/provider/acp/CursorAcpSupport.test.ts +++ b/apps/server/src/provider/acp/CursorAcpSupport.test.ts @@ -1,4 +1,4 @@ -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import type * as EffectAcpSchema from "effect-acp/schema"; import { describe, expect, it } from "vitest"; @@ -105,11 +105,9 @@ describe("applyCursorAcpModelSelection", () => { { id: "fastMode", value: true }, ], mapError: ({ step, configId, cause }) => - new Error( - step === "set-config-option" - ? `failed to set config option ${configId}: ${cause.message}` - : `failed to set model: ${cause.message}`, - ), + step === "set-config-option" + ? `failed to set config option ${configId}: ${cause.message}` + : `failed to set model: ${cause.message}`, }), ); diff --git a/apps/server/src/provider/acp/CursorAcpSupport.ts b/apps/server/src/provider/acp/CursorAcpSupport.ts index 3e405dd7ff3..5893c33215d 100644 --- a/apps/server/src/provider/acp/CursorAcpSupport.ts +++ b/apps/server/src/provider/acp/CursorAcpSupport.ts @@ -1,5 +1,7 @@ import { type CursorSettings, type ProviderOptionSelection } from "@t3tools/contracts"; -import { Effect, Layer, Scope } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Scope from "effect/Scope"; import { ChildProcessSpawner } from "effect/unstable/process"; import type * as EffectAcpErrors from "effect-acp/errors"; diff --git a/apps/server/src/provider/builtInProviderCatalog.ts b/apps/server/src/provider/builtInProviderCatalog.ts index ee25b6d0184..559726c65ea 100644 --- a/apps/server/src/provider/builtInProviderCatalog.ts +++ b/apps/server/src/provider/builtInProviderCatalog.ts @@ -1,5 +1,5 @@ import type { ProviderDriverKind, ProviderInstanceId, ServerProvider } from "@t3tools/contracts"; -import type { Stream } from "effect"; +import type * as Stream from "effect/Stream"; import type { ServerProviderShape } from "./Services/ServerProvider.ts"; export type ProviderSnapshotSource = { diff --git a/apps/server/src/provider/makeManagedServerProvider.test.ts b/apps/server/src/provider/makeManagedServerProvider.test.ts index 8595a56d5b2..89c7452bb18 100644 --- a/apps/server/src/provider/makeManagedServerProvider.test.ts +++ b/apps/server/src/provider/makeManagedServerProvider.test.ts @@ -1,7 +1,12 @@ import { describe, it, assert } from "@effect/vitest"; import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts"; import { createModelCapabilities } from "@t3tools/shared/model"; -import { Deferred, Effect, Fiber, PubSub, Ref, Stream } from "effect"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Stream from "effect/Stream"; import { makeManagedServerProvider } from "./makeManagedServerProvider.ts"; diff --git a/apps/server/src/provider/makeManagedServerProvider.ts b/apps/server/src/provider/makeManagedServerProvider.ts index a6800da065d..1ecc9b70656 100644 --- a/apps/server/src/provider/makeManagedServerProvider.ts +++ b/apps/server/src/provider/makeManagedServerProvider.ts @@ -1,5 +1,12 @@ import type { ServerProvider } from "@t3tools/contracts"; -import { Duration, Effect, Equal, Fiber, PubSub, Ref, Scope, Stream } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Equal from "effect/Equal"; +import * as Fiber from "effect/Fiber"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import * as Semaphore from "effect/Semaphore"; import type { ServerProviderShape } from "./Services/ServerProvider.ts"; diff --git a/apps/server/src/provider/opencodeRuntime.ts b/apps/server/src/provider/opencodeRuntime.ts index c3e973e7e32..5780ec8661b 100644 --- a/apps/server/src/provider/opencodeRuntime.ts +++ b/apps/server/src/provider/opencodeRuntime.ts @@ -11,22 +11,21 @@ import { type QuestionAnswer, type QuestionRequest, } from "@opencode-ai/sdk/v2"; -import { - Cause, - Context, - Data, - Deferred, - Effect, - Exit, - Fiber, - Layer, - Option, - Predicate as P, - Ref, - Result, - Scope, - Stream, -} from "effect"; +import * as Cause from "effect/Cause"; +import * as Context from "effect/Context"; +import * as Data from "effect/Data"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as P from "effect/Predicate"; +import * as Ref from "effect/Ref"; +import * as Result from "effect/Result"; +import * as Scope from "effect/Scope"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { isWindowsCommandNotFound } from "../processRunner.ts"; @@ -66,7 +65,7 @@ export function openCodeRuntimeErrorDetail(cause: unknown): string { const status = (anyCause.response as { status?: number } | undefined)?.status; const body = anyCause.error ?? anyCause.data ?? anyCause.body; try { - return `status=${status ?? "?"} body=${JSON.stringify(body ?? cause)}`; + return `status=${status ?? "?"} body=${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(body ?? cause)}`; } catch { /* fall through */ } @@ -337,7 +336,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () { shell: process.platform === "win32", env: { ...(input.environment ?? process.env), - OPENCODE_CONFIG_CONTENT: JSON.stringify({}), + OPENCODE_CONFIG_CONTENT: Schema.encodeUnknownSync(Schema.UnknownFromJsonString)({}), }, }), ) diff --git a/apps/server/src/provider/providerMaintenance.test.ts b/apps/server/src/provider/providerMaintenance.test.ts index 20c47dd5f5b..e032b87a4d0 100644 --- a/apps/server/src/provider/providerMaintenance.test.ts +++ b/apps/server/src/provider/providerMaintenance.test.ts @@ -1,10 +1,12 @@ +// @effect-diagnostics nodeBuiltinImport:off import { afterEach, describe, expect, it } from "@effect/vitest"; import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; import * as NodeServices from "@effect/platform-node/NodeServices"; import os from "node:os"; import path from "node:path"; import { ProviderDriverKind } from "@t3tools/contracts"; -import { Effect, Random } from "effect"; +import * as Effect from "effect/Effect"; +import * as Random from "effect/Random"; import { clearLatestProviderVersionCacheForTests, createProviderVersionAdvisory, diff --git a/apps/server/src/provider/providerMaintenance.ts b/apps/server/src/provider/providerMaintenance.ts index 5a44a5ec21e..a733506766d 100644 --- a/apps/server/src/provider/providerMaintenance.ts +++ b/apps/server/src/provider/providerMaintenance.ts @@ -4,7 +4,11 @@ import { type ServerProviderVersionAdvisory, } from "@t3tools/contracts"; import { resolveCommandPath } from "@t3tools/shared/shell"; -import { DateTime, Effect, FileSystem, Option, Schema } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { HttpClient, HttpClientRequest } from "effect/unstable/http"; import { compareCliVersions } from "./cliVersion.ts"; diff --git a/apps/server/src/provider/providerMaintenanceCommandCoordinator.ts b/apps/server/src/provider/providerMaintenanceCommandCoordinator.ts index 875102c836d..7c456c3c484 100644 --- a/apps/server/src/provider/providerMaintenanceCommandCoordinator.ts +++ b/apps/server/src/provider/providerMaintenanceCommandCoordinator.ts @@ -1,4 +1,5 @@ -import { Effect, Ref } from "effect"; +import * as Effect from "effect/Effect"; +import * as Ref from "effect/Ref"; import * as Semaphore from "effect/Semaphore"; export interface ProviderMaintenanceCommandCoordinatorShape { diff --git a/apps/server/src/provider/providerMaintenanceRunner.test.ts b/apps/server/src/provider/providerMaintenanceRunner.test.ts index 4c0a751a62e..6975459735b 100644 --- a/apps/server/src/provider/providerMaintenanceRunner.test.ts +++ b/apps/server/src/provider/providerMaintenanceRunner.test.ts @@ -6,7 +6,15 @@ import { type ServerProviderUpdateState, } from "@t3tools/contracts"; import { ServerProviderUpdateError } from "@t3tools/contracts"; -import { Cause, Effect, Exit, Fiber, Layer, Ref, Schema, Sink, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as Layer from "effect/Layer"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as Sink from "effect/Sink"; +import * as Stream from "effect/Stream"; import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; diff --git a/apps/server/src/provider/providerMaintenanceRunner.ts b/apps/server/src/provider/providerMaintenanceRunner.ts index 36657bac998..6aa07515037 100644 --- a/apps/server/src/provider/providerMaintenanceRunner.ts +++ b/apps/server/src/provider/providerMaintenanceRunner.ts @@ -7,7 +7,16 @@ import { type ServerProviderUpdatedPayload, type ServerProviderUpdateState, } from "@t3tools/contracts"; -import { Cause, Context, DateTime, Duration, Effect, Layer, Option, Ref, Schema } from "effect"; +import * as Cause from "effect/Cause"; +import * as Context from "effect/Context"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; import { HttpClient } from "effect/unstable/http"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; @@ -45,6 +54,11 @@ export class ProviderMaintenanceRunner extends Context.Service< ProviderMaintenanceRunnerShape >()("t3/provider/ProviderMaintenanceRunner") {} +class ProviderMaintenanceCommandError extends Data.TaggedError("ProviderMaintenanceCommandError")<{ + readonly message: string; + readonly cause?: unknown; +}> {} + interface VerifiedProviderRefresh { readonly providers: ReadonlyArray; readonly verifiedProviders: ReadonlyArray; @@ -65,7 +79,10 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR .pipe( Effect.mapError( (cause) => - new Error(`Failed to run update command ${input.command}: ${cause.message}`), + new ProviderMaintenanceCommandError({ + message: `Failed to run update command ${input.command}: ${cause.message}`, + cause, + }), ), ); yield* Effect.addFinalizer(() => child.kill().pipe(Effect.ignore)); @@ -86,7 +103,10 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR ).pipe( Effect.mapError( (cause) => - new Error(cause instanceof Error ? cause.message : "Update command failed to run."), + new ProviderMaintenanceCommandError({ + message: cause instanceof Error ? cause.message : "Update command failed to run.", + cause, + }), ), ); diff --git a/apps/server/src/provider/providerSnapshot.ts b/apps/server/src/provider/providerSnapshot.ts index ce9eb950f10..c40903e1b45 100644 --- a/apps/server/src/provider/providerSnapshot.ts +++ b/apps/server/src/provider/providerSnapshot.ts @@ -8,7 +8,9 @@ import type { ServerProviderModel, ServerProviderState, } from "@t3tools/contracts"; -import { Effect, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Data from "effect/Data"; +import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { normalizeModelSlug } from "@t3tools/shared/model"; import { isWindowsCommandNotFound } from "../processRunner.ts"; @@ -25,6 +27,12 @@ export interface CommandResult { readonly code: number; } +export class ProviderCommandExecutionError extends Data.TaggedError( + "ProviderCommandExecutionError", +)<{ + readonly message: string; +}> {} + export interface ProviderProbeResult { readonly installed: boolean; readonly version: string | null; @@ -47,7 +55,7 @@ export function nonEmptyTrimmed(value: string | undefined): string | undefined { return trimmed.length > 0 ? trimmed : undefined; } -export function isCommandMissingCause(error: Error): boolean { +export function isCommandMissingCause(error: { readonly message: string }): boolean { const lower = error.message.toLowerCase(); return lower.includes("enoent") || lower.includes("notfound"); } @@ -67,7 +75,7 @@ export const spawnAndCollect = (binaryPath: string, command: ChildProcess.Comman const result: CommandResult = { stdout, stderr, code: exitCode }; if (isWindowsCommandNotFound(exitCode, stderr)) { - return yield* Effect.fail(new Error(`spawn ${binaryPath} ENOENT`)); + return yield* new ProviderCommandExecutionError({ message: `spawn ${binaryPath} ENOENT` }); } return result; }).pipe(Effect.scoped); diff --git a/apps/server/src/provider/providerStatusCache.test.ts b/apps/server/src/provider/providerStatusCache.test.ts index 8986ba48f29..64cb9ccd417 100644 --- a/apps/server/src/provider/providerStatusCache.test.ts +++ b/apps/server/src/provider/providerStatusCache.test.ts @@ -7,7 +7,8 @@ import { } from "@t3tools/contracts"; import { createModelCapabilities } from "@t3tools/shared/model"; import { assert, it } from "@effect/vitest"; -import { Effect, FileSystem } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import { hydrateCachedProvider, diff --git a/apps/server/src/provider/providerStatusCache.ts b/apps/server/src/provider/providerStatusCache.ts index db6c358c99e..0b9b365f360 100644 --- a/apps/server/src/provider/providerStatusCache.ts +++ b/apps/server/src/provider/providerStatusCache.ts @@ -4,7 +4,11 @@ import { type ServerProvider, ServerProvider as ServerProviderSchema, } from "@t3tools/contracts"; -import { Cause, Effect, FileSystem, Path, Schema } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { writeFileStringAtomically } from "../atomicWrite.ts"; diff --git a/apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts b/apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts index 9a4f107db8b..c696a51c37e 100644 --- a/apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts +++ b/apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts @@ -18,7 +18,11 @@ import { ProviderDriverKind, type ProviderInstanceId, } from "@t3tools/contracts"; -import { Effect, PubSub, Record, Result, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as PubSub from "effect/PubSub"; +import * as Record from "effect/Record"; +import * as Result from "effect/Result"; +import * as Stream from "effect/Stream"; import { ProviderUnsupportedError, type ProviderAdapterError } from "../Errors.ts"; import type { ProviderAdapterShape } from "../Services/ProviderAdapter.ts"; diff --git a/apps/server/src/provider/unavailableProviderSnapshot.ts b/apps/server/src/provider/unavailableProviderSnapshot.ts index 97a532a9d98..f8cbe042f3c 100644 --- a/apps/server/src/provider/unavailableProviderSnapshot.ts +++ b/apps/server/src/provider/unavailableProviderSnapshot.ts @@ -15,6 +15,8 @@ import { type ProviderInstanceId, type ServerProvider, } from "@t3tools/contracts"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; import { buildServerProvider } from "./providerSnapshot.ts"; @@ -25,7 +27,8 @@ export interface UnavailableProviderSnapshotInput { readonly accentColor?: string | undefined; readonly reason: string; /** - * Optional override for `checkedAt`. Defaulted to `new Date()` so callers + * Optional override for `checkedAt`. Defaulted to the current Effect + * `DateTime` so callers * (notably tests) don't have to pass it. */ readonly checkedAt?: string; @@ -40,7 +43,8 @@ export interface UnavailableProviderSnapshotInput { export function buildUnavailableProviderSnapshot( input: UnavailableProviderSnapshotInput, ): ServerProvider { - const checkedAt = input.checkedAt ?? new Date().toISOString(); + const checkedAt = + input.checkedAt ?? Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); const displayName = input.displayName?.trim() || (input.driverKind as string); const base = buildServerProvider({ diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index 32261dd618b..819abb979da 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -27,18 +27,17 @@ import { } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; import { assertFailure, assertInclude, assertTrue } from "@effect/vitest/utils"; -import { - Deferred, - DateTime, - Duration, - Effect, - FileSystem, - Layer, - ManagedRuntime, - Option, - Path, - Stream, -} from "effect"; +import * as Clock from "effect/Clock"; +import * as Deferred from "effect/Deferred"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Stream from "effect/Stream"; import { ChildProcessSpawner } from "effect/unstable/process"; import { FetchHttpClient, @@ -92,6 +91,7 @@ import { import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts"; import { ProjectSetupScriptRunner, + ProjectSetupScriptRunnerError, type ProjectSetupScriptRunnerShape, } from "./project/Services/ProjectSetupScriptRunner.ts"; import { @@ -116,6 +116,7 @@ import { ServerSecretStoreLive } from "./auth/Layers/ServerSecretStore.ts"; import { ServerAuthLive } from "./auth/Layers/ServerAuth.ts"; import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts"; +import * as Data from "effect/Data"; const defaultProjectId = ProjectId.make("project-default"); const defaultThreadId = ThreadId.make("thread-default"); @@ -137,7 +138,7 @@ const testEnvironmentDescriptor = { }, }; const makeDefaultOrchestrationReadModel = () => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; return { snapshotSequence: 0, updatedAt: now, @@ -181,7 +182,7 @@ const makeDefaultOrchestrationReadModel = () => { const makeDefaultOrchestrationThreadShell = ( overrides: Partial = {}, ): OrchestrationThreadShell => { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; return { id: defaultThreadId, projectId: defaultProjectId, @@ -302,15 +303,13 @@ const makeBrowserOtlpPayload = (spanName: string) => yield* Effect.promise(() => runtime.dispose()); } - const request = yield* Effect.promise(() => - Promise.race([ - collector.firstRequest, - new Promise((_, reject) => { - setTimeout(() => reject(new Error("Timed out waiting for OTLP trace export")), 1_000); - }), - ]), + const request = yield* Effect.raceFirst( + Effect.promise(() => collector.firstRequest).pipe(Effect.orDie), + Effect.sleep(Duration.seconds(1)).pipe( + Effect.andThen(Effect.die(new Error("Timed out waiting for OTLP trace export"))), + ), ); - + // @effect-diagnostics-next-line preferSchemaOverJson:off return JSON.parse(request.body) as OtlpTracer.TraceData; }); @@ -629,7 +628,7 @@ const buildAppUnderTest = (options?: { snapshotSequence: 0, projects: [], threads: [], - updatedAt: new Date(0).toISOString(), + updatedAt: "1970-01-01T00:00:00.000Z", }), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getProjectShellById: () => Effect.succeed(Option.none()), @@ -823,17 +822,23 @@ const bootstrapBearerSession = (credential = defaultDesktopBootstrapToken) => }; }); +class AuthenticationGetterError extends Data.TaggedError("AuthenticationGetterError")<{ + readonly message: string; +}> {} + const getAuthenticatedSessionCookieHeader = (credential = defaultDesktopBootstrapToken) => Effect.gen(function* () { const { response, cookie } = yield* bootstrapBrowserSession(credential); if (!response.ok) { - return yield* Effect.fail( - new Error(`Expected bootstrap session response to succeed, got ${response.status}`), - ); + return yield* new AuthenticationGetterError({ + message: `Expected bootstrap session response to succeed, got ${response.status}`, + }); } if (!cookie) { - return yield* Effect.fail(new Error("Expected bootstrap session response to set a cookie.")); + return yield* new AuthenticationGetterError({ + message: "Expected bootstrap session response to set a cookie.", + }); } return cookie.split(";")[0] ?? cookie; @@ -843,15 +848,15 @@ const getAuthenticatedBearerSessionToken = (credential = defaultDesktopBootstrap Effect.gen(function* () { const { response, body } = yield* bootstrapBearerSession(credential); if (!response.ok) { - return yield* Effect.fail( - new Error(`Expected bearer bootstrap response to succeed, got ${response.status}`), - ); + return yield* new AuthenticationGetterError({ + message: `Expected bearer bootstrap response to succeed, got ${response.status}`, + }); } if (!body.sessionToken) { - return yield* Effect.fail( - new Error("Expected bearer bootstrap response to include a session token."), - ); + return yield* new AuthenticationGetterError({ + message: "Expected bearer bootstrap response to include a session token.", + }); } return body.sessionToken; @@ -1758,6 +1763,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { "content-type": "application/json", origin: "http://localhost:5733", }, + // @effect-diagnostics-next-line preferSchemaOverJson:off body: HttpBody.text(JSON.stringify(payload), "application/json"), }); @@ -1803,6 +1809,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { ]); assert.deepEqual(upstreamRequests, [ { + // @effect-diagnostics-next-line preferSchemaOverJson:off body: JSON.stringify(payload), contentType: "application/json", }, @@ -1875,6 +1882,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { cookie: yield* getAuthenticatedSessionCookieHeader(), "content-type": "application/json", }, + // @effect-diagnostics-next-line preferSchemaOverJson:off body: HttpBody.text(JSON.stringify(payload), "application/json"), }); @@ -2182,7 +2190,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { version: 1 as const, sequence: 2, type: "ready" as const, - payload: { at: new Date().toISOString(), environment: testEnvironmentDescriptor }, + payload: { at: "2026-01-01T00:00:00.000Z", environment: testEnvironmentDescriptor }, }); yield* buildAppUnderTest({ @@ -2370,7 +2378,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex", }, - createdAt: new Date().toISOString(), + createdAt: "2026-01-01T00:00:00.000Z", }), ), ); @@ -2913,11 +2921,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }); const wsUrl = yield* getWsServerUrl("/ws"); - const startedAt = Date.now(); + const startedAt = yield* Clock.currentTimeMillis; const result = yield* Effect.scoped( withWsRpcClient(wsUrl, (client) => client[WS_METHODS.vcsPull]({ cwd: "/tmp/repo" })), ); - const elapsedMs = Date.now() - startedAt; + const elapsedMs = (yield* Clock.currentTimeMillis) - startedAt; assert.equal(result.status, "pulled"); assertTrue(elapsedMs < 1_000); @@ -2982,7 +2990,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }); const wsUrl = yield* getWsServerUrl("/ws"); - const startedAt = Date.now(); + const startedAt = yield* Clock.currentTimeMillis; yield* Effect.scoped( withWsRpcClient(wsUrl, (client) => client[WS_METHODS.gitRunStackedAction]({ @@ -2992,7 +3000,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Stream.runCollect), ), ); - const elapsedMs = Date.now() - startedAt; + const elapsedMs = (yield* Clock.currentTimeMillis) - startedAt; assertTrue(elapsedMs < 1_000); }).pipe(Effect.provide(NodeHttpServer.layerTest)), @@ -3079,7 +3087,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { it.effect("routes websocket rpc orchestration methods", () => Effect.gen(function* () { - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; const snapshot = { snapshotSequence: 1, updatedAt: now, @@ -3292,7 +3300,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { const threadId = ThreadId.make("thread-archive"); const effects: string[] = []; const dispatchedCommands: Array = []; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* buildAppUnderTest({ layers: { @@ -3363,7 +3371,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { const threadId = ThreadId.make("thread-archive-precheck"); const effects: string[] = []; const dispatchedCommands: Array = []; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; let archived = false; yield* buildAppUnderTest({ @@ -3494,7 +3502,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { const threadId = ThreadId.make("thread-archive-stopped-session"); const effects: string[] = []; const dispatchedCommands: Array = []; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* buildAppUnderTest({ layers: { @@ -3560,7 +3568,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { const threadId = ThreadId.make("thread-archive-stop-failure"); const effects: string[] = []; const dispatchedCommands: Array = []; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* buildAppUnderTest({ layers: { @@ -3637,7 +3645,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { const threadId = ThreadId.make("thread-archive-stop-defect"); const effects: string[] = []; const dispatchedCommands: Array = []; - const now = new Date().toISOString(); + const now = "2026-01-01T00:00:00.000Z"; yield* buildAppUnderTest({ layers: { @@ -3769,7 +3777,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; const wsUrl = yield* getWsServerUrl("/ws"); const response = yield* Effect.scoped( withWsRpcClient(wsUrl, (client) => @@ -3864,7 +3872,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { ); const runForThread = vi.fn( (_: Parameters[0]) => - Effect.fail(new Error("pty unavailable")), + Effect.fail(new ProjectSetupScriptRunnerError({ message: "pty unavailable" })), ); yield* buildAppUnderTest({ @@ -3886,7 +3894,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; const wsUrl = yield* getWsServerUrl("/ws"); const response = yield* Effect.scoped( withWsRpcClient(wsUrl, (client) => @@ -4003,7 +4011,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; const wsUrl = yield* getWsServerUrl("/ws"); const response = yield* Effect.scoped( withWsRpcClient(wsUrl, (client) => @@ -4087,7 +4095,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, }); - const createdAt = new Date().toISOString(); + const createdAt = "2026-01-01T00:00:00.000Z"; const wsUrl = yield* getWsServerUrl("/ws"); const result = yield* Effect.scoped( withWsRpcClient(wsUrl, (client) => @@ -4149,7 +4157,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { history: "", exitCode: null, exitSignal: null, - updatedAt: new Date().toISOString(), + updatedAt: "2026-01-01T00:00:00.000Z", }; yield* buildAppUnderTest({ diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 980aa82268b..02ad134e8df 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -1,4 +1,5 @@ -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; import { ServerConfig } from "./config.ts"; @@ -333,7 +334,7 @@ export const makeServerLayer = Layer.unwrap( return; } - const state = makePersistedServerRuntimeState({ + const state = yield* makePersistedServerRuntimeState({ config, port: address.port, }); @@ -418,8 +419,4 @@ export const makeServerLayer = Layer.unwrap( ); // Important: Only `ServerConfig` should be provided by the CLI layer!!! Don't let other requirements leak into the launch layer. -export const runServer = Layer.launch(makeServerLayer) satisfies Effect.Effect< - never, - any, - ServerConfig ->; +export const runServer = Layer.launch(makeServerLayer); diff --git a/apps/server/src/serverLifecycleEvents.test.ts b/apps/server/src/serverLifecycleEvents.test.ts index 47e24c1b87c..14fbba9e238 100644 --- a/apps/server/src/serverLifecycleEvents.test.ts +++ b/apps/server/src/serverLifecycleEvents.test.ts @@ -1,7 +1,8 @@ import { EnvironmentId } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; import { assertTrue } from "@effect/vitest/utils"; -import { Effect, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; import { ServerLifecycleEvents, ServerLifecycleEventsLive } from "./serverLifecycleEvents.ts"; @@ -37,7 +38,7 @@ it.effect( version: 1, type: "ready", payload: { - at: new Date().toISOString(), + at: "2026-01-01T00:00:00.000Z", environment, }, }) diff --git a/apps/server/src/serverLifecycleEvents.ts b/apps/server/src/serverLifecycleEvents.ts index 145d1cbaa4e..88661b1593a 100644 --- a/apps/server/src/serverLifecycleEvents.ts +++ b/apps/server/src/serverLifecycleEvents.ts @@ -1,5 +1,10 @@ import type { ServerLifecycleStreamEvent } from "@t3tools/contracts"; -import { Effect, Layer, PubSub, Ref, Context, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Context from "effect/Context"; +import * as Stream from "effect/Stream"; type LifecycleEventInput = | Omit, "sequence"> diff --git a/apps/server/src/serverLogger.ts b/apps/server/src/serverLogger.ts index 57d51b2a9e8..a7cb1d6a26e 100644 --- a/apps/server/src/serverLogger.ts +++ b/apps/server/src/serverLogger.ts @@ -1,4 +1,7 @@ -import { Effect, Logger, References, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Logger from "effect/Logger"; +import * as References from "effect/References"; +import * as Layer from "effect/Layer"; import { ServerConfig } from "./config.ts"; diff --git a/apps/server/src/serverRuntimeStartup.test.ts b/apps/server/src/serverRuntimeStartup.test.ts index 7f13693289c..b47c7d0af6a 100644 --- a/apps/server/src/serverRuntimeStartup.test.ts +++ b/apps/server/src/serverRuntimeStartup.test.ts @@ -1,7 +1,12 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { DEFAULT_MODEL, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; -import { Deferred, Effect, Fiber, Option, Ref, Stream } from "effect"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as Option from "effect/Option"; +import * as Ref from "effect/Ref"; +import * as Stream from "effect/Stream"; import { ServerConfig } from "./config.ts"; import { diff --git a/apps/server/src/serverRuntimeStartup.ts b/apps/server/src/serverRuntimeStartup.ts index 1f164860a6f..59fde342280 100644 --- a/apps/server/src/serverRuntimeStartup.ts +++ b/apps/server/src/serverRuntimeStartup.ts @@ -7,20 +7,19 @@ import { ProviderInstanceId, ThreadId, } from "@t3tools/contracts"; -import { - Data, - Deferred, - Effect, - Exit, - Layer, - Option, - Path, - Queue, - Ref, - Scope, - Context, - Console, -} from "effect"; +import * as Data from "effect/Data"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Queue from "effect/Queue"; +import * as Ref from "effect/Ref"; +import * as Scope from "effect/Scope"; +import * as Context from "effect/Context"; +import * as Console from "effect/Console"; +import * as DateTime from "effect/DateTime"; import { ServerConfig } from "./config.ts"; import { Keybindings } from "./keybindings.ts"; @@ -188,7 +187,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { let nextProjectDefaultModelSelection: ModelSelection; if (Option.isNone(existingProject)) { - const createdAt = new Date().toISOString(); + const createdAt = DateTime.formatIso(yield* DateTime.now); nextProjectId = ProjectId.make(crypto.randomUUID()); const bootstrapProjectTitle = path.basename(serverConfig.cwd) || "project"; nextProjectDefaultModelSelection = getAutoBootstrapDefaultModelSelection(); @@ -210,7 +209,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { const existingThreadId = yield* projectionReadModelQuery.getFirstActiveThreadIdByProjectId(nextProjectId); if (Option.isNone(existingThreadId)) { - const createdAt = new Date().toISOString(); + const createdAt = DateTime.formatIso(yield* DateTime.now); const createdThreadId = ThreadId.make(crypto.randomUUID()); yield* orchestrationEngine.dispatch({ type: "thread.create", @@ -423,7 +422,7 @@ export const makeServerRuntimeStartup = Effect.gen(function* () { version: 1, type: "ready", payload: { - at: new Date().toISOString(), + at: DateTime.formatIso(yield* DateTime.now), environment: yield* serverEnvironment.getDescriptor, }, }), diff --git a/apps/server/src/serverRuntimeState.ts b/apps/server/src/serverRuntimeState.ts index 4b300f29c2c..996f9a2bfc9 100644 --- a/apps/server/src/serverRuntimeState.ts +++ b/apps/server/src/serverRuntimeState.ts @@ -1,4 +1,8 @@ -import { Effect, FileSystem, Option, Schema } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { writeFileStringAtomically } from "./atomicWrite.ts"; import { type ServerConfigShape } from "./config.ts"; @@ -30,14 +34,15 @@ const runtimeOriginForConfig = ( export const makePersistedServerRuntimeState = (input: { readonly config: Pick; readonly port: number; -}): PersistedServerRuntimeState => ({ - version: 1, - pid: process.pid, - ...(input.config.host ? { host: input.config.host } : {}), - port: input.port, - origin: runtimeOriginForConfig(input.config, input.port), - startedAt: new Date().toISOString(), -}); +}): Effect.Effect => + Effect.map(DateTime.now, (now) => ({ + version: 1, + pid: process.pid, + ...(input.config.host ? { host: input.config.host } : {}), + port: input.port, + origin: runtimeOriginForConfig(input.config, input.port), + startedAt: DateTime.formatIso(now), + })); export const persistServerRuntimeState = (input: { readonly path: string; diff --git a/apps/server/src/serverSettings.test.ts b/apps/server/src/serverSettings.test.ts index f11c5bf4519..93243b21c60 100644 --- a/apps/server/src/serverSettings.test.ts +++ b/apps/server/src/serverSettings.test.ts @@ -8,7 +8,10 @@ import { } from "@t3tools/contracts"; import { createModelSelection } from "@t3tools/shared/model"; import { assert, it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; import { ServerConfig } from "./config.ts"; import { ServerSettingsLive, ServerSettingsService } from "./serverSettings.ts"; @@ -439,6 +442,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { assert.equal(next.providers.codex.binaryPath, "/opt/homebrew/bin/codex"); const raw = yield* fileSystem.readFileString(serverConfig.settingsPath); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepEqual(JSON.parse(raw), { addProjectBaseDirectory: "~/Development", observability: { @@ -490,6 +494,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { const raw = yield* fileSystem.readFileString(serverConfig.settingsPath); assert.notInclude(raw, "sk-or-secret"); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepEqual(JSON.parse(raw).providerInstances.codex_personal.environment, [ { name: "OPENROUTER_API_KEY", diff --git a/apps/server/src/serverSettings.ts b/apps/server/src/serverSettings.ts index 2b9d8a52951..8ef90972798 100644 --- a/apps/server/src/serverSettings.ts +++ b/apps/server/src/serverSettings.ts @@ -24,25 +24,23 @@ import { ServerSettingsError, type ServerSettingsPatch, } from "@t3tools/contracts"; -import { - Cache, - Deferred, - Duration, - Effect, - Exit, - FileSystem, - Layer, - Path, - Equal, - PubSub, - Ref, - Schema, - SchemaIssue, - Scope, - Context, - Stream, - Cause, -} from "effect"; +import * as Cache from "effect/Cache"; +import * as Deferred from "effect/Deferred"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Equal from "effect/Equal"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as SchemaIssue from "effect/SchemaIssue"; +import * as Scope from "effect/Scope"; +import * as Context from "effect/Context"; +import * as Stream from "effect/Stream"; +import * as Cause from "effect/Cause"; import * as Semaphore from "effect/Semaphore"; import { writeFileStringAtomically } from "./atomicWrite.ts"; import { ServerConfig } from "./config.ts"; diff --git a/apps/server/src/sourceControl/AzureDevOpsCli.test.ts b/apps/server/src/sourceControl/AzureDevOpsCli.test.ts index 406c9772f34..6958f42fb13 100644 --- a/apps/server/src/sourceControl/AzureDevOpsCli.test.ts +++ b/apps/server/src/sourceControl/AzureDevOpsCli.test.ts @@ -1,8 +1,10 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it, afterEach, describe, expect, vi } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ChildProcessSpawner } from "effect/unstable/process"; -import type { VcsError } from "@t3tools/contracts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; @@ -35,6 +37,7 @@ describe("AzureDevOpsCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ pullRequestId: 42, title: "Add Azure provider", @@ -91,6 +94,7 @@ describe("AzureDevOpsCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { pullRequestId: 7, @@ -149,6 +153,7 @@ describe("AzureDevOpsCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ name: "repo", webUrl: "https://dev.azure.com/acme/project/_git/repo", @@ -181,6 +186,7 @@ describe("AzureDevOpsCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ name: "repo", webUrl: "https://dev.azure.com/acme/project/_git/repo", @@ -231,7 +237,7 @@ describe("AzureDevOpsCli.layer", () => { it.effect("creates pull requests using the body file as the Azure description", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; - const bodyFile = `/tmp/t3code-azure-devops-cli-${Date.now()}.md`; + const bodyFile = `/tmp/t3code-azure-devops-cli-.md`; yield* fileSystem.writeFileString(bodyFile, "Generated body"); mockRun.mockReturnValueOnce(Effect.succeed(processOutput("{}"))); diff --git a/apps/server/src/sourceControl/AzureDevOpsCli.ts b/apps/server/src/sourceControl/AzureDevOpsCli.ts index 375bfc1a54b..d4a4d69267b 100644 --- a/apps/server/src/sourceControl/AzureDevOpsCli.ts +++ b/apps/server/src/sourceControl/AzureDevOpsCli.ts @@ -1,4 +1,9 @@ -import { Context, Effect, Layer, Result, Schema, SchemaIssue } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; +import * as SchemaIssue from "effect/SchemaIssue"; import { TrimmedNonEmptyString, type SourceControlRepositoryVisibility, diff --git a/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.test.ts b/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.test.ts index c46bc0ee7fc..4ba3777159b 100644 --- a/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.test.ts +++ b/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.test.ts @@ -1,5 +1,7 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; import * as AzureDevOpsSourceControlProvider from "./AzureDevOpsSourceControlProvider.ts"; diff --git a/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts b/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts index cd4162a934e..8d8e081cb89 100644 --- a/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts +++ b/apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts @@ -1,4 +1,5 @@ -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; @@ -30,7 +31,7 @@ function parseAzureAuth(input: SourceControlProviderDiscovery.SourceControlAuthP }); } - if (account && account.length > 0) { + if (account !== undefined && account.length > 0) { return SourceControlProviderDiscovery.providerAuth({ status: "authenticated", account, @@ -90,7 +91,7 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* .listPullRequests({ cwd: input.cwd, headSelector: input.headSelector, - ...(source ? { source } : {}), + ...(source !== undefined ? { source } : {}), state: input.state, ...(input.limit !== undefined ? { limit: input.limit } : {}), }) @@ -111,8 +112,8 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* cwd: input.cwd, baseBranch: input.baseRefName, headSelector: input.headSelector, - ...(source ? { source } : {}), - ...(input.target ? { target: input.target } : {}), + ...(source !== undefined ? { source } : {}), + ...(input.target !== undefined ? { target: input.target } : {}), title: input.title, bodyFile: input.bodyFile, }) @@ -135,7 +136,7 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* .checkoutPullRequest({ cwd: input.cwd, reference: input.reference, - ...(input.context ? { remoteName: input.context.remoteName } : {}), + ...(input.context !== undefined ? { remoteName: input.context.remoteName } : {}), }) .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))), }); diff --git a/apps/server/src/sourceControl/BitbucketApi.test.ts b/apps/server/src/sourceControl/BitbucketApi.test.ts index efc7ec5fb9b..e93362b8423 100644 --- a/apps/server/src/sourceControl/BitbucketApi.test.ts +++ b/apps/server/src/sourceControl/BitbucketApi.test.ts @@ -1,6 +1,11 @@ import { assert, it, vi } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { ConfigProvider, DateTime, Effect, FileSystem, Layer, Option } from "effect"; +import * as ConfigProvider from "effect/ConfigProvider"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; import * as BitbucketApi from "./BitbucketApi.ts"; @@ -423,6 +428,7 @@ it.effect("creates repositories through the Bitbucket REST API", () => { assert.ok(request); const rawBody = (request.body as { readonly body?: Uint8Array }).body; assert.ok(rawBody); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(new TextDecoder().decode(rawBody)), { scm: "git", is_private: true, @@ -458,6 +464,7 @@ it.effect("creates pull requests using the official REST payload shape", () => { assert.ok(request); const rawBody = (request.body as { readonly body?: Uint8Array }).body; assert.ok(rawBody); + // @effect-diagnostics-next-line preferSchemaOverJson:off assert.deepStrictEqual(JSON.parse(new TextDecoder().decode(rawBody)), { title: "Provider PR", description: "PR body", diff --git a/apps/server/src/sourceControl/BitbucketApi.ts b/apps/server/src/sourceControl/BitbucketApi.ts index b95d5190039..165e32df356 100644 --- a/apps/server/src/sourceControl/BitbucketApi.ts +++ b/apps/server/src/sourceControl/BitbucketApi.ts @@ -1,4 +1,10 @@ -import { Config, Context, Effect, FileSystem, Layer, Option, Schema } from "effect"; +import * as Config from "effect/Config"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { TrimmedNonEmptyString, type SourceControlProviderAuth, diff --git a/apps/server/src/sourceControl/BitbucketSourceControlProvider.test.ts b/apps/server/src/sourceControl/BitbucketSourceControlProvider.test.ts index 4bf658f5685..07a3d386a35 100644 --- a/apps/server/src/sourceControl/BitbucketSourceControlProvider.test.ts +++ b/apps/server/src/sourceControl/BitbucketSourceControlProvider.test.ts @@ -1,5 +1,7 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as BitbucketApi from "./BitbucketApi.ts"; import * as BitbucketSourceControlProvider from "./BitbucketSourceControlProvider.ts"; diff --git a/apps/server/src/sourceControl/BitbucketSourceControlProvider.ts b/apps/server/src/sourceControl/BitbucketSourceControlProvider.ts index ede80e921dc..f3fd502f7fb 100644 --- a/apps/server/src/sourceControl/BitbucketSourceControlProvider.ts +++ b/apps/server/src/sourceControl/BitbucketSourceControlProvider.ts @@ -1,4 +1,6 @@ -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; import * as BitbucketApi from "./BitbucketApi.ts"; diff --git a/apps/server/src/sourceControl/GitHubCli.test.ts b/apps/server/src/sourceControl/GitHubCli.test.ts index 778e0c4962e..fb765b352c2 100644 --- a/apps/server/src/sourceControl/GitHubCli.test.ts +++ b/apps/server/src/sourceControl/GitHubCli.test.ts @@ -1,7 +1,8 @@ import { assert, it, afterEach, describe, expect, vi } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { ChildProcessSpawner } from "effect/unstable/process"; -import { VcsProcessExitError, type VcsError } from "@t3tools/contracts"; +import { VcsProcessExitError } from "@t3tools/contracts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as GitHubCli from "./GitHubCli.ts"; @@ -34,6 +35,7 @@ describe("GitHubCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ number: 42, title: "Add PR thread creation", @@ -92,6 +94,7 @@ describe("GitHubCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ number: 42, title: " Add PR thread creation \n", @@ -137,6 +140,7 @@ describe("GitHubCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { number: 0, @@ -187,6 +191,7 @@ describe("GitHubCli.layer", () => { mockRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ nameWithOwner: "octocat/codething-mvp", url: "https://github.com/octocat/codething-mvp", diff --git a/apps/server/src/sourceControl/GitHubCli.ts b/apps/server/src/sourceControl/GitHubCli.ts index fe83d41ef43..14d01aab2ed 100644 --- a/apps/server/src/sourceControl/GitHubCli.ts +++ b/apps/server/src/sourceControl/GitHubCli.ts @@ -1,4 +1,9 @@ -import { Context, Effect, Layer, Result, Schema, SchemaIssue } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; +import * as SchemaIssue from "effect/SchemaIssue"; import { TrimmedNonEmptyString, diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts index 3c4ad5ac473..a480f3bec86 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts @@ -1,5 +1,8 @@ import { assert, it } from "@effect/vitest"; -import { DateTime, Effect, Layer, Option } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as VcsProcess from "../vcs/VcsProcess.ts"; diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts index 7dba4893697..a7128e1c5fd 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts @@ -1,4 +1,8 @@ -import { Effect, Layer, Option, Result, Schema } from "effect"; +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 { SourceControlProviderError, type ChangeRequest, diff --git a/apps/server/src/sourceControl/GitLabCli.test.ts b/apps/server/src/sourceControl/GitLabCli.test.ts index 5c7e978d857..c075027151a 100644 --- a/apps/server/src/sourceControl/GitLabCli.test.ts +++ b/apps/server/src/sourceControl/GitLabCli.test.ts @@ -1,5 +1,6 @@ import { assert, it, afterEach, expect, vi } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { ChildProcessSpawner } from "effect/unstable/process"; import { VcsProcessExitError } from "@t3tools/contracts"; @@ -38,6 +39,7 @@ layer("GitLabCli.layer", (it) => { mockedRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ iid: 42, title: "Add MR thread creation", @@ -89,6 +91,7 @@ layer("GitLabCli.layer", (it) => { mockedRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify([ { iid: 0, @@ -154,6 +157,7 @@ layer("GitLabCli.layer", (it) => { mockedRun.mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ path_with_namespace: "octocat/t3code", web_url: "https://gitlab.com/octocat/t3code", @@ -219,10 +223,19 @@ layer("GitLabCli.layer", (it) => { it.effect("creates repositories under an explicit namespace", () => Effect.gen(function* () { mockedRun - .mockReturnValueOnce(Effect.succeed(processOutput(JSON.stringify({ id: 1234 })))) + + .mockReturnValueOnce( + Effect.succeed( + processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off + JSON.stringify({ id: 1234 }), + ), + ), + ) .mockReturnValueOnce( Effect.succeed( processOutput( + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ path_with_namespace: "octocat/t3code", web_url: "https://gitlab.com/octocat/t3code", diff --git a/apps/server/src/sourceControl/GitLabCli.ts b/apps/server/src/sourceControl/GitLabCli.ts index c4485bb09b7..faabe87263d 100644 --- a/apps/server/src/sourceControl/GitLabCli.ts +++ b/apps/server/src/sourceControl/GitLabCli.ts @@ -1,4 +1,11 @@ -import { Context, Effect, Layer, Option, Result, Schema, SchemaIssue, type DateTime } from "effect"; +import * as Context from "effect/Context"; +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 SchemaIssue from "effect/SchemaIssue"; +import type * as DateTime from "effect/DateTime"; import { TrimmedNonEmptyString, type SourceControlRepositoryVisibility } from "@t3tools/contracts"; diff --git a/apps/server/src/sourceControl/GitLabSourceControlProvider.test.ts b/apps/server/src/sourceControl/GitLabSourceControlProvider.test.ts index 930c1c018f5..94d363d1c1b 100644 --- a/apps/server/src/sourceControl/GitLabSourceControlProvider.test.ts +++ b/apps/server/src/sourceControl/GitLabSourceControlProvider.test.ts @@ -1,5 +1,7 @@ import { assert, it } from "@effect/vitest"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as GitLabCli from "./GitLabCli.ts"; import * as GitLabSourceControlProvider from "./GitLabSourceControlProvider.ts"; diff --git a/apps/server/src/sourceControl/GitLabSourceControlProvider.ts b/apps/server/src/sourceControl/GitLabSourceControlProvider.ts index 5b0538babd8..ccab2bd1f76 100644 --- a/apps/server/src/sourceControl/GitLabSourceControlProvider.ts +++ b/apps/server/src/sourceControl/GitLabSourceControlProvider.ts @@ -1,4 +1,6 @@ -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; import * as GitLabCli from "./GitLabCli.ts"; diff --git a/apps/server/src/sourceControl/SourceControlDiscovery.test.ts b/apps/server/src/sourceControl/SourceControlDiscovery.test.ts index ce41265d336..d1c6c65c752 100644 --- a/apps/server/src/sourceControl/SourceControlDiscovery.test.ts +++ b/apps/server/src/sourceControl/SourceControlDiscovery.test.ts @@ -1,6 +1,8 @@ import { assert, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Effect, Layer, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ChildProcessSpawner } from "effect/unstable/process"; import { VcsProcessSpawnError } from "@t3tools/contracts"; diff --git a/apps/server/src/sourceControl/SourceControlDiscovery.ts b/apps/server/src/sourceControl/SourceControlDiscovery.ts index 4a44d35087a..49398396492 100644 --- a/apps/server/src/sourceControl/SourceControlDiscovery.ts +++ b/apps/server/src/sourceControl/SourceControlDiscovery.ts @@ -3,7 +3,10 @@ import { type VcsDiscoveryItem, type VcsDriverKind, } from "@t3tools/contracts"; -import { Context, Effect, Layer, Option } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ServerConfig } from "../config.ts"; import * as VcsProcess from "../vcs/VcsProcess.ts"; diff --git a/apps/server/src/sourceControl/SourceControlProvider.ts b/apps/server/src/sourceControl/SourceControlProvider.ts index 12f89caf77e..a0465008212 100644 --- a/apps/server/src/sourceControl/SourceControlProvider.ts +++ b/apps/server/src/sourceControl/SourceControlProvider.ts @@ -1,4 +1,5 @@ -import { Context, Effect } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; import type { ChangeRequest, ChangeRequestState, diff --git a/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts b/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts index 87c0c4756a4..425c4f61778 100644 --- a/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts +++ b/apps/server/src/sourceControl/SourceControlProviderDiscovery.ts @@ -3,7 +3,8 @@ import type { SourceControlProviderDiscoveryItem, SourceControlProviderKind, } from "@t3tools/contracts"; -import { Effect, Option } from "effect"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; import type * as VcsProcess from "../vcs/VcsProcess.ts"; diff --git a/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts b/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts index 395bd9b5e87..829f6be1eb9 100644 --- a/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts +++ b/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts @@ -1,6 +1,9 @@ import { assert, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { DateTime, Effect, Layer, Option } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ServerConfig } from "../config.ts"; import type * as VcsDriver from "../vcs/VcsDriver.ts"; diff --git a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts index c8b79f21651..28826e764b0 100644 --- a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts +++ b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts @@ -1,4 +1,9 @@ -import { Cache, Context, Duration, Effect, Exit, Layer } from "effect"; +import * as Cache from "effect/Cache"; +import * as Context from "effect/Context"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; import { SourceControlProviderError, type SourceControlProviderDiscoveryItem, diff --git a/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts b/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts index 5280ee0e59c..811b55c70a3 100644 --- a/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts +++ b/apps/server/src/sourceControl/SourceControlRepositoryService.test.ts @@ -1,6 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; -import { Effect, FileSystem, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; import { ChildProcessSpawner } from "effect/unstable/process"; import { GitCommandError, type SourceControlProviderError } from "@t3tools/contracts"; diff --git a/apps/server/src/sourceControl/SourceControlRepositoryService.ts b/apps/server/src/sourceControl/SourceControlRepositoryService.ts index e7d488e317f..742240fa550 100644 --- a/apps/server/src/sourceControl/SourceControlRepositoryService.ts +++ b/apps/server/src/sourceControl/SourceControlRepositoryService.ts @@ -1,5 +1,10 @@ import * as NodeOS from "node:os"; -import { Context, Effect, FileSystem, Layer, Path, Schema } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { SourceControlRepositoryError, diff --git a/apps/server/src/sourceControl/azureDevOpsPullRequests.ts b/apps/server/src/sourceControl/azureDevOpsPullRequests.ts index 48c7a836110..e8f138e0e32 100644 --- a/apps/server/src/sourceControl/azureDevOpsPullRequests.ts +++ b/apps/server/src/sourceControl/azureDevOpsPullRequests.ts @@ -1,4 +1,9 @@ -import { Cause, DateTime, Exit, Option, Result, Schema } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Exit from "effect/Exit"; +import * as Option from "effect/Option"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; diff --git a/apps/server/src/sourceControl/bitbucketPullRequests.ts b/apps/server/src/sourceControl/bitbucketPullRequests.ts index 5313eaba974..6d67477bca7 100644 --- a/apps/server/src/sourceControl/bitbucketPullRequests.ts +++ b/apps/server/src/sourceControl/bitbucketPullRequests.ts @@ -1,4 +1,6 @@ -import { DateTime, Option, Schema } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; export interface NormalizedBitbucketPullRequestRecord { diff --git a/apps/server/src/sourceControl/gitHubPullRequests.ts b/apps/server/src/sourceControl/gitHubPullRequests.ts index f0804dda8c6..d9dcb7f9ad1 100644 --- a/apps/server/src/sourceControl/gitHubPullRequests.ts +++ b/apps/server/src/sourceControl/gitHubPullRequests.ts @@ -1,4 +1,9 @@ -import { Cause, DateTime, Exit, Option, Result, Schema } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Exit from "effect/Exit"; +import * as Option from "effect/Option"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; diff --git a/apps/server/src/sourceControl/gitLabMergeRequests.ts b/apps/server/src/sourceControl/gitLabMergeRequests.ts index d8245d3249a..afd1eceaeed 100644 --- a/apps/server/src/sourceControl/gitLabMergeRequests.ts +++ b/apps/server/src/sourceControl/gitLabMergeRequests.ts @@ -1,4 +1,9 @@ -import { Cause, DateTime, Exit, Option, Result, Schema } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Exit from "effect/Exit"; +import * as Option from "effect/Option"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; diff --git a/apps/server/src/startupAccess.ts b/apps/server/src/startupAccess.ts index 32791901418..d3b6898d75b 100644 --- a/apps/server/src/startupAccess.ts +++ b/apps/server/src/startupAccess.ts @@ -1,7 +1,7 @@ import { networkInterfaces } from "node:os"; import { QrCode } from "@t3tools/shared/qrCode"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { HttpServer } from "effect/unstable/http"; import { ServerConfig } from "./config.ts"; diff --git a/apps/server/src/stream/collectUint8StreamText.test.ts b/apps/server/src/stream/collectUint8StreamText.test.ts index 727d14dd09a..d6715294cce 100644 --- a/apps/server/src/stream/collectUint8StreamText.test.ts +++ b/apps/server/src/stream/collectUint8StreamText.test.ts @@ -1,5 +1,6 @@ import { assert, describe, it } from "@effect/vitest"; -import { Effect, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Stream from "effect/Stream"; import { collectUint8StreamText } from "./collectUint8StreamText.ts"; diff --git a/apps/server/src/stream/collectUint8StreamText.ts b/apps/server/src/stream/collectUint8StreamText.ts index 88f654908d9..3fa6dc224fe 100644 --- a/apps/server/src/stream/collectUint8StreamText.ts +++ b/apps/server/src/stream/collectUint8StreamText.ts @@ -1,4 +1,5 @@ -import { Effect, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Stream from "effect/Stream"; export interface CollectedUint8StreamText { readonly text: string; diff --git a/apps/server/src/telemetry/Identify.ts b/apps/server/src/telemetry/Identify.ts index e81393bbbc3..ed8e98a87a4 100644 --- a/apps/server/src/telemetry/Identify.ts +++ b/apps/server/src/telemetry/Identify.ts @@ -1,4 +1,8 @@ -import { Effect, FileSystem, Path, Random, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Random from "effect/Random"; +import * as Schema from "effect/Schema"; import * as Crypto from "node:crypto"; import { homedir } from "node:os"; import { ServerConfig } from "../config.ts"; diff --git a/apps/server/src/telemetry/Layers/AnalyticsService.test.ts b/apps/server/src/telemetry/Layers/AnalyticsService.test.ts index 5fe0795ce48..03ebd2fdc65 100644 --- a/apps/server/src/telemetry/Layers/AnalyticsService.test.ts +++ b/apps/server/src/telemetry/Layers/AnalyticsService.test.ts @@ -1,7 +1,9 @@ import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; -import { ConfigProvider, Effect, Layer } from "effect"; +import * as ConfigProvider from "effect/ConfigProvider"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import * as HttpServer from "effect/unstable/http/HttpServer"; import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; diff --git a/apps/server/src/telemetry/Layers/AnalyticsService.ts b/apps/server/src/telemetry/Layers/AnalyticsService.ts index 9067b71a552..ec859fb18f5 100644 --- a/apps/server/src/telemetry/Layers/AnalyticsService.ts +++ b/apps/server/src/telemetry/Layers/AnalyticsService.ts @@ -7,7 +7,11 @@ * @module AnalyticsServiceLive */ -import { Config, DateTime, Effect, Layer, Ref } from "effect"; +import * as Config from "effect/Config"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Ref from "effect/Ref"; import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/telemetry/Services/AnalyticsService.ts b/apps/server/src/telemetry/Services/AnalyticsService.ts index 0e703573d46..a2717c790dc 100644 --- a/apps/server/src/telemetry/Services/AnalyticsService.ts +++ b/apps/server/src/telemetry/Services/AnalyticsService.ts @@ -6,7 +6,9 @@ * * @module AnalyticsService */ -import { Effect, Layer, Context } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Context from "effect/Context"; export interface AnalyticsServiceShape { /** diff --git a/apps/server/src/terminal/Layers/BunPTY.ts b/apps/server/src/terminal/Layers/BunPTY.ts index f0aab813c7c..40113a1915c 100644 --- a/apps/server/src/terminal/Layers/BunPTY.ts +++ b/apps/server/src/terminal/Layers/BunPTY.ts @@ -1,4 +1,5 @@ -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { PtyAdapter } from "../Services/PTY.ts"; import type { PtyAdapterShape, PtyExitEvent, PtyProcess } from "../Services/PTY.ts"; diff --git a/apps/server/src/terminal/Layers/Manager.test.ts b/apps/server/src/terminal/Layers/Manager.test.ts index 7c4f429e40d..0f3c2913800 100644 --- a/apps/server/src/terminal/Layers/Manager.test.ts +++ b/apps/server/src/terminal/Layers/Manager.test.ts @@ -1,5 +1,3 @@ -import path from "node:path"; - import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { @@ -8,19 +6,18 @@ import { type TerminalOpenInput, type TerminalRestartInput, } from "@t3tools/contracts"; -import { - Duration, - Effect, - Encoding, - Exit, - Fiber, - FileSystem, - Option, - PlatformError, - Ref, - Schedule, - Scope, -} from "effect"; +import * as Cause from "effect/Cause"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Encoding from "effect/Encoding"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as PlatformError from "effect/PlatformError"; +import * as Ref from "effect/Ref"; +import * as Schedule from "effect/Schedule"; +import * as Scope from "effect/Scope"; import { TestClock } from "effect/testing"; import { expect } from "vitest"; @@ -130,20 +127,11 @@ class FakePtyAdapter implements PtyAdapterShape { const waitFor = ( predicate: Effect.Effect, timeout: Duration.Input = 800, -): Effect.Effect => +): Effect.Effect => predicate.pipe( - Effect.filterOrFail( - (done) => done, - () => new Error("Condition not met"), - ), + Effect.filter((done) => done), Effect.retry(Schedule.spaced("15 millis")), - Effect.timeoutOption(timeout), - Effect.flatMap((result) => - Option.match(result, { - onNone: () => Effect.fail(new Error("Timed out waiting for condition")), - onSome: () => Effect.void, - }), - ), + Effect.timeout(timeout), ); function openInput(overrides: Partial = {}): TerminalOpenInput { @@ -181,7 +169,7 @@ function multiTerminalHistoryLogName(threadId: string, terminalId: string): stri } function historyLogPath(logsDir: string, threadId = "thread-1"): string { - return path.join(logsDir, historyLogName(threadId)); + return joinPath(logsDir, historyLogName(threadId)); } function multiTerminalHistoryLogPath( @@ -189,7 +177,11 @@ function multiTerminalHistoryLogPath( threadId = "thread-1", terminalId = "default", ): string { - return path.join(logsDir, multiTerminalHistoryLogName(threadId, terminalId)); + return joinPath(logsDir, multiTerminalHistoryLogName(threadId, terminalId)); +} + +function joinPath(...segments: ReadonlyArray): string { + return segments.join("/"); } interface CreateManagerOptions { @@ -217,12 +209,13 @@ const createManager = ( ): Effect.Effect< ManagerFixture, PlatformError.PlatformError, - FileSystem.FileSystem | Scope.Scope + FileSystem.FileSystem | Path.Path | Scope.Scope > => Effect.flatMap(Effect.service(FileSystem.FileSystem), (fs) => Effect.gen(function* () { + const pathService = yield* Path.Path; const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-terminal-" }); - const logsDir = path.join(baseDir, "userdata", "logs", "terminals"); + const logsDir = pathService.join(baseDir, "userdata", "logs", "terminals"); const ptyAdapter = options.ptyAdapter ?? new FakePtyAdapter(); const manager = yield* makeTerminalManagerWithOptions({ @@ -302,8 +295,8 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( if (process.platform === "win32") return; const { manager, baseDir } = yield* createManager(); - const blockedRoot = path.join(baseDir, "blocked-root"); - const blockedCwd = path.join(blockedRoot, "cwd"); + const blockedRoot = joinPath(baseDir, "blocked-root"); + const blockedCwd = joinPath(blockedRoot, "cwd"); yield* makeDirectory(blockedCwd); yield* chmod(blockedRoot, 0o000); @@ -441,8 +434,8 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( it.effect("propagates explicit worktree metadata through snapshots and lifecycle events", () => Effect.gen(function* () { const { manager, getEvents, baseDir } = yield* createManager(); - const firstWorktreePath = path.join(baseDir, "worktrees", "feature-a"); - const secondWorktreePath = path.join(baseDir, "worktrees", "feature-b"); + const firstWorktreePath = joinPath(baseDir, "worktrees", "feature-a"); + const secondWorktreePath = joinPath(baseDir, "worktrees", "feature-b"); yield* makeDirectory(firstWorktreePath); yield* makeDirectory(secondWorktreePath); const startedSnapshot = yield* manager.open( @@ -478,7 +471,7 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( it.effect("preserves worktree metadata when reopening an exited session", () => Effect.gen(function* () { const { manager, ptyAdapter, getEvents, baseDir } = yield* createManager(); - const worktreePath = path.join(baseDir, "worktrees", "feature-a"); + const worktreePath = joinPath(baseDir, "worktrees", "feature-a"); yield* makeDirectory(worktreePath); yield* manager.open( @@ -816,7 +809,7 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( it.effect("migrates legacy transcript filenames to terminal-scoped history path on open", () => Effect.gen(function* () { const { manager, logsDir } = yield* createManager(); - const legacyPath = path.join(logsDir, "thread-1.log"); + const legacyPath = joinPath(logsDir, "thread-1.log"); const nextPath = historyLogPath(logsDir); yield* writeFileString(legacyPath, "legacy-line\n"); diff --git a/apps/server/src/terminal/Layers/Manager.ts b/apps/server/src/terminal/Layers/Manager.ts index 5e14db8e5c1..48936301a30 100644 --- a/apps/server/src/terminal/Layers/Manager.ts +++ b/apps/server/src/terminal/Layers/Manager.ts @@ -1,5 +1,3 @@ -import path from "node:path"; - import { DEFAULT_TERMINAL_ID, type TerminalEvent, @@ -7,20 +5,20 @@ import { type TerminalSessionStatus, } from "@t3tools/contracts"; import { makeKeyedCoalescingWorker } from "@t3tools/shared/KeyedCoalescingWorker"; -import { - Effect, - Encoding, - Equal, - Exit, - Fiber, - FileSystem, - Layer, - Option, - Schema, - Scope, - Semaphore, - SynchronizedRef, -} from "effect"; +import * as Effect from "effect/Effect"; +import * as Encoding from "effect/Encoding"; +import * as Equal from "effect/Equal"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as FileSystem from "effect/FileSystem"; +import * as DateTime from "effect/DateTime"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Semaphore from "effect/Semaphore"; +import * as SynchronizedRef from "effect/SynchronizedRef"; import { ServerConfig } from "../../config.ts"; import { @@ -213,15 +211,25 @@ function normalizeShellCommand( return firstToken.replace(/^['"]|['"]$/g, ""); } +function basename(command: string, platform: NodeJS.Platform): string { + const normalized = platform === "win32" ? command.replaceAll("/", "\\") : command; + const separator = platform === "win32" ? "\\" : "/"; + return normalized.slice(normalized.lastIndexOf(separator) + 1); +} + +function joinWindowsPath(...segments: ReadonlyArray): string { + return segments + .filter((segment) => segment.length > 0) + .join("\\") + .replace(/\\+/g, "\\"); +} + function shellCandidateFromCommand( command: string | null, platform: NodeJS.Platform = process.platform, ): ShellCandidate | null { if (!command || command.length === 0) return null; - const shellName = - platform === "win32" - ? path.win32.basename(command).toLowerCase() - : path.basename(command).toLowerCase(); + const shellName = basename(command, platform).toLowerCase(); if (platform === "win32" && (shellName === "pwsh.exe" || shellName === "powershell.exe")) { return { shell: command, args: ["-NoLogo"] }; } @@ -236,7 +244,7 @@ function windowsSystemRoot(env: NodeJS.ProcessEnv): string { } function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { - return path.win32.join( + return joinWindowsPath( windowsSystemRoot(env), "System32", "WindowsPowerShell", @@ -246,7 +254,7 @@ function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { } function windowsCmdPath(env: NodeJS.ProcessEnv): string { - return path.win32.join(windowsSystemRoot(env), "System32", "cmd.exe"); + return joinWindowsPath(windowsSystemRoot(env), "System32", "cmd.exe"); } function formatShellCandidate(candidate: ShellCandidate): string { @@ -714,8 +722,10 @@ const makeTerminalManager = Effect.fn("makeTerminalManager")(function* () { export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWithOptions")( function* (options: TerminalManagerOptions) { const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; const context = yield* Effect.context(); const runFork = Effect.runForkWith(context); + const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); const logsDir = options.logsDir; const historyLineLimit = options.historyLineLimit ?? DEFAULT_HISTORY_LINE_LIMIT; @@ -1160,6 +1170,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith expectedPid: number, ) { while (true) { + const updatedAt = yield* nowIso; const action: DrainProcessEventAction = yield* Effect.sync(() => { if (session.pid !== expectedPid || !session.process || session.status !== "running") { session.pendingProcessEvents = []; @@ -1194,7 +1205,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith historyLineLimit, ); } - session.updatedAt = new Date().toISOString(); + session.updatedAt = updatedAt; return { type: "output", @@ -1221,7 +1232,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith session.exitSignal = Number.isInteger(nextEvent.event.signal) ? nextEvent.event.signal : null; - session.updatedAt = new Date().toISOString(); + session.updatedAt = updatedAt; return { type: "exit", @@ -1242,22 +1253,24 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith yield* queuePersist(action.threadId, action.terminalId, action.history); } + const createdAt = yield* nowIso; yield* publishEvent({ type: "output", threadId: action.threadId, terminalId: action.terminalId, - createdAt: new Date().toISOString(), + createdAt, data: action.data, }); continue; } yield* clearKillFiber(action.process); + const createdAt = yield* nowIso; yield* publishEvent({ type: "exited", threadId: action.threadId, terminalId: action.terminalId, - createdAt: new Date().toISOString(), + createdAt, exitCode: action.exitCode, exitSignal: action.exitSignal, }); @@ -1272,6 +1285,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith const process = session.process; if (!process) return; + const updatedAt = yield* nowIso; yield* modifyManagerState((state) => { cleanupProcessHandles(session); session.process = null; @@ -1282,7 +1296,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith session.pendingProcessEvents = []; session.pendingProcessEventIndex = 0; session.processEventDrainRunning = false; - session.updatedAt = new Date().toISOString(); + session.updatedAt = updatedAt; return [undefined, state] as const; }); @@ -1361,6 +1375,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith "terminal.cwd": input.cwd, }); + const startingAt = yield* nowIso; yield* modifyManagerState((state) => { session.status = "starting"; session.cwd = input.cwd; @@ -1373,7 +1388,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith session.pendingProcessEvents = []; session.pendingProcessEventIndex = 0; session.processEventDrainRunning = false; - session.updatedAt = new Date().toISOString(); + session.updatedAt = startingAt; return [undefined, state] as const; }); @@ -1404,11 +1419,12 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith runFork(drainProcessEvents(session, processPid)); }); + const startedAt = yield* nowIso; yield* modifyManagerState((state) => { session.process = ptyProcess; session.pid = processPid; session.status = "running"; - session.updatedAt = new Date().toISOString(); + session.updatedAt = startedAt; session.unsubscribeData = unsubscribeData; session.unsubscribeExit = unsubscribeExit; return [undefined, state] as const; @@ -1418,7 +1434,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith type: eventType, threadId: session.threadId, terminalId: session.terminalId, - createdAt: new Date().toISOString(), + createdAt: startedAt, snapshot: snapshot(session), }); }), @@ -1436,6 +1452,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith yield* startKillEscalation(ptyProcess, session.threadId, session.terminalId); } + const failedAt = yield* nowIso; yield* modifyManagerState((state) => { session.status = "error"; session.pid = null; @@ -1446,7 +1463,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith session.pendingProcessEvents = []; session.pendingProcessEventIndex = 0; session.processEventDrainRunning = false; - session.updatedAt = new Date().toISOString(); + session.updatedAt = failedAt; return [undefined, state] as const; }); @@ -1457,7 +1474,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith type: "error", threadId: session.threadId, terminalId: session.terminalId, - createdAt: new Date().toISOString(), + createdAt: failedAt, message, }); yield* Effect.logError("failed to start terminal", { @@ -1529,6 +1546,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith return; } + const updatedAt = yield* nowIso; const event = yield* modifyManagerState((state) => { const liveSession: Option.Option = Option.fromNullishOr( state.sessions.get(toSessionKey(session.threadId, session.terminalId)), @@ -1543,14 +1561,14 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith } liveSession.value.hasRunningSubprocess = hasRunningSubprocess.value; - liveSession.value.updatedAt = new Date().toISOString(); + liveSession.value.updatedAt = updatedAt; return [ Option.some({ type: "activity" as const, threadId: liveSession.value.threadId, terminalId: liveSession.value.terminalId, - createdAt: new Date().toISOString(), + createdAt: updatedAt, hasRunningSubprocess: hasRunningSubprocess.value, }), state, @@ -1629,6 +1647,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith const history = yield* readHistory(input.threadId, terminalId); const cols = input.cols ?? DEFAULT_OPEN_COLS; const rows = input.rows ?? DEFAULT_OPEN_ROWS; + const createdAt = yield* nowIso; const session: TerminalSessionState = { threadId: input.threadId, terminalId, @@ -1643,7 +1662,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith processEventDrainRunning: false, exitCode: null, exitSignal: null, - updatedAt: new Date().toISOString(), + updatedAt: createdAt, cols, rows, process: null, @@ -1734,7 +1753,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith if (liveSession.cols !== targetCols || liveSession.rows !== targetRows) { liveSession.cols = targetCols; liveSession.rows = targetRows; - liveSession.updatedAt = new Date().toISOString(); + liveSession.updatedAt = yield* nowIso; liveSession.process.resize(targetCols, targetRows); } @@ -1768,7 +1787,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith } session.cols = input.cols; session.rows = input.rows; - session.updatedAt = new Date().toISOString(); + session.updatedAt = yield* nowIso; yield* Effect.sync(() => process.resize(input.cols, input.rows)); }); @@ -1783,13 +1802,14 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith session.pendingProcessEvents = []; session.pendingProcessEventIndex = 0; session.processEventDrainRunning = false; - session.updatedAt = new Date().toISOString(); + const clearedAt = yield* nowIso; + session.updatedAt = clearedAt; yield* persistHistory(input.threadId, terminalId, session.history); yield* publishEvent({ type: "cleared", threadId: input.threadId, terminalId, - createdAt: new Date().toISOString(), + createdAt: clearedAt, }); }), ); @@ -1808,6 +1828,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith if (Option.isNone(existingSession)) { const cols = input.cols ?? DEFAULT_OPEN_COLS; const rows = input.rows ?? DEFAULT_OPEN_ROWS; + const createdAt = yield* nowIso; session = { threadId: input.threadId, terminalId, @@ -1822,7 +1843,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith processEventDrainRunning: false, exitCode: null, exitSignal: null, - updatedAt: new Date().toISOString(), + updatedAt: createdAt, cols, rows, process: null, diff --git a/apps/server/src/terminal/Layers/NodePTY.test.ts b/apps/server/src/terminal/Layers/NodePTY.test.ts index 06f186312aa..15d24360f7e 100644 --- a/apps/server/src/terminal/Layers/NodePTY.test.ts +++ b/apps/server/src/terminal/Layers/NodePTY.test.ts @@ -1,4 +1,6 @@ -import { FileSystem, Path, Effect } from "effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; +import * as Effect from "effect/Effect"; import { assert, it } from "@effect/vitest"; import { ensureNodePtySpawnHelperExecutable } from "./NodePTY.ts"; diff --git a/apps/server/src/terminal/Layers/NodePTY.ts b/apps/server/src/terminal/Layers/NodePTY.ts index 1c75a4a958b..c81d76f5d1e 100644 --- a/apps/server/src/terminal/Layers/NodePTY.ts +++ b/apps/server/src/terminal/Layers/NodePTY.ts @@ -1,6 +1,9 @@ import { createRequire } from "node:module"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { PtyAdapter } from "../Services/PTY.ts"; import { PtySpawnError, diff --git a/apps/server/src/terminal/Services/Manager.ts b/apps/server/src/terminal/Services/Manager.ts index fb7a7da7b64..6db23d571b2 100644 --- a/apps/server/src/terminal/Services/Manager.ts +++ b/apps/server/src/terminal/Services/Manager.ts @@ -23,7 +23,8 @@ import { TerminalWriteInput, } from "@t3tools/contracts"; import type { PtyProcess } from "./PTY.ts"; -import { Effect, Context } from "effect"; +import * as Effect from "effect/Effect"; +import * as Context from "effect/Context"; export { TerminalCwdError, diff --git a/apps/server/src/terminal/Services/PTY.ts b/apps/server/src/terminal/Services/PTY.ts index 091e527ef2b..93cd1b0d47b 100644 --- a/apps/server/src/terminal/Services/PTY.ts +++ b/apps/server/src/terminal/Services/PTY.ts @@ -6,7 +6,9 @@ * * @module PtyAdapter */ -import { Effect, Schema, Context } from "effect"; +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; /** * PtyError - Error type for PTY adapter operations. diff --git a/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts b/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts index 19cff6f0344..42667fd3e82 100644 --- a/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts +++ b/apps/server/src/textGeneration/ClaudeTextGeneration.test.ts @@ -1,7 +1,11 @@ import { ClaudeSettings, ProviderInstanceId } from "@t3tools/contracts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { createModelSelection } from "@t3tools/shared/model"; import { expect } from "vitest"; @@ -287,6 +291,7 @@ it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGeneration", (it) => { const claudeHome = path.join(process.cwd(), ".claude-work-test"); return yield* withFakeClaudeEnv( { + // @effect-diagnostics-next-line preferSchemaOverJson:off output: JSON.stringify({ structured_output: { title: "Use Claude home", diff --git a/apps/server/src/textGeneration/ClaudeTextGeneration.ts b/apps/server/src/textGeneration/ClaudeTextGeneration.ts index 2f0cbc509b6..f801ef2f1fa 100644 --- a/apps/server/src/textGeneration/ClaudeTextGeneration.ts +++ b/apps/server/src/textGeneration/ClaudeTextGeneration.ts @@ -7,7 +7,10 @@ * * @module ClaudeTextGeneration */ -import { Effect, Option, Schema, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { type ClaudeSettings, type ModelSelection } from "@t3tools/contracts"; @@ -93,7 +96,9 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu outputSchemaJson: S; modelSelection: ModelSelection; }): Effect.fn.Return { - const jsonSchemaStr = JSON.stringify(toJsonSchemaObject(outputSchemaJson)); + const jsonSchemaStr = Schema.encodeUnknownSync(Schema.UnknownFromJsonString)( + toJsonSchemaObject(outputSchemaJson), + ); const caps = getClaudeModelCapabilities(modelSelection.model); const descriptors = getProviderOptionDescriptors({ caps, @@ -126,7 +131,9 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu "--model", resolveClaudeApiModelId(modelSelection), ...(cliEffort ? ["--effort", cliEffort] : []), - ...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []), + ...(Object.keys(settings).length > 0 + ? ["--settings", Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(settings)] + : []), "--dangerously-skip-permissions", ], { diff --git a/apps/server/src/textGeneration/CodexTextGeneration.test.ts b/apps/server/src/textGeneration/CodexTextGeneration.test.ts index 07123e921b1..322cb2fb29e 100644 --- a/apps/server/src/textGeneration/CodexTextGeneration.test.ts +++ b/apps/server/src/textGeneration/CodexTextGeneration.test.ts @@ -1,6 +1,11 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path, Result, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; import { createModelSelection } from "@t3tools/shared/model"; import { expect } from "vitest"; @@ -113,6 +118,7 @@ function makeFakeCodexBinary( : []), ...(input.stdinMustContain !== undefined ? [ + // @effect-diagnostics-next-line preferSchemaOverJson:off `if ! printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustContain)} >/dev/null; then`, ' printf "%s\\n" "stdin missing expected content" >&2', ` exit 3`, @@ -121,6 +127,7 @@ function makeFakeCodexBinary( : []), ...(input.stdinMustNotContain !== undefined ? [ + // @effect-diagnostics-next-line preferSchemaOverJson:off `if printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustNotContain)} >/dev/null; then`, ' printf "%s\\n" "stdin contained forbidden content" >&2', ` exit 4`, @@ -128,7 +135,10 @@ function makeFakeCodexBinary( ] : []), ...(input.stderr !== undefined - ? [`printf "%s\\n" ${JSON.stringify(input.stderr)} >&2`] + ? [ + // @effect-diagnostics-next-line preferSchemaOverJson:off + `printf "%s\\n" ${JSON.stringify(input.stderr)} >&2`, + ] : []), 'if [ -n "$output_path" ]; then', " cat > \"$output_path\" <<'__T3CODE_FAKE_CODEX_OUTPUT__'", @@ -415,7 +425,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const { attachmentsDir } = yield* ServerConfig; - const attachmentId = `thread-branch-image-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; + const attachmentId = "thread-branch-image-attachment"; const attachmentPath = path.join(attachmentsDir, `${attachmentId}.png`); yield* fs.makeDirectory(attachmentsDir, { recursive: true }); yield* fs.writeFile(attachmentPath, Buffer.from("hello")); @@ -453,7 +463,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const { attachmentsDir } = yield* ServerConfig; - const attachmentId = `thread-1-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; + const attachmentId = "thread-1-attachment"; const imagePath = path.join(attachmentsDir, `${attachmentId}.png`); yield* fs.makeDirectory(attachmentsDir, { recursive: true }); yield* fs.writeFile(imagePath, Buffer.from("hello")); @@ -502,7 +512,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const { attachmentsDir } = yield* ServerConfig; - const missingAttachmentId = `thread-missing-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; + const missingAttachmentId = "thread-missing-attachment"; const missingPath = path.join(attachmentsDir, `${missingAttachmentId}.png`); yield* fs.remove(missingPath).pipe(Effect.catch(() => Effect.void)); diff --git a/apps/server/src/textGeneration/CodexTextGeneration.ts b/apps/server/src/textGeneration/CodexTextGeneration.ts index 786a0be4c49..69e9b74c697 100644 --- a/apps/server/src/textGeneration/CodexTextGeneration.ts +++ b/apps/server/src/textGeneration/CodexTextGeneration.ts @@ -1,4 +1,11 @@ -import { Effect, FileSystem, Option, Path, Random, Schema, Scope, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Random from "effect/Random"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { type CodexSettings, type ModelSelection } from "@t3tools/contracts"; @@ -152,7 +159,7 @@ export const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(func const schemaPath = yield* writeTempFile( operation, "codex-schema", - JSON.stringify(toJsonSchemaObject(outputSchemaJson)), + Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(toJsonSchemaObject(outputSchemaJson)), ); const outputPath = yield* writeTempFile(operation, "codex-output", ""); diff --git a/apps/server/src/textGeneration/CursorTextGeneration.test.ts b/apps/server/src/textGeneration/CursorTextGeneration.test.ts index 0de135c8465..6e3cd064fff 100644 --- a/apps/server/src/textGeneration/CursorTextGeneration.test.ts +++ b/apps/server/src/textGeneration/CursorTextGeneration.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as path from "node:path"; import * as os from "node:os"; import { fileURLToPath } from "node:url"; @@ -5,7 +6,11 @@ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Effect, Layer, Schema } from "effect"; +import * as Clock from "effect/Clock"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; import { createModelSelection } from "@t3tools/shared/model"; import { expect } from "vitest"; @@ -67,17 +72,19 @@ function withFakeAcpAgent( } function waitForFileContent(path: string): Effect.Effect { - return Effect.promise(async () => { - const deadline = Date.now() + 5_000; + return Effect.gen(function* () { + const deadline = (yield* Clock.currentTimeMillis) + 5_000; for (;;) { - try { - return readFileSync(path, "utf8"); - } catch (error) { - if (Date.now() >= deadline) { - throw error instanceof Error ? error : new Error(String(error)); + const result = yield* Effect.exit(Effect.sync(() => readFileSync(path, "utf8"))); + if (Exit.isSuccess(result)) { + return result.value; + } + { + if ((yield* Clock.currentTimeMillis) >= deadline) { + return yield* Effect.die(result.cause); } } - await new Promise((resolve) => setTimeout(resolve, 25)); + yield* Effect.sleep(25); } }); } diff --git a/apps/server/src/textGeneration/CursorTextGeneration.ts b/apps/server/src/textGeneration/CursorTextGeneration.ts index 1cde82d61b6..590ac1d25d4 100644 --- a/apps/server/src/textGeneration/CursorTextGeneration.ts +++ b/apps/server/src/textGeneration/CursorTextGeneration.ts @@ -1,4 +1,7 @@ -import { Effect, Option, Ref, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; import { ChildProcessSpawner } from "effect/unstable/process"; import { type CursorSettings, type ModelSelection } from "@t3tools/contracts"; diff --git a/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts b/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts index 907c749355f..acc57cd96e3 100644 --- a/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts +++ b/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts @@ -1,7 +1,10 @@ import { OpenCodeSettings, ProviderInstanceId } from "@t3tools/contracts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; -import { Duration, Effect, Layer, Schema } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; import { TestClock } from "effect/testing"; import { NetService } from "@t3tools/shared/Net"; import { beforeEach, expect } from "vitest"; diff --git a/apps/server/src/textGeneration/OpenCodeTextGeneration.ts b/apps/server/src/textGeneration/OpenCodeTextGeneration.ts index d646e4f2e5a..17191098fd1 100644 --- a/apps/server/src/textGeneration/OpenCodeTextGeneration.ts +++ b/apps/server/src/textGeneration/OpenCodeTextGeneration.ts @@ -1,4 +1,8 @@ -import { Effect, Exit, Fiber, Schema, Scope } from "effect"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; import * as Semaphore from "effect/Semaphore"; import { diff --git a/apps/server/src/textGeneration/TextGeneration.test.ts b/apps/server/src/textGeneration/TextGeneration.test.ts index 4f3d44f9258..2f518f1656d 100644 --- a/apps/server/src/textGeneration/TextGeneration.test.ts +++ b/apps/server/src/textGeneration/TextGeneration.test.ts @@ -1,5 +1,8 @@ import { it } from "@effect/vitest"; -import { Effect, PubSub, Result, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as PubSub from "effect/PubSub"; +import * as Result from "effect/Result"; +import * as Stream from "effect/Stream"; import { describe, expect } from "vitest"; import { ProviderInstanceId } from "@t3tools/contracts"; diff --git a/apps/server/src/textGeneration/TextGeneration.ts b/apps/server/src/textGeneration/TextGeneration.ts index 51796faf8a7..36a23d509db 100644 --- a/apps/server/src/textGeneration/TextGeneration.ts +++ b/apps/server/src/textGeneration/TextGeneration.ts @@ -1,4 +1,6 @@ -import { Context, Effect, Layer } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import type { ChatAttachment, ModelSelection, ProviderInstanceId } from "@t3tools/contracts"; import { TextGenerationError } from "@t3tools/contracts"; diff --git a/apps/server/src/textGeneration/TextGenerationPolicy.ts b/apps/server/src/textGeneration/TextGenerationPolicy.ts index b0e020fa4fc..422927373d3 100644 --- a/apps/server/src/textGeneration/TextGenerationPolicy.ts +++ b/apps/server/src/textGeneration/TextGenerationPolicy.ts @@ -1,4 +1,4 @@ -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; export const TextGenerationPolicyKind = Schema.Literals([ "default", diff --git a/apps/server/src/textGeneration/TextGenerationPrompts.ts b/apps/server/src/textGeneration/TextGenerationPrompts.ts index 43ae62047b9..6015e83b5d4 100644 --- a/apps/server/src/textGeneration/TextGenerationPrompts.ts +++ b/apps/server/src/textGeneration/TextGenerationPrompts.ts @@ -6,7 +6,7 @@ * * @module textGenerationPrompts */ -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import type { ChatAttachment } from "@t3tools/contracts"; import { limitSection } from "./TextGenerationUtils.ts"; diff --git a/apps/server/src/textGeneration/TextGenerationUtils.ts b/apps/server/src/textGeneration/TextGenerationUtils.ts index fcd8cc8b689..29205409a36 100644 --- a/apps/server/src/textGeneration/TextGenerationUtils.ts +++ b/apps/server/src/textGeneration/TextGenerationUtils.ts @@ -1,4 +1,4 @@ -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import { TextGenerationError } from "@t3tools/contracts"; diff --git a/apps/server/src/vcs/GitVcsDriver.test.ts b/apps/server/src/vcs/GitVcsDriver.test.ts index 0e5ba5b82ec..8ec819fc8d8 100644 --- a/apps/server/src/vcs/GitVcsDriver.test.ts +++ b/apps/server/src/vcs/GitVcsDriver.test.ts @@ -1,5 +1,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Effect, FileSystem, Layer, Path, PlatformError } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as PlatformError from "effect/PlatformError"; import { ChildProcessSpawner } from "effect/unstable/process"; import { assert, it } from "@effect/vitest"; diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts index 49a135aec4c..2bd2d5c00da 100644 --- a/apps/server/src/vcs/GitVcsDriver.ts +++ b/apps/server/src/vcs/GitVcsDriver.ts @@ -1,4 +1,8 @@ -import { Context, DateTime, Effect, Layer, Option } from "effect"; +import * as Context from "effect/Context"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ChildProcessSpawner } from "effect/unstable/process"; import { diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 0daf9ab9564..558d6ef296a 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1,6 +1,11 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it, describe } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path, PlatformError, Scope } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as PlatformError from "effect/PlatformError"; +import * as Scope from "effect/Scope"; import { GitCommandError } from "@t3tools/contracts"; import { ServerConfig } from "../config.ts"; diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 489e3b3583b..ea343bfe4ff 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1,21 +1,19 @@ -import { - Cache, - Data, - DateTime, - Duration, - Effect, - Exit, - FileSystem, - Option, - Path, - PlatformError, - Ref, - Result, - Schema, - Scope, - Semaphore, - Stream, -} from "effect"; +import * as Cache from "effect/Cache"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as PlatformError from "effect/PlatformError"; +import * as Ref from "effect/Ref"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; +import * as Scope from "effect/Scope"; +import * as Semaphore from "effect/Semaphore"; +import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { GitCommandError, type VcsRef } from "@t3tools/contracts"; diff --git a/apps/server/src/vcs/VcsDriver.ts b/apps/server/src/vcs/VcsDriver.ts index ae09d840f88..3bc524ca019 100644 --- a/apps/server/src/vcs/VcsDriver.ts +++ b/apps/server/src/vcs/VcsDriver.ts @@ -1,4 +1,5 @@ -import { Context, type Effect } from "effect"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { VcsDriverCapabilities, diff --git a/apps/server/src/vcs/VcsDriverRegistry.test.ts b/apps/server/src/vcs/VcsDriverRegistry.test.ts index b9330c885a8..de5b95ffe22 100644 --- a/apps/server/src/vcs/VcsDriverRegistry.test.ts +++ b/apps/server/src/vcs/VcsDriverRegistry.test.ts @@ -1,5 +1,6 @@ import { assert, it, describe } from "@effect/vitest"; -import { Effect, Layer } from "effect"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as VcsProcess from "./VcsProcess.ts"; diff --git a/apps/server/src/vcs/VcsDriverRegistry.ts b/apps/server/src/vcs/VcsDriverRegistry.ts index b29fa43ed8e..22868855737 100644 --- a/apps/server/src/vcs/VcsDriverRegistry.ts +++ b/apps/server/src/vcs/VcsDriverRegistry.ts @@ -1,4 +1,9 @@ -import { Cache, Context, Duration, Effect, Exit, Layer } from "effect"; +import * as Cache from "effect/Cache"; +import * as Context from "effect/Context"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; import type { VcsDriverKind, VcsError, VcsRepositoryIdentity } from "@t3tools/contracts"; import { VcsUnsupportedOperationError } from "@t3tools/contracts"; diff --git a/apps/server/src/vcs/VcsProcess.ts b/apps/server/src/vcs/VcsProcess.ts index 639621ceeea..7e80b961944 100644 --- a/apps/server/src/vcs/VcsProcess.ts +++ b/apps/server/src/vcs/VcsProcess.ts @@ -1,4 +1,11 @@ -import { Duration, Context, Effect, Layer, Option, PlatformError, Sink, Stream } from "effect"; +import * as Duration from "effect/Duration"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as PlatformError from "effect/PlatformError"; +import * as Sink from "effect/Sink"; +import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { diff --git a/apps/server/src/vcs/VcsProjectConfig.test.ts b/apps/server/src/vcs/VcsProjectConfig.test.ts index b08b5716732..aac4beb7e32 100644 --- a/apps/server/src/vcs/VcsProjectConfig.test.ts +++ b/apps/server/src/vcs/VcsProjectConfig.test.ts @@ -1,6 +1,9 @@ import { assert, it, describe } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import * as VcsProjectConfig from "./VcsProjectConfig.ts"; @@ -38,6 +41,7 @@ describe("VcsProjectConfig", () => { yield* fileSystem.makeDirectory(nested, { recursive: true }); yield* fileSystem.writeFileString( path.join(configDir, "vcs.json"), + // @effect-diagnostics-next-line preferSchemaOverJson:off JSON.stringify({ vcs: { kind: "jj" } }), ); diff --git a/apps/server/src/vcs/VcsProjectConfig.ts b/apps/server/src/vcs/VcsProjectConfig.ts index 6b4ba008d4e..007a3255e15 100644 --- a/apps/server/src/vcs/VcsProjectConfig.ts +++ b/apps/server/src/vcs/VcsProjectConfig.ts @@ -1,4 +1,9 @@ -import { Context, Effect, FileSystem, Layer, Path, Schema } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { VcsDriverKind, type VcsDriverKind as VcsDriverKindType } from "@t3tools/contracts"; diff --git a/apps/server/src/vcs/VcsProvisioningService.test.ts b/apps/server/src/vcs/VcsProvisioningService.test.ts index b26f331a3c7..ba919a5f435 100644 --- a/apps/server/src/vcs/VcsProvisioningService.test.ts +++ b/apps/server/src/vcs/VcsProvisioningService.test.ts @@ -1,5 +1,8 @@ import { assert, it } from "@effect/vitest"; -import { DateTime, Effect, Layer, Option } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as VcsDriver from "./VcsDriver.ts"; diff --git a/apps/server/src/vcs/VcsProvisioningService.ts b/apps/server/src/vcs/VcsProvisioningService.ts index 9f8f822f2a2..38006b4b603 100644 --- a/apps/server/src/vcs/VcsProvisioningService.ts +++ b/apps/server/src/vcs/VcsProvisioningService.ts @@ -1,4 +1,6 @@ -import { Context, Effect, Layer } from "effect"; +import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; import { type VcsDriverKind, diff --git a/apps/server/src/vcs/VcsStatusBroadcaster.test.ts b/apps/server/src/vcs/VcsStatusBroadcaster.test.ts index ca7cccf303c..90567d437d2 100644 --- a/apps/server/src/vcs/VcsStatusBroadcaster.test.ts +++ b/apps/server/src/vcs/VcsStatusBroadcaster.test.ts @@ -1,6 +1,14 @@ import { assert, it, describe } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Deferred, Effect, Exit, FileSystem, Layer, Option, Path, Scope, Stream } from "effect"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import type { VcsStatusLocalResult, VcsStatusRemoteResult, @@ -46,6 +54,7 @@ function makeTestLayer(state: { remoteInvalidationCalls: number; }) { return VcsStatusBroadcaster.layer.pipe( + Layer.provideMerge(NodeServices.layer), Layer.provide( Layer.mock(GitWorkflowService.GitWorkflowService)({ localStatus: () => @@ -185,6 +194,7 @@ describe("VcsStatusBroadcaster", () => { remoteInvalidationCalls: 0, }; const testLayer = VcsStatusBroadcaster.layer.pipe( + Layer.provideMerge(NodeServices.layer), Layer.provide( Layer.mock(GitWorkflowService.GitWorkflowService)({ localStatus: (input) => @@ -231,7 +241,7 @@ describe("VcsStatusBroadcaster", () => { assert.deepStrictEqual(seenCwds, [realPath, realPath]); assert.equal(state.localStatusCalls, 1); assert.equal(state.remoteStatusCalls, 1); - }).pipe(Effect.provide(Layer.mergeAll(testLayer, NodeServices.layer))); + }).pipe(Effect.provide(testLayer)); }); it.effect("streams a local snapshot first and remote updates later", () => { @@ -286,6 +296,7 @@ describe("VcsStatusBroadcaster", () => { let remoteInterruptedDeferred: Deferred.Deferred | null = null; let remoteStartedDeferred: Deferred.Deferred | null = null; const testLayer = VcsStatusBroadcaster.layer.pipe( + Layer.provideMerge(NodeServices.layer), Layer.provide( Layer.mock(GitWorkflowService.GitWorkflowService)({ localStatus: () => diff --git a/apps/server/src/vcs/VcsStatusBroadcaster.ts b/apps/server/src/vcs/VcsStatusBroadcaster.ts index 1d0cddc4c41..4c431fd0079 100644 --- a/apps/server/src/vcs/VcsStatusBroadcaster.ts +++ b/apps/server/src/vcs/VcsStatusBroadcaster.ts @@ -1,18 +1,15 @@ -import { realpathSync } from "node:fs"; - -import { - Context, - Duration, - Effect, - Exit, - Fiber, - Layer, - PubSub, - Ref, - Scope, - Stream, - SynchronizedRef, -} from "effect"; +import * as Context from "effect/Context"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Fiber from "effect/Fiber"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as PubSub from "effect/PubSub"; +import * as Ref from "effect/Ref"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; +import * as SynchronizedRef from "effect/SynchronizedRef"; import type { GitManagerServiceError, VcsStatusInput, @@ -69,18 +66,17 @@ function fingerprintStatusPart(status: unknown): string { return JSON.stringify(status); } -function normalizeCwd(cwd: string): string { - try { - return realpathSync.native(cwd); - } catch { - return cwd; - } -} +const normalizeCwd = (cwd: string) => + Effect.service(FileSystem.FileSystem).pipe( + Effect.flatMap((fs) => fs.realPath(cwd)), + Effect.orElseSucceed(() => cwd), + ); export const layer = Layer.effect( VcsStatusBroadcaster, Effect.gen(function* () { const workflow = yield* GitWorkflowService.GitWorkflowService; + const fs = yield* FileSystem.FileSystem; const changesPubSub = yield* Effect.acquireRelease( PubSub.unbounded(), (pubsub) => PubSub.shutdown(pubsub), @@ -195,10 +191,12 @@ export const layer = Layer.effect( }, ); + const withFileSystem = Effect.provideService(FileSystem.FileSystem, fs); + const getStatus: VcsStatusBroadcasterShape["getStatus"] = Effect.fn( "VcsStatusBroadcaster.getStatus", )(function* (input) { - const cwd = normalizeCwd(input.cwd); + const cwd = yield* withFileSystem(normalizeCwd(input.cwd)); const [local, remote] = yield* Effect.all([ getOrLoadLocalStatus(cwd), getOrLoadRemoteStatus(cwd), @@ -209,7 +207,7 @@ export const layer = Layer.effect( const refreshLocalStatus: VcsStatusBroadcasterShape["refreshLocalStatus"] = Effect.fn( "VcsStatusBroadcaster.refreshLocalStatus", )(function* (rawCwd) { - const cwd = normalizeCwd(rawCwd); + const cwd = yield* withFileSystem(normalizeCwd(rawCwd)); yield* workflow.invalidateLocalStatus(cwd); const local = yield* workflow.localStatus({ cwd }); return yield* updateCachedLocalStatus(cwd, local, { publish: true }); @@ -226,7 +224,7 @@ export const layer = Layer.effect( const refreshStatus: VcsStatusBroadcasterShape["refreshStatus"] = Effect.fn( "VcsStatusBroadcaster.refreshStatus", )(function* (rawCwd) { - const cwd = normalizeCwd(rawCwd); + const cwd = yield* withFileSystem(normalizeCwd(rawCwd)); const [local, remote] = yield* Effect.all([ refreshLocalStatus(cwd), refreshRemoteStatus(cwd), @@ -312,7 +310,7 @@ export const layer = Layer.effect( const streamStatus: VcsStatusBroadcasterShape["streamStatus"] = (input) => Stream.unwrap( Effect.gen(function* () { - const cwd = normalizeCwd(input.cwd); + const cwd = yield* withFileSystem(normalizeCwd(input.cwd)); const subscription = yield* PubSub.subscribe(changesPubSub); const initialLocal = yield* getOrLoadLocalStatus(cwd); const initialRemote = (yield* getCachedStatus(cwd))?.remote?.value ?? null; diff --git a/apps/server/src/vcs/testing/VcsDriverContractHarness.ts b/apps/server/src/vcs/testing/VcsDriverContractHarness.ts index f513f03b756..e18cea3499f 100644 --- a/apps/server/src/vcs/testing/VcsDriverContractHarness.ts +++ b/apps/server/src/vcs/testing/VcsDriverContractHarness.ts @@ -1,14 +1,12 @@ import { assert, it, describe } from "@effect/vitest"; -import { - Effect, - FileSystem, - Layer, - Path, - type PlatformError, - type Scope, - DateTime, - Option, -} from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import type * as PlatformError from "effect/PlatformError"; +import type * as Scope from "effect/Scope"; +import * as DateTime from "effect/DateTime"; +import * as Option from "effect/Option"; import type { VcsDriverKind } from "@t3tools/contracts"; import * as VcsDriver from "../VcsDriver.ts"; diff --git a/apps/server/src/workspace/Layers/WorkspaceEntries.test.ts b/apps/server/src/workspace/Layers/WorkspaceEntries.test.ts index a7385794e93..84ea5c51937 100644 --- a/apps/server/src/workspace/Layers/WorkspaceEntries.test.ts +++ b/apps/server/src/workspace/Layers/WorkspaceEntries.test.ts @@ -1,8 +1,13 @@ +// @effect-diagnostics nodeBuiltinImport:off import fsPromises from "node:fs/promises"; - import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, afterEach, describe, expect, vi } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path, PlatformError } from "effect"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as PlatformError from "effect/PlatformError"; import { ServerConfig } from "../../config.ts"; import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; @@ -222,25 +227,37 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { yield* writeTextFile(cwd, "src/components/Composer.tsx"); let rootReadCount = 0; + let releaseRootRead: (() => void) | undefined; + const rootReadGate = new Promise((resolve) => { + releaseRootRead = resolve; + }); const originalReaddir = fsPromises.readdir.bind(fsPromises); vi.spyOn(fsPromises, "readdir").mockImplementation((async ( ...args: Parameters ) => { if (args[0] === cwd) { rootReadCount += 1; - await new Promise((resolve) => setTimeout(resolve, 20)); + await rootReadGate; } return originalReaddir(...args); }) as typeof fsPromises.readdir); - yield* Effect.all( + const searches = yield* Effect.all( [ searchWorkspaceEntries({ cwd, query: "", limit: 100 }), searchWorkspaceEntries({ cwd, query: "comp", limit: 100 }), searchWorkspaceEntries({ cwd, query: "src", limit: 100 }), ], { concurrency: "unbounded" }, - ); + ).pipe(Effect.forkScoped); + for (let attempt = 0; attempt < 50; attempt += 1) { + if (rootReadCount > 0) { + break; + } + yield* Effect.yieldNow; + } + releaseRootRead?.(); + yield* Fiber.join(searches); expect(rootReadCount).toBe(1); }), @@ -257,6 +274,10 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { let activeReads = 0; let peakReads = 0; + let releaseReads: (() => void) | undefined; + const readsGate = new Promise((resolve) => { + releaseReads = resolve; + }); const originalReaddir = fsPromises.readdir.bind(fsPromises); vi.spyOn(fsPromises, "readdir").mockImplementation((async ( ...args: Parameters @@ -265,7 +286,7 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { if (typeof target === "string" && target.startsWith(cwd)) { activeReads += 1; peakReads = Math.max(peakReads, activeReads); - await new Promise((resolve) => setTimeout(resolve, 4)); + await readsGate; try { return await originalReaddir(...args); } finally { @@ -275,7 +296,17 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { return originalReaddir(...args); }) as typeof fsPromises.readdir); - yield* searchWorkspaceEntries({ cwd, query: "", limit: 200 }); + const search = yield* searchWorkspaceEntries({ cwd, query: "", limit: 200 }).pipe( + Effect.forkScoped, + ); + for (let attempt = 0; attempt < 50; attempt += 1) { + if (activeReads > 0) { + break; + } + yield* Effect.yieldNow; + } + releaseReads?.(); + yield* Fiber.join(search); expect(peakReads).toBeLessThanOrEqual(32); }), diff --git a/apps/server/src/workspace/Layers/WorkspaceEntries.ts b/apps/server/src/workspace/Layers/WorkspaceEntries.ts index 3046546813f..0c0ab638207 100644 --- a/apps/server/src/workspace/Layers/WorkspaceEntries.ts +++ b/apps/server/src/workspace/Layers/WorkspaceEntries.ts @@ -1,8 +1,15 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as OS from "node:os"; import fsPromises from "node:fs/promises"; import type { Dirent } from "node:fs"; -import { Cache, DateTime, Duration, Effect, Exit, Layer, Path } from "effect"; +import * as Cache from "effect/Cache"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { type FilesystemBrowseInput, type ProjectEntry } from "@t3tools/contracts"; import { isExplicitRelativePath, isWindowsAbsolutePath } from "@t3tools/shared/path"; diff --git a/apps/server/src/workspace/Layers/WorkspaceFileSystem.test.ts b/apps/server/src/workspace/Layers/WorkspaceFileSystem.test.ts index 9c38b88f851..e748a27a58a 100644 --- a/apps/server/src/workspace/Layers/WorkspaceFileSystem.test.ts +++ b/apps/server/src/workspace/Layers/WorkspaceFileSystem.test.ts @@ -1,6 +1,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, describe, expect } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { ServerConfig } from "../../config.ts"; import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; diff --git a/apps/server/src/workspace/Layers/WorkspaceFileSystem.ts b/apps/server/src/workspace/Layers/WorkspaceFileSystem.ts index 84e5d9c6d12..9f53ade1bb9 100644 --- a/apps/server/src/workspace/Layers/WorkspaceFileSystem.ts +++ b/apps/server/src/workspace/Layers/WorkspaceFileSystem.ts @@ -1,4 +1,7 @@ -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { WorkspaceFileSystem, diff --git a/apps/server/src/workspace/Layers/WorkspacePaths.test.ts b/apps/server/src/workspace/Layers/WorkspacePaths.test.ts index 13658e9c1ae..0a9252a7def 100644 --- a/apps/server/src/workspace/Layers/WorkspacePaths.test.ts +++ b/apps/server/src/workspace/Layers/WorkspacePaths.test.ts @@ -1,6 +1,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, describe, expect } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { WorkspacePaths } from "../Services/WorkspacePaths.ts"; import { WorkspacePathsLive } from "./WorkspacePaths.ts"; diff --git a/apps/server/src/workspace/Layers/WorkspacePaths.ts b/apps/server/src/workspace/Layers/WorkspacePaths.ts index f19bb3624d8..9dd33aaac7d 100644 --- a/apps/server/src/workspace/Layers/WorkspacePaths.ts +++ b/apps/server/src/workspace/Layers/WorkspacePaths.ts @@ -1,5 +1,8 @@ import * as OS from "node:os"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { WorkspacePaths, diff --git a/apps/server/src/workspace/Services/WorkspaceEntries.ts b/apps/server/src/workspace/Services/WorkspaceEntries.ts index e546bf4c5d1..a5d7ed8c082 100644 --- a/apps/server/src/workspace/Services/WorkspaceEntries.ts +++ b/apps/server/src/workspace/Services/WorkspaceEntries.ts @@ -6,8 +6,9 @@ * * @module WorkspaceEntries */ -import { Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { FilesystemBrowseInput, diff --git a/apps/server/src/workspace/Services/WorkspaceFileSystem.ts b/apps/server/src/workspace/Services/WorkspaceFileSystem.ts index dc6cc6e9d85..b448a8ab505 100644 --- a/apps/server/src/workspace/Services/WorkspaceFileSystem.ts +++ b/apps/server/src/workspace/Services/WorkspaceFileSystem.ts @@ -6,8 +6,9 @@ * * @module WorkspaceFileSystem */ -import { Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; import type { ProjectWriteFileInput, ProjectWriteFileResult } from "@t3tools/contracts"; import { WorkspacePathOutsideRootError } from "./WorkspacePaths.ts"; diff --git a/apps/server/src/workspace/Services/WorkspacePaths.ts b/apps/server/src/workspace/Services/WorkspacePaths.ts index 1cd016284a4..7c57ca19bd2 100644 --- a/apps/server/src/workspace/Services/WorkspacePaths.ts +++ b/apps/server/src/workspace/Services/WorkspacePaths.ts @@ -6,8 +6,9 @@ * * @module WorkspacePaths */ -import { Schema, Context } from "effect"; -import type { Effect } from "effect"; +import * as Schema from "effect/Schema"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; export class WorkspaceRootNotExistsError extends Schema.TaggedErrorClass()( "WorkspaceRootNotExistsError", diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 476140dd3ae..78e5e9f3b04 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -1,4 +1,13 @@ -import { Cause, Duration, Effect, Layer, Option, Queue, Ref, Schema, Stream } from "effect"; +import * as Cause from "effect/Cause"; +import * as DateTime from "effect/DateTime"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Queue from "effect/Queue"; +import * as Ref from "effect/Ref"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { type AuthAccessStreamEvent, AuthSessionId, @@ -29,6 +38,8 @@ import { RpcSerialization, RpcServer } from "effect/unstable/rpc"; import { CheckpointDiffQuery } from "./checkpointing/Services/CheckpointDiffQuery.ts"; import { ServerConfig } from "./config.ts"; + +const nowIsoSync = () => Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); import { Keybindings } from "./keybindings.ts"; import { Open, resolveAvailableEditors } from "./open.ts"; import { normalizeDispatchCommand } from "./orchestration/Normalizer.ts"; @@ -398,7 +409,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => threadId: command.threadId, kind: "setup-script.started", summary: "Setup script started", - createdAt: new Date().toISOString(), + createdAt: nowIsoSync(), payload, tone: "info", }), @@ -423,7 +434,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => bootstrap?.runSetupScript && targetWorktreePath ? (() => { const worktreePath = targetWorktreePath; - const requestedAt = new Date().toISOString(); + const requestedAt = nowIsoSync(); return projectSetupScriptRunner .runForThread({ threadId: command.threadId, @@ -597,7 +608,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => `session-stop-for-archive:${normalizedCommand.commandId}`, ), threadId: normalizedCommand.threadId, - createdAt: new Date().toISOString(), + createdAt: nowIsoSync(), }); yield* dispatchNormalizedCommand(stopCommand); diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index c19bdbf4565..b86bbf1f16c 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -3,21 +3,7 @@ "compilerOptions": { "composite": true, "types": ["node", "bun"], - "lib": ["ESNext", "esnext.disposable"], - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "warning", - "instanceOfSchema": "warning", - "deterministicKeys": "warning", - "preferSchemaOverJson": "off", - "globalErrorInEffectFailure": "off" - } - } - ] + "lib": ["ESNext", "esnext.disposable"] }, "include": ["src", "tsdown.config.ts", "scripts", "integration", "../../scripts/lib"] } diff --git a/apps/web/src/components/BranchToolbar.logic.ts b/apps/web/src/components/BranchToolbar.logic.ts index 31f614a2e2c..65388962c08 100644 --- a/apps/web/src/components/BranchToolbar.logic.ts +++ b/apps/web/src/components/BranchToolbar.logic.ts @@ -1,5 +1,5 @@ import type { EnvironmentId, VcsRef, ProjectId } from "@t3tools/contracts"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; export { dedupeRemoteBranchesWithLocalMatches, deriveLocalBranchNameFromRemoteRef, diff --git a/apps/web/src/components/ChatView.browser.tsx b/apps/web/src/components/ChatView.browser.tsx index 578dc7c045d..17cf014c225 100644 --- a/apps/web/src/components/ChatView.browser.tsx +++ b/apps/web/src/components/ChatView.browser.tsx @@ -22,7 +22,7 @@ import { import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime"; import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model"; import { RouterProvider, createMemoryHistory } from "@tanstack/react-router"; -import { Option } from "effect"; +import * as Option from "effect/Option"; import { HttpResponse, http, ws } from "msw"; import { setupWorker } from "msw/browser"; import { page } from "vitest/browser"; diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index 417313ef2c1..bf87add28d9 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -10,7 +10,7 @@ import { } from "@t3tools/contracts"; import { type ChatMessage, type SessionPhase, type Thread, type ThreadSession } from "../types"; import { type ComposerImageAttachment, type DraftThreadState } from "../composerDraftStore"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import { selectThreadByRef, useStore } from "../store"; import { filterTerminalContextsWithText, diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 027688a2848..1b3936ec7e5 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -13,7 +13,7 @@ import { } from "@t3tools/contracts"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useNavigate, useParams } from "@tanstack/react-router"; -import { Option } from "effect"; +import * as Option from "effect/Option"; import { ArrowDownIcon, ArrowLeftIcon, diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 01b84bd94a5..d8c5cdd3fa1 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -12,7 +12,7 @@ import type { } from "@t3tools/contracts"; import { useIsMutating, useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "@tanstack/react-router"; -import { Option } from "effect"; +import * as Option from "effect/Option"; import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from "react"; import { flushSync } from "react-dom"; import { diff --git a/apps/web/src/components/settings/ConnectionsSettings.tsx b/apps/web/src/components/settings/ConnectionsSettings.tsx index 98c02d92f64..b3dea6c3115 100644 --- a/apps/web/src/components/settings/ConnectionsSettings.tsx +++ b/apps/web/src/components/settings/ConnectionsSettings.tsx @@ -17,7 +17,7 @@ import { type DesktopServerExposureState, type EnvironmentId, } from "@t3tools/contracts"; -import { DateTime } from "effect"; +import * as DateTime from "effect/DateTime"; import { useCopyToClipboard } from "../../hooks/useCopyToClipboard"; import { cn } from "../../lib/utils"; diff --git a/apps/web/src/components/settings/DiagnosticsSettings.tsx b/apps/web/src/components/settings/DiagnosticsSettings.tsx index bd590a1242c..394c957166c 100644 --- a/apps/web/src/components/settings/DiagnosticsSettings.tsx +++ b/apps/web/src/components/settings/DiagnosticsSettings.tsx @@ -9,7 +9,8 @@ import { } from "lucide-react"; import { useCallback, useMemo, useState, type ReactNode } from "react"; import type { ServerProcessDiagnosticsEntry, ServerProcessSignal } from "@t3tools/contracts"; -import { DateTime, Option } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Option from "effect/Option"; import { ensureLocalApi } from "../../localApi"; import { cn } from "../../lib/utils"; diff --git a/apps/web/src/components/settings/ProviderSettingsForm.tsx b/apps/web/src/components/settings/ProviderSettingsForm.tsx index 244fa9f31ca..a417a942cc6 100644 --- a/apps/web/src/components/settings/ProviderSettingsForm.tsx +++ b/apps/web/src/components/settings/ProviderSettingsForm.tsx @@ -1,7 +1,7 @@ "use client"; import { useMemo, type ReactNode } from "react"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import type { ProviderSettingsFormAnnotation, ProviderSettingsFormControl, diff --git a/apps/web/src/components/settings/SettingsPanels.browser.tsx b/apps/web/src/components/settings/SettingsPanels.browser.tsx index 9e3d52e89c9..1b3c2aec5c6 100644 --- a/apps/web/src/components/settings/SettingsPanels.browser.tsx +++ b/apps/web/src/components/settings/SettingsPanels.browser.tsx @@ -16,7 +16,8 @@ import { type ServerProvider, type SourceControlDiscoveryResult, } from "@t3tools/contracts"; -import { DateTime, Option } from "effect"; +import * as DateTime from "effect/DateTime"; +import * as Option from "effect/Option"; import { page } from "vitest/browser"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { render } from "vitest-browser-react"; diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 3c0a25d18b0..20d3b1040a8 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -14,7 +14,7 @@ import { import { scopeThreadRef } from "@t3tools/client-runtime"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; import { createModelSelection } from "@t3tools/shared/model"; -import { Equal } from "effect"; +import * as Equal from "effect/Equal"; import { APP_VERSION } from "../../branding"; import { canCheckForUpdate, diff --git a/apps/web/src/components/settings/SourceControlSettings.tsx b/apps/web/src/components/settings/SourceControlSettings.tsx index c58a4468363..0518848bc58 100644 --- a/apps/web/src/components/settings/SourceControlSettings.tsx +++ b/apps/web/src/components/settings/SourceControlSettings.tsx @@ -1,5 +1,5 @@ import { GitPullRequestIcon, RefreshCwIcon } from "lucide-react"; -import { Option } from "effect"; +import * as Option from "effect/Option"; import { type ReactNode } from "react"; import type { SourceControlProviderKind, diff --git a/apps/web/src/components/settings/providerDriverMeta.ts b/apps/web/src/components/settings/providerDriverMeta.ts index ccdfd9a64d8..8d3d7482f62 100644 --- a/apps/web/src/components/settings/providerDriverMeta.ts +++ b/apps/web/src/components/settings/providerDriverMeta.ts @@ -5,7 +5,7 @@ import { OpenCodeSettings, ProviderDriverKind, } from "@t3tools/contracts"; -import type { Schema } from "effect"; +import type * as Schema from "effect/Schema"; import { ClaudeAI, CursorIcon, type Icon, OpenAI, OpenCodeIcon } from "../Icons"; type ProviderSettingsSchema = { diff --git a/apps/web/src/components/ui/sidebar.tsx b/apps/web/src/components/ui/sidebar.tsx index 32206be7080..278df12fc23 100644 --- a/apps/web/src/components/ui/sidebar.tsx +++ b/apps/web/src/components/ui/sidebar.tsx @@ -19,7 +19,7 @@ import { Skeleton } from "~/components/ui/skeleton"; import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; import { useIsMobile } from "~/hooks/useMediaQuery"; import { getLocalStorageItem, setLocalStorageItem } from "~/hooks/useLocalStorage"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; const SIDEBAR_COOKIE_NAME = "sidebar_state"; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; diff --git a/apps/web/src/environments/primary/auth.ts b/apps/web/src/environments/primary/auth.ts index 93fcdd5fcbb..9a7e39a0207 100644 --- a/apps/web/src/environments/primary/auth.ts +++ b/apps/web/src/environments/primary/auth.ts @@ -16,7 +16,8 @@ import { } from "../../pairingUrl"; import { resolvePrimaryEnvironmentHttpUrl } from "./target"; -import { Data, Predicate } from "effect"; +import * as Data from "effect/Data"; +import * as Predicate from "effect/Predicate"; export class BootstrapHttpError extends Data.TaggedError("BootstrapHttpError")<{ readonly message: string; diff --git a/apps/web/src/hooks/useSettings.ts b/apps/web/src/hooks/useSettings.ts index 6541bb7a822..005c8ad82fc 100644 --- a/apps/web/src/hooks/useSettings.ts +++ b/apps/web/src/hooks/useSettings.ts @@ -19,7 +19,7 @@ import { UnifiedSettings, } from "@t3tools/contracts/settings"; import { ensureLocalApi } from "~/localApi"; -import { Struct } from "effect"; +import * as Struct from "effect/Struct"; import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; import { applySettingsUpdated, getServerConfig, useServerSettings } from "~/rpc/serverState"; diff --git a/apps/web/src/lib/gitStatusState.ts b/apps/web/src/lib/gitStatusState.ts index 4304dcc4aab..4174c97c797 100644 --- a/apps/web/src/lib/gitStatusState.ts +++ b/apps/web/src/lib/gitStatusState.ts @@ -4,7 +4,7 @@ import { type GitManagerServiceError, type VcsStatusResult, } from "@t3tools/contracts"; -import { Cause } from "effect"; +import * as Cause from "effect/Cause"; import { Atom } from "effect/unstable/reactivity"; import { useEffect } from "react"; diff --git a/apps/web/src/lib/processDiagnosticsState.ts b/apps/web/src/lib/processDiagnosticsState.ts index 17d13dcf336..b2cce4861e3 100644 --- a/apps/web/src/lib/processDiagnosticsState.ts +++ b/apps/web/src/lib/processDiagnosticsState.ts @@ -1,6 +1,8 @@ import { useAtomValue } from "@effect/atom-react"; import type { ServerProcessDiagnosticsResult } from "@t3tools/contracts"; -import { Cause, Effect, Option } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; import { AsyncResult, Atom } from "effect/unstable/reactivity"; import { useCallback } from "react"; diff --git a/apps/web/src/lib/projectScriptKeybindings.ts b/apps/web/src/lib/projectScriptKeybindings.ts index 4ea80cf824f..7877c3cd133 100644 --- a/apps/web/src/lib/projectScriptKeybindings.ts +++ b/apps/web/src/lib/projectScriptKeybindings.ts @@ -4,7 +4,7 @@ import { type KeybindingRule, type ResolvedKeybindingsConfig, } from "@t3tools/contracts"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; export const PROJECT_SCRIPT_KEYBINDING_INVALID_MESSAGE = "Invalid keybinding."; diff --git a/apps/web/src/lib/providerReactQuery.ts b/apps/web/src/lib/providerReactQuery.ts index 663d618b92e..f404724f68f 100644 --- a/apps/web/src/lib/providerReactQuery.ts +++ b/apps/web/src/lib/providerReactQuery.ts @@ -5,7 +5,8 @@ import { ThreadId, } from "@t3tools/contracts"; import { queryOptions } from "@tanstack/react-query"; -import { Option, Schema } from "effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; import { ensureEnvironmentApi } from "../environmentApi"; interface CheckpointDiffQueryInput { diff --git a/apps/web/src/lib/sourceControlDiscoveryState.ts b/apps/web/src/lib/sourceControlDiscoveryState.ts index 911517fb4e5..daff900a0d5 100644 --- a/apps/web/src/lib/sourceControlDiscoveryState.ts +++ b/apps/web/src/lib/sourceControlDiscoveryState.ts @@ -7,7 +7,7 @@ import { sourceControlDiscoveryStateAtom, } from "@t3tools/client-runtime"; import { EnvironmentId, type SourceControlDiscoveryResult } from "@t3tools/contracts"; -import { Effect } from "effect"; +import * as Effect from "effect/Effect"; import { Atom } from "effect/unstable/reactivity"; import { readPrimaryEnvironmentDescriptor } from "../environments/primary"; diff --git a/apps/web/src/lib/traceDiagnosticsState.ts b/apps/web/src/lib/traceDiagnosticsState.ts index bfe1b3a83fa..73d9a6c3949 100644 --- a/apps/web/src/lib/traceDiagnosticsState.ts +++ b/apps/web/src/lib/traceDiagnosticsState.ts @@ -1,6 +1,8 @@ import { useAtomValue } from "@effect/atom-react"; import type { ServerTraceDiagnosticsResult } from "@t3tools/contracts"; -import { Cause, Effect, Option } from "effect"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; import { AsyncResult, Atom } from "effect/unstable/reactivity"; import { useCallback } from "react"; diff --git a/apps/web/src/observability/clientTracing.ts b/apps/web/src/observability/clientTracing.ts index f9eae4b5a19..c0d104ac517 100644 --- a/apps/web/src/observability/clientTracing.ts +++ b/apps/web/src/observability/clientTracing.ts @@ -1,4 +1,8 @@ -import { Exit, Layer, ManagedRuntime, Scope, Tracer } from "effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as Scope from "effect/Scope"; +import * as Tracer from "effect/Tracer"; import { FetchHttpClient, HttpClient } from "effect/unstable/http"; import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; diff --git a/apps/web/src/projectScripts.ts b/apps/web/src/projectScripts.ts index 1cb3a6c7582..50f7cef039c 100644 --- a/apps/web/src/projectScripts.ts +++ b/apps/web/src/projectScripts.ts @@ -4,7 +4,7 @@ import { type KeybindingCommand, type ProjectScript, } from "@t3tools/contracts"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; function normalizeScriptId(value: string): string { const cleaned = value diff --git a/apps/web/src/rpc/protocol.ts b/apps/web/src/rpc/protocol.ts index 3c52764a0aa..eb5caf2f7ab 100644 --- a/apps/web/src/rpc/protocol.ts +++ b/apps/web/src/rpc/protocol.ts @@ -1,5 +1,8 @@ import { WsRpcGroup } from "@t3tools/contracts"; -import { Duration, Effect, Layer, Schedule } from "effect"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schedule from "effect/Schedule"; import { RpcClient, RpcSerialization } from "effect/unstable/rpc"; import * as Socket from "effect/unstable/socket/Socket"; diff --git a/apps/web/src/rpc/wsRpcClient.ts b/apps/web/src/rpc/wsRpcClient.ts index ca56b6143c0..b36ffb0ab2e 100644 --- a/apps/web/src/rpc/wsRpcClient.ts +++ b/apps/web/src/rpc/wsRpcClient.ts @@ -10,7 +10,8 @@ import { WS_METHODS, } from "@t3tools/contracts"; import { applyGitStatusStreamEvent } from "@t3tools/shared/git"; -import { Effect, Stream } from "effect"; +import * as Effect from "effect/Effect"; +import * as Stream from "effect/Stream"; import { type WsRpcProtocolClient } from "./protocol"; import { resetWsReconnectBackoff } from "./wsConnectionState"; diff --git a/apps/web/src/rpc/wsTransport.test.ts b/apps/web/src/rpc/wsTransport.test.ts index 84d886668fb..6e69acbb55b 100644 --- a/apps/web/src/rpc/wsTransport.test.ts +++ b/apps/web/src/rpc/wsTransport.test.ts @@ -1,5 +1,5 @@ import { DEFAULT_SERVER_SETTINGS, WS_METHODS } from "@t3tools/contracts"; -import { Stream } from "effect"; +import * as Stream from "effect/Stream"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { diff --git a/apps/web/src/rpc/wsTransport.ts b/apps/web/src/rpc/wsTransport.ts index 316be9db9ee..9e8cc821364 100644 --- a/apps/web/src/rpc/wsTransport.ts +++ b/apps/web/src/rpc/wsTransport.ts @@ -1,14 +1,12 @@ -import { - Cause, - Duration, - Effect, - Exit, - Layer, - ManagedRuntime, - Option, - Scope, - Stream, -} from "effect"; +import * as Cause from "effect/Cause"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as ManagedRuntime from "effect/ManagedRuntime"; +import * as Option from "effect/Option"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { RpcClient } from "effect/unstable/rpc"; import { ClientTracingLive } from "../observability/clientTracing"; diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts index 921054df34f..60345e52284 100644 --- a/apps/web/src/store.ts +++ b/apps/web/src/store.ts @@ -20,7 +20,7 @@ import type { } from "@t3tools/contracts"; import { isProviderDriverKind, ProviderDriverKind } from "@t3tools/contracts"; import type { ThreadId, TurnId } from "@t3tools/contracts"; -import { Schema } from "effect"; +import * as Schema from "effect/Schema"; import { resolveModelSlugForProvider } from "@t3tools/shared/model"; import { create } from "zustand"; import { diff --git a/apps/web/test/wsRpcHarness.ts b/apps/web/test/wsRpcHarness.ts index 07d4c74050c..2609464c1d7 100644 --- a/apps/web/test/wsRpcHarness.ts +++ b/apps/web/test/wsRpcHarness.ts @@ -1,5 +1,9 @@ -import { Effect, Exit, PubSub, Scope, Stream } from "effect"; import { ORCHESTRATION_WS_METHODS, WS_METHODS, WsRpcGroup } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as PubSub from "effect/PubSub"; +import * as Scope from "effect/Scope"; +import * as Stream from "effect/Stream"; import { RpcMessage, RpcSerialization, RpcServer } from "effect/unstable/rpc"; type RpcServerInstance = RpcServer.RpcServer; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index c721aa85f99..033144d714b 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -15,12 +15,22 @@ "plugins": [ { "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node"], + "namespaceImportPackages": ["@effect/platform-node", "effect"], "diagnosticSeverity": { "importFromBarrel": "error", "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" + "unsafeEffectTypeAssertion": "error", + "instanceOfSchema": "error", + "deterministicKeys": "error", + "strictEffectProvide": "off", + "missingEffectServiceDependency": "error", + "leakingRequirements": "error", + "globalErrorInEffectCatch": "error", + "globalErrorInEffectFailure": "error", + "unknownInEffectCatch": "error", + "strictBooleanExpressions": "off", + "preferSchemaOverJson": "error", + "schemaSyncInEffect": "error" } } ] diff --git a/bun.lock b/bun.lock index ae9812f6632..94bb7e336b1 100644 --- a/bun.lock +++ b/bun.lock @@ -201,6 +201,7 @@ }, "devDependencies": { "@effect/language-service": "catalog:", + "@effect/platform-node": "catalog:", "@effect/vitest": "catalog:", "@types/node": "catalog:", "typescript": "catalog:", diff --git a/packages/client-runtime/tsconfig.json b/packages/client-runtime/tsconfig.json index bd559329ef3..73a306f847a 100644 --- a/packages/client-runtime/tsconfig.json +++ b/packages/client-runtime/tsconfig.json @@ -1,18 +1,5 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node", "effect"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] - }, + "compilerOptions": {}, "include": ["src"] } diff --git a/packages/contracts/src/model.ts b/packages/contracts/src/model.ts index 85ee815818e..8e7daaa0c79 100644 --- a/packages/contracts/src/model.ts +++ b/packages/contracts/src/model.ts @@ -99,10 +99,10 @@ function coerceLegacyOptionsObjectToArray( const entries: Array = []; for (const [rawKey, rawValue] of Object.entries(record)) { const id = typeof rawKey === "string" ? rawKey.trim() : ""; - if (!id) continue; + if (id.length === 0) continue; if (typeof rawValue === "string") { const trimmed = rawValue.trim(); - if (trimmed) entries.push({ id, value: trimmed }); + if (trimmed.length > 0) entries.push({ id, value: trimmed }); } else if (typeof rawValue === "boolean") { entries.push({ id, value: rawValue }); } diff --git a/packages/contracts/src/terminal.test.ts b/packages/contracts/src/terminal.test.ts index 2401e38d4e0..1f96570acf8 100644 --- a/packages/contracts/src/terminal.test.ts +++ b/packages/contracts/src/terminal.test.ts @@ -164,6 +164,8 @@ describe("TerminalCloseInput", () => { }); describe("TerminalSessionSnapshot", () => { + const isoTimestamp = "2026-01-01T00:00:00.000Z"; + it("accepts running snapshots", () => { expect( decodes(TerminalSessionSnapshot, { @@ -176,20 +178,22 @@ describe("TerminalSessionSnapshot", () => { history: "hello\n", exitCode: null, exitSignal: null, - updatedAt: new Date().toISOString(), + updatedAt: isoTimestamp, }), ).toBe(true); }); }); describe("TerminalEvent", () => { + const isoTimestamp = "2026-01-01T00:00:00.000Z"; + it("accepts output events", () => { expect( decodes(TerminalEvent, { type: "output", threadId: "thread-1", terminalId: DEFAULT_TERMINAL_ID, - createdAt: new Date().toISOString(), + createdAt: isoTimestamp, data: "line\n", }), ).toBe(true); @@ -201,7 +205,7 @@ describe("TerminalEvent", () => { type: "exited", threadId: "thread-1", terminalId: DEFAULT_TERMINAL_ID, - createdAt: new Date().toISOString(), + createdAt: isoTimestamp, exitCode: 0, exitSignal: null, }), @@ -214,7 +218,7 @@ describe("TerminalEvent", () => { type: "activity", threadId: "thread-1", terminalId: DEFAULT_TERMINAL_ID, - createdAt: new Date().toISOString(), + createdAt: isoTimestamp, hasRunningSubprocess: true, }), ).toBe(true); @@ -226,7 +230,7 @@ describe("TerminalEvent", () => { type: "started", threadId: "thread-1", terminalId: DEFAULT_TERMINAL_ID, - createdAt: new Date().toISOString(), + createdAt: isoTimestamp, snapshot: { threadId: "thread-1", terminalId: DEFAULT_TERMINAL_ID, @@ -237,7 +241,7 @@ describe("TerminalEvent", () => { history: "", exitCode: null, exitSignal: null, - updatedAt: new Date().toISOString(), + updatedAt: isoTimestamp, }, }), ).toBe(true); diff --git a/packages/contracts/src/terminal.ts b/packages/contracts/src/terminal.ts index b91e4cb89e0..a26de01099c 100644 --- a/packages/contracts/src/terminal.ts +++ b/packages/contracts/src/terminal.ts @@ -170,10 +170,13 @@ export class TerminalCwdError extends Schema.TaggedErrorClass( return `Terminal cwd does not exist: ${this.cwd}`; } const causeMessage = - this.cause && typeof this.cause === "object" && "message" in this.cause + this.cause !== undefined && + this.cause !== null && + typeof this.cause === "object" && + "message" in this.cause ? this.cause.message : undefined; - return causeMessage + return typeof causeMessage === "string" && causeMessage.length > 0 ? `Failed to access terminal cwd: ${this.cwd} (${causeMessage})` : `Failed to access terminal cwd: ${this.cwd}`; } diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json index bd559329ef3..73a306f847a 100644 --- a/packages/contracts/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -1,18 +1,5 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node", "effect"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] - }, + "compilerOptions": {}, "include": ["src"] } diff --git a/packages/effect-acp/test/examples/cursor-acp-client.example.ts b/packages/effect-acp/test/examples/cursor-acp-client.example.ts index 929ed62640e..f730c3dbde0 100644 --- a/packages/effect-acp/test/examples/cursor-acp-client.example.ts +++ b/packages/effect-acp/test/examples/cursor-acp-client.example.ts @@ -48,7 +48,7 @@ const program = Effect.gen(function* () { version: "0.0.0", }, }); - yield* Console.log("initialized", JSON.stringify(initialized, null, 4)); + yield* Console.log("initialized", initialized); const session = yield* acp.agent.createSession({ cwd: process.cwd(), @@ -61,7 +61,7 @@ const program = Effect.gen(function* () { value: "claude-opus-4-6", }); - yield* Console.log("config", JSON.stringify(config, null, 4)); + yield* Console.log("config", config); const result = yield* acp.agent.prompt({ sessionId: session.sessionId, @@ -73,7 +73,7 @@ const program = Effect.gen(function* () { ], }); - yield* Console.log("prompt result", JSON.stringify(result)); + yield* Console.log("prompt result", result); yield* acp.agent.cancel({ sessionId: session.sessionId }); }).pipe(Effect.provide(acpLayer)); }); diff --git a/packages/effect-acp/tsconfig.json b/packages/effect-acp/tsconfig.json index b66569ba110..04f1d37800f 100644 --- a/packages/effect-acp/tsconfig.json +++ b/packages/effect-acp/tsconfig.json @@ -1,18 +1,5 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node", "effect"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] - }, + "compilerOptions": {}, "include": ["src", "scripts", "test"] } diff --git a/packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts b/packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts index a727c2ab037..b6383c0ab7d 100644 --- a/packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts +++ b/packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts @@ -45,17 +45,17 @@ const program = Effect.gen(function* () { optOutNotificationMethods: null, }, }); - yield* Console.log("initialize", JSON.stringify(initialized, null, 2)); + yield* Console.log("initialize", initialized); yield* client.notify("initialized", undefined); const account = yield* client.request("account/read", {}); - yield* Console.log("account/read", JSON.stringify(account, null, 2)); + yield* Console.log("account/read", account); const skills = yield* client.request("skills/list", { cwds: [process.cwd()], }); - yield* Console.log("skills/list", JSON.stringify(skills, null, 2)); + yield* Console.log("skills/list", skills); }).pipe(Effect.provide(codexLayer)); }); diff --git a/packages/effect-codex-app-server/tsconfig.json b/packages/effect-codex-app-server/tsconfig.json index b66569ba110..04f1d37800f 100644 --- a/packages/effect-codex-app-server/tsconfig.json +++ b/packages/effect-codex-app-server/tsconfig.json @@ -1,18 +1,5 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node", "effect"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] - }, + "compilerOptions": {}, "include": ["src", "scripts", "test"] } diff --git a/packages/shared/package.json b/packages/shared/package.json index 5e785efc4d7..f410a98b381 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -95,6 +95,7 @@ }, "devDependencies": { "@effect/language-service": "catalog:", + "@effect/platform-node": "catalog:", "@effect/vitest": "catalog:", "@types/node": "catalog:", "typescript": "catalog:", diff --git a/packages/shared/src/logging.ts b/packages/shared/src/logging.ts index 98fee9464c7..8e1d1019e1d 100644 --- a/packages/shared/src/logging.ts +++ b/packages/shared/src/logging.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import fs from "node:fs"; import path from "node:path"; diff --git a/packages/shared/src/observability.test.ts b/packages/shared/src/observability.test.ts index 644b9cf0e14..efde31639aa 100644 --- a/packages/shared/src/observability.test.ts +++ b/packages/shared/src/observability.test.ts @@ -1,11 +1,10 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; - import { assert, describe, it } from "@effect/vitest"; +import * as NodeServices from "@effect/platform-node/NodeServices"; import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Logger from "effect/Logger"; +import * as Path from "effect/Path"; import * as References from "effect/References"; import * as Schema from "effect/Schema"; import * as Tracer from "effect/Tracer"; @@ -57,13 +56,14 @@ const makeRecord = (name: string, suffix = ""): TraceRecord => ({ }, }); -const readTraceRecords = (tracePath: string) => - fs - .readFileSync(tracePath, "utf8") +const readTraceRecords = Effect.fn("readTraceRecords")(function* (tracePath: string) { + const fileSystem = yield* FileSystem.FileSystem; + return (yield* fileSystem.readFileString(tracePath)) .trim() .split("\n") .filter((line) => line.length > 0) .map((line) => decodeTraceRecordLine(line)); +}); const makeTestLayer = (tracePath: string) => Layer.mergeAll( @@ -106,9 +106,11 @@ describe("observability", () => { }); it("normalizes invalid dates without throwing", () => { + // @effect-diagnostics-next-line globalDate:off + const invalidDate = new Date("not-a-real-date"); assert.deepStrictEqual( compactTraceAttributes({ - invalidDate: new Date("not-a-real-date"), + invalidDate, }), { invalidDate: "Invalid Date", @@ -119,122 +121,116 @@ describe("observability", () => { it.effect("flushes buffered trace records on close", () => Effect.scoped( Effect.gen(function* () { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-trace-sink-")); + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-trace-sink-" }); const tracePath = path.join(tempDir, "shared.trace.ndjson"); - try { - const sink = yield* makeTraceSink({ - filePath: tracePath, - maxBytes: 1024, - maxFiles: 2, - batchWindowMs: 10_000, - }); - - sink.push(makeRecord("alpha")); - sink.push(makeRecord("beta")); - yield* sink.close(); - - const lines = readTraceRecords(tracePath); - - assert.equal(lines.length, 2); - assert.equal(lines[0]?.name, "alpha"); - assert.equal(lines[1]?.name, "beta"); - } finally { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }), + const sink = yield* makeTraceSink({ + filePath: tracePath, + maxBytes: 1024, + maxFiles: 2, + batchWindowMs: 10_000, + }); + + sink.push(makeRecord("alpha")); + sink.push(makeRecord("beta")); + yield* sink.close(); + + const lines = yield* readTraceRecords(tracePath); + + assert.equal(lines.length, 2); + assert.equal(lines[0]?.name, "alpha"); + assert.equal(lines[1]?.name, "beta"); + }).pipe(Effect.provide(NodeServices.layer)), ), ); it.effect("rotates the trace file when the configured max size is exceeded", () => Effect.scoped( Effect.gen(function* () { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-trace-sink-")); + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-trace-sink-" }); const tracePath = path.join(tempDir, "shared.trace.ndjson"); - try { - const sink = yield* makeTraceSink({ - filePath: tracePath, - maxBytes: 180, - maxFiles: 2, - batchWindowMs: 10_000, - }); - - for (let index = 0; index < 8; index += 1) { - sink.push(makeRecord("rotate", `${index}-${"x".repeat(48)}`)); - yield* sink.flush; - } - yield* sink.close(); - - const matchingFiles = fs - .readdirSync(tempDir) - .filter( - (entry) => - entry === "shared.trace.ndjson" || entry.startsWith("shared.trace.ndjson."), - ) - .toSorted(); - - assert.equal( - matchingFiles.some((entry) => entry === "shared.trace.ndjson.1"), - true, - ); - assert.equal( - matchingFiles.some((entry) => entry === "shared.trace.ndjson.3"), - false, - ); - } finally { - fs.rmSync(tempDir, { recursive: true, force: true }); + const sink = yield* makeTraceSink({ + filePath: tracePath, + maxBytes: 180, + maxFiles: 2, + batchWindowMs: 10_000, + }); + + for (let index = 0; index < 8; index += 1) { + sink.push(makeRecord("rotate", `${index}-${"x".repeat(48)}`)); + yield* sink.flush; } - }), + yield* sink.close(); + + const matchingFiles = (yield* fileSystem.readDirectory(tempDir)) + .filter( + (entry) => entry === "shared.trace.ndjson" || entry.startsWith("shared.trace.ndjson."), + ) + .toSorted(); + + assert.equal( + matchingFiles.some((entry) => entry === "shared.trace.ndjson.1"), + true, + ); + assert.equal( + matchingFiles.some((entry) => entry === "shared.trace.ndjson.3"), + false, + ); + }).pipe(Effect.provide(NodeServices.layer)), ), ); it.effect("drops only the invalid trace record when serialization fails", () => Effect.scoped( Effect.gen(function* () { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-trace-sink-")); + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-trace-sink-" }); const tracePath = path.join(tempDir, "shared.trace.ndjson"); - try { - const sink = yield* makeTraceSink({ - filePath: tracePath, - maxBytes: 1024, - maxFiles: 2, - batchWindowMs: 10_000, - }); - - const circular: Array = []; - circular.push(circular); - - sink.push(makeRecord("alpha")); - sink.push({ - ...makeRecord("invalid"), - attributes: { - circular, - }, - } as TraceRecord); - sink.push(makeRecord("beta")); - yield* sink.close(); - - const lines = readTraceRecords(tracePath); - - assert.deepStrictEqual( - lines.map((line) => line.name), - ["alpha", "beta"], - ); - } finally { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }), + const sink = yield* makeTraceSink({ + filePath: tracePath, + maxBytes: 1024, + maxFiles: 2, + batchWindowMs: 10_000, + }); + + const circular: Array = []; + circular.push(circular); + + sink.push(makeRecord("alpha")); + sink.push({ + ...makeRecord("invalid"), + attributes: { + circular, + }, + } as TraceRecord); + sink.push(makeRecord("beta")); + yield* sink.close(); + + const lines = yield* readTraceRecords(tracePath); + + assert.deepStrictEqual( + lines.map((line) => line.name), + ["alpha", "beta"], + ); + }).pipe(Effect.provide(NodeServices.layer)), ), ); it.effect("writes nested spans to disk and captures log messages as span events", () => - Effect.gen(function* () { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-local-tracer-")); - const tracePath = path.join(tempDir, "shared.trace.ndjson"); + Effect.scoped( + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-local-tracer-" }); + const tracePath = path.join(tempDir, "shared.trace.ndjson"); - try { yield* Effect.scoped( Effect.gen(function* () { const program = Effect.gen(function* () { @@ -254,7 +250,7 @@ describe("observability", () => { }), ); - const records = readTraceRecords(tracePath); + const records = yield* readTraceRecords(tracePath); assert.equal(records.length, 2); const parent = records.find((record) => record.name === "parent-span"); @@ -281,18 +277,18 @@ describe("observability", () => { child.events.some((event) => event.attributes["effect.logLevel"] === "INFO"), true, ); - } finally { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }), + }).pipe(Effect.provide(NodeServices.layer)), + ), ); it.effect("serializes interrupted spans with an interrupted exit status", () => - Effect.gen(function* () { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-local-tracer-")); - const tracePath = path.join(tempDir, "shared.trace.ndjson"); + Effect.scoped( + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-local-tracer-" }); + const tracePath = path.join(tempDir, "shared.trace.ndjson"); - try { yield* Effect.scoped( Effect.exit( Effect.interrupt.pipe( @@ -302,13 +298,11 @@ describe("observability", () => { ), ); - const records = readTraceRecords(tracePath); + const records = yield* readTraceRecords(tracePath); assert.equal(records.length, 1); assert.equal(records[0]?.name, "interrupt-span"); assert.equal(records[0]?.exit?._tag, "Interrupted"); - } finally { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - }), + }).pipe(Effect.provide(NodeServices.layer)), + ), ); }); diff --git a/packages/shared/src/shell.ts b/packages/shared/src/shell.ts index 9b833feac64..0572aaf989a 100644 --- a/packages/shared/src/shell.ts +++ b/packages/shared/src/shell.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import * as NodeOS from "node:os"; import { execFileSync } from "node:child_process"; import { accessSync, constants, statSync } from "node:fs"; diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index bd559329ef3..73a306f847a 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -1,18 +1,5 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node", "effect"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] - }, + "compilerOptions": {}, "include": ["src"] } diff --git a/packages/ssh/tsconfig.json b/packages/ssh/tsconfig.json index bd559329ef3..73a306f847a 100644 --- a/packages/ssh/tsconfig.json +++ b/packages/ssh/tsconfig.json @@ -1,18 +1,5 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "plugins": [ - { - "name": "@effect/language-service", - "namespaceImportPackages": ["@effect/platform-node", "effect"], - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "error", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] - }, + "compilerOptions": {}, "include": ["src"] } diff --git a/packages/tailscale/tsconfig.json b/packages/tailscale/tsconfig.json index 2f8e6d4df6d..73a306f847a 100644 --- a/packages/tailscale/tsconfig.json +++ b/packages/tailscale/tsconfig.json @@ -1,17 +1,5 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "plugins": [ - { - "name": "@effect/language-service", - "diagnosticSeverity": { - "importFromBarrel": "error", - "anyUnknownInErrorContext": "warning", - "instanceOfSchema": "warning", - "deterministicKeys": "warning" - } - } - ] - }, + "compilerOptions": {}, "include": ["src"] } diff --git a/scripts/apply-web-brand-assets.ts b/scripts/apply-web-brand-assets.ts index 1bbdd237c22..4ca293f3e27 100644 --- a/scripts/apply-web-brand-assets.ts +++ b/scripts/apply-web-brand-assets.ts @@ -2,7 +2,10 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Effect, FileSystem, Option, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; import { Argument, Command } from "effect/unstable/cli"; import { resolveWebIconOverrides, type WebAssetBrand } from "./lib/brand-assets.ts"; diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index 86452693d09..0cdf0905045 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -1,6 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; -import { ConfigProvider, Effect, Option } from "effect"; +import * as ConfigProvider from "effect/ConfigProvider"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; import { resolveBuildOptions, diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 74e8bed0cb8..f395e08f35b 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -10,18 +10,16 @@ import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { - Config, - Data, - Effect, - FileSystem, - Layer, - Logger, - Option, - Path, - Schema, - Stream, -} from "effect"; +import * as Config from "effect/Config"; +import * as Data from "effect/Data"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; import { Command, Flag } from "effect/unstable/cli"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; diff --git a/scripts/dev-runner.test.ts b/scripts/dev-runner.test.ts index ce4865ecede..724b328c4c6 100644 --- a/scripts/dev-runner.test.ts +++ b/scripts/dev-runner.test.ts @@ -1,7 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import * as NodeOS from "node:os"; import { assert, describe, it } from "@effect/vitest"; -import { Effect, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as Path from "effect/Path"; import { checkPortAvailabilityOnHosts, diff --git a/scripts/dev-runner.ts b/scripts/dev-runner.ts index 1621b60da73..78580e95eeb 100644 --- a/scripts/dev-runner.ts +++ b/scripts/dev-runner.ts @@ -5,7 +5,15 @@ import * as NodeOS from "node:os"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { NetService } from "@t3tools/shared/Net"; -import { Config, Data, Effect, Hash, Layer, Logger, Option, Path, Schema } from "effect"; +import * as Config from "effect/Config"; +import * as Data from "effect/Data"; +import * as Effect from "effect/Effect"; +import * as Hash from "effect/Hash"; +import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { Argument, Command, Flag } from "effect/unstable/cli"; import { ChildProcess } from "effect/unstable/process"; diff --git a/scripts/merge-update-manifests.test.ts b/scripts/merge-update-manifests.test.ts index 3f2e3b08713..32431b81d33 100644 --- a/scripts/merge-update-manifests.test.ts +++ b/scripts/merge-update-manifests.test.ts @@ -1,6 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; -import { Effect, FileSystem, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import { Command, CliError } from "effect/unstable/cli"; import { diff --git a/scripts/merge-update-manifests.ts b/scripts/merge-update-manifests.ts index 1913cd7113f..f7ba30f8a30 100644 --- a/scripts/merge-update-manifests.ts +++ b/scripts/merge-update-manifests.ts @@ -2,7 +2,11 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Effect, FileSystem, Option, Path, Schema } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { Argument, Command, Flag } from "effect/unstable/cli"; import { diff --git a/scripts/mock-update-server.test.ts b/scripts/mock-update-server.test.ts index 218dcd224f4..747a21c68b4 100644 --- a/scripts/mock-update-server.test.ts +++ b/scripts/mock-update-server.test.ts @@ -1,7 +1,10 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { NodeHttpServer } from "@effect/platform-node"; +import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; import { assert, it } from "@effect/vitest"; -import { Effect, FileSystem, Layer, Path } from "effect"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { HttpClient, HttpRouter } from "effect/unstable/http"; import { makeMockUpdateRouteLayer } from "./mock-update-server.ts"; diff --git a/scripts/mock-update-server.ts b/scripts/mock-update-server.ts index 8062f01b12f..27715b5c5ad 100644 --- a/scripts/mock-update-server.ts +++ b/scripts/mock-update-server.ts @@ -1,8 +1,14 @@ +// @effect-diagnostics-next-line nodeBuiltinImport:off - NodeHttpServer.layer takes `NodeHttp.createServer` as arg import * as NodeHttp from "node:http"; -import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"; +import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; +import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Config, Effect, FileSystem, Layer, Path } from "effect"; +import * as Config from "effect/Config"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; interface MockUpdateServerConfig { diff --git a/scripts/notify-discord-release.ts b/scripts/notify-discord-release.ts index 554bd65aa31..1e3106b05ed 100644 --- a/scripts/notify-discord-release.ts +++ b/scripts/notify-discord-release.ts @@ -2,7 +2,13 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Config, Data, DateTime, Effect, Layer, Logger, Schema } from "effect"; +import * as Config from "effect/Config"; +import * as Data from "effect/Data"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Logger from "effect/Logger"; +import * as Schema from "effect/Schema"; import { Argument, Command, Flag } from "effect/unstable/cli"; import { FetchHttpClient, diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts index 45c08e8d8c6..ce7b0bc6a30 100644 --- a/scripts/release-smoke.ts +++ b/scripts/release-smoke.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off import { execFileSync } from "node:child_process"; import { cpSync, @@ -11,6 +12,8 @@ import { import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import * as Console from "effect/Console"; +import * as Effect from "effect/Effect"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); @@ -389,7 +392,7 @@ try { "Windows release smoke unexpectedly removed the x64 builder debug fixture.", ); - console.log("Release smoke checks passed."); + Effect.runSync(Console.log("Release smoke checks passed.")); } finally { rmSync(tempRoot, { recursive: true, force: true }); } diff --git a/scripts/resolve-nightly-release.ts b/scripts/resolve-nightly-release.ts index 5f9413db208..e3f064305bf 100644 --- a/scripts/resolve-nightly-release.ts +++ b/scripts/resolve-nightly-release.ts @@ -2,7 +2,13 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Config, Effect, FileSystem, Option, Path, Schema } from "effect"; +import * as Console from "effect/Console"; +import * as Config from "effect/Config"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; import { Command, Flag } from "effect/unstable/cli"; interface NightlyReleaseMetadata { @@ -93,7 +99,7 @@ const writeOutput = Effect.fn("writeOutput")(function* ( yield* fs.writeFileString(githubOutputPath, serialized, { flag: "a" }); } else { for (const [key, value] of entries) { - console.log(`${key}=${value}`); + yield* Console.log(`${key}=${value}`); } } }); diff --git a/scripts/resolve-previous-release-tag.ts b/scripts/resolve-previous-release-tag.ts index 5f2ef1316b2..f75c3a4f8a4 100644 --- a/scripts/resolve-previous-release-tag.ts +++ b/scripts/resolve-previous-release-tag.ts @@ -2,7 +2,13 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Array, Config, Effect, FileSystem, Schema, Stream, String } from "effect"; +import * as Array from "effect/Array"; +import * as Config from "effect/Config"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Schema from "effect/Schema"; +import * as Stream from "effect/Stream"; +import * as String from "effect/String"; import { Command, Flag } from "effect/unstable/cli"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 3b189a7671a..61307d2992e 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -3,12 +3,7 @@ "compilerOptions": { "composite": true, "types": ["node"], - "lib": ["ESNext", "esnext.disposable"], - "plugins": [ - { - "name": "@effect/language-service" - } - ] + "lib": ["ESNext", "esnext.disposable"] }, "include": ["**/*.ts"] } diff --git a/scripts/update-release-package-versions.test.ts b/scripts/update-release-package-versions.test.ts index df2b194ce34..e5f20d413f7 100644 --- a/scripts/update-release-package-versions.test.ts +++ b/scripts/update-release-package-versions.test.ts @@ -1,6 +1,12 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; -import { ConfigProvider, Effect, FileSystem, Layer, Path, Schema, SchemaGetter } from "effect"; +import * as ConfigProvider from "effect/ConfigProvider"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as SchemaGetter from "effect/SchemaGetter"; import { Command, CliError } from "effect/unstable/cli"; import * as TestConsole from "effect/testing/TestConsole"; diff --git a/scripts/update-release-package-versions.ts b/scripts/update-release-package-versions.ts index d2baa85a162..768e3aad24e 100644 --- a/scripts/update-release-package-versions.ts +++ b/scripts/update-release-package-versions.ts @@ -2,7 +2,14 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { Config, Console, Effect, FileSystem, Option, Path, Schema, SchemaGetter } from "effect"; +import * as Config from "effect/Config"; +import * as Console from "effect/Console"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; +import * as Schema from "effect/Schema"; +import * as SchemaGetter from "effect/SchemaGetter"; import { Argument, Command, Flag } from "effect/unstable/cli"; export const releasePackageFiles = [ diff --git a/tsconfig.base.json b/tsconfig.base.json index 8d481cc7f81..4f7edbaf759 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,6 +17,39 @@ "skipLibCheck": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "plugins": [ + { + "name": "@effect/language-service", + "namespaceImportPackages": ["@effect/platform-node", "effect"], + "diagnosticSeverity": { + "importFromBarrel": "error", + "anyUnknownInErrorContext": "error", + "unsafeEffectTypeAssertion": "error", + "instanceOfSchema": "error", + "deterministicKeys": "error", + "strictEffectProvide": "off", + "missingEffectServiceDependency": "error", + "leakingRequirements": "error", + "globalErrorInEffectCatch": "error", + "globalErrorInEffectFailure": "error", + "unknownInEffectCatch": "error", + "strictBooleanExpressions": "off", + "preferSchemaOverJson": "error", + "schemaSyncInEffect": "error", + "cryptoRandomUUID": "error", + "cryptoRandomUUIDInEffect": "error", + "nodeBuiltinImport": "error", + "globalDate": "error", + "globalDateInEffect": "error", + "globalConsole": "error", + "globalConsoleInEffect": "error", + "globalRandom": "error", + "globalRandomInEffect": "error", + "globalTimers": "error", + "globalTimersInEffect": "error" + } + } + ] } } From 329a458eaae3efc8ed4752848cdb008a44145981 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 02:11:37 -0700 Subject: [PATCH 2/9] Normalize Effect service imports and NetService usage - Switch test imports to the current TestClock module path - Use the NetService context tag consistently across server, desktop, and ssh code --- .../src/backend/DesktopBackendManager.test.ts | 2 +- apps/desktop/src/ssh/DesktopSshEnvironment.ts | 6 ++-- .../src/ssh/DesktopSshPasswordPrompts.test.ts | 2 +- .../src/updates/DesktopUpdates.test.ts | 2 +- .../Layers/BootstrapCredentialService.test.ts | 2 +- .../Layers/SessionCredentialService.test.ts | 2 +- apps/server/src/bin.test.ts | 2 +- apps/server/src/bin.ts | 2 +- apps/server/src/bootstrap.test.ts | 2 +- apps/server/src/cli/config.test.ts | 2 +- apps/server/src/cli/config.ts | 4 +-- apps/server/src/observability/Metrics.test.ts | 2 +- .../observability/RpcInstrumentation.test.ts | 2 +- .../Layers/RepositoryIdentityResolver.test.ts | 2 +- .../src/provider/Layers/ClaudeAdapter.test.ts | 2 +- .../provider/Layers/OpenCodeAdapter.test.ts | 2 +- .../provider/Layers/ProviderRegistry.test.ts | 2 +- .../provider/Layers/ProviderService.test.ts | 2 +- apps/server/src/provider/opencodeRuntime.ts | 4 +-- apps/server/src/server.ts | 2 +- .../src/terminal/Layers/Manager.test.ts | 2 +- .../OpenCodeTextGeneration.test.ts | 4 +-- packages/shared/src/Net.test.ts | 22 ++++++------ packages/shared/src/Net.ts | 35 +++++++++---------- packages/ssh/src/command.test.ts | 2 +- packages/ssh/src/tunnel.test.ts | 8 ++--- packages/ssh/src/tunnel.ts | 12 +++---- scripts/dev-runner.ts | 14 ++++---- 28 files changed, 73 insertions(+), 74 deletions(-) diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts index cfd32fee664..dbc23d34ee0 100644 --- a/apps/desktop/src/backend/DesktopBackendManager.test.ts +++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts @@ -15,7 +15,7 @@ import * as Schema from "effect/Schema"; import * as Sink from "effect/Sink"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; diff --git a/apps/desktop/src/ssh/DesktopSshEnvironment.ts b/apps/desktop/src/ssh/DesktopSshEnvironment.ts index 98984a417c6..2fbf1f4357b 100644 --- a/apps/desktop/src/ssh/DesktopSshEnvironment.ts +++ b/apps/desktop/src/ssh/DesktopSshEnvironment.ts @@ -3,7 +3,7 @@ import type { DesktopSshEnvironmentBootstrap, DesktopSshEnvironmentTarget, } from "@t3tools/contracts"; -import { type NetError, NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import { SshPasswordPrompt, type SshPasswordPromptShape, @@ -35,7 +35,7 @@ export type DesktopSshEnvironmentRuntimeServices = | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient - | NetService; + | NetService.NetService; export type DesktopSshEnvironmentOperationError = | SshCommandError @@ -44,7 +44,7 @@ export type DesktopSshEnvironmentOperationError = | SshPairingError | SshReadinessError | SshPasswordPromptError - | NetError; + | NetService.NetError; export type DesktopSshEnvironmentDiscoverError = SshHostDiscoveryError; diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts index c074241a3a2..9e9cfcc737e 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import type * as Electron from "electron"; import * as ElectronWindow from "../electron/ElectronWindow.ts"; diff --git a/apps/desktop/src/updates/DesktopUpdates.test.ts b/apps/desktop/src/updates/DesktopUpdates.test.ts index 9838fe10747..34d18f11a77 100644 --- a/apps/desktop/src/updates/DesktopUpdates.test.ts +++ b/apps/desktop/src/updates/DesktopUpdates.test.ts @@ -8,7 +8,7 @@ import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts"; import * as DesktopConfig from "../app/DesktopConfig.ts"; diff --git a/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts b/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts index 08f57874b23..110dae84379 100644 --- a/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts +++ b/apps/server/src/auth/Layers/BootstrapCredentialService.test.ts @@ -3,7 +3,7 @@ import { expect, it } from "@effect/vitest"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import type { ServerConfigShape } from "../../config.ts"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/auth/Layers/SessionCredentialService.test.ts b/apps/server/src/auth/Layers/SessionCredentialService.test.ts index 71cd092ea8c..5609c2fdea2 100644 --- a/apps/server/src/auth/Layers/SessionCredentialService.test.ts +++ b/apps/server/src/auth/Layers/SessionCredentialService.test.ts @@ -3,7 +3,7 @@ import { expect, it } from "@effect/vitest"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import type { ServerConfigShape } from "../../config.ts"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/bin.test.ts b/apps/server/src/bin.test.ts index 6e5240eb34c..a7bf2686101 100644 --- a/apps/server/src/bin.test.ts +++ b/apps/server/src/bin.test.ts @@ -6,7 +6,7 @@ import { join } from "node:path"; import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import { assert, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; diff --git a/apps/server/src/bin.ts b/apps/server/src/bin.ts index f106d25e5eb..4e829332c17 100644 --- a/apps/server/src/bin.ts +++ b/apps/server/src/bin.ts @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { Command } from "effect/unstable/cli"; -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import packageJson from "../package.json" with { type: "json" }; import { authCommand } from "./cli/auth.ts"; import { sharedServerCommandFlags } from "./cli/config.ts"; diff --git a/apps/server/src/bootstrap.test.ts b/apps/server/src/bootstrap.test.ts index 17128ddad0a..f37aff8103d 100644 --- a/apps/server/src/bootstrap.test.ts +++ b/apps/server/src/bootstrap.test.ts @@ -9,7 +9,7 @@ import * as Schema from "effect/Schema"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { vi } from "vitest"; import { readBootstrapEnvelope, resolveFdPath } from "./bootstrap.ts"; diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index 5bdf2d1f59b..9e73773d5a5 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -13,7 +13,7 @@ import { DesktopBackendBootstrap, type DesktopBackendBootstrap as DesktopBackendBootstrapValue, } from "@t3tools/contracts"; -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { deriveServerPaths } from "../config.ts"; import { resolveServerConfig } from "./config.ts"; diff --git a/apps/server/src/cli/config.ts b/apps/server/src/cli/config.ts index c374bc8f182..7182854e18c 100644 --- a/apps/server/src/cli/config.ts +++ b/apps/server/src/cli/config.ts @@ -1,4 +1,4 @@ -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings"; import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts"; import * as Config from "effect/Config"; @@ -213,7 +213,7 @@ export const resolveServerConfig = ( }, ) => Effect.gen(function* () { - const { findAvailablePort } = yield* NetService; + const { findAvailablePort } = yield* NetService.NetService; const path = yield* Path.Path; const fs = yield* FileSystem.FileSystem; const env = yield* EnvServerConfig; diff --git a/apps/server/src/observability/Metrics.test.ts b/apps/server/src/observability/Metrics.test.ts index 899a1e28bc4..3fe6b9a8cd8 100644 --- a/apps/server/src/observability/Metrics.test.ts +++ b/apps/server/src/observability/Metrics.test.ts @@ -4,7 +4,7 @@ import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Metric from "effect/Metric"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { withMetrics } from "./Metrics.ts"; diff --git a/apps/server/src/observability/RpcInstrumentation.test.ts b/apps/server/src/observability/RpcInstrumentation.test.ts index 627357e2955..c7fb7d12b09 100644 --- a/apps/server/src/observability/RpcInstrumentation.test.ts +++ b/apps/server/src/observability/RpcInstrumentation.test.ts @@ -7,7 +7,7 @@ import * as Fiber from "effect/Fiber"; import * as Metric from "effect/Metric"; import * as Stream from "effect/Stream"; import * as Tracer from "effect/Tracer"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { observeRpcEffect, diff --git a/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts b/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts index 1092ce36989..df88c1033e1 100644 --- a/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts +++ b/apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts @@ -7,7 +7,7 @@ import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { runProcess } from "../../processRunner.ts"; import { RepositoryIdentityResolver } from "../Services/RepositoryIdentityResolver.ts"; diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index f095337a41c..05b93713d61 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -30,7 +30,7 @@ import * as Layer from "effect/Layer"; import * as Random from "effect/Random"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { attachmentRelativePath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts index 96c1f7fe7ab..c23d9b62ed8 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts @@ -10,7 +10,7 @@ import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { beforeEach } from "vitest"; import { diff --git a/apps/server/src/provider/Layers/ProviderRegistry.test.ts b/apps/server/src/provider/Layers/ProviderRegistry.test.ts index a903974f4f0..02520e9c527 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.test.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.test.ts @@ -9,7 +9,7 @@ import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import * as CodexErrors from "effect-codex-app-server/errors"; import { ClaudeSettings, diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index ca84cb06986..fc0450b8b69 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -32,7 +32,7 @@ import * as PubSub from "effect/PubSub"; import * as Ref from "effect/Ref"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { diff --git a/apps/server/src/provider/opencodeRuntime.ts b/apps/server/src/provider/opencodeRuntime.ts index 5780ec8661b..a336030bdbe 100644 --- a/apps/server/src/provider/opencodeRuntime.ts +++ b/apps/server/src/provider/opencodeRuntime.ts @@ -30,7 +30,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { isWindowsCommandNotFound } from "../processRunner.ts"; import { collectStreamAsString } from "./providerSnapshot.ts"; -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; const OPENCODE_SERVER_READY_PREFIX = "opencode server listening"; const DEFAULT_OPENCODE_SERVER_TIMEOUT_MS = 5_000; @@ -269,7 +269,7 @@ function ensureRuntimeError( const makeOpenCodeRuntime = Effect.gen(function* () { const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; - const netService = yield* NetService; + const netService = yield* NetService.NetService; const runOpenCodeCommand: OpenCodeRuntimeShape["runOpenCodeCommand"] = (input) => Effect.gen(function* () { diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 02ad134e8df..813cdae7317 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -87,7 +87,7 @@ import { orchestrationDispatchRouteLayer, orchestrationSnapshotRouteLayer, } from "./orchestration/http.ts"; -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import { disableTailscaleServe, ensureTailscaleServe } from "@t3tools/tailscale"; const PtyAdapterLive = Layer.unwrap( diff --git a/apps/server/src/terminal/Layers/Manager.test.ts b/apps/server/src/terminal/Layers/Manager.test.ts index 0f3c2913800..0cca0f64e19 100644 --- a/apps/server/src/terminal/Layers/Manager.test.ts +++ b/apps/server/src/terminal/Layers/Manager.test.ts @@ -18,7 +18,7 @@ import * as PlatformError from "effect/PlatformError"; import * as Ref from "effect/Ref"; import * as Schedule from "effect/Schedule"; import * as Scope from "effect/Scope"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { expect } from "vitest"; import type { TerminalManagerShape } from "../Services/Manager.ts"; diff --git a/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts b/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts index acc57cd96e3..8a5deacdb79 100644 --- a/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts +++ b/apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts @@ -5,8 +5,8 @@ import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; -import { TestClock } from "effect/testing"; -import { NetService } from "@t3tools/shared/Net"; +import * as TestClock from "effect/testing/TestClock"; +import * as NetService from "@t3tools/shared/Net"; import { beforeEach, expect } from "vitest"; import { ServerConfig } from "../config.ts"; diff --git a/packages/shared/src/Net.test.ts b/packages/shared/src/Net.test.ts index b8e74f1f1cc..e165d944b56 100644 --- a/packages/shared/src/Net.test.ts +++ b/packages/shared/src/Net.test.ts @@ -3,7 +3,7 @@ import * as NodeNet from "node:net"; import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; -import * as Net from "./Net.ts"; +import * as NetService from "./Net.ts"; const closeServer = (server: NodeNet.Server) => Effect.sync(() => { @@ -19,19 +19,21 @@ const getPort = (server: NodeNet.Server): number => { return typeof address === "object" && address !== null ? address.port : 0; }; -const openServer = (host?: string): Effect.Effect => - Effect.callback((resume) => { +const openServer = (host?: string): Effect.Effect => + Effect.callback((resume) => { const server = NodeNet.createServer(); let settled = false; - const settle = (effect: Effect.Effect) => { + const settle = (effect: Effect.Effect) => { if (settled) return; settled = true; resume(effect); }; server.once("error", (cause) => { - settle(Effect.fail(new Net.NetError({ message: "Failed to open test server", cause }))); + settle( + Effect.fail(new NetService.NetError({ message: "Failed to open test server", cause })), + ); }); if (host) { @@ -43,11 +45,11 @@ const openServer = (host?: string): Effect.Effect return closeServer(server); }); -it.layer(Net.NetService.layer)("NetService", (it) => { +it.layer(NetService.layer)("NetService", (it) => { describe("Net helpers", () => { it.effect("reserveLoopbackPort returns a positive loopback port", () => Effect.gen(function* () { - const net = yield* Net.NetService; + const net = yield* NetService.NetService; const port = yield* net.reserveLoopbackPort(); assert.ok(port > 0); @@ -59,7 +61,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { openServer("127.0.0.1"), (server) => Effect.gen(function* () { - const net = yield* Net.NetService; + const net = yield* NetService.NetService; const port = getPort(server); const available = yield* net.isPortAvailableOnLoopback(port); @@ -71,7 +73,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { it.effect("findAvailablePort returns preferred when it is free", () => Effect.gen(function* () { - const net = yield* Net.NetService; + const net = yield* NetService.NetService; const preferred = yield* net.reserveLoopbackPort(); const resolved = yield* net.findAvailablePort(preferred); @@ -84,7 +86,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { openServer(), (server) => Effect.gen(function* () { - const net = yield* Net.NetService; + const net = yield* NetService.NetService; const preferred = getPort(server); const resolved = yield* net.findAvailablePort(preferred); diff --git a/packages/shared/src/Net.ts b/packages/shared/src/Net.ts index 22e80dffb3d..0a3c6283756 100644 --- a/packages/shared/src/Net.ts +++ b/packages/shared/src/Net.ts @@ -4,22 +4,21 @@ import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Context from "effect/Context"; +import * as Predicate from "effect/Predicate"; export class NetError extends Data.TaggedError("NetError")<{ readonly message: string; readonly cause?: unknown; }> {} -function isErrnoExceptionWithCode(cause: unknown): cause is { +const isErrnoExceptionWithCode = ( + cause: unknown, +): cause is { readonly code: string; -} { - return ( - typeof cause === "object" && - cause !== null && - "code" in cause && - typeof (cause as { readonly code: unknown }).code === "string" - ); -} +} => + Predicate.isObject(cause) && + Predicate.hasProperty(cause, "code") && + Predicate.isString(cause.code); const closeServer = (server: NodeNet.Server) => { try { @@ -85,6 +84,13 @@ export interface NetServiceShape { readonly findAvailablePort: (preferred: number) => Effect.Effect; } +/** + * NetService - Service tag for startup networking helpers. + */ +export class NetService extends Context.Service()( + "@t3tools/shared/Net/NetService", +) {} + export const make = () => { /** * Returns true when a TCP server can bind to {host, port}. @@ -175,13 +181,4 @@ export const make = () => { } satisfies NetServiceShape; }; -/** - * NetService - Service tag for startup networking helpers. - */ -export class NetService extends Context.Service()( - "@t3tools/shared/Net/NetService", -) { - static readonly layer = Layer.sync(NetService, make); -} - -export const layer = NetService.layer; +export const layer = Layer.sync(NetService, make); diff --git a/packages/ssh/src/command.test.ts b/packages/ssh/src/command.test.ts index ef7636fa89f..d95ffed49dc 100644 --- a/packages/ssh/src/command.test.ts +++ b/packages/ssh/src/command.test.ts @@ -7,7 +7,7 @@ import * as Layer from "effect/Layer"; import * as Result from "effect/Result"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { ChildProcessSpawner } from "effect/unstable/process"; import { diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 98908ac89e2..0219396e7da 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -1,6 +1,6 @@ import { assert, describe, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; @@ -8,7 +8,7 @@ import * as Layer from "effect/Layer"; import * as Result from "effect/Result"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; -import { TestClock } from "effect/testing"; +import * as TestClock from "effect/testing/TestClock"; import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; @@ -74,7 +74,7 @@ const testHttpClient = HttpClient.make((request) => const hangingHttpClient = HttpClient.make(() => Effect.never); -const testNetService = NetService.of({ +const testNetService = NetService.NetService.of({ canListenOnHost: () => Effect.succeed(true), isPortAvailableOnLoopback: () => Effect.succeed(true), reserveLoopbackPort: () => Effect.succeed(41_773), @@ -270,7 +270,7 @@ describe("ssh tunnel scripts", () => { NodeServices.layer, Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), Layer.succeed(HttpClient.HttpClient, testHttpClient), - Layer.succeed(NetService, testNetService), + Layer.succeed(NetService.NetService, testNetService), SshPasswordPrompt.disabledLayer, SshEnvironmentManager.layer(), ); diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index f4b26df0acd..f0e189efaea 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -2,7 +2,7 @@ import type { DesktopSshEnvironmentBootstrap, DesktopSshEnvironmentTarget, } from "@t3tools/contracts"; -import { type NetError, NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import { fromLenientJson } from "@t3tools/shared/schemaJson"; import * as Context from "effect/Context"; import * as Deferred from "effect/Deferred"; @@ -82,7 +82,7 @@ type SshEnvironmentEffectContext = | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient - | NetService + | NetService.NetService | SshPasswordPrompt; type SshEnvironmentEffectError = @@ -92,7 +92,7 @@ type SshEnvironmentEffectError = | SshPairingError | SshReadinessError | SshPasswordPromptError - | NetError; + | NetService.NetError; function makeSshTunnelCancelledError(target: DesktopSshEnvironmentTarget): SshCommandError { return new SshCommandError({ @@ -903,7 +903,7 @@ export const fetchLoopbackSshJson = Effect.fn("ssh/tunnel.fetchLoopbackSshJson") }); const reserveLocalTunnelPort = Effect.fn("ssh/tunnel.reserveLocalTunnelPort")(function* () { - const net = yield* NetService; + const net = yield* NetService.NetService; return yield* net.reserveLoopbackPort(); }); @@ -923,7 +923,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient - | NetService + | NetService.NetService | Scope.Scope > { const hostSpec = yield* buildSshHostSpecEffect(input.resolvedTarget); @@ -1076,7 +1076,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: ), Effect.tapError((cause) => Effect.gen(function* () { - const net = yield* NetService; + const net = yield* NetService.NetService; const processRunningExit = yield* Effect.exit(child.isRunning); const localPortAvailableExit = yield* Effect.exit( net.canListenOnHost(input.localPort, "127.0.0.1"), diff --git a/scripts/dev-runner.ts b/scripts/dev-runner.ts index 78580e95eeb..58bbb1ac35e 100644 --- a/scripts/dev-runner.ts +++ b/scripts/dev-runner.ts @@ -4,7 +4,7 @@ import * as NodeOS from "node:os"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { NetService } from "@t3tools/shared/Net"; +import * as NetService from "@t3tools/shared/Net"; import * as Config from "effect/Config"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; @@ -246,22 +246,22 @@ export function checkPortAvailabilityOnHosts( }); } -const defaultCheckPortAvailability: PortAvailabilityCheck = (port) => +const defaultCheckPortAvailability: PortAvailabilityCheck = (port) => Effect.gen(function* () { - const net = yield* NetService; + const net = yield* NetService.NetService; return yield* checkPortAvailabilityOnHosts(port, DEV_PORT_PROBE_HOSTS, (candidatePort, host) => net.canListenOnHost(candidatePort, host), ); }); -interface FindFirstAvailableOffsetInput { +interface FindFirstAvailableOffsetInput { readonly startOffset: number; readonly requireServerPort: boolean; readonly requireWebPort: boolean; readonly checkPortAvailability?: PortAvailabilityCheck; } -export function findFirstAvailableOffset({ +export function findFirstAvailableOffset({ startOffset, requireServerPort, requireWebPort, @@ -308,7 +308,7 @@ export function findFirstAvailableOffset({ }); } -interface ResolveModePortOffsetsInput { +interface ResolveModePortOffsetsInput { readonly mode: DevMode; readonly startOffset: number; readonly hasExplicitServerPort: boolean; @@ -316,7 +316,7 @@ interface ResolveModePortOffsetsInput { readonly checkPortAvailability?: PortAvailabilityCheck; } -export function resolveModePortOffsets({ +export function resolveModePortOffsets({ mode, startOffset, hasExplicitServerPort, From af3bba4a30914e439978f7ab2543370681b24db1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 8 May 2026 15:53:18 +0000 Subject: [PATCH 3/9] Fix busy-wait polling in CheckpointReactor tests Replace Effect.yieldNow (near-instant microtask resolution) with Effect.sleep("10 millis") to restore a real delay between poll iterations in waitForThread, waitForEvent, and waitForGitRefExists. Without a real delay, these polling loops spin as tight busy-waits, starving the event loop of time to process I/O callbacks from git operations and database writes. Applied via @cursor push command --- .../src/orchestration/Layers/CheckpointReactor.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts index ecc78d0b484..b610c0abc28 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts @@ -170,7 +170,7 @@ async function waitForThread( if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error("Timed out waiting for thread state."); } - await Effect.runPromise(Effect.yieldNow); + await Effect.runPromise(Effect.sleep("10 millis")); return poll(); }; return poll(); @@ -192,7 +192,7 @@ async function waitForEvent( if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error("Timed out waiting for orchestration event."); } - await Effect.runPromise(Effect.yieldNow); + await Effect.runPromise(Effect.sleep("10 millis")); return poll(); }; return poll(); @@ -239,7 +239,7 @@ async function waitForGitRefExists(cwd: string, ref: string, timeoutMs = 15_000) if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { throw new Error(`Timed out waiting for git ref '${ref}'.`); } - await Effect.runPromise(Effect.yieldNow); + await Effect.runPromise(Effect.sleep("10 millis")); return poll(); }; return poll(); From c1f7919421c30618c2056a4395215913bae80e71 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 09:13:39 -0700 Subject: [PATCH 4/9] Make provider snapshots and timestamps effectful - Replace sync timestamp reads with Effect-based time generation - Update provider initial snapshots and unavailable snapshots to return effects - Adjust managed provider tests for the new async snapshot contract --- .../orchestration/Layers/CheckpointReactor.ts | 44 ++--- .../Layers/OrchestrationEngine.ts | 2 +- apps/server/src/orchestration/decider.ts | 18 +- .../src/provider/Drivers/ClaudeDriver.ts | 3 +- .../src/provider/Drivers/CodexDriver.ts | 3 +- .../src/provider/Drivers/CursorDriver.ts | 3 +- .../src/provider/Drivers/OpenCodeDriver.ts | 3 +- .../src/provider/Layers/ClaudeProvider.ts | 57 ++++--- .../src/provider/Layers/CodexProvider.ts | 47 +++--- .../provider/Layers/CodexSessionRuntime.ts | 2 +- .../src/provider/Layers/CursorProvider.ts | 44 ++--- .../src/provider/Layers/OpenCodeAdapter.ts | 63 +++---- .../src/provider/Layers/OpenCodeProvider.ts | 59 +++---- .../Layers/ProviderInstanceRegistryLive.ts | 6 +- .../src/provider/Layers/ProviderService.ts | 2 +- .../makeManagedServerProvider.test.ts | 8 +- .../src/provider/makeManagedServerProvider.ts | 4 +- .../provider/unavailableProviderSnapshot.ts | 61 +++---- apps/server/src/terminal/Layers/Manager.ts | 2 +- apps/server/src/ws.ts | 155 +++++++++--------- 20 files changed, 308 insertions(+), 278 deletions(-) diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts index 4e1606928bf..da135b0c056 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts @@ -33,7 +33,7 @@ import { isGitRepository } from "../../git/Utils.ts"; import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; import { WorkspaceEntries } from "../../workspace/Services/WorkspaceEntries.ts"; -const nowIsoSync = () => Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); +const nowIso = Effect.map(DateTime.now, DateTime.formatIso); type ReactorInput = | { @@ -724,12 +724,14 @@ const make = Effect.gen(function* () { if (event.type === "thread.checkpoint-revert-requested") { yield* handleRevertRequested(event).pipe( Effect.catch((error) => - appendRevertFailureActivity({ - threadId: event.payload.threadId, - turnCount: event.payload.turnCount, - detail: error.message, - createdAt: nowIsoSync(), - }), + Effect.flatMap(nowIso, (createdAt) => + appendRevertFailureActivity({ + threadId: event.payload.threadId, + turnCount: event.payload.turnCount, + detail: error.message, + createdAt, + }), + ), ), ); return; @@ -743,12 +745,14 @@ const make = Effect.gen(function* () { if (event.type === "thread.turn-diff-completed") { yield* captureCheckpointFromPlaceholder(event).pipe( Effect.catch((error) => - appendCaptureFailureActivity({ - threadId: event.payload.threadId, - turnId: event.payload.turnId, - detail: error.message, - createdAt: nowIsoSync(), - }).pipe(Effect.catch(() => Effect.void)), + Effect.flatMap(nowIso, (createdAt) => + appendCaptureFailureActivity({ + threadId: event.payload.threadId, + turnId: event.payload.turnId, + detail: error.message, + createdAt, + }).pipe(Effect.catch(() => Effect.void)), + ), ), ); } @@ -767,12 +771,14 @@ const make = Effect.gen(function* () { yield* refreshLocalGitStatusFromTurnCompletion(event); yield* captureCheckpointFromTurnCompletion(event).pipe( Effect.catch((error) => - appendCaptureFailureActivity({ - threadId: event.threadId, - turnId, - detail: error.message, - createdAt: nowIsoSync(), - }).pipe(Effect.catch(() => Effect.void)), + Effect.flatMap(nowIso, (createdAt) => + appendCaptureFailureActivity({ + threadId: event.threadId, + turnId, + detail: error.message, + createdAt, + }).pipe(Effect.catch(() => Effect.void)), + ), ), ); return; diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts index 88561c17658..e795176cec1 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts @@ -78,7 +78,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { const projectionPipeline = yield* OrchestrationProjectionPipeline; const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; - const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); + const nowIso = Effect.map(DateTime.now, DateTime.formatIso); let commandReadModel = createEmptyReadModel(yield* nowIso); const commandQueue = yield* Queue.unbounded(); diff --git a/apps/server/src/orchestration/decider.ts b/apps/server/src/orchestration/decider.ts index 3a6e42997a8..1004c945dbf 100644 --- a/apps/server/src/orchestration/decider.ts +++ b/apps/server/src/orchestration/decider.ts @@ -18,7 +18,7 @@ import { } from "./commandInvariants.ts"; import { projectEvent } from "./projector.ts"; -const currentIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); +const nowIso = Effect.map(DateTime.now, DateTime.formatIso); function withEventBase( input: Pick & { @@ -117,7 +117,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, projectId: command.projectId, }); - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "project", @@ -174,7 +174,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" }); } - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "project", @@ -230,7 +230,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, threadId: command.threadId, }); - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "thread", @@ -252,7 +252,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, threadId: command.threadId, }); - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "thread", @@ -275,7 +275,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, threadId: command.threadId, }); - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "thread", @@ -297,7 +297,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, threadId: command.threadId, }); - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "thread", @@ -325,7 +325,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, threadId: command.threadId, }); - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "thread", @@ -348,7 +348,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, threadId: command.threadId, }); - const occurredAt = yield* currentIso; + const occurredAt = yield* nowIso; return { ...withEventBase({ aggregateKind: "thread", diff --git a/apps/server/src/provider/Drivers/ClaudeDriver.ts b/apps/server/src/provider/Drivers/ClaudeDriver.ts index 87b08d01553..1ae8670d26b 100644 --- a/apps/server/src/provider/Drivers/ClaudeDriver.ts +++ b/apps/server/src/provider/Drivers/ClaudeDriver.ts @@ -165,7 +165,8 @@ export const ClaudeDriver: ProviderDriver = { getSettings: Effect.succeed(effectiveConfig), streamSettings: Stream.never, haveSettingsChanged: () => false, - initialSnapshot: (settings) => stampIdentity(makePendingClaudeProvider(settings)), + initialSnapshot: (settings) => + makePendingClaudeProvider(settings).pipe(Effect.map(stampIdentity)), checkProvider, enrichSnapshot: ({ snapshot, publishSnapshot }) => enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( diff --git a/apps/server/src/provider/Drivers/CodexDriver.ts b/apps/server/src/provider/Drivers/CodexDriver.ts index ce00d1340ff..9e7216a4c40 100644 --- a/apps/server/src/provider/Drivers/CodexDriver.ts +++ b/apps/server/src/provider/Drivers/CodexDriver.ts @@ -165,7 +165,8 @@ export const CodexDriver: ProviderDriver = { getSettings: Effect.succeed(effectiveConfig), streamSettings: Stream.never, haveSettingsChanged: () => false, - initialSnapshot: (settings) => stampIdentity(makePendingCodexProvider(settings)), + initialSnapshot: (settings) => + makePendingCodexProvider(settings).pipe(Effect.map(stampIdentity)), checkProvider, enrichSnapshot: ({ snapshot, publishSnapshot }) => enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/apps/server/src/provider/Drivers/CursorDriver.ts index 135d8637062..41770538400 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.ts @@ -133,7 +133,8 @@ export const CursorDriver: ProviderDriver = { getSettings: Effect.succeed(effectiveConfig), streamSettings: Stream.never, haveSettingsChanged: () => false, - initialSnapshot: (settings) => stampIdentity(buildInitialCursorProviderSnapshot(settings)), + initialSnapshot: (settings) => + buildInitialCursorProviderSnapshot(settings).pipe(Effect.map(stampIdentity)), checkProvider, // Preserve the background ACP model-capability probe that used to // live on `CursorProviderLive`. Only fires when the snapshot reports diff --git a/apps/server/src/provider/Drivers/OpenCodeDriver.ts b/apps/server/src/provider/Drivers/OpenCodeDriver.ts index a6e184b2d4b..e647fb91421 100644 --- a/apps/server/src/provider/Drivers/OpenCodeDriver.ts +++ b/apps/server/src/provider/Drivers/OpenCodeDriver.ts @@ -144,7 +144,8 @@ export const OpenCodeDriver: ProviderDriver getSettings: Effect.succeed(effectiveConfig), streamSettings: Stream.never, haveSettingsChanged: () => false, - initialSnapshot: (settings) => stampIdentity(makePendingOpenCodeProvider(settings)), + initialSnapshot: (settings) => + makePendingOpenCodeProvider(settings).pipe(Effect.map(stampIdentity)), checkProvider, enrichSnapshot: ({ snapshot, publishSnapshot }) => enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts index aacdb12b709..27bff1b0b69 100644 --- a/apps/server/src/provider/Layers/ClaudeProvider.ts +++ b/apps/server/src/provider/Layers/ClaudeProvider.ts @@ -668,19 +668,39 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( }); }); -export const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): ServerProviderDraft => { - const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); - const models = providerModelsFromSettings( - BUILT_IN_MODELS, - PROVIDER, - claudeSettings.customModels, - DEFAULT_CLAUDE_MODEL_CAPABILITIES, - ); +const nowIso = Effect.map(DateTime.now, DateTime.formatIso); + +export const makePendingClaudeProvider = ( + claudeSettings: ClaudeSettings, +): Effect.Effect => + Effect.gen(function* () { + const checkedAt = yield* nowIso; + const models = providerModelsFromSettings( + BUILT_IN_MODELS, + PROVIDER, + claudeSettings.customModels, + DEFAULT_CLAUDE_MODEL_CAPABILITIES, + ); + + if (!claudeSettings.enabled) { + return buildServerProvider({ + presentation: CLAUDE_PRESENTATION, + enabled: false, + checkedAt, + models, + probe: { + installed: false, + version: null, + status: "warning", + auth: { status: "unknown" }, + message: "Claude is disabled in T3 Code settings.", + }, + }); + } - if (!claudeSettings.enabled) { return buildServerProvider({ presentation: CLAUDE_PRESENTATION, - enabled: false, + enabled: true, checkedAt, models, probe: { @@ -688,24 +708,9 @@ export const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): Serve version: null, status: "warning", auth: { status: "unknown" }, - message: "Claude is disabled in T3 Code settings.", + message: "Claude provider status has not been checked in this session yet.", }, }); - } - - return buildServerProvider({ - presentation: CLAUDE_PRESENTATION, - enabled: true, - checkedAt, - models, - probe: { - installed: false, - version: null, - status: "warning", - auth: { status: "unknown" }, - message: "Claude provider status has not been checked in this session yet.", - }, }); -}; export { probeClaudeCapabilities }; diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts index 683960e1d8a..499c6fd068c 100644 --- a/apps/server/src/provider/Layers/CodexProvider.ts +++ b/apps/server/src/provider/Layers/CodexProvider.ts @@ -332,14 +332,33 @@ const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvi capabilities: null, })); -const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProviderDraft => { - const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); - const models = emptyCodexModelsFromSettings(codexSettings); +const makePendingCodexProvider = ( + codexSettings: CodexSettings, +): Effect.Effect => + Effect.gen(function* () { + const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); + const models = emptyCodexModelsFromSettings(codexSettings); + + if (!codexSettings.enabled) { + return buildServerProvider({ + presentation: CODEX_PRESENTATION, + enabled: false, + checkedAt, + models, + skills: [], + probe: { + installed: false, + version: null, + status: "warning", + auth: { status: "unknown" }, + message: "Codex is disabled in T3 Code settings.", + }, + }); + } - if (!codexSettings.enabled) { return buildServerProvider({ presentation: CODEX_PRESENTATION, - enabled: false, + enabled: true, checkedAt, models, skills: [], @@ -348,26 +367,10 @@ const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProviderD version: null, status: "warning", auth: { status: "unknown" }, - message: "Codex is disabled in T3 Code settings.", + message: "Codex provider status has not been checked in this session yet.", }, }); - } - - return buildServerProvider({ - presentation: CODEX_PRESENTATION, - enabled: true, - checkedAt, - models, - skills: [], - probe: { - installed: false, - version: null, - status: "warning", - auth: { status: "unknown" }, - message: "Codex provider status has not been checked in this session yet.", - }, }); -}; function accountProbeStatus(account: CodexAppServerProviderSnapshot["account"]): { readonly status: Exclude; diff --git a/apps/server/src/provider/Layers/CodexSessionRuntime.ts b/apps/server/src/provider/Layers/CodexSessionRuntime.ts index 546ac4e74d2..41c876f29af 100644 --- a/apps/server/src/provider/Layers/CodexSessionRuntime.ts +++ b/apps/server/src/provider/Layers/CodexSessionRuntime.ts @@ -737,7 +737,7 @@ export const makeCodexSessionRuntime = ( Effect.provide(clientContext), ); const serverNotifications = yield* Queue.unbounded(); - const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); + const nowIso = Effect.map(DateTime.now, DateTime.formatIso); const sessionCreatedAt = yield* nowIso; const initialSession = { diff --git a/apps/server/src/provider/Layers/CursorProvider.ts b/apps/server/src/provider/Layers/CursorProvider.ts index b7ce0cf63c2..035c08437a9 100644 --- a/apps/server/src/provider/Layers/CursorProvider.ts +++ b/apps/server/src/provider/Layers/CursorProvider.ts @@ -65,38 +65,40 @@ export const CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES = { export function buildInitialCursorProviderSnapshot( cursorSettings: CursorSettings, -): ServerProviderDraft { - const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); - const models = getCursorFallbackModels(cursorSettings); +): Effect.Effect { + return Effect.gen(function* () { + const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); + const models = getCursorFallbackModels(cursorSettings); + + if (!cursorSettings.enabled) { + return buildServerProvider({ + presentation: CURSOR_PRESENTATION, + enabled: false, + checkedAt, + models, + probe: { + installed: false, + version: null, + status: "warning", + auth: { status: "unknown" }, + message: "Cursor is disabled in T3 Code settings.", + }, + }); + } - if (!cursorSettings.enabled) { return buildServerProvider({ presentation: CURSOR_PRESENTATION, - enabled: false, + enabled: true, checkedAt, models, probe: { - installed: false, + installed: true, version: null, status: "warning", auth: { status: "unknown" }, - message: "Cursor is disabled in T3 Code settings.", + message: "Checking Cursor Agent availability...", }, }); - } - - return buildServerProvider({ - presentation: CURSOR_PRESENTATION, - enabled: true, - checkedAt, - models, - probe: { - installed: true, - version: null, - status: "warning", - auth: { status: "unknown" }, - message: "Checking Cursor Agent availability...", - }, }); } diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.ts index d3561507f9f..2eb75b24d59 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.ts @@ -103,9 +103,7 @@ export interface OpenCodeAdapterLiveOptions { readonly nativeEventLogger?: EventNdjsonLogger; } -function nowIso(): string { - return Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); -} +const nowIso = Effect.map(DateTime.now, DateTime.formatIso); /** * Map a tagged OpenCodeRuntimeError produced by {@link runOpenCodeSdk} into @@ -148,12 +146,14 @@ const buildEventBase = (input: { "eventId" | "provider" | "threadId" | "createdAt" | "turnId" | "itemId" | "requestId" | "raw" > > => - Random.nextUUIDv4.pipe( - Effect.map((uuid) => ({ + Effect.gen(function* () { + const uuid = yield* Random.nextUUIDv4; + const createdAt = input.createdAt ?? (yield* nowIso); + return { eventId: EventId.make(uuid), provider: PROVIDER, threadId: input.threadId, - createdAt: input.createdAt ?? nowIso(), + createdAt, ...(input.turnId ? { turnId: input.turnId } : {}), ...(input.itemId ? { itemId: RuntimeItemId.make(input.itemId) } : {}), ...(input.requestId ? { requestId: RuntimeRequestId.make(input.requestId) } : {}), @@ -165,8 +165,8 @@ const buildEventBase = (input: { }, } : {}), - })), - ); + }; + }); function toToolLifecycleItemType(toolName: string): ToolLifecycleItemType { const normalized = toolName.toLowerCase(); @@ -416,21 +416,24 @@ function updateProviderSession( readonly clearActiveTurnId?: boolean; readonly clearLastError?: boolean; }, -): ProviderSession { - const nextSession = { - ...context.session, - ...patch, - updatedAt: nowIso(), - } as ProviderSession & Record; - const mutableSession = nextSession as Record; - if (options?.clearActiveTurnId) { - delete mutableSession.activeTurnId; - } - if (options?.clearLastError) { - delete mutableSession.lastError; - } - context.session = nextSession; - return nextSession; +): Effect.Effect { + return Effect.gen(function* () { + const updatedAt = yield* nowIso; + const nextSession = { + ...context.session, + ...patch, + updatedAt, + } as ProviderSession & Record; + const mutableSession = nextSession as Record; + if (options?.clearActiveTurnId) { + delete mutableSession.activeTurnId; + } + if (options?.clearLastError) { + delete mutableSession.lastError; + } + context.session = nextSession; + return nextSession; + }); } const stopOpenCodeContext = Effect.fn("stopOpenCodeContext")(function* ( @@ -650,7 +653,7 @@ export function makeOpenCodeAdapter( const turnId = context.activeTurnId; yield* writeNativeEventBestEffort(context.session.threadId, { - observedAt: nowIso(), + observedAt: yield* nowIso, event: { provider: PROVIDER, threadId: context.session.threadId, @@ -871,7 +874,7 @@ export function makeOpenCodeAdapter( case "session.status": { if (event.properties.status.type === "busy") { - updateProviderSession(context, { + yield* updateProviderSession(context, { status: "running", activeTurnId: turnId, }); @@ -895,7 +898,7 @@ export function makeOpenCodeAdapter( if (event.properties.status.type === "idle" && turnId) { context.activeTurnId = undefined; - updateProviderSession(context, { status: "ready" }, { clearActiveTurnId: true }); + yield* updateProviderSession(context, { status: "ready" }, { clearActiveTurnId: true }); yield* emit({ ...(yield* buildEventBase({ threadId: context.session.threadId, @@ -915,7 +918,7 @@ export function makeOpenCodeAdapter( const message = sessionErrorMessage(event.properties.error); const activeTurnId = context.activeTurnId; context.activeTurnId = undefined; - updateProviderSession( + yield* updateProviderSession( context, { status: "error", @@ -1092,7 +1095,7 @@ export function makeOpenCodeAdapter( return raceWinner.session; } - const createdAt = nowIso(); + const createdAt = yield* nowIso; const session: ProviderSession = { provider: PROVIDER, providerInstanceId: boundInstanceId, @@ -1193,7 +1196,7 @@ export function makeOpenCodeAdapter( context.activeTurnId = turnId; context.activeAgent = agent ?? (input.interactionMode === "plan" ? "plan" : undefined); context.activeVariant = variant; - updateProviderSession( + yield* updateProviderSession( context, { status: "running", @@ -1231,7 +1234,7 @@ export function makeOpenCodeAdapter( context.activeTurnId = undefined; context.activeAgent = undefined; context.activeVariant = undefined; - updateProviderSession( + yield* updateProviderSession( context, { status: "ready", diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.ts b/apps/server/src/provider/Layers/OpenCodeProvider.ts index 5abbd7daf62..b247e8586c0 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.ts @@ -252,19 +252,38 @@ function flattenOpenCodeModels(input: OpenCodeInventory): ReadonlyArray { - const checkedAt = Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); - const models = providerModelsFromSettings( - [], - PROVIDER, - openCodeSettings.customModels, - DEFAULT_OPENCODE_MODEL_CAPABILITIES, - ); +): Effect.Effect => + Effect.gen(function* () { + const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); + const models = providerModelsFromSettings( + [], + PROVIDER, + openCodeSettings.customModels, + DEFAULT_OPENCODE_MODEL_CAPABILITIES, + ); + + if (!openCodeSettings.enabled) { + return buildServerProvider({ + presentation: OPENCODE_PRESENTATION, + enabled: false, + checkedAt, + models, + probe: { + installed: false, + version: null, + status: "warning", + auth: { status: "unknown" }, + message: + openCodeSettings.serverUrl.trim().length > 0 + ? "OpenCode is disabled in T3 Code settings. A server URL is configured." + : "OpenCode is disabled in T3 Code settings.", + }, + }); + } - if (!openCodeSettings.enabled) { return buildServerProvider({ presentation: OPENCODE_PRESENTATION, - enabled: false, + enabled: true, checkedAt, models, probe: { @@ -272,28 +291,10 @@ export const makePendingOpenCodeProvider = ( version: null, status: "warning", auth: { status: "unknown" }, - message: - openCodeSettings.serverUrl.trim().length > 0 - ? "OpenCode is disabled in T3 Code settings. A server URL is configured." - : "OpenCode is disabled in T3 Code settings.", + message: "OpenCode provider status has not been checked in this session yet.", }, }); - } - - return buildServerProvider({ - presentation: OPENCODE_PRESENTATION, - enabled: true, - checkedAt, - models, - probe: { - installed: false, - version: null, - status: "warning", - auth: { status: "unknown" }, - message: "OpenCode provider status has not been checked in this session yet.", - }, }); -}; export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatus")(function* ( openCodeSettings: OpenCodeSettings, diff --git a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts index 59db5709d18..b51dc67793e 100644 --- a/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts +++ b/apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts @@ -124,7 +124,7 @@ const buildEntry = (input: { if (!driver) { return { kind: "unavailable" as const, - snapshot: buildUnavailableProviderSnapshot({ + snapshot: yield* buildUnavailableProviderSnapshot({ driverKind: entry.driver, instanceId, displayName: entry.displayName, @@ -146,7 +146,7 @@ const buildEntry = (input: { }); return { kind: "unavailable" as const, - snapshot: buildUnavailableProviderSnapshot({ + snapshot: yield* buildUnavailableProviderSnapshot({ driverKind: entry.driver, instanceId, displayName: entry.displayName, @@ -184,7 +184,7 @@ const buildEntry = (input: { yield* Scope.close(childScope, Exit.void).pipe(Effect.ignore); return { kind: "unavailable" as const, - snapshot: buildUnavailableProviderSnapshot({ + snapshot: yield* buildUnavailableProviderSnapshot({ driverKind: entry.driver, instanceId, displayName: entry.displayName, diff --git a/apps/server/src/provider/Layers/ProviderService.ts b/apps/server/src/provider/Layers/ProviderService.ts index 1f96742b787..f0a0688724e 100644 --- a/apps/server/src/provider/Layers/ProviderService.ts +++ b/apps/server/src/provider/Layers/ProviderService.ts @@ -208,7 +208,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( const registry = yield* ProviderAdapterRegistry; const directory = yield* ProviderSessionDirectory; const runtimeEventPubSub = yield* PubSub.unbounded(); - const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); + const nowIso = Effect.map(DateTime.now, DateTime.formatIso); const publishRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect => Effect.succeed(event).pipe( diff --git a/apps/server/src/provider/makeManagedServerProvider.test.ts b/apps/server/src/provider/makeManagedServerProvider.test.ts index 89c7452bb18..1f3ebeab089 100644 --- a/apps/server/src/provider/makeManagedServerProvider.test.ts +++ b/apps/server/src/provider/makeManagedServerProvider.test.ts @@ -113,7 +113,7 @@ describe("makeManagedServerProvider", () => { getSettings: Effect.succeed({ enabled: true }), streamSettings: Stream.empty, haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, - initialSnapshot: () => initialSnapshot, + initialSnapshot: () => Effect.succeed(initialSnapshot), checkProvider: Ref.update(checkCalls, (count) => count + 1).pipe( Effect.flatMap(() => Deferred.await(releaseCheck)), Effect.as(refreshedSnapshot), @@ -155,7 +155,7 @@ describe("makeManagedServerProvider", () => { getSettings: Ref.get(settingsRef), streamSettings: Stream.fromPubSub(settingsChanges), haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, - initialSnapshot: () => initialSnapshot, + initialSnapshot: () => Effect.succeed(initialSnapshot), checkProvider: Ref.updateAndGet(checkCalls, (count) => count + 1).pipe( Effect.flatMap((count) => count === 1 @@ -197,7 +197,7 @@ describe("makeManagedServerProvider", () => { getSettings: Effect.succeed({ enabled: true }), streamSettings: Stream.empty, haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, - initialSnapshot: () => initialSnapshot, + initialSnapshot: () => Effect.succeed(initialSnapshot), checkProvider: Deferred.await(releaseCheck).pipe(Effect.as(refreshedSnapshot)), enrichSnapshot: ({ publishSnapshot }) => Deferred.await(releaseEnrichment).pipe( @@ -238,7 +238,7 @@ describe("makeManagedServerProvider", () => { getSettings: Effect.succeed({ enabled: true }), streamSettings: Stream.empty, haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, - initialSnapshot: () => initialSnapshot, + initialSnapshot: () => Effect.succeed(initialSnapshot), checkProvider: Ref.updateAndGet(refreshCount, (count) => count + 1).pipe( Effect.flatMap((count) => count === 1 diff --git a/apps/server/src/provider/makeManagedServerProvider.ts b/apps/server/src/provider/makeManagedServerProvider.ts index 1ecc9b70656..2f07c5d508c 100644 --- a/apps/server/src/provider/makeManagedServerProvider.ts +++ b/apps/server/src/provider/makeManagedServerProvider.ts @@ -24,7 +24,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( readonly getSettings: Effect.Effect; readonly streamSettings: Stream.Stream; readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean; - readonly initialSnapshot: (settings: Settings) => ServerProvider; + readonly initialSnapshot: (settings: Settings) => Effect.Effect; readonly checkProvider: Effect.Effect; readonly enrichSnapshot?: (input: { readonly settings: Settings; @@ -40,7 +40,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( PubSub.shutdown, ); const initialSettings = yield* input.getSettings; - const initialSnapshot = input.initialSnapshot(initialSettings); + const initialSnapshot = yield* input.initialSnapshot(initialSettings); const snapshotStateRef = yield* Ref.make({ snapshot: initialSnapshot, enrichmentGeneration: 0, diff --git a/apps/server/src/provider/unavailableProviderSnapshot.ts b/apps/server/src/provider/unavailableProviderSnapshot.ts index f8cbe042f3c..66143ee09df 100644 --- a/apps/server/src/provider/unavailableProviderSnapshot.ts +++ b/apps/server/src/provider/unavailableProviderSnapshot.ts @@ -34,6 +34,8 @@ export interface UnavailableProviderSnapshotInput { readonly checkedAt?: string; } +const nowIso = Effect.map(DateTime.now, DateTime.formatIso)) + /** * Produce a `ServerProvider` snapshot representing a configured instance * whose driver the running build does not implement. The result is safe @@ -42,35 +44,36 @@ export interface UnavailableProviderSnapshotInput { */ export function buildUnavailableProviderSnapshot( input: UnavailableProviderSnapshotInput, -): ServerProvider { - const checkedAt = - input.checkedAt ?? Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); - const displayName = input.displayName?.trim() || (input.driverKind as string); +): Effect.Effect { + return Effect.gen(function* () { + const checkedAt = input.checkedAt ?? (yield* nowIso); + const displayName = input.displayName?.trim() || (input.driverKind as string); - const base = buildServerProvider({ - presentation: { displayName }, - enabled: false, - checkedAt, - models: [], - skills: [], - probe: { - installed: false, - version: null, - status: "error", - auth: { status: "unknown" }, - message: input.reason, - }, - }); + const base = buildServerProvider({ + presentation: { displayName }, + enabled: false, + checkedAt, + models: [], + skills: [], + probe: { + installed: false, + version: null, + status: "error", + auth: { status: "unknown" }, + message: input.reason, + }, + }); - return { - ...base, - instanceId: input.instanceId, - ...(input.accentColor ? { accentColor: input.accentColor } : {}), - driver: - typeof input.driverKind === "string" - ? ProviderDriverKind.make(input.driverKind) - : input.driverKind, - availability: "unavailable", - unavailableReason: input.reason, - }; + return { + ...base, + instanceId: input.instanceId, + ...(input.accentColor ? { accentColor: input.accentColor } : {}), + driver: + typeof input.driverKind === "string" + ? ProviderDriverKind.make(input.driverKind) + : input.driverKind, + availability: "unavailable", + unavailableReason: input.reason, + }; + }); } diff --git a/apps/server/src/terminal/Layers/Manager.ts b/apps/server/src/terminal/Layers/Manager.ts index 48936301a30..39875202dd8 100644 --- a/apps/server/src/terminal/Layers/Manager.ts +++ b/apps/server/src/terminal/Layers/Manager.ts @@ -725,7 +725,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith const path = yield* Path.Path; const context = yield* Effect.context(); const runFork = Effect.runForkWith(context); - const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); + const nowIso = Effect.map(DateTime.now, DateTime.formatIso); const logsDir = options.logsDir; const historyLineLimit = options.historyLineLimit ?? DEFAULT_HISTORY_LINE_LIMIT; diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 78e5e9f3b04..ba1eb70cc73 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -38,8 +38,6 @@ import { RpcSerialization, RpcServer } from "effect/unstable/rpc"; import { CheckpointDiffQuery } from "./checkpointing/Services/CheckpointDiffQuery.ts"; import { ServerConfig } from "./config.ts"; - -const nowIsoSync = () => Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso))); import { Keybindings } from "./keybindings.ts"; import { Open, resolveAvailableEditors } from "./open.ts"; import { normalizeDispatchCommand } from "./orchestration/Normalizer.ts"; @@ -89,6 +87,8 @@ import { } from "./auth/Services/SessionCredentialService.ts"; import { respondToAuthError } from "./auth/http.ts"; +const nowIso = Effect.map(DateTime.now, DateTime.formatIso); + function isThreadDetailEvent(event: OrchestrationEvent): event is Extract< OrchestrationEvent, { @@ -389,83 +389,86 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => readonly scriptId: string; readonly scriptName: string; readonly terminalId: string; - }) => { - const payload = { - scriptId: input.scriptId, - scriptName: input.scriptName, - terminalId: input.terminalId, - worktreePath: input.worktreePath, - }; - return Effect.all([ - appendSetupScriptActivity({ - threadId: command.threadId, - kind: "setup-script.requested", - summary: "Starting setup script", - createdAt: input.requestedAt, - payload, - tone: "info", - }), - appendSetupScriptActivity({ - threadId: command.threadId, - kind: "setup-script.started", - summary: "Setup script started", - createdAt: nowIsoSync(), - payload, - tone: "info", - }), - ]).pipe( - Effect.asVoid, - Effect.catch((error) => - Effect.logWarning( - "bootstrap turn start launched setup script but failed to record setup activity", - { - threadId: command.threadId, - worktreePath: input.worktreePath, - scriptId: input.scriptId, - terminalId: input.terminalId, - detail: error.message, - }, + }) => + Effect.gen(function* () { + const startedAt = yield* nowIso; + const payload = { + scriptId: input.scriptId, + scriptName: input.scriptName, + terminalId: input.terminalId, + worktreePath: input.worktreePath, + }; + yield* Effect.all([ + appendSetupScriptActivity({ + threadId: command.threadId, + kind: "setup-script.requested", + summary: "Starting setup script", + createdAt: input.requestedAt, + payload, + tone: "info", + }), + appendSetupScriptActivity({ + threadId: command.threadId, + kind: "setup-script.started", + summary: "Setup script started", + createdAt: startedAt, + payload, + tone: "info", + }), + ]).pipe( + Effect.asVoid, + Effect.catch((error) => + Effect.logWarning( + "bootstrap turn start launched setup script but failed to record setup activity", + { + threadId: command.threadId, + worktreePath: input.worktreePath, + scriptId: input.scriptId, + terminalId: input.terminalId, + detail: error.message, + }, + ), ), - ), - ); - }; + ); + }); const runSetupProgram = () => - bootstrap?.runSetupScript && targetWorktreePath - ? (() => { - const worktreePath = targetWorktreePath; - const requestedAt = nowIsoSync(); - return projectSetupScriptRunner - .runForThread({ - threadId: command.threadId, - ...(targetProjectId ? { projectId: targetProjectId } : {}), - ...(targetProjectCwd ? { projectCwd: targetProjectCwd } : {}), - worktreePath, - }) - .pipe( - Effect.matchEffect({ - onFailure: (error) => - recordSetupScriptLaunchFailure({ - error, - requestedAt, - worktreePath, - }), - onSuccess: (setupResult) => { - if (setupResult.status !== "started") { - return Effect.void; - } - return recordSetupScriptStarted({ - requestedAt, - worktreePath, - scriptId: setupResult.scriptId, - scriptName: setupResult.scriptName, - terminalId: setupResult.terminalId, - }); - }, + Effect.gen(function* () { + if (!bootstrap?.runSetupScript || !targetWorktreePath) { + return; + } + const worktreePath = targetWorktreePath; + const requestedAt = yield* nowIso; + yield* projectSetupScriptRunner + .runForThread({ + threadId: command.threadId, + ...(targetProjectId ? { projectId: targetProjectId } : {}), + ...(targetProjectCwd ? { projectCwd: targetProjectCwd } : {}), + worktreePath, + }) + .pipe( + Effect.matchEffect({ + onFailure: (error) => + recordSetupScriptLaunchFailure({ + error, + requestedAt, + worktreePath, }), - ); - })() - : Effect.void; + onSuccess: (setupResult) => { + if (setupResult.status !== "started") { + return Effect.void; + } + return recordSetupScriptStarted({ + requestedAt, + worktreePath, + scriptId: setupResult.scriptId, + scriptName: setupResult.scriptName, + terminalId: setupResult.terminalId, + }); + }, + }), + ); + }); const bootstrapProgram = Effect.gen(function* () { if (bootstrap?.createThread) { @@ -608,7 +611,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => `session-stop-for-archive:${normalizedCommand.commandId}`, ), threadId: normalizedCommand.threadId, - createdAt: nowIsoSync(), + createdAt: yield* nowIso, }); yield* dispatchNormalizedCommand(stopCommand); From 00d4b68c04321f13f5710531c8ac713d9b52927d Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 09:21:05 -0700 Subject: [PATCH 5/9] Validate timestamps before storing and comparing them - Reject invalid session exp claims - Use safer DateTime parsing across diagnostics, adapter, and settings --- .../auth/Layers/SessionCredentialService.ts | 9 ++++++- .../src/diagnostics/TraceDiagnostics.ts | 24 +++++-------------- .../src/provider/Layers/OpenCodeAdapter.ts | 18 +++++++------- .../settings/SettingsPanels.browser.tsx | 2 +- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/apps/server/src/auth/Layers/SessionCredentialService.ts b/apps/server/src/auth/Layers/SessionCredentialService.ts index cbc491a6b05..31e2b13240a 100644 --- a/apps/server/src/auth/Layers/SessionCredentialService.ts +++ b/apps/server/src/auth/Layers/SessionCredentialService.ts @@ -312,12 +312,19 @@ export const makeSessionCredentialService = Effect.gen(function* () { }); } + const expiresAt = DateTime.make(claims.exp); + if (Option.isNone(expiresAt)) { + return yield* new SessionCredentialError({ + message: "Invalid `exp` claim", + }); + } + return { sessionId: claims.sid, token, method: claims.method, client: toClientMetadata(row.value.client), - expiresAt: DateTime.makeUnsafe(claims.exp), + expiresAt: expiresAt.value, subject: claims.sub, role: claims.role, } satisfies VerifiedSession; diff --git a/apps/server/src/diagnostics/TraceDiagnostics.ts b/apps/server/src/diagnostics/TraceDiagnostics.ts index d90186a8647..4324478267f 100644 --- a/apps/server/src/diagnostics/TraceDiagnostics.ts +++ b/apps/server/src/diagnostics/TraceDiagnostics.ts @@ -90,21 +90,8 @@ function unixNanoToDateTime(value: unknown): DateTime.Utc | null { const text = toStringValue(value); if (!text) return null; - try { - const millis = Number(BigInt(text) / 1_000_000n); - if (!Number.isFinite(millis)) return null; - return DateTime.makeUnsafe(millis); - } catch { - return null; - } -} - -function isAfter(left: DateTime.Utc, right: DateTime.Utc): boolean { - return DateTime.toEpochMillis(left) > DateTime.toEpochMillis(right); -} - -function isBefore(left: DateTime.Utc, right: DateTime.Utc): boolean { - return DateTime.toEpochMillis(left) < DateTime.toEpochMillis(right); + const millis = Number(BigInt(text) / 1_000_000n); + return Option.getOrNull(DateTime.make(millis)); } function readExitTag(exit: unknown): string | null { @@ -254,10 +241,11 @@ export function aggregateTraceDiagnostics( recordCount += 1; firstSpanAt = - startedAt && (firstSpanAt === null || isBefore(startedAt, firstSpanAt)) + startedAt && (firstSpanAt === null || DateTime.isLessThan(startedAt, firstSpanAt)) ? startedAt : firstSpanAt; - lastSpanAt = lastSpanAt === null || isAfter(endedAt, lastSpanAt) ? endedAt : lastSpanAt; + lastSpanAt = + lastSpanAt === null || DateTime.isGreaterThan(endedAt, lastSpanAt) ? endedAt : lastSpanAt; const exitTag = readExitTag(parsed.exit); const isFailure = exitTag === "Failure"; @@ -289,7 +277,7 @@ export function aggregateTraceDiagnostics( const failureKey = `${name}\0${cause}`; const existing = failuresByKey.get(failureKey); - const isLatestFailure = !existing || isAfter(endedAt, existing.lastSeenAt); + const isLatestFailure = !existing || DateTime.isGreaterThan(endedAt, existing.lastSeenAt); failuresByKey.set(failureKey, { name, cause, diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.ts index 2eb75b24d59..9de9336c1eb 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.ts @@ -48,6 +48,7 @@ import { toOpenCodeQuestionAnswers, type OpenCodeServerConnection, } from "../opencodeRuntime.ts"; +import * as Option from "effect/Option"; const PROVIDER = ProviderDriverKind.make("opencode"); @@ -355,12 +356,13 @@ export function appendOpenCodeAssistantTextDelta( }; } -function isoFromEpochMs(value: number | undefined): string | undefined { - if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) { - return undefined; - } - return DateTime.formatIso(DateTime.makeUnsafe(value)); -} +const isoFromEpochMs = (value: number) => + DateTime.make(value).pipe( + Option.match({ + onNone: () => undefined, + onSome: DateTime.formatIso, + }), + ); function messageRoleForPart( context: OpenCodeSessionContext, @@ -603,8 +605,8 @@ export function makeOpenCodeAdapter( turnId, itemId: part.id, createdAt: - part.type === "text" || part.type === "reasoning" - ? isoFromEpochMs(part.time?.start) + (part.type === "text" || part.type === "reasoning") && part.time !== undefined + ? isoFromEpochMs(part.time.start) : undefined, raw, })), diff --git a/apps/web/src/components/settings/SettingsPanels.browser.tsx b/apps/web/src/components/settings/SettingsPanels.browser.tsx index 1b3c2aec5c6..5dfc1bac044 100644 --- a/apps/web/src/components/settings/SettingsPanels.browser.tsx +++ b/apps/web/src/components/settings/SettingsPanels.browser.tsx @@ -262,7 +262,7 @@ function createOutdatedProvider(driver: string): ServerProvider { } function makeUtc(value: string) { - return DateTime.makeUnsafe(Date.parse(value)); + return DateTime.makeUnsafe(value); } function makePairingLink(input: { From 8611aa7e33f26525d36da882c3f1aa2ef333f34a Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 09:24:48 -0700 Subject: [PATCH 6/9] Fix syntax error in unavailable provider snapshot --- apps/server/src/provider/unavailableProviderSnapshot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/provider/unavailableProviderSnapshot.ts b/apps/server/src/provider/unavailableProviderSnapshot.ts index 66143ee09df..de0c799a8cd 100644 --- a/apps/server/src/provider/unavailableProviderSnapshot.ts +++ b/apps/server/src/provider/unavailableProviderSnapshot.ts @@ -34,7 +34,7 @@ export interface UnavailableProviderSnapshotInput { readonly checkedAt?: string; } -const nowIso = Effect.map(DateTime.now, DateTime.formatIso)) +const nowIso = Effect.map(DateTime.now, DateTime.formatIso); /** * Produce a `ServerProvider` snapshot representing a configured instance From 65402a4106eca3b5ba8559479f52a0732b94de24 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 09:31:16 -0700 Subject: [PATCH 7/9] Handle exceptions in unixNanoToDateTime function to prevent crashes on invalid input --- apps/server/src/diagnostics/TraceDiagnostics.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/server/src/diagnostics/TraceDiagnostics.ts b/apps/server/src/diagnostics/TraceDiagnostics.ts index 4324478267f..ff63410b9bc 100644 --- a/apps/server/src/diagnostics/TraceDiagnostics.ts +++ b/apps/server/src/diagnostics/TraceDiagnostics.ts @@ -89,9 +89,12 @@ function toNumberValue(value: unknown): number | null { function unixNanoToDateTime(value: unknown): DateTime.Utc | null { const text = toStringValue(value); if (!text) return null; - - const millis = Number(BigInt(text) / 1_000_000n); - return Option.getOrNull(DateTime.make(millis)); + try { + const millis = Number(BigInt(text) / 1_000_000n); + return Option.getOrNull(DateTime.make(millis)); + } catch { + return null; + } } function readExitTag(exit: unknown): string | null { From 68775bcc1c9b190ec1cb5012f8856b7d4df24b22 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 09:46:07 -0700 Subject: [PATCH 8/9] Reuse shared pretty JSON schema helpers - Centralize stable 2-space JSON encoding in `packages/shared` - Switch CLI and release-version scripts to the shared helper - Keep keybindings config writes using the same pretty encoder --- apps/server/scripts/cli.ts | 10 ++++++---- apps/server/src/keybindings.ts | 14 ++------------ packages/shared/src/schemaJson.ts | 18 ++++++++++++++++++ .../update-release-package-versions.test.ts | 18 +++++------------- scripts/update-release-package-versions.ts | 12 ++---------- 5 files changed, 33 insertions(+), 39 deletions(-) diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts index fc9b621691b..c7f40de42d7 100644 --- a/apps/server/scripts/cli.ts +++ b/apps/server/scripts/cli.ts @@ -16,6 +16,7 @@ import { PUBLISH_ICON_OVERRIDES, } from "../../../scripts/lib/brand-assets.ts"; import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts"; +import { fromJsonStringPretty } from "@t3tools/shared/schemaJson"; import rootPackageJson from "../../../package.json" with { type: "json" }; import serverPackageJson from "../package.json" with { type: "json" }; @@ -35,6 +36,9 @@ interface PackageJson { overrides: Record; } +const PackageJsonPrettyJson = fromJsonStringPretty(Schema.Unknown); +const encodePackageJson = Schema.encodeEffect(PackageJsonPrettyJson); + class CliError extends Data.TaggedError("CliError")<{ readonly message: string; readonly cause?: unknown; @@ -232,11 +236,9 @@ const publishCmd = Command.make( }; const original = yield* fs.readFileString(packageJsonPath); + const packageJsonString = yield* encodePackageJson(pkg); yield* fs.writeFileString(backupPath, original); - yield* fs.writeFileString( - packageJsonPath, - `${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(pkg)}\n`, - ); + yield* fs.writeFileString(packageJsonPath, `${packageJsonString}\n`); yield* Effect.log("[cli] Prepared package.json for publish"); const iconBackups = yield* applyPublishIconOverrides(repoRoot, serverDir); diff --git a/apps/server/src/keybindings.ts b/apps/server/src/keybindings.ts index 83d303c0fad..38e1153956a 100644 --- a/apps/server/src/keybindings.ts +++ b/apps/server/src/keybindings.ts @@ -33,7 +33,6 @@ import * as Option from "effect/Option"; import * as Predicate from "effect/Predicate"; import * as PubSub from "effect/PubSub"; import * as Schema from "effect/Schema"; -import * as SchemaGetter from "effect/SchemaGetter"; import * as SchemaIssue from "effect/SchemaIssue"; import * as SchemaTransformation from "effect/SchemaTransformation"; import * as Ref from "effect/Ref"; @@ -43,7 +42,7 @@ import * as Stream from "effect/Stream"; import * as Semaphore from "effect/Semaphore"; import { ServerConfig } from "./config.ts"; import { writeFileStringAtomically } from "./atomicWrite.ts"; -import { fromLenientJson } from "@t3tools/shared/schemaJson"; +import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson"; import { DEFAULT_KEYBINDINGS, DEFAULT_RESOLVED_KEYBINDINGS, @@ -170,16 +169,7 @@ function encodeWhenAst(node: KeybindingWhenNode): string { } const RawKeybindingsEntries = fromLenientJson(Schema.Array(Schema.Unknown)); -const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig); -const PrettyJsonString = SchemaGetter.parseJson().compose( - SchemaGetter.stringifyJson({ space: 2 }), -); -const KeybindingsConfigPrettyJson = KeybindingsConfigJson.pipe( - Schema.encode({ - decode: PrettyJsonString, - encode: PrettyJsonString, - }), -); +const KeybindingsConfigPrettyJson = fromJsonStringPretty(KeybindingsConfig); export interface KeybindingsConfigState { readonly keybindings: ResolvedKeybindingsConfig; diff --git a/packages/shared/src/schemaJson.ts b/packages/shared/src/schemaJson.ts index ada6b790656..32abdcd7234 100644 --- a/packages/shared/src/schemaJson.ts +++ b/packages/shared/src/schemaJson.ts @@ -84,6 +84,10 @@ export const fromLenientJsonString = new SchemaTransformation.Transformation( SchemaGetter.stringifyJson(), ); +export const prettyJsonString = SchemaGetter.parseJson().compose( + SchemaGetter.stringifyJson({ space: 2 }), +); + /** * Build a schema that decodes a lenient JSON string into `A`. * @@ -92,3 +96,17 @@ export const fromLenientJsonString = new SchemaTransformation.Transformation( */ export const fromLenientJson = (schema: S) => Schema.String.pipe(Schema.decodeTo(schema, fromLenientJsonString)); + +/** + * Build a JSON string schema that encodes with stable 2-space formatting. + * + * Decode behavior matches `Schema.fromJsonString(schema)`. Encode behavior + * keeps the transformation schema-based while preserving human-readable JSON. + */ +export const fromJsonStringPretty = (schema: S) => + Schema.fromJsonString(schema).pipe( + Schema.encode({ + decode: prettyJsonString, + encode: prettyJsonString, + }), + ); diff --git a/scripts/update-release-package-versions.test.ts b/scripts/update-release-package-versions.test.ts index e5f20d413f7..802e13f35d9 100644 --- a/scripts/update-release-package-versions.test.ts +++ b/scripts/update-release-package-versions.test.ts @@ -6,9 +6,9 @@ import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; -import * as SchemaGetter from "effect/SchemaGetter"; import { Command, CliError } from "effect/unstable/cli"; import * as TestConsole from "effect/testing/TestConsole"; +import { fromJsonStringPretty } from "@t3tools/shared/schemaJson"; import { releasePackageFiles, @@ -19,17 +19,9 @@ import { const ScriptTestLayer = Layer.mergeAll(NodeServices.layer, TestConsole.layer); const runCli = Command.runWith(updateReleasePackageVersionsCommand, { version: "0.0.0" }); const PackageJsonSchema = Schema.Record(Schema.String, Schema.Unknown); -const PrettyJsonString = SchemaGetter.parseJson().compose( - SchemaGetter.stringifyJson({ space: 2 }), -); -const PackageJsonPrettyJson = Schema.fromJsonString(PackageJsonSchema).pipe( - Schema.encode({ - decode: PrettyJsonString, - encode: PrettyJsonString, - }), -); -const decodePackageJson = Schema.decodeUnknownEffect(PackageJsonPrettyJson); -const encodePackageJson = Schema.encodeSync(PackageJsonPrettyJson); +const PackageJsonPrettyJson = fromJsonStringPretty(PackageJsonSchema); +const decodePackageJson = Schema.decodeEffect(PackageJsonPrettyJson); +const encodePackageJson = Schema.encodeEffect(PackageJsonPrettyJson); const writePackageJsonFixtures = Effect.fn("writePackageJsonFixtures")(function* ( rootDir: string, @@ -43,7 +35,7 @@ const writePackageJsonFixtures = Effect.fn("writePackageJsonFixtures")(function* yield* fs.makeDirectory(path.dirname(filePath), { recursive: true }); yield* fs.writeFileString( filePath, - `${encodePackageJson({ + `${yield* encodePackageJson({ name: relativePath, version, private: true, diff --git a/scripts/update-release-package-versions.ts b/scripts/update-release-package-versions.ts index 768e3aad24e..498332cd3bc 100644 --- a/scripts/update-release-package-versions.ts +++ b/scripts/update-release-package-versions.ts @@ -9,8 +9,8 @@ import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; -import * as SchemaGetter from "effect/SchemaGetter"; import { Argument, Command, Flag } from "effect/unstable/cli"; +import { fromJsonStringPretty } from "@t3tools/shared/schemaJson"; export const releasePackageFiles = [ "apps/server/package.json", @@ -24,15 +24,7 @@ interface UpdateReleasePackageVersionsOptions { } const PackageJsonSchema = Schema.Record(Schema.String, Schema.Unknown); -const PrettyJsonString = SchemaGetter.parseJson().compose( - SchemaGetter.stringifyJson({ space: 2 }), -); -const PackageJsonPrettyJson = Schema.fromJsonString(PackageJsonSchema).pipe( - Schema.encode({ - decode: PrettyJsonString, - encode: PrettyJsonString, - }), -); +const PackageJsonPrettyJson = fromJsonStringPretty(PackageJsonSchema); const decodePackageJson = Schema.decodeUnknownEffect(PackageJsonPrettyJson); const encodePackageJson = Schema.encodeSync(PackageJsonPrettyJson); From 1ccd9f4054da0ae2f365b7159738a63455d35fae Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 8 May 2026 09:47:38 -0700 Subject: [PATCH 9/9] Add force-kill timeout to app server spawn - Set a default `forceKillAfter` when spawning the Codex app server - Rely on child-process lifecycle from the spawned handle instead of an explicit release kill --- .../effect-codex-app-server/src/client.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/effect-codex-app-server/src/client.ts b/packages/effect-codex-app-server/src/client.ts index 9821fef8d8e..18a9ed9316c 100644 --- a/packages/effect-codex-app-server/src/client.ts +++ b/packages/effect-codex-app-server/src/client.ts @@ -17,6 +17,8 @@ import { } from "./_internal/shared.ts"; import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts"; +const DEFAULT_APP_SERVER_FORCE_KILL_AFTER = "2 seconds" as const; + export interface CodexAppServerClientOptions { readonly logIncoming?: boolean; readonly logOutgoing?: boolean; @@ -273,26 +275,24 @@ export const layerCommand = ( > => Layer.effect( CodexAppServerClient, - Effect.acquireRelease( - Effect.gen(function* () { - const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; - const command = ChildProcess.make(options.command, [...(options.args ?? [])], { - ...(options.cwd ? { cwd: options.cwd } : {}), - ...(options.env ? { env: { ...process.env, ...options.env } } : {}), - shell: process.platform === "win32", - }); - return yield* spawner.spawn(command).pipe( - Effect.mapError( - (cause) => - new CodexError.CodexAppServerSpawnError({ - command: [options.command, ...(options.args ?? [])].join(" "), - cause, - }), - ), - ); - }), - (handle) => handle.kill().pipe(Effect.orDie), - ).pipe( + Effect.gen(function* () { + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const command = ChildProcess.make(options.command, [...(options.args ?? [])], { + ...(options.cwd ? { cwd: options.cwd } : {}), + ...(options.env ? { env: { ...process.env, ...options.env } } : {}), + forceKillAfter: DEFAULT_APP_SERVER_FORCE_KILL_AFTER, + shell: process.platform === "win32", + }); + return yield* spawner.spawn(command).pipe( + Effect.mapError( + (cause) => + new CodexError.CodexAppServerSpawnError({ + command: [options.command, ...(options.args ?? [])].join(" "), + cause, + }), + ), + ); + }).pipe( Effect.flatMap((handle) => make(makeChildStdio(handle), options, makeTerminationError(handle)), ),