| author | |
| committer | |
| log | 25b02f4ba28d9c6a1adff3475625e5b3fdacce4b |
| tree | cf015483f76067409dc4afcfe3edceea84494560 |
| parent | 131234be3fa53d9b0daff3e8116138d5bad079ba |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Cursor Agent <cursoragent@cursor.com>409 files changed, 3120 insertions(+), 2111 deletions(-)
apps/desktop/src/backend/DesktopBackendManager.test.ts+1-1| ... | @@ -15,7 +15,7 @@ import * as Schema from "effect/Schema"; | ... | @@ -15,7 +15,7 @@ import * as Schema from "effect/Schema"; |
| 15 | import * as Sink from "effect/Sink"; | 15 | import * as Sink from "effect/Sink"; |
| 16 | import * as Scope from "effect/Scope"; | 16 | import * as Scope from "effect/Scope"; |
| 17 | import * as Stream from "effect/Stream"; | 17 | import * as Stream from "effect/Stream"; |
| 18 | import { TestClock } from "effect/testing"; | 18 | import * as TestClock from "effect/testing/TestClock"; |
| 19 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; | 19 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; |
| 20 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 20 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 21 | 21 |
apps/desktop/src/ssh/DesktopSshEnvironment.ts+3-3| ... | @@ -3,7 +3,7 @@ import type { | ... | @@ -3,7 +3,7 @@ import type { |
| 3 | DesktopSshEnvironmentBootstrap, | 3 | DesktopSshEnvironmentBootstrap, |
| 4 | DesktopSshEnvironmentTarget, | 4 | DesktopSshEnvironmentTarget, |
| 5 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { type NetError, NetService } from "@t3tools/shared/Net"; | 6 | import * as NetService from "@t3tools/shared/Net"; |
| 7 | import { | 7 | import { |
| 8 | SshPasswordPrompt, | 8 | SshPasswordPrompt, |
| 9 | type SshPasswordPromptShape, | 9 | type SshPasswordPromptShape, |
| ... | @@ -35,7 +35,7 @@ export type DesktopSshEnvironmentRuntimeServices = | ... | @@ -35,7 +35,7 @@ export type DesktopSshEnvironmentRuntimeServices = |
| 35 | | FileSystem.FileSystem | 35 | | FileSystem.FileSystem |
| 36 | | Path.Path | 36 | | Path.Path |
| 37 | | HttpClient.HttpClient | 37 | | HttpClient.HttpClient |
| 38 | | NetService; | 38 | | NetService.NetService; |
| 39 | 39 | ||
| 40 | export type DesktopSshEnvironmentOperationError = | 40 | export type DesktopSshEnvironmentOperationError = |
| 41 | | SshCommandError | 41 | | SshCommandError |
| ... | @@ -44,7 +44,7 @@ export type DesktopSshEnvironmentOperationError = | ... | @@ -44,7 +44,7 @@ export type DesktopSshEnvironmentOperationError = |
| 44 | | SshPairingError | 44 | | SshPairingError |
| 45 | | SshReadinessError | 45 | | SshReadinessError |
| 46 | | SshPasswordPromptError | 46 | | SshPasswordPromptError |
| 47 | | NetError; | 47 | | NetService.NetError; |
| 48 | 48 | ||
| 49 | export type DesktopSshEnvironmentDiscoverError = SshHostDiscoveryError; | 49 | export type DesktopSshEnvironmentDiscoverError = SshHostDiscoveryError; |
| 50 | 50 |
apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts+1-1| ... | @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; | ... | @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; |
| 4 | import * as Fiber from "effect/Fiber"; | 4 | import * as Fiber from "effect/Fiber"; |
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as Option from "effect/Option"; | 6 | import * as Option from "effect/Option"; |
| 7 | import { TestClock } from "effect/testing"; | 7 | import * as TestClock from "effect/testing/TestClock"; |
| 8 | import type * as Electron from "electron"; | 8 | import type * as Electron from "electron"; |
| 9 | 9 | ||
| 10 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; | 10 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; |
apps/desktop/src/updates/DesktopUpdates.test.ts+1-1| ... | @@ -8,7 +8,7 @@ import * as Effect from "effect/Effect"; | ... | @@ -8,7 +8,7 @@ import * as Effect from "effect/Effect"; |
| 8 | import * as Fiber from "effect/Fiber"; | 8 | import * as Fiber from "effect/Fiber"; |
| 9 | import * as Layer from "effect/Layer"; | 9 | import * as Layer from "effect/Layer"; |
| 10 | import * as Option from "effect/Option"; | 10 | import * as Option from "effect/Option"; |
| 11 | import { TestClock } from "effect/testing"; | 11 | import * as TestClock from "effect/testing/TestClock"; |
| 12 | 12 | ||
| 13 | import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts"; | 13 | import * as DesktopBackendManager from "../backend/DesktopBackendManager.ts"; |
| 14 | import * as DesktopConfig from "../app/DesktopConfig.ts"; | 14 | import * as DesktopConfig from "../app/DesktopConfig.ts"; |
apps/desktop/tsconfig.json+1-13| ... | @@ -3,19 +3,7 @@ | ... | @@ -3,19 +3,7 @@ |
| 3 | "compilerOptions": { | 3 | "compilerOptions": { |
| 4 | "composite": true, | 4 | "composite": true, |
| 5 | "types": ["node", "electron"], | 5 | "types": ["node", "electron"], |
| 6 | "lib": ["ESNext", "DOM", "esnext.disposable"], | 6 | "lib": ["ESNext", "DOM", "esnext.disposable"] |
| 7 | "plugins": [ | ||
| 8 | { | ||
| 9 | "name": "@effect/language-service", | ||
| 10 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 11 | "diagnosticSeverity": { | ||
| 12 | "importFromBarrel": "error", | ||
| 13 | "anyUnknownInErrorContext": "error", | ||
| 14 | "instanceOfSchema": "warning", | ||
| 15 | "deterministicKeys": "warning" | ||
| 16 | } | ||
| 17 | } | ||
| 18 | ] | ||
| 19 | }, | 7 | }, |
| 20 | "include": ["src", "tsdown.config.ts"] | 8 | "include": ["src", "tsdown.config.ts"] |
| 21 | } | 9 | } |
apps/server/integration/OrchestrationEngineHarness.integration.ts+13-14| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { execFileSync } from "node:child_process"; | 2 | import { execFileSync } from "node:child_process"; |
| 2 | 3 | ||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| ... | @@ -8,20 +9,18 @@ import { | ... | @@ -8,20 +9,18 @@ import { |
| 8 | type OrchestrationEvent, | 9 | type OrchestrationEvent, |
| 9 | type OrchestrationThread, | 10 | type OrchestrationThread, |
| 10 | } from "@t3tools/contracts"; | 11 | } from "@t3tools/contracts"; |
| 11 | import { | 12 | import * as Effect from "effect/Effect"; |
| 12 | Effect, | 13 | import * as Exit from "effect/Exit"; |
| 13 | Exit, | 14 | import * as FileSystem from "effect/FileSystem"; |
| 14 | FileSystem, | 15 | import * as Layer from "effect/Layer"; |
| 15 | Layer, | 16 | import * as ManagedRuntime from "effect/ManagedRuntime"; |
| 16 | ManagedRuntime, | 17 | import * as Option from "effect/Option"; |
| 17 | Option, | 18 | import * as Path from "effect/Path"; |
| 18 | Path, | 19 | import * as Ref from "effect/Ref"; |
| 19 | Ref, | 20 | import * as Schedule from "effect/Schedule"; |
| 20 | Schedule, | 21 | import * as Schema from "effect/Schema"; |
| 21 | Schema, | 22 | import * as Scope from "effect/Scope"; |
| 22 | Scope, | 23 | import * as Stream from "effect/Stream"; |
| 23 | Stream, | ||
| 24 | } from "effect"; | ||
| 25 | 24 | ||
| 26 | import { CheckpointStoreLive } from "../src/checkpointing/Layers/CheckpointStore.ts"; | 25 | import { CheckpointStoreLive } from "../src/checkpointing/Layers/CheckpointStore.ts"; |
| 27 | import { CheckpointStore } from "../src/checkpointing/Services/CheckpointStore.ts"; | 26 | import { CheckpointStore } from "../src/checkpointing/Services/CheckpointStore.ts"; |
apps/server/integration/TestProviderAdapter.integration.ts+7-7| ... | @@ -1,5 +1,3 @@ | ... | @@ -1,5 +1,3 @@ |
| 1 | import { randomUUID } from "node:crypto"; | ||
| 2 | |||
| 3 | import { | 1 | import { |
| 4 | ApprovalRequestId, | 2 | ApprovalRequestId, |
| 5 | EventId, | 3 | EventId, |
| ... | @@ -12,7 +10,10 @@ import { | ... | @@ -12,7 +10,10 @@ import { |
| 12 | TurnId, | 10 | TurnId, |
| 13 | ProviderDriverKind, | 11 | ProviderDriverKind, |
| 14 | } from "@t3tools/contracts"; | 12 | } from "@t3tools/contracts"; |
| 15 | import { Effect, Queue, Stream } from "effect"; | 13 | import * as Effect from "effect/Effect"; |
| 14 | import * as Queue from "effect/Queue"; | ||
| 15 | import * as Random from "effect/Random"; | ||
| 16 | import * as Stream from "effect/Stream"; | ||
| 16 | 17 | ||
| 17 | import { | 18 | import { |
| 18 | ProviderAdapterSessionNotFoundError, | 19 | ProviderAdapterSessionNotFoundError, |
| ... | @@ -202,7 +203,7 @@ interface MakeTestProviderAdapterHarnessOptions { | ... | @@ -202,7 +203,7 @@ interface MakeTestProviderAdapterHarnessOptions { |
| 202 | } | 203 | } |
| 203 | 204 | ||
| 204 | function nowIso(): string { | 205 | function nowIso(): string { |
| 205 | return new Date().toISOString(); | 206 | return "2026-01-01T00:00:00.000Z"; |
| 206 | } | 207 | } |
| 207 | 208 | ||
| 208 | function sessionNotFound( | 209 | function sessionNotFound( |
| ... | @@ -308,10 +309,9 @@ export const makeTestProviderAdapterHarness = (options?: MakeTestProviderAdapter | ... | @@ -308,10 +309,9 @@ export const makeTestProviderAdapterHarness = (options?: MakeTestProviderAdapter |
| 308 | for (const fixtureEvent of response.events) { | 309 | for (const fixtureEvent of response.events) { |
| 309 | const rawEvent: Record<string, unknown> = { | 310 | const rawEvent: Record<string, unknown> = { |
| 310 | ...(fixtureEvent as Record<string, unknown>), | 311 | ...(fixtureEvent as Record<string, unknown>), |
| 311 | eventId: randomUUID(), | 312 | eventId: yield* Random.nextUUIDv4, |
| 312 | provider, | 313 | provider, |
| 313 | sessionId: RuntimeSessionId.make(String(input.threadId)), | 314 | sessionId: RuntimeSessionId.make(String(input.threadId)), |
| 314 | createdAt: nowIso(), | ||
| 315 | }; | 315 | }; |
| 316 | rawEvent.threadId = state.snapshot.threadId; | 316 | rawEvent.threadId = state.snapshot.threadId; |
| 317 | if (Object.hasOwn(rawEvent, "turnId")) { | 317 | if (Object.hasOwn(rawEvent, "turnId")) { |
| ... | @@ -366,7 +366,7 @@ export const makeTestProviderAdapterHarness = (options?: MakeTestProviderAdapter | ... | @@ -366,7 +366,7 @@ export const makeTestProviderAdapterHarness = (options?: MakeTestProviderAdapter |
| 366 | if (deferredTurnCompletedEvents.length === 0) { | 366 | if (deferredTurnCompletedEvents.length === 0) { |
| 367 | yield* emit({ | 367 | yield* emit({ |
| 368 | type: "turn.completed", | 368 | type: "turn.completed", |
| 369 | eventId: EventId.make(randomUUID()), | 369 | eventId: EventId.make(yield* Random.nextUUIDv4), |
| 370 | provider, | 370 | provider, |
| 371 | createdAt: nowIso(), | 371 | createdAt: nowIso(), |
| 372 | threadId: state.snapshot.threadId, | 372 | threadId: state.snapshot.threadId, |
apps/server/integration/orchestrationEngine.integration.test.ts+12-5| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import path from "node:path"; | 3 | import path from "node:path"; |
| 3 | 4 | ||
| ... | @@ -17,7 +18,10 @@ import { | ... | @@ -17,7 +18,10 @@ import { |
| 17 | ProviderInstanceId, | 18 | ProviderInstanceId, |
| 18 | } from "@t3tools/contracts"; | 19 | } from "@t3tools/contracts"; |
| 19 | import { assert, it } from "@effect/vitest"; | 20 | import { assert, it } from "@effect/vitest"; |
| 20 | import { Effect, Option, Schema } from "effect"; | 21 | import * as Clock from "effect/Clock"; |
| 22 | import * as Effect from "effect/Effect"; | ||
| 23 | import * as Option from "effect/Option"; | ||
| 24 | import * as Schema from "effect/Schema"; | ||
| 21 | 25 | ||
| 22 | import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts"; | 26 | import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts"; |
| 23 | import { | 27 | import { |
| ... | @@ -47,7 +51,7 @@ const CODEX_PROVIDER = ProviderDriverKind.make("codex"); | ... | @@ -47,7 +51,7 @@ const CODEX_PROVIDER = ProviderDriverKind.make("codex"); |
| 47 | const CLAUDE_AGENT_PROVIDER = ProviderDriverKind.make("claudeAgent"); | 51 | const CLAUDE_AGENT_PROVIDER = ProviderDriverKind.make("claudeAgent"); |
| 48 | 52 | ||
| 49 | function nowIso() { | 53 | function nowIso() { |
| 50 | return new Date().toISOString(); | 54 | return "2026-05-01T00:00:00.000Z"; |
| 51 | } | 55 | } |
| 52 | 56 | ||
| 53 | class IntegrationWaitTimeoutError extends Schema.TaggedErrorClass<IntegrationWaitTimeoutError>()( | 57 | class IntegrationWaitTimeoutError extends Schema.TaggedErrorClass<IntegrationWaitTimeoutError>()( |
| ... | @@ -64,14 +68,14 @@ function waitForSync<A>( | ... | @@ -64,14 +68,14 @@ function waitForSync<A>( |
| 64 | timeoutMs = 10_000, | 68 | timeoutMs = 10_000, |
| 65 | ): Effect.Effect<A, never> { | 69 | ): Effect.Effect<A, never> { |
| 66 | return Effect.gen(function* () { | 70 | return Effect.gen(function* () { |
| 67 | const deadline = Date.now() + timeoutMs; | 71 | const deadline = (yield* Clock.currentTimeMillis) + timeoutMs; |
| 68 | 72 | ||
| 69 | while (true) { | 73 | while (true) { |
| 70 | const value = read(); | 74 | const value = read(); |
| 71 | if (predicate(value)) { | 75 | if (predicate(value)) { |
| 72 | return value; | 76 | return value; |
| 73 | } | 77 | } |
| 74 | if (Date.now() >= deadline) { | 78 | if ((yield* Clock.currentTimeMillis) >= deadline) { |
| 75 | return yield* Effect.die(new IntegrationWaitTimeoutError({ description })); | 79 | return yield* Effect.die(new IntegrationWaitTimeoutError({ description })); |
| 76 | } | 80 | } |
| 77 | yield* Effect.sleep(10); | 81 | yield* Effect.sleep(10); |
| ... | @@ -156,6 +160,7 @@ const startTurn = (input: { | ... | @@ -156,6 +160,7 @@ const startTurn = (input: { |
| 156 | readonly messageId: string; | 160 | readonly messageId: string; |
| 157 | readonly text: string; | 161 | readonly text: string; |
| 158 | readonly modelSelection?: ModelSelection; | 162 | readonly modelSelection?: ModelSelection; |
| 163 | readonly createdAt?: string; | ||
| 159 | }) => | 164 | }) => |
| 160 | input.harness.engine.dispatch({ | 165 | input.harness.engine.dispatch({ |
| 161 | type: "thread.turn.start", | 166 | type: "thread.turn.start", |
| ... | @@ -174,7 +179,7 @@ const startTurn = (input: { | ... | @@ -174,7 +179,7 @@ const startTurn = (input: { |
| 174 | : {}), | 179 | : {}), |
| 175 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | 180 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 176 | runtimeMode: "approval-required", | 181 | runtimeMode: "approval-required", |
| 177 | createdAt: nowIso(), | 182 | createdAt: input.createdAt ?? nowIso(), |
| 178 | }); | 183 | }); |
| 179 | 184 | ||
| 180 | it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () => | 185 | 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 | ... | @@ -755,6 +760,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git |
| 755 | commandId: "cmd-turn-start-revert-1", | 760 | commandId: "cmd-turn-start-revert-1", |
| 756 | messageId: "msg-user-revert-1", | 761 | messageId: "msg-user-revert-1", |
| 757 | text: "First edit", | 762 | text: "First edit", |
| 763 | createdAt: "2026-02-24T10:04:59.900Z", | ||
| 758 | }); | 764 | }); |
| 759 | 765 | ||
| 760 | yield* harness.waitForThread( | 766 | yield* harness.waitForThread( |
| ... | @@ -813,6 +819,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git | ... | @@ -813,6 +819,7 @@ it.live("reverts to an earlier checkpoint and trims checkpoint projections + git |
| 813 | commandId: "cmd-turn-start-revert-2", | 819 | commandId: "cmd-turn-start-revert-2", |
| 814 | messageId: "msg-user-revert-2", | 820 | messageId: "msg-user-revert-2", |
| 815 | text: "Second edit", | 821 | text: "Second edit", |
| 822 | createdAt: "2026-02-24T10:05:00.900Z", | ||
| 816 | }); | 823 | }); |
| 817 | 824 | ||
| 818 | yield* harness.waitForThread( | 825 | yield* harness.waitForThread( |
apps/server/integration/providerService.integration.test.ts+6-1| ... | @@ -3,7 +3,12 @@ import { ProviderDriverKind, ProviderInstanceId, ThreadId } from "@t3tools/contr | ... | @@ -3,7 +3,12 @@ import { ProviderDriverKind, ProviderInstanceId, ThreadId } from "@t3tools/contr |
| 3 | import { DEFAULT_SERVER_SETTINGS } from "@t3tools/contracts/settings"; | 3 | import { DEFAULT_SERVER_SETTINGS } from "@t3tools/contracts/settings"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { it, assert } from "@effect/vitest"; | 5 | import { it, assert } from "@effect/vitest"; |
| 6 | import { Effect, FileSystem, Layer, Path, Queue, Stream } from "effect"; | 6 | import * as Effect from "effect/Effect"; |
| 7 | import * as FileSystem from "effect/FileSystem"; | ||
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as Path from "effect/Path"; | ||
| 10 | import * as Queue from "effect/Queue"; | ||
| 11 | import * as Stream from "effect/Stream"; | ||
| 7 | 12 | ||
| 8 | import { ProviderAdapterRegistry } from "../src/provider/Services/ProviderAdapterRegistry.ts"; | 13 | import { ProviderAdapterRegistry } from "../src/provider/Services/ProviderAdapterRegistry.ts"; |
| 9 | import { makeAdapterRegistryMock } from "../src/provider/testUtils/providerAdapterRegistryMock.ts"; | 14 | import { makeAdapterRegistryMock } from "../src/provider/testUtils/providerAdapterRegistryMock.ts"; |
apps/server/scripts/acp-mock-agent.ts+1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | #!/usr/bin/env bun | 1 | #!/usr/bin/env bun |
| 2 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 2 | import { appendFileSync } from "node:fs"; | 3 | import { appendFileSync } from "node:fs"; |
| 3 | 4 | ||
| 4 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
apps/server/scripts/cli.ts+13-3| ... | @@ -1,8 +1,13 @@ | ... | @@ -1,8 +1,13 @@ |
| 1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
| 2 | |||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 2 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Data, Effect, FileSystem, Logger, Option, Path } from "effect"; | 4 | import * as Data from "effect/Data"; |
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Logger from "effect/Logger"; | ||
| 8 | import * as Option from "effect/Option"; | ||
| 9 | import * as Path from "effect/Path"; | ||
| 10 | import * as Schema from "effect/Schema"; | ||
| 6 | import { Command, Flag } from "effect/unstable/cli"; | 11 | import { Command, Flag } from "effect/unstable/cli"; |
| 7 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 12 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 8 | 13 | ||
| ... | @@ -11,6 +16,7 @@ import { | ... | @@ -11,6 +16,7 @@ import { |
| 11 | PUBLISH_ICON_OVERRIDES, | 16 | PUBLISH_ICON_OVERRIDES, |
| 12 | } from "../../../scripts/lib/brand-assets.ts"; | 17 | } from "../../../scripts/lib/brand-assets.ts"; |
| 13 | import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts"; | 18 | import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts"; |
| 19 | import { fromJsonStringPretty } from "@t3tools/shared/schemaJson"; | ||
| 14 | import rootPackageJson from "../../../package.json" with { type: "json" }; | 20 | import rootPackageJson from "../../../package.json" with { type: "json" }; |
| 15 | import serverPackageJson from "../package.json" with { type: "json" }; | 21 | import serverPackageJson from "../package.json" with { type: "json" }; |
| 16 | 22 | ||
| ... | @@ -30,6 +36,9 @@ interface PackageJson { | ... | @@ -30,6 +36,9 @@ interface PackageJson { |
| 30 | overrides: Record<string, string>; | 36 | overrides: Record<string, string>; |
| 31 | } | 37 | } |
| 32 | 38 | ||
| 39 | const PackageJsonPrettyJson = fromJsonStringPretty(Schema.Unknown); | ||
| 40 | const encodePackageJson = Schema.encodeEffect(PackageJsonPrettyJson); | ||
| 41 | |||
| 33 | class CliError extends Data.TaggedError("CliError")<{ | 42 | class CliError extends Data.TaggedError("CliError")<{ |
| 34 | readonly message: string; | 43 | readonly message: string; |
| 35 | readonly cause?: unknown; | 44 | readonly cause?: unknown; |
| ... | @@ -227,8 +236,9 @@ const publishCmd = Command.make( | ... | @@ -227,8 +236,9 @@ const publishCmd = Command.make( |
| 227 | }; | 236 | }; |
| 228 | 237 | ||
| 229 | const original = yield* fs.readFileString(packageJsonPath); | 238 | const original = yield* fs.readFileString(packageJsonPath); |
| 239 | const packageJsonString = yield* encodePackageJson(pkg); | ||
| 230 | yield* fs.writeFileString(backupPath, original); | 240 | yield* fs.writeFileString(backupPath, original); |
| 231 | yield* fs.writeFileString(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`); | 241 | yield* fs.writeFileString(packageJsonPath, `${packageJsonString}\n`); |
| 232 | yield* Effect.log("[cli] Prepared package.json for publish"); | 242 | yield* Effect.log("[cli] Prepared package.json for publish"); |
| 233 | 243 | ||
| 234 | const iconBackups = yield* applyPublishIconOverrides(repoRoot, serverDir); | 244 | const iconBackups = yield* applyPublishIconOverrides(repoRoot, serverDir); |
apps/server/scripts/cursor-acp-model-mismatch-probe.ts+8-4| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; | 2 | import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; |
| 2 | import process from "node:process"; | 3 | import process from "node:process"; |
| 3 | import readline from "node:readline"; | 4 | import readline from "node:readline"; |
| 5 | import * as NodeTimers from "node:timers"; | ||
| 4 | 6 | ||
| 5 | type JsonPrimitive = null | boolean | number | string; | 7 | type JsonPrimitive = null | boolean | number | string; |
| 6 | type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; | 8 | type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; |
| ... | @@ -114,7 +116,8 @@ function matchesKeyword(option: SessionConfigOption, keyword: string): boolean { | ... | @@ -114,7 +116,8 @@ function matchesKeyword(option: SessionConfigOption, keyword: string): boolean { |
| 114 | 116 | ||
| 115 | function sleep(ms: number) { | 117 | function sleep(ms: number) { |
| 116 | return new Promise<void>((resolve) => { | 118 | return new Promise<void>((resolve) => { |
| 117 | setTimeout(resolve, ms); | 119 | // @effect-diagnostics-next-line globalTimers:off - Standalone Node probe script, not an Effect runtime test. |
| 120 | NodeTimers.setTimeout(resolve, ms); | ||
| 118 | }); | 121 | }); |
| 119 | } | 122 | } |
| 120 | 123 | ||
| ... | @@ -177,7 +180,8 @@ class JsonRpcChild { | ... | @@ -177,7 +180,8 @@ class JsonRpcChild { |
| 177 | const id = this.nextId++; | 180 | const id = this.nextId++; |
| 178 | 181 | ||
| 179 | const responsePromise = new Promise<JsonValue | undefined>((resolve, reject) => { | 182 | const responsePromise = new Promise<JsonValue | undefined>((resolve, reject) => { |
| 180 | const timeout = setTimeout(() => { | 183 | // @effect-diagnostics-next-line globalTimers:off - Standalone Node probe script request timeout. |
| 184 | const timeout = NodeTimers.setTimeout(() => { | ||
| 181 | this.pending.delete(id); | 185 | this.pending.delete(id); |
| 182 | reject(new Error(`Timed out waiting for ${method} response after ${timeoutMs}ms.`)); | 186 | reject(new Error(`Timed out waiting for ${method} response after ${timeoutMs}ms.`)); |
| 183 | }, timeoutMs); | 187 | }, timeoutMs); |
| ... | @@ -185,11 +189,11 @@ class JsonRpcChild { | ... | @@ -185,11 +189,11 @@ class JsonRpcChild { |
| 185 | this.pending.set(id, { | 189 | this.pending.set(id, { |
| 186 | method, | 190 | method, |
| 187 | resolve: (value) => { | 191 | resolve: (value) => { |
| 188 | clearTimeout(timeout); | 192 | NodeTimers.clearTimeout(timeout); |
| 189 | resolve(value); | 193 | resolve(value); |
| 190 | }, | 194 | }, |
| 191 | reject: (error) => { | 195 | reject: (error) => { |
| 192 | clearTimeout(timeout); | 196 | NodeTimers.clearTimeout(timeout); |
| 193 | reject(error); | 197 | reject(error); |
| 194 | }, | 198 | }, |
| 195 | }); | 199 | }); |
apps/server/src/atomicWrite.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { Effect, FileSystem, Path } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import * as Path from "effect/Path"; | ||
| 2 | import * as Random from "effect/Random"; | 4 | import * as Random from "effect/Random"; |
| 3 | 5 | ||
| 4 | export const writeFileStringAtomically = (input: { | 6 | export const writeFileStringAtomically = (input: { |
apps/server/src/attachmentPaths.ts+6-5| ... | @@ -1,9 +1,10 @@ | ... | @@ -1,9 +1,10 @@ |
| 1 | import path from "node:path"; | 1 | // @effect-diagnostics nodeBuiltinImport:off |
| 2 | import NodePath from "node:path"; | ||
| 2 | 3 | ||
| 3 | export const ATTACHMENTS_ROUTE_PREFIX = "/attachments"; | 4 | export const ATTACHMENTS_ROUTE_PREFIX = "/attachments"; |
| 4 | 5 | ||
| 5 | export function normalizeAttachmentRelativePath(rawRelativePath: string): string | null { | 6 | export function normalizeAttachmentRelativePath(rawRelativePath: string): string | null { |
| 6 | const normalized = path.normalize(rawRelativePath).replace(/^[/\\]+/, ""); | 7 | const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, ""); |
| 7 | if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) { | 8 | if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) { |
| 8 | return null; | 9 | return null; |
| 9 | } | 10 | } |
| ... | @@ -19,9 +20,9 @@ export function resolveAttachmentRelativePath(input: { | ... | @@ -19,9 +20,9 @@ export function resolveAttachmentRelativePath(input: { |
| 19 | return null; | 20 | return null; |
| 20 | } | 21 | } |
| 21 | 22 | ||
| 22 | const attachmentsRoot = path.resolve(input.attachmentsDir); | 23 | const attachmentsRoot = NodePath.resolve(input.attachmentsDir); |
| 23 | const filePath = path.resolve(path.join(attachmentsRoot, normalizedRelativePath)); | 24 | const filePath = NodePath.resolve(NodePath.join(attachmentsRoot, normalizedRelativePath)); |
| 24 | if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) { | 25 | if (!filePath.startsWith(`${attachmentsRoot}${NodePath.sep}`)) { |
| 25 | return null; | 26 | return null; |
| 26 | } | 27 | } |
| 27 | return filePath; | 28 | return filePath; |
apps/server/src/attachmentStore.test.ts+1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
apps/server/src/attachmentStore.ts+1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { randomUUID } from "node:crypto"; | 2 | import { randomUUID } from "node:crypto"; |
| 2 | import { existsSync } from "node:fs"; | 3 | import { existsSync } from "node:fs"; |
| 3 | 4 |
apps/server/src/auth/Layers/AuthControlPlane.test.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { expect, it } from "@effect/vitest"; | 2 | import { expect, it } from "@effect/vitest"; |
| 3 | import { Effect, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 4 | 5 | ||
| 5 | import type { ServerConfigShape } from "../../config.ts"; | 6 | import type { ServerConfigShape } from "../../config.ts"; |
| 6 | import { ServerConfig } from "../../config.ts"; | 7 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/auth/Layers/AuthControlPlane.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import type { AuthClientSession, AuthPairingLink } from "@t3tools/contracts"; | 1 | import type { AuthClientSession, AuthPairingLink } from "@t3tools/contracts"; |
| 2 | import { DateTime, Effect, Layer } from "effect"; | 2 | import * as DateTime from "effect/DateTime"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 3 | 5 | ||
| 4 | import { BootstrapCredentialServiceLive } from "./BootstrapCredentialService.ts"; | 6 | import { BootstrapCredentialServiceLive } from "./BootstrapCredentialService.ts"; |
| 5 | import { ServerSecretStoreLive } from "./ServerSecretStore.ts"; | 7 | import { ServerSecretStoreLive } from "./ServerSecretStore.ts"; |
apps/server/src/auth/Layers/BootstrapCredentialService.test.ts+4-2| ... | @@ -1,7 +1,9 @@ | ... | @@ -1,7 +1,9 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { expect, it } from "@effect/vitest"; | 2 | import { expect, it } from "@effect/vitest"; |
| 3 | import { Duration, Effect, Layer } from "effect"; | 3 | import * as Duration from "effect/Duration"; |
| 4 | import { TestClock } from "effect/testing"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as TestClock from "effect/testing/TestClock"; | ||
| 5 | 7 | ||
| 6 | import type { ServerConfigShape } from "../../config.ts"; | 8 | import type { ServerConfigShape } from "../../config.ts"; |
| 7 | import { ServerConfig } from "../../config.ts"; | 9 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/auth/Layers/BootstrapCredentialService.ts+8-2| ... | @@ -1,6 +1,12 @@ | ... | @@ -1,6 +1,12 @@ |
| 1 | import type { AuthPairingLink } from "@t3tools/contracts"; | 1 | import type { AuthPairingLink } from "@t3tools/contracts"; |
| 2 | import { DateTime, Duration, Effect, Layer, PubSub, Ref, Stream } from "effect"; | 2 | import * as DateTime from "effect/DateTime"; |
| 3 | import { Option } from "effect"; | 3 | import * as Duration from "effect/Duration"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as PubSub from "effect/PubSub"; | ||
| 7 | import * as Ref from "effect/Ref"; | ||
| 8 | import * as Stream from "effect/Stream"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 4 | 10 | ||
| 5 | import { ServerConfig } from "../../config.ts"; | 11 | import { ServerConfig } from "../../config.ts"; |
| 6 | import { AuthPairingLinkRepositoryLive } from "../../persistence/Layers/AuthPairingLinks.ts"; | 12 | import { AuthPairingLinkRepositoryLive } from "../../persistence/Layers/AuthPairingLinks.ts"; |
apps/server/src/auth/Layers/ServerAuth.test.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { expect, it } from "@effect/vitest"; | 2 | import { expect, it } from "@effect/vitest"; |
| 3 | import { Effect, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 4 | 5 | ||
| 5 | import type { ServerConfigShape } from "../../config.ts"; | 6 | import type { ServerConfigShape } from "../../config.ts"; |
| 6 | import { ServerConfig } from "../../config.ts"; | 7 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/auth/Layers/ServerAuth.ts+4-1| ... | @@ -6,7 +6,10 @@ import { | ... | @@ -6,7 +6,10 @@ import { |
| 6 | type AuthSessionState, | 6 | type AuthSessionState, |
| 7 | type AuthWebSocketTokenResult, | 7 | type AuthWebSocketTokenResult, |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { DateTime, Effect, Layer, Option } from "effect"; | 9 | import * as DateTime from "effect/DateTime"; |
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Layer from "effect/Layer"; | ||
| 12 | import * as Option from "effect/Option"; | ||
| 10 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; | 13 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 11 | 14 | ||
| 12 | import { AuthControlPlane } from "../Services/AuthControlPlane.ts"; | 15 | import { AuthControlPlane } from "../Services/AuthControlPlane.ts"; |
apps/server/src/auth/Layers/ServerAuthPolicy.test.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { expect, it } from "@effect/vitest"; | 2 | import { expect, it } from "@effect/vitest"; |
| 3 | import { Effect, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 4 | 5 | ||
| 5 | import type { ServerConfigShape } from "../../config.ts"; | 6 | import type { ServerConfigShape } from "../../config.ts"; |
| 6 | import { ServerConfig } from "../../config.ts"; | 7 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/auth/Layers/ServerAuthPolicy.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import type { ServerAuthDescriptor } from "@t3tools/contracts"; | 1 | import type { ServerAuthDescriptor } from "@t3tools/contracts"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | 4 | ||
| 4 | import { ServerConfig } from "../../config.ts"; | 5 | import { ServerConfig } from "../../config.ts"; |
| 5 | import { ServerAuthPolicy, type ServerAuthPolicyShape } from "../Services/ServerAuthPolicy.ts"; | 6 | import { ServerAuthPolicy, type ServerAuthPolicyShape } from "../Services/ServerAuthPolicy.ts"; |
apps/server/src/auth/Layers/ServerSecretStore.test.ts+6-1| ... | @@ -1,6 +1,11 @@ | ... | @@ -1,6 +1,11 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { expect, it } from "@effect/vitest"; | 2 | import { expect, it } from "@effect/vitest"; |
| 3 | import { Cause, Deferred, Effect, FileSystem, Layer, Ref } from "effect"; | 3 | import * as Cause from "effect/Cause"; |
| 4 | import * as Deferred from "effect/Deferred"; | ||
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Layer from "effect/Layer"; | ||
| 8 | import * as Ref from "effect/Ref"; | ||
| 4 | import * as PlatformError from "effect/PlatformError"; | 9 | import * as PlatformError from "effect/PlatformError"; |
| 5 | 10 | ||
| 6 | import { ServerConfig } from "../../config.ts"; | 11 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/auth/Layers/ServerSecretStore.ts+5-1| ... | @@ -1,6 +1,10 @@ | ... | @@ -1,6 +1,10 @@ |
| 1 | import * as Crypto from "node:crypto"; | 1 | import * as Crypto from "node:crypto"; |
| 2 | 2 | ||
| 3 | import { Effect, FileSystem, Layer, Path, Predicate } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 7 | import * as Predicate from "effect/Predicate"; | ||
| 4 | import * as PlatformError from "effect/PlatformError"; | 8 | import * as PlatformError from "effect/PlatformError"; |
| 5 | 9 | ||
| 6 | import { ServerConfig } from "../../config.ts"; | 10 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/auth/Layers/SessionCredentialService.test.ts+4-2| ... | @@ -1,7 +1,9 @@ | ... | @@ -1,7 +1,9 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { expect, it } from "@effect/vitest"; | 2 | import { expect, it } from "@effect/vitest"; |
| 3 | import { Duration, Effect, Layer } from "effect"; | 3 | import * as Duration from "effect/Duration"; |
| 4 | import { TestClock } from "effect/testing"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as TestClock from "effect/testing/TestClock"; | ||
| 5 | 7 | ||
| 6 | import type { ServerConfigShape } from "../../config.ts"; | 8 | import type { ServerConfigShape } from "../../config.ts"; |
| 7 | import { ServerConfig } from "../../config.ts"; | 9 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/auth/Layers/SessionCredentialService.ts+33-5| ... | @@ -1,6 +1,14 @@ | ... | @@ -1,6 +1,14 @@ |
| 1 | import { AuthSessionId, type AuthClientMetadata, type AuthClientSession } from "@t3tools/contracts"; | 1 | import { AuthSessionId, type AuthClientMetadata, type AuthClientSession } from "@t3tools/contracts"; |
| 2 | import { Clock, DateTime, Duration, Effect, Layer, PubSub, Ref, Schema, Stream } from "effect"; | 2 | import * as Clock from "effect/Clock"; |
| 3 | import { Option } from "effect"; | 3 | import * as DateTime from "effect/DateTime"; |
| 4 | import * as Duration from "effect/Duration"; | ||
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 7 | import * as PubSub from "effect/PubSub"; | ||
| 8 | import * as Ref from "effect/Ref"; | ||
| 9 | import * as Schema from "effect/Schema"; | ||
| 10 | import * as Stream from "effect/Stream"; | ||
| 11 | import * as Option from "effect/Option"; | ||
| 4 | 12 | ||
| 5 | import { ServerConfig } from "../../config.ts"; | 13 | import { ServerConfig } from "../../config.ts"; |
| 6 | import { AuthSessionRepositoryLive } from "../../persistence/Layers/AuthSessions.ts"; | 14 | import { AuthSessionRepositoryLive } from "../../persistence/Layers/AuthSessions.ts"; |
| ... | @@ -192,6 +200,7 @@ export const makeSessionCredentialService = Effect.gen(function* () { | ... | @@ -192,6 +200,7 @@ export const makeSessionCredentialService = Effect.gen(function* () { |
| 192 | ), | 200 | ), |
| 193 | ); | 201 | ); |
| 194 | 202 | ||
| 203 | const encodeClaims = Schema.encodeEffect(Schema.fromJsonString(SessionClaims)); | ||
| 195 | const issue: SessionCredentialServiceShape["issue"] = (input) => | 204 | const issue: SessionCredentialServiceShape["issue"] = (input) => |
| 196 | Effect.gen(function* () { | 205 | Effect.gen(function* () { |
| 197 | const sessionId = AuthSessionId.make(crypto.randomUUID()); | 206 | const sessionId = AuthSessionId.make(crypto.randomUUID()); |
| ... | @@ -209,7 +218,13 @@ export const makeSessionCredentialService = Effect.gen(function* () { | ... | @@ -209,7 +218,13 @@ export const makeSessionCredentialService = Effect.gen(function* () { |
| 209 | iat: issuedAt.epochMilliseconds, | 218 | iat: issuedAt.epochMilliseconds, |
| 210 | exp: expiresAt.epochMilliseconds, | 219 | exp: expiresAt.epochMilliseconds, |
| 211 | }; | 220 | }; |
| 212 | const encodedPayload = base64UrlEncode(JSON.stringify(claims)); | 221 | |
| 222 | const encodedPayload = yield* encodeClaims(claims).pipe( | ||
| 223 | Effect.map(base64UrlEncode), | ||
| 224 | Effect.mapError( | ||
| 225 | (cause) => new SessionCredentialError({ message: "Failed to encode claims", cause }), | ||
| 226 | ), | ||
| 227 | ); | ||
| 213 | const signature = signPayload(encodedPayload, signingSecret); | 228 | const signature = signPayload(encodedPayload, signingSecret); |
| 214 | const client = input?.client ?? createDefaultClientMetadata(); | 229 | const client = input?.client ?? createDefaultClientMetadata(); |
| 215 | yield* authSessions.create({ | 230 | yield* authSessions.create({ |
| ... | @@ -297,12 +312,19 @@ export const makeSessionCredentialService = Effect.gen(function* () { | ... | @@ -297,12 +312,19 @@ export const makeSessionCredentialService = Effect.gen(function* () { |
| 297 | }); | 312 | }); |
| 298 | } | 313 | } |
| 299 | 314 | ||
| 315 | const expiresAt = DateTime.make(claims.exp); | ||
| 316 | if (Option.isNone(expiresAt)) { | ||
| 317 | return yield* new SessionCredentialError({ | ||
| 318 | message: "Invalid `exp` claim", | ||
| 319 | }); | ||
| 320 | } | ||
| 321 | |||
| 300 | return { | 322 | return { |
| 301 | sessionId: claims.sid, | 323 | sessionId: claims.sid, |
| 302 | token, | 324 | token, |
| 303 | method: claims.method, | 325 | method: claims.method, |
| 304 | client: toClientMetadata(row.value.client), | 326 | client: toClientMetadata(row.value.client), |
| 305 | expiresAt: DateTime.makeUnsafe(claims.exp), | 327 | expiresAt: expiresAt.value, |
| 306 | subject: claims.sub, | 328 | subject: claims.sub, |
| 307 | role: claims.role, | 329 | role: claims.role, |
| 308 | } satisfies VerifiedSession; | 330 | } satisfies VerifiedSession; |
| ... | @@ -317,6 +339,7 @@ export const makeSessionCredentialService = Effect.gen(function* () { | ... | @@ -317,6 +339,7 @@ export const makeSessionCredentialService = Effect.gen(function* () { |
| 317 | ), | 339 | ), |
| 318 | ); | 340 | ); |
| 319 | 341 | ||
| 342 | const encodeWsClaims = Schema.encodeEffect(Schema.fromJsonString(WebSocketClaims)); | ||
| 320 | const issueWebSocketToken: SessionCredentialServiceShape["issueWebSocketToken"] = ( | 343 | const issueWebSocketToken: SessionCredentialServiceShape["issueWebSocketToken"] = ( |
| 321 | sessionId, | 344 | sessionId, |
| 322 | input, | 345 | input, |
| ... | @@ -333,7 +356,12 @@ export const makeSessionCredentialService = Effect.gen(function* () { | ... | @@ -333,7 +356,12 @@ export const makeSessionCredentialService = Effect.gen(function* () { |
| 333 | iat: issuedAt.epochMilliseconds, | 356 | iat: issuedAt.epochMilliseconds, |
| 334 | exp: expiresAt.epochMilliseconds, | 357 | exp: expiresAt.epochMilliseconds, |
| 335 | }; | 358 | }; |
| 336 | const encodedPayload = base64UrlEncode(JSON.stringify(claims)); | 359 | const encodedPayload = yield* encodeWsClaims(claims).pipe( |
| 360 | Effect.map(base64UrlEncode), | ||
| 361 | Effect.mapError( | ||
| 362 | (cause) => new SessionCredentialError({ message: "Failed to encode claims", cause }), | ||
| 363 | ), | ||
| 364 | ); | ||
| 337 | const signature = signPayload(encodedPayload, signingSecret); | 365 | const signature = signPayload(encodedPayload, signingSecret); |
| 338 | return { | 366 | return { |
| 339 | token: `${encodedPayload}.${signature}`, | 367 | token: `${encodedPayload}.${signature}`, |
apps/server/src/auth/Services/AuthControlPlane.ts+5-1| ... | @@ -4,7 +4,11 @@ import type { | ... | @@ -4,7 +4,11 @@ import type { |
| 4 | AuthPairingLink, | 4 | AuthPairingLink, |
| 5 | AuthSessionId, | 5 | AuthSessionId, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Data, DateTime, Duration, Effect, Context } from "effect"; | 7 | import * as Data from "effect/Data"; |
| 8 | import * as DateTime from "effect/DateTime"; | ||
| 9 | import * as Duration from "effect/Duration"; | ||
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Context from "effect/Context"; | ||
| 8 | import type { SessionRole } from "./SessionCredentialService.ts"; | 12 | import type { SessionRole } from "./SessionCredentialService.ts"; |
| 9 | 13 | ||
| 10 | export const DEFAULT_SESSION_SUBJECT = "cli-issued-session"; | 14 | export const DEFAULT_SESSION_SUBJECT = "cli-issued-session"; |
apps/server/src/auth/Services/BootstrapCredentialService.ts+6-2| ... | @@ -1,6 +1,10 @@ | ... | @@ -1,6 +1,10 @@ |
| 1 | import type { AuthPairingLink, ServerAuthBootstrapMethod } from "@t3tools/contracts"; | 1 | import type { AuthPairingLink, ServerAuthBootstrapMethod } from "@t3tools/contracts"; |
| 2 | import { Data, DateTime, Duration, Context } from "effect"; | 2 | import * as Data from "effect/Data"; |
| 3 | import type { Effect, Stream } from "effect"; | 3 | import * as DateTime from "effect/DateTime"; |
| 4 | import * as Duration from "effect/Duration"; | ||
| 5 | import * as Context from "effect/Context"; | ||
| 6 | import type * as Effect from "effect/Effect"; | ||
| 7 | import type * as Stream from "effect/Stream"; | ||
| 4 | 8 | ||
| 5 | export type BootstrapCredentialRole = "owner" | "client"; | 9 | export type BootstrapCredentialRole = "owner" | "client"; |
| 6 | 10 |
apps/server/src/auth/Services/ServerAuth.ts+4-2| ... | @@ -12,8 +12,10 @@ import type { | ... | @@ -12,8 +12,10 @@ import type { |
| 12 | ServerAuthSessionMethod, | 12 | ServerAuthSessionMethod, |
| 13 | AuthWebSocketTokenResult, | 13 | AuthWebSocketTokenResult, |
| 14 | } from "@t3tools/contracts"; | 14 | } from "@t3tools/contracts"; |
| 15 | import { Data, DateTime, Context } from "effect"; | 15 | import * as Data from "effect/Data"; |
| 16 | import type { Effect } from "effect"; | 16 | import * as DateTime from "effect/DateTime"; |
| 17 | import * as Context from "effect/Context"; | ||
| 18 | import type * as Effect from "effect/Effect"; | ||
| 17 | import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; | 19 | import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 18 | import type { SessionRole } from "./SessionCredentialService.ts"; | 20 | import type { SessionRole } from "./SessionCredentialService.ts"; |
| 19 | 21 |
apps/server/src/auth/Services/ServerAuthPolicy.ts+2-2| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import type { ServerAuthDescriptor } from "@t3tools/contracts"; | 1 | import type { ServerAuthDescriptor } from "@t3tools/contracts"; |
| 2 | import { Context } from "effect"; | 2 | import * as Context from "effect/Context"; |
| 3 | import type { Effect } from "effect"; | 3 | import type * as Effect from "effect/Effect"; |
| 4 | 4 | ||
| 5 | export interface ServerAuthPolicyShape { | 5 | export interface ServerAuthPolicyShape { |
| 6 | readonly getDescriptor: () => Effect.Effect<ServerAuthDescriptor>; | 6 | readonly getDescriptor: () => Effect.Effect<ServerAuthDescriptor>; |
apps/server/src/auth/Services/ServerSecretStore.ts+3-2| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { Data, Context } from "effect"; | 1 | import * as Data from "effect/Data"; |
| 2 | import type { Effect } from "effect"; | 2 | import * as Context from "effect/Context"; |
| 3 | import type * as Effect from "effect/Effect"; | ||
| 3 | 4 | ||
| 4 | export class SecretStoreError extends Data.TaggedError("SecretStoreError")<{ | 5 | export class SecretStoreError extends Data.TaggedError("SecretStoreError")<{ |
| 5 | readonly message: string; | 6 | readonly message: string; |
apps/server/src/auth/Services/SessionCredentialService.ts+6-2| ... | @@ -4,8 +4,12 @@ import type { | ... | @@ -4,8 +4,12 @@ import type { |
| 4 | AuthSessionId, | 4 | AuthSessionId, |
| 5 | ServerAuthSessionMethod, | 5 | ServerAuthSessionMethod, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Data, DateTime, Duration, Context } from "effect"; | 7 | import * as Data from "effect/Data"; |
| 8 | import type { Effect, Stream } from "effect"; | 8 | import * as DateTime from "effect/DateTime"; |
| 9 | import * as Duration from "effect/Duration"; | ||
| 10 | import * as Context from "effect/Context"; | ||
| 11 | import type * as Effect from "effect/Effect"; | ||
| 12 | import type * as Stream from "effect/Stream"; | ||
| 9 | 13 | ||
| 10 | export type SessionRole = "owner" | "client"; | 14 | export type SessionRole = "owner" | "client"; |
| 11 | 15 |
apps/server/src/auth/http.ts+3-1| ... | @@ -6,7 +6,9 @@ import { | ... | @@ -6,7 +6,9 @@ import { |
| 6 | AuthRevokePairingLinkInput, | 6 | AuthRevokePairingLinkInput, |
| 7 | type AuthWebSocketTokenResult, | 7 | type AuthWebSocketTokenResult, |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { DateTime, Effect, Schema } from "effect"; | 9 | import * as DateTime from "effect/DateTime"; |
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Schema from "effect/Schema"; | ||
| 10 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; | 12 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; |
| 11 | 13 | ||
| 12 | import { AuthError, ServerAuth } from "./Services/ServerAuth.ts"; | 14 | import { AuthError, ServerAuth } from "./Services/ServerAuth.ts"; |
apps/server/src/bin.test.ts+7-2| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics-next-line nodeBuiltinImport:off - NodeHttpServer.layer takes `NodeHttp.createServer` as arg | ||
| 1 | import * as NodeHttp from "node:http"; | 2 | import * as NodeHttp from "node:http"; |
| 2 | import { mkdtempSync } from "node:fs"; | 3 | import { mkdtempSync } from "node:fs"; |
| 3 | import { tmpdir } from "node:os"; | 4 | import { tmpdir } from "node:os"; |
| ... | @@ -5,7 +6,7 @@ import { join } from "node:path"; | ... | @@ -5,7 +6,7 @@ import { join } from "node:path"; |
| 5 | 6 | ||
| 6 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; | 7 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; |
| 7 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 8 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 8 | import { NetService } from "@t3tools/shared/Net"; | 9 | import * as NetService from "@t3tools/shared/Net"; |
| 9 | import { assert, it } from "@effect/vitest"; | 10 | import { assert, it } from "@effect/vitest"; |
| 10 | import * as Effect from "effect/Effect"; | 11 | import * as Effect from "effect/Effect"; |
| 11 | import * as Layer from "effect/Layer"; | 12 | import * as Layer from "effect/Layer"; |
| ... | @@ -138,7 +139,7 @@ const withLiveProjectCliServer = <A, E, R>(baseDir: string, run: () => Effect.Ef | ... | @@ -138,7 +139,7 @@ const withLiveProjectCliServer = <A, E, R>(baseDir: string, run: () => Effect.Ef |
| 138 | } | 139 | } |
| 139 | yield* persistServerRuntimeState({ | 140 | yield* persistServerRuntimeState({ |
| 140 | path: config.serverRuntimeStatePath, | 141 | path: config.serverRuntimeStatePath, |
| 141 | state: makePersistedServerRuntimeState({ | 142 | state: yield* makePersistedServerRuntimeState({ |
| 142 | config, | 143 | config, |
| 143 | port: address.port, | 144 | port: address.port, |
| 144 | }), | 145 | }), |
| ... | @@ -179,6 +180,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { | ... | @@ -179,6 +180,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { |
| 179 | const createdOutput = yield* captureStdout( | 180 | const createdOutput = yield* captureStdout( |
| 180 | runCli(["auth", "pairing", "create", "--base-dir", baseDir, "--json"]), | 181 | runCli(["auth", "pairing", "create", "--base-dir", baseDir, "--json"]), |
| 181 | ); | 182 | ); |
| 183 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 182 | const created = JSON.parse(createdOutput.output) as { | 184 | const created = JSON.parse(createdOutput.output) as { |
| 183 | readonly id: string; | 185 | readonly id: string; |
| 184 | readonly credential: string; | 186 | readonly credential: string; |
| ... | @@ -186,6 +188,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { | ... | @@ -186,6 +188,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { |
| 186 | const listedOutput = yield* captureStdout( | 188 | const listedOutput = yield* captureStdout( |
| 187 | runCli(["auth", "pairing", "list", "--base-dir", baseDir, "--json"]), | 189 | runCli(["auth", "pairing", "list", "--base-dir", baseDir, "--json"]), |
| 188 | ); | 190 | ); |
| 191 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 189 | const listed = JSON.parse(listedOutput.output) as ReadonlyArray<{ | 192 | const listed = JSON.parse(listedOutput.output) as ReadonlyArray<{ |
| 190 | readonly id: string; | 193 | readonly id: string; |
| 191 | readonly credential?: string; | 194 | readonly credential?: string; |
| ... | @@ -207,6 +210,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { | ... | @@ -207,6 +210,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { |
| 207 | const issuedOutput = yield* captureStdout( | 210 | const issuedOutput = yield* captureStdout( |
| 208 | runCli(["auth", "session", "issue", "--base-dir", baseDir, "--json"]), | 211 | runCli(["auth", "session", "issue", "--base-dir", baseDir, "--json"]), |
| 209 | ); | 212 | ); |
| 213 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 210 | const issued = JSON.parse(issuedOutput.output) as { | 214 | const issued = JSON.parse(issuedOutput.output) as { |
| 211 | readonly sessionId: string; | 215 | readonly sessionId: string; |
| 212 | readonly token: string; | 216 | readonly token: string; |
| ... | @@ -215,6 +219,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { | ... | @@ -215,6 +219,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { |
| 215 | const listedOutput = yield* captureStdout( | 219 | const listedOutput = yield* captureStdout( |
| 216 | runCli(["auth", "session", "list", "--base-dir", baseDir, "--json"]), | 220 | runCli(["auth", "session", "list", "--base-dir", baseDir, "--json"]), |
| 217 | ); | 221 | ); |
| 222 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 218 | const listed = JSON.parse(listedOutput.output) as ReadonlyArray<{ | 223 | const listed = JSON.parse(listedOutput.output) as ReadonlyArray<{ |
| 219 | readonly sessionId: string; | 224 | readonly sessionId: string; |
| 220 | readonly token?: string; | 225 | readonly token?: string; |
apps/server/src/bin.ts+1-1| ... | @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; | ... | @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 5 | import { Command } from "effect/unstable/cli"; | 5 | import { Command } from "effect/unstable/cli"; |
| 6 | 6 | ||
| 7 | import { NetService } from "@t3tools/shared/Net"; | 7 | import * as NetService from "@t3tools/shared/Net"; |
| 8 | import packageJson from "../package.json" with { type: "json" }; | 8 | import packageJson from "../package.json" with { type: "json" }; |
| 9 | import { authCommand } from "./cli/auth.ts"; | 9 | import { authCommand } from "./cli/auth.ts"; |
| 10 | import { sharedServerCommandFlags } from "./cli/config.ts"; | 10 | import { sharedServerCommandFlags } from "./cli/config.ts"; |
apps/server/src/bootstrap.test.ts+4-2| ... | @@ -1,13 +1,15 @@ | ... | @@ -1,13 +1,15 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as NFS from "node:fs"; | 2 | import * as NFS from "node:fs"; |
| 2 | import * as path from "node:path"; | 3 | import * as path from "node:path"; |
| 3 | import { execFileSync, spawn } from "node:child_process"; | 4 | import { execFileSync, spawn } from "node:child_process"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 5 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { assert, it } from "@effect/vitest"; | 6 | import { assert, it } from "@effect/vitest"; |
| 6 | import { FileSystem, Schema } from "effect"; | 7 | import * as FileSystem from "effect/FileSystem"; |
| 8 | import * as Schema from "effect/Schema"; | ||
| 7 | import * as Duration from "effect/Duration"; | 9 | import * as Duration from "effect/Duration"; |
| 8 | import * as Effect from "effect/Effect"; | 10 | import * as Effect from "effect/Effect"; |
| 9 | import * as Fiber from "effect/Fiber"; | 11 | import * as Fiber from "effect/Fiber"; |
| 10 | import { TestClock } from "effect/testing"; | 12 | import * as TestClock from "effect/testing/TestClock"; |
| 11 | import { vi } from "vitest"; | 13 | import { vi } from "vitest"; |
| 12 | 14 | ||
| 13 | import { readBootstrapEnvelope, resolveFdPath } from "./bootstrap.ts"; | 15 | import { readBootstrapEnvelope, resolveFdPath } from "./bootstrap.ts"; |
apps/server/src/bootstrap.ts+7-1| ... | @@ -1,9 +1,15 @@ | ... | @@ -1,9 +1,15 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as NFS from "node:fs"; | 2 | import * as NFS from "node:fs"; |
| 2 | import * as Net from "node:net"; | 3 | import * as Net from "node:net"; |
| 3 | import * as readline from "node:readline"; | 4 | import * as readline from "node:readline"; |
| 4 | import type { Readable } from "node:stream"; | 5 | import type { Readable } from "node:stream"; |
| 5 | 6 | ||
| 6 | import { Data, Effect, Option, Predicate, Result, Schema } from "effect"; | 7 | import * as Data from "effect/Data"; |
| 8 | import * as Effect from "effect/Effect"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 10 | import * as Predicate from "effect/Predicate"; | ||
| 11 | import * as Result from "effect/Result"; | ||
| 12 | import * as Schema from "effect/Schema"; | ||
| 7 | import { decodeJsonResult } from "@t3tools/shared/schemaJson"; | 13 | import { decodeJsonResult } from "@t3tools/shared/schemaJson"; |
| 8 | 14 | ||
| 9 | class BootstrapError extends Data.TaggedError("BootstrapError")<{ | 15 | class BootstrapError extends Data.TaggedError("BootstrapError")<{ |
apps/server/src/checkpointing/Errors.ts+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Schema } from "effect"; | 1 | import * as Schema from "effect/Schema"; |
| 2 | import type { ProjectionRepositoryError } from "../persistence/Errors.ts"; | 2 | import type { ProjectionRepositoryError } from "../persistence/Errors.ts"; |
| 3 | import type { VcsError } from "@t3tools/contracts"; | 3 | import type { VcsError } from "@t3tools/contracts"; |
| 4 | 4 |
apps/server/src/checkpointing/Layers/CheckpointDiffQuery.test.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { CheckpointRef, ProjectId, ThreadId, TurnId } from "@t3tools/contracts"; | 1 | import { CheckpointRef, ProjectId, ThreadId, TurnId } from "@t3tools/contracts"; |
| 2 | import { Effect, Layer, Option } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 3 | import { describe, expect, it } from "vitest"; | 5 | import { describe, expect, it } from "vitest"; |
| 4 | 6 | ||
| 5 | import { | 7 | import { |
apps/server/src/checkpointing/Layers/CheckpointDiffQuery.ts+4-1| ... | @@ -4,7 +4,10 @@ import { | ... | @@ -4,7 +4,10 @@ import { |
| 4 | type OrchestrationGetFullThreadDiffResult, | 4 | type OrchestrationGetFullThreadDiffResult, |
| 5 | type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType, | 5 | type OrchestrationGetTurnDiffResult as OrchestrationGetTurnDiffResultType, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Effect, Layer, Option, Schema } from "effect"; | 7 | import * as Effect from "effect/Effect"; |
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 10 | import * as Schema from "effect/Schema"; | ||
| 8 | 11 | ||
| 9 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; | 12 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 10 | import { CheckpointInvariantError, CheckpointUnavailableError } from "../Errors.ts"; | 13 | import { CheckpointInvariantError, CheckpointUnavailableError } from "../Errors.ts"; |
apps/server/src/checkpointing/Layers/CheckpointStore.test.ts+6-1| ... | @@ -1,8 +1,13 @@ | ... | @@ -1,8 +1,13 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import path from "node:path"; | 2 | import path from "node:path"; |
| 2 | 3 | ||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { it } from "@effect/vitest"; | 5 | import { it } from "@effect/vitest"; |
| 5 | import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect"; | 6 | import * as Effect from "effect/Effect"; |
| 7 | import * as FileSystem from "effect/FileSystem"; | ||
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as PlatformError from "effect/PlatformError"; | ||
| 10 | import * as Scope from "effect/Scope"; | ||
| 6 | import { describe, expect } from "vitest"; | 11 | import { describe, expect } from "vitest"; |
| 7 | 12 | ||
| 8 | import { checkpointRefForThreadTurn } from "../Utils.ts"; | 13 | import { checkpointRefForThreadTurn } from "../Utils.ts"; |
apps/server/src/checkpointing/Layers/CheckpointStore.ts+4-1| ... | @@ -11,7 +11,10 @@ | ... | @@ -11,7 +11,10 @@ |
| 11 | */ | 11 | */ |
| 12 | import { randomUUID } from "node:crypto"; | 12 | import { randomUUID } from "node:crypto"; |
| 13 | 13 | ||
| 14 | import { Effect, Layer, FileSystem, Path } from "effect"; | 14 | import * as Effect from "effect/Effect"; |
| 15 | import * as Layer from "effect/Layer"; | ||
| 16 | import * as FileSystem from "effect/FileSystem"; | ||
| 17 | import * as Path from "effect/Path"; | ||
| 15 | 18 | ||
| 16 | import { CheckpointInvariantError } from "../Errors.ts"; | 19 | import { CheckpointInvariantError } from "../Errors.ts"; |
| 17 | import { VcsProcessExitError } from "@t3tools/contracts"; | 20 | import { VcsProcessExitError } from "@t3tools/contracts"; |
apps/server/src/checkpointing/Services/CheckpointDiffQuery.ts+2-2| ... | @@ -12,8 +12,8 @@ import type { | ... | @@ -12,8 +12,8 @@ import type { |
| 12 | OrchestrationGetTurnDiffInput, | 12 | OrchestrationGetTurnDiffInput, |
| 13 | OrchestrationGetTurnDiffResult, | 13 | OrchestrationGetTurnDiffResult, |
| 14 | } from "@t3tools/contracts"; | 14 | } from "@t3tools/contracts"; |
| 15 | import { Context } from "effect"; | 15 | import * as Context from "effect/Context"; |
| 16 | import type { Effect } from "effect"; | 16 | import type * as Effect from "effect/Effect"; |
| 17 | 17 | ||
| 18 | import type { CheckpointServiceError } from "../Errors.ts"; | 18 | import type { CheckpointServiceError } from "../Errors.ts"; |
| 19 | 19 |
apps/server/src/checkpointing/Services/CheckpointStore.ts+2-2| ... | @@ -10,8 +10,8 @@ | ... | @@ -10,8 +10,8 @@ |
| 10 | * | 10 | * |
| 11 | * @module CheckpointStore | 11 | * @module CheckpointStore |
| 12 | */ | 12 | */ |
| 13 | import { Context } from "effect"; | 13 | import * as Context from "effect/Context"; |
| 14 | import type { Effect } from "effect"; | 14 | import type * as Effect from "effect/Effect"; |
| 15 | 15 | ||
| 16 | import type { CheckpointStoreError } from "../Errors.ts"; | 16 | import type { CheckpointStoreError } from "../Errors.ts"; |
| 17 | import { CheckpointRef } from "@t3tools/contracts"; | 17 | import { CheckpointRef } from "@t3tools/contracts"; |
apps/server/src/checkpointing/Utils.ts+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Encoding } from "effect"; | 1 | import * as Encoding from "effect/Encoding"; |
| 2 | import { CheckpointRef, ProjectId, type ThreadId } from "@t3tools/contracts"; | 2 | import { CheckpointRef, ProjectId, type ThreadId } from "@t3tools/contracts"; |
| 3 | 3 | ||
| 4 | export const CHECKPOINT_REFS_PREFIX = "refs/t3/checkpoints"; | 4 | export const CHECKPOINT_REFS_PREFIX = "refs/t3/checkpoints"; |
apps/server/src/cli/auth.ts+5-1| ... | @@ -1,5 +1,9 @@ | ... | @@ -1,5 +1,9 @@ |
| 1 | import { AuthSessionId } from "@t3tools/contracts"; | 1 | import { AuthSessionId } from "@t3tools/contracts"; |
| 2 | import { Console, Effect, Layer, Option, References } from "effect"; | 2 | import * as Console from "effect/Console"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import * as References from "effect/References"; | ||
| 3 | import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli"; | 7 | import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli"; |
| 4 | 8 | ||
| 5 | import { AuthControlPlaneRuntimeLive } from "../auth/Layers/AuthControlPlane.ts"; | 9 | import { AuthControlPlaneRuntimeLive } from "../auth/Layers/AuthControlPlane.ts"; |
apps/server/src/cli/config.test.ts+16-9| ... | @@ -1,13 +1,19 @@ | ... | @@ -1,13 +1,19 @@ |
| 1 | import os from "node:os"; | 1 | import NodeOS from "node:os"; |
| 2 | 2 | ||
| 3 | import { assert, expect, it } from "@effect/vitest"; | 3 | import { assert, expect, it } from "@effect/vitest"; |
| 4 | import { ConfigProvider, Effect, FileSystem, Layer, Option, Path, Schema } from "effect"; | 4 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Layer from "effect/Layer"; | ||
| 8 | import * as Option from "effect/Option"; | ||
| 9 | import * as Path from "effect/Path"; | ||
| 10 | import * as Schema from "effect/Schema"; | ||
| 5 | 11 | ||
| 6 | import { | 12 | import { |
| 7 | DesktopBackendBootstrap, | 13 | DesktopBackendBootstrap, |
| 8 | type DesktopBackendBootstrap as DesktopBackendBootstrapValue, | 14 | type DesktopBackendBootstrap as DesktopBackendBootstrapValue, |
| 9 | } from "@t3tools/contracts"; | 15 | } from "@t3tools/contracts"; |
| 10 | import { NetService } from "@t3tools/shared/Net"; | 16 | import * as NetService from "@t3tools/shared/Net"; |
| 11 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 17 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 12 | import { deriveServerPaths } from "../config.ts"; | 18 | import { deriveServerPaths } from "../config.ts"; |
| 13 | import { resolveServerConfig } from "./config.ts"; | 19 | import { resolveServerConfig } from "./config.ts"; |
| ... | @@ -53,7 +59,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -53,7 +59,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 53 | it.effect("falls back to effect/config values when flags are omitted", () => | 59 | it.effect("falls back to effect/config values when flags are omitted", () => |
| 54 | Effect.gen(function* () { | 60 | Effect.gen(function* () { |
| 55 | const { join } = yield* Path.Path; | 61 | const { join } = yield* Path.Path; |
| 56 | const baseDir = join(os.tmpdir(), "t3-cli-config-env-base"); | 62 | const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-env-base"); |
| 57 | const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:5173")); | 63 | const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:5173")); |
| 58 | const resolved = yield* resolveServerConfig( | 64 | const resolved = yield* resolveServerConfig( |
| 59 | { | 65 | { |
| ... | @@ -119,7 +125,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -119,7 +125,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 119 | it.effect("uses CLI flags when provided", () => | 125 | it.effect("uses CLI flags when provided", () => |
| 120 | Effect.gen(function* () { | 126 | Effect.gen(function* () { |
| 121 | const { join } = yield* Path.Path; | 127 | const { join } = yield* Path.Path; |
| 122 | const baseDir = join(os.tmpdir(), "t3-cli-config-flags-base"); | 128 | const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-flags-base"); |
| 123 | const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:4173")); | 129 | const derivedPaths = yield* deriveServerPaths(baseDir, new URL("http://127.0.0.1:4173")); |
| 124 | const resolved = yield* resolveServerConfig( | 130 | const resolved = yield* resolveServerConfig( |
| 125 | { | 131 | { |
| ... | @@ -147,7 +153,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -147,7 +153,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 147 | T3CODE_MODE: "desktop", | 153 | T3CODE_MODE: "desktop", |
| 148 | T3CODE_PORT: "4001", | 154 | T3CODE_PORT: "4001", |
| 149 | T3CODE_HOST: "0.0.0.0", | 155 | T3CODE_HOST: "0.0.0.0", |
| 150 | T3CODE_HOME: join(os.tmpdir(), "ignored-base"), | 156 | T3CODE_HOME: join(NodeOS.tmpdir(), "ignored-base"), |
| 151 | VITE_DEV_SERVER_URL: "http://127.0.0.1:5173", | 157 | VITE_DEV_SERVER_URL: "http://127.0.0.1:5173", |
| 152 | T3CODE_NO_BROWSER: "false", | 158 | T3CODE_NO_BROWSER: "false", |
| 153 | T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false", | 159 | T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false", |
| ... | @@ -185,7 +191,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -185,7 +191,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 185 | it.effect("preserves explicit false CLI boolean flags over env and bootstrap values", () => | 191 | it.effect("preserves explicit false CLI boolean flags over env and bootstrap values", () => |
| 186 | Effect.gen(function* () { | 192 | Effect.gen(function* () { |
| 187 | const { join } = yield* Path.Path; | 193 | const { join } = yield* Path.Path; |
| 188 | const baseDir = join(os.tmpdir(), "t3-cli-config-false-flags"); | 194 | const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-false-flags"); |
| 189 | const fd = yield* openBootstrapFd( | 195 | const fd = yield* openBootstrapFd( |
| 190 | makeDesktopBootstrap({ | 196 | makeDesktopBootstrap({ |
| 191 | noBrowser: true, | 197 | noBrowser: true, |
| ... | @@ -378,7 +384,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -378,7 +384,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 378 | it.effect("applies flag then env precedence over bootstrap envelope values", () => | 384 | it.effect("applies flag then env precedence over bootstrap envelope values", () => |
| 379 | Effect.gen(function* () { | 385 | Effect.gen(function* () { |
| 380 | const { join } = yield* Path.Path; | 386 | const { join } = yield* Path.Path; |
| 381 | const baseDir = join(os.tmpdir(), "t3-cli-config-env-wins"); | 387 | const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-env-wins"); |
| 382 | const fd = yield* openBootstrapFd( | 388 | const fd = yield* openBootstrapFd( |
| 383 | makeDesktopBootstrap({ | 389 | makeDesktopBootstrap({ |
| 384 | port: 4888, | 390 | port: 4888, |
| ... | @@ -459,6 +465,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -459,6 +465,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 459 | yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }); | 465 | yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }); |
| 460 | yield* fs.writeFileString( | 466 | yield* fs.writeFileString( |
| 461 | derivedPaths.settingsPath, | 467 | derivedPaths.settingsPath, |
| 468 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 462 | `${JSON.stringify({ | 469 | `${JSON.stringify({ |
| 463 | observability: { | 470 | observability: { |
| 464 | otlpTracesUrl: "http://localhost:4318/v1/traces", | 471 | otlpTracesUrl: "http://localhost:4318/v1/traces", |
| ... | @@ -521,7 +528,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { | ... | @@ -521,7 +528,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { |
| 521 | it.effect("forces noBrowser and disables auto-bootstrap for headless startup presentation", () => | 528 | it.effect("forces noBrowser and disables auto-bootstrap for headless startup presentation", () => |
| 522 | Effect.gen(function* () { | 529 | Effect.gen(function* () { |
| 523 | const { join } = yield* Path.Path; | 530 | const { join } = yield* Path.Path; |
| 524 | const baseDir = join(os.tmpdir(), "t3-cli-config-headless-base"); | 531 | const baseDir = join(NodeOS.tmpdir(), "t3-cli-config-headless-base"); |
| 525 | const derivedPaths = yield* deriveServerPaths(baseDir, undefined); | 532 | const derivedPaths = yield* deriveServerPaths(baseDir, undefined); |
| 526 | 533 | ||
| 527 | const resolved = yield* resolveServerConfig( | 534 | const resolved = yield* resolveServerConfig( |
apps/server/src/cli/config.ts+12-14| ... | @@ -1,18 +1,16 @@ | ... | @@ -1,18 +1,16 @@ |
| 1 | import { NetService } from "@t3tools/shared/Net"; | 1 | import * as NetService from "@t3tools/shared/Net"; |
| 2 | import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings"; | 2 | import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings"; |
| 3 | import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts"; | 3 | import { DesktopBackendBootstrap, PortSchema } from "@t3tools/contracts"; |
| 4 | import { | 4 | import * as Config from "effect/Config"; |
| 5 | Config, | 5 | import * as Duration from "effect/Duration"; |
| 6 | Duration, | 6 | import * as Effect from "effect/Effect"; |
| 7 | Effect, | 7 | import * as FileSystem from "effect/FileSystem"; |
| 8 | FileSystem, | 8 | import * as LogLevel from "effect/LogLevel"; |
| 9 | LogLevel, | 9 | import * as Option from "effect/Option"; |
| 10 | Option, | 10 | import * as Path from "effect/Path"; |
| 11 | Path, | 11 | import * as Schema from "effect/Schema"; |
| 12 | Schema, | 12 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 13 | SchemaIssue, | 13 | import * as SchemaTransformation from "effect/SchemaTransformation"; |
| 14 | SchemaTransformation, | ||
| 15 | } from "effect"; | ||
| 16 | import { Argument, Flag } from "effect/unstable/cli"; | 14 | import { Argument, Flag } from "effect/unstable/cli"; |
| 17 | 15 | ||
| 18 | import { readBootstrapEnvelope } from "../bootstrap.ts"; | 16 | import { readBootstrapEnvelope } from "../bootstrap.ts"; |
| ... | @@ -215,7 +213,7 @@ export const resolveServerConfig = ( | ... | @@ -215,7 +213,7 @@ export const resolveServerConfig = ( |
| 215 | }, | 213 | }, |
| 216 | ) => | 214 | ) => |
| 217 | Effect.gen(function* () { | 215 | Effect.gen(function* () { |
| 218 | const { findAvailablePort } = yield* NetService; | 216 | const { findAvailablePort } = yield* NetService.NetService; |
| 219 | const path = yield* Path.Path; | 217 | const path = yield* Path.Path; |
| 220 | const fs = yield* FileSystem.FileSystem; | 218 | const fs = yield* FileSystem.FileSystem; |
| 221 | const env = yield* EnvServerConfig; | 219 | const env = yield* EnvServerConfig; |
apps/server/src/cli/project.ts+27-21| ... | @@ -4,18 +4,18 @@ import { | ... | @@ -4,18 +4,18 @@ import { |
| 4 | ProjectId, | 4 | ProjectId, |
| 5 | type ClientOrchestrationCommand, | 5 | type ClientOrchestrationCommand, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { | 7 | import * as Console from "effect/Console"; |
| 8 | Console, | 8 | import * as Data from "effect/Data"; |
| 9 | Duration, | 9 | import * as DateTime from "effect/DateTime"; |
| 10 | Effect, | 10 | import * as Duration from "effect/Duration"; |
| 11 | Exit, | 11 | import * as Effect from "effect/Effect"; |
| 12 | FileSystem, | 12 | import * as Exit from "effect/Exit"; |
| 13 | Layer, | 13 | import * as FileSystem from "effect/FileSystem"; |
| 14 | Option, | 14 | import * as Layer from "effect/Layer"; |
| 15 | Path, | 15 | import * as Option from "effect/Option"; |
| 16 | References, | 16 | import * as Path from "effect/Path"; |
| 17 | Schema, | 17 | import * as References from "effect/References"; |
| 18 | } from "effect"; | 18 | import * as Schema from "effect/Schema"; |
| 19 | import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli"; | 19 | import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli"; |
| 20 | import { | 20 | import { |
| 21 | FetchHttpClient, | 21 | FetchHttpClient, |
| ... | @@ -54,6 +54,10 @@ type ProjectCliDispatchCommand = Extract< | ... | @@ -54,6 +54,10 @@ type ProjectCliDispatchCommand = Extract< |
| 54 | { type: "project.create" | "project.meta.update" | "project.delete" } | 54 | { type: "project.create" | "project.meta.update" | "project.delete" } |
| 55 | >; | 55 | >; |
| 56 | 56 | ||
| 57 | class ProjectCommandError extends Data.TaggedError("ProjectCommandError")<{ | ||
| 58 | readonly message: string; | ||
| 59 | }> {} | ||
| 60 | |||
| 57 | const ProjectCliRuntimeLive = Layer.mergeAll( | 61 | const ProjectCliRuntimeLive = Layer.mergeAll( |
| 58 | WorkspacePathsLive, | 62 | WorkspacePathsLive, |
| 59 | OrchestrationLayerLive.pipe( | 63 | OrchestrationLayerLive.pipe( |
| ... | @@ -124,7 +128,7 @@ const resolveProjectTitle = Effect.fn("resolveProjectTitle")(function* ( | ... | @@ -124,7 +128,7 @@ const resolveProjectTitle = Effect.fn("resolveProjectTitle")(function* ( |
| 124 | if (trimmed.length > 0) { | 128 | if (trimmed.length > 0) { |
| 125 | return trimmed; | 129 | return trimmed; |
| 126 | } | 130 | } |
| 127 | return yield* Effect.fail(new Error("Project title cannot be empty.")); | 131 | return yield* new ProjectCommandError({ message: "Project title cannot be empty." }); |
| 128 | } | 132 | } |
| 129 | 133 | ||
| 130 | const path = yield* Path.Path; | 134 | const path = yield* Path.Path; |
| ... | @@ -138,7 +142,7 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* ( | ... | @@ -138,7 +142,7 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* ( |
| 138 | }) { | 142 | }) { |
| 139 | const trimmedIdentifier = input.identifier.trim(); | 143 | const trimmedIdentifier = input.identifier.trim(); |
| 140 | if (trimmedIdentifier.length === 0) { | 144 | if (trimmedIdentifier.length === 0) { |
| 141 | return yield* Effect.fail(new Error("Project identifier cannot be empty.")); | 145 | return yield* new ProjectCommandError({ message: "Project identifier cannot be empty." }); |
| 142 | } | 146 | } |
| 143 | 147 | ||
| 144 | const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null); | 148 | const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null); |
| ... | @@ -165,7 +169,9 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* ( | ... | @@ -165,7 +169,9 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* ( |
| 165 | 169 | ||
| 166 | const resolved = exactWorkspaceMatch; | 170 | const resolved = exactWorkspaceMatch; |
| 167 | if (!resolved) { | 171 | if (!resolved) { |
| 168 | return yield* Effect.fail(new Error(`No active project found for '${trimmedIdentifier}'.`)); | 172 | return yield* new ProjectCommandError({ |
| 173 | message: `No active project found for '${trimmedIdentifier}'.`, | ||
| 174 | }); | ||
| 169 | } | 175 | } |
| 170 | 176 | ||
| 171 | return { | 177 | return { |
| ... | @@ -185,7 +191,7 @@ const fetchLiveOrchestrationSnapshot = (origin: string, bearerToken: string) => | ... | @@ -185,7 +191,7 @@ const fetchLiveOrchestrationSnapshot = (origin: string, bearerToken: string) => |
| 185 | "2xx": decodeOrchestrationReadModelResponse, | 191 | "2xx": decodeOrchestrationReadModelResponse, |
| 186 | orElse: (response) => | 192 | orElse: (response) => |
| 187 | readErrorMessageFromResponse(response).pipe( | 193 | readErrorMessageFromResponse(response).pipe( |
| 188 | Effect.flatMap((message) => Effect.fail(new Error(message))), | 194 | Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))), |
| 189 | ), | 195 | ), |
| 190 | }), | 196 | }), |
| 191 | ); | 197 | ); |
| ... | @@ -206,7 +212,7 @@ const dispatchLiveOrchestrationCommand = ( | ... | @@ -206,7 +212,7 @@ const dispatchLiveOrchestrationCommand = ( |
| 206 | "2xx": () => Effect.void, | 212 | "2xx": () => Effect.void, |
| 207 | orElse: (response) => | 213 | orElse: (response) => |
| 208 | readErrorMessageFromResponse(response).pipe( | 214 | readErrorMessageFromResponse(response).pipe( |
| 209 | Effect.flatMap((message) => Effect.fail(new Error(message))), | 215 | Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))), |
| 210 | ), | 216 | ), |
| 211 | }), | 217 | }), |
| 212 | ), | 218 | ), |
| ... | @@ -331,9 +337,9 @@ const projectAddCommand = Command.make("add", { | ... | @@ -331,9 +337,9 @@ const projectAddCommand = Command.make("add", { |
| 331 | (project) => project.deletedAt === null && project.workspaceRoot === workspaceRoot, | 337 | (project) => project.deletedAt === null && project.workspaceRoot === workspaceRoot, |
| 332 | ); | 338 | ); |
| 333 | if (existingProject) { | 339 | if (existingProject) { |
| 334 | return yield* Effect.fail( | 340 | return yield* new ProjectCommandError({ |
| 335 | new Error(`An active project already exists for '${workspaceRoot}'.`), | 341 | message: `An active project already exists for '${workspaceRoot}'.`, |
| 336 | ); | 342 | }); |
| 337 | } | 343 | } |
| 338 | 344 | ||
| 339 | const title = yield* resolveProjectTitle(workspaceRoot, Option.getOrUndefined(flags.title)); | 345 | const title = yield* resolveProjectTitle(workspaceRoot, Option.getOrUndefined(flags.title)); |
| ... | @@ -345,7 +351,7 @@ const projectAddCommand = Command.make("add", { | ... | @@ -345,7 +351,7 @@ const projectAddCommand = Command.make("add", { |
| 345 | title, | 351 | title, |
| 346 | workspaceRoot, | 352 | workspaceRoot, |
| 347 | defaultModelSelection: getAutoBootstrapDefaultModelSelection(), | 353 | defaultModelSelection: getAutoBootstrapDefaultModelSelection(), |
| 348 | createdAt: new Date().toISOString(), | 354 | createdAt: DateTime.formatIso(yield* DateTime.now), |
| 349 | }); | 355 | }); |
| 350 | return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; | 356 | return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; |
| 351 | }), | 357 | }), |
apps/server/src/cli/server.ts+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Effect } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import { Command, GlobalFlag } from "effect/unstable/cli"; | 2 | import { Command, GlobalFlag } from "effect/unstable/cli"; |
| 3 | 3 | ||
| 4 | import { ServerConfig, type StartupPresentation } from "../config.ts"; | 4 | import { ServerConfig, type StartupPresentation } from "../config.ts"; |
apps/server/src/cliAuthFormat.test.ts+8-8| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import { expect, it } from "@effect/vitest"; | 1 | import { expect, it } from "@effect/vitest"; |
| 2 | import { DateTime } from "effect"; | 2 | import * as DateTime from "effect/DateTime"; |
| 3 | 3 | ||
| 4 | import { | 4 | import { |
| 5 | formatIssuedPairingCredential, | 5 | formatIssuedPairingCredential, |
| ... | @@ -15,8 +15,8 @@ it("formats issued pairing credentials with the secret and optional pair URL", ( | ... | @@ -15,8 +15,8 @@ it("formats issued pairing credentials with the secret and optional pair URL", ( |
| 15 | credential: "secret-pairing-token", | 15 | credential: "secret-pairing-token", |
| 16 | role: "client", | 16 | role: "client", |
| 17 | subject: "one-time-token", | 17 | subject: "one-time-token", |
| 18 | createdAt: DateTime.fromDateUnsafe(new Date("2026-04-08T09:00:00.000Z")), | 18 | createdAt: DateTime.makeUnsafe("2026-04-08T09:00:00.000Z"), |
| 19 | expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), | 19 | expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), |
| 20 | }, | 20 | }, |
| 21 | { baseUrl: "https://example.com", json: false }, | 21 | { baseUrl: "https://example.com", json: false }, |
| 22 | ); | 22 | ); |
| ... | @@ -34,8 +34,8 @@ it("formats pairing listings without exposing the secret token", () => { | ... | @@ -34,8 +34,8 @@ it("formats pairing listings without exposing the secret token", () => { |
| 34 | subject: "one-time-token", | 34 | subject: "one-time-token", |
| 35 | label: "Phone", | 35 | label: "Phone", |
| 36 | role: "client", | 36 | role: "client", |
| 37 | createdAt: DateTime.fromDateUnsafe(new Date("2026-04-08T09:00:00.000Z")), | 37 | createdAt: DateTime.makeUnsafe("2026-04-08T09:00:00.000Z"), |
| 38 | expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), | 38 | expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), |
| 39 | }, | 39 | }, |
| 40 | ], | 40 | ], |
| 41 | { json: false }, | 41 | { json: false }, |
| ... | @@ -57,7 +57,7 @@ it("formats issued sessions with the bearer token but omits tokens from listings | ... | @@ -57,7 +57,7 @@ it("formats issued sessions with the bearer token but omits tokens from listings |
| 57 | label: "deploy-bot", | 57 | label: "deploy-bot", |
| 58 | deviceType: "bot", | 58 | deviceType: "bot", |
| 59 | }, | 59 | }, |
| 60 | expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), | 60 | expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), |
| 61 | }, | 61 | }, |
| 62 | { json: false }, | 62 | { json: false }, |
| 63 | ); | 63 | ); |
| ... | @@ -75,8 +75,8 @@ it("formats issued sessions with the bearer token but omits tokens from listings | ... | @@ -75,8 +75,8 @@ it("formats issued sessions with the bearer token but omits tokens from listings |
| 75 | }, | 75 | }, |
| 76 | connected: false, | 76 | connected: false, |
| 77 | current: false, | 77 | current: false, |
| 78 | issuedAt: DateTime.fromDateUnsafe(new Date("2026-04-08T09:00:00.000Z")), | 78 | issuedAt: DateTime.makeUnsafe("2026-04-08T09:00:00.000Z"), |
| 79 | expiresAt: DateTime.fromDateUnsafe(new Date("2026-04-08T10:00:00.000Z")), | 79 | expiresAt: DateTime.makeUnsafe("2026-04-08T10:00:00.000Z"), |
| 80 | lastConnectedAt: null, | 80 | lastConnectedAt: null, |
| 81 | }, | 81 | }, |
| 82 | ], | 82 | ], |
apps/server/src/cliAuthFormat.ts+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import type { AuthClientMetadata, AuthClientSession, AuthPairingLink } from "@t3tools/contracts"; | 1 | import type { AuthClientMetadata, AuthClientSession, AuthPairingLink } from "@t3tools/contracts"; |
| 2 | import { DateTime } from "effect"; | 2 | import * as DateTime from "effect/DateTime"; |
| 3 | 3 | ||
| 4 | import type { IssuedBearerSession, IssuedPairingLink } from "./auth/Services/AuthControlPlane.ts"; | 4 | import type { IssuedBearerSession, IssuedPairingLink } from "./auth/Services/AuthControlPlane.ts"; |
| 5 | 5 |
apps/server/src/config.ts+7-1| ... | @@ -6,7 +6,13 @@ | ... | @@ -6,7 +6,13 @@ |
| 6 | * | 6 | * |
| 7 | * @module ServerConfig | 7 | * @module ServerConfig |
| 8 | */ | 8 | */ |
| 9 | import { Effect, FileSystem, Layer, LogLevel, Path, Schema, Context } from "effect"; | 9 | import * as Effect from "effect/Effect"; |
| 10 | import * as FileSystem from "effect/FileSystem"; | ||
| 11 | import * as Layer from "effect/Layer"; | ||
| 12 | import * as LogLevel from "effect/LogLevel"; | ||
| 13 | import * as Path from "effect/Path"; | ||
| 14 | import * as Schema from "effect/Schema"; | ||
| 15 | import * as Context from "effect/Context"; | ||
| 10 | 16 | ||
| 11 | export const DEFAULT_PORT = 3773; | 17 | export const DEFAULT_PORT = 3773; |
| 12 | 18 |
apps/server/src/diagnostics/TraceDiagnostics.ts+5-14| ... | @@ -89,24 +89,14 @@ function toNumberValue(value: unknown): number | null { | ... | @@ -89,24 +89,14 @@ function toNumberValue(value: unknown): number | null { |
| 89 | function unixNanoToDateTime(value: unknown): DateTime.Utc | null { | 89 | function unixNanoToDateTime(value: unknown): DateTime.Utc | null { |
| 90 | const text = toStringValue(value); | 90 | const text = toStringValue(value); |
| 91 | if (!text) return null; | 91 | if (!text) return null; |
| 92 | |||
| 93 | try { | 92 | try { |
| 94 | const millis = Number(BigInt(text) / 1_000_000n); | 93 | const millis = Number(BigInt(text) / 1_000_000n); |
| 95 | if (!Number.isFinite(millis)) return null; | 94 | return Option.getOrNull(DateTime.make(millis)); |
| 96 | return DateTime.makeUnsafe(millis); | ||
| 97 | } catch { | 95 | } catch { |
| 98 | return null; | 96 | return null; |
| 99 | } | 97 | } |
| 100 | } | 98 | } |
| 101 | 99 | ||
| 102 | function isAfter(left: DateTime.Utc, right: DateTime.Utc): boolean { | ||
| 103 | return DateTime.toEpochMillis(left) > DateTime.toEpochMillis(right); | ||
| 104 | } | ||
| 105 | |||
| 106 | function isBefore(left: DateTime.Utc, right: DateTime.Utc): boolean { | ||
| 107 | return DateTime.toEpochMillis(left) < DateTime.toEpochMillis(right); | ||
| 108 | } | ||
| 109 | |||
| 110 | function readExitTag(exit: unknown): string | null { | 100 | function readExitTag(exit: unknown): string | null { |
| 111 | if (!isRecordObject(exit) || !("_tag" in exit)) return null; | 101 | if (!isRecordObject(exit) || !("_tag" in exit)) return null; |
| 112 | return toStringValue(exit._tag); | 102 | return toStringValue(exit._tag); |
| ... | @@ -254,10 +244,11 @@ export function aggregateTraceDiagnostics( | ... | @@ -254,10 +244,11 @@ export function aggregateTraceDiagnostics( |
| 254 | 244 | ||
| 255 | recordCount += 1; | 245 | recordCount += 1; |
| 256 | firstSpanAt = | 246 | firstSpanAt = |
| 257 | startedAt && (firstSpanAt === null || isBefore(startedAt, firstSpanAt)) | 247 | startedAt && (firstSpanAt === null || DateTime.isLessThan(startedAt, firstSpanAt)) |
| 258 | ? startedAt | 248 | ? startedAt |
| 259 | : firstSpanAt; | 249 | : firstSpanAt; |
| 260 | lastSpanAt = lastSpanAt === null || isAfter(endedAt, lastSpanAt) ? endedAt : lastSpanAt; | 250 | lastSpanAt = |
| 251 | lastSpanAt === null || DateTime.isGreaterThan(endedAt, lastSpanAt) ? endedAt : lastSpanAt; | ||
| 261 | 252 | ||
| 262 | const exitTag = readExitTag(parsed.exit); | 253 | const exitTag = readExitTag(parsed.exit); |
| 263 | const isFailure = exitTag === "Failure"; | 254 | const isFailure = exitTag === "Failure"; |
| ... | @@ -289,7 +280,7 @@ export function aggregateTraceDiagnostics( | ... | @@ -289,7 +280,7 @@ export function aggregateTraceDiagnostics( |
| 289 | 280 | ||
| 290 | const failureKey = `${name}\0${cause}`; | 281 | const failureKey = `${name}\0${cause}`; |
| 291 | const existing = failuresByKey.get(failureKey); | 282 | const existing = failuresByKey.get(failureKey); |
| 292 | const isLatestFailure = !existing || isAfter(endedAt, existing.lastSeenAt); | 283 | const isLatestFailure = !existing || DateTime.isGreaterThan(endedAt, existing.lastSeenAt); |
| 293 | failuresByKey.set(failureKey, { | 284 | failuresByKey.set(failureKey, { |
| 294 | name, | 285 | name, |
| 295 | cause, | 286 | cause, |
apps/server/src/environment/Layers/ServerEnvironment.test.ts+6-1| ... | @@ -1,7 +1,12 @@ | ... | @@ -1,7 +1,12 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as nodePath from "node:path"; | 2 | import * as nodePath from "node:path"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { expect, it } from "@effect/vitest"; | 4 | import { expect, it } from "@effect/vitest"; |
| 4 | import { Effect, Exit, FileSystem, Layer, PlatformError } from "effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Exit from "effect/Exit"; | ||
| 7 | import * as FileSystem from "effect/FileSystem"; | ||
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as PlatformError from "effect/PlatformError"; | ||
| 5 | 10 | ||
| 6 | import { deriveServerPaths, ServerConfig, type ServerConfigShape } from "../../config.ts"; | 11 | import { deriveServerPaths, ServerConfig, type ServerConfigShape } from "../../config.ts"; |
| 7 | import { ServerEnvironment } from "../Services/ServerEnvironment.ts"; | 12 | import { ServerEnvironment } from "../Services/ServerEnvironment.ts"; |
apps/server/src/environment/Layers/ServerEnvironment.ts+5-1| ... | @@ -1,5 +1,9 @@ | ... | @@ -1,5 +1,9 @@ |
| 1 | import { EnvironmentId, type ExecutionEnvironmentDescriptor } from "@t3tools/contracts"; | 1 | import { EnvironmentId, type ExecutionEnvironmentDescriptor } from "@t3tools/contracts"; |
| 2 | import { Effect, FileSystem, Layer, Path, Random } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as FileSystem from "effect/FileSystem"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Path from "effect/Path"; | ||
| 6 | import * as Random from "effect/Random"; | ||
| 3 | 7 | ||
| 4 | import { ServerConfig } from "../../config.ts"; | 8 | import { ServerConfig } from "../../config.ts"; |
| 5 | import { ServerEnvironment, type ServerEnvironmentShape } from "../Services/ServerEnvironment.ts"; | 9 | import { ServerEnvironment, type ServerEnvironmentShape } from "../Services/ServerEnvironment.ts"; |
apps/server/src/environment/Layers/ServerEnvironmentLabel.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { afterEach, describe, expect, it } from "@effect/vitest"; | 1 | import { afterEach, describe, expect, it } from "@effect/vitest"; |
| 2 | import { Effect, FileSystem } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import { vi } from "vitest"; | 4 | import { vi } from "vitest"; |
| 4 | 5 | ||
| 5 | vi.mock("../../processRunner.ts", () => ({ | 6 | vi.mock("../../processRunner.ts", () => ({ |
apps/server/src/environment/Layers/ServerEnvironmentLabel.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import * as OS from "node:os"; | 1 | import * as OS from "node:os"; |
| 2 | 2 | ||
| 3 | import { Effect, FileSystem } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 4 | 5 | ||
| 5 | import { runProcess } from "../../processRunner.ts"; | 6 | import { runProcess } from "../../processRunner.ts"; |
| 6 | 7 |
apps/server/src/environment/Services/ServerEnvironment.ts+2-2| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import type { EnvironmentId, ExecutionEnvironmentDescriptor } from "@t3tools/contracts"; | 1 | import type { EnvironmentId, ExecutionEnvironmentDescriptor } from "@t3tools/contracts"; |
| 2 | import { Context } from "effect"; | 2 | import * as Context from "effect/Context"; |
| 3 | import type { Effect } from "effect"; | 3 | import type * as Effect from "effect/Effect"; |
| 4 | 4 | ||
| 5 | export interface ServerEnvironmentShape { | 5 | export interface ServerEnvironmentShape { |
| 6 | readonly getEnvironmentId: Effect.Effect<EnvironmentId>; | 6 | readonly getEnvironmentId: Effect.Effect<EnvironmentId>; |
apps/server/src/git/GitManager.test.ts+58-4| ... | @@ -1,10 +1,15 @@ | ... | @@ -1,10 +1,15 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import path from "node:path"; | 3 | import path from "node:path"; |
| 3 | import { spawnSync } from "node:child_process"; | 4 | import { spawnSync } from "node:child_process"; |
| 4 | 5 | ||
| 5 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 6 | import { it } from "@effect/vitest"; | 7 | import { it } from "@effect/vitest"; |
| 7 | import { Effect, FileSystem, Layer, PlatformError, Scope } from "effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as FileSystem from "effect/FileSystem"; | ||
| 10 | import * as Layer from "effect/Layer"; | ||
| 11 | import * as PlatformError from "effect/PlatformError"; | ||
| 12 | import * as Scope from "effect/Scope"; | ||
| 8 | import { ChildProcessSpawner } from "effect/unstable/process"; | 13 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 9 | import { expect } from "vitest"; | 14 | import { expect } from "vitest"; |
| 10 | import type { | 15 | import type { |
| ... | @@ -32,6 +37,7 @@ import { ServerConfig } from "../config.ts"; | ... | @@ -32,6 +37,7 @@ import { ServerConfig } from "../config.ts"; |
| 32 | import { ServerSettingsService } from "../serverSettings.ts"; | 37 | import { ServerSettingsService } from "../serverSettings.ts"; |
| 33 | import { | 38 | import { |
| 34 | ProjectSetupScriptRunner, | 39 | ProjectSetupScriptRunner, |
| 40 | ProjectSetupScriptRunnerError, | ||
| 35 | type ProjectSetupScriptRunnerInput, | 41 | type ProjectSetupScriptRunnerInput, |
| 36 | type ProjectSetupScriptRunnerShape, | 42 | type ProjectSetupScriptRunnerShape, |
| 37 | } from "../project/Services/ProjectSetupScriptRunner.ts"; | 43 | } from "../project/Services/ProjectSetupScriptRunner.ts"; |
| ... | @@ -713,6 +719,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -713,6 +719,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 713 | const { manager } = yield* makeManager({ | 719 | const { manager } = yield* makeManager({ |
| 714 | ghScenario: { | 720 | ghScenario: { |
| 715 | prListSequence: [ | 721 | prListSequence: [ |
| 722 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 716 | JSON.stringify([ | 723 | JSON.stringify([ |
| 717 | { | 724 | { |
| 718 | number: 13, | 725 | number: 13, |
| ... | @@ -754,6 +761,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -754,6 +761,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 754 | const { manager } = yield* makeManager({ | 761 | const { manager } = yield* makeManager({ |
| 755 | ghScenario: { | 762 | ghScenario: { |
| 756 | prListSequence: [ | 763 | prListSequence: [ |
| 764 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 757 | JSON.stringify([ | 765 | JSON.stringify([ |
| 758 | { | 766 | { |
| 759 | number: 14, | 767 | number: 14, |
| ... | @@ -792,6 +800,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -792,6 +800,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 792 | const { manager } = yield* makeManager({ | 800 | const { manager } = yield* makeManager({ |
| 793 | ghScenario: { | 801 | ghScenario: { |
| 794 | prListSequence: [ | 802 | prListSequence: [ |
| 803 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 795 | JSON.stringify([ | 804 | JSON.stringify([ |
| 796 | { | 805 | { |
| 797 | number: 0, | 806 | number: 0, |
| ... | @@ -843,6 +852,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -843,6 +852,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 843 | const { manager } = yield* makeManager({ | 852 | const { manager } = yield* makeManager({ |
| 844 | ghScenario: { | 853 | ghScenario: { |
| 845 | prListSequence: [ | 854 | prListSequence: [ |
| 855 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 846 | JSON.stringify([ | 856 | JSON.stringify([ |
| 847 | { | 857 | { |
| 848 | number: 16, | 858 | number: 16, |
| ... | @@ -955,6 +965,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -955,6 +965,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 955 | }; | 965 | }; |
| 956 | const { manager, ghCalls } = yield* makeManager({ | 966 | const { manager, ghCalls } = yield* makeManager({ |
| 957 | ghScenario: { | 967 | ghScenario: { |
| 968 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 958 | prListSequence: [JSON.stringify([existingPr]), JSON.stringify([existingPr])], | 969 | prListSequence: [JSON.stringify([existingPr]), JSON.stringify([existingPr])], |
| 959 | }, | 970 | }, |
| 960 | }); | 971 | }); |
| ... | @@ -981,6 +992,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -981,6 +992,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 981 | const { manager } = yield* makeManager({ | 992 | const { manager } = yield* makeManager({ |
| 982 | ghScenario: { | 993 | ghScenario: { |
| 983 | prListSequence: [ | 994 | prListSequence: [ |
| 995 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 984 | JSON.stringify([ | 996 | JSON.stringify([ |
| 985 | { | 997 | { |
| 986 | number: 1661, | 998 | number: 1661, |
| ... | @@ -1034,8 +1046,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1034,8 +1046,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1034 | const { manager, ghCalls } = yield* makeManager({ | 1046 | const { manager, ghCalls } = yield* makeManager({ |
| 1035 | ghScenario: { | 1047 | ghScenario: { |
| 1036 | prListSequence: [ | 1048 | prListSequence: [ |
| 1049 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1037 | JSON.stringify([]), | 1050 | JSON.stringify([]), |
| 1051 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1038 | JSON.stringify([]), | 1052 | JSON.stringify([]), |
| 1053 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1039 | JSON.stringify([ | 1054 | JSON.stringify([ |
| 1040 | { | 1055 | { |
| 1041 | number: 488, | 1056 | number: 488, |
| ... | @@ -1110,6 +1125,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1110,6 +1125,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1110 | const { manager, ghCalls } = yield* makeManager({ | 1125 | const { manager, ghCalls } = yield* makeManager({ |
| 1111 | ghScenario: { | 1126 | ghScenario: { |
| 1112 | prListByHeadSelector: { | 1127 | prListByHeadSelector: { |
| 1128 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1113 | "effect-atom": JSON.stringify([ | 1129 | "effect-atom": JSON.stringify([ |
| 1114 | { | 1130 | { |
| 1115 | number: 1618, | 1131 | number: 1618, |
| ... | @@ -1121,6 +1137,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1121,6 +1137,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1121 | updatedAt: "2026-03-01T10:00:00Z", | 1137 | updatedAt: "2026-03-01T10:00:00Z", |
| 1122 | }, | 1138 | }, |
| 1123 | ]), | 1139 | ]), |
| 1140 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1124 | "upstream/effect-atom": JSON.stringify([ | 1141 | "upstream/effect-atom": JSON.stringify([ |
| 1125 | { | 1142 | { |
| 1126 | number: 1518, | 1143 | number: 1518, |
| ... | @@ -1132,8 +1149,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1132,8 +1149,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1132 | updatedAt: "2026-04-01T10:00:00Z", | 1149 | updatedAt: "2026-04-01T10:00:00Z", |
| 1133 | }, | 1150 | }, |
| 1134 | ]), | 1151 | ]), |
| 1152 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1135 | "pingdotgg:effect-atom": JSON.stringify([]), | 1153 | "pingdotgg:effect-atom": JSON.stringify([]), |
| 1154 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1136 | "my-org/upstream:effect-atom": JSON.stringify([]), | 1155 | "my-org/upstream:effect-atom": JSON.stringify([]), |
| 1156 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1137 | "pingdotgg:upstream/effect-atom": JSON.stringify([ | 1157 | "pingdotgg:upstream/effect-atom": JSON.stringify([ |
| 1138 | { | 1158 | { |
| 1139 | number: 1518, | 1159 | number: 1518, |
| ... | @@ -1145,6 +1165,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1145,6 +1165,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1145 | updatedAt: "2026-04-01T10:00:00Z", | 1165 | updatedAt: "2026-04-01T10:00:00Z", |
| 1146 | }, | 1166 | }, |
| 1147 | ]), | 1167 | ]), |
| 1168 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1148 | "my-org/upstream:upstream/effect-atom": JSON.stringify([ | 1169 | "my-org/upstream:upstream/effect-atom": JSON.stringify([ |
| 1149 | { | 1170 | { |
| 1150 | number: 1518, | 1171 | number: 1518, |
| ... | @@ -1194,6 +1215,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1194,6 +1215,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1194 | const { manager } = yield* makeManager({ | 1215 | const { manager } = yield* makeManager({ |
| 1195 | ghScenario: { | 1216 | ghScenario: { |
| 1196 | prListSequence: [ | 1217 | prListSequence: [ |
| 1218 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1197 | JSON.stringify([ | 1219 | JSON.stringify([ |
| 1198 | { | 1220 | { |
| 1199 | number: 22, | 1221 | number: 22, |
| ... | @@ -1231,6 +1253,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1231,6 +1253,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1231 | const { manager } = yield* makeManager({ | 1253 | const { manager } = yield* makeManager({ |
| 1232 | ghScenario: { | 1254 | ghScenario: { |
| 1233 | prListSequence: [ | 1255 | prListSequence: [ |
| 1256 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1234 | JSON.stringify([ | 1257 | JSON.stringify([ |
| 1235 | { | 1258 | { |
| 1236 | number: 23, | 1259 | number: 23, |
| ... | @@ -1262,6 +1285,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1262,6 +1285,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1262 | const { manager } = yield* makeManager({ | 1285 | const { manager } = yield* makeManager({ |
| 1263 | ghScenario: { | 1286 | ghScenario: { |
| 1264 | prListSequence: [ | 1287 | prListSequence: [ |
| 1288 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1265 | JSON.stringify([ | 1289 | JSON.stringify([ |
| 1266 | { | 1290 | { |
| 1267 | number: 45, | 1291 | number: 45, |
| ... | @@ -1612,6 +1636,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1612,6 +1636,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1612 | ghScenario: { | 1636 | ghScenario: { |
| 1613 | prListSequence: [ | 1637 | prListSequence: [ |
| 1614 | "[]", | 1638 | "[]", |
| 1639 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1615 | JSON.stringify([ | 1640 | JSON.stringify([ |
| 1616 | { | 1641 | { |
| 1617 | number: 77, | 1642 | number: 77, |
| ... | @@ -1747,6 +1772,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1747,6 +1772,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1747 | ghScenario: { | 1772 | ghScenario: { |
| 1748 | prListSequence: [ | 1773 | prListSequence: [ |
| 1749 | "[]", | 1774 | "[]", |
| 1775 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1750 | JSON.stringify([ | 1776 | JSON.stringify([ |
| 1751 | { | 1777 | { |
| 1752 | number: 303, | 1778 | number: 303, |
| ... | @@ -1793,6 +1819,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1793,6 +1819,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1793 | ghScenario: { | 1819 | ghScenario: { |
| 1794 | prListSequence: [ | 1820 | prListSequence: [ |
| 1795 | "[]", | 1821 | "[]", |
| 1822 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1796 | JSON.stringify([ | 1823 | JSON.stringify([ |
| 1797 | { | 1824 | { |
| 1798 | number: 404, | 1825 | number: 404, |
| ... | @@ -1833,6 +1860,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1833,6 +1860,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1833 | const { manager, ghCalls } = yield* makeManager({ | 1860 | const { manager, ghCalls } = yield* makeManager({ |
| 1834 | ghScenario: { | 1861 | ghScenario: { |
| 1835 | prListSequence: [ | 1862 | prListSequence: [ |
| 1863 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1836 | JSON.stringify([ | 1864 | JSON.stringify([ |
| 1837 | { | 1865 | { |
| 1838 | number: 42, | 1866 | number: 42, |
| ... | @@ -1886,7 +1914,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1886,7 +1914,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1886 | const { manager, ghCalls } = yield* makeManager({ | 1914 | const { manager, ghCalls } = yield* makeManager({ |
| 1887 | ghScenario: { | 1915 | ghScenario: { |
| 1888 | prListSequence: [ | 1916 | prListSequence: [ |
| 1917 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1889 | JSON.stringify([]), | 1918 | JSON.stringify([]), |
| 1919 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1890 | JSON.stringify([ | 1920 | JSON.stringify([ |
| 1891 | { | 1921 | { |
| 1892 | number: 142, | 1922 | number: 142, |
| ... | @@ -1963,6 +1993,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1963,6 +1993,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1963 | const { manager, ghCalls } = yield* makeManager({ | 1993 | const { manager, ghCalls } = yield* makeManager({ |
| 1964 | ghScenario: { | 1994 | ghScenario: { |
| 1965 | prListByHeadSelector: { | 1995 | prListByHeadSelector: { |
| 1996 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1966 | "effect-atom": JSON.stringify([ | 1997 | "effect-atom": JSON.stringify([ |
| 1967 | { | 1998 | { |
| 1968 | number: 1618, | 1999 | number: 1618, |
| ... | @@ -1972,6 +2003,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1972,6 +2003,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1972 | headRefName: "effect-atom", | 2003 | headRefName: "effect-atom", |
| 1973 | }, | 2004 | }, |
| 1974 | ]), | 2005 | ]), |
| 2006 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1975 | "upstream/effect-atom": JSON.stringify([ | 2007 | "upstream/effect-atom": JSON.stringify([ |
| 1976 | { | 2008 | { |
| 1977 | number: 1518, | 2009 | number: 1518, |
| ... | @@ -1981,8 +2013,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1981,8 +2013,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1981 | headRefName: "upstream/effect-atom", | 2013 | headRefName: "upstream/effect-atom", |
| 1982 | }, | 2014 | }, |
| 1983 | ]), | 2015 | ]), |
| 2016 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1984 | "pingdotgg:effect-atom": JSON.stringify([]), | 2017 | "pingdotgg:effect-atom": JSON.stringify([]), |
| 2018 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1985 | "my-org/upstream:effect-atom": JSON.stringify([]), | 2019 | "my-org/upstream:effect-atom": JSON.stringify([]), |
| 2020 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1986 | "pingdotgg:upstream/effect-atom": JSON.stringify([ | 2021 | "pingdotgg:upstream/effect-atom": JSON.stringify([ |
| 1987 | { | 2022 | { |
| 1988 | number: 1518, | 2023 | number: 1518, |
| ... | @@ -1992,6 +2027,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -1992,6 +2027,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 1992 | headRefName: "upstream/effect-atom", | 2027 | headRefName: "upstream/effect-atom", |
| 1993 | }, | 2028 | }, |
| 1994 | ]), | 2029 | ]), |
| 2030 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1995 | "my-org/upstream:upstream/effect-atom": JSON.stringify([ | 2031 | "my-org/upstream:upstream/effect-atom": JSON.stringify([ |
| 1996 | { | 2032 | { |
| 1997 | number: 1518, | 2033 | number: 1518, |
| ... | @@ -2041,7 +2077,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2041,7 +2077,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2041 | const { manager, ghCalls } = yield* makeManager({ | 2077 | const { manager, ghCalls } = yield* makeManager({ |
| 2042 | ghScenario: { | 2078 | ghScenario: { |
| 2043 | prListByHeadSelector: { | 2079 | prListByHeadSelector: { |
| 2080 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2044 | "t3code/pr-142/statemachine": JSON.stringify([]), | 2081 | "t3code/pr-142/statemachine": JSON.stringify([]), |
| 2082 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2045 | statemachine: JSON.stringify([ | 2083 | statemachine: JSON.stringify([ |
| 2046 | { | 2084 | { |
| 2047 | number: 41, | 2085 | number: 41, |
| ... | @@ -2051,6 +2089,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2051,6 +2089,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2051 | headRefName: "statemachine", | 2089 | headRefName: "statemachine", |
| 2052 | }, | 2090 | }, |
| 2053 | ]), | 2091 | ]), |
| 2092 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2054 | "octocat:statemachine": JSON.stringify([ | 2093 | "octocat:statemachine": JSON.stringify([ |
| 2055 | { | 2094 | { |
| 2056 | number: 142, | 2095 | number: 142, |
| ... | @@ -2068,6 +2107,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2068,6 +2107,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2068 | }, | 2107 | }, |
| 2069 | }, | 2108 | }, |
| 2070 | ]), | 2109 | ]), |
| 2110 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2071 | "fork-seed:statemachine": JSON.stringify([]), | 2111 | "fork-seed:statemachine": JSON.stringify([]), |
| 2072 | }, | 2112 | }, |
| 2073 | }, | 2113 | }, |
| ... | @@ -2112,6 +2152,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2112,6 +2152,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2112 | const { manager, ghCalls } = yield* makeManager({ | 2152 | const { manager, ghCalls } = yield* makeManager({ |
| 2113 | ghScenario: { | 2153 | ghScenario: { |
| 2114 | prListByHeadSelector: { | 2154 | prListByHeadSelector: { |
| 2155 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2115 | "octocat:statemachine": JSON.stringify([ | 2156 | "octocat:statemachine": JSON.stringify([ |
| 2116 | { | 2157 | { |
| 2117 | number: 142, | 2158 | number: 142, |
| ... | @@ -2129,8 +2170,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2129,8 +2170,11 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2129 | }, | 2170 | }, |
| 2130 | }, | 2171 | }, |
| 2131 | ]), | 2172 | ]), |
| 2173 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2132 | "fork-seed:statemachine": JSON.stringify([]), | 2174 | "fork-seed:statemachine": JSON.stringify([]), |
| 2175 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2133 | "t3code/pr-142/statemachine": JSON.stringify([]), | 2176 | "t3code/pr-142/statemachine": JSON.stringify([]), |
| 2177 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2134 | statemachine: JSON.stringify([]), | 2178 | statemachine: JSON.stringify([]), |
| 2135 | }, | 2179 | }, |
| 2136 | }, | 2180 | }, |
| ... | @@ -2170,6 +2214,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2170,6 +2214,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2170 | ghScenario: { | 2214 | ghScenario: { |
| 2171 | prListSequence: [ | 2215 | prListSequence: [ |
| 2172 | "[]", | 2216 | "[]", |
| 2217 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2173 | JSON.stringify([ | 2218 | JSON.stringify([ |
| 2174 | { | 2219 | { |
| 2175 | number: 88, | 2220 | number: 88, |
| ... | @@ -2215,6 +2260,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2215,6 +2260,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2215 | const { manager, ghCalls } = yield* makeManager({ | 2260 | const { manager, ghCalls } = yield* makeManager({ |
| 2216 | ghScenario: { | 2261 | ghScenario: { |
| 2217 | prListSequence: [ | 2262 | prListSequence: [ |
| 2263 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2218 | JSON.stringify([ | 2264 | JSON.stringify([ |
| 2219 | { | 2265 | { |
| 2220 | number: 1661, | 2266 | number: 1661, |
| ... | @@ -2232,6 +2278,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2232,6 +2278,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2232 | }, | 2278 | }, |
| 2233 | }, | 2279 | }, |
| 2234 | ]), | 2280 | ]), |
| 2281 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2235 | JSON.stringify([ | 2282 | JSON.stringify([ |
| 2236 | { | 2283 | { |
| 2237 | number: 188, | 2284 | number: 188, |
| ... | @@ -2294,7 +2341,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2294,7 +2341,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2294 | ghScenario: { | 2341 | ghScenario: { |
| 2295 | prListSequenceByHeadSelector: { | 2342 | prListSequenceByHeadSelector: { |
| 2296 | "octocat:statemachine": [ | 2343 | "octocat:statemachine": [ |
| 2344 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2297 | JSON.stringify([]), | 2345 | JSON.stringify([]), |
| 2346 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2298 | JSON.stringify([ | 2347 | JSON.stringify([ |
| 2299 | { | 2348 | { |
| 2300 | number: 188, | 2349 | number: 188, |
| ... | @@ -2313,7 +2362,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2313,7 +2362,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2313 | }, | 2362 | }, |
| 2314 | ]), | 2363 | ]), |
| 2315 | ], | 2364 | ], |
| 2365 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2316 | "fork-seed:statemachine": [JSON.stringify([])], | 2366 | "fork-seed:statemachine": [JSON.stringify([])], |
| 2367 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 2317 | statemachine: [JSON.stringify([])], | 2368 | statemachine: [JSON.stringify([])], |
| 2318 | }, | 2369 | }, |
| 2319 | }, | 2370 | }, |
| ... | @@ -2870,7 +2921,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -2870,7 +2921,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 2870 | yield* runGit(repoDir, ["add", "existing.txt"]); | 2921 | yield* runGit(repoDir, ["add", "existing.txt"]); |
| 2871 | yield* runGit(repoDir, ["commit", "-m", "Existing worktree branch"]); | 2922 | yield* runGit(repoDir, ["commit", "-m", "Existing worktree branch"]); |
| 2872 | yield* runGit(repoDir, ["checkout", "main"]); | 2923 | yield* runGit(repoDir, ["checkout", "main"]); |
| 2873 | const worktreePath = path.join(repoDir, "..", `pr-existing-${Date.now()}`); | 2924 | const worktreePath = path.join(repoDir, "..", `pr-existing-${path.basename(repoDir)}`); |
| 2874 | yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-existing-worktree"]); | 2925 | yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-existing-worktree"]); |
| 2875 | 2926 | ||
| 2876 | const setupCalls: ProjectSetupScriptRunnerInput[] = []; | 2927 | const setupCalls: ProjectSetupScriptRunnerInput[] = []; |
| ... | @@ -3044,7 +3095,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -3044,7 +3095,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 3044 | yield* runGit(repoDir, ["commit", "-m", "Reused fork PR branch"]); | 3095 | yield* runGit(repoDir, ["commit", "-m", "Reused fork PR branch"]); |
| 3045 | yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-reused-fork"]); | 3096 | yield* runGit(repoDir, ["push", "-u", "fork-seed", "feature/pr-reused-fork"]); |
| 3046 | yield* runGit(repoDir, ["checkout", "main"]); | 3097 | yield* runGit(repoDir, ["checkout", "main"]); |
| 3047 | const worktreePath = path.join(repoDir, "..", `pr-reused-fork-${Date.now()}`); | 3098 | const worktreePath = path.join(repoDir, "..", `pr-reused-fork-${path.basename(repoDir)}`); |
| 3048 | yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-reused-fork"]); | 3099 | yield* runGit(repoDir, ["worktree", "add", worktreePath, "feature/pr-reused-fork"]); |
| 3049 | yield* runGit(worktreePath, ["branch", "--unset-upstream"], true); | 3100 | yield* runGit(worktreePath, ["branch", "--unset-upstream"], true); |
| 3050 | 3101 | ||
| ... | @@ -3112,7 +3163,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -3112,7 +3163,8 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 3112 | }, | 3163 | }, |
| 3113 | }, | 3164 | }, |
| 3114 | setupScriptRunner: { | 3165 | setupScriptRunner: { |
| 3115 | runForThread: () => Effect.fail(new Error("terminal start failed")), | 3166 | runForThread: () => |
| 3167 | Effect.fail(new ProjectSetupScriptRunnerError({ message: "terminal start failed" })), | ||
| 3116 | }, | 3168 | }, |
| 3117 | }); | 3169 | }); |
| 3118 | 3170 | ||
| ... | @@ -3289,7 +3341,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { | ... | @@ -3289,7 +3341,9 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { |
| 3289 | const { manager } = yield* makeManager({ | 3341 | const { manager } = yield* makeManager({ |
| 3290 | ghScenario: { | 3342 | ghScenario: { |
| 3291 | prListSequence: [ | 3343 | prListSequence: [ |
| 3344 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 3292 | JSON.stringify([]), | 3345 | JSON.stringify([]), |
| 3346 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 3293 | JSON.stringify([ | 3347 | JSON.stringify([ |
| 3294 | { | 3348 | { |
| 3295 | number: 201, | 3349 | number: 201, |
apps/server/src/git/GitManager.ts+13-15| ... | @@ -1,20 +1,18 @@ | ... | @@ -1,20 +1,18 @@ |
| 1 | import { randomUUID } from "node:crypto"; | 1 | import { randomUUID } from "node:crypto"; |
| 2 | 2 | ||
| 3 | import { | 3 | import * as Arr from "effect/Array"; |
| 4 | Array as Arr, | 4 | import * as Cache from "effect/Cache"; |
| 5 | Cache, | 5 | import * as Context from "effect/Context"; |
| 6 | Context, | 6 | import * as DateTime from "effect/DateTime"; |
| 7 | DateTime, | 7 | import * as Duration from "effect/Duration"; |
| 8 | Duration, | 8 | import * as Effect from "effect/Effect"; |
| 9 | Effect, | 9 | import * as Exit from "effect/Exit"; |
| 10 | Exit, | 10 | import * as FileSystem from "effect/FileSystem"; |
| 11 | FileSystem, | 11 | import * as Layer from "effect/Layer"; |
| 12 | Layer, | 12 | import * as Option from "effect/Option"; |
| 13 | Option, | 13 | import * as Order from "effect/Order"; |
| 14 | Order, | 14 | import * as Path from "effect/Path"; |
| 15 | Path, | 15 | import * as Ref from "effect/Ref"; |
| 16 | Ref, | ||
| 17 | } from "effect"; | ||
| 18 | import { | 16 | import { |
| 19 | GitActionProgressEvent, | 17 | GitActionProgressEvent, |
| 20 | GitActionProgressPhase, | 18 | GitActionProgressPhase, |
apps/server/src/git/GitWorkflowService.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, describe, it, vi } from "@effect/vitest"; | 1 | import { assert, describe, it, vi } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | 4 | ||
| 4 | import * as GitManager from "./GitManager.ts"; | 5 | import * as GitManager from "./GitManager.ts"; |
| 5 | import * as GitWorkflowService from "./GitWorkflowService.ts"; | 6 | import * as GitWorkflowService from "./GitWorkflowService.ts"; |
apps/server/src/git/GitWorkflowService.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { Context, Effect, Layer } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 2 | 4 | ||
| 3 | import { | 5 | import { |
| 4 | GitManagerError, | 6 | GitManagerError, |
apps/server/src/git/Utils.ts+1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { existsSync } from "node:fs"; | 2 | import { existsSync } from "node:fs"; |
| 2 | import { join } from "node:path"; | 3 | import { join } from "node:path"; |
| 3 | 4 |
apps/server/src/http.ts+5-1| ... | @@ -1,6 +1,10 @@ | ... | @@ -1,6 +1,10 @@ |
| 1 | import Mime from "@effect/platform-node/Mime"; | 1 | import Mime from "@effect/platform-node/Mime"; |
| 2 | import { decodeOtlpTraceRecords } from "@t3tools/shared/observability"; | 2 | import { decodeOtlpTraceRecords } from "@t3tools/shared/observability"; |
| 3 | import { Data, Effect, FileSystem, Option, Path } from "effect"; | 3 | import * as Data from "effect/Data"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as FileSystem from "effect/FileSystem"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Path from "effect/Path"; | ||
| 4 | import { cast } from "effect/Function"; | 8 | import { cast } from "effect/Function"; |
| 5 | import { | 9 | import { |
| 6 | HttpBody, | 10 | HttpBody, |
apps/server/src/keybindings.test.ts+8-1| ... | @@ -2,7 +2,13 @@ import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/c | ... | @@ -2,7 +2,13 @@ import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/c |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { assert, it } from "@effect/vitest"; | 3 | import { assert, it } from "@effect/vitest"; |
| 4 | import { assertFailure } from "@effect/vitest/utils"; | 4 | import { assertFailure } from "@effect/vitest/utils"; |
| 5 | import { Cause, Effect, FileSystem, Layer, Logger, Path, Schema } from "effect"; | 5 | import * as Cause from "effect/Cause"; |
| 6 | import * as Effect from "effect/Effect"; | ||
| 7 | import * as FileSystem from "effect/FileSystem"; | ||
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as Logger from "effect/Logger"; | ||
| 10 | import * as Path from "effect/Path"; | ||
| 11 | import * as Schema from "effect/Schema"; | ||
| 6 | import { ServerConfig } from "./config.ts"; | 12 | import { ServerConfig } from "./config.ts"; |
| 7 | 13 | ||
| 8 | import { | 14 | import { |
| ... | @@ -229,6 +235,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { | ... | @@ -229,6 +235,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { |
| 229 | const { keybindingsConfigPath } = yield* ServerConfig; | 235 | const { keybindingsConfigPath } = yield* ServerConfig; |
| 230 | yield* fs.writeFileString( | 236 | yield* fs.writeFileString( |
| 231 | keybindingsConfigPath, | 237 | keybindingsConfigPath, |
| 238 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 232 | JSON.stringify([ | 239 | JSON.stringify([ |
| 233 | { key: "mod+j", command: "terminal.toggle" }, | 240 | { key: "mod+j", command: "terminal.toggle" }, |
| 234 | { key: "mod+shift+d+o", command: "terminal.new" }, | 241 | { key: "mod+shift+d+o", command: "terminal.new" }, |
apps/server/src/keybindings.ts+22-34| ... | @@ -19,33 +19,30 @@ import { | ... | @@ -19,33 +19,30 @@ import { |
| 19 | type ServerUpsertKeybindingInput, | 19 | type ServerUpsertKeybindingInput, |
| 20 | type ServerConfigIssue, | 20 | type ServerConfigIssue, |
| 21 | } from "@t3tools/contracts"; | 21 | } from "@t3tools/contracts"; |
| 22 | import { | 22 | import * as Array from "effect/Array"; |
| 23 | Array, | 23 | import * as Cache from "effect/Cache"; |
| 24 | Cache, | 24 | import * as Cause from "effect/Cause"; |
| 25 | Cause, | 25 | import * as Deferred from "effect/Deferred"; |
| 26 | Deferred, | 26 | import * as Duration from "effect/Duration"; |
| 27 | Duration, | 27 | import * as Effect from "effect/Effect"; |
| 28 | Effect, | 28 | import * as Exit from "effect/Exit"; |
| 29 | Exit, | 29 | import * as FileSystem from "effect/FileSystem"; |
| 30 | FileSystem, | 30 | import * as Path from "effect/Path"; |
| 31 | Path, | 31 | import * as Layer from "effect/Layer"; |
| 32 | Layer, | 32 | import * as Option from "effect/Option"; |
| 33 | Option, | 33 | import * as Predicate from "effect/Predicate"; |
| 34 | Predicate, | 34 | import * as PubSub from "effect/PubSub"; |
| 35 | PubSub, | 35 | import * as Schema from "effect/Schema"; |
| 36 | Schema, | 36 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 37 | SchemaGetter, | 37 | import * as SchemaTransformation from "effect/SchemaTransformation"; |
| 38 | SchemaIssue, | 38 | import * as Ref from "effect/Ref"; |
| 39 | SchemaTransformation, | 39 | import * as Context from "effect/Context"; |
| 40 | Ref, | 40 | import * as Scope from "effect/Scope"; |
| 41 | Context, | 41 | import * as Stream from "effect/Stream"; |
| 42 | Scope, | ||
| 43 | Stream, | ||
| 44 | } from "effect"; | ||
| 45 | import * as Semaphore from "effect/Semaphore"; | 42 | import * as Semaphore from "effect/Semaphore"; |
| 46 | import { ServerConfig } from "./config.ts"; | 43 | import { ServerConfig } from "./config.ts"; |
| 47 | import { writeFileStringAtomically } from "./atomicWrite.ts"; | 44 | import { writeFileStringAtomically } from "./atomicWrite.ts"; |
| 48 | import { fromLenientJson } from "@t3tools/shared/schemaJson"; | 45 | import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson"; |
| 49 | import { | 46 | import { |
| 50 | DEFAULT_KEYBINDINGS, | 47 | DEFAULT_KEYBINDINGS, |
| 51 | DEFAULT_RESOLVED_KEYBINDINGS, | 48 | DEFAULT_RESOLVED_KEYBINDINGS, |
| ... | @@ -172,16 +169,7 @@ function encodeWhenAst(node: KeybindingWhenNode): string { | ... | @@ -172,16 +169,7 @@ function encodeWhenAst(node: KeybindingWhenNode): string { |
| 172 | } | 169 | } |
| 173 | 170 | ||
| 174 | const RawKeybindingsEntries = fromLenientJson(Schema.Array(Schema.Unknown)); | 171 | const RawKeybindingsEntries = fromLenientJson(Schema.Array(Schema.Unknown)); |
| 175 | const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig); | 172 | const KeybindingsConfigPrettyJson = fromJsonStringPretty(KeybindingsConfig); |
| 176 | const PrettyJsonString = SchemaGetter.parseJson<string>().compose( | ||
| 177 | SchemaGetter.stringifyJson({ space: 2 }), | ||
| 178 | ); | ||
| 179 | const KeybindingsConfigPrettyJson = KeybindingsConfigJson.pipe( | ||
| 180 | Schema.encode({ | ||
| 181 | decode: PrettyJsonString, | ||
| 182 | encode: PrettyJsonString, | ||
| 183 | }), | ||
| 184 | ); | ||
| 185 | 173 | ||
| 186 | export interface KeybindingsConfigState { | 174 | export interface KeybindingsConfigState { |
| 187 | readonly keybindings: ResolvedKeybindingsConfig; | 175 | readonly keybindings: ResolvedKeybindingsConfig; |
apps/server/src/observability/Attributes.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Cause, Exit } from "effect"; | 1 | import * as Cause from "effect/Cause"; |
| 2 | import * as Exit from "effect/Exit"; | ||
| 2 | 3 | ||
| 3 | export type MetricAttributeValue = string; | 4 | export type MetricAttributeValue = string; |
| 4 | export type MetricAttributes = Readonly<Record<string, MetricAttributeValue>>; | 5 | export type MetricAttributes = Readonly<Record<string, MetricAttributeValue>>; |
apps/server/src/observability/Layers/Observability.ts+4-1| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | import { makeLocalFileTracer, makeTraceSink } from "@t3tools/shared/observability"; | 1 | import { makeLocalFileTracer, makeTraceSink } from "@t3tools/shared/observability"; |
| 2 | import { Effect, Layer, References, Tracer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as References from "effect/References"; | ||
| 5 | import * as Tracer from "effect/Tracer"; | ||
| 3 | import { OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; | 6 | import { OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; |
| 4 | 7 | ||
| 5 | import { ServerConfig } from "../../config.ts"; | 8 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/observability/Metrics.test.ts+5-2| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | 1 | import { assert, describe, it } from "@effect/vitest"; |
| 2 | import { ProviderDriverKind } from "@t3tools/contracts"; | 2 | import { ProviderDriverKind } from "@t3tools/contracts"; |
| 3 | import { Duration, Effect, Fiber, Metric } from "effect"; | 3 | import * as Duration from "effect/Duration"; |
| 4 | import { TestClock } from "effect/testing"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Fiber from "effect/Fiber"; | ||
| 6 | import * as Metric from "effect/Metric"; | ||
| 7 | import * as TestClock from "effect/testing/TestClock"; | ||
| 5 | 8 | ||
| 6 | import { withMetrics } from "./Metrics.ts"; | 9 | import { withMetrics } from "./Metrics.ts"; |
| 7 | 10 |
apps/server/src/observability/Metrics.ts+5-1| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Clock, Duration, Effect, Exit, Metric } from "effect"; | 1 | import * as Clock from "effect/Clock"; |
| 2 | import * as Duration from "effect/Duration"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Exit from "effect/Exit"; | ||
| 5 | import * as Metric from "effect/Metric"; | ||
| 2 | import { dual } from "effect/Function"; | 6 | import { dual } from "effect/Function"; |
| 3 | 7 | ||
| 4 | import { | 8 | import { |
apps/server/src/observability/RpcInstrumentation.test.ts+8-2| ... | @@ -1,7 +1,13 @@ | ... | @@ -1,7 +1,13 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | 1 | import { assert, describe, it } from "@effect/vitest"; |
| 2 | import { WS_METHODS } from "@t3tools/contracts"; | 2 | import { WS_METHODS } from "@t3tools/contracts"; |
| 3 | import { Duration, Effect, Exit, Fiber, Metric, Stream, Tracer } from "effect"; | 3 | import * as Duration from "effect/Duration"; |
| 4 | import { TestClock } from "effect/testing"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as Fiber from "effect/Fiber"; | ||
| 7 | import * as Metric from "effect/Metric"; | ||
| 8 | import * as Stream from "effect/Stream"; | ||
| 9 | import * as Tracer from "effect/Tracer"; | ||
| 10 | import * as TestClock from "effect/testing/TestClock"; | ||
| 5 | 11 | ||
| 6 | import { | 12 | import { |
| 7 | observeRpcEffect, | 13 | observeRpcEffect, |
apps/server/src/observability/RpcInstrumentation.ts+7-1| ... | @@ -1,5 +1,11 @@ | ... | @@ -1,5 +1,11 @@ |
| 1 | import { WS_METHODS } from "@t3tools/contracts"; | 1 | import { WS_METHODS } from "@t3tools/contracts"; |
| 2 | import { Clock, Duration, Effect, Exit, Metric, References, Stream } from "effect"; | 2 | import * as Clock from "effect/Clock"; |
| 3 | import * as Duration from "effect/Duration"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as Metric from "effect/Metric"; | ||
| 7 | import * as References from "effect/References"; | ||
| 8 | import * as Stream from "effect/Stream"; | ||
| 3 | 9 | ||
| 4 | import { outcomeFromExit } from "./Attributes.ts"; | 10 | import { outcomeFromExit } from "./Attributes.ts"; |
| 5 | import { metricAttributes, rpcRequestDuration, rpcRequestsTotal, withMetrics } from "./Metrics.ts"; | 11 | import { metricAttributes, rpcRequestDuration, rpcRequestsTotal, withMetrics } from "./Metrics.ts"; |
apps/server/src/observability/Services/BrowserTraceCollector.ts+2-2| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import type { TraceRecord } from "@t3tools/shared/observability"; | 1 | import type { TraceRecord } from "@t3tools/shared/observability"; |
| 2 | import { Context } from "effect"; | 2 | import * as Context from "effect/Context"; |
| 3 | import type { Effect } from "effect"; | 3 | import type * as Effect from "effect/Effect"; |
| 4 | 4 | ||
| 5 | export interface BrowserTraceCollectorShape { | 5 | export interface BrowserTraceCollectorShape { |
| 6 | readonly record: (records: ReadonlyArray<TraceRecord>) => Effect.Effect<void>; | 6 | readonly record: (records: ReadonlyArray<TraceRecord>) => Effect.Effect<void>; |
apps/server/src/open.test.ts+5-2| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { assertSuccess } from "@effect/vitest/utils"; | 3 | import { assertSuccess } from "@effect/vitest/utils"; |
| 4 | import { FileSystem, Path, Effect } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 7 | import * as Random from "effect/Random"; | ||
| 5 | 8 | ||
| 6 | import { | 9 | import { |
| 7 | isCommandAvailable, | 10 | isCommandAvailable, |
| ... | @@ -488,7 +491,7 @@ it.layer(NodeServices.layer)("launchDetached", (it) => { | ... | @@ -488,7 +491,7 @@ it.layer(NodeServices.layer)("launchDetached", (it) => { |
| 488 | it.effect("rejects when command does not exist", () => | 491 | it.effect("rejects when command does not exist", () => |
| 489 | Effect.gen(function* () { | 492 | Effect.gen(function* () { |
| 490 | const result = yield* launchDetached({ | 493 | const result = yield* launchDetached({ |
| 491 | command: `t3code-no-such-command-${Date.now()}`, | 494 | command: `t3code-no-such-command-${yield* Random.nextUUIDv4}`, |
| 492 | args: [], | 495 | args: [], |
| 493 | }).pipe(Effect.result); | 496 | }).pipe(Effect.result); |
| 494 | assert.equal(result._tag, "Failure"); | 497 | assert.equal(result._tag, "Failure"); |
apps/server/src/open.ts+4-1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | /** | 2 | /** |
| 2 | * Open - Browser/editor launch service interface. | 3 | * Open - Browser/editor launch service interface. |
| 3 | * | 4 | * |
| ... | @@ -10,7 +11,9 @@ import { spawn } from "node:child_process"; | ... | @@ -10,7 +11,9 @@ import { spawn } from "node:child_process"; |
| 10 | 11 | ||
| 11 | import { EDITORS, OpenError, type EditorId } from "@t3tools/contracts"; | 12 | import { EDITORS, OpenError, type EditorId } from "@t3tools/contracts"; |
| 12 | import { isCommandAvailable, type CommandAvailabilityOptions } from "@t3tools/shared/shell"; | 13 | import { isCommandAvailable, type CommandAvailabilityOptions } from "@t3tools/shared/shell"; |
| 13 | import { Context, Effect, Layer } from "effect"; | 14 | import * as Context from "effect/Context"; |
| 15 | import * as Effect from "effect/Effect"; | ||
| 16 | import * as Layer from "effect/Layer"; | ||
| 14 | 17 | ||
| 15 | // ============================== | 18 | // ============================== |
| 16 | // Definitions | 19 | // Definitions |
apps/server/src/orchestration/Errors.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { SchemaIssue, Schema } from "effect"; | 1 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 2 | import * as Schema from "effect/Schema"; | ||
| 2 | 3 | ||
| 3 | import type { ProjectionRepositoryError } from "../persistence/Errors.ts"; | 4 | import type { ProjectionRepositoryError } from "../persistence/Errors.ts"; |
| 4 | 5 |
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+45-37| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
| ... | @@ -19,7 +20,14 @@ import { | ... | @@ -19,7 +20,14 @@ import { |
| 19 | TurnId, | 20 | TurnId, |
| 20 | } from "@t3tools/contracts"; | 21 | } from "@t3tools/contracts"; |
| 21 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 22 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 22 | import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect"; | 23 | import * as Clock from "effect/Clock"; |
| 24 | import * as Effect from "effect/Effect"; | ||
| 25 | import * as Exit from "effect/Exit"; | ||
| 26 | import * as Layer from "effect/Layer"; | ||
| 27 | import * as ManagedRuntime from "effect/ManagedRuntime"; | ||
| 28 | import * as PubSub from "effect/PubSub"; | ||
| 29 | import * as Scope from "effect/Scope"; | ||
| 30 | import * as Stream from "effect/Stream"; | ||
| 23 | import { afterEach, describe, expect, it, vi } from "vitest"; | 31 | import { afterEach, describe, expect, it, vi } from "vitest"; |
| 24 | 32 | ||
| 25 | import { CheckpointStoreLive } from "../../checkpointing/Layers/CheckpointStore.ts"; | 33 | import { CheckpointStoreLive } from "../../checkpointing/Layers/CheckpointStore.ts"; |
| ... | @@ -73,7 +81,7 @@ function createProviderServiceHarness( | ... | @@ -73,7 +81,7 @@ function createProviderServiceHarness( |
| 73 | sessionCwd = cwd, | 81 | sessionCwd = cwd, |
| 74 | providerName: ProviderSession["provider"] = ProviderDriverKind.make("codex"), | 82 | providerName: ProviderSession["provider"] = ProviderDriverKind.make("codex"), |
| 75 | ) { | 83 | ) { |
| 76 | const now = new Date().toISOString(); | 84 | const now = "2026-01-01T00:00:00.000Z"; |
| 77 | const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>()); | 85 | const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>()); |
| 78 | const rollbackConversation = vi.fn( | 86 | const rollbackConversation = vi.fn( |
| 79 | (_input: { readonly threadId: ThreadId; readonly numTurns: number }) => Effect.void, | 87 | (_input: { readonly threadId: ThreadId; readonly numTurns: number }) => Effect.void, |
| ... | @@ -148,7 +156,7 @@ async function waitForThread( | ... | @@ -148,7 +156,7 @@ async function waitForThread( |
| 148 | }) => boolean, | 156 | }) => boolean, |
| 149 | timeoutMs = 15_000, | 157 | timeoutMs = 15_000, |
| 150 | ) { | 158 | ) { |
| 151 | const deadline = Date.now() + timeoutMs; | 159 | const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; |
| 152 | const poll = async (): Promise<{ | 160 | const poll = async (): Promise<{ |
| 153 | latestTurn: { turnId: string } | null; | 161 | latestTurn: { turnId: string } | null; |
| 154 | checkpoints: ReadonlyArray<{ checkpointTurnCount: number }>; | 162 | checkpoints: ReadonlyArray<{ checkpointTurnCount: number }>; |
| ... | @@ -159,10 +167,10 @@ async function waitForThread( | ... | @@ -159,10 +167,10 @@ async function waitForThread( |
| 159 | if (thread && predicate(thread)) { | 167 | if (thread && predicate(thread)) { |
| 160 | return thread; | 168 | return thread; |
| 161 | } | 169 | } |
| 162 | if (Date.now() >= deadline) { | 170 | if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { |
| 163 | throw new Error("Timed out waiting for thread state."); | 171 | throw new Error("Timed out waiting for thread state."); |
| 164 | } | 172 | } |
| 165 | await new Promise((resolve) => setTimeout(resolve, 10)); | 173 | await Effect.runPromise(Effect.sleep("10 millis")); |
| 166 | return poll(); | 174 | return poll(); |
| 167 | }; | 175 | }; |
| 168 | return poll(); | 176 | return poll(); |
| ... | @@ -173,7 +181,7 @@ async function waitForEvent( | ... | @@ -173,7 +181,7 @@ async function waitForEvent( |
| 173 | predicate: (event: { type: string }) => boolean, | 181 | predicate: (event: { type: string }) => boolean, |
| 174 | timeoutMs = 15_000, | 182 | timeoutMs = 15_000, |
| 175 | ) { | 183 | ) { |
| 176 | const deadline = Date.now() + timeoutMs; | 184 | const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; |
| 177 | const poll = async () => { | 185 | const poll = async () => { |
| 178 | const events = await Effect.runPromise( | 186 | const events = await Effect.runPromise( |
| 179 | Stream.runCollect(engine.readEvents(0)).pipe(Effect.map((chunk) => Array.from(chunk))), | 187 | Stream.runCollect(engine.readEvents(0)).pipe(Effect.map((chunk) => Array.from(chunk))), |
| ... | @@ -181,10 +189,10 @@ async function waitForEvent( | ... | @@ -181,10 +189,10 @@ async function waitForEvent( |
| 181 | if (events.some(predicate)) { | 189 | if (events.some(predicate)) { |
| 182 | return events; | 190 | return events; |
| 183 | } | 191 | } |
| 184 | if (Date.now() >= deadline) { | 192 | if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { |
| 185 | throw new Error("Timed out waiting for orchestration event."); | 193 | throw new Error("Timed out waiting for orchestration event."); |
| 186 | } | 194 | } |
| 187 | await new Promise((resolve) => setTimeout(resolve, 10)); | 195 | await Effect.runPromise(Effect.sleep("10 millis")); |
| 188 | return poll(); | 196 | return poll(); |
| 189 | }; | 197 | }; |
| 190 | return poll(); | 198 | return poll(); |
| ... | @@ -223,15 +231,15 @@ function gitShowFileAtRef(cwd: string, ref: string, filePath: string): string { | ... | @@ -223,15 +231,15 @@ function gitShowFileAtRef(cwd: string, ref: string, filePath: string): string { |
| 223 | } | 231 | } |
| 224 | 232 | ||
| 225 | async function waitForGitRefExists(cwd: string, ref: string, timeoutMs = 15_000) { | 233 | async function waitForGitRefExists(cwd: string, ref: string, timeoutMs = 15_000) { |
| 226 | const deadline = Date.now() + timeoutMs; | 234 | const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; |
| 227 | const poll = async (): Promise<void> => { | 235 | const poll = async (): Promise<void> => { |
| 228 | if (gitRefExists(cwd, ref)) { | 236 | if (gitRefExists(cwd, ref)) { |
| 229 | return; | 237 | return; |
| 230 | } | 238 | } |
| 231 | if (Date.now() >= deadline) { | 239 | if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { |
| 232 | throw new Error(`Timed out waiting for git ref '${ref}'.`); | 240 | throw new Error(`Timed out waiting for git ref '${ref}'.`); |
| 233 | } | 241 | } |
| 234 | await new Promise((resolve) => setTimeout(resolve, 10)); | 242 | await Effect.runPromise(Effect.sleep("10 millis")); |
| 235 | return poll(); | 243 | return poll(); |
| 236 | }; | 244 | }; |
| 237 | return poll(); | 245 | return poll(); |
| ... | @@ -342,7 +350,7 @@ describe("CheckpointReactor", () => { | ... | @@ -342,7 +350,7 @@ describe("CheckpointReactor", () => { |
| 342 | await Effect.runPromise(reactor.start().pipe(Scope.provide(scope))); | 350 | await Effect.runPromise(reactor.start().pipe(Scope.provide(scope))); |
| 343 | const drain = () => Effect.runPromise(reactor.drain); | 351 | const drain = () => Effect.runPromise(reactor.drain); |
| 344 | 352 | ||
| 345 | const createdAt = new Date().toISOString(); | 353 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 346 | await Effect.runPromise( | 354 | await Effect.runPromise( |
| 347 | engine.dispatch({ | 355 | engine.dispatch({ |
| 348 | type: "project.create", | 356 | type: "project.create", |
| ... | @@ -410,7 +418,7 @@ describe("CheckpointReactor", () => { | ... | @@ -410,7 +418,7 @@ describe("CheckpointReactor", () => { |
| 410 | 418 | ||
| 411 | it("captures pre-turn baseline on turn.started and post-turn checkpoint on turn.completed", async () => { | 419 | it("captures pre-turn baseline on turn.started and post-turn checkpoint on turn.completed", async () => { |
| 412 | const harness = await createHarness({ seedFilesystemCheckpoints: false }); | 420 | const harness = await createHarness({ seedFilesystemCheckpoints: false }); |
| 413 | const createdAt = new Date().toISOString(); | 421 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 414 | 422 | ||
| 415 | await Effect.runPromise( | 423 | await Effect.runPromise( |
| 416 | harness.engine.dispatch({ | 424 | harness.engine.dispatch({ |
| ... | @@ -435,7 +443,7 @@ describe("CheckpointReactor", () => { | ... | @@ -435,7 +443,7 @@ describe("CheckpointReactor", () => { |
| 435 | eventId: EventId.make("evt-turn-started-1"), | 443 | eventId: EventId.make("evt-turn-started-1"), |
| 436 | provider: ProviderDriverKind.make("codex"), | 444 | provider: ProviderDriverKind.make("codex"), |
| 437 | 445 | ||
| 438 | createdAt: new Date().toISOString(), | 446 | createdAt: "2026-01-01T00:00:00.000Z", |
| 439 | threadId: ThreadId.make("thread-1"), | 447 | threadId: ThreadId.make("thread-1"), |
| 440 | turnId: asTurnId("turn-1"), | 448 | turnId: asTurnId("turn-1"), |
| 441 | }); | 449 | }); |
| ... | @@ -450,7 +458,7 @@ describe("CheckpointReactor", () => { | ... | @@ -450,7 +458,7 @@ describe("CheckpointReactor", () => { |
| 450 | eventId: EventId.make("evt-turn-completed-1"), | 458 | eventId: EventId.make("evt-turn-completed-1"), |
| 451 | provider: ProviderDriverKind.make("codex"), | 459 | provider: ProviderDriverKind.make("codex"), |
| 452 | 460 | ||
| 453 | createdAt: new Date().toISOString(), | 461 | createdAt: "2026-01-01T00:00:00.000Z", |
| 454 | threadId: ThreadId.make("thread-1"), | 462 | threadId: ThreadId.make("thread-1"), |
| 455 | turnId: asTurnId("turn-1"), | 463 | turnId: asTurnId("turn-1"), |
| 456 | payload: { state: "completed" }, | 464 | payload: { state: "completed" }, |
| ... | @@ -495,7 +503,7 @@ describe("CheckpointReactor", () => { | ... | @@ -495,7 +503,7 @@ describe("CheckpointReactor", () => { |
| 495 | type: "turn.completed", | 503 | type: "turn.completed", |
| 496 | eventId: EventId.make("evt-turn-completed-refresh-local-status"), | 504 | eventId: EventId.make("evt-turn-completed-refresh-local-status"), |
| 497 | provider: ProviderDriverKind.make("codex"), | 505 | provider: ProviderDriverKind.make("codex"), |
| 498 | createdAt: new Date().toISOString(), | 506 | createdAt: "2026-01-01T00:00:00.000Z", |
| 499 | threadId: ThreadId.make("thread-1"), | 507 | threadId: ThreadId.make("thread-1"), |
| 500 | turnId: asTurnId("turn-refresh-local-status"), | 508 | turnId: asTurnId("turn-refresh-local-status"), |
| 501 | payload: { state: "completed" }, | 509 | payload: { state: "completed" }, |
| ... | @@ -508,7 +516,7 @@ describe("CheckpointReactor", () => { | ... | @@ -508,7 +516,7 @@ describe("CheckpointReactor", () => { |
| 508 | 516 | ||
| 509 | it("ignores auxiliary thread turn completion while primary turn is active", async () => { | 517 | it("ignores auxiliary thread turn completion while primary turn is active", async () => { |
| 510 | const harness = await createHarness({ seedFilesystemCheckpoints: false }); | 518 | const harness = await createHarness({ seedFilesystemCheckpoints: false }); |
| 511 | const createdAt = new Date().toISOString(); | 519 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 512 | 520 | ||
| 513 | await Effect.runPromise( | 521 | await Effect.runPromise( |
| 514 | harness.engine.dispatch({ | 522 | harness.engine.dispatch({ |
| ... | @@ -533,7 +541,7 @@ describe("CheckpointReactor", () => { | ... | @@ -533,7 +541,7 @@ describe("CheckpointReactor", () => { |
| 533 | eventId: EventId.make("evt-turn-started-main"), | 541 | eventId: EventId.make("evt-turn-started-main"), |
| 534 | provider: ProviderDriverKind.make("codex"), | 542 | provider: ProviderDriverKind.make("codex"), |
| 535 | 543 | ||
| 536 | createdAt: new Date().toISOString(), | 544 | createdAt: "2026-01-01T00:00:00.000Z", |
| 537 | threadId: ThreadId.make("thread-1"), | 545 | threadId: ThreadId.make("thread-1"), |
| 538 | turnId: asTurnId("turn-main"), | 546 | turnId: asTurnId("turn-main"), |
| 539 | }); | 547 | }); |
| ... | @@ -549,7 +557,7 @@ describe("CheckpointReactor", () => { | ... | @@ -549,7 +557,7 @@ describe("CheckpointReactor", () => { |
| 549 | eventId: EventId.make("evt-turn-completed-aux"), | 557 | eventId: EventId.make("evt-turn-completed-aux"), |
| 550 | provider: ProviderDriverKind.make("codex"), | 558 | provider: ProviderDriverKind.make("codex"), |
| 551 | 559 | ||
| 552 | createdAt: new Date().toISOString(), | 560 | createdAt: "2026-01-01T00:00:00.000Z", |
| 553 | threadId: ThreadId.make("thread-1"), | 561 | threadId: ThreadId.make("thread-1"), |
| 554 | turnId: asTurnId("turn-aux"), | 562 | turnId: asTurnId("turn-aux"), |
| 555 | payload: { state: "completed" }, | 563 | payload: { state: "completed" }, |
| ... | @@ -565,7 +573,7 @@ describe("CheckpointReactor", () => { | ... | @@ -565,7 +573,7 @@ describe("CheckpointReactor", () => { |
| 565 | eventId: EventId.make("evt-turn-completed-main"), | 573 | eventId: EventId.make("evt-turn-completed-main"), |
| 566 | provider: ProviderDriverKind.make("codex"), | 574 | provider: ProviderDriverKind.make("codex"), |
| 567 | 575 | ||
| 568 | createdAt: new Date().toISOString(), | 576 | createdAt: "2026-01-01T00:00:00.000Z", |
| 569 | threadId: ThreadId.make("thread-1"), | 577 | threadId: ThreadId.make("thread-1"), |
| 570 | turnId: asTurnId("turn-main"), | 578 | turnId: asTurnId("turn-main"), |
| 571 | payload: { state: "completed" }, | 579 | payload: { state: "completed" }, |
| ... | @@ -583,7 +591,7 @@ describe("CheckpointReactor", () => { | ... | @@ -583,7 +591,7 @@ describe("CheckpointReactor", () => { |
| 583 | seedFilesystemCheckpoints: false, | 591 | seedFilesystemCheckpoints: false, |
| 584 | providerName: ProviderDriverKind.make("claudeAgent"), | 592 | providerName: ProviderDriverKind.make("claudeAgent"), |
| 585 | }); | 593 | }); |
| 586 | const createdAt = new Date().toISOString(); | 594 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 587 | 595 | ||
| 588 | await Effect.runPromise( | 596 | await Effect.runPromise( |
| 589 | harness.engine.dispatch({ | 597 | harness.engine.dispatch({ |
| ... | @@ -607,7 +615,7 @@ describe("CheckpointReactor", () => { | ... | @@ -607,7 +615,7 @@ describe("CheckpointReactor", () => { |
| 607 | type: "turn.started", | 615 | type: "turn.started", |
| 608 | eventId: EventId.make("evt-turn-started-claude-1"), | 616 | eventId: EventId.make("evt-turn-started-claude-1"), |
| 609 | provider: ProviderDriverKind.make("claudeAgent"), | 617 | provider: ProviderDriverKind.make("claudeAgent"), |
| 610 | createdAt: new Date().toISOString(), | 618 | createdAt: "2026-01-01T00:00:00.000Z", |
| 611 | threadId: ThreadId.make("thread-1"), | 619 | threadId: ThreadId.make("thread-1"), |
| 612 | turnId: asTurnId("turn-claude-1"), | 620 | turnId: asTurnId("turn-claude-1"), |
| 613 | }); | 621 | }); |
| ... | @@ -621,7 +629,7 @@ describe("CheckpointReactor", () => { | ... | @@ -621,7 +629,7 @@ describe("CheckpointReactor", () => { |
| 621 | type: "turn.completed", | 629 | type: "turn.completed", |
| 622 | eventId: EventId.make("evt-turn-completed-claude-1"), | 630 | eventId: EventId.make("evt-turn-completed-claude-1"), |
| 623 | provider: ProviderDriverKind.make("claudeAgent"), | 631 | provider: ProviderDriverKind.make("claudeAgent"), |
| 624 | createdAt: new Date().toISOString(), | 632 | createdAt: "2026-01-01T00:00:00.000Z", |
| 625 | threadId: ThreadId.make("thread-1"), | 633 | threadId: ThreadId.make("thread-1"), |
| 626 | turnId: asTurnId("turn-claude-1"), | 634 | turnId: asTurnId("turn-claude-1"), |
| 627 | payload: { state: "completed" }, | 635 | payload: { state: "completed" }, |
| ... | @@ -641,7 +649,7 @@ describe("CheckpointReactor", () => { | ... | @@ -641,7 +649,7 @@ describe("CheckpointReactor", () => { |
| 641 | 649 | ||
| 642 | it("appends capture failure activity when turn diff summary cannot be derived", async () => { | 650 | it("appends capture failure activity when turn diff summary cannot be derived", async () => { |
| 643 | const harness = await createHarness({ seedFilesystemCheckpoints: false }); | 651 | const harness = await createHarness({ seedFilesystemCheckpoints: false }); |
| 644 | const createdAt = new Date().toISOString(); | 652 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 645 | 653 | ||
| 646 | await Effect.runPromise( | 654 | await Effect.runPromise( |
| 647 | harness.engine.dispatch({ | 655 | harness.engine.dispatch({ |
| ... | @@ -666,7 +674,7 @@ describe("CheckpointReactor", () => { | ... | @@ -666,7 +674,7 @@ describe("CheckpointReactor", () => { |
| 666 | eventId: EventId.make("evt-turn-completed-missing-baseline"), | 674 | eventId: EventId.make("evt-turn-completed-missing-baseline"), |
| 667 | provider: ProviderDriverKind.make("codex"), | 675 | provider: ProviderDriverKind.make("codex"), |
| 668 | 676 | ||
| 669 | createdAt: new Date().toISOString(), | 677 | createdAt: "2026-01-01T00:00:00.000Z", |
| 670 | threadId: ThreadId.make("thread-1"), | 678 | threadId: ThreadId.make("thread-1"), |
| 671 | turnId: asTurnId("turn-missing-baseline"), | 679 | turnId: asTurnId("turn-missing-baseline"), |
| 672 | payload: { state: "completed" }, | 680 | payload: { state: "completed" }, |
| ... | @@ -706,7 +714,7 @@ describe("CheckpointReactor", () => { | ... | @@ -706,7 +714,7 @@ describe("CheckpointReactor", () => { |
| 706 | }, | 714 | }, |
| 707 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | 715 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 708 | runtimeMode: "approval-required", | 716 | runtimeMode: "approval-required", |
| 709 | createdAt: new Date().toISOString(), | 717 | createdAt: "2026-01-01T00:00:00.000Z", |
| 710 | }), | 718 | }), |
| 711 | ); | 719 | ); |
| 712 | 720 | ||
| ... | @@ -729,7 +737,7 @@ describe("CheckpointReactor", () => { | ... | @@ -729,7 +737,7 @@ describe("CheckpointReactor", () => { |
| 729 | seedFilesystemCheckpoints: false, | 737 | seedFilesystemCheckpoints: false, |
| 730 | threadWorktreePath: null, | 738 | threadWorktreePath: null, |
| 731 | }); | 739 | }); |
| 732 | const createdAt = new Date().toISOString(); | 740 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 733 | 741 | ||
| 734 | await Effect.runPromise( | 742 | await Effect.runPromise( |
| 735 | harness.engine.dispatch({ | 743 | harness.engine.dispatch({ |
| ... | @@ -755,7 +763,7 @@ describe("CheckpointReactor", () => { | ... | @@ -755,7 +763,7 @@ describe("CheckpointReactor", () => { |
| 755 | eventId: EventId.make("evt-turn-completed-missing-provider-cwd"), | 763 | eventId: EventId.make("evt-turn-completed-missing-provider-cwd"), |
| 756 | provider: ProviderDriverKind.make("codex"), | 764 | provider: ProviderDriverKind.make("codex"), |
| 757 | 765 | ||
| 758 | createdAt: new Date().toISOString(), | 766 | createdAt: "2026-01-01T00:00:00.000Z", |
| 759 | threadId: ThreadId.make("thread-1"), | 767 | threadId: ThreadId.make("thread-1"), |
| 760 | turnId: asTurnId("turn-missing-cwd"), | 768 | turnId: asTurnId("turn-missing-cwd"), |
| 761 | payload: { state: "completed" }, | 769 | payload: { state: "completed" }, |
| ... | @@ -776,7 +784,7 @@ describe("CheckpointReactor", () => { | ... | @@ -776,7 +784,7 @@ describe("CheckpointReactor", () => { |
| 776 | 784 | ||
| 777 | it("ignores non-v2 checkpoint.captured runtime events", async () => { | 785 | it("ignores non-v2 checkpoint.captured runtime events", async () => { |
| 778 | const harness = await createHarness(); | 786 | const harness = await createHarness(); |
| 779 | const createdAt = new Date().toISOString(); | 787 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 780 | 788 | ||
| 781 | await Effect.runPromise( | 789 | await Effect.runPromise( |
| 782 | harness.engine.dispatch({ | 790 | harness.engine.dispatch({ |
| ... | @@ -801,7 +809,7 @@ describe("CheckpointReactor", () => { | ... | @@ -801,7 +809,7 @@ describe("CheckpointReactor", () => { |
| 801 | eventId: EventId.make("evt-checkpoint-captured-3"), | 809 | eventId: EventId.make("evt-checkpoint-captured-3"), |
| 802 | provider: ProviderDriverKind.make("codex"), | 810 | provider: ProviderDriverKind.make("codex"), |
| 803 | 811 | ||
| 804 | createdAt: new Date().toISOString(), | 812 | createdAt: "2026-01-01T00:00:00.000Z", |
| 805 | threadId: ThreadId.make("thread-1"), | 813 | threadId: ThreadId.make("thread-1"), |
| 806 | turnId: asTurnId("turn-3"), | 814 | turnId: asTurnId("turn-3"), |
| 807 | turnCount: 3, | 815 | turnCount: 3, |
| ... | @@ -826,7 +834,7 @@ describe("CheckpointReactor", () => { | ... | @@ -826,7 +834,7 @@ describe("CheckpointReactor", () => { |
| 826 | seedFilesystemCheckpoints: false, | 834 | seedFilesystemCheckpoints: false, |
| 827 | providerSessionCwd: nonRepositorySessionCwd, | 835 | providerSessionCwd: nonRepositorySessionCwd, |
| 828 | }); | 836 | }); |
| 829 | const createdAt = new Date().toISOString(); | 837 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 830 | 838 | ||
| 831 | await Effect.runPromise( | 839 | await Effect.runPromise( |
| 832 | harness.engine.dispatch({ | 840 | harness.engine.dispatch({ |
| ... | @@ -851,7 +859,7 @@ describe("CheckpointReactor", () => { | ... | @@ -851,7 +859,7 @@ describe("CheckpointReactor", () => { |
| 851 | eventId: EventId.make("evt-runtime-capture-failure"), | 859 | eventId: EventId.make("evt-runtime-capture-failure"), |
| 852 | provider: ProviderDriverKind.make("codex"), | 860 | provider: ProviderDriverKind.make("codex"), |
| 853 | 861 | ||
| 854 | createdAt: new Date().toISOString(), | 862 | createdAt: "2026-01-01T00:00:00.000Z", |
| 855 | threadId: ThreadId.make("thread-1"), | 863 | threadId: ThreadId.make("thread-1"), |
| 856 | turnId: asTurnId("turn-runtime-failure"), | 864 | turnId: asTurnId("turn-runtime-failure"), |
| 857 | payload: { state: "completed" }, | 865 | payload: { state: "completed" }, |
| ... | @@ -862,7 +870,7 @@ describe("CheckpointReactor", () => { | ... | @@ -862,7 +870,7 @@ describe("CheckpointReactor", () => { |
| 862 | eventId: EventId.make("evt-turn-started-after-runtime-failure"), | 870 | eventId: EventId.make("evt-turn-started-after-runtime-failure"), |
| 863 | provider: ProviderDriverKind.make("codex"), | 871 | provider: ProviderDriverKind.make("codex"), |
| 864 | 872 | ||
| 865 | createdAt: new Date().toISOString(), | 873 | createdAt: "2026-01-01T00:00:00.000Z", |
| 866 | threadId: ThreadId.make("thread-1"), | 874 | threadId: ThreadId.make("thread-1"), |
| 867 | turnId: asTurnId("turn-after-runtime-failure"), | 875 | turnId: asTurnId("turn-after-runtime-failure"), |
| 868 | }); | 876 | }); |
| ... | @@ -878,7 +886,7 @@ describe("CheckpointReactor", () => { | ... | @@ -878,7 +886,7 @@ describe("CheckpointReactor", () => { |
| 878 | 886 | ||
| 879 | it("executes provider revert and emits thread.reverted for checkpoint revert requests", async () => { | 887 | it("executes provider revert and emits thread.reverted for checkpoint revert requests", async () => { |
| 880 | const harness = await createHarness(); | 888 | const harness = await createHarness(); |
| 881 | const createdAt = new Date().toISOString(); | 889 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 882 | 890 | ||
| 883 | await Effect.runPromise( | 891 | await Effect.runPromise( |
| 884 | harness.engine.dispatch({ | 892 | harness.engine.dispatch({ |
| ... | @@ -959,7 +967,7 @@ describe("CheckpointReactor", () => { | ... | @@ -959,7 +967,7 @@ describe("CheckpointReactor", () => { |
| 959 | 967 | ||
| 960 | it("executes provider revert and emits thread.reverted for claude sessions", async () => { | 968 | it("executes provider revert and emits thread.reverted for claude sessions", async () => { |
| 961 | const harness = await createHarness({ providerName: ProviderDriverKind.make("claudeAgent") }); | 969 | const harness = await createHarness({ providerName: ProviderDriverKind.make("claudeAgent") }); |
| 962 | const createdAt = new Date().toISOString(); | 970 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 963 | 971 | ||
| 964 | await Effect.runPromise( | 972 | await Effect.runPromise( |
| 965 | harness.engine.dispatch({ | 973 | harness.engine.dispatch({ |
| ... | @@ -1028,7 +1036,7 @@ describe("CheckpointReactor", () => { | ... | @@ -1028,7 +1036,7 @@ describe("CheckpointReactor", () => { |
| 1028 | 1036 | ||
| 1029 | it("processes consecutive revert requests with deterministic rollback sequencing", async () => { | 1037 | it("processes consecutive revert requests with deterministic rollback sequencing", async () => { |
| 1030 | const harness = await createHarness(); | 1038 | const harness = await createHarness(); |
| 1031 | const createdAt = new Date().toISOString(); | 1039 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 1032 | 1040 | ||
| 1033 | await Effect.runPromise( | 1041 | await Effect.runPromise( |
| 1034 | harness.engine.dispatch({ | 1042 | harness.engine.dispatch({ |
| ... | @@ -1111,7 +1119,7 @@ describe("CheckpointReactor", () => { | ... | @@ -1111,7 +1119,7 @@ describe("CheckpointReactor", () => { |
| 1111 | 1119 | ||
| 1112 | it("appends an error activity when revert is requested without an active session", async () => { | 1120 | it("appends an error activity when revert is requested without an active session", async () => { |
| 1113 | const harness = await createHarness({ hasSession: false }); | 1121 | const harness = await createHarness({ hasSession: false }); |
| 1114 | const createdAt = new Date().toISOString(); | 1122 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 1115 | 1123 | ||
| 1116 | await Effect.runPromise( | 1124 | await Effect.runPromise( |
| 1117 | harness.engine.dispatch({ | 1125 | harness.engine.dispatch({ |
apps/server/src/orchestration/Layers/CheckpointReactor.ts+33-20| ... | @@ -8,7 +8,12 @@ import { | ... | @@ -8,7 +8,12 @@ import { |
| 8 | type OrchestrationEvent, | 8 | type OrchestrationEvent, |
| 9 | type ProviderRuntimeEvent, | 9 | type ProviderRuntimeEvent, |
| 10 | } from "@t3tools/contracts"; | 10 | } from "@t3tools/contracts"; |
| 11 | import { Cause, Effect, Layer, Option, Stream } from "effect"; | 11 | import * as Cause from "effect/Cause"; |
| 12 | import * as DateTime from "effect/DateTime"; | ||
| 13 | import * as Effect from "effect/Effect"; | ||
| 14 | import * as Layer from "effect/Layer"; | ||
| 15 | import * as Option from "effect/Option"; | ||
| 16 | import * as Stream from "effect/Stream"; | ||
| 12 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; | 17 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; |
| 13 | 18 | ||
| 14 | import { parseTurnDiffFilesFromUnifiedDiff } from "../../checkpointing/Diffs.ts"; | 19 | import { parseTurnDiffFilesFromUnifiedDiff } from "../../checkpointing/Diffs.ts"; |
| ... | @@ -28,6 +33,8 @@ import { isGitRepository } from "../../git/Utils.ts"; | ... | @@ -28,6 +33,8 @@ import { isGitRepository } from "../../git/Utils.ts"; |
| 28 | import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; | 33 | import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; |
| 29 | import { WorkspaceEntries } from "../../workspace/Services/WorkspaceEntries.ts"; | 34 | import { WorkspaceEntries } from "../../workspace/Services/WorkspaceEntries.ts"; |
| 30 | 35 | ||
| 36 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); | ||
| 37 | |||
| 31 | type ReactorInput = | 38 | type ReactorInput = |
| 32 | | { | 39 | | { |
| 33 | readonly source: "runtime"; | 40 | readonly source: "runtime"; |
| ... | @@ -584,7 +591,7 @@ const make = Effect.gen(function* () { | ... | @@ -584,7 +591,7 @@ const make = Effect.gen(function* () { |
| 584 | const handleRevertRequested = Effect.fn("handleRevertRequested")(function* ( | 591 | const handleRevertRequested = Effect.fn("handleRevertRequested")(function* ( |
| 585 | event: Extract<OrchestrationEvent, { type: "thread.checkpoint-revert-requested" }>, | 592 | event: Extract<OrchestrationEvent, { type: "thread.checkpoint-revert-requested" }>, |
| 586 | ) { | 593 | ) { |
| 587 | const now = new Date().toISOString(); | 594 | const now = DateTime.formatIso(yield* DateTime.now); |
| 588 | 595 | ||
| 589 | const thread = yield* resolveThreadDetail(event.payload.threadId); | 596 | const thread = yield* resolveThreadDetail(event.payload.threadId); |
| 590 | if (!thread) { | 597 | if (!thread) { |
| ... | @@ -717,12 +724,14 @@ const make = Effect.gen(function* () { | ... | @@ -717,12 +724,14 @@ const make = Effect.gen(function* () { |
| 717 | if (event.type === "thread.checkpoint-revert-requested") { | 724 | if (event.type === "thread.checkpoint-revert-requested") { |
| 718 | yield* handleRevertRequested(event).pipe( | 725 | yield* handleRevertRequested(event).pipe( |
| 719 | Effect.catch((error) => | 726 | Effect.catch((error) => |
| 720 | appendRevertFailureActivity({ | 727 | Effect.flatMap(nowIso, (createdAt) => |
| 721 | threadId: event.payload.threadId, | 728 | appendRevertFailureActivity({ |
| 722 | turnCount: event.payload.turnCount, | 729 | threadId: event.payload.threadId, |
| 723 | detail: error.message, | 730 | turnCount: event.payload.turnCount, |
| 724 | createdAt: new Date().toISOString(), | 731 | detail: error.message, |
| 725 | }), | 732 | createdAt, |
| 733 | }), | ||
| 734 | ), | ||
| 726 | ), | 735 | ), |
| 727 | ); | 736 | ); |
| 728 | return; | 737 | return; |
| ... | @@ -736,12 +745,14 @@ const make = Effect.gen(function* () { | ... | @@ -736,12 +745,14 @@ const make = Effect.gen(function* () { |
| 736 | if (event.type === "thread.turn-diff-completed") { | 745 | if (event.type === "thread.turn-diff-completed") { |
| 737 | yield* captureCheckpointFromPlaceholder(event).pipe( | 746 | yield* captureCheckpointFromPlaceholder(event).pipe( |
| 738 | Effect.catch((error) => | 747 | Effect.catch((error) => |
| 739 | appendCaptureFailureActivity({ | 748 | Effect.flatMap(nowIso, (createdAt) => |
| 740 | threadId: event.payload.threadId, | 749 | appendCaptureFailureActivity({ |
| 741 | turnId: event.payload.turnId, | 750 | threadId: event.payload.threadId, |
| 742 | detail: error.message, | 751 | turnId: event.payload.turnId, |
| 743 | createdAt: new Date().toISOString(), | 752 | detail: error.message, |
| 744 | }).pipe(Effect.catch(() => Effect.void)), | 753 | createdAt, |
| 754 | }).pipe(Effect.catch(() => Effect.void)), | ||
| 755 | ), | ||
| 745 | ), | 756 | ), |
| 746 | ); | 757 | ); |
| 747 | } | 758 | } |
| ... | @@ -760,12 +771,14 @@ const make = Effect.gen(function* () { | ... | @@ -760,12 +771,14 @@ const make = Effect.gen(function* () { |
| 760 | yield* refreshLocalGitStatusFromTurnCompletion(event); | 771 | yield* refreshLocalGitStatusFromTurnCompletion(event); |
| 761 | yield* captureCheckpointFromTurnCompletion(event).pipe( | 772 | yield* captureCheckpointFromTurnCompletion(event).pipe( |
| 762 | Effect.catch((error) => | 773 | Effect.catch((error) => |
| 763 | appendCaptureFailureActivity({ | 774 | Effect.flatMap(nowIso, (createdAt) => |
| 764 | threadId: event.threadId, | 775 | appendCaptureFailureActivity({ |
| 765 | turnId, | 776 | threadId: event.threadId, |
| 766 | detail: error.message, | 777 | turnId, |
| 767 | createdAt: new Date().toISOString(), | 778 | detail: error.message, |
| 768 | }).pipe(Effect.catch(() => Effect.void)), | 779 | createdAt, |
| 780 | }).pipe(Effect.catch(() => Effect.void)), | ||
| 781 | ), | ||
| 769 | ), | 782 | ), |
| 770 | ); | 783 | ); |
| 771 | return; | 784 | return; |
apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts+8-3| ... | @@ -8,9 +8,14 @@ import { | ... | @@ -8,9 +8,14 @@ import { |
| 8 | TurnId, | 8 | TurnId, |
| 9 | type OrchestrationEvent, | 9 | type OrchestrationEvent, |
| 10 | ProviderInstanceId, | 10 | ProviderInstanceId, |
| 11 | type OrchestrationReadModel, | ||
| 12 | } from "@t3tools/contracts"; | 11 | } from "@t3tools/contracts"; |
| 13 | import { Effect, Layer, ManagedRuntime, Metric, Option, Queue, Stream } from "effect"; | 12 | import * as Effect from "effect/Effect"; |
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as ManagedRuntime from "effect/ManagedRuntime"; | ||
| 15 | import * as Metric from "effect/Metric"; | ||
| 16 | import * as Option from "effect/Option"; | ||
| 17 | import * as Queue from "effect/Queue"; | ||
| 18 | import * as Stream from "effect/Stream"; | ||
| 14 | import { describe, expect, it } from "vitest"; | 19 | import { describe, expect, it } from "vitest"; |
| 15 | 20 | ||
| 16 | import { PersistenceSqlError } from "../../persistence/Errors.ts"; | 21 | import { PersistenceSqlError } from "../../persistence/Errors.ts"; |
| ... | @@ -69,7 +74,7 @@ async function createOrchestrationSystem() { | ... | @@ -69,7 +74,7 @@ async function createOrchestrationSystem() { |
| 69 | } | 74 | } |
| 70 | 75 | ||
| 71 | function now() { | 76 | function now() { |
| 72 | return new Date().toISOString(); | 77 | return "2026-01-01T00:00:00.000Z"; |
| 73 | } | 78 | } |
| 74 | 79 | ||
| 75 | const hasMetricSnapshot = ( | 80 | const hasMetricSnapshot = ( |
apps/server/src/orchestration/Layers/OrchestrationEngine.ts+26-20| ... | @@ -5,20 +5,20 @@ import type { | ... | @@ -5,20 +5,20 @@ import type { |
| 5 | ThreadId, | 5 | ThreadId, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { OrchestrationCommand } from "@t3tools/contracts"; | 7 | import { OrchestrationCommand } from "@t3tools/contracts"; |
| 8 | import { | 8 | import * as Cause from "effect/Cause"; |
| 9 | Cause, | 9 | import * as Clock from "effect/Clock"; |
| 10 | Deferred, | 10 | import * as DateTime from "effect/DateTime"; |
| 11 | Duration, | 11 | import * as Deferred from "effect/Deferred"; |
| 12 | Effect, | 12 | import * as Duration from "effect/Duration"; |
| 13 | Exit, | 13 | import * as Effect from "effect/Effect"; |
| 14 | Layer, | 14 | import * as Exit from "effect/Exit"; |
| 15 | Metric, | 15 | import * as Layer from "effect/Layer"; |
| 16 | Option, | 16 | import * as Metric from "effect/Metric"; |
| 17 | PubSub, | 17 | import * as Option from "effect/Option"; |
| 18 | Queue, | 18 | import * as PubSub from "effect/PubSub"; |
| 19 | Schema, | 19 | import * as Queue from "effect/Queue"; |
| 20 | Stream, | 20 | import * as Schema from "effect/Schema"; |
| 21 | } from "effect"; | 21 | import * as Stream from "effect/Stream"; |
| 22 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 22 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 23 | 23 | ||
| 24 | import { | 24 | import { |
| ... | @@ -78,7 +78,8 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -78,7 +78,8 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 78 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 78 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 79 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; | 79 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; |
| 80 | 80 | ||
| 81 | let commandReadModel = createEmptyReadModel(new Date().toISOString()); | 81 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 82 | let commandReadModel = createEmptyReadModel(yield* nowIso); | ||
| 82 | 83 | ||
| 83 | const commandQueue = yield* Queue.unbounded<CommandEnvelope>(); | 84 | const commandQueue = yield* Queue.unbounded<CommandEnvelope>(); |
| 84 | const eventPubSub = yield* PubSub.unbounded<OrchestrationEvent>(); | 85 | const eventPubSub = yield* PubSub.unbounded<OrchestrationEvent>(); |
| ... | @@ -97,7 +98,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -97,7 +98,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 97 | 98 | ||
| 98 | const processEnvelope = (envelope: CommandEnvelope): Effect.Effect<void> => { | 99 | const processEnvelope = (envelope: CommandEnvelope): Effect.Effect<void> => { |
| 99 | const dispatchStartSequence = commandReadModel.snapshotSequence; | 100 | const dispatchStartSequence = commandReadModel.snapshotSequence; |
| 100 | const processingStartedAtMs = Date.now(); | 101 | let processingStartedAtMs = 0; |
| 101 | const aggregateRef = commandToAggregateRef(envelope.command); | 102 | const aggregateRef = commandToAggregateRef(envelope.command); |
| 102 | const baseMetricAttributes = { | 103 | const baseMetricAttributes = { |
| 103 | commandType: envelope.command.type, | 104 | commandType: envelope.command.type, |
| ... | @@ -120,6 +121,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -120,6 +121,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 120 | 121 | ||
| 121 | return Effect.exit( | 122 | return Effect.exit( |
| 122 | Effect.gen(function* () { | 123 | Effect.gen(function* () { |
| 124 | processingStartedAtMs = yield* Clock.currentTimeMillis; | ||
| 123 | yield* Effect.annotateCurrentSpan({ | 125 | yield* Effect.annotateCurrentSpan({ |
| 124 | "orchestration.command_id": envelope.command.commandId, | 126 | "orchestration.command_id": envelope.command.commandId, |
| 125 | "orchestration.command_type": envelope.command.type, | 127 | "orchestration.command_type": envelope.command.type, |
| ... | @@ -205,7 +207,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -205,7 +207,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 205 | ackEventType: event.type, | 207 | ackEventType: event.type, |
| 206 | }), | 208 | }), |
| 207 | ), | 209 | ), |
| 208 | Duration.millis(Math.max(0, Date.now() - envelope.startedAtMs)), | 210 | Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - envelope.startedAtMs)), |
| 209 | ); | 211 | ); |
| 210 | } | 212 | } |
| 211 | } | 213 | } |
| ... | @@ -224,7 +226,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -224,7 +226,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 224 | orchestrationCommandDuration, | 226 | orchestrationCommandDuration, |
| 225 | metricAttributes(baseMetricAttributes), | 227 | metricAttributes(baseMetricAttributes), |
| 226 | ), | 228 | ), |
| 227 | Duration.millis(Math.max(0, Date.now() - processingStartedAtMs)), | 229 | Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - processingStartedAtMs)), |
| 228 | ); | 230 | ); |
| 229 | yield* Metric.update( | 231 | yield* Metric.update( |
| 230 | Metric.withAttributes( | 232 | Metric.withAttributes( |
| ... | @@ -263,7 +265,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -263,7 +265,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 263 | commandId: envelope.command.commandId, | 265 | commandId: envelope.command.commandId, |
| 264 | aggregateKind: aggregateRef.aggregateKind, | 266 | aggregateKind: aggregateRef.aggregateKind, |
| 265 | aggregateId: aggregateRef.aggregateId, | 267 | aggregateId: aggregateRef.aggregateId, |
| 266 | acceptedAt: new Date().toISOString(), | 268 | acceptedAt: yield* nowIso, |
| 267 | resultSequence: commandReadModel.snapshotSequence, | 269 | resultSequence: commandReadModel.snapshotSequence, |
| 268 | status: "rejected", | 270 | status: "rejected", |
| 269 | error: error.message, | 271 | error: error.message, |
| ... | @@ -293,7 +295,11 @@ const makeOrchestrationEngine = Effect.gen(function* () { | ... | @@ -293,7 +295,11 @@ const makeOrchestrationEngine = Effect.gen(function* () { |
| 293 | const dispatch: OrchestrationEngineShape["dispatch"] = (command) => | 295 | const dispatch: OrchestrationEngineShape["dispatch"] = (command) => |
| 294 | Effect.gen(function* () { | 296 | Effect.gen(function* () { |
| 295 | const result = yield* Deferred.make<{ sequence: number }, OrchestrationDispatchError>(); | 297 | const result = yield* Deferred.make<{ sequence: number }, OrchestrationDispatchError>(); |
| 296 | yield* Queue.offer(commandQueue, { command, result, startedAtMs: Date.now() }); | 298 | yield* Queue.offer(commandQueue, { |
| 299 | command, | ||
| 300 | result, | ||
| 301 | startedAtMs: yield* Clock.currentTimeMillis, | ||
| 302 | }); | ||
| 297 | return yield* Deferred.await(result); | 303 | return yield* Deferred.await(result); |
| 298 | }); | 304 | }); |
| 299 | 305 |
apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts+5-1| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Effect, Exit, Layer, ManagedRuntime, Scope } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Exit from "effect/Exit"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as ManagedRuntime from "effect/ManagedRuntime"; | ||
| 5 | import * as Scope from "effect/Scope"; | ||
| 2 | import { afterEach, describe, expect, it } from "vitest"; | 6 | import { afterEach, describe, expect, it } from "vitest"; |
| 3 | 7 | ||
| 4 | import { CheckpointReactor } from "../Services/CheckpointReactor.ts"; | 8 | import { CheckpointReactor } from "../Services/CheckpointReactor.ts"; |
apps/server/src/orchestration/Layers/OrchestrationReactor.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Effect, Layer } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 2 | 3 | ||
| 3 | import { | 4 | import { |
| 4 | OrchestrationReactor, | 5 | OrchestrationReactor, |
apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+23-16| ... | @@ -11,7 +11,10 @@ import { | ... | @@ -11,7 +11,10 @@ import { |
| 11 | } from "@t3tools/contracts"; | 11 | } from "@t3tools/contracts"; |
| 12 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 12 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 13 | import { assert, it } from "@effect/vitest"; | 13 | import { assert, it } from "@effect/vitest"; |
| 14 | import { Effect, FileSystem, Layer, Path } from "effect"; | 14 | import * as Effect from "effect/Effect"; |
| 15 | import * as FileSystem from "effect/FileSystem"; | ||
| 16 | import * as Layer from "effect/Layer"; | ||
| 17 | import * as Path from "effect/Path"; | ||
| 15 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 18 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 16 | 19 | ||
| 17 | import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts"; | 20 | import { OrchestrationCommandReceiptRepositoryLive } from "../../persistence/Layers/OrchestrationCommandReceipts.ts"; |
| ... | @@ -55,7 +58,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { | ... | @@ -55,7 +58,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 55 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 58 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 56 | const eventStore = yield* OrchestrationEventStore; | 59 | const eventStore = yield* OrchestrationEventStore; |
| 57 | const sql = yield* SqlClient.SqlClient; | 60 | const sql = yield* SqlClient.SqlClient; |
| 58 | const now = new Date().toISOString(); | 61 | const now = "2026-01-01T00:00:00.000Z"; |
| 59 | 62 | ||
| 60 | yield* eventStore.append({ | 63 | yield* eventStore.append({ |
| 61 | type: "project.created", | 64 | type: "project.created", |
| ... | @@ -180,7 +183,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-base-")))( | ... | @@ -180,7 +183,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-base-")))( |
| 180 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 183 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 181 | const eventStore = yield* OrchestrationEventStore; | 184 | const eventStore = yield* OrchestrationEventStore; |
| 182 | const sql = yield* SqlClient.SqlClient; | 185 | const sql = yield* SqlClient.SqlClient; |
| 183 | const now = new Date().toISOString(); | 186 | const now = "2026-01-01T00:00:00.000Z"; |
| 184 | 187 | ||
| 185 | yield* eventStore.append({ | 188 | yield* eventStore.append({ |
| 186 | type: "thread.message-sent", | 189 | type: "thread.message-sent", |
| ... | @@ -224,6 +227,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-base-")))( | ... | @@ -224,6 +227,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-base-")))( |
| 224 | WHERE message_id = 'message-attachments' | 227 | WHERE message_id = 'message-attachments' |
| 225 | `; | 228 | `; |
| 226 | assert.equal(rows.length, 1); | 229 | assert.equal(rows.length, 1); |
| 230 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 227 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ | 231 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ |
| 228 | { | 232 | { |
| 229 | type: "image", | 233 | type: "image", |
| ... | @@ -246,7 +250,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta | ... | @@ -246,7 +250,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta |
| 246 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 250 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 247 | const eventStore = yield* OrchestrationEventStore; | 251 | const eventStore = yield* OrchestrationEventStore; |
| 248 | const sql = yield* SqlClient.SqlClient; | 252 | const sql = yield* SqlClient.SqlClient; |
| 249 | const now = new Date().toISOString(); | 253 | const now = "2026-01-01T00:00:00.000Z"; |
| 250 | 254 | ||
| 251 | yield* eventStore.append({ | 255 | yield* eventStore.append({ |
| 252 | type: "thread.message-sent", | 256 | type: "thread.message-sent", |
| ... | @@ -297,6 +301,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta | ... | @@ -297,6 +301,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta |
| 297 | WHERE message_id = 'message-attachments-safe' | 301 | WHERE message_id = 'message-attachments-safe' |
| 298 | `; | 302 | `; |
| 299 | assert.equal(rows.length, 1); | 303 | assert.equal(rows.length, 1); |
| 304 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 300 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ | 305 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ |
| 301 | { | 306 | { |
| 302 | type: "image", | 307 | type: "image", |
| ... | @@ -326,8 +331,8 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { | ... | @@ -326,8 +331,8 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 326 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 331 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 327 | const eventStore = yield* OrchestrationEventStore; | 332 | const eventStore = yield* OrchestrationEventStore; |
| 328 | const sql = yield* SqlClient.SqlClient; | 333 | const sql = yield* SqlClient.SqlClient; |
| 329 | const now = new Date().toISOString(); | 334 | const now = "2026-01-01T00:00:00.000Z"; |
| 330 | const later = new Date(Date.now() + 1_000).toISOString(); | 335 | const later = "2026-01-01T00:00:01.000Z"; |
| 331 | 336 | ||
| 332 | yield* eventStore.append({ | 337 | yield* eventStore.append({ |
| 333 | type: "project.created", | 338 | type: "project.created", |
| ... | @@ -441,6 +446,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { | ... | @@ -441,6 +446,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 441 | WHERE message_id = 'message-clear-attachments' | 446 | WHERE message_id = 'message-clear-attachments' |
| 442 | `; | 447 | `; |
| 443 | assert.equal(rows.length, 1); | 448 | assert.equal(rows.length, 1); |
| 449 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 444 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), []); | 450 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), []); |
| 445 | }), | 451 | }), |
| 446 | ); | 452 | ); |
| ... | @@ -454,8 +460,8 @@ it.layer( | ... | @@ -454,8 +460,8 @@ it.layer( |
| 454 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 460 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 455 | const eventStore = yield* OrchestrationEventStore; | 461 | const eventStore = yield* OrchestrationEventStore; |
| 456 | const sql = yield* SqlClient.SqlClient; | 462 | const sql = yield* SqlClient.SqlClient; |
| 457 | const now = new Date().toISOString(); | 463 | const now = "2026-01-01T00:00:00.000Z"; |
| 458 | const later = new Date(Date.now() + 1_000).toISOString(); | 464 | const later = "2026-01-01T00:00:01.000Z"; |
| 459 | 465 | ||
| 460 | yield* eventStore.append({ | 466 | yield* eventStore.append({ |
| 461 | type: "project.created", | 467 | type: "project.created", |
| ... | @@ -576,6 +582,7 @@ it.layer( | ... | @@ -576,6 +582,7 @@ it.layer( |
| 576 | WHERE message_id = 'message-overwrite' | 582 | WHERE message_id = 'message-overwrite' |
| 577 | `; | 583 | `; |
| 578 | assert.equal(rows.length, 1); | 584 | assert.equal(rows.length, 1); |
| 585 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 579 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ | 586 | assert.deepEqual(JSON.parse(rows[0]?.attachmentsJson ?? "null"), [ |
| 580 | { | 587 | { |
| 581 | type: "image", | 588 | type: "image", |
| ... | @@ -598,7 +605,7 @@ it.layer( | ... | @@ -598,7 +605,7 @@ it.layer( |
| 598 | const eventStore = yield* OrchestrationEventStore; | 605 | const eventStore = yield* OrchestrationEventStore; |
| 599 | const path = yield* Path.Path; | 606 | const path = yield* Path.Path; |
| 600 | const sql = yield* SqlClient.SqlClient; | 607 | const sql = yield* SqlClient.SqlClient; |
| 601 | const now = new Date().toISOString(); | 608 | const now = "2026-01-01T00:00:00.000Z"; |
| 602 | 609 | ||
| 603 | const appendAndProject = (event: Parameters<typeof eventStore.append>[0]) => | 610 | const appendAndProject = (event: Parameters<typeof eventStore.append>[0]) => |
| 604 | eventStore | 611 | eventStore |
| ... | @@ -722,7 +729,7 @@ it.layer( | ... | @@ -722,7 +729,7 @@ it.layer( |
| 722 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 729 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 723 | const eventStore = yield* OrchestrationEventStore; | 730 | const eventStore = yield* OrchestrationEventStore; |
| 724 | const { attachmentsDir } = yield* ServerConfig; | 731 | const { attachmentsDir } = yield* ServerConfig; |
| 725 | const now = new Date().toISOString(); | 732 | const now = "2026-01-01T00:00:00.000Z"; |
| 726 | const threadId = ThreadId.make("Thread Revert.Files"); | 733 | const threadId = ThreadId.make("Thread Revert.Files"); |
| 727 | const keepAttachmentId = "thread-revert-files-00000000-0000-4000-8000-000000000001"; | 734 | const keepAttachmentId = "thread-revert-files-00000000-0000-4000-8000-000000000001"; |
| 728 | const removeAttachmentId = "thread-revert-files-00000000-0000-4000-8000-000000000002"; | 735 | const removeAttachmentId = "thread-revert-files-00000000-0000-4000-8000-000000000002"; |
| ... | @@ -931,7 +938,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta | ... | @@ -931,7 +938,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta |
| 931 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 938 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 932 | const eventStore = yield* OrchestrationEventStore; | 939 | const eventStore = yield* OrchestrationEventStore; |
| 933 | const { attachmentsDir } = yield* ServerConfig; | 940 | const { attachmentsDir } = yield* ServerConfig; |
| 934 | const now = new Date().toISOString(); | 941 | const now = "2026-01-01T00:00:00.000Z"; |
| 935 | const threadId = ThreadId.make("Thread Delete.Files"); | 942 | const threadId = ThreadId.make("Thread Delete.Files"); |
| 936 | const attachmentId = "thread-delete-files-00000000-0000-4000-8000-000000000001"; | 943 | const attachmentId = "thread-delete-files-00000000-0000-4000-8000-000000000001"; |
| 937 | const otherThreadAttachmentId = | 944 | const otherThreadAttachmentId = |
| ... | @@ -1063,7 +1070,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta | ... | @@ -1063,7 +1070,7 @@ it.layer(Layer.fresh(makeProjectionPipelinePrefixedTestLayer("t3-projection-atta |
| 1063 | const path = yield* Path.Path; | 1070 | const path = yield* Path.Path; |
| 1064 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 1071 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 1065 | const eventStore = yield* OrchestrationEventStore; | 1072 | const eventStore = yield* OrchestrationEventStore; |
| 1066 | const now = new Date().toISOString(); | 1073 | const now = "2026-01-01T00:00:00.000Z"; |
| 1067 | const { attachmentsDir: attachmentsRootDir, stateDir } = yield* ServerConfig; | 1074 | const { attachmentsDir: attachmentsRootDir, stateDir } = yield* ServerConfig; |
| 1068 | const attachmentsSentinelPath = path.join(attachmentsRootDir, "sentinel.txt"); | 1075 | const attachmentsSentinelPath = path.join(attachmentsRootDir, "sentinel.txt"); |
| 1069 | const stateDirSentinelPath = path.join(stateDir, "state-sentinel.txt"); | 1076 | const stateDirSentinelPath = path.join(stateDir, "state-sentinel.txt"); |
| ... | @@ -1103,7 +1110,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { | ... | @@ -1103,7 +1110,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 1103 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 1110 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 1104 | const eventStore = yield* OrchestrationEventStore; | 1111 | const eventStore = yield* OrchestrationEventStore; |
| 1105 | const sql = yield* SqlClient.SqlClient; | 1112 | const sql = yield* SqlClient.SqlClient; |
| 1106 | const now = new Date().toISOString(); | 1113 | const now = "2026-01-01T00:00:00.000Z"; |
| 1107 | 1114 | ||
| 1108 | yield* eventStore.append({ | 1115 | yield* eventStore.append({ |
| 1109 | type: "project.created", | 1116 | type: "project.created", |
| ... | @@ -1230,7 +1237,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { | ... | @@ -1230,7 +1237,7 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 1230 | const projectionPipeline = yield* OrchestrationProjectionPipeline; | 1237 | const projectionPipeline = yield* OrchestrationProjectionPipeline; |
| 1231 | const eventStore = yield* OrchestrationEventStore; | 1238 | const eventStore = yield* OrchestrationEventStore; |
| 1232 | const sql = yield* SqlClient.SqlClient; | 1239 | const sql = yield* SqlClient.SqlClient; |
| 1233 | const now = new Date().toISOString(); | 1240 | const now = "2026-01-01T00:00:00.000Z"; |
| 1234 | 1241 | ||
| 1235 | yield* eventStore.append({ | 1242 | yield* eventStore.append({ |
| 1236 | type: "project.created", | 1243 | type: "project.created", |
| ... | @@ -2187,7 +2194,7 @@ engineLayer("OrchestrationProjectionPipeline via engine dispatch", (it) => { | ... | @@ -2187,7 +2194,7 @@ engineLayer("OrchestrationProjectionPipeline via engine dispatch", (it) => { |
| 2187 | Effect.gen(function* () { | 2194 | Effect.gen(function* () { |
| 2188 | const engine = yield* OrchestrationEngineService; | 2195 | const engine = yield* OrchestrationEngineService; |
| 2189 | const sql = yield* SqlClient.SqlClient; | 2196 | const sql = yield* SqlClient.SqlClient; |
| 2190 | const createdAt = new Date().toISOString(); | 2197 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 2191 | 2198 | ||
| 2192 | yield* engine.dispatch({ | 2199 | yield* engine.dispatch({ |
| 2193 | type: "project.create", | 2200 | type: "project.create", |
| ... | @@ -2225,7 +2232,7 @@ engineLayer("OrchestrationProjectionPipeline via engine dispatch", (it) => { | ... | @@ -2225,7 +2232,7 @@ engineLayer("OrchestrationProjectionPipeline via engine dispatch", (it) => { |
| 2225 | Effect.gen(function* () { | 2232 | Effect.gen(function* () { |
| 2226 | const engine = yield* OrchestrationEngineService; | 2233 | const engine = yield* OrchestrationEngineService; |
| 2227 | const sql = yield* SqlClient.SqlClient; | 2234 | const sql = yield* SqlClient.SqlClient; |
| 2228 | const createdAt = new Date().toISOString(); | 2235 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 2229 | 2236 | ||
| 2230 | yield* engine.dispatch({ | 2237 | yield* engine.dispatch({ |
| 2231 | type: "project.create", | 2238 | type: "project.create", |
apps/server/src/orchestration/Layers/ProjectionPipeline.ts+6-1| ... | @@ -4,7 +4,12 @@ import { | ... | @@ -4,7 +4,12 @@ import { |
| 4 | type OrchestrationEvent, | 4 | type OrchestrationEvent, |
| 5 | ThreadId, | 5 | ThreadId, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Effect, FileSystem, Layer, Option, Path, Stream } from "effect"; | 7 | import * as Effect from "effect/Effect"; |
| 8 | import * as FileSystem from "effect/FileSystem"; | ||
| 9 | import * as Layer from "effect/Layer"; | ||
| 10 | import * as Option from "effect/Option"; | ||
| 11 | import * as Path from "effect/Path"; | ||
| 12 | import * as Stream from "effect/Stream"; | ||
| 8 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 13 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 9 | 14 | ||
| 10 | import { toPersistenceSqlError, type ProjectionRepositoryError } from "../../persistence/Errors.ts"; | 15 | import { toPersistenceSqlError, type ProjectionRepositoryError } from "../../persistence/Errors.ts"; |
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts+2-1| ... | @@ -8,7 +8,8 @@ import { | ... | @@ -8,7 +8,8 @@ import { |
| 8 | ProviderInstanceId, | 8 | ProviderInstanceId, |
| 9 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 10 | import { assert, it } from "@effect/vitest"; | 10 | import { assert, it } from "@effect/vitest"; |
| 11 | import { Effect, Layer } from "effect"; | 11 | import * as Effect from "effect/Effect"; |
| 12 | import * as Layer from "effect/Layer"; | ||
| 12 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 13 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 13 | 14 | ||
| 14 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; | 15 | import { SqlitePersistenceMemory } from "../../persistence/Layers/Sqlite.ts"; |
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts+8-4| ... | @@ -23,7 +23,11 @@ import { | ... | @@ -23,7 +23,11 @@ import { |
| 23 | ProjectId, | 23 | ProjectId, |
| 24 | ThreadId, | 24 | ThreadId, |
| 25 | } from "@t3tools/contracts"; | 25 | } from "@t3tools/contracts"; |
| 26 | import { Effect, Layer, Option, Schema, Struct } from "effect"; | 26 | import * as Effect from "effect/Effect"; |
| 27 | import * as Layer from "effect/Layer"; | ||
| 28 | import * as Option from "effect/Option"; | ||
| 29 | import * as Schema from "effect/Schema"; | ||
| 30 | import * as Struct from "effect/Struct"; | ||
| 27 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 31 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 28 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 32 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 29 | 33 | ||
| ... | @@ -1005,7 +1009,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { | ... | @@ -1005,7 +1009,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1005 | snapshotSequence: computeSnapshotSequence(stateRows), | 1009 | snapshotSequence: computeSnapshotSequence(stateRows), |
| 1006 | projects, | 1010 | projects, |
| 1007 | threads, | 1011 | threads, |
| 1008 | updatedAt: updatedAt ?? new Date(0).toISOString(), | 1012 | updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", |
| 1009 | }; | 1013 | }; |
| 1010 | 1014 | ||
| 1011 | return yield* decodeReadModel(snapshot).pipe( | 1015 | return yield* decodeReadModel(snapshot).pipe( |
| ... | @@ -1204,7 +1208,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { | ... | @@ -1204,7 +1208,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1204 | snapshotSequence: computeSnapshotSequence(stateRows), | 1208 | snapshotSequence: computeSnapshotSequence(stateRows), |
| 1205 | projects, | 1209 | projects, |
| 1206 | threads, | 1210 | threads, |
| 1207 | updatedAt: updatedAt ?? new Date(0).toISOString(), | 1211 | updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", |
| 1208 | } satisfies OrchestrationReadModel; | 1212 | } satisfies OrchestrationReadModel; |
| 1209 | }), | 1213 | }), |
| 1210 | ), | 1214 | ), |
| ... | @@ -1326,7 +1330,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { | ... | @@ -1326,7 +1330,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 1326 | hasActionableProposedPlan: row.hasActionableProposedPlan > 0, | 1330 | hasActionableProposedPlan: row.hasActionableProposedPlan > 0, |
| 1327 | }), | 1331 | }), |
| 1328 | ), | 1332 | ), |
| 1329 | updatedAt: updatedAt ?? new Date(0).toISOString(), | 1333 | updatedAt: updatedAt ?? "1970-01-01T00:00:00.000Z", |
| 1330 | }; | 1334 | }; |
| 1331 | 1335 | ||
| 1332 | return yield* decodeShellSnapshot(snapshot).pipe( | 1336 | return yield* decodeShellSnapshot(snapshot).pipe( |
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+43-35| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
| ... | @@ -20,7 +21,13 @@ import { | ... | @@ -20,7 +21,13 @@ import { |
| 20 | ThreadId, | 21 | ThreadId, |
| 21 | TurnId, | 22 | TurnId, |
| 22 | } from "@t3tools/contracts"; | 23 | } from "@t3tools/contracts"; |
| 23 | import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect"; | 24 | import * as Effect from "effect/Effect"; |
| 25 | import * as Exit from "effect/Exit"; | ||
| 26 | import * as Layer from "effect/Layer"; | ||
| 27 | import * as ManagedRuntime from "effect/ManagedRuntime"; | ||
| 28 | import * as PubSub from "effect/PubSub"; | ||
| 29 | import * as Scope from "effect/Scope"; | ||
| 30 | import * as Stream from "effect/Stream"; | ||
| 24 | import { afterEach, describe, expect, it, vi } from "vitest"; | 31 | import { afterEach, describe, expect, it, vi } from "vitest"; |
| 25 | 32 | ||
| 26 | import { deriveServerPaths, ServerConfig } from "../../config.ts"; | 33 | import { deriveServerPaths, ServerConfig } from "../../config.ts"; |
| ... | @@ -47,6 +54,7 @@ import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts"; | ... | @@ -47,6 +54,7 @@ import { OrchestrationEngineService } from "../Services/OrchestrationEngine.ts"; |
| 47 | import { ProviderCommandReactor } from "../Services/ProviderCommandReactor.ts"; | 54 | import { ProviderCommandReactor } from "../Services/ProviderCommandReactor.ts"; |
| 48 | import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts"; | 55 | import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts"; |
| 49 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 56 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 57 | import * as Clock from "effect/Clock"; | ||
| 50 | import { ServerSettingsService } from "../../serverSettings.ts"; | 58 | import { ServerSettingsService } from "../../serverSettings.ts"; |
| 51 | import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; | 59 | import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; |
| 52 | import { GitWorkflowService, type GitWorkflowServiceShape } from "../../git/GitWorkflowService.ts"; | 60 | import { GitWorkflowService, type GitWorkflowServiceShape } from "../../git/GitWorkflowService.ts"; |
| ... | @@ -63,15 +71,15 @@ async function waitFor( | ... | @@ -63,15 +71,15 @@ async function waitFor( |
| 63 | predicate: () => boolean | Promise<boolean>, | 71 | predicate: () => boolean | Promise<boolean>, |
| 64 | timeoutMs = 2000, | 72 | timeoutMs = 2000, |
| 65 | ): Promise<void> { | 73 | ): Promise<void> { |
| 66 | const deadline = Date.now() + timeoutMs; | 74 | const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; |
| 67 | const poll = async (): Promise<void> => { | 75 | const poll = async (): Promise<void> => { |
| 68 | if (await predicate()) { | 76 | if (await predicate()) { |
| 69 | return; | 77 | return; |
| 70 | } | 78 | } |
| 71 | if (Date.now() >= deadline) { | 79 | if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { |
| 72 | throw new Error("Timed out waiting for expectation."); | 80 | throw new Error("Timed out waiting for expectation."); |
| 73 | } | 81 | } |
| 74 | await new Promise((resolve) => setTimeout(resolve, 10)); | 82 | await Effect.runPromise(Effect.yieldNow); |
| 75 | return poll(); | 83 | return poll(); |
| 76 | }; | 84 | }; |
| 77 | 85 | ||
| ... | @@ -135,7 +143,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -135,7 +143,7 @@ describe("ProviderCommandReactor", () => { |
| 135 | readonly threadModelSelection?: ModelSelection; | 143 | readonly threadModelSelection?: ModelSelection; |
| 136 | readonly sessionModelSwitch?: "unsupported" | "in-session"; | 144 | readonly sessionModelSwitch?: "unsupported" | "in-session"; |
| 137 | }) { | 145 | }) { |
| 138 | const now = new Date().toISOString(); | 146 | const now = "2026-01-01T00:00:00.000Z"; |
| 139 | const baseDir = input?.baseDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "t3code-reactor-")); | 147 | const baseDir = input?.baseDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "t3code-reactor-")); |
| 140 | createdBaseDirs.add(baseDir); | 148 | createdBaseDirs.add(baseDir); |
| 141 | const { stateDir } = deriveServerPathsSync(baseDir, undefined); | 149 | const { stateDir } = deriveServerPathsSync(baseDir, undefined); |
| ... | @@ -408,7 +416,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -408,7 +416,7 @@ describe("ProviderCommandReactor", () => { |
| 408 | 416 | ||
| 409 | it("reacts to thread.turn.start by ensuring session and sending provider turn", async () => { | 417 | it("reacts to thread.turn.start by ensuring session and sending provider turn", async () => { |
| 410 | const harness = await createHarness(); | 418 | const harness = await createHarness(); |
| 411 | const now = new Date().toISOString(); | 419 | const now = "2026-01-01T00:00:00.000Z"; |
| 412 | 420 | ||
| 413 | await Effect.runPromise( | 421 | await Effect.runPromise( |
| 414 | harness.engine.dispatch({ | 422 | harness.engine.dispatch({ |
| ... | @@ -447,7 +455,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -447,7 +455,7 @@ describe("ProviderCommandReactor", () => { |
| 447 | 455 | ||
| 448 | it("generates a thread title on the first turn", async () => { | 456 | it("generates a thread title on the first turn", async () => { |
| 449 | const harness = await createHarness(); | 457 | const harness = await createHarness(); |
| 450 | const now = new Date().toISOString(); | 458 | const now = "2026-01-01T00:00:00.000Z"; |
| 451 | const seededTitle = "Please investigate reconnect failures after restar..."; | 459 | const seededTitle = "Please investigate reconnect failures after restar..."; |
| 452 | harness.generateThreadTitle.mockReturnValue(Effect.succeed({ title: "Generated title" })); | 460 | harness.generateThreadTitle.mockReturnValue(Effect.succeed({ title: "Generated title" })); |
| 453 | 461 | ||
| ... | @@ -497,7 +505,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -497,7 +505,7 @@ describe("ProviderCommandReactor", () => { |
| 497 | 505 | ||
| 498 | it("does not overwrite an existing custom thread title on the first turn", async () => { | 506 | it("does not overwrite an existing custom thread title on the first turn", async () => { |
| 499 | const harness = await createHarness(); | 507 | const harness = await createHarness(); |
| 500 | const now = new Date().toISOString(); | 508 | const now = "2026-01-01T00:00:00.000Z"; |
| 501 | const seededTitle = "Please investigate reconnect failures after restar..."; | 509 | const seededTitle = "Please investigate reconnect failures after restar..."; |
| 502 | 510 | ||
| 503 | await Effect.runPromise( | 511 | await Effect.runPromise( |
| ... | @@ -537,7 +545,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -537,7 +545,7 @@ describe("ProviderCommandReactor", () => { |
| 537 | 545 | ||
| 538 | it("matches the client-seeded title even when the outgoing prompt is reformatted", async () => { | 546 | it("matches the client-seeded title even when the outgoing prompt is reformatted", async () => { |
| 539 | const harness = await createHarness(); | 547 | const harness = await createHarness(); |
| 540 | const now = new Date().toISOString(); | 548 | const now = "2026-01-01T00:00:00.000Z"; |
| 541 | const seededTitle = "Fix reconnect spinner on resume"; | 549 | const seededTitle = "Fix reconnect spinner on resume"; |
| 542 | harness.generateThreadTitle.mockReturnValue( | 550 | harness.generateThreadTitle.mockReturnValue( |
| 543 | Effect.succeed({ | 551 | Effect.succeed({ |
| ... | @@ -588,7 +596,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -588,7 +596,7 @@ describe("ProviderCommandReactor", () => { |
| 588 | 596 | ||
| 589 | it("generates a worktree branch name for the first turn", async () => { | 597 | it("generates a worktree branch name for the first turn", async () => { |
| 590 | const harness = await createHarness(); | 598 | const harness = await createHarness(); |
| 591 | const now = new Date().toISOString(); | 599 | const now = "2026-01-01T00:00:00.000Z"; |
| 592 | 600 | ||
| 593 | await Effect.runPromise( | 601 | await Effect.runPromise( |
| 594 | harness.engine.dispatch({ | 602 | harness.engine.dispatch({ |
| ... | @@ -642,7 +650,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -642,7 +650,7 @@ describe("ProviderCommandReactor", () => { |
| 642 | 650 | ||
| 643 | it("forwards codex model options through session start and turn send", async () => { | 651 | it("forwards codex model options through session start and turn send", async () => { |
| 644 | const harness = await createHarness(); | 652 | const harness = await createHarness(); |
| 645 | const now = new Date().toISOString(); | 653 | const now = "2026-01-01T00:00:00.000Z"; |
| 646 | 654 | ||
| 647 | await Effect.runPromise( | 655 | await Effect.runPromise( |
| 648 | harness.engine.dispatch({ | 656 | harness.engine.dispatch({ |
| ... | @@ -689,7 +697,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -689,7 +697,7 @@ describe("ProviderCommandReactor", () => { |
| 689 | model: "claude-sonnet-4-6", | 697 | model: "claude-sonnet-4-6", |
| 690 | }, | 698 | }, |
| 691 | }); | 699 | }); |
| 692 | const now = new Date().toISOString(); | 700 | const now = "2026-01-01T00:00:00.000Z"; |
| 693 | 701 | ||
| 694 | await Effect.runPromise( | 702 | await Effect.runPromise( |
| 695 | harness.engine.dispatch({ | 703 | harness.engine.dispatch({ |
| ... | @@ -739,7 +747,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -739,7 +747,7 @@ describe("ProviderCommandReactor", () => { |
| 739 | model: "claude-opus-4-6", | 747 | model: "claude-opus-4-6", |
| 740 | }, | 748 | }, |
| 741 | }); | 749 | }); |
| 742 | const now = new Date().toISOString(); | 750 | const now = "2026-01-01T00:00:00.000Z"; |
| 743 | 751 | ||
| 744 | await Effect.runPromise( | 752 | await Effect.runPromise( |
| 745 | harness.engine.dispatch({ | 753 | harness.engine.dispatch({ |
| ... | @@ -784,7 +792,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -784,7 +792,7 @@ describe("ProviderCommandReactor", () => { |
| 784 | 792 | ||
| 785 | it("forwards plan interaction mode to the provider turn request", async () => { | 793 | it("forwards plan interaction mode to the provider turn request", async () => { |
| 786 | const harness = await createHarness(); | 794 | const harness = await createHarness(); |
| 787 | const now = new Date().toISOString(); | 795 | const now = "2026-01-01T00:00:00.000Z"; |
| 788 | 796 | ||
| 789 | await Effect.runPromise( | 797 | await Effect.runPromise( |
| 790 | harness.engine.dispatch({ | 798 | harness.engine.dispatch({ |
| ... | @@ -822,7 +830,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -822,7 +830,7 @@ describe("ProviderCommandReactor", () => { |
| 822 | 830 | ||
| 823 | it("preserves the active session model when in-session model switching is unsupported", async () => { | 831 | it("preserves the active session model when in-session model switching is unsupported", async () => { |
| 824 | const harness = await createHarness({ sessionModelSwitch: "unsupported" }); | 832 | const harness = await createHarness({ sessionModelSwitch: "unsupported" }); |
| 825 | const now = new Date().toISOString(); | 833 | const now = "2026-01-01T00:00:00.000Z"; |
| 826 | 834 | ||
| 827 | await Effect.runPromise( | 835 | await Effect.runPromise( |
| 828 | harness.engine.dispatch({ | 836 | harness.engine.dispatch({ |
| ... | @@ -875,7 +883,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -875,7 +883,7 @@ describe("ProviderCommandReactor", () => { |
| 875 | const harness = await createHarness({ | 883 | const harness = await createHarness({ |
| 876 | threadModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex" }, | 884 | threadModelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5-codex" }, |
| 877 | }); | 885 | }); |
| 878 | const now = new Date().toISOString(); | 886 | const now = "2026-01-01T00:00:00.000Z"; |
| 879 | 887 | ||
| 880 | await Effect.runPromise( | 888 | await Effect.runPromise( |
| 881 | harness.engine.dispatch({ | 889 | harness.engine.dispatch({ |
| ... | @@ -921,7 +929,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -921,7 +929,7 @@ describe("ProviderCommandReactor", () => { |
| 921 | 929 | ||
| 922 | it("reuses the same provider session when runtime mode is unchanged", async () => { | 930 | it("reuses the same provider session when runtime mode is unchanged", async () => { |
| 923 | const harness = await createHarness(); | 931 | const harness = await createHarness(); |
| 924 | const now = new Date().toISOString(); | 932 | const now = "2026-01-01T00:00:00.000Z"; |
| 925 | 933 | ||
| 926 | await Effect.runPromise( | 934 | await Effect.runPromise( |
| 927 | harness.engine.dispatch({ | 935 | harness.engine.dispatch({ |
| ... | @@ -967,7 +975,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -967,7 +975,7 @@ describe("ProviderCommandReactor", () => { |
| 967 | 975 | ||
| 968 | it("restarts an existing Codex thread on a compatible requested instance", async () => { | 976 | it("restarts an existing Codex thread on a compatible requested instance", async () => { |
| 969 | const harness = await createHarness(); | 977 | const harness = await createHarness(); |
| 970 | const now = new Date().toISOString(); | 978 | const now = "2026-01-01T00:00:00.000Z"; |
| 971 | 979 | ||
| 972 | await Effect.runPromise( | 980 | await Effect.runPromise( |
| 973 | harness.engine.dispatch({ | 981 | harness.engine.dispatch({ |
| ... | @@ -1009,7 +1017,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1009,7 +1017,7 @@ describe("ProviderCommandReactor", () => { |
| 1009 | }, | 1017 | }, |
| 1010 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | 1018 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 1011 | runtimeMode: "approval-required", | 1019 | runtimeMode: "approval-required", |
| 1012 | createdAt: new Date().toISOString(), | 1020 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1013 | }), | 1021 | }), |
| 1014 | ); | 1022 | ); |
| 1015 | 1023 | ||
| ... | @@ -1034,7 +1042,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1034,7 +1042,7 @@ describe("ProviderCommandReactor", () => { |
| 1034 | model: "claude-sonnet-4-6", | 1042 | model: "claude-sonnet-4-6", |
| 1035 | }, | 1043 | }, |
| 1036 | }); | 1044 | }); |
| 1037 | const now = new Date().toISOString(); | 1045 | const now = "2026-01-01T00:00:00.000Z"; |
| 1038 | 1046 | ||
| 1039 | await Effect.runPromise( | 1047 | await Effect.runPromise( |
| 1040 | harness.engine.dispatch({ | 1048 | harness.engine.dispatch({ |
| ... | @@ -1107,7 +1115,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1107,7 +1115,7 @@ describe("ProviderCommandReactor", () => { |
| 1107 | model: "claude-sonnet-4-6", | 1115 | model: "claude-sonnet-4-6", |
| 1108 | }, | 1116 | }, |
| 1109 | }); | 1117 | }); |
| 1110 | const now = new Date().toISOString(); | 1118 | const now = "2026-01-01T00:00:00.000Z"; |
| 1111 | 1119 | ||
| 1112 | await Effect.runPromise( | 1120 | await Effect.runPromise( |
| 1113 | harness.engine.dispatch({ | 1121 | harness.engine.dispatch({ |
| ... | @@ -1170,7 +1178,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1170,7 +1178,7 @@ describe("ProviderCommandReactor", () => { |
| 1170 | 1178 | ||
| 1171 | it("restarts the provider session when runtime mode is updated on the thread", async () => { | 1179 | it("restarts the provider session when runtime mode is updated on the thread", async () => { |
| 1172 | const harness = await createHarness(); | 1180 | const harness = await createHarness(); |
| 1173 | const now = new Date().toISOString(); | 1181 | const now = "2026-01-01T00:00:00.000Z"; |
| 1174 | 1182 | ||
| 1175 | await Effect.runPromise( | 1183 | await Effect.runPromise( |
| 1176 | harness.engine.dispatch({ | 1184 | harness.engine.dispatch({ |
| ... | @@ -1260,7 +1268,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1260,7 +1268,7 @@ describe("ProviderCommandReactor", () => { |
| 1260 | model: "claude-opus-4-6", | 1268 | model: "claude-opus-4-6", |
| 1261 | }, | 1269 | }, |
| 1262 | }); | 1270 | }); |
| 1263 | const now = new Date().toISOString(); | 1271 | const now = "2026-01-01T00:00:00.000Z"; |
| 1264 | 1272 | ||
| 1265 | await Effect.runPromise( | 1273 | await Effect.runPromise( |
| 1266 | harness.engine.dispatch({ | 1274 | harness.engine.dispatch({ |
| ... | @@ -1303,7 +1311,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1303,7 +1311,7 @@ describe("ProviderCommandReactor", () => { |
| 1303 | 1311 | ||
| 1304 | it("does not stop the active session when restart fails before rebind", async () => { | 1312 | it("does not stop the active session when restart fails before rebind", async () => { |
| 1305 | const harness = await createHarness(); | 1313 | const harness = await createHarness(); |
| 1306 | const now = new Date().toISOString(); | 1314 | const now = "2026-01-01T00:00:00.000Z"; |
| 1307 | 1315 | ||
| 1308 | await Effect.runPromise( | 1316 | await Effect.runPromise( |
| 1309 | harness.engine.dispatch({ | 1317 | harness.engine.dispatch({ |
| ... | @@ -1336,7 +1344,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1336,7 +1344,7 @@ describe("ProviderCommandReactor", () => { |
| 1336 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); | 1344 | await waitFor(() => harness.sendTurn.mock.calls.length === 1); |
| 1337 | 1345 | ||
| 1338 | harness.startSession.mockImplementationOnce( | 1346 | harness.startSession.mockImplementationOnce( |
| 1339 | (_: unknown, __: unknown) => Effect.fail(new Error("simulated restart failure")) as never, | 1347 | (_: unknown, __: unknown) => Effect.fail("simulated restart failure") as never, |
| 1340 | ); | 1348 | ); |
| 1341 | 1349 | ||
| 1342 | await Effect.runPromise( | 1350 | await Effect.runPromise( |
| ... | @@ -1368,7 +1376,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1368,7 +1376,7 @@ describe("ProviderCommandReactor", () => { |
| 1368 | 1376 | ||
| 1369 | it("rejects provider changes after a thread is already bound to a session provider", async () => { | 1377 | it("rejects provider changes after a thread is already bound to a session provider", async () => { |
| 1370 | const harness = await createHarness(); | 1378 | const harness = await createHarness(); |
| 1371 | const now = new Date().toISOString(); | 1379 | const now = "2026-01-01T00:00:00.000Z"; |
| 1372 | 1380 | ||
| 1373 | await Effect.runPromise( | 1381 | await Effect.runPromise( |
| 1374 | harness.engine.dispatch({ | 1382 | harness.engine.dispatch({ |
| ... | @@ -1440,7 +1448,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1440,7 +1448,7 @@ describe("ProviderCommandReactor", () => { |
| 1440 | 1448 | ||
| 1441 | it("rejects cross-driver provider changes after the existing thread session has stopped", async () => { | 1449 | it("rejects cross-driver provider changes after the existing thread session has stopped", async () => { |
| 1442 | const harness = await createHarness(); | 1450 | const harness = await createHarness(); |
| 1443 | const now = new Date().toISOString(); | 1451 | const now = "2026-01-01T00:00:00.000Z"; |
| 1444 | 1452 | ||
| 1445 | await Effect.runPromise( | 1453 | await Effect.runPromise( |
| 1446 | harness.engine.dispatch({ | 1454 | harness.engine.dispatch({ |
| ... | @@ -1506,7 +1514,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1506,7 +1514,7 @@ describe("ProviderCommandReactor", () => { |
| 1506 | 1514 | ||
| 1507 | it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => { | 1515 | it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => { |
| 1508 | const harness = await createHarness(); | 1516 | const harness = await createHarness(); |
| 1509 | const now = new Date().toISOString(); | 1517 | const now = "2026-01-01T00:00:00.000Z"; |
| 1510 | 1518 | ||
| 1511 | await Effect.runPromise( | 1519 | await Effect.runPromise( |
| 1512 | harness.engine.dispatch({ | 1520 | harness.engine.dispatch({ |
| ... | @@ -1544,7 +1552,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1544,7 +1552,7 @@ describe("ProviderCommandReactor", () => { |
| 1544 | 1552 | ||
| 1545 | it("starts a fresh session when only projected session state exists", async () => { | 1553 | it("starts a fresh session when only projected session state exists", async () => { |
| 1546 | const harness = await createHarness(); | 1554 | const harness = await createHarness(); |
| 1547 | const now = new Date().toISOString(); | 1555 | const now = "2026-01-01T00:00:00.000Z"; |
| 1548 | 1556 | ||
| 1549 | await Effect.runPromise( | 1557 | await Effect.runPromise( |
| 1550 | harness.engine.dispatch({ | 1558 | harness.engine.dispatch({ |
| ... | @@ -1599,7 +1607,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1599,7 +1607,7 @@ describe("ProviderCommandReactor", () => { |
| 1599 | 1607 | ||
| 1600 | it("rejects active runtime sessions that are missing provider instance ids", async () => { | 1608 | it("rejects active runtime sessions that are missing provider instance ids", async () => { |
| 1601 | const harness = await createHarness(); | 1609 | const harness = await createHarness(); |
| 1602 | const now = new Date().toISOString(); | 1610 | const now = "2026-01-01T00:00:00.000Z"; |
| 1603 | 1611 | ||
| 1604 | await Effect.runPromise( | 1612 | await Effect.runPromise( |
| 1605 | harness.engine.dispatch({ | 1613 | harness.engine.dispatch({ |
| ... | @@ -1670,7 +1678,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1670,7 +1678,7 @@ describe("ProviderCommandReactor", () => { |
| 1670 | 1678 | ||
| 1671 | it("reacts to thread.approval.respond by forwarding provider approval response", async () => { | 1679 | it("reacts to thread.approval.respond by forwarding provider approval response", async () => { |
| 1672 | const harness = await createHarness(); | 1680 | const harness = await createHarness(); |
| 1673 | const now = new Date().toISOString(); | 1681 | const now = "2026-01-01T00:00:00.000Z"; |
| 1674 | 1682 | ||
| 1675 | await Effect.runPromise( | 1683 | await Effect.runPromise( |
| 1676 | harness.engine.dispatch({ | 1684 | harness.engine.dispatch({ |
| ... | @@ -1711,7 +1719,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1711,7 +1719,7 @@ describe("ProviderCommandReactor", () => { |
| 1711 | 1719 | ||
| 1712 | it("reacts to thread.user-input.respond by forwarding structured user input answers", async () => { | 1720 | it("reacts to thread.user-input.respond by forwarding structured user input answers", async () => { |
| 1713 | const harness = await createHarness(); | 1721 | const harness = await createHarness(); |
| 1714 | const now = new Date().toISOString(); | 1722 | const now = "2026-01-01T00:00:00.000Z"; |
| 1715 | 1723 | ||
| 1716 | await Effect.runPromise( | 1724 | await Effect.runPromise( |
| 1717 | harness.engine.dispatch({ | 1725 | harness.engine.dispatch({ |
| ... | @@ -1756,7 +1764,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1756,7 +1764,7 @@ describe("ProviderCommandReactor", () => { |
| 1756 | 1764 | ||
| 1757 | it("surfaces stale provider approval request failures without faking approval resolution", async () => { | 1765 | it("surfaces stale provider approval request failures without faking approval resolution", async () => { |
| 1758 | const harness = await createHarness(); | 1766 | const harness = await createHarness(); |
| 1759 | const now = new Date().toISOString(); | 1767 | const now = "2026-01-01T00:00:00.000Z"; |
| 1760 | harness.respondToRequest.mockImplementation(() => | 1768 | harness.respondToRequest.mockImplementation(() => |
| 1761 | Effect.fail( | 1769 | Effect.fail( |
| 1762 | new ProviderAdapterRequestError({ | 1770 | new ProviderAdapterRequestError({ |
| ... | @@ -1851,7 +1859,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1851,7 +1859,7 @@ describe("ProviderCommandReactor", () => { |
| 1851 | 1859 | ||
| 1852 | it("surfaces stale provider user-input failures without faking user-input resolution", async () => { | 1860 | it("surfaces stale provider user-input failures without faking user-input resolution", async () => { |
| 1853 | const harness = await createHarness(); | 1861 | const harness = await createHarness(); |
| 1854 | const now = new Date().toISOString(); | 1862 | const now = "2026-01-01T00:00:00.000Z"; |
| 1855 | harness.respondToUserInput.mockImplementation(() => | 1863 | harness.respondToUserInput.mockImplementation(() => |
| 1856 | Effect.fail( | 1864 | Effect.fail( |
| 1857 | new ProviderAdapterRequestError({ | 1865 | new ProviderAdapterRequestError({ |
| ... | @@ -1960,7 +1968,7 @@ describe("ProviderCommandReactor", () => { | ... | @@ -1960,7 +1968,7 @@ describe("ProviderCommandReactor", () => { |
| 1960 | 1968 | ||
| 1961 | it("reacts to thread.session.stop by stopping provider session and clearing thread session state", async () => { | 1969 | it("reacts to thread.session.stop by stopping provider session and clearing thread session state", async () => { |
| 1962 | const harness = await createHarness(); | 1970 | const harness = await createHarness(); |
| 1963 | const now = new Date().toISOString(); | 1971 | const now = "2026-01-01T00:00:00.000Z"; |
| 1964 | 1972 | ||
| 1965 | await Effect.runPromise( | 1973 | await Effect.runPromise( |
| 1966 | harness.engine.dispatch({ | 1974 | harness.engine.dispatch({ |
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+9-1| ... | @@ -13,7 +13,15 @@ import { | ... | @@ -13,7 +13,15 @@ import { |
| 13 | type TurnId, | 13 | type TurnId, |
| 14 | } from "@t3tools/contracts"; | 14 | } from "@t3tools/contracts"; |
| 15 | import { isTemporaryWorktreeBranch, WORKTREE_BRANCH_PREFIX } from "@t3tools/shared/git"; | 15 | import { isTemporaryWorktreeBranch, WORKTREE_BRANCH_PREFIX } from "@t3tools/shared/git"; |
| 16 | import { Cache, Cause, Duration, Effect, Equal, Layer, Option, Schema, Stream } from "effect"; | 16 | import * as Cache from "effect/Cache"; |
| 17 | import * as Cause from "effect/Cause"; | ||
| 18 | import * as Duration from "effect/Duration"; | ||
| 19 | import * as Effect from "effect/Effect"; | ||
| 20 | import * as Equal from "effect/Equal"; | ||
| 21 | import * as Layer from "effect/Layer"; | ||
| 22 | import * as Option from "effect/Option"; | ||
| 23 | import * as Schema from "effect/Schema"; | ||
| 24 | import * as Stream from "effect/Stream"; | ||
| 17 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; | 25 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; |
| 18 | 26 | ||
| 19 | import { resolveThreadWorkspaceCwd } from "../../checkpointing/Utils.ts"; | 27 | import { resolveThreadWorkspaceCwd } from "../../checkpointing/Utils.ts"; |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+69-61| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
| ... | @@ -21,7 +22,14 @@ import { | ... | @@ -21,7 +22,14 @@ import { |
| 21 | ThreadId, | 22 | ThreadId, |
| 22 | TurnId, | 23 | TurnId, |
| 23 | } from "@t3tools/contracts"; | 24 | } from "@t3tools/contracts"; |
| 24 | import { Effect, Exit, Layer, ManagedRuntime, PubSub, Scope, Stream } from "effect"; | 25 | import * as Clock from "effect/Clock"; |
| 26 | import * as Effect from "effect/Effect"; | ||
| 27 | import * as Exit from "effect/Exit"; | ||
| 28 | import * as Layer from "effect/Layer"; | ||
| 29 | import * as ManagedRuntime from "effect/ManagedRuntime"; | ||
| 30 | import * as PubSub from "effect/PubSub"; | ||
| 31 | import * as Scope from "effect/Scope"; | ||
| 32 | import * as Stream from "effect/Stream"; | ||
| 25 | import { afterEach, describe, expect, it } from "vitest"; | 33 | import { afterEach, describe, expect, it } from "vitest"; |
| 26 | 34 | ||
| 27 | import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts"; | 35 | import { OrchestrationEventStoreLive } from "../../persistence/Layers/OrchestrationEventStore.ts"; |
| ... | @@ -165,17 +173,17 @@ async function waitForThread( | ... | @@ -165,17 +173,17 @@ async function waitForThread( |
| 165 | timeoutMs = 2000, | 173 | timeoutMs = 2000, |
| 166 | threadId: ThreadId = asThreadId("thread-1"), | 174 | threadId: ThreadId = asThreadId("thread-1"), |
| 167 | ) { | 175 | ) { |
| 168 | const deadline = Date.now() + timeoutMs; | 176 | const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; |
| 169 | const poll = async (): Promise<ProviderRuntimeTestThread> => { | 177 | const poll = async (): Promise<ProviderRuntimeTestThread> => { |
| 170 | const snapshot = await readModel(); | 178 | const snapshot = await readModel(); |
| 171 | const thread = snapshot.threads.find((entry) => entry.id === threadId); | 179 | const thread = snapshot.threads.find((entry) => entry.id === threadId); |
| 172 | if (thread && predicate(thread)) { | 180 | if (thread && predicate(thread)) { |
| 173 | return thread; | 181 | return thread; |
| 174 | } | 182 | } |
| 175 | if (Date.now() >= deadline) { | 183 | if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { |
| 176 | throw new Error("Timed out waiting for thread state"); | 184 | throw new Error("Timed out waiting for thread state"); |
| 177 | } | 185 | } |
| 178 | await new Promise((resolve) => setTimeout(resolve, 10)); | 186 | await Effect.runPromise(Effect.yieldNow); |
| 179 | return poll(); | 187 | return poll(); |
| 180 | }; | 188 | }; |
| 181 | return poll(); | 189 | return poll(); |
| ... | @@ -242,7 +250,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -242,7 +250,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 242 | await Effect.runPromise(ingestion.start().pipe(Scope.provide(scope))); | 250 | await Effect.runPromise(ingestion.start().pipe(Scope.provide(scope))); |
| 243 | const drain = () => Effect.runPromise(ingestion.drain); | 251 | const drain = () => Effect.runPromise(ingestion.drain); |
| 244 | 252 | ||
| 245 | const createdAt = new Date().toISOString(); | 253 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 246 | await Effect.runPromise( | 254 | await Effect.runPromise( |
| 247 | engine.dispatch({ | 255 | engine.dispatch({ |
| 248 | type: "project.create", | 256 | type: "project.create", |
| ... | @@ -312,7 +320,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -312,7 +320,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 312 | 320 | ||
| 313 | it("maps turn started/completed events into thread session updates", async () => { | 321 | it("maps turn started/completed events into thread session updates", async () => { |
| 314 | const harness = await createHarness(); | 322 | const harness = await createHarness(); |
| 315 | const now = new Date().toISOString(); | 323 | const now = "2026-01-01T00:00:00.000Z"; |
| 316 | 324 | ||
| 317 | harness.emit({ | 325 | harness.emit({ |
| 318 | type: "turn.started", | 326 | type: "turn.started", |
| ... | @@ -333,7 +341,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -333,7 +341,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 333 | eventId: asEventId("evt-turn-completed"), | 341 | eventId: asEventId("evt-turn-completed"), |
| 334 | provider: ProviderDriverKind.make("codex"), | 342 | provider: ProviderDriverKind.make("codex"), |
| 335 | threadId: asThreadId("thread-1"), | 343 | threadId: asThreadId("thread-1"), |
| 336 | createdAt: new Date().toISOString(), | 344 | createdAt: "2026-01-01T00:00:00.000Z", |
| 337 | turnId: asTurnId("turn-1"), | 345 | turnId: asTurnId("turn-1"), |
| 338 | payload: { | 346 | payload: { |
| 339 | state: "failed", | 347 | state: "failed", |
| ... | @@ -354,7 +362,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -354,7 +362,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 354 | 362 | ||
| 355 | it("applies provider session.state.changed transitions directly", async () => { | 363 | it("applies provider session.state.changed transitions directly", async () => { |
| 356 | const harness = await createHarness(); | 364 | const harness = await createHarness(); |
| 357 | const waitingAt = new Date().toISOString(); | 365 | const waitingAt = "2026-01-01T00:00:00.000Z"; |
| 358 | 366 | ||
| 359 | harness.emit({ | 367 | harness.emit({ |
| 360 | type: "session.state.changed", | 368 | type: "session.state.changed", |
| ... | @@ -380,7 +388,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -380,7 +388,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 380 | eventId: asEventId("evt-session-state-error"), | 388 | eventId: asEventId("evt-session-state-error"), |
| 381 | provider: ProviderDriverKind.make("codex"), | 389 | provider: ProviderDriverKind.make("codex"), |
| 382 | threadId: asThreadId("thread-1"), | 390 | threadId: asThreadId("thread-1"), |
| 383 | createdAt: new Date().toISOString(), | 391 | createdAt: "2026-01-01T00:00:00.000Z", |
| 384 | payload: { | 392 | payload: { |
| 385 | state: "error", | 393 | state: "error", |
| 386 | reason: "provider crashed", | 394 | reason: "provider crashed", |
| ... | @@ -402,7 +410,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -402,7 +410,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 402 | eventId: asEventId("evt-session-state-stopped"), | 410 | eventId: asEventId("evt-session-state-stopped"), |
| 403 | provider: ProviderDriverKind.make("codex"), | 411 | provider: ProviderDriverKind.make("codex"), |
| 404 | threadId: asThreadId("thread-1"), | 412 | threadId: asThreadId("thread-1"), |
| 405 | createdAt: new Date().toISOString(), | 413 | createdAt: "2026-01-01T00:00:00.000Z", |
| 406 | payload: { | 414 | payload: { |
| 407 | state: "stopped", | 415 | state: "stopped", |
| 408 | }, | 416 | }, |
| ... | @@ -423,7 +431,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -423,7 +431,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 423 | eventId: asEventId("evt-session-state-ready"), | 431 | eventId: asEventId("evt-session-state-ready"), |
| 424 | provider: ProviderDriverKind.make("codex"), | 432 | provider: ProviderDriverKind.make("codex"), |
| 425 | threadId: asThreadId("thread-1"), | 433 | threadId: asThreadId("thread-1"), |
| 426 | createdAt: new Date().toISOString(), | 434 | createdAt: "2026-01-01T00:00:00.000Z", |
| 427 | payload: { | 435 | payload: { |
| 428 | state: "ready", | 436 | state: "ready", |
| 429 | }, | 437 | }, |
| ... | @@ -442,7 +450,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -442,7 +450,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 442 | 450 | ||
| 443 | it("does not clear active turn when session/thread started arrives mid-turn", async () => { | 451 | it("does not clear active turn when session/thread started arrives mid-turn", async () => { |
| 444 | const harness = await createHarness(); | 452 | const harness = await createHarness(); |
| 445 | const now = new Date().toISOString(); | 453 | const now = "2026-01-01T00:00:00.000Z"; |
| 446 | 454 | ||
| 447 | harness.emit({ | 455 | harness.emit({ |
| 448 | type: "turn.started", | 456 | type: "turn.started", |
| ... | @@ -464,14 +472,14 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -464,14 +472,14 @@ describe("ProviderRuntimeIngestion", () => { |
| 464 | type: "thread.started", | 472 | type: "thread.started", |
| 465 | eventId: asEventId("evt-thread-started-midturn-lifecycle"), | 473 | eventId: asEventId("evt-thread-started-midturn-lifecycle"), |
| 466 | provider: ProviderDriverKind.make("codex"), | 474 | provider: ProviderDriverKind.make("codex"), |
| 467 | createdAt: new Date().toISOString(), | 475 | createdAt: "2026-01-01T00:00:00.000Z", |
| 468 | threadId: asThreadId("thread-1"), | 476 | threadId: asThreadId("thread-1"), |
| 469 | }); | 477 | }); |
| 470 | harness.emit({ | 478 | harness.emit({ |
| 471 | type: "session.started", | 479 | type: "session.started", |
| 472 | eventId: asEventId("evt-session-started-midturn-lifecycle"), | 480 | eventId: asEventId("evt-session-started-midturn-lifecycle"), |
| 473 | provider: ProviderDriverKind.make("codex"), | 481 | provider: ProviderDriverKind.make("codex"), |
| 474 | createdAt: new Date().toISOString(), | 482 | createdAt: "2026-01-01T00:00:00.000Z", |
| 475 | threadId: asThreadId("thread-1"), | 483 | threadId: asThreadId("thread-1"), |
| 476 | }); | 484 | }); |
| 477 | 485 | ||
| ... | @@ -485,7 +493,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -485,7 +493,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 485 | type: "turn.completed", | 493 | type: "turn.completed", |
| 486 | eventId: asEventId("evt-turn-completed-midturn-lifecycle"), | 494 | eventId: asEventId("evt-turn-completed-midturn-lifecycle"), |
| 487 | provider: ProviderDriverKind.make("codex"), | 495 | provider: ProviderDriverKind.make("codex"), |
| 488 | createdAt: new Date().toISOString(), | 496 | createdAt: "2026-01-01T00:00:00.000Z", |
| 489 | threadId: asThreadId("thread-1"), | 497 | threadId: asThreadId("thread-1"), |
| 490 | turnId: asTurnId("turn-midturn-lifecycle"), | 498 | turnId: asTurnId("turn-midturn-lifecycle"), |
| 491 | status: "completed", | 499 | status: "completed", |
| ... | @@ -499,7 +507,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -499,7 +507,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 499 | 507 | ||
| 500 | it("accepts claude turn lifecycle when seeded thread id is a synthetic placeholder", async () => { | 508 | it("accepts claude turn lifecycle when seeded thread id is a synthetic placeholder", async () => { |
| 501 | const harness = await createHarness(); | 509 | const harness = await createHarness(); |
| 502 | const seededAt = new Date().toISOString(); | 510 | const seededAt = "2026-01-01T00:00:00.000Z"; |
| 503 | 511 | ||
| 504 | await Effect.runPromise( | 512 | await Effect.runPromise( |
| 505 | harness.engine.dispatch({ | 513 | harness.engine.dispatch({ |
| ... | @@ -523,7 +531,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -523,7 +531,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 523 | type: "turn.started", | 531 | type: "turn.started", |
| 524 | eventId: asEventId("evt-turn-started-claude-placeholder"), | 532 | eventId: asEventId("evt-turn-started-claude-placeholder"), |
| 525 | provider: ProviderDriverKind.make("claudeAgent"), | 533 | provider: ProviderDriverKind.make("claudeAgent"), |
| 526 | createdAt: new Date().toISOString(), | 534 | createdAt: "2026-01-01T00:00:00.000Z", |
| 527 | threadId: asThreadId("thread-1"), | 535 | threadId: asThreadId("thread-1"), |
| 528 | turnId: asTurnId("turn-claude-placeholder"), | 536 | turnId: asTurnId("turn-claude-placeholder"), |
| 529 | }); | 537 | }); |
| ... | @@ -539,7 +547,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -539,7 +547,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 539 | type: "turn.completed", | 547 | type: "turn.completed", |
| 540 | eventId: asEventId("evt-turn-completed-claude-placeholder"), | 548 | eventId: asEventId("evt-turn-completed-claude-placeholder"), |
| 541 | provider: ProviderDriverKind.make("claudeAgent"), | 549 | provider: ProviderDriverKind.make("claudeAgent"), |
| 542 | createdAt: new Date().toISOString(), | 550 | createdAt: "2026-01-01T00:00:00.000Z", |
| 543 | threadId: asThreadId("thread-1"), | 551 | threadId: asThreadId("thread-1"), |
| 544 | turnId: asTurnId("turn-claude-placeholder"), | 552 | turnId: asTurnId("turn-claude-placeholder"), |
| 545 | status: "completed", | 553 | status: "completed", |
| ... | @@ -553,7 +561,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -553,7 +561,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 553 | 561 | ||
| 554 | it("ignores auxiliary turn completions from a different provider thread", async () => { | 562 | it("ignores auxiliary turn completions from a different provider thread", async () => { |
| 555 | const harness = await createHarness(); | 563 | const harness = await createHarness(); |
| 556 | const now = new Date().toISOString(); | 564 | const now = "2026-01-01T00:00:00.000Z"; |
| 557 | 565 | ||
| 558 | harness.emit({ | 566 | harness.emit({ |
| 559 | type: "turn.started", | 567 | type: "turn.started", |
| ... | @@ -574,7 +582,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -574,7 +582,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 574 | type: "turn.completed", | 582 | type: "turn.completed", |
| 575 | eventId: asEventId("evt-turn-completed-aux"), | 583 | eventId: asEventId("evt-turn-completed-aux"), |
| 576 | provider: ProviderDriverKind.make("codex"), | 584 | provider: ProviderDriverKind.make("codex"), |
| 577 | createdAt: new Date().toISOString(), | 585 | createdAt: "2026-01-01T00:00:00.000Z", |
| 578 | threadId: asThreadId("thread-1"), | 586 | threadId: asThreadId("thread-1"), |
| 579 | turnId: asTurnId("turn-aux"), | 587 | turnId: asTurnId("turn-aux"), |
| 580 | status: "completed", | 588 | status: "completed", |
| ... | @@ -590,7 +598,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -590,7 +598,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 590 | type: "turn.completed", | 598 | type: "turn.completed", |
| 591 | eventId: asEventId("evt-turn-completed-primary"), | 599 | eventId: asEventId("evt-turn-completed-primary"), |
| 592 | provider: ProviderDriverKind.make("codex"), | 600 | provider: ProviderDriverKind.make("codex"), |
| 593 | createdAt: new Date().toISOString(), | 601 | createdAt: "2026-01-01T00:00:00.000Z", |
| 594 | threadId: asThreadId("thread-1"), | 602 | threadId: asThreadId("thread-1"), |
| 595 | turnId: asTurnId("turn-primary"), | 603 | turnId: asTurnId("turn-primary"), |
| 596 | status: "completed", | 604 | status: "completed", |
| ... | @@ -604,7 +612,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -604,7 +612,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 604 | 612 | ||
| 605 | it("ignores non-active turn completion when runtime omits thread id", async () => { | 613 | it("ignores non-active turn completion when runtime omits thread id", async () => { |
| 606 | const harness = await createHarness(); | 614 | const harness = await createHarness(); |
| 607 | const now = new Date().toISOString(); | 615 | const now = "2026-01-01T00:00:00.000Z"; |
| 608 | 616 | ||
| 609 | harness.emit({ | 617 | harness.emit({ |
| 610 | type: "turn.started", | 618 | type: "turn.started", |
| ... | @@ -626,7 +634,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -626,7 +634,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 626 | type: "turn.completed", | 634 | type: "turn.completed", |
| 627 | eventId: asEventId("evt-turn-completed-guarded-other"), | 635 | eventId: asEventId("evt-turn-completed-guarded-other"), |
| 628 | provider: ProviderDriverKind.make("codex"), | 636 | provider: ProviderDriverKind.make("codex"), |
| 629 | createdAt: new Date().toISOString(), | 637 | createdAt: "2026-01-01T00:00:00.000Z", |
| 630 | threadId: asThreadId("thread-1"), | 638 | threadId: asThreadId("thread-1"), |
| 631 | turnId: asTurnId("turn-guarded-other"), | 639 | turnId: asTurnId("turn-guarded-other"), |
| 632 | status: "completed", | 640 | status: "completed", |
| ... | @@ -642,7 +650,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -642,7 +650,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 642 | type: "turn.completed", | 650 | type: "turn.completed", |
| 643 | eventId: asEventId("evt-turn-completed-guarded-main"), | 651 | eventId: asEventId("evt-turn-completed-guarded-main"), |
| 644 | provider: ProviderDriverKind.make("codex"), | 652 | provider: ProviderDriverKind.make("codex"), |
| 645 | createdAt: new Date().toISOString(), | 653 | createdAt: "2026-01-01T00:00:00.000Z", |
| 646 | threadId: asThreadId("thread-1"), | 654 | threadId: asThreadId("thread-1"), |
| 647 | turnId: asTurnId("turn-guarded-main"), | 655 | turnId: asTurnId("turn-guarded-main"), |
| 648 | status: "completed", | 656 | status: "completed", |
| ... | @@ -656,7 +664,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -656,7 +664,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 656 | 664 | ||
| 657 | it("maps canonical content delta/item completed into finalized assistant messages", async () => { | 665 | it("maps canonical content delta/item completed into finalized assistant messages", async () => { |
| 658 | const harness = await createHarness(); | 666 | const harness = await createHarness(); |
| 659 | const now = new Date().toISOString(); | 667 | const now = "2026-01-01T00:00:00.000Z"; |
| 660 | 668 | ||
| 661 | harness.emit({ | 669 | harness.emit({ |
| 662 | type: "content.delta", | 670 | type: "content.delta", |
| ... | @@ -713,7 +721,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -713,7 +721,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 713 | 721 | ||
| 714 | it("uses assistant item completion detail when no assistant deltas were streamed", async () => { | 722 | it("uses assistant item completion detail when no assistant deltas were streamed", async () => { |
| 715 | const harness = await createHarness(); | 723 | const harness = await createHarness(); |
| 716 | const now = new Date().toISOString(); | 724 | const now = "2026-01-01T00:00:00.000Z"; |
| 717 | 725 | ||
| 718 | harness.emit({ | 726 | harness.emit({ |
| 719 | type: "item.completed", | 727 | type: "item.completed", |
| ... | @@ -745,7 +753,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -745,7 +753,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 745 | 753 | ||
| 746 | it("preserves completed tool metadata on projected tool activities", async () => { | 754 | it("preserves completed tool metadata on projected tool activities", async () => { |
| 747 | const harness = await createHarness(); | 755 | const harness = await createHarness(); |
| 748 | const now = new Date().toISOString(); | 756 | const now = "2026-01-01T00:00:00.000Z"; |
| 749 | 757 | ||
| 750 | harness.emit({ | 758 | harness.emit({ |
| 751 | type: "item.completed", | 759 | type: "item.completed", |
| ... | @@ -801,7 +809,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -801,7 +809,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 801 | 809 | ||
| 802 | it("normalizes command execution activities to ran-command summaries", async () => { | 810 | it("normalizes command execution activities to ran-command summaries", async () => { |
| 803 | const harness = await createHarness(); | 811 | const harness = await createHarness(); |
| 804 | const now = new Date().toISOString(); | 812 | const now = "2026-01-01T00:00:00.000Z"; |
| 805 | 813 | ||
| 806 | harness.emit({ | 814 | harness.emit({ |
| 807 | type: "item.completed", | 815 | type: "item.completed", |
| ... | @@ -843,7 +851,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -843,7 +851,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 843 | 851 | ||
| 844 | it("uses structured read-file paths when available", async () => { | 852 | it("uses structured read-file paths when available", async () => { |
| 845 | const harness = await createHarness(); | 853 | const harness = await createHarness(); |
| 846 | const now = new Date().toISOString(); | 854 | const now = "2026-01-01T00:00:00.000Z"; |
| 847 | 855 | ||
| 848 | harness.emit({ | 856 | harness.emit({ |
| 849 | type: "item.completed", | 857 | type: "item.completed", |
| ... | @@ -885,7 +893,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -885,7 +893,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 885 | 893 | ||
| 886 | it("projects completed plan items into first-class proposed plans", async () => { | 894 | it("projects completed plan items into first-class proposed plans", async () => { |
| 887 | const harness = await createHarness(); | 895 | const harness = await createHarness(); |
| 888 | const now = new Date().toISOString(); | 896 | const now = "2026-01-01T00:00:00.000Z"; |
| 889 | 897 | ||
| 890 | harness.emit({ | 898 | harness.emit({ |
| 891 | type: "turn.proposed.completed", | 899 | type: "turn.proposed.completed", |
| ... | @@ -919,7 +927,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -919,7 +927,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 919 | const targetThreadId = asThreadId("thread-implement"); | 927 | const targetThreadId = asThreadId("thread-implement"); |
| 920 | const sourceTurnId = asTurnId("turn-plan-source"); | 928 | const sourceTurnId = asTurnId("turn-plan-source"); |
| 921 | const targetTurnId = asTurnId("turn-plan-implement"); | 929 | const targetTurnId = asTurnId("turn-plan-implement"); |
| 922 | const createdAt = new Date().toISOString(); | 930 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 923 | 931 | ||
| 924 | await Effect.runPromise( | 932 | await Effect.runPromise( |
| 925 | harness.engine.dispatch({ | 933 | harness.engine.dispatch({ |
| ... | @@ -1050,7 +1058,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1050,7 +1058,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1050 | }, | 1058 | }, |
| 1051 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | 1059 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 1052 | runtimeMode: "approval-required", | 1060 | runtimeMode: "approval-required", |
| 1053 | createdAt: new Date().toISOString(), | 1061 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1054 | }), | 1062 | }), |
| 1055 | ); | 1063 | ); |
| 1056 | 1064 | ||
| ... | @@ -1075,7 +1083,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1075,7 +1083,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1075 | type: "turn.started", | 1083 | type: "turn.started", |
| 1076 | eventId: asEventId("evt-plan-target-started"), | 1084 | eventId: asEventId("evt-plan-target-started"), |
| 1077 | provider: ProviderDriverKind.make("codex"), | 1085 | provider: ProviderDriverKind.make("codex"), |
| 1078 | createdAt: new Date().toISOString(), | 1086 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1079 | threadId: targetThreadId, | 1087 | threadId: targetThreadId, |
| 1080 | turnId: targetTurnId, | 1088 | turnId: targetTurnId, |
| 1081 | }); | 1089 | }); |
| ... | @@ -1106,7 +1114,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1106,7 +1114,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1106 | const sourceTurnId = asTurnId("turn-plan-source"); | 1114 | const sourceTurnId = asTurnId("turn-plan-source"); |
| 1107 | const activeTurnId = asTurnId("turn-already-running"); | 1115 | const activeTurnId = asTurnId("turn-already-running"); |
| 1108 | const staleTurnId = asTurnId("turn-stale-start"); | 1116 | const staleTurnId = asTurnId("turn-stale-start"); |
| 1109 | const createdAt = new Date().toISOString(); | 1117 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 1110 | 1118 | ||
| 1111 | await Effect.runPromise( | 1119 | await Effect.runPromise( |
| 1112 | harness.engine.dispatch({ | 1120 | harness.engine.dispatch({ |
| ... | @@ -1219,7 +1227,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1219,7 +1227,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1219 | }, | 1227 | }, |
| 1220 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | 1228 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 1221 | runtimeMode: "approval-required", | 1229 | runtimeMode: "approval-required", |
| 1222 | createdAt: new Date().toISOString(), | 1230 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1223 | }), | 1231 | }), |
| 1224 | ); | 1232 | ); |
| 1225 | 1233 | ||
| ... | @@ -1227,7 +1235,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1227,7 +1235,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1227 | type: "turn.started", | 1235 | type: "turn.started", |
| 1228 | eventId: asEventId("evt-turn-started-stale-plan-implementation"), | 1236 | eventId: asEventId("evt-turn-started-stale-plan-implementation"), |
| 1229 | provider: ProviderDriverKind.make("codex"), | 1237 | provider: ProviderDriverKind.make("codex"), |
| 1230 | createdAt: new Date().toISOString(), | 1238 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1231 | threadId: targetThreadId, | 1239 | threadId: targetThreadId, |
| 1232 | turnId: staleTurnId, | 1240 | turnId: staleTurnId, |
| 1233 | }); | 1241 | }); |
| ... | @@ -1259,7 +1267,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1259,7 +1267,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1259 | const sourceTurnId = asTurnId("turn-plan-source"); | 1267 | const sourceTurnId = asTurnId("turn-plan-source"); |
| 1260 | const expectedTurnId = asTurnId("turn-plan-implement"); | 1268 | const expectedTurnId = asTurnId("turn-plan-implement"); |
| 1261 | const replayedTurnId = asTurnId("turn-replayed"); | 1269 | const replayedTurnId = asTurnId("turn-replayed"); |
| 1262 | const createdAt = new Date().toISOString(); | 1270 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 1263 | 1271 | ||
| 1264 | await Effect.runPromise( | 1272 | await Effect.runPromise( |
| 1265 | harness.engine.dispatch({ | 1273 | harness.engine.dispatch({ |
| ... | @@ -1381,7 +1389,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1381,7 +1389,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1381 | }, | 1389 | }, |
| 1382 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | 1390 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 1383 | runtimeMode: "approval-required", | 1391 | runtimeMode: "approval-required", |
| 1384 | createdAt: new Date().toISOString(), | 1392 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1385 | }), | 1393 | }), |
| 1386 | ); | 1394 | ); |
| 1387 | 1395 | ||
| ... | @@ -1399,7 +1407,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1399,7 +1407,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1399 | type: "turn.started", | 1407 | type: "turn.started", |
| 1400 | eventId: asEventId("evt-turn-started-unrelated-plan-implementation"), | 1408 | eventId: asEventId("evt-turn-started-unrelated-plan-implementation"), |
| 1401 | provider: ProviderDriverKind.make("codex"), | 1409 | provider: ProviderDriverKind.make("codex"), |
| 1402 | createdAt: new Date().toISOString(), | 1410 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1403 | threadId: targetThreadId, | 1411 | threadId: targetThreadId, |
| 1404 | turnId: replayedTurnId, | 1412 | turnId: replayedTurnId, |
| 1405 | }); | 1413 | }); |
| ... | @@ -1420,7 +1428,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1420,7 +1428,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1420 | 1428 | ||
| 1421 | it("finalizes buffered proposed-plan deltas into a first-class proposed plan on turn completion", async () => { | 1429 | it("finalizes buffered proposed-plan deltas into a first-class proposed plan on turn completion", async () => { |
| 1422 | const harness = await createHarness(); | 1430 | const harness = await createHarness(); |
| 1423 | const now = new Date().toISOString(); | 1431 | const now = "2026-01-01T00:00:00.000Z"; |
| 1424 | 1432 | ||
| 1425 | harness.emit({ | 1433 | harness.emit({ |
| 1426 | type: "turn.started", | 1434 | type: "turn.started", |
| ... | @@ -1486,7 +1494,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1486,7 +1494,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1486 | 1494 | ||
| 1487 | it("buffers assistant deltas by default until completion", async () => { | 1495 | it("buffers assistant deltas by default until completion", async () => { |
| 1488 | const harness = await createHarness(); | 1496 | const harness = await createHarness(); |
| 1489 | const now = new Date().toISOString(); | 1497 | const now = "2026-01-01T00:00:00.000Z"; |
| 1490 | 1498 | ||
| 1491 | harness.emit({ | 1499 | harness.emit({ |
| 1492 | type: "turn.started", | 1500 | type: "turn.started", |
| ... | @@ -1554,7 +1562,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1554,7 +1562,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1554 | 1562 | ||
| 1555 | it("flushes and completes buffered assistant text when an approval request opens", async () => { | 1563 | it("flushes and completes buffered assistant text when an approval request opens", async () => { |
| 1556 | const harness = await createHarness(); | 1564 | const harness = await createHarness(); |
| 1557 | const now = new Date().toISOString(); | 1565 | const now = "2026-01-01T00:00:00.000Z"; |
| 1558 | 1566 | ||
| 1559 | harness.emit({ | 1567 | harness.emit({ |
| 1560 | type: "turn.started", | 1568 | type: "turn.started", |
| ... | @@ -1614,7 +1622,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1614,7 +1622,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1614 | 1622 | ||
| 1615 | it("flushes and completes buffered assistant text when user input is requested", async () => { | 1623 | it("flushes and completes buffered assistant text when user input is requested", async () => { |
| 1616 | const harness = await createHarness(); | 1624 | const harness = await createHarness(); |
| 1617 | const now = new Date().toISOString(); | 1625 | const now = "2026-01-01T00:00:00.000Z"; |
| 1618 | 1626 | ||
| 1619 | harness.emit({ | 1627 | harness.emit({ |
| 1620 | type: "turn.started", | 1628 | type: "turn.started", |
| ... | @@ -1980,7 +1988,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -1980,7 +1988,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 1980 | 1988 | ||
| 1981 | it("streams assistant deltas when thread.turn.start requests streaming mode", async () => { | 1989 | it("streams assistant deltas when thread.turn.start requests streaming mode", async () => { |
| 1982 | const harness = await createHarness({ serverSettings: { enableAssistantStreaming: true } }); | 1990 | const harness = await createHarness({ serverSettings: { enableAssistantStreaming: true } }); |
| 1983 | const now = new Date().toISOString(); | 1991 | const now = "2026-01-01T00:00:00.000Z"; |
| 1984 | 1992 | ||
| 1985 | await Effect.runPromise( | 1993 | await Effect.runPromise( |
| 1986 | harness.engine.dispatch({ | 1994 | harness.engine.dispatch({ |
| ... | @@ -2072,7 +2080,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2072,7 +2080,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2072 | 2080 | ||
| 2073 | it("spills oversized buffered deltas and still finalizes full assistant text", async () => { | 2081 | it("spills oversized buffered deltas and still finalizes full assistant text", async () => { |
| 2074 | const harness = await createHarness(); | 2082 | const harness = await createHarness(); |
| 2075 | const now = new Date().toISOString(); | 2083 | const now = "2026-01-01T00:00:00.000Z"; |
| 2076 | const oversizedText = "x".repeat(40_000); | 2084 | const oversizedText = "x".repeat(40_000); |
| 2077 | 2085 | ||
| 2078 | harness.emit({ | 2086 | harness.emit({ |
| ... | @@ -2133,7 +2141,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2133,7 +2141,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2133 | 2141 | ||
| 2134 | it("does not duplicate assistant completion when item.completed is followed by turn.completed", async () => { | 2142 | it("does not duplicate assistant completion when item.completed is followed by turn.completed", async () => { |
| 2135 | const harness = await createHarness(); | 2143 | const harness = await createHarness(); |
| 2136 | const now = new Date().toISOString(); | 2144 | const now = "2026-01-01T00:00:00.000Z"; |
| 2137 | 2145 | ||
| 2138 | harness.emit({ | 2146 | harness.emit({ |
| 2139 | type: "turn.started", | 2147 | type: "turn.started", |
| ... | @@ -2219,7 +2227,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2219,7 +2227,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2219 | 2227 | ||
| 2220 | it("maps canonical request events into approval activities with requestKind", async () => { | 2228 | it("maps canonical request events into approval activities with requestKind", async () => { |
| 2221 | const harness = await createHarness(); | 2229 | const harness = await createHarness(); |
| 2222 | const now = new Date().toISOString(); | 2230 | const now = "2026-01-01T00:00:00.000Z"; |
| 2223 | 2231 | ||
| 2224 | harness.emit({ | 2232 | harness.emit({ |
| 2225 | type: "request.opened", | 2233 | type: "request.opened", |
| ... | @@ -2285,7 +2293,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2285,7 +2293,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2285 | 2293 | ||
| 2286 | it("maps runtime.error into errored session state", async () => { | 2294 | it("maps runtime.error into errored session state", async () => { |
| 2287 | const harness = await createHarness(); | 2295 | const harness = await createHarness(); |
| 2288 | const now = new Date().toISOString(); | 2296 | const now = "2026-01-01T00:00:00.000Z"; |
| 2289 | 2297 | ||
| 2290 | harness.emit({ | 2298 | harness.emit({ |
| 2291 | type: "runtime.error", | 2299 | type: "runtime.error", |
| ... | @@ -2312,7 +2320,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2312,7 +2320,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2312 | 2320 | ||
| 2313 | it("records runtime.error activities from the typed payload message", async () => { | 2321 | it("records runtime.error activities from the typed payload message", async () => { |
| 2314 | const harness = await createHarness(); | 2322 | const harness = await createHarness(); |
| 2315 | const now = new Date().toISOString(); | 2323 | const now = "2026-01-01T00:00:00.000Z"; |
| 2316 | 2324 | ||
| 2317 | harness.emit({ | 2325 | harness.emit({ |
| 2318 | type: "runtime.error", | 2326 | type: "runtime.error", |
| ... | @@ -2343,7 +2351,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2343,7 +2351,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2343 | 2351 | ||
| 2344 | it("keeps the session running when a runtime.warning arrives during an active turn", async () => { | 2352 | it("keeps the session running when a runtime.warning arrives during an active turn", async () => { |
| 2345 | const harness = await createHarness(); | 2353 | const harness = await createHarness(); |
| 2346 | const now = new Date().toISOString(); | 2354 | const now = "2026-01-01T00:00:00.000Z"; |
| 2347 | 2355 | ||
| 2348 | harness.emit({ | 2356 | harness.emit({ |
| 2349 | type: "turn.started", | 2357 | type: "turn.started", |
| ... | @@ -2387,7 +2395,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2387,7 +2395,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2387 | 2395 | ||
| 2388 | it("maps session/thread lifecycle and item.started into session/activity projections", async () => { | 2396 | it("maps session/thread lifecycle and item.started into session/activity projections", async () => { |
| 2389 | const harness = await createHarness(); | 2397 | const harness = await createHarness(); |
| 2390 | const now = new Date().toISOString(); | 2398 | const now = "2026-01-01T00:00:00.000Z"; |
| 2391 | 2399 | ||
| 2392 | harness.emit({ | 2400 | harness.emit({ |
| 2393 | type: "session.started", | 2401 | type: "session.started", |
| ... | @@ -2439,7 +2447,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2439,7 +2447,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2439 | 2447 | ||
| 2440 | it("consumes P1 runtime events into thread metadata, diff checkpoints, and activities", async () => { | 2448 | it("consumes P1 runtime events into thread metadata, diff checkpoints, and activities", async () => { |
| 2441 | const harness = await createHarness(); | 2449 | const harness = await createHarness(); |
| 2442 | const now = new Date().toISOString(); | 2450 | const now = "2026-01-01T00:00:00.000Z"; |
| 2443 | 2451 | ||
| 2444 | harness.emit({ | 2452 | harness.emit({ |
| 2445 | type: "thread.metadata.updated", | 2453 | type: "thread.metadata.updated", |
| ... | @@ -2573,7 +2581,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2573,7 +2581,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2573 | 2581 | ||
| 2574 | it("projects context window updates into normalized thread activities", async () => { | 2582 | it("projects context window updates into normalized thread activities", async () => { |
| 2575 | const harness = await createHarness(); | 2583 | const harness = await createHarness(); |
| 2576 | const now = new Date().toISOString(); | 2584 | const now = "2026-01-01T00:00:00.000Z"; |
| 2577 | 2585 | ||
| 2578 | harness.emit({ | 2586 | harness.emit({ |
| 2579 | type: "thread.token-usage.updated", | 2587 | type: "thread.token-usage.updated", |
| ... | @@ -2625,7 +2633,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2625,7 +2633,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2625 | 2633 | ||
| 2626 | it("projects Codex camelCase token usage payloads into normalized thread activities", async () => { | 2634 | it("projects Codex camelCase token usage payloads into normalized thread activities", async () => { |
| 2627 | const harness = await createHarness(); | 2635 | const harness = await createHarness(); |
| 2628 | const now = new Date().toISOString(); | 2636 | const now = "2026-01-01T00:00:00.000Z"; |
| 2629 | 2637 | ||
| 2630 | harness.emit({ | 2638 | harness.emit({ |
| 2631 | type: "thread.token-usage.updated", | 2639 | type: "thread.token-usage.updated", |
| ... | @@ -2678,7 +2686,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2678,7 +2686,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2678 | 2686 | ||
| 2679 | it("projects Claude usage snapshots with context window into normalized thread activities", async () => { | 2687 | it("projects Claude usage snapshots with context window into normalized thread activities", async () => { |
| 2680 | const harness = await createHarness(); | 2688 | const harness = await createHarness(); |
| 2681 | const now = new Date().toISOString(); | 2689 | const now = "2026-01-01T00:00:00.000Z"; |
| 2682 | 2690 | ||
| 2683 | harness.emit({ | 2691 | harness.emit({ |
| 2684 | type: "thread.token-usage.updated", | 2692 | type: "thread.token-usage.updated", |
| ... | @@ -2722,7 +2730,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2722,7 +2730,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2722 | 2730 | ||
| 2723 | it("projects compacted thread state into context compaction activities", async () => { | 2731 | it("projects compacted thread state into context compaction activities", async () => { |
| 2724 | const harness = await createHarness(); | 2732 | const harness = await createHarness(); |
| 2725 | const now = new Date().toISOString(); | 2733 | const now = "2026-01-01T00:00:00.000Z"; |
| 2726 | 2734 | ||
| 2727 | harness.emit({ | 2735 | harness.emit({ |
| 2728 | type: "thread.state.changed", | 2736 | type: "thread.state.changed", |
| ... | @@ -2752,7 +2760,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2752,7 +2760,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2752 | 2760 | ||
| 2753 | it("projects Codex task lifecycle chunks into thread activities", async () => { | 2761 | it("projects Codex task lifecycle chunks into thread activities", async () => { |
| 2754 | const harness = await createHarness(); | 2762 | const harness = await createHarness(); |
| 2755 | const now = new Date().toISOString(); | 2763 | const now = "2026-01-01T00:00:00.000Z"; |
| 2756 | 2764 | ||
| 2757 | harness.emit({ | 2765 | harness.emit({ |
| 2758 | type: "task.started", | 2766 | type: "task.started", |
| ... | @@ -2855,7 +2863,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2855,7 +2863,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2855 | 2863 | ||
| 2856 | it("projects structured user input request and resolution as thread activities", async () => { | 2864 | it("projects structured user input request and resolution as thread activities", async () => { |
| 2857 | const harness = await createHarness(); | 2865 | const harness = await createHarness(); |
| 2858 | const now = new Date().toISOString(); | 2866 | const now = "2026-01-01T00:00:00.000Z"; |
| 2859 | 2867 | ||
| 2860 | harness.emit({ | 2868 | harness.emit({ |
| 2861 | type: "user-input.requested", | 2869 | type: "user-input.requested", |
| ... | @@ -2886,7 +2894,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2886,7 +2894,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2886 | type: "user-input.resolved", | 2894 | type: "user-input.resolved", |
| 2887 | eventId: asEventId("evt-user-input-resolved"), | 2895 | eventId: asEventId("evt-user-input-resolved"), |
| 2888 | provider: ProviderDriverKind.make("codex"), | 2896 | provider: ProviderDriverKind.make("codex"), |
| 2889 | createdAt: new Date().toISOString(), | 2897 | createdAt: "2026-01-01T00:00:00.000Z", |
| 2890 | threadId: asThreadId("thread-1"), | 2898 | threadId: asThreadId("thread-1"), |
| 2891 | turnId: asTurnId("turn-user-input"), | 2899 | turnId: asTurnId("turn-user-input"), |
| 2892 | requestId: ApprovalRequestId.make("req-user-input-1"), | 2900 | requestId: ApprovalRequestId.make("req-user-input-1"), |
| ... | @@ -2928,7 +2936,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2928,7 +2936,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2928 | 2936 | ||
| 2929 | it("continues processing runtime events after a single event handler failure", async () => { | 2937 | it("continues processing runtime events after a single event handler failure", async () => { |
| 2930 | const harness = await createHarness(); | 2938 | const harness = await createHarness(); |
| 2931 | const now = new Date().toISOString(); | 2939 | const now = "2026-01-01T00:00:00.000Z"; |
| 2932 | 2940 | ||
| 2933 | harness.emit({ | 2941 | harness.emit({ |
| 2934 | type: "content.delta", | 2942 | type: "content.delta", |
| ... | @@ -2948,7 +2956,7 @@ describe("ProviderRuntimeIngestion", () => { | ... | @@ -2948,7 +2956,7 @@ describe("ProviderRuntimeIngestion", () => { |
| 2948 | type: "runtime.error", | 2956 | type: "runtime.error", |
| 2949 | eventId: asEventId("evt-runtime-error-after-failure"), | 2957 | eventId: asEventId("evt-runtime-error-after-failure"), |
| 2950 | provider: ProviderDriverKind.make("codex"), | 2958 | provider: ProviderDriverKind.make("codex"), |
| 2951 | createdAt: new Date().toISOString(), | 2959 | createdAt: "2026-01-01T00:00:00.000Z", |
| 2952 | threadId: asThreadId("thread-1"), | 2960 | threadId: asThreadId("thread-1"), |
| 2953 | turnId: asTurnId("turn-after-failure"), | 2961 | turnId: asTurnId("turn-after-failure"), |
| 2954 | payload: { | 2962 | payload: { |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+7-1| ... | @@ -17,7 +17,13 @@ import { | ... | @@ -17,7 +17,13 @@ import { |
| 17 | type OrchestrationThreadActivity, | 17 | type OrchestrationThreadActivity, |
| 18 | type ProviderRuntimeEvent, | 18 | type ProviderRuntimeEvent, |
| 19 | } from "@t3tools/contracts"; | 19 | } from "@t3tools/contracts"; |
| 20 | import { Cache, Cause, Duration, Effect, Layer, Option, Stream } from "effect"; | 20 | import * as Cache from "effect/Cache"; |
| 21 | import * as Cause from "effect/Cause"; | ||
| 22 | import * as Duration from "effect/Duration"; | ||
| 23 | import * as Effect from "effect/Effect"; | ||
| 24 | import * as Layer from "effect/Layer"; | ||
| 25 | import * as Option from "effect/Option"; | ||
| 26 | import * as Stream from "effect/Stream"; | ||
| 21 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; | 27 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; |
| 22 | 28 | ||
| 23 | import { ProviderService } from "../../provider/Services/ProviderService.ts"; | 29 | import { ProviderService } from "../../provider/Services/ProviderService.ts"; |
apps/server/src/orchestration/Layers/RuntimeReceiptBus.ts+4-1| ... | @@ -8,7 +8,10 @@ | ... | @@ -8,7 +8,10 @@ |
| 8 | * | 8 | * |
| 9 | * @module RuntimeReceiptBus | 9 | * @module RuntimeReceiptBus |
| 10 | */ | 10 | */ |
| 11 | import { Effect, Layer, PubSub, Stream } from "effect"; | 11 | import * as Effect from "effect/Effect"; |
| 12 | import * as Layer from "effect/Layer"; | ||
| 13 | import * as PubSub from "effect/PubSub"; | ||
| 14 | import * as Stream from "effect/Stream"; | ||
| 12 | 15 | ||
| 13 | import { | 16 | import { |
| 14 | RuntimeReceiptBus, | 17 | RuntimeReceiptBus, |
apps/server/src/orchestration/Layers/ThreadDeletionReactor.test.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { ThreadId } from "@t3tools/contracts"; | 1 | import { ThreadId } from "@t3tools/contracts"; |
| 2 | import { Cause, Effect, Exit } from "effect"; | 2 | import * as Cause from "effect/Cause"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Exit from "effect/Exit"; | ||
| 3 | import { describe, expect, it } from "vitest"; | 5 | import { describe, expect, it } from "vitest"; |
| 4 | 6 | ||
| 5 | import { logCleanupCauseUnlessInterrupted } from "./ThreadDeletionReactor.ts"; | 7 | import { logCleanupCauseUnlessInterrupted } from "./ThreadDeletionReactor.ts"; |
apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import type { OrchestrationEvent } from "@t3tools/contracts"; | 1 | import type { OrchestrationEvent } from "@t3tools/contracts"; |
| 2 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; | 2 | import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; |
| 3 | import { Cause, Effect, Layer, Stream } from "effect"; | 3 | import * as Cause from "effect/Cause"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Stream from "effect/Stream"; | ||
| 4 | 7 | ||
| 5 | import { ProviderService } from "../../provider/Services/ProviderService.ts"; | 8 | import { ProviderService } from "../../provider/Services/ProviderService.ts"; |
| 6 | import { TerminalManager } from "../../terminal/Services/Manager.ts"; | 9 | import { TerminalManager } from "../../terminal/Services/Manager.ts"; |
apps/server/src/orchestration/Normalizer.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { Effect, FileSystem, Path } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import * as Path from "effect/Path"; | ||
| 2 | import { | 4 | import { |
| 3 | type ClientOrchestrationCommand, | 5 | type ClientOrchestrationCommand, |
| 4 | type OrchestrationCommand, | 6 | type OrchestrationCommand, |
apps/server/src/orchestration/Services/CheckpointReactor.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module CheckpointReactor | 7 | * @module CheckpointReactor |
| 8 | */ | 8 | */ |
| 9 | import { Context } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type { Effect, Scope } from "effect"; | 10 | import type * as Effect from "effect/Effect"; |
| 11 | import type * as Scope from "effect/Scope"; | ||
| 11 | 12 | ||
| 12 | /** | 13 | /** |
| 13 | * CheckpointReactorShape - Service API for checkpoint reactor lifecycle. | 14 | * CheckpointReactorShape - Service API for checkpoint reactor lifecycle. |
apps/server/src/orchestration/Services/OrchestrationEngine.ts+3-2| ... | @@ -11,8 +11,9 @@ | ... | @@ -11,8 +11,9 @@ |
| 11 | * @module OrchestrationEngineService | 11 | * @module OrchestrationEngineService |
| 12 | */ | 12 | */ |
| 13 | import type { OrchestrationCommand, OrchestrationEvent } from "@t3tools/contracts"; | 13 | import type { OrchestrationCommand, OrchestrationEvent } from "@t3tools/contracts"; |
| 14 | import { Context } from "effect"; | 14 | import * as Context from "effect/Context"; |
| 15 | import type { Effect, Stream } from "effect"; | 15 | import type * as Effect from "effect/Effect"; |
| 16 | import type * as Stream from "effect/Stream"; | ||
| 16 | 17 | ||
| 17 | import type { OrchestrationDispatchError } from "../Errors.ts"; | 18 | import type { OrchestrationDispatchError } from "../Errors.ts"; |
| 18 | import type { OrchestrationEventStoreError } from "../../persistence/Errors.ts"; | 19 | import type { OrchestrationEventStoreError } from "../../persistence/Errors.ts"; |
apps/server/src/orchestration/Services/OrchestrationReactor.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module OrchestrationReactor | 7 | * @module OrchestrationReactor |
| 8 | */ | 8 | */ |
| 9 | import { Context } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type { Effect, Scope } from "effect"; | 10 | import type * as Effect from "effect/Effect"; |
| 11 | import type * as Scope from "effect/Scope"; | ||
| 11 | 12 | ||
| 12 | /** | 13 | /** |
| 13 | * OrchestrationReactorShape - Service API for orchestration reactor lifecycle. | 14 | * OrchestrationReactorShape - Service API for orchestration reactor lifecycle. |
apps/server/src/orchestration/Services/ProjectionPipeline.ts+2-2| ... | @@ -7,8 +7,8 @@ | ... | @@ -7,8 +7,8 @@ |
| 7 | * @module OrchestrationProjectionPipeline | 7 | * @module OrchestrationProjectionPipeline |
| 8 | */ | 8 | */ |
| 9 | import type { OrchestrationEvent } from "@t3tools/contracts"; | 9 | import type { OrchestrationEvent } from "@t3tools/contracts"; |
| 10 | import { Context } from "effect"; | 10 | import * as Context from "effect/Context"; |
| 11 | import type { Effect } from "effect"; | 11 | import type * as Effect from "effect/Effect"; |
| 12 | 12 | ||
| 13 | import type { ProjectionRepositoryError } from "../../persistence/Errors.ts"; | 13 | import type { ProjectionRepositoryError } from "../../persistence/Errors.ts"; |
| 14 | 14 |
apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts+3-3| ... | @@ -17,9 +17,9 @@ import type { | ... | @@ -17,9 +17,9 @@ import type { |
| 17 | ProjectId, | 17 | ProjectId, |
| 18 | ThreadId, | 18 | ThreadId, |
| 19 | } from "@t3tools/contracts"; | 19 | } from "@t3tools/contracts"; |
| 20 | import { Context } from "effect"; | 20 | import * as Context from "effect/Context"; |
| 21 | import type { Option } from "effect"; | 21 | import type * as Option from "effect/Option"; |
| 22 | import type { Effect } from "effect"; | 22 | import type * as Effect from "effect/Effect"; |
| 23 | 23 | ||
| 24 | import type { ProjectionRepositoryError } from "../../persistence/Errors.ts"; | 24 | import type { ProjectionRepositoryError } from "../../persistence/Errors.ts"; |
| 25 | 25 |
apps/server/src/orchestration/Services/ProviderCommandReactor.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module ProviderCommandReactor | 7 | * @module ProviderCommandReactor |
| 8 | */ | 8 | */ |
| 9 | import { Context } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type { Effect, Scope } from "effect"; | 10 | import type * as Effect from "effect/Effect"; |
| 11 | import type * as Scope from "effect/Scope"; | ||
| 11 | 12 | ||
| 12 | /** | 13 | /** |
| 13 | * ProviderCommandReactorShape - Service API for provider command reactors. | 14 | * ProviderCommandReactorShape - Service API for provider command reactors. |
apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module ProviderRuntimeIngestionService | 7 | * @module ProviderRuntimeIngestionService |
| 8 | */ | 8 | */ |
| 9 | import { Context } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type { Effect, Scope } from "effect"; | 10 | import type * as Effect from "effect/Effect"; |
| 11 | import type * as Scope from "effect/Scope"; | ||
| 11 | 12 | ||
| 12 | /** | 13 | /** |
| 13 | * ProviderRuntimeIngestionShape - Service API for runtime ingestion lifecycle. | 14 | * ProviderRuntimeIngestionShape - Service API for runtime ingestion lifecycle. |
apps/server/src/orchestration/Services/RuntimeReceiptBus.ts+4-2| ... | @@ -15,8 +15,10 @@ | ... | @@ -15,8 +15,10 @@ |
| 15 | * @module RuntimeReceiptBus | 15 | * @module RuntimeReceiptBus |
| 16 | */ | 16 | */ |
| 17 | import { CheckpointRef, IsoDateTime, NonNegativeInt, ThreadId, TurnId } from "@t3tools/contracts"; | 17 | import { CheckpointRef, IsoDateTime, NonNegativeInt, ThreadId, TurnId } from "@t3tools/contracts"; |
| 18 | import { Schema, Context } from "effect"; | 18 | import * as Schema from "effect/Schema"; |
| 19 | import type { Effect, Stream } from "effect"; | 19 | import * as Context from "effect/Context"; |
| 20 | import type * as Effect from "effect/Effect"; | ||
| 21 | import type * as Stream from "effect/Stream"; | ||
| 20 | 22 | ||
| 21 | export const CheckpointBaselineCapturedReceipt = Schema.Struct({ | 23 | export const CheckpointBaselineCapturedReceipt = Schema.Struct({ |
| 22 | type: Schema.Literal("checkpoint.baseline.captured"), | 24 | type: Schema.Literal("checkpoint.baseline.captured"), |
apps/server/src/orchestration/Services/ThreadDeletionReactor.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module ThreadDeletionReactor | 7 | * @module ThreadDeletionReactor |
| 8 | */ | 8 | */ |
| 9 | import { Context } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type { Effect, Scope } from "effect"; | 10 | import type * as Effect from "effect/Effect"; |
| 11 | import type * as Scope from "effect/Scope"; | ||
| 11 | 12 | ||
| 12 | /** | 13 | /** |
| 13 | * ThreadDeletionReactorShape - Service API for thread deletion cleanup. | 14 | * ThreadDeletionReactorShape - Service API for thread deletion cleanup. |
apps/server/src/orchestration/commandInvariants.test.ts+2-2| ... | @@ -9,7 +9,7 @@ import { | ... | @@ -9,7 +9,7 @@ import { |
| 9 | type OrchestrationReadModel, | 9 | type OrchestrationReadModel, |
| 10 | ProviderInstanceId, | 10 | ProviderInstanceId, |
| 11 | } from "@t3tools/contracts"; | 11 | } from "@t3tools/contracts"; |
| 12 | import { Effect } from "effect"; | 12 | import * as Effect from "effect/Effect"; |
| 13 | 13 | ||
| 14 | import { | 14 | import { |
| 15 | findThreadById, | 15 | findThreadById, |
| ... | @@ -19,7 +19,7 @@ import { | ... | @@ -19,7 +19,7 @@ import { |
| 19 | requireThreadAbsent, | 19 | requireThreadAbsent, |
| 20 | } from "./commandInvariants.ts"; | 20 | } from "./commandInvariants.ts"; |
| 21 | 21 | ||
| 22 | const now = new Date().toISOString(); | 22 | const now = "2026-01-01T00:00:00.000Z"; |
| 23 | 23 | ||
| 24 | const readModel: OrchestrationReadModel = { | 24 | const readModel: OrchestrationReadModel = { |
| 25 | snapshotSequence: 2, | 25 | snapshotSequence: 2, |
apps/server/src/orchestration/commandInvariants.ts+1-1| ... | @@ -6,7 +6,7 @@ import type { | ... | @@ -6,7 +6,7 @@ import type { |
| 6 | ProjectId, | 6 | ProjectId, |
| 7 | ThreadId, | 7 | ThreadId, |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { Effect } from "effect"; | 9 | import * as Effect from "effect/Effect"; |
| 10 | 10 | ||
| 11 | import { OrchestrationCommandInvariantError } from "./Errors.ts"; | 11 | import { OrchestrationCommandInvariantError } from "./Errors.ts"; |
| 12 | 12 |
apps/server/src/orchestration/decider.delete.test.ts+2-2| ... | @@ -9,7 +9,7 @@ import { | ... | @@ -9,7 +9,7 @@ import { |
| 9 | type OrchestrationReadModel, | 9 | type OrchestrationReadModel, |
| 10 | ProviderInstanceId, | 10 | ProviderInstanceId, |
| 11 | } from "@t3tools/contracts"; | 11 | } from "@t3tools/contracts"; |
| 12 | import { Effect } from "effect"; | 12 | import * as Effect from "effect/Effect"; |
| 13 | import { describe, expect, it } from "vitest"; | 13 | import { describe, expect, it } from "vitest"; |
| 14 | 14 | ||
| 15 | import { decideOrchestrationCommand } from "./decider.ts"; | 15 | import { decideOrchestrationCommand } from "./decider.ts"; |
| ... | @@ -21,7 +21,7 @@ const asProjectId = (value: string): ProjectId => ProjectId.make(value); | ... | @@ -21,7 +21,7 @@ const asProjectId = (value: string): ProjectId => ProjectId.make(value); |
| 21 | const asThreadId = (value: string): ThreadId => ThreadId.make(value); | 21 | const asThreadId = (value: string): ThreadId => ThreadId.make(value); |
| 22 | 22 | ||
| 23 | async function seedReadModel(): Promise<OrchestrationReadModel> { | 23 | async function seedReadModel(): Promise<OrchestrationReadModel> { |
| 24 | const now = new Date().toISOString(); | 24 | const now = "2026-01-01T00:00:00.000Z"; |
| 25 | const initial = createEmptyReadModel(now); | 25 | const initial = createEmptyReadModel(now); |
| 26 | const withProject = await Effect.runPromise( | 26 | const withProject = await Effect.runPromise( |
| 27 | projectEvent(initial, { | 27 | projectEvent(initial, { |
apps/server/src/orchestration/decider.projectScripts.test.ts+6-6| ... | @@ -9,7 +9,7 @@ import { | ... | @@ -9,7 +9,7 @@ import { |
| 9 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 10 | import { createModelSelection } from "@t3tools/shared/model"; | 10 | import { createModelSelection } from "@t3tools/shared/model"; |
| 11 | import { describe, expect, it } from "vitest"; | 11 | import { describe, expect, it } from "vitest"; |
| 12 | import { Effect } from "effect"; | 12 | import * as Effect from "effect/Effect"; |
| 13 | 13 | ||
| 14 | import { decideOrchestrationCommand } from "./decider.ts"; | 14 | import { decideOrchestrationCommand } from "./decider.ts"; |
| 15 | import { createEmptyReadModel, projectEvent } from "./projector.ts"; | 15 | import { createEmptyReadModel, projectEvent } from "./projector.ts"; |
| ... | @@ -20,7 +20,7 @@ const asMessageId = (value: string): MessageId => MessageId.make(value); | ... | @@ -20,7 +20,7 @@ const asMessageId = (value: string): MessageId => MessageId.make(value); |
| 20 | 20 | ||
| 21 | describe("decider project scripts", () => { | 21 | describe("decider project scripts", () => { |
| 22 | it("emits empty scripts on project.create", async () => { | 22 | it("emits empty scripts on project.create", async () => { |
| 23 | const now = new Date().toISOString(); | 23 | const now = "2026-01-01T00:00:00.000Z"; |
| 24 | const readModel = createEmptyReadModel(now); | 24 | const readModel = createEmptyReadModel(now); |
| 25 | 25 | ||
| 26 | const result = await Effect.runPromise( | 26 | const result = await Effect.runPromise( |
| ... | @@ -43,7 +43,7 @@ describe("decider project scripts", () => { | ... | @@ -43,7 +43,7 @@ describe("decider project scripts", () => { |
| 43 | }); | 43 | }); |
| 44 | 44 | ||
| 45 | it("propagates scripts in project.meta.update payload", async () => { | 45 | it("propagates scripts in project.meta.update payload", async () => { |
| 46 | const now = new Date().toISOString(); | 46 | const now = "2026-01-01T00:00:00.000Z"; |
| 47 | const initial = createEmptyReadModel(now); | 47 | const initial = createEmptyReadModel(now); |
| 48 | const readModel = await Effect.runPromise( | 48 | const readModel = await Effect.runPromise( |
| 49 | projectEvent(initial, { | 49 | projectEvent(initial, { |
| ... | @@ -97,7 +97,7 @@ describe("decider project scripts", () => { | ... | @@ -97,7 +97,7 @@ describe("decider project scripts", () => { |
| 97 | }); | 97 | }); |
| 98 | 98 | ||
| 99 | it("emits user message and turn-start-requested events for thread.turn.start", async () => { | 99 | it("emits user message and turn-start-requested events for thread.turn.start", async () => { |
| 100 | const now = new Date().toISOString(); | 100 | const now = "2026-01-01T00:00:00.000Z"; |
| 101 | const initial = createEmptyReadModel(now); | 101 | const initial = createEmptyReadModel(now); |
| 102 | const withProject = await Effect.runPromise( | 102 | const withProject = await Effect.runPromise( |
| 103 | projectEvent(initial, { | 103 | projectEvent(initial, { |
| ... | @@ -198,7 +198,7 @@ describe("decider project scripts", () => { | ... | @@ -198,7 +198,7 @@ describe("decider project scripts", () => { |
| 198 | }); | 198 | }); |
| 199 | 199 | ||
| 200 | it("emits thread.runtime-mode-set from thread.runtime-mode.set", async () => { | 200 | it("emits thread.runtime-mode-set from thread.runtime-mode.set", async () => { |
| 201 | const now = new Date().toISOString(); | 201 | const now = "2026-01-01T00:00:00.000Z"; |
| 202 | const initial = createEmptyReadModel(now); | 202 | const initial = createEmptyReadModel(now); |
| 203 | const withProject = await Effect.runPromise( | 203 | const withProject = await Effect.runPromise( |
| 204 | projectEvent(initial, { | 204 | projectEvent(initial, { |
| ... | @@ -280,7 +280,7 @@ describe("decider project scripts", () => { | ... | @@ -280,7 +280,7 @@ describe("decider project scripts", () => { |
| 280 | }); | 280 | }); |
| 281 | 281 | ||
| 282 | it("emits thread.interaction-mode-set from thread.interaction-mode.set", async () => { | 282 | it("emits thread.interaction-mode-set from thread.interaction-mode.set", async () => { |
| 283 | const now = new Date().toISOString(); | 283 | const now = "2026-01-01T00:00:00.000Z"; |
| 284 | const initial = createEmptyReadModel(now); | 284 | const initial = createEmptyReadModel(now); |
| 285 | const withProject = await Effect.runPromise( | 285 | const withProject = await Effect.runPromise( |
| 286 | projectEvent(initial, { | 286 | projectEvent(initial, { |
apps/server/src/orchestration/decider.ts+11-10| ... | @@ -3,7 +3,8 @@ import type { | ... | @@ -3,7 +3,8 @@ import type { |
| 3 | OrchestrationEvent, | 3 | OrchestrationEvent, |
| 4 | OrchestrationReadModel, | 4 | OrchestrationReadModel, |
| 5 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { DateTime, Effect } from "effect"; | 6 | import * as DateTime from "effect/DateTime"; |
| 7 | import * as Effect from "effect/Effect"; | ||
| 7 | 8 | ||
| 8 | import { OrchestrationCommandInvariantError } from "./Errors.ts"; | 9 | import { OrchestrationCommandInvariantError } from "./Errors.ts"; |
| 9 | import { | 10 | import { |
| ... | @@ -17,7 +18,7 @@ import { | ... | @@ -17,7 +18,7 @@ import { |
| 17 | } from "./commandInvariants.ts"; | 18 | } from "./commandInvariants.ts"; |
| 18 | import { projectEvent } from "./projector.ts"; | 19 | import { projectEvent } from "./projector.ts"; |
| 19 | 20 | ||
| 20 | const currentIso = DateTime.now.pipe(Effect.map(DateTime.formatIso)); | 21 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 21 | 22 | ||
| 22 | function withEventBase( | 23 | function withEventBase( |
| 23 | input: Pick<OrchestrationCommand, "commandId"> & { | 24 | input: Pick<OrchestrationCommand, "commandId"> & { |
| ... | @@ -116,7 +117,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -116,7 +117,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 116 | command, | 117 | command, |
| 117 | projectId: command.projectId, | 118 | projectId: command.projectId, |
| 118 | }); | 119 | }); |
| 119 | const occurredAt = yield* currentIso; | 120 | const occurredAt = yield* nowIso; |
| 120 | return { | 121 | return { |
| 121 | ...withEventBase({ | 122 | ...withEventBase({ |
| 122 | aggregateKind: "project", | 123 | aggregateKind: "project", |
| ... | @@ -173,7 +174,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -173,7 +174,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 173 | }); | 174 | }); |
| 174 | } | 175 | } |
| 175 | 176 | ||
| 176 | const occurredAt = yield* currentIso; | 177 | const occurredAt = yield* nowIso; |
| 177 | return { | 178 | return { |
| 178 | ...withEventBase({ | 179 | ...withEventBase({ |
| 179 | aggregateKind: "project", | 180 | aggregateKind: "project", |
| ... | @@ -229,7 +230,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -229,7 +230,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 229 | command, | 230 | command, |
| 230 | threadId: command.threadId, | 231 | threadId: command.threadId, |
| 231 | }); | 232 | }); |
| 232 | const occurredAt = yield* currentIso; | 233 | const occurredAt = yield* nowIso; |
| 233 | return { | 234 | return { |
| 234 | ...withEventBase({ | 235 | ...withEventBase({ |
| 235 | aggregateKind: "thread", | 236 | aggregateKind: "thread", |
| ... | @@ -251,7 +252,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -251,7 +252,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 251 | command, | 252 | command, |
| 252 | threadId: command.threadId, | 253 | threadId: command.threadId, |
| 253 | }); | 254 | }); |
| 254 | const occurredAt = yield* currentIso; | 255 | const occurredAt = yield* nowIso; |
| 255 | return { | 256 | return { |
| 256 | ...withEventBase({ | 257 | ...withEventBase({ |
| 257 | aggregateKind: "thread", | 258 | aggregateKind: "thread", |
| ... | @@ -274,7 +275,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -274,7 +275,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 274 | command, | 275 | command, |
| 275 | threadId: command.threadId, | 276 | threadId: command.threadId, |
| 276 | }); | 277 | }); |
| 277 | const occurredAt = yield* currentIso; | 278 | const occurredAt = yield* nowIso; |
| 278 | return { | 279 | return { |
| 279 | ...withEventBase({ | 280 | ...withEventBase({ |
| 280 | aggregateKind: "thread", | 281 | aggregateKind: "thread", |
| ... | @@ -296,7 +297,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -296,7 +297,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 296 | command, | 297 | command, |
| 297 | threadId: command.threadId, | 298 | threadId: command.threadId, |
| 298 | }); | 299 | }); |
| 299 | const occurredAt = yield* currentIso; | 300 | const occurredAt = yield* nowIso; |
| 300 | return { | 301 | return { |
| 301 | ...withEventBase({ | 302 | ...withEventBase({ |
| 302 | aggregateKind: "thread", | 303 | aggregateKind: "thread", |
| ... | @@ -324,7 +325,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -324,7 +325,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 324 | command, | 325 | command, |
| 325 | threadId: command.threadId, | 326 | threadId: command.threadId, |
| 326 | }); | 327 | }); |
| 327 | const occurredAt = yield* currentIso; | 328 | const occurredAt = yield* nowIso; |
| 328 | return { | 329 | return { |
| 329 | ...withEventBase({ | 330 | ...withEventBase({ |
| 330 | aggregateKind: "thread", | 331 | aggregateKind: "thread", |
| ... | @@ -347,7 +348,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" | ... | @@ -347,7 +348,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 347 | command, | 348 | command, |
| 348 | threadId: command.threadId, | 349 | threadId: command.threadId, |
| 349 | }); | 350 | }); |
| 350 | const occurredAt = yield* currentIso; | 351 | const occurredAt = yield* nowIso; |
| 351 | return { | 352 | return { |
| 352 | ...withEventBase({ | 353 | ...withEventBase({ |
| 353 | aggregateKind: "thread", | 354 | aggregateKind: "thread", |
apps/server/src/orchestration/http.ts+1-1| ... | @@ -4,7 +4,7 @@ import { | ... | @@ -4,7 +4,7 @@ import { |
| 4 | OrchestrationGetSnapshotError, | 4 | OrchestrationGetSnapshotError, |
| 5 | type OrchestrationReadModel, | 5 | type OrchestrationReadModel, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Effect } from "effect"; | 7 | import * as Effect from "effect/Effect"; |
| 8 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; | 8 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; |
| 9 | 9 | ||
| 10 | import { ServerAuth } from "../auth/Services/ServerAuth.ts"; | 10 | import { ServerAuth } from "../auth/Services/ServerAuth.ts"; |
apps/server/src/orchestration/projector.test.ts+6-6| ... | @@ -6,7 +6,7 @@ import { | ... | @@ -6,7 +6,7 @@ import { |
| 6 | ThreadId, | 6 | ThreadId, |
| 7 | type OrchestrationEvent, | 7 | type OrchestrationEvent, |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { Effect } from "effect"; | 9 | import * as Effect from "effect/Effect"; |
| 10 | import { describe, expect, it } from "vitest"; | 10 | import { describe, expect, it } from "vitest"; |
| 11 | 11 | ||
| 12 | import { createEmptyReadModel, projectEvent } from "./projector.ts"; | 12 | import { createEmptyReadModel, projectEvent } from "./projector.ts"; |
| ... | @@ -40,7 +40,7 @@ function makeEvent(input: { | ... | @@ -40,7 +40,7 @@ function makeEvent(input: { |
| 40 | 40 | ||
| 41 | describe("orchestration projector", () => { | 41 | describe("orchestration projector", () => { |
| 42 | it("applies thread.created events", async () => { | 42 | it("applies thread.created events", async () => { |
| 43 | const now = new Date().toISOString(); | 43 | const now = "2026-01-01T00:00:00.000Z"; |
| 44 | const model = createEmptyReadModel(now); | 44 | const model = createEmptyReadModel(now); |
| 45 | 45 | ||
| 46 | const next = await Effect.runPromise( | 46 | const next = await Effect.runPromise( |
| ... | @@ -100,7 +100,7 @@ describe("orchestration projector", () => { | ... | @@ -100,7 +100,7 @@ describe("orchestration projector", () => { |
| 100 | }); | 100 | }); |
| 101 | 101 | ||
| 102 | it("fails when event payload cannot be decoded by runtime schema", async () => { | 102 | it("fails when event payload cannot be decoded by runtime schema", async () => { |
| 103 | const now = new Date().toISOString(); | 103 | const now = "2026-01-01T00:00:00.000Z"; |
| 104 | const model = createEmptyReadModel(now); | 104 | const model = createEmptyReadModel(now); |
| 105 | 105 | ||
| 106 | await expect( | 106 | await expect( |
| ... | @@ -134,8 +134,8 @@ describe("orchestration projector", () => { | ... | @@ -134,8 +134,8 @@ describe("orchestration projector", () => { |
| 134 | }); | 134 | }); |
| 135 | 135 | ||
| 136 | it("applies thread.archived and thread.unarchived events", async () => { | 136 | it("applies thread.archived and thread.unarchived events", async () => { |
| 137 | const now = new Date().toISOString(); | 137 | const now = "2026-01-01T00:00:00.000Z"; |
| 138 | const later = new Date(Date.parse(now) + 1_000).toISOString(); | 138 | const later = "2026-01-01T00:00:01.000Z"; |
| 139 | const created = await Effect.runPromise( | 139 | const created = await Effect.runPromise( |
| 140 | projectEvent( | 140 | projectEvent( |
| 141 | createEmptyReadModel(now), | 141 | createEmptyReadModel(now), |
| ... | @@ -206,7 +206,7 @@ describe("orchestration projector", () => { | ... | @@ -206,7 +206,7 @@ describe("orchestration projector", () => { |
| 206 | }); | 206 | }); |
| 207 | 207 | ||
| 208 | it("keeps projector forward-compatible for unhandled event types", async () => { | 208 | it("keeps projector forward-compatible for unhandled event types", async () => { |
| 209 | const now = new Date().toISOString(); | 209 | const now = "2026-01-01T00:00:00.000Z"; |
| 210 | const model = createEmptyReadModel(now); | 210 | const model = createEmptyReadModel(now); |
| 211 | 211 | ||
| 212 | const next = await Effect.runPromise( | 212 | const next = await Effect.runPromise( |
apps/server/src/orchestration/projector.ts+2-1| ... | @@ -5,7 +5,8 @@ import { | ... | @@ -5,7 +5,8 @@ import { |
| 5 | OrchestrationSession, | 5 | OrchestrationSession, |
| 6 | OrchestrationThread, | 6 | OrchestrationThread, |
| 7 | } from "@t3tools/contracts"; | 7 | } from "@t3tools/contracts"; |
| 8 | import { Effect, Schema } from "effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Schema from "effect/Schema"; | ||
| 9 | 10 | ||
| 10 | import { toProjectorDecodeError, type OrchestrationProjectorDecodeError } from "./Errors.ts"; | 11 | import { toProjectorDecodeError, type OrchestrationProjectorDecodeError } from "./Errors.ts"; |
| 11 | import { | 12 | import { |
apps/server/src/orchestration/runtimeLayer.ts+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Layer } from "effect"; | 1 | import * as Layer from "effect/Layer"; |
| 2 | 2 | ||
| 3 | import { OrchestrationCommandReceiptRepositoryLive } from "../persistence/Layers/OrchestrationCommandReceipts.ts"; | 3 | import { OrchestrationCommandReceiptRepositoryLive } from "../persistence/Layers/OrchestrationCommandReceipts.ts"; |
| 4 | import { OrchestrationEventStoreLive } from "../persistence/Layers/OrchestrationEventStore.ts"; | 4 | import { OrchestrationEventStoreLive } from "../persistence/Layers/OrchestrationEventStore.ts"; |
apps/server/src/os-jank.test.ts deleted-185| ... | @@ -1,185 +0,0 @@ | ||
| 1 | import { describe, expect, it, vi } from "vitest"; | ||
| 2 | |||
| 3 | import { fixPath } from "./os-jank.ts"; | ||
| 4 | |||
| 5 | describe("fixPath", () => { | ||
| 6 | it("hydrates PATH on linux using the resolved login shell", () => { | ||
| 7 | const env: NodeJS.ProcessEnv = { | ||
| 8 | SHELL: "/bin/zsh", | ||
| 9 | PATH: "/Users/test/.local/bin:/usr/bin", | ||
| 10 | }; | ||
| 11 | const readPath = vi.fn(() => "/opt/homebrew/bin:/usr/bin"); | ||
| 12 | |||
| 13 | fixPath({ | ||
| 14 | env, | ||
| 15 | platform: "linux", | ||
| 16 | readPath, | ||
| 17 | }); | ||
| 18 | |||
| 19 | expect(readPath).toHaveBeenCalledWith("/bin/zsh"); | ||
| 20 | expect(env.PATH).toBe("/opt/homebrew/bin:/usr/bin:/Users/test/.local/bin"); | ||
| 21 | }); | ||
| 22 | |||
| 23 | it("falls back to launchctl PATH on macOS when shell probing fails", () => { | ||
| 24 | const env: NodeJS.ProcessEnv = { | ||
| 25 | SHELL: "/opt/homebrew/bin/nu", | ||
| 26 | PATH: "/usr/bin", | ||
| 27 | }; | ||
| 28 | const readPath = vi | ||
| 29 | .fn() | ||
| 30 | .mockImplementationOnce(() => { | ||
| 31 | throw new Error("unknown flag"); | ||
| 32 | }) | ||
| 33 | .mockImplementationOnce(() => undefined); | ||
| 34 | const readLaunchctlPath = vi.fn(() => "/opt/homebrew/bin:/usr/bin"); | ||
| 35 | const logWarning = vi.fn(); | ||
| 36 | |||
| 37 | fixPath({ | ||
| 38 | env, | ||
| 39 | platform: "darwin", | ||
| 40 | readPath, | ||
| 41 | readLaunchctlPath, | ||
| 42 | userShell: "/bin/zsh", | ||
| 43 | logWarning, | ||
| 44 | }); | ||
| 45 | |||
| 46 | expect(readPath).toHaveBeenNthCalledWith(1, "/opt/homebrew/bin/nu"); | ||
| 47 | expect(readPath).toHaveBeenNthCalledWith(2, "/bin/zsh"); | ||
| 48 | expect(readLaunchctlPath).toHaveBeenCalledTimes(1); | ||
| 49 | expect(logWarning).toHaveBeenCalledWith( | ||
| 50 | "Failed to read PATH from login shell /opt/homebrew/bin/nu.", | ||
| 51 | expect.any(Error), | ||
| 52 | ); | ||
| 53 | expect(env.PATH).toBe("/opt/homebrew/bin:/usr/bin"); | ||
| 54 | }); | ||
| 55 | |||
| 56 | it("repairs PATH on Windows by merging PowerShell PATH with inherited PATH", () => { | ||
| 57 | const env: NodeJS.ProcessEnv = { | ||
| 58 | PATH: "C:\\Windows\\System32", | ||
| 59 | APPDATA: "C:\\Users\\testuser\\AppData\\Roaming", | ||
| 60 | LOCALAPPDATA: "C:\\Users\\testuser\\AppData\\Local", | ||
| 61 | USERPROFILE: "C:\\Users\\testuser", | ||
| 62 | }; | ||
| 63 | const readWindowsEnvironment = vi.fn(() => ({ | ||
| 64 | PATH: "C:\\Custom\\Bin;C:\\Windows\\System32", | ||
| 65 | })); | ||
| 66 | const isWindowsCommandAvailable = vi.fn(() => true); | ||
| 67 | |||
| 68 | fixPath({ | ||
| 69 | env, | ||
| 70 | platform: "win32", | ||
| 71 | readWindowsEnvironment, | ||
| 72 | isWindowsCommandAvailable, | ||
| 73 | }); | ||
| 74 | |||
| 75 | expect(readWindowsEnvironment).toHaveBeenCalledWith(["PATH"], { loadProfile: false }); | ||
| 76 | expect(env.PATH).toBe( | ||
| 77 | [ | ||
| 78 | "C:\\Users\\testuser\\AppData\\Roaming\\npm", | ||
| 79 | "C:\\Users\\testuser\\AppData\\Local\\Programs\\nodejs", | ||
| 80 | "C:\\Users\\testuser\\AppData\\Local\\Volta\\bin", | ||
| 81 | "C:\\Users\\testuser\\AppData\\Local\\pnpm", | ||
| 82 | "C:\\Users\\testuser\\.bun\\bin", | ||
| 83 | "C:\\Users\\testuser\\scoop\\shims", | ||
| 84 | "C:\\Custom\\Bin", | ||
| 85 | "C:\\Windows\\System32", | ||
| 86 | ].join(";"), | ||
| 87 | ); | ||
| 88 | }); | ||
| 89 | |||
| 90 | it("applies profile-derived fnm variables on Windows when node is missing", () => { | ||
| 91 | const env: NodeJS.ProcessEnv = { | ||
| 92 | PATH: "C:\\Windows\\System32", | ||
| 93 | APPDATA: "C:\\Users\\testuser\\AppData\\Roaming", | ||
| 94 | LOCALAPPDATA: "C:\\Users\\testuser\\AppData\\Local", | ||
| 95 | USERPROFILE: "C:\\Users\\testuser", | ||
| 96 | }; | ||
| 97 | const readWindowsEnvironment = vi.fn( | ||
| 98 | (_names: ReadonlyArray<string>, options?: { loadProfile?: boolean }) => | ||
| 99 | options?.loadProfile | ||
| 100 | ? { | ||
| 101 | PATH: "C:\\Profile\\Node;C:\\Windows\\System32", | ||
| 102 | FNM_DIR: "C:\\Users\\testuser\\AppData\\Roaming\\fnm", | ||
| 103 | FNM_MULTISHELL_PATH: "C:\\Users\\testuser\\AppData\\Local\\fnm_multishells\\123", | ||
| 104 | } | ||
| 105 | : { PATH: "C:\\Custom\\Bin;C:\\Windows\\System32" }, | ||
| 106 | ); | ||
| 107 | const isWindowsCommandAvailable = vi.fn().mockReturnValueOnce(false).mockReturnValueOnce(true); | ||
| 108 | |||
| 109 | fixPath({ | ||
| 110 | env, | ||
| 111 | platform: "win32", | ||
| 112 | readWindowsEnvironment, | ||
| 113 | isWindowsCommandAvailable, | ||
| 114 | }); | ||
| 115 | |||
| 116 | expect(env.PATH).toBe( | ||
| 117 | [ | ||
| 118 | "C:\\Profile\\Node", | ||
| 119 | "C:\\Windows\\System32", | ||
| 120 | "C:\\Users\\testuser\\AppData\\Roaming\\npm", | ||
| 121 | "C:\\Users\\testuser\\AppData\\Local\\Programs\\nodejs", | ||
| 122 | "C:\\Users\\testuser\\AppData\\Local\\Volta\\bin", | ||
| 123 | "C:\\Users\\testuser\\AppData\\Local\\pnpm", | ||
| 124 | "C:\\Users\\testuser\\.bun\\bin", | ||
| 125 | "C:\\Users\\testuser\\scoop\\shims", | ||
| 126 | "C:\\Custom\\Bin", | ||
| 127 | ].join(";"), | ||
| 128 | ); | ||
| 129 | expect(env.FNM_DIR).toBe("C:\\Users\\testuser\\AppData\\Roaming\\fnm"); | ||
| 130 | expect(env.FNM_MULTISHELL_PATH).toBe( | ||
| 131 | "C:\\Users\\testuser\\AppData\\Local\\fnm_multishells\\123", | ||
| 132 | ); | ||
| 133 | }); | ||
| 134 | |||
| 135 | it("preserves baseline PATH on Windows when the profile probe fails", () => { | ||
| 136 | const env: NodeJS.ProcessEnv = { | ||
| 137 | PATH: "C:\\Windows\\System32", | ||
| 138 | APPDATA: "C:\\Users\\testuser\\AppData\\Roaming", | ||
| 139 | USERPROFILE: "C:\\Users\\testuser", | ||
| 140 | }; | ||
| 141 | const readWindowsEnvironment = vi.fn( | ||
| 142 | (_names: ReadonlyArray<string>, options?: { loadProfile?: boolean }) => { | ||
| 143 | if (options?.loadProfile) { | ||
| 144 | throw new Error("profile load failed"); | ||
| 145 | } | ||
| 146 | return { PATH: "C:\\Custom\\Bin;C:\\Windows\\System32" }; | ||
| 147 | }, | ||
| 148 | ); | ||
| 149 | const isWindowsCommandAvailable = vi.fn(() => false); | ||
| 150 | |||
| 151 | fixPath({ | ||
| 152 | env, | ||
| 153 | platform: "win32", | ||
| 154 | readWindowsEnvironment, | ||
| 155 | isWindowsCommandAvailable, | ||
| 156 | }); | ||
| 157 | |||
| 158 | expect(env.PATH).toBe( | ||
| 159 | [ | ||
| 160 | "C:\\Users\\testuser\\AppData\\Roaming\\npm", | ||
| 161 | "C:\\Users\\testuser\\.bun\\bin", | ||
| 162 | "C:\\Users\\testuser\\scoop\\shims", | ||
| 163 | "C:\\Custom\\Bin", | ||
| 164 | "C:\\Windows\\System32", | ||
| 165 | ].join(";"), | ||
| 166 | ); | ||
| 167 | }); | ||
| 168 | |||
| 169 | it("does nothing on unsupported platforms", () => { | ||
| 170 | const env: NodeJS.ProcessEnv = { | ||
| 171 | SHELL: "C:/Program Files/Git/bin/bash.exe", | ||
| 172 | PATH: "C:\\Windows\\System32", | ||
| 173 | }; | ||
| 174 | const readPath = vi.fn(() => "/usr/local/bin:/usr/bin"); | ||
| 175 | |||
| 176 | fixPath({ | ||
| 177 | env, | ||
| 178 | platform: "freebsd", | ||
| 179 | readPath, | ||
| 180 | }); | ||
| 181 | |||
| 182 | expect(readPath).not.toHaveBeenCalled(); | ||
| 183 | expect(env.PATH).toBe("C:\\Windows\\System32"); | ||
| 184 | }); | ||
| 185 | }); | ||
apps/server/src/os-jank.ts+9-6| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import * as OS from "node:os"; | 1 | import * as NodeOS from "node:os"; |
| 2 | import { Effect, Path } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Path from "effect/Path"; | ||
| 3 | import { | 4 | import { |
| 4 | readPathFromLoginShell, | 5 | readPathFromLoginShell, |
| 5 | readEnvironmentFromWindowsShell, | 6 | readEnvironmentFromWindowsShell, |
| ... | @@ -17,7 +18,9 @@ type WindowsCommandAvailabilityChecker = ( | ... | @@ -17,7 +18,9 @@ type WindowsCommandAvailabilityChecker = ( |
| 17 | ) => boolean; | 18 | ) => boolean; |
| 18 | 19 | ||
| 19 | function logPathHydrationWarning(message: string, error?: unknown): void { | 20 | function logPathHydrationWarning(message: string, error?: unknown): void { |
| 20 | console.warn(`[server] ${message}`, error instanceof Error ? error.message : (error ?? "")); | 21 | process.stderr.write( |
| 22 | `[server] ${message} ${error instanceof Error ? error.message : (error ?? "")}\n`, | ||
| 23 | ); | ||
| 21 | } | 24 | } |
| 22 | 25 | ||
| 23 | export function fixPath( | 26 | export function fixPath( |
| ... | @@ -84,10 +87,10 @@ export function fixPath( | ... | @@ -84,10 +87,10 @@ export function fixPath( |
| 84 | export const expandHomePath = Effect.fn(function* (input: string) { | 87 | export const expandHomePath = Effect.fn(function* (input: string) { |
| 85 | const { join } = yield* Path.Path; | 88 | const { join } = yield* Path.Path; |
| 86 | if (input === "~") { | 89 | if (input === "~") { |
| 87 | return OS.homedir(); | 90 | return NodeOS.homedir(); |
| 88 | } | 91 | } |
| 89 | if (input.startsWith("~/") || input.startsWith("~\\")) { | 92 | if (input.startsWith("~/") || input.startsWith("~\\")) { |
| 90 | return join(OS.homedir(), input.slice(2)); | 93 | return join(NodeOS.homedir(), input.slice(2)); |
| 91 | } | 94 | } |
| 92 | return input; | 95 | return input; |
| 93 | }); | 96 | }); |
| ... | @@ -95,7 +98,7 @@ export const expandHomePath = Effect.fn(function* (input: string) { | ... | @@ -95,7 +98,7 @@ export const expandHomePath = Effect.fn(function* (input: string) { |
| 95 | export const resolveBaseDir = Effect.fn(function* (raw: string | undefined) { | 98 | export const resolveBaseDir = Effect.fn(function* (raw: string | undefined) { |
| 96 | const { join, resolve } = yield* Path.Path; | 99 | const { join, resolve } = yield* Path.Path; |
| 97 | if (!raw || raw.trim().length === 0) { | 100 | if (!raw || raw.trim().length === 0) { |
| 98 | return join(OS.homedir(), ".t3"); | 101 | return join(NodeOS.homedir(), ".t3"); |
| 99 | } | 102 | } |
| 100 | return resolve(yield* expandHomePath(raw.trim())); | 103 | return resolve(yield* expandHomePath(raw.trim())); |
| 101 | }); | 104 | }); |
apps/server/src/pathExpansion.test.ts+1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { homedir } from "node:os"; | 2 | import { homedir } from "node:os"; |
| 2 | import { join } from "node:path"; | 3 | import { join } from "node:path"; |
| 3 | import { describe, expect, it } from "vitest"; | 4 | import { describe, expect, it } from "vitest"; |
apps/server/src/pathExpansion.ts+1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { homedir } from "node:os"; | 2 | import { homedir } from "node:os"; |
| 2 | import { join } from "node:path"; | 3 | import { join } from "node:path"; |
| 3 | 4 |
apps/server/src/persistence/Errors.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Schema, SchemaIssue } from "effect"; | 1 | import * as Schema from "effect/Schema"; |
| 2 | import * as SchemaIssue from "effect/SchemaIssue"; | ||
| 2 | 3 | ||
| 3 | // =============================== | 4 | // =============================== |
| 4 | // Core Persistence Errors | 5 | // Core Persistence Errors |
apps/server/src/persistence/Layers/AuthPairingLinks.ts+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { Effect, Layer, Schema } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 4 | 6 | ||
| 5 | import { | 7 | import { |
| 6 | toPersistenceDecodeError, | 8 | toPersistenceDecodeError, |
apps/server/src/persistence/Layers/AuthSessions.ts+4-1| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | import { AuthSessionId } from "@t3tools/contracts"; | 1 | import { AuthSessionId } from "@t3tools/contracts"; |
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 4 | import { Effect, Layer, Option, Schema } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 5 | 8 | ||
| 6 | import { | 9 | import { |
| 7 | toPersistenceDecodeError, | 10 | toPersistenceDecodeError, |
apps/server/src/persistence/Layers/OrchestrationCommandReceipts.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { Effect, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 4 | 5 | ||
| 5 | import { toPersistenceSqlError } from "../Errors.ts"; | 6 | import { toPersistenceSqlError } from "../Errors.ts"; |
| 6 | 7 |
apps/server/src/persistence/Layers/OrchestrationEventStore.test.ts+6-3| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import { CommandId, EventId, ProjectId } from "@t3tools/contracts"; | 1 | import { CommandId, EventId, ProjectId } from "@t3tools/contracts"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { Effect, Layer, Schema, Stream } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 6 | import * as Stream from "effect/Stream"; | ||
| 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 7 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 5 | 8 | ||
| 6 | import { PersistenceDecodeError } from "../Errors.ts"; | 9 | import { PersistenceDecodeError } from "../Errors.ts"; |
| ... | @@ -17,7 +20,7 @@ layer("OrchestrationEventStore", (it) => { | ... | @@ -17,7 +20,7 @@ layer("OrchestrationEventStore", (it) => { |
| 17 | Effect.gen(function* () { | 20 | Effect.gen(function* () { |
| 18 | const eventStore = yield* OrchestrationEventStore; | 21 | const eventStore = yield* OrchestrationEventStore; |
| 19 | const sql = yield* SqlClient.SqlClient; | 22 | const sql = yield* SqlClient.SqlClient; |
| 20 | const now = new Date().toISOString(); | 23 | const now = "2026-01-01T00:00:00.000Z"; |
| 21 | 24 | ||
| 22 | const appended = yield* eventStore.append({ | 25 | const appended = yield* eventStore.append({ |
| 23 | type: "project.created", | 26 | type: "project.created", |
| ... | @@ -69,7 +72,7 @@ layer("OrchestrationEventStore", (it) => { | ... | @@ -69,7 +72,7 @@ layer("OrchestrationEventStore", (it) => { |
| 69 | Effect.gen(function* () { | 72 | Effect.gen(function* () { |
| 70 | const eventStore = yield* OrchestrationEventStore; | 73 | const eventStore = yield* OrchestrationEventStore; |
| 71 | const sql = yield* SqlClient.SqlClient; | 74 | const sql = yield* SqlClient.SqlClient; |
| 72 | const now = new Date().toISOString(); | 75 | const now = "2026-01-01T00:00:00.000Z"; |
| 73 | 76 | ||
| 74 | yield* sql` | 77 | yield* sql` |
| 75 | INSERT INTO orchestration_events ( | 78 | INSERT INTO orchestration_events ( |
apps/server/src/persistence/Layers/OrchestrationEventStore.ts+4-1| ... | @@ -13,7 +13,10 @@ import { | ... | @@ -13,7 +13,10 @@ import { |
| 13 | } from "@t3tools/contracts"; | 13 | } from "@t3tools/contracts"; |
| 14 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 14 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 15 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 15 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 16 | import { Effect, Layer, Schema, Stream } from "effect"; | 16 | import * as Effect from "effect/Effect"; |
| 17 | import * as Layer from "effect/Layer"; | ||
| 18 | import * as Schema from "effect/Schema"; | ||
| 19 | import * as Stream from "effect/Stream"; | ||
| 17 | 20 | ||
| 18 | import { | 21 | import { |
| 19 | toPersistenceDecodeError, | 22 | toPersistenceDecodeError, |
apps/server/src/persistence/Layers/ProjectionCheckpoints.ts+5-1| ... | @@ -1,7 +1,11 @@ | ... | @@ -1,7 +1,11 @@ |
| 1 | import { OrchestrationCheckpointFile } from "@t3tools/contracts"; | 1 | import { OrchestrationCheckpointFile } from "@t3tools/contracts"; |
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 4 | import { Effect, Layer, Option, Schema, Struct } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 8 | import * as Struct from "effect/Struct"; | ||
| 5 | 9 | ||
| 6 | import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; | 10 | import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; |
| 7 | import { | 11 | import { |
apps/server/src/persistence/Layers/ProjectionPendingApprovals.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { Effect, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 4 | 5 | ||
| 5 | import { toPersistenceSqlError } from "../Errors.ts"; | 6 | import { toPersistenceSqlError } from "../Errors.ts"; |
| 6 | import { | 7 | import { |
apps/server/src/persistence/Layers/ProjectionProjects.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { Effect, Layer, Schema, Struct } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 6 | import * as Struct from "effect/Struct"; | ||
| 4 | 7 | ||
| 5 | import { ModelSelection, ProjectScript } from "@t3tools/contracts"; | 8 | import { ModelSelection, ProjectScript } from "@t3tools/contracts"; |
| 6 | import { toPersistenceSqlError } from "../Errors.ts"; | 9 | import { toPersistenceSqlError } from "../Errors.ts"; |
apps/server/src/persistence/Layers/ProjectionRepositories.test.ts+7-3| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import { ProjectId, ThreadId, ProviderInstanceId } from "@t3tools/contracts"; | 1 | import { ProjectId, ThreadId, ProviderInstanceId } from "@t3tools/contracts"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { Effect, Layer, Option } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 6 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 5 | 7 | ||
| 6 | import { SqlitePersistenceMemory } from "./Sqlite.ts"; | 8 | import { SqlitePersistenceMemory } from "./Sqlite.ts"; |
| ... | @@ -46,11 +48,12 @@ projectionRepositoriesLayer("Projection repositories", (it) => { | ... | @@ -46,11 +48,12 @@ projectionRepositoriesLayer("Projection repositories", (it) => { |
| 46 | `; | 48 | `; |
| 47 | const row = rows[0]; | 49 | const row = rows[0]; |
| 48 | if (!row) { | 50 | if (!row) { |
| 49 | return yield* Effect.fail(new Error("Expected projection_projects row to exist.")); | 51 | return yield* Effect.die("Expected projection_projects row to exist."); |
| 50 | } | 52 | } |
| 51 | 53 | ||
| 52 | assert.strictEqual( | 54 | assert.strictEqual( |
| 53 | row.defaultModelSelection, | 55 | row.defaultModelSelection, |
| 56 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 54 | JSON.stringify({ | 57 | JSON.stringify({ |
| 55 | instanceId: ProviderInstanceId.make("codex"), | 58 | instanceId: ProviderInstanceId.make("codex"), |
| 56 | model: "gpt-5.4", | 59 | model: "gpt-5.4", |
| ... | @@ -104,11 +107,12 @@ projectionRepositoriesLayer("Projection repositories", (it) => { | ... | @@ -104,11 +107,12 @@ projectionRepositoriesLayer("Projection repositories", (it) => { |
| 104 | `; | 107 | `; |
| 105 | const row = rows[0]; | 108 | const row = rows[0]; |
| 106 | if (!row) { | 109 | if (!row) { |
| 107 | return yield* Effect.fail(new Error("Expected projection_threads row to exist.")); | 110 | return yield* Effect.die("Expected projection_threads row to exist."); |
| 108 | } | 111 | } |
| 109 | 112 | ||
| 110 | assert.strictEqual( | 113 | assert.strictEqual( |
| 111 | row.modelSelection, | 114 | row.modelSelection, |
| 115 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 112 | JSON.stringify({ | 116 | JSON.stringify({ |
| 113 | instanceId: ProviderInstanceId.make("claudeAgent"), | 117 | instanceId: ProviderInstanceId.make("claudeAgent"), |
| 114 | model: "claude-opus-4-6", | 118 | model: "claude-opus-4-6", |
apps/server/src/persistence/Layers/ProjectionState.ts+3-1| ... | @@ -1,7 +1,9 @@ | ... | @@ -1,7 +1,9 @@ |
| 1 | import { NonNegativeInt } from "@t3tools/contracts"; | 1 | import { NonNegativeInt } from "@t3tools/contracts"; |
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 4 | import { Effect, Layer, Schema } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 5 | 7 | ||
| 6 | import { toPersistenceSqlError } from "../Errors.ts"; | 8 | import { toPersistenceSqlError } from "../Errors.ts"; |
| 7 | 9 |
apps/server/src/persistence/Layers/ProjectionThreadActivities.ts+4-1| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { NonNegativeInt } from "@t3tools/contracts"; | 3 | import { NonNegativeInt } from "@t3tools/contracts"; |
| 4 | import { Effect, Layer, Schema, Struct } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 7 | import * as Struct from "effect/Struct"; | ||
| 5 | 8 | ||
| 6 | import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; | 9 | import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; |
| 7 | 10 |
apps/server/src/persistence/Layers/ProjectionThreadMessages.test.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import { MessageId, ThreadId } from "@t3tools/contracts"; | 1 | import { MessageId, ThreadId } from "@t3tools/contracts"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { Effect, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 4 | 5 | ||
| 5 | import { ProjectionThreadMessageRepository } from "../Services/ProjectionThreadMessages.ts"; | 6 | import { ProjectionThreadMessageRepository } from "../Services/ProjectionThreadMessages.ts"; |
| 6 | import { ProjectionThreadMessageRepositoryLive } from "./ProjectionThreadMessages.ts"; | 7 | import { ProjectionThreadMessageRepositoryLive } from "./ProjectionThreadMessages.ts"; |
apps/server/src/persistence/Layers/ProjectionThreadMessages.ts+5-1| ... | @@ -1,6 +1,10 @@ | ... | @@ -1,6 +1,10 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { Effect, Layer, Option, Schema, Struct } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 7 | import * as Struct from "effect/Struct"; | ||
| 4 | import { ChatAttachment } from "@t3tools/contracts"; | 8 | import { ChatAttachment } from "@t3tools/contracts"; |
| 5 | 9 | ||
| 6 | import { toPersistenceSqlError } from "../Errors.ts"; | 10 | import { toPersistenceSqlError } from "../Errors.ts"; |
apps/server/src/persistence/Layers/ProjectionThreadProposedPlans.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Effect, Layer } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 4 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 4 | 5 |
apps/server/src/persistence/Layers/ProjectionThreadSessions.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { Effect, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 4 | 5 | ||
| 5 | import { toPersistenceSqlError } from "../Errors.ts"; | 6 | import { toPersistenceSqlError } from "../Errors.ts"; |
| 6 | 7 |
apps/server/src/persistence/Layers/ProjectionThreads.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 1 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 2 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 3 | import { Effect, Layer, Schema, Struct } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 6 | import * as Struct from "effect/Struct"; | ||
| 4 | 7 | ||
| 5 | import { toPersistenceSqlError } from "../Errors.ts"; | 8 | import { toPersistenceSqlError } from "../Errors.ts"; |
| 6 | import { | 9 | import { |
apps/server/src/persistence/Layers/ProjectionTurns.ts+5-1| ... | @@ -1,7 +1,11 @@ | ... | @@ -1,7 +1,11 @@ |
| 1 | import { OrchestrationCheckpointFile } from "@t3tools/contracts"; | 1 | import { OrchestrationCheckpointFile } from "@t3tools/contracts"; |
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 4 | import { Effect, Layer, Option, Schema, Struct } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 8 | import * as Struct from "effect/Struct"; | ||
| 5 | 9 | ||
| 6 | import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; | 10 | import { toPersistenceDecodeError, toPersistenceSqlError } from "../Errors.ts"; |
| 7 | import { | 11 | import { |
apps/server/src/persistence/Layers/ProviderSessionRuntime.ts+5-1| ... | @@ -1,7 +1,11 @@ | ... | @@ -1,7 +1,11 @@ |
| 1 | import { ThreadId } from "@t3tools/contracts"; | 1 | import { ThreadId } from "@t3tools/contracts"; |
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; | 3 | import * as SqlSchema from "effect/unstable/sql/SqlSchema"; |
| 4 | import { Effect, Layer, Option, Schema, Struct } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 8 | import * as Struct from "effect/Struct"; | ||
| 5 | 9 | ||
| 6 | import { | 10 | import { |
| 7 | toPersistenceDecodeError, | 11 | toPersistenceDecodeError, |
apps/server/src/persistence/Layers/Sqlite.ts+4-1| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | import { Effect, Layer, FileSystem, Path } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as FileSystem from "effect/FileSystem"; | ||
| 4 | import * as Path from "effect/Path"; | ||
| 2 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 5 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 3 | 6 | ||
| 4 | import { runMigrations } from "../Migrations.ts"; | 7 | import { runMigrations } from "../Migrations.ts"; |
apps/server/src/persistence/Migrations/016_CanonicalizeModelSelections.test.ts+9-8| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 5 | ||
| 5 | import { runMigrations } from "../Migrations.ts"; | 6 | import { runMigrations } from "../Migrations.ts"; |
| ... | @@ -264,7 +265,7 @@ layer("016_CanonicalizeModelSelections", (it) => { | ... | @@ -264,7 +265,7 @@ layer("016_CanonicalizeModelSelections", (it) => { |
| 264 | FROM orchestration_events | 265 | FROM orchestration_events |
| 265 | ORDER BY rowid ASC | 266 | ORDER BY rowid ASC |
| 266 | `; | 267 | `; |
| 267 | 268 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 268 | assert.deepStrictEqual(JSON.parse(eventRows[0]!.payloadJson), { | 269 | assert.deepStrictEqual(JSON.parse(eventRows[0]!.payloadJson), { |
| 269 | projectId: "project-1", | 270 | projectId: "project-1", |
| 270 | title: "Project", | 271 | title: "Project", |
| ... | @@ -280,7 +281,7 @@ layer("016_CanonicalizeModelSelections", (it) => { | ... | @@ -280,7 +281,7 @@ layer("016_CanonicalizeModelSelections", (it) => { |
| 280 | createdAt: "2026-01-01T00:00:00.000Z", | 281 | createdAt: "2026-01-01T00:00:00.000Z", |
| 281 | updatedAt: "2026-01-01T00:00:00.000Z", | 282 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 282 | }); | 283 | }); |
| 283 | 284 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 284 | assert.deepStrictEqual(JSON.parse(eventRows[1]!.payloadJson), { | 285 | assert.deepStrictEqual(JSON.parse(eventRows[1]!.payloadJson), { |
| 285 | projectId: "project-2", | 286 | projectId: "project-2", |
| 286 | title: "Fallback Project", | 287 | title: "Fallback Project", |
| ... | @@ -296,7 +297,7 @@ layer("016_CanonicalizeModelSelections", (it) => { | ... | @@ -296,7 +297,7 @@ layer("016_CanonicalizeModelSelections", (it) => { |
| 296 | createdAt: "2026-01-01T00:00:00.000Z", | 297 | createdAt: "2026-01-01T00:00:00.000Z", |
| 297 | updatedAt: "2026-01-01T00:00:00.000Z", | 298 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 298 | }); | 299 | }); |
| 299 | 300 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 300 | assert.deepStrictEqual(JSON.parse(eventRows[2]!.payloadJson), { | 301 | assert.deepStrictEqual(JSON.parse(eventRows[2]!.payloadJson), { |
| 301 | projectId: "project-3", | 302 | projectId: "project-3", |
| 302 | title: "Null Model Project", | 303 | title: "Null Model Project", |
| ... | @@ -306,7 +307,7 @@ layer("016_CanonicalizeModelSelections", (it) => { | ... | @@ -306,7 +307,7 @@ layer("016_CanonicalizeModelSelections", (it) => { |
| 306 | createdAt: "2026-01-01T00:00:00.000Z", | 307 | createdAt: "2026-01-01T00:00:00.000Z", |
| 307 | updatedAt: "2026-01-01T00:00:00.000Z", | 308 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 308 | }); | 309 | }); |
| 309 | 310 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 310 | assert.deepStrictEqual(JSON.parse(eventRows[3]!.payloadJson), { | 311 | assert.deepStrictEqual(JSON.parse(eventRows[3]!.payloadJson), { |
| 311 | threadId: "thread-1", | 312 | threadId: "thread-1", |
| 312 | projectId: "project-1", | 313 | projectId: "project-1", |
| ... | @@ -326,7 +327,7 @@ layer("016_CanonicalizeModelSelections", (it) => { | ... | @@ -326,7 +327,7 @@ layer("016_CanonicalizeModelSelections", (it) => { |
| 326 | createdAt: "2026-01-01T00:00:00.000Z", | 327 | createdAt: "2026-01-01T00:00:00.000Z", |
| 327 | updatedAt: "2026-01-01T00:00:00.000Z", | 328 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 328 | }); | 329 | }); |
| 329 | 330 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 330 | assert.deepStrictEqual(JSON.parse(eventRows[4]!.payloadJson), { | 331 | assert.deepStrictEqual(JSON.parse(eventRows[4]!.payloadJson), { |
| 331 | threadId: "thread-2", | 332 | threadId: "thread-2", |
| 332 | projectId: "project-1", | 333 | projectId: "project-1", |
| ... | @@ -345,7 +346,7 @@ layer("016_CanonicalizeModelSelections", (it) => { | ... | @@ -345,7 +346,7 @@ layer("016_CanonicalizeModelSelections", (it) => { |
| 345 | createdAt: "2026-01-01T00:00:00.000Z", | 346 | createdAt: "2026-01-01T00:00:00.000Z", |
| 346 | updatedAt: "2026-01-01T00:00:00.000Z", | 347 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 347 | }); | 348 | }); |
| 348 | 349 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 349 | assert.deepStrictEqual(JSON.parse(eventRows[5]!.payloadJson), { | 350 | assert.deepStrictEqual(JSON.parse(eventRows[5]!.payloadJson), { |
| 350 | threadId: "thread-1", | 351 | threadId: "thread-1", |
| 351 | turnId: "turn-1", | 352 | turnId: "turn-1", |
| ... | @@ -359,7 +360,7 @@ layer("016_CanonicalizeModelSelections", (it) => { | ... | @@ -359,7 +360,7 @@ layer("016_CanonicalizeModelSelections", (it) => { |
| 359 | }, | 360 | }, |
| 360 | deliveryMode: "buffered", | 361 | deliveryMode: "buffered", |
| 361 | }); | 362 | }); |
| 362 | 363 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 363 | assert.deepStrictEqual(JSON.parse(eventRows[6]!.payloadJson), { | 364 | assert.deepStrictEqual(JSON.parse(eventRows[6]!.payloadJson), { |
| 364 | threadId: "thread-3", | 365 | threadId: "thread-3", |
| 365 | projectId: "project-1", | 366 | projectId: "project-1", |
apps/server/src/persistence/Migrations/019_ProjectionSnapshotLookupIndexes.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 5 | ||
| 5 | import { runMigrations } from "../Migrations.ts"; | 6 | import { runMigrations } from "../Migrations.ts"; |
apps/server/src/persistence/Migrations/024_BackfillProjectionThreadShellSummary.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 5 | ||
| 5 | import { runMigrations } from "../Migrations.ts"; | 6 | import { runMigrations } from "../Migrations.ts"; |
apps/server/src/persistence/Migrations/025_CleanupInvalidProjectionPendingApprovals.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 5 | ||
| 5 | import { runMigrations } from "../Migrations.ts"; | 6 | import { runMigrations } from "../Migrations.ts"; |
apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 5 | ||
| 5 | import { runMigrations } from "../Migrations.ts"; | 6 | import { runMigrations } from "../Migrations.ts"; |
apps/server/src/persistence/Migrations/027_028_ProviderInstanceIdColumns.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 5 | ||
| 5 | import { runMigrations } from "../Migrations.ts"; | 6 | import { runMigrations } from "../Migrations.ts"; |
apps/server/src/persistence/Migrations/029_ProjectionThreadDetailOrderingIndexes.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 4 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 5 | ||
| 5 | import { runMigrations } from "../Migrations.ts"; | 6 | import { runMigrations } from "../Migrations.ts"; |
apps/server/src/persistence/NodeSqliteClient.test.ts+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 3 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 4 | 4 | ||
| 5 | import * as SqliteClient from "./NodeSqliteClient.ts"; | 5 | import * as SqliteClient from "./NodeSqliteClient.ts"; |
apps/server/src/persistence/Services/AuthPairingLinks.ts+4-2| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { Option, Schema, Context } from "effect"; | 1 | import * as Option from "effect/Option"; |
| 2 | import type { Effect } from "effect"; | 2 | import * as Schema from "effect/Schema"; |
| 3 | import * as Context from "effect/Context"; | ||
| 4 | import type * as Effect from "effect/Effect"; | ||
| 3 | 5 | ||
| 4 | import type { AuthPairingLinkRepositoryError } from "../Errors.ts"; | 6 | import type { AuthPairingLinkRepositoryError } from "../Errors.ts"; |
| 5 | 7 |
apps/server/src/persistence/Services/AuthSessions.ts+4-2| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import { AuthClientMetadataDeviceType, AuthSessionId } from "@t3tools/contracts"; | 1 | import { AuthClientMetadataDeviceType, AuthSessionId } from "@t3tools/contracts"; |
| 2 | import { Option, Schema, Context } from "effect"; | 2 | import * as Option from "effect/Option"; |
| 3 | import type { Effect } from "effect"; | 3 | import * as Schema from "effect/Schema"; |
| 4 | import * as Context from "effect/Context"; | ||
| 5 | import type * as Effect from "effect/Effect"; | ||
| 4 | 6 | ||
| 5 | import type { AuthSessionRepositoryError } from "../Errors.ts"; | 7 | import type { AuthSessionRepositoryError } from "../Errors.ts"; |
| 6 | 8 |
apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts+4-2| ... | @@ -15,8 +15,10 @@ import { | ... | @@ -15,8 +15,10 @@ import { |
| 15 | ProjectId, | 15 | ProjectId, |
| 16 | ThreadId, | 16 | ThreadId, |
| 17 | } from "@t3tools/contracts"; | 17 | } from "@t3tools/contracts"; |
| 18 | import { Option, Schema, Context } from "effect"; | 18 | import * as Option from "effect/Option"; |
| 19 | import type { Effect } from "effect"; | 19 | import * as Schema from "effect/Schema"; |
| 20 | import * as Context from "effect/Context"; | ||
| 21 | import type * as Effect from "effect/Effect"; | ||
| 20 | 22 | ||
| 21 | import type { OrchestrationCommandReceiptRepositoryError } from "../Errors.ts"; | 23 | import type { OrchestrationCommandReceiptRepositoryError } from "../Errors.ts"; |
| 22 | 24 |
apps/server/src/persistence/Services/OrchestrationEventStore.ts+3-2| ... | @@ -10,8 +10,9 @@ | ... | @@ -10,8 +10,9 @@ |
| 10 | * @module OrchestrationEventStore | 10 | * @module OrchestrationEventStore |
| 11 | */ | 11 | */ |
| 12 | import { OrchestrationEvent } from "@t3tools/contracts"; | 12 | import { OrchestrationEvent } from "@t3tools/contracts"; |
| 13 | import { Context } from "effect"; | 13 | import * as Context from "effect/Context"; |
| 14 | import type { Effect, Stream } from "effect"; | 14 | import type * as Effect from "effect/Effect"; |
| 15 | import type * as Stream from "effect/Stream"; | ||
| 15 | 16 | ||
| 16 | import type { OrchestrationEventStoreError } from "../Errors.ts"; | 17 | import type { OrchestrationEventStoreError } from "../Errors.ts"; |
| 17 | 18 |
apps/server/src/persistence/Services/ProjectionCheckpoints.ts+4-2| ... | @@ -16,8 +16,10 @@ import { | ... | @@ -16,8 +16,10 @@ import { |
| 16 | ThreadId, | 16 | ThreadId, |
| 17 | TurnId, | 17 | TurnId, |
| 18 | } from "@t3tools/contracts"; | 18 | } from "@t3tools/contracts"; |
| 19 | import { Option, Context, Schema } from "effect"; | 19 | import * as Option from "effect/Option"; |
| 20 | import type { Effect } from "effect"; | 20 | import * as Context from "effect/Context"; |
| 21 | import * as Schema from "effect/Schema"; | ||
| 22 | import type * as Effect from "effect/Effect"; | ||
| 21 | 23 | ||
| 22 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 24 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 23 | 25 |
apps/server/src/persistence/Services/ProjectionPendingApprovals.ts+4-2| ... | @@ -14,8 +14,10 @@ import { | ... | @@ -14,8 +14,10 @@ import { |
| 14 | ThreadId, | 14 | ThreadId, |
| 15 | TurnId, | 15 | TurnId, |
| 16 | } from "@t3tools/contracts"; | 16 | } from "@t3tools/contracts"; |
| 17 | import { Option, Schema, Context } from "effect"; | 17 | import * as Option from "effect/Option"; |
| 18 | import type { Effect } from "effect"; | 18 | import * as Schema from "effect/Schema"; |
| 19 | import * as Context from "effect/Context"; | ||
| 20 | import type * as Effect from "effect/Effect"; | ||
| 19 | 21 | ||
| 20 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 22 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 21 | 23 |
apps/server/src/persistence/Services/ProjectionProjects.ts+4-2| ... | @@ -7,8 +7,10 @@ | ... | @@ -7,8 +7,10 @@ |
| 7 | * @module ProjectionProjectRepository | 7 | * @module ProjectionProjectRepository |
| 8 | */ | 8 | */ |
| 9 | import { IsoDateTime, ModelSelection, ProjectId, ProjectScript } from "@t3tools/contracts"; | 9 | import { IsoDateTime, ModelSelection, ProjectId, ProjectScript } from "@t3tools/contracts"; |
| 10 | import { Option, Schema, Context } from "effect"; | 10 | import * as Option from "effect/Option"; |
| 11 | import type { Effect } from "effect"; | 11 | import * as Schema from "effect/Schema"; |
| 12 | import * as Context from "effect/Context"; | ||
| 13 | import type * as Effect from "effect/Effect"; | ||
| 12 | 14 | ||
| 13 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 15 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 14 | 16 |
apps/server/src/persistence/Services/ProjectionState.ts+4-2| ... | @@ -7,8 +7,10 @@ | ... | @@ -7,8 +7,10 @@ |
| 7 | * @module ProjectionStateRepository | 7 | * @module ProjectionStateRepository |
| 8 | */ | 8 | */ |
| 9 | import { IsoDateTime, NonNegativeInt } from "@t3tools/contracts"; | 9 | import { IsoDateTime, NonNegativeInt } from "@t3tools/contracts"; |
| 10 | import { Option, Schema, Context } from "effect"; | 10 | import * as Option from "effect/Option"; |
| 11 | import type { Effect } from "effect"; | 11 | import * as Schema from "effect/Schema"; |
| 12 | import * as Context from "effect/Context"; | ||
| 13 | import type * as Effect from "effect/Effect"; | ||
| 12 | 14 | ||
| 13 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 15 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 14 | 16 |
apps/server/src/persistence/Services/ProjectionThreadActivities.ts+3-2| ... | @@ -14,8 +14,9 @@ import { | ... | @@ -14,8 +14,9 @@ import { |
| 14 | ThreadId, | 14 | ThreadId, |
| 15 | TurnId, | 15 | TurnId, |
| 16 | } from "@t3tools/contracts"; | 16 | } from "@t3tools/contracts"; |
| 17 | import { Schema, Context } from "effect"; | 17 | import * as Schema from "effect/Schema"; |
| 18 | import type { Effect } from "effect"; | 18 | import * as Context from "effect/Context"; |
| 19 | import type * as Effect from "effect/Effect"; | ||
| 19 | 20 | ||
| 20 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 21 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 21 | 22 |
apps/server/src/persistence/Services/ProjectionThreadMessages.ts+4-3| ... | @@ -14,9 +14,10 @@ import { | ... | @@ -14,9 +14,10 @@ import { |
| 14 | TurnId, | 14 | TurnId, |
| 15 | IsoDateTime, | 15 | IsoDateTime, |
| 16 | } from "@t3tools/contracts"; | 16 | } from "@t3tools/contracts"; |
| 17 | import { Schema, Context } from "effect"; | 17 | import * as Schema from "effect/Schema"; |
| 18 | import type { Option } from "effect"; | 18 | import * as Context from "effect/Context"; |
| 19 | import type { Effect } from "effect"; | 19 | import type * as Option from "effect/Option"; |
| 20 | import type * as Effect from "effect/Effect"; | ||
| 20 | 21 | ||
| 21 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 22 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 22 | 23 |
apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts+3-2| ... | @@ -5,8 +5,9 @@ import { | ... | @@ -5,8 +5,9 @@ import { |
| 5 | TrimmedNonEmptyString, | 5 | TrimmedNonEmptyString, |
| 6 | TurnId, | 6 | TurnId, |
| 7 | } from "@t3tools/contracts"; | 7 | } from "@t3tools/contracts"; |
| 8 | import { Schema, Context } from "effect"; | 8 | import * as Schema from "effect/Schema"; |
| 9 | import type { Effect } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type * as Effect from "effect/Effect"; | ||
| 10 | 11 | ||
| 11 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 12 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 12 | 13 |
apps/server/src/persistence/Services/ProjectionThreadSessions.ts+4-2| ... | @@ -14,8 +14,10 @@ import { | ... | @@ -14,8 +14,10 @@ import { |
| 14 | ThreadId, | 14 | ThreadId, |
| 15 | TurnId, | 15 | TurnId, |
| 16 | } from "@t3tools/contracts"; | 16 | } from "@t3tools/contracts"; |
| 17 | import { Option, Schema, Context } from "effect"; | 17 | import * as Option from "effect/Option"; |
| 18 | import type { Effect } from "effect"; | 18 | import * as Schema from "effect/Schema"; |
| 19 | import * as Context from "effect/Context"; | ||
| 20 | import type * as Effect from "effect/Effect"; | ||
| 19 | 21 | ||
| 20 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 22 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 21 | 23 |
apps/server/src/persistence/Services/ProjectionThreads.ts+4-2| ... | @@ -16,8 +16,10 @@ import { | ... | @@ -16,8 +16,10 @@ import { |
| 16 | ThreadId, | 16 | ThreadId, |
| 17 | TurnId, | 17 | TurnId, |
| 18 | } from "@t3tools/contracts"; | 18 | } from "@t3tools/contracts"; |
| 19 | import { Option, Schema, Context } from "effect"; | 19 | import * as Option from "effect/Option"; |
| 20 | import type { Effect } from "effect"; | 20 | import * as Schema from "effect/Schema"; |
| 21 | import * as Context from "effect/Context"; | ||
| 22 | import type * as Effect from "effect/Effect"; | ||
| 21 | 23 | ||
| 22 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 24 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 23 | 25 |
apps/server/src/persistence/Services/ProjectionTurns.ts+4-2| ... | @@ -17,8 +17,10 @@ import { | ... | @@ -17,8 +17,10 @@ import { |
| 17 | ThreadId, | 17 | ThreadId, |
| 18 | TurnId, | 18 | TurnId, |
| 19 | } from "@t3tools/contracts"; | 19 | } from "@t3tools/contracts"; |
| 20 | import { Option, Schema, Context } from "effect"; | 20 | import * as Option from "effect/Option"; |
| 21 | import type { Effect } from "effect"; | 21 | import * as Schema from "effect/Schema"; |
| 22 | import * as Context from "effect/Context"; | ||
| 23 | import type * as Effect from "effect/Effect"; | ||
| 22 | 24 | ||
| 23 | import type { ProjectionRepositoryError } from "../Errors.ts"; | 25 | import type { ProjectionRepositoryError } from "../Errors.ts"; |
| 24 | 26 |
apps/server/src/persistence/Services/ProviderSessionRuntime.ts+4-2| ... | @@ -12,8 +12,10 @@ import { | ... | @@ -12,8 +12,10 @@ import { |
| 12 | RuntimeMode, | 12 | RuntimeMode, |
| 13 | ThreadId, | 13 | ThreadId, |
| 14 | } from "@t3tools/contracts"; | 14 | } from "@t3tools/contracts"; |
| 15 | import { Option, Schema, Context } from "effect"; | 15 | import * as Option from "effect/Option"; |
| 16 | import type { Effect } from "effect"; | 16 | import * as Schema from "effect/Schema"; |
| 17 | import * as Context from "effect/Context"; | ||
| 18 | import type * as Effect from "effect/Effect"; | ||
| 17 | 19 | ||
| 18 | import type { ProviderSessionRuntimeRepositoryError } from "../Errors.ts"; | 20 | import type { ProviderSessionRuntimeRepositoryError } from "../Errors.ts"; |
| 19 | 21 |
apps/server/src/processRunner.ts+9-6| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { type ChildProcess as ChildProcessHandle, spawn, spawnSync } from "node:child_process"; | 2 | import { type ChildProcess as ChildProcessHandle, spawn, spawnSync } from "node:child_process"; |
| 2 | 3 | import * as NodeTimers from "node:timers"; | |
| 3 | export interface ProcessRunOptions { | 4 | export interface ProcessRunOptions { |
| 4 | cwd?: string | undefined; | 5 | cwd?: string | undefined; |
| 5 | timeoutMs?: number | undefined; | 6 | timeoutMs?: number | undefined; |
| ... | @@ -163,12 +164,14 @@ export async function runProcess( | ... | @@ -163,12 +164,14 @@ export async function runProcess( |
| 163 | let stderrTruncated = false; | 164 | let stderrTruncated = false; |
| 164 | let timedOut = false; | 165 | let timedOut = false; |
| 165 | let settled = false; | 166 | let settled = false; |
| 166 | let forceKillTimer: ReturnType<typeof setTimeout> | null = null; | 167 | let forceKillTimer: ReturnType<typeof NodeTimers.setTimeout> | null = null; |
| 167 | 168 | ||
| 168 | const timeoutTimer = setTimeout(() => { | 169 | // @effect-diagnostics-next-line globalTimers:off - Promise/child_process boundary; moving this runner to Effect timers is a separate refactor. |
| 170 | const timeoutTimer = NodeTimers.setTimeout(() => { | ||
| 169 | timedOut = true; | 171 | timedOut = true; |
| 170 | killChild(child, "SIGTERM"); | 172 | killChild(child, "SIGTERM"); |
| 171 | forceKillTimer = setTimeout(() => { | 173 | // @effect-diagnostics-next-line globalTimers:off - Promise/child_process boundary; see timeout timer above. |
| 174 | forceKillTimer = NodeTimers.setTimeout(() => { | ||
| 172 | killChild(child, "SIGKILL"); | 175 | killChild(child, "SIGKILL"); |
| 173 | }, 1_000); | 176 | }, 1_000); |
| 174 | }, timeoutMs); | 177 | }, timeoutMs); |
| ... | @@ -176,9 +179,9 @@ export async function runProcess( | ... | @@ -176,9 +179,9 @@ export async function runProcess( |
| 176 | const finalize = (callback: () => void): void => { | 179 | const finalize = (callback: () => void): void => { |
| 177 | if (settled) return; | 180 | if (settled) return; |
| 178 | settled = true; | 181 | settled = true; |
| 179 | clearTimeout(timeoutTimer); | 182 | NodeTimers.clearTimeout(timeoutTimer); |
| 180 | if (forceKillTimer) { | 183 | if (forceKillTimer) { |
| 181 | clearTimeout(forceKillTimer); | 184 | NodeTimers.clearTimeout(forceKillTimer); |
| 182 | } | 185 | } |
| 183 | callback(); | 186 | callback(); |
| 184 | }; | 187 | }; |
apps/server/src/project/Layers/ProjectFaviconResolver.test.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { it, describe, expect } from "@effect/vitest"; | 2 | import { it, describe, expect } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Layer, Path } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 4 | 7 | ||
| 5 | import { ProjectFaviconResolver } from "../Services/ProjectFaviconResolver.ts"; | 8 | import { ProjectFaviconResolver } from "../Services/ProjectFaviconResolver.ts"; |
| 6 | import { ProjectFaviconResolverLive } from "./ProjectFaviconResolver.ts"; | 9 | import { ProjectFaviconResolverLive } from "./ProjectFaviconResolver.ts"; |
apps/server/src/project/Layers/ProjectFaviconResolver.ts+4-1| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | import { Effect, FileSystem, Layer, Path } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Path from "effect/Path"; | ||
| 2 | 5 | ||
| 3 | import { | 6 | import { |
| 4 | ProjectFaviconResolver, | 7 | ProjectFaviconResolver, |
apps/server/src/project/Layers/ProjectSetupScriptRunner.test.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { ProjectId, type OrchestrationProject } from "@t3tools/contracts"; | 1 | import { ProjectId, type OrchestrationProject } from "@t3tools/contracts"; |
| 2 | import { Effect, Layer, Option } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 3 | import { describe, expect, it, vi } from "vitest"; | 5 | import { describe, expect, it, vi } from "vitest"; |
| 4 | 6 | ||
| 5 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; | 7 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; |
apps/server/src/project/Layers/ProjectSetupScriptRunner.ts+27-3| ... | @@ -1,12 +1,15 @@ | ... | @@ -1,12 +1,15 @@ |
| 1 | import { ProjectId } from "@t3tools/contracts"; | 1 | import { ProjectId } from "@t3tools/contracts"; |
| 2 | import { projectScriptRuntimeEnv, setupProjectScript } from "@t3tools/shared/projectScripts"; | 2 | import { projectScriptRuntimeEnv, setupProjectScript } from "@t3tools/shared/projectScripts"; |
| 3 | import { Effect, Layer, Option } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 4 | 6 | ||
| 5 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; | 7 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 6 | import { TerminalManager } from "../../terminal/Services/Manager.ts"; | 8 | import { TerminalManager } from "../../terminal/Services/Manager.ts"; |
| 7 | import { | 9 | import { |
| 8 | type ProjectSetupScriptRunnerShape, | 10 | type ProjectSetupScriptRunnerShape, |
| 9 | ProjectSetupScriptRunner, | 11 | ProjectSetupScriptRunner, |
| 12 | ProjectSetupScriptRunnerError, | ||
| 10 | } from "../Services/ProjectSetupScriptRunner.ts"; | 13 | } from "../Services/ProjectSetupScriptRunner.ts"; |
| 11 | 14 | ||
| 12 | const makeProjectSetupScriptRunner = Effect.gen(function* () { | 15 | const makeProjectSetupScriptRunner = Effect.gen(function* () { |
| ... | @@ -29,7 +32,9 @@ const makeProjectSetupScriptRunner = Effect.gen(function* () { | ... | @@ -29,7 +32,9 @@ const makeProjectSetupScriptRunner = Effect.gen(function* () { |
| 29 | null; | 32 | null; |
| 30 | 33 | ||
| 31 | if (!project) { | 34 | if (!project) { |
| 32 | return yield* Effect.fail(new Error("Project was not found for setup script execution.")); | 35 | return yield* new ProjectSetupScriptRunnerError({ |
| 36 | message: "Project was not found for setup script execution.", | ||
| 37 | }); | ||
| 33 | } | 38 | } |
| 34 | 39 | ||
| 35 | const script = setupProjectScript(project.scripts); | 40 | const script = setupProjectScript(project.scripts); |
| ... | @@ -66,7 +71,26 @@ const makeProjectSetupScriptRunner = Effect.gen(function* () { | ... | @@ -66,7 +71,26 @@ const makeProjectSetupScriptRunner = Effect.gen(function* () { |
| 66 | terminalId, | 71 | terminalId, |
| 67 | cwd, | 72 | cwd, |
| 68 | } as const; | 73 | } as const; |
| 69 | }); | 74 | }).pipe( |
| 75 | Effect.mapError((cause) => { | ||
| 76 | if ( | ||
| 77 | typeof cause === "object" && | ||
| 78 | cause !== null && | ||
| 79 | "_tag" in cause && | ||
| 80 | cause._tag === "ProjectSetupScriptRunnerError" | ||
| 81 | ) { | ||
| 82 | return cause as ProjectSetupScriptRunnerError; | ||
| 83 | } | ||
| 84 | const message = | ||
| 85 | typeof cause === "object" && | ||
| 86 | cause !== null && | ||
| 87 | "message" in cause && | ||
| 88 | typeof cause.message === "string" | ||
| 89 | ? cause.message | ||
| 90 | : String(cause); | ||
| 91 | return new ProjectSetupScriptRunnerError({ message }); | ||
| 92 | }), | ||
| 93 | ); | ||
| 70 | 94 | ||
| 71 | return { | 95 | return { |
| 72 | runForThread, | 96 | runForThread, |
apps/server/src/project/Layers/RepositoryIdentityResolver.test.ts+6-2| ... | @@ -1,9 +1,13 @@ | ... | @@ -1,9 +1,13 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { realpathSync } from "node:fs"; | 2 | import { realpathSync } from "node:fs"; |
| 2 | 3 | ||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { expect, it } from "@effect/vitest"; | 5 | import { expect, it } from "@effect/vitest"; |
| 5 | import { Duration, Effect, FileSystem, Layer } from "effect"; | 6 | import * as Duration from "effect/Duration"; |
| 6 | import { TestClock } from "effect/testing"; | 7 | import * as Effect from "effect/Effect"; |
| 8 | import * as FileSystem from "effect/FileSystem"; | ||
| 9 | import * as Layer from "effect/Layer"; | ||
| 10 | import * as TestClock from "effect/testing/TestClock"; | ||
| 7 | 11 | ||
| 8 | import { runProcess } from "../../processRunner.ts"; | 12 | import { runProcess } from "../../processRunner.ts"; |
| 9 | import { RepositoryIdentityResolver } from "../Services/RepositoryIdentityResolver.ts"; | 13 | import { RepositoryIdentityResolver } from "../Services/RepositoryIdentityResolver.ts"; |
apps/server/src/project/Layers/RepositoryIdentityResolver.ts+5-1| ... | @@ -1,5 +1,9 @@ | ... | @@ -1,5 +1,9 @@ |
| 1 | import type { RepositoryIdentity } from "@t3tools/contracts"; | 1 | import type { RepositoryIdentity } from "@t3tools/contracts"; |
| 2 | import { Cache, Duration, Effect, Exit, Layer } from "effect"; | 2 | import * as Cache from "effect/Cache"; |
| 3 | import * as Duration from "effect/Duration"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 3 | import { | 7 | import { |
| 4 | detectSourceControlProviderFromGitRemoteUrl, | 8 | detectSourceControlProviderFromGitRemoteUrl, |
| 5 | normalizeGitRemoteUrl, | 9 | normalizeGitRemoteUrl, |
apps/server/src/project/Services/ProjectFaviconResolver.ts+2-2| ... | @@ -6,8 +6,8 @@ | ... | @@ -6,8 +6,8 @@ |
| 6 | * | 6 | * |
| 7 | * @module ProjectFaviconResolver | 7 | * @module ProjectFaviconResolver |
| 8 | */ | 8 | */ |
| 9 | import { Context } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type { Effect } from "effect"; | 10 | import type * as Effect from "effect/Effect"; |
| 11 | 11 | ||
| 12 | /** | 12 | /** |
| 13 | * ProjectFaviconResolverShape - Service API for project favicon lookup. | 13 | * ProjectFaviconResolverShape - Service API for project favicon lookup. |
apps/server/src/project/Services/ProjectSetupScriptRunner.ts+10-3| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { Context } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import type { Effect } from "effect"; | 2 | import * as Data from "effect/Data"; |
| 3 | import type * as Effect from "effect/Effect"; | ||
| 3 | 4 | ||
| 4 | export interface ProjectSetupScriptRunnerResultNoScript { | 5 | export interface ProjectSetupScriptRunnerResultNoScript { |
| 5 | readonly status: "no-script"; | 6 | readonly status: "no-script"; |
| ... | @@ -25,10 +26,16 @@ export interface ProjectSetupScriptRunnerInput { | ... | @@ -25,10 +26,16 @@ export interface ProjectSetupScriptRunnerInput { |
| 25 | readonly preferredTerminalId?: string; | 26 | readonly preferredTerminalId?: string; |
| 26 | } | 27 | } |
| 27 | 28 | ||
| 29 | export class ProjectSetupScriptRunnerError extends Data.TaggedError( | ||
| 30 | "ProjectSetupScriptRunnerError", | ||
| 31 | )<{ | ||
| 32 | readonly message: string; | ||
| 33 | }> {} | ||
| 34 | |||
| 28 | export interface ProjectSetupScriptRunnerShape { | 35 | export interface ProjectSetupScriptRunnerShape { |
| 29 | readonly runForThread: ( | 36 | readonly runForThread: ( |
| 30 | input: ProjectSetupScriptRunnerInput, | 37 | input: ProjectSetupScriptRunnerInput, |
| 31 | ) => Effect.Effect<ProjectSetupScriptRunnerResult, Error>; | 38 | ) => Effect.Effect<ProjectSetupScriptRunnerResult, ProjectSetupScriptRunnerError>; |
| 32 | } | 39 | } |
| 33 | 40 | ||
| 34 | export class ProjectSetupScriptRunner extends Context.Service< | 41 | export class ProjectSetupScriptRunner extends Context.Service< |
apps/server/src/project/Services/RepositoryIdentityResolver.ts+2-2| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import type { RepositoryIdentity } from "@t3tools/contracts"; | 1 | import type { RepositoryIdentity } from "@t3tools/contracts"; |
| 2 | import { Context } from "effect"; | 2 | import * as Context from "effect/Context"; |
| 3 | import type { Effect } from "effect"; | 3 | import type * as Effect from "effect/Effect"; |
| 4 | 4 | ||
| 5 | export interface RepositoryIdentityResolverShape { | 5 | export interface RepositoryIdentityResolverShape { |
| 6 | readonly resolve: (cwd: string) => Effect.Effect<RepositoryIdentity | null>; | 6 | readonly resolve: (cwd: string) => Effect.Effect<RepositoryIdentity | null>; |
apps/server/src/provider/Drivers/ClaudeDriver.ts+9-2| ... | @@ -13,7 +13,13 @@ | ... | @@ -13,7 +13,13 @@ |
| 13 | * @module provider/Drivers/ClaudeDriver | 13 | * @module provider/Drivers/ClaudeDriver |
| 14 | */ | 14 | */ |
| 15 | import { ClaudeSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; | 15 | import { ClaudeSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; |
| 16 | import { Cache, Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; | 16 | import * as Cache from "effect/Cache"; |
| 17 | import * as Duration from "effect/Duration"; | ||
| 18 | import * as Effect from "effect/Effect"; | ||
| 19 | import * as FileSystem from "effect/FileSystem"; | ||
| 20 | import * as Path from "effect/Path"; | ||
| 21 | import * as Schema from "effect/Schema"; | ||
| 22 | import * as Stream from "effect/Stream"; | ||
| 17 | import { HttpClient } from "effect/unstable/http"; | 23 | import { HttpClient } from "effect/unstable/http"; |
| 18 | import { ChildProcessSpawner } from "effect/unstable/process"; | 24 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 19 | 25 | ||
| ... | @@ -159,7 +165,8 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { | ... | @@ -159,7 +165,8 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { |
| 159 | getSettings: Effect.succeed(effectiveConfig), | 165 | getSettings: Effect.succeed(effectiveConfig), |
| 160 | streamSettings: Stream.never, | 166 | streamSettings: Stream.never, |
| 161 | haveSettingsChanged: () => false, | 167 | haveSettingsChanged: () => false, |
| 162 | initialSnapshot: (settings) => stampIdentity(makePendingClaudeProvider(settings)), | 168 | initialSnapshot: (settings) => |
| 169 | makePendingClaudeProvider(settings).pipe(Effect.map(stampIdentity)), | ||
| 163 | checkProvider, | 170 | checkProvider, |
| 164 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | 171 | enrichSnapshot: ({ snapshot, publishSnapshot }) => |
| 165 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | 172 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( |
apps/server/src/provider/Drivers/ClaudeHome.test.ts+2-1| ... | @@ -2,7 +2,8 @@ import * as NodeOS from "node:os"; | ... | @@ -2,7 +2,8 @@ import * as NodeOS from "node:os"; |
| 2 | 2 | ||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { describe, expect, it } from "@effect/vitest"; | 4 | import { describe, expect, it } from "@effect/vitest"; |
| 5 | import { Effect, Path } from "effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Path from "effect/Path"; | ||
| 6 | 7 | ||
| 7 | import { | 8 | import { |
| 8 | makeClaudeCapabilitiesCacheKey, | 9 | makeClaudeCapabilitiesCacheKey, |
apps/server/src/provider/Drivers/ClaudeHome.ts+2-1| ... | @@ -1,7 +1,8 @@ | ... | @@ -1,7 +1,8 @@ |
| 1 | import * as NodeOS from "node:os"; | 1 | import * as NodeOS from "node:os"; |
| 2 | 2 | ||
| 3 | import type { ClaudeSettings } from "@t3tools/contracts"; | 3 | import type { ClaudeSettings } from "@t3tools/contracts"; |
| 4 | import { Effect, Path } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Path from "effect/Path"; | ||
| 5 | 6 | ||
| 6 | import { expandHomePath } from "../../pathExpansion.ts"; | 7 | import { expandHomePath } from "../../pathExpansion.ts"; |
| 7 | 8 |
apps/server/src/provider/Drivers/CodexDriver.ts+8-2| ... | @@ -22,7 +22,12 @@ | ... | @@ -22,7 +22,12 @@ |
| 22 | * @module provider/Drivers/CodexDriver | 22 | * @module provider/Drivers/CodexDriver |
| 23 | */ | 23 | */ |
| 24 | import { CodexSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; | 24 | import { CodexSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; |
| 25 | import { Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; | 25 | import * as Duration from "effect/Duration"; |
| 26 | import * as Effect from "effect/Effect"; | ||
| 27 | import * as FileSystem from "effect/FileSystem"; | ||
| 28 | import * as Path from "effect/Path"; | ||
| 29 | import * as Schema from "effect/Schema"; | ||
| 30 | import * as Stream from "effect/Stream"; | ||
| 26 | import { HttpClient } from "effect/unstable/http"; | 31 | import { HttpClient } from "effect/unstable/http"; |
| 27 | import { ChildProcessSpawner } from "effect/unstable/process"; | 32 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 28 | 33 | ||
| ... | @@ -160,7 +165,8 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { | ... | @@ -160,7 +165,8 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { |
| 160 | getSettings: Effect.succeed(effectiveConfig), | 165 | getSettings: Effect.succeed(effectiveConfig), |
| 161 | streamSettings: Stream.never, | 166 | streamSettings: Stream.never, |
| 162 | haveSettingsChanged: () => false, | 167 | haveSettingsChanged: () => false, |
| 163 | initialSnapshot: (settings) => stampIdentity(makePendingCodexProvider(settings)), | 168 | initialSnapshot: (settings) => |
| 169 | makePendingCodexProvider(settings).pipe(Effect.map(stampIdentity)), | ||
| 164 | checkProvider, | 170 | checkProvider, |
| 165 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | 171 | enrichSnapshot: ({ snapshot, publishSnapshot }) => |
| 166 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | 172 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( |
apps/server/src/provider/Drivers/CodexHomeLayout.test.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { describe, expect, it } from "@effect/vitest"; | 2 | import { describe, expect, it } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Path, Schema } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Path from "effect/Path"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 4 | 7 | ||
| 5 | import { CodexSettings } from "@t3tools/contracts"; | 8 | import { CodexSettings } from "@t3tools/contracts"; |
| 6 | import { | 9 | import { |
apps/server/src/provider/Drivers/CodexHomeLayout.ts+4-1| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | import * as NodeOS from "node:os"; | 1 | import * as NodeOS from "node:os"; |
| 2 | 2 | ||
| 3 | import { ProviderDriverKind, type CodexSettings } from "@t3tools/contracts"; | 3 | import { ProviderDriverKind, type CodexSettings } from "@t3tools/contracts"; |
| 4 | import { Effect, FileSystem, Path, Schema } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 5 | import * as PlatformError from "effect/PlatformError"; | 8 | import * as PlatformError from "effect/PlatformError"; |
| 6 | 9 | ||
| 7 | import { expandHomePath } from "../../pathExpansion.ts"; | 10 | import { expandHomePath } from "../../pathExpansion.ts"; |
apps/server/src/provider/Drivers/CursorDriver.ts+8-2| ... | @@ -13,7 +13,12 @@ | ... | @@ -13,7 +13,12 @@ |
| 13 | * @module provider/Drivers/CursorDriver | 13 | * @module provider/Drivers/CursorDriver |
| 14 | */ | 14 | */ |
| 15 | import { CursorSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; | 15 | import { CursorSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; |
| 16 | import { Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; | 16 | import * as Duration from "effect/Duration"; |
| 17 | import * as Effect from "effect/Effect"; | ||
| 18 | import * as FileSystem from "effect/FileSystem"; | ||
| 19 | import * as Path from "effect/Path"; | ||
| 20 | import * as Schema from "effect/Schema"; | ||
| 21 | import * as Stream from "effect/Stream"; | ||
| 17 | import { HttpClient } from "effect/unstable/http"; | 22 | import { HttpClient } from "effect/unstable/http"; |
| 18 | import { ChildProcessSpawner } from "effect/unstable/process"; | 23 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 19 | 24 | ||
| ... | @@ -128,7 +133,8 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { | ... | @@ -128,7 +133,8 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { |
| 128 | getSettings: Effect.succeed(effectiveConfig), | 133 | getSettings: Effect.succeed(effectiveConfig), |
| 129 | streamSettings: Stream.never, | 134 | streamSettings: Stream.never, |
| 130 | haveSettingsChanged: () => false, | 135 | haveSettingsChanged: () => false, |
| 131 | initialSnapshot: (settings) => stampIdentity(buildInitialCursorProviderSnapshot(settings)), | 136 | initialSnapshot: (settings) => |
| 137 | buildInitialCursorProviderSnapshot(settings).pipe(Effect.map(stampIdentity)), | ||
| 132 | checkProvider, | 138 | checkProvider, |
| 133 | // Preserve the background ACP model-capability probe that used to | 139 | // Preserve the background ACP model-capability probe that used to |
| 134 | // live on `CursorProviderLive`. Only fires when the snapshot reports | 140 | // live on `CursorProviderLive`. Only fires when the snapshot reports |
apps/server/src/provider/Drivers/OpenCodeDriver.ts+8-2| ... | @@ -13,7 +13,12 @@ | ... | @@ -13,7 +13,12 @@ |
| 13 | * @module provider/Drivers/OpenCodeDriver | 13 | * @module provider/Drivers/OpenCodeDriver |
| 14 | */ | 14 | */ |
| 15 | import { OpenCodeSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; | 15 | import { OpenCodeSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts"; |
| 16 | import { Duration, Effect, FileSystem, Path, Schema, Stream } from "effect"; | 16 | import * as Duration from "effect/Duration"; |
| 17 | import * as Effect from "effect/Effect"; | ||
| 18 | import * as FileSystem from "effect/FileSystem"; | ||
| 19 | import * as Path from "effect/Path"; | ||
| 20 | import * as Schema from "effect/Schema"; | ||
| 21 | import * as Stream from "effect/Stream"; | ||
| 17 | import { HttpClient } from "effect/unstable/http"; | 22 | import { HttpClient } from "effect/unstable/http"; |
| 18 | import { ChildProcessSpawner } from "effect/unstable/process"; | 23 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 19 | 24 | ||
| ... | @@ -139,7 +144,8 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> | ... | @@ -139,7 +144,8 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> |
| 139 | getSettings: Effect.succeed(effectiveConfig), | 144 | getSettings: Effect.succeed(effectiveConfig), |
| 140 | streamSettings: Stream.never, | 145 | streamSettings: Stream.never, |
| 141 | haveSettingsChanged: () => false, | 146 | haveSettingsChanged: () => false, |
| 142 | initialSnapshot: (settings) => stampIdentity(makePendingOpenCodeProvider(settings)), | 147 | initialSnapshot: (settings) => |
| 148 | makePendingOpenCodeProvider(settings).pipe(Effect.map(stampIdentity)), | ||
| 143 | checkProvider, | 149 | checkProvider, |
| 144 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | 150 | enrichSnapshot: ({ snapshot, publishSnapshot }) => |
| 145 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | 151 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( |
apps/server/src/provider/Errors.ts+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Schema } from "effect"; | 1 | import * as Schema from "effect/Schema"; |
| 2 | 2 | ||
| 3 | import type { CheckpointServiceError } from "../checkpointing/Errors.ts"; | 3 | import type { CheckpointServiceError } from "../checkpointing/Errors.ts"; |
| 4 | 4 |
apps/server/src/provider/Layers/ClaudeAdapter.test.ts+11-2| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; | 2 | import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
| ... | @@ -22,7 +23,14 @@ import { | ... | @@ -22,7 +23,14 @@ import { |
| 22 | } from "@t3tools/contracts"; | 23 | } from "@t3tools/contracts"; |
| 23 | import { createModelSelection } from "@t3tools/shared/model"; | 24 | import { createModelSelection } from "@t3tools/shared/model"; |
| 24 | import { assert, describe, it } from "@effect/vitest"; | 25 | import { assert, describe, it } from "@effect/vitest"; |
| 25 | import { Context, Effect, Fiber, Layer, Random, Schema, Stream } from "effect"; | 26 | import * as Context from "effect/Context"; |
| 27 | import * as Effect from "effect/Effect"; | ||
| 28 | import * as Fiber from "effect/Fiber"; | ||
| 29 | import * as Layer from "effect/Layer"; | ||
| 30 | import * as Random from "effect/Random"; | ||
| 31 | import * as Schema from "effect/Schema"; | ||
| 32 | import * as Stream from "effect/Stream"; | ||
| 33 | import * as TestClock from "effect/testing/TestClock"; | ||
| 26 | 34 | ||
| 27 | import { attachmentRelativePath } from "../../attachmentStore.ts"; | 35 | import { attachmentRelativePath } from "../../attachmentStore.ts"; |
| 28 | import { ServerConfig } from "../../config.ts"; | 36 | import { ServerConfig } from "../../config.ts"; |
| ... | @@ -1463,7 +1471,8 @@ describe("ClaudeAdapterLive", () => { | ... | @@ -1463,7 +1471,8 @@ describe("ClaudeAdapterLive", () => { |
| 1463 | yield* Effect.yieldNow; | 1471 | yield* Effect.yieldNow; |
| 1464 | yield* Effect.yieldNow; | 1472 | yield* Effect.yieldNow; |
| 1465 | yield* Effect.yieldNow; | 1473 | yield* Effect.yieldNow; |
| 1466 | yield* Effect.promise(() => new Promise((resolve) => setTimeout(resolve, 50))); | 1474 | yield* TestClock.adjust("50 millis"); |
| 1475 | yield* Effect.yieldNow; | ||
| 1467 | 1476 | ||
| 1468 | runtimeEventsFiber.interruptUnsafe(); | 1477 | runtimeEventsFiber.interruptUnsafe(); |
| 1469 | 1478 |
apps/server/src/provider/Layers/ClaudeAdapter.ts+65-36| ... | @@ -51,20 +51,19 @@ import { | ... | @@ -51,20 +51,19 @@ import { |
| 51 | getProviderOptionDescriptors, | 51 | getProviderOptionDescriptors, |
| 52 | resolvePromptInjectedEffort, | 52 | resolvePromptInjectedEffort, |
| 53 | } from "@t3tools/shared/model"; | 53 | } from "@t3tools/shared/model"; |
| 54 | import { | 54 | import * as Cause from "effect/Cause"; |
| 55 | Cause, | 55 | import * as DateTime from "effect/DateTime"; |
| 56 | DateTime, | 56 | import * as Deferred from "effect/Deferred"; |
| 57 | Deferred, | 57 | import * as Effect from "effect/Effect"; |
| 58 | Effect, | 58 | import * as Exit from "effect/Exit"; |
| 59 | Exit, | 59 | import * as FileSystem from "effect/FileSystem"; |
| 60 | FileSystem, | 60 | import * as Fiber from "effect/Fiber"; |
| 61 | Fiber, | 61 | import * as Path from "effect/Path"; |
| 62 | Path, | 62 | import * as Queue from "effect/Queue"; |
| 63 | Queue, | 63 | import * as Random from "effect/Random"; |
| 64 | Random, | 64 | import * as Ref from "effect/Ref"; |
| 65 | Ref, | 65 | import * as Schema from "effect/Schema"; |
| 66 | Stream, | 66 | import * as Stream from "effect/Stream"; |
| 67 | } from "effect"; | ||
| 68 | 67 | ||
| 69 | import { resolveAttachmentPath } from "../../attachmentStore.ts"; | 68 | import { resolveAttachmentPath } from "../../attachmentStore.ts"; |
| 70 | import { ServerConfig } from "../../config.ts"; | 69 | import { ServerConfig } from "../../config.ts"; |
| ... | @@ -222,11 +221,22 @@ function toMessage(cause: unknown, fallback: string): string { | ... | @@ -222,11 +221,22 @@ function toMessage(cause: unknown, fallback: string): string { |
| 222 | return fallback; | 221 | return fallback; |
| 223 | } | 222 | } |
| 224 | 223 | ||
| 225 | function toError(cause: unknown, fallback: string): Error { | 224 | function toProcessError( |
| 226 | return cause instanceof Error ? cause : new Error(toMessage(cause, fallback)); | 225 | cause: unknown, |
| 226 | fallback: string, | ||
| 227 | threadId: ThreadId, | ||
| 228 | ): ProviderAdapterProcessError { | ||
| 229 | return new ProviderAdapterProcessError({ | ||
| 230 | provider: PROVIDER, | ||
| 231 | threadId, | ||
| 232 | detail: toMessage(cause, fallback), | ||
| 233 | cause, | ||
| 234 | }); | ||
| 227 | } | 235 | } |
| 228 | 236 | ||
| 229 | function normalizeClaudeStreamMessages(cause: Cause.Cause<Error>): ReadonlyArray<string> { | 237 | function normalizeClaudeStreamMessages( |
| 238 | cause: Cause.Cause<{ readonly message: string }>, | ||
| 239 | ): ReadonlyArray<string> { | ||
| 230 | const errors = Cause.prettyErrors(cause) | 240 | const errors = Cause.prettyErrors(cause) |
| 231 | .map((error) => error.message.trim()) | 241 | .map((error) => error.message.trim()) |
| 232 | .filter((message) => message.length > 0); | 242 | .filter((message) => message.length > 0); |
| ... | @@ -252,18 +262,23 @@ function isClaudeInterruptedMessage(message: string): boolean { | ... | @@ -252,18 +262,23 @@ function isClaudeInterruptedMessage(message: string): boolean { |
| 252 | ); | 262 | ); |
| 253 | } | 263 | } |
| 254 | 264 | ||
| 255 | function isClaudeInterruptedCause(cause: Cause.Cause<Error>): boolean { | 265 | function isClaudeInterruptedCause(cause: Cause.Cause<{ readonly message: string }>): boolean { |
| 256 | return ( | 266 | return ( |
| 257 | Cause.hasInterruptsOnly(cause) || | 267 | Cause.hasInterruptsOnly(cause) || |
| 258 | normalizeClaudeStreamMessages(cause).some(isClaudeInterruptedMessage) | 268 | normalizeClaudeStreamMessages(cause).some(isClaudeInterruptedMessage) |
| 259 | ); | 269 | ); |
| 260 | } | 270 | } |
| 261 | 271 | ||
| 262 | function messageFromClaudeStreamCause(cause: Cause.Cause<Error>, fallback: string): string { | 272 | function messageFromClaudeStreamCause( |
| 273 | cause: Cause.Cause<{ readonly message: string }>, | ||
| 274 | fallback: string, | ||
| 275 | ): string { | ||
| 263 | return normalizeClaudeStreamMessages(cause)[0] ?? fallback; | 276 | return normalizeClaudeStreamMessages(cause)[0] ?? fallback; |
| 264 | } | 277 | } |
| 265 | 278 | ||
| 266 | function interruptionMessageFromClaudeCause(cause: Cause.Cause<Error>): string { | 279 | function interruptionMessageFromClaudeCause( |
| 280 | cause: Cause.Cause<{ readonly message: string }>, | ||
| 281 | ): string { | ||
| 267 | const message = messageFromClaudeStreamCause(cause, "Claude runtime interrupted."); | 282 | const message = messageFromClaudeStreamCause(cause, "Claude runtime interrupted."); |
| 268 | return isClaudeInterruptedMessage(message) ? "Claude runtime interrupted." : message; | 283 | return isClaudeInterruptedMessage(message) ? "Claude runtime interrupted." : message; |
| 269 | } | 284 | } |
| ... | @@ -530,7 +545,7 @@ function summarizeToolRequest(toolName: string, input: Record<string, unknown>): | ... | @@ -530,7 +545,7 @@ function summarizeToolRequest(toolName: string, input: Record<string, unknown>): |
| 530 | } | 545 | } |
| 531 | } | 546 | } |
| 532 | 547 | ||
| 533 | const serialized = JSON.stringify(input); | 548 | const serialized = Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(input); |
| 534 | if (serialized.length <= 400) { | 549 | if (serialized.length <= 400) { |
| 535 | return `${toolName}: ${serialized}`; | 550 | return `${toolName}: ${serialized}`; |
| 536 | } | 551 | } |
| ... | @@ -796,7 +811,7 @@ function exitPlanCaptureKey(input: { | ... | @@ -796,7 +811,7 @@ function exitPlanCaptureKey(input: { |
| 796 | 811 | ||
| 797 | function tryParseJsonRecord(value: string): Record<string, unknown> | undefined { | 812 | function tryParseJsonRecord(value: string): Record<string, unknown> | undefined { |
| 798 | try { | 813 | try { |
| 799 | const parsed = JSON.parse(value); | 814 | const parsed = Schema.decodeUnknownSync(Schema.UnknownFromJsonString)(value); |
| 800 | return parsed && typeof parsed === "object" && !Array.isArray(parsed) | 815 | return parsed && typeof parsed === "object" && !Array.isArray(parsed) |
| 801 | ? (parsed as Record<string, unknown>) | 816 | ? (parsed as Record<string, unknown>) |
| 802 | : undefined; | 817 | : undefined; |
| ... | @@ -807,7 +822,7 @@ function tryParseJsonRecord(value: string): Record<string, unknown> | undefined | ... | @@ -807,7 +822,7 @@ function tryParseJsonRecord(value: string): Record<string, unknown> | undefined |
| 807 | 822 | ||
| 808 | function toolInputFingerprint(input: Record<string, unknown>): string | undefined { | 823 | function toolInputFingerprint(input: Record<string, unknown>): string | undefined { |
| 809 | try { | 824 | try { |
| 810 | return JSON.stringify(input); | 825 | return Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(input); |
| 811 | } catch { | 826 | } catch { |
| 812 | return undefined; | 827 | return undefined; |
| 813 | } | 828 | } |
| ... | @@ -1020,7 +1035,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( | ... | @@ -1020,7 +1035,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 1020 | return; | 1035 | return; |
| 1021 | } | 1036 | } |
| 1022 | 1037 | ||
| 1023 | const observedAt = new Date().toISOString(); | 1038 | const observedAt = yield* nowIso; |
| 1024 | const itemId = sdkNativeItemId(message); | 1039 | const itemId = sdkNativeItemId(message); |
| 1025 | 1040 | ||
| 1026 | yield* nativeEventLogger.write( | 1041 | yield* nativeEventLogger.write( |
| ... | @@ -1030,7 +1045,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( | ... | @@ -1030,7 +1045,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 1030 | id: | 1045 | id: |
| 1031 | "uuid" in message && typeof message.uuid === "string" | 1046 | "uuid" in message && typeof message.uuid === "string" |
| 1032 | ? message.uuid | 1047 | ? message.uuid |
| 1033 | : crypto.randomUUID(), | 1048 | : yield* Random.nextUUIDv4, |
| 1034 | kind: "notification", | 1049 | kind: "notification", |
| 1035 | provider: PROVIDER, | 1050 | provider: PROVIDER, |
| 1036 | createdAt: observedAt, | 1051 | createdAt: observedAt, |
| ... | @@ -2353,9 +2368,11 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( | ... | @@ -2353,9 +2368,11 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 2353 | } | 2368 | } |
| 2354 | }); | 2369 | }); |
| 2355 | 2370 | ||
| 2356 | const runSdkStream = (context: ClaudeSessionContext): Effect.Effect<void, Error> => | 2371 | const runSdkStream = ( |
| 2372 | context: ClaudeSessionContext, | ||
| 2373 | ): Effect.Effect<void, ProviderAdapterProcessError> => | ||
| 2357 | Stream.fromAsyncIterable(context.query, (cause) => | 2374 | Stream.fromAsyncIterable(context.query, (cause) => |
| 2358 | toError(cause, "Claude runtime stream failed."), | 2375 | toProcessError(cause, "Claude runtime stream failed.", context.session.threadId), |
| 2359 | ).pipe( | 2376 | ).pipe( |
| 2360 | Stream.takeWhile(() => !context.stopped), | 2377 | Stream.takeWhile(() => !context.stopped), |
| 2361 | Stream.runForEach((message) => handleSdkMessage(context, message)), | 2378 | Stream.runForEach((message) => handleSdkMessage(context, message)), |
| ... | @@ -2363,7 +2380,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( | ... | @@ -2363,7 +2380,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 2363 | 2380 | ||
| 2364 | const handleStreamExit = Effect.fn("handleStreamExit")(function* ( | 2381 | const handleStreamExit = Effect.fn("handleStreamExit")(function* ( |
| 2365 | context: ClaudeSessionContext, | 2382 | context: ClaudeSessionContext, |
| 2366 | exit: Exit.Exit<void, Error>, | 2383 | exit: Exit.Exit<void, ProviderAdapterProcessError>, |
| 2367 | ) { | 2384 | ) { |
| 2368 | if (context.stopped) { | 2385 | if (context.stopped) { |
| 2369 | return; | 2386 | return; |
| ... | @@ -2432,12 +2449,20 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( | ... | @@ -2432,12 +2449,20 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 2432 | yield* Fiber.interrupt(streamFiber); | 2449 | yield* Fiber.interrupt(streamFiber); |
| 2433 | } | 2450 | } |
| 2434 | 2451 | ||
| 2435 | // @effect-diagnostics-next-line tryCatchInEffectGen:off | 2452 | yield* Effect.try({ |
| 2436 | try { | 2453 | try: () => context.query.close(), |
| 2437 | context.query.close(); | 2454 | catch: (cause) => |
| 2438 | } catch (cause) { | 2455 | new ProviderAdapterProcessError({ |
| 2439 | yield* emitRuntimeError(context, "Failed to close Claude runtime query.", cause); | 2456 | provider: PROVIDER, |
| 2440 | } | 2457 | threadId: context.session.threadId, |
| 2458 | detail: toMessage(cause, "Failed to close Claude runtime query."), | ||
| 2459 | cause, | ||
| 2460 | }), | ||
| 2461 | }).pipe( | ||
| 2462 | Effect.catch((cause) => | ||
| 2463 | emitRuntimeError(context, "Failed to close Claude runtime query.", cause), | ||
| 2464 | ), | ||
| 2465 | ); | ||
| 2441 | 2466 | ||
| 2442 | const updatedAt = yield* nowIso; | 2467 | const updatedAt = yield* nowIso; |
| 2443 | context.session = { | 2468 | context.session = { |
| ... | @@ -2916,8 +2941,12 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( | ... | @@ -2916,8 +2941,12 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 2916 | "claude.query.include_partial_messages": true, | 2941 | "claude.query.include_partial_messages": true, |
| 2917 | "claude.query.additional_directories": input.cwd ? [input.cwd] : [], | 2942 | "claude.query.additional_directories": input.cwd ? [input.cwd] : [], |
| 2918 | "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES], | 2943 | "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES], |
| 2919 | "claude.query.settings_json": JSON.stringify(settings), | 2944 | "claude.query.settings_json": Schema.encodeUnknownSync(Schema.UnknownFromJsonString)( |
| 2920 | "claude.query.extra_args_json": JSON.stringify(extraArgs), | 2945 | settings, |
| 2946 | ), | ||
| 2947 | "claude.query.extra_args_json": Schema.encodeUnknownSync(Schema.UnknownFromJsonString)( | ||
| 2948 | extraArgs, | ||
| 2949 | ), | ||
| 2921 | "claude.query.path_to_executable": claudeBinaryPath, | 2950 | "claude.query.path_to_executable": claudeBinaryPath, |
| 2922 | }); | 2951 | }); |
| 2923 | 2952 |
apps/server/src/provider/Layers/ClaudeProvider.ts+37-28| ... | @@ -6,7 +6,11 @@ import { | ... | @@ -6,7 +6,11 @@ import { |
| 6 | type ServerProviderModel, | 6 | type ServerProviderModel, |
| 7 | type ServerProviderSlashCommand, | 7 | type ServerProviderSlashCommand, |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { Effect, Option, Path, Result } from "effect"; | 9 | import * as DateTime from "effect/DateTime"; |
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Option from "effect/Option"; | ||
| 12 | import * as Path from "effect/Path"; | ||
| 13 | import * as Result from "effect/Result"; | ||
| 10 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 14 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 11 | import { | 15 | import { |
| 12 | createModelCapabilities, | 16 | createModelCapabilities, |
| ... | @@ -521,7 +525,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( | ... | @@ -521,7 +525,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( |
| 521 | never, | 525 | never, |
| 522 | ChildProcessSpawner.ChildProcessSpawner | Path.Path | 526 | ChildProcessSpawner.ChildProcessSpawner | Path.Path |
| 523 | > { | 527 | > { |
| 524 | const checkedAt = new Date().toISOString(); | 528 | const checkedAt = DateTime.formatIso(yield* DateTime.now); |
| 525 | const allModels = providerModelsFromSettings( | 529 | const allModels = providerModelsFromSettings( |
| 526 | BUILT_IN_MODELS, | 530 | BUILT_IN_MODELS, |
| 527 | PROVIDER, | 531 | PROVIDER, |
| ... | @@ -664,19 +668,39 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( | ... | @@ -664,19 +668,39 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( |
| 664 | }); | 668 | }); |
| 665 | }); | 669 | }); |
| 666 | 670 | ||
| 667 | export const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): ServerProviderDraft => { | 671 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 668 | const checkedAt = new Date().toISOString(); | 672 | |
| 669 | const models = providerModelsFromSettings( | 673 | export const makePendingClaudeProvider = ( |
| 670 | BUILT_IN_MODELS, | 674 | claudeSettings: ClaudeSettings, |
| 671 | PROVIDER, | 675 | ): Effect.Effect<ServerProviderDraft> => |
| 672 | claudeSettings.customModels, | 676 | Effect.gen(function* () { |
| 673 | DEFAULT_CLAUDE_MODEL_CAPABILITIES, | 677 | const checkedAt = yield* nowIso; |
| 674 | ); | 678 | const models = providerModelsFromSettings( |
| 679 | BUILT_IN_MODELS, | ||
| 680 | PROVIDER, | ||
| 681 | claudeSettings.customModels, | ||
| 682 | DEFAULT_CLAUDE_MODEL_CAPABILITIES, | ||
| 683 | ); | ||
| 684 | |||
| 685 | if (!claudeSettings.enabled) { | ||
| 686 | return buildServerProvider({ | ||
| 687 | presentation: CLAUDE_PRESENTATION, | ||
| 688 | enabled: false, | ||
| 689 | checkedAt, | ||
| 690 | models, | ||
| 691 | probe: { | ||
| 692 | installed: false, | ||
| 693 | version: null, | ||
| 694 | status: "warning", | ||
| 695 | auth: { status: "unknown" }, | ||
| 696 | message: "Claude is disabled in T3 Code settings.", | ||
| 697 | }, | ||
| 698 | }); | ||
| 699 | } | ||
| 675 | 700 | ||
| 676 | if (!claudeSettings.enabled) { | ||
| 677 | return buildServerProvider({ | 701 | return buildServerProvider({ |
| 678 | presentation: CLAUDE_PRESENTATION, | 702 | presentation: CLAUDE_PRESENTATION, |
| 679 | enabled: false, | 703 | enabled: true, |
| 680 | checkedAt, | 704 | checkedAt, |
| 681 | models, | 705 | models, |
| 682 | probe: { | 706 | probe: { |
| ... | @@ -684,24 +708,9 @@ export const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): Serve | ... | @@ -684,24 +708,9 @@ export const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): Serve |
| 684 | version: null, | 708 | version: null, |
| 685 | status: "warning", | 709 | status: "warning", |
| 686 | auth: { status: "unknown" }, | 710 | auth: { status: "unknown" }, |
| 687 | message: "Claude is disabled in T3 Code settings.", | 711 | message: "Claude provider status has not been checked in this session yet.", |
| 688 | }, | 712 | }, |
| 689 | }); | 713 | }); |
| 690 | } | ||
| 691 | |||
| 692 | return buildServerProvider({ | ||
| 693 | presentation: CLAUDE_PRESENTATION, | ||
| 694 | enabled: true, | ||
| 695 | checkedAt, | ||
| 696 | models, | ||
| 697 | probe: { | ||
| 698 | installed: false, | ||
| 699 | version: null, | ||
| 700 | status: "warning", | ||
| 701 | auth: { status: "unknown" }, | ||
| 702 | message: "Claude provider status has not been checked in this session yet.", | ||
| 703 | }, | ||
| 704 | }); | 714 | }); |
| 705 | }; | ||
| 706 | 715 | ||
| 707 | export { probeClaudeCapabilities }; | 716 | export { probeClaudeCapabilities }; |
apps/server/src/provider/Layers/CodexAdapter.test.ts+28-18| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import assert from "node:assert/strict"; | 2 | import assert from "node:assert/strict"; |
| 2 | import fs from "node:fs"; | 3 | import fs from "node:fs"; |
| 3 | import os from "node:os"; | 4 | import os from "node:os"; |
| ... | @@ -21,7 +22,16 @@ import { createModelSelection } from "@t3tools/shared/model"; | ... | @@ -21,7 +22,16 @@ import { createModelSelection } from "@t3tools/shared/model"; |
| 21 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 22 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 22 | import { it, vi } from "@effect/vitest"; | 23 | import { it, vi } from "@effect/vitest"; |
| 23 | 24 | ||
| 24 | import { Context, Effect, Exit, Fiber, Layer, Option, Queue, Schema, Scope, Stream } from "effect"; | 25 | import * as Context from "effect/Context"; |
| 26 | import * as Effect from "effect/Effect"; | ||
| 27 | import * as Exit from "effect/Exit"; | ||
| 28 | import * as Fiber from "effect/Fiber"; | ||
| 29 | import * as Layer from "effect/Layer"; | ||
| 30 | import * as Option from "effect/Option"; | ||
| 31 | import * as Queue from "effect/Queue"; | ||
| 32 | import * as Schema from "effect/Schema"; | ||
| 33 | import * as Scope from "effect/Scope"; | ||
| 34 | import * as Stream from "effect/Stream"; | ||
| 25 | import * as CodexErrors from "effect-codex-app-server/errors"; | 35 | import * as CodexErrors from "effect-codex-app-server/errors"; |
| 26 | 36 | ||
| 27 | import { ServerConfig } from "../../config.ts"; | 37 | import { ServerConfig } from "../../config.ts"; |
| ... | @@ -49,7 +59,7 @@ const asItemId = (value: string): ProviderItemId => ProviderItemId.make(value); | ... | @@ -49,7 +59,7 @@ const asItemId = (value: string): ProviderItemId => ProviderItemId.make(value); |
| 49 | 59 | ||
| 50 | class FakeCodexRuntime implements CodexSessionRuntimeShape { | 60 | class FakeCodexRuntime implements CodexSessionRuntimeShape { |
| 51 | private readonly eventQueue = Effect.runSync(Queue.unbounded<ProviderEvent>()); | 61 | private readonly eventQueue = Effect.runSync(Queue.unbounded<ProviderEvent>()); |
| 52 | private readonly now = new Date().toISOString(); | 62 | private readonly now = "2026-01-01T00:00:00.000Z"; |
| 53 | 63 | ||
| 54 | public readonly startImpl = vi.fn(() => | 64 | public readonly startImpl = vi.fn(() => |
| 55 | Promise.resolve({ | 65 | Promise.resolve({ |
| ... | @@ -446,7 +456,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -446,7 +456,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 446 | id: asEventId("evt-msg-complete"), | 456 | id: asEventId("evt-msg-complete"), |
| 447 | kind: "notification", | 457 | kind: "notification", |
| 448 | provider: ProviderDriverKind.make("codex"), | 458 | provider: ProviderDriverKind.make("codex"), |
| 449 | createdAt: new Date().toISOString(), | 459 | createdAt: "2026-01-01T00:00:00.000Z", |
| 450 | method: "item/completed", | 460 | method: "item/completed", |
| 451 | threadId: asThreadId("thread-1"), | 461 | threadId: asThreadId("thread-1"), |
| 452 | turnId: asTurnId("turn-1"), | 462 | turnId: asTurnId("turn-1"), |
| ... | @@ -489,7 +499,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -489,7 +499,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 489 | id: asEventId("evt-plan-complete"), | 499 | id: asEventId("evt-plan-complete"), |
| 490 | kind: "notification", | 500 | kind: "notification", |
| 491 | provider: ProviderDriverKind.make("codex"), | 501 | provider: ProviderDriverKind.make("codex"), |
| 492 | createdAt: new Date().toISOString(), | 502 | createdAt: "2026-01-01T00:00:00.000Z", |
| 493 | method: "item/completed", | 503 | method: "item/completed", |
| 494 | threadId: asThreadId("thread-1"), | 504 | threadId: asThreadId("thread-1"), |
| 495 | turnId: asTurnId("turn-1"), | 505 | turnId: asTurnId("turn-1"), |
| ... | @@ -531,7 +541,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -531,7 +541,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 531 | id: asEventId("evt-plan-delta"), | 541 | id: asEventId("evt-plan-delta"), |
| 532 | kind: "notification", | 542 | kind: "notification", |
| 533 | provider: ProviderDriverKind.make("codex"), | 543 | provider: ProviderDriverKind.make("codex"), |
| 534 | createdAt: new Date().toISOString(), | 544 | createdAt: "2026-01-01T00:00:00.000Z", |
| 535 | method: "item/plan/delta", | 545 | method: "item/plan/delta", |
| 536 | threadId: asThreadId("thread-1"), | 546 | threadId: asThreadId("thread-1"), |
| 537 | turnId: asTurnId("turn-1"), | 547 | turnId: asTurnId("turn-1"), |
| ... | @@ -569,7 +579,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -569,7 +579,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 569 | kind: "session", | 579 | kind: "session", |
| 570 | provider: ProviderDriverKind.make("codex"), | 580 | provider: ProviderDriverKind.make("codex"), |
| 571 | threadId: asThreadId("thread-1"), | 581 | threadId: asThreadId("thread-1"), |
| 572 | createdAt: new Date().toISOString(), | 582 | createdAt: "2026-01-01T00:00:00.000Z", |
| 573 | method: "session/closed", | 583 | method: "session/closed", |
| 574 | message: "Session stopped", | 584 | message: "Session stopped", |
| 575 | }; | 585 | }; |
| ... | @@ -600,7 +610,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -600,7 +610,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 600 | kind: "notification", | 610 | kind: "notification", |
| 601 | provider: ProviderDriverKind.make("codex"), | 611 | provider: ProviderDriverKind.make("codex"), |
| 602 | threadId: asThreadId("thread-1"), | 612 | threadId: asThreadId("thread-1"), |
| 603 | createdAt: new Date().toISOString(), | 613 | createdAt: "2026-01-01T00:00:00.000Z", |
| 604 | method: "error", | 614 | method: "error", |
| 605 | turnId: asTurnId("turn-1"), | 615 | turnId: asTurnId("turn-1"), |
| 606 | payload: { | 616 | payload: { |
| ... | @@ -638,7 +648,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -638,7 +648,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 638 | kind: "notification", | 648 | kind: "notification", |
| 639 | provider: ProviderDriverKind.make("codex"), | 649 | provider: ProviderDriverKind.make("codex"), |
| 640 | threadId: asThreadId("thread-1"), | 650 | threadId: asThreadId("thread-1"), |
| 641 | createdAt: new Date().toISOString(), | 651 | createdAt: "2026-01-01T00:00:00.000Z", |
| 642 | method: "process/stderr", | 652 | method: "process/stderr", |
| 643 | turnId: asTurnId("turn-1"), | 653 | turnId: asTurnId("turn-1"), |
| 644 | message: "The filename or extension is too long. (os error 206)", | 654 | message: "The filename or extension is too long. (os error 206)", |
| ... | @@ -672,7 +682,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -672,7 +682,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 672 | kind: "notification", | 682 | kind: "notification", |
| 673 | provider: ProviderDriverKind.make("codex"), | 683 | provider: ProviderDriverKind.make("codex"), |
| 674 | threadId: asThreadId("thread-1"), | 684 | threadId: asThreadId("thread-1"), |
| 675 | createdAt: new Date().toISOString(), | 685 | createdAt: "2026-01-01T00:00:00.000Z", |
| 676 | method: "thread/realtime/started", | 686 | method: "thread/realtime/started", |
| 677 | payload: { | 687 | payload: { |
| 678 | threadId: "thread-1", | 688 | threadId: "thread-1", |
| ... | @@ -706,7 +716,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -706,7 +716,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 706 | kind: "notification", | 716 | kind: "notification", |
| 707 | provider: ProviderDriverKind.make("codex"), | 717 | provider: ProviderDriverKind.make("codex"), |
| 708 | threadId: asThreadId("thread-1"), | 718 | threadId: asThreadId("thread-1"), |
| 709 | createdAt: new Date().toISOString(), | 719 | createdAt: "2026-01-01T00:00:00.000Z", |
| 710 | method: "process/stderr", | 720 | method: "process/stderr", |
| 711 | turnId: asTurnId("turn-1"), | 721 | turnId: asTurnId("turn-1"), |
| 712 | message: | 722 | message: |
| ... | @@ -742,7 +752,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -742,7 +752,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 742 | kind: "notification", | 752 | kind: "notification", |
| 743 | provider: ProviderDriverKind.make("codex"), | 753 | provider: ProviderDriverKind.make("codex"), |
| 744 | threadId: asThreadId("thread-1"), | 754 | threadId: asThreadId("thread-1"), |
| 745 | createdAt: new Date().toISOString(), | 755 | createdAt: "2026-01-01T00:00:00.000Z", |
| 746 | method: "serverRequest/resolved", | 756 | method: "serverRequest/resolved", |
| 747 | requestKind: "command", | 757 | requestKind: "command", |
| 748 | requestId: ApprovalRequestId.make("req-1"), | 758 | requestId: ApprovalRequestId.make("req-1"), |
| ... | @@ -777,7 +787,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -777,7 +787,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 777 | kind: "notification", | 787 | kind: "notification", |
| 778 | provider: ProviderDriverKind.make("codex"), | 788 | provider: ProviderDriverKind.make("codex"), |
| 779 | threadId: asThreadId("thread-1"), | 789 | threadId: asThreadId("thread-1"), |
| 780 | createdAt: new Date().toISOString(), | 790 | createdAt: "2026-01-01T00:00:00.000Z", |
| 781 | method: "serverRequest/resolved", | 791 | method: "serverRequest/resolved", |
| 782 | requestKind: "file-read", | 792 | requestKind: "file-read", |
| 783 | requestId: ApprovalRequestId.make("req-file-read-1"), | 793 | requestId: ApprovalRequestId.make("req-file-read-1"), |
| ... | @@ -812,7 +822,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -812,7 +822,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 812 | kind: "notification", | 822 | kind: "notification", |
| 813 | provider: ProviderDriverKind.make("codex"), | 823 | provider: ProviderDriverKind.make("codex"), |
| 814 | threadId: asThreadId("thread-1"), | 824 | threadId: asThreadId("thread-1"), |
| 815 | createdAt: new Date().toISOString(), | 825 | createdAt: "2026-01-01T00:00:00.000Z", |
| 816 | method: "item/tool/requestUserInput/answered", | 826 | method: "item/tool/requestUserInput/answered", |
| 817 | payload: { | 827 | payload: { |
| 818 | answers: { | 828 | answers: { |
| ... | @@ -852,7 +862,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -852,7 +862,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 852 | kind: "notification", | 862 | kind: "notification", |
| 853 | provider: ProviderDriverKind.make("codex"), | 863 | provider: ProviderDriverKind.make("codex"), |
| 854 | threadId: asThreadId("thread-1"), | 864 | threadId: asThreadId("thread-1"), |
| 855 | createdAt: new Date().toISOString(), | 865 | createdAt: "2026-01-01T00:00:00.000Z", |
| 856 | method: "windowsSandbox/setupCompleted", | 866 | method: "windowsSandbox/setupCompleted", |
| 857 | message: "Sandbox setup failed", | 867 | message: "Sandbox setup failed", |
| 858 | payload: { | 868 | payload: { |
| ... | @@ -897,7 +907,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -897,7 +907,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 897 | kind: "request", | 907 | kind: "request", |
| 898 | provider: ProviderDriverKind.make("codex"), | 908 | provider: ProviderDriverKind.make("codex"), |
| 899 | threadId: asThreadId("thread-1"), | 909 | threadId: asThreadId("thread-1"), |
| 900 | createdAt: new Date().toISOString(), | 910 | createdAt: "2026-01-01T00:00:00.000Z", |
| 901 | method: "item/tool/requestUserInput", | 911 | method: "item/tool/requestUserInput", |
| 902 | requestId: ApprovalRequestId.make("req-user-input-1"), | 912 | requestId: ApprovalRequestId.make("req-user-input-1"), |
| 903 | payload: { | 913 | payload: { |
| ... | @@ -924,7 +934,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -924,7 +934,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 924 | kind: "notification", | 934 | kind: "notification", |
| 925 | provider: ProviderDriverKind.make("codex"), | 935 | provider: ProviderDriverKind.make("codex"), |
| 926 | threadId: asThreadId("thread-1"), | 936 | threadId: asThreadId("thread-1"), |
| 927 | createdAt: new Date().toISOString(), | 937 | createdAt: "2026-01-01T00:00:00.000Z", |
| 928 | method: "item/tool/requestUserInput/answered", | 938 | method: "item/tool/requestUserInput/answered", |
| 929 | requestId: ApprovalRequestId.make("req-user-input-1"), | 939 | requestId: ApprovalRequestId.make("req-user-input-1"), |
| 930 | payload: { | 940 | payload: { |
| ... | @@ -965,7 +975,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { | ... | @@ -965,7 +975,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { |
| 965 | provider: ProviderDriverKind.make("codex"), | 975 | provider: ProviderDriverKind.make("codex"), |
| 966 | threadId: asThreadId("thread-1"), | 976 | threadId: asThreadId("thread-1"), |
| 967 | turnId: asTurnId("turn-1"), | 977 | turnId: asTurnId("turn-1"), |
| 968 | createdAt: new Date().toISOString(), | 978 | createdAt: "2026-01-01T00:00:00.000Z", |
| 969 | method: "thread/tokenUsage/updated", | 979 | method: "thread/tokenUsage/updated", |
| 970 | payload: { | 980 | payload: { |
| 971 | threadId: "thread-1", | 981 | threadId: "thread-1", |
| ... | @@ -1147,7 +1157,7 @@ it.effect("flushes managed native logs when the adapter layer shuts down", () => | ... | @@ -1147,7 +1157,7 @@ it.effect("flushes managed native logs when the adapter layer shuts down", () => |
| 1147 | kind: "notification", | 1157 | kind: "notification", |
| 1148 | provider: ProviderDriverKind.make("codex"), | 1158 | provider: ProviderDriverKind.make("codex"), |
| 1149 | threadId: asThreadId("thread-logger"), | 1159 | threadId: asThreadId("thread-logger"), |
| 1150 | createdAt: new Date().toISOString(), | 1160 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1151 | method: "process/stderr", | 1161 | method: "process/stderr", |
| 1152 | message: "native flush test", | 1162 | message: "native flush test", |
| 1153 | } satisfies ProviderEvent); | 1163 | } satisfies ProviderEvent); |
apps/server/src/provider/Layers/CodexAdapter.ts+8-1| ... | @@ -24,7 +24,14 @@ import { | ... | @@ -24,7 +24,14 @@ import { |
| 24 | ThreadId, | 24 | ThreadId, |
| 25 | ProviderSendTurnInput, | 25 | ProviderSendTurnInput, |
| 26 | } from "@t3tools/contracts"; | 26 | } from "@t3tools/contracts"; |
| 27 | import { Effect, Exit, Fiber, FileSystem, Queue, Schema, Scope, Stream } from "effect"; | 27 | import * as Effect from "effect/Effect"; |
| 28 | import * as Exit from "effect/Exit"; | ||
| 29 | import * as Fiber from "effect/Fiber"; | ||
| 30 | import * as FileSystem from "effect/FileSystem"; | ||
| 31 | import * as Queue from "effect/Queue"; | ||
| 32 | import * as Schema from "effect/Schema"; | ||
| 33 | import * as Scope from "effect/Scope"; | ||
| 34 | import * as Stream from "effect/Stream"; | ||
| 28 | import { ChildProcessSpawner } from "effect/unstable/process"; | 35 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 29 | import * as CodexErrors from "effect-codex-app-server/errors"; | 36 | import * as CodexErrors from "effect-codex-app-server/errors"; |
| 30 | import * as EffectCodexSchema from "effect-codex-app-server/schema"; | 37 | import * as EffectCodexSchema from "effect-codex-app-server/schema"; |
apps/server/src/provider/Layers/CodexProvider.ts+33-23| ... | @@ -1,4 +1,11 @@ | ... | @@ -1,4 +1,11 @@ |
| 1 | import { DateTime, Duration, Effect, Layer, Option, Result, Schema, Types } from "effect"; | 1 | import * as DateTime from "effect/DateTime"; |
| 2 | import * as Duration from "effect/Duration"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import * as Result from "effect/Result"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 8 | import * as Types from "effect/Types"; | ||
| 2 | import { ChildProcessSpawner } from "effect/unstable/process"; | 9 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 3 | import * as CodexClient from "effect-codex-app-server/client"; | 10 | import * as CodexClient from "effect-codex-app-server/client"; |
| 4 | import * as CodexSchema from "effect-codex-app-server/schema"; | 11 | import * as CodexSchema from "effect-codex-app-server/schema"; |
| ... | @@ -325,14 +332,33 @@ const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvi | ... | @@ -325,14 +332,33 @@ const emptyCodexModelsFromSettings = (codexSettings: CodexSettings): ServerProvi |
| 325 | capabilities: null, | 332 | capabilities: null, |
| 326 | })); | 333 | })); |
| 327 | 334 | ||
| 328 | const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProviderDraft => { | 335 | const makePendingCodexProvider = ( |
| 329 | const checkedAt = new Date().toISOString(); | 336 | codexSettings: CodexSettings, |
| 330 | const models = emptyCodexModelsFromSettings(codexSettings); | 337 | ): Effect.Effect<ServerProviderDraft> => |
| 338 | Effect.gen(function* () { | ||
| 339 | const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); | ||
| 340 | const models = emptyCodexModelsFromSettings(codexSettings); | ||
| 341 | |||
| 342 | if (!codexSettings.enabled) { | ||
| 343 | return buildServerProvider({ | ||
| 344 | presentation: CODEX_PRESENTATION, | ||
| 345 | enabled: false, | ||
| 346 | checkedAt, | ||
| 347 | models, | ||
| 348 | skills: [], | ||
| 349 | probe: { | ||
| 350 | installed: false, | ||
| 351 | version: null, | ||
| 352 | status: "warning", | ||
| 353 | auth: { status: "unknown" }, | ||
| 354 | message: "Codex is disabled in T3 Code settings.", | ||
| 355 | }, | ||
| 356 | }); | ||
| 357 | } | ||
| 331 | 358 | ||
| 332 | if (!codexSettings.enabled) { | ||
| 333 | return buildServerProvider({ | 359 | return buildServerProvider({ |
| 334 | presentation: CODEX_PRESENTATION, | 360 | presentation: CODEX_PRESENTATION, |
| 335 | enabled: false, | 361 | enabled: true, |
| 336 | checkedAt, | 362 | checkedAt, |
| 337 | models, | 363 | models, |
| 338 | skills: [], | 364 | skills: [], |
| ... | @@ -341,26 +367,10 @@ const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProviderD | ... | @@ -341,26 +367,10 @@ const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProviderD |
| 341 | version: null, | 367 | version: null, |
| 342 | status: "warning", | 368 | status: "warning", |
| 343 | auth: { status: "unknown" }, | 369 | auth: { status: "unknown" }, |
| 344 | message: "Codex is disabled in T3 Code settings.", | 370 | message: "Codex provider status has not been checked in this session yet.", |
| 345 | }, | 371 | }, |
| 346 | }); | 372 | }); |
| 347 | } | ||
| 348 | |||
| 349 | return buildServerProvider({ | ||
| 350 | presentation: CODEX_PRESENTATION, | ||
| 351 | enabled: true, | ||
| 352 | checkedAt, | ||
| 353 | models, | ||
| 354 | skills: [], | ||
| 355 | probe: { | ||
| 356 | installed: false, | ||
| 357 | version: null, | ||
| 358 | status: "warning", | ||
| 359 | auth: { status: "unknown" }, | ||
| 360 | message: "Codex provider status has not been checked in this session yet.", | ||
| 361 | }, | ||
| 362 | }); | 373 | }); |
| 363 | }; | ||
| 364 | 374 | ||
| 365 | function accountProbeStatus(account: CodexAppServerProviderSnapshot["account"]): { | 375 | function accountProbeStatus(account: CodexAppServerProviderSnapshot["account"]): { |
| 366 | readonly status: Exclude<ServerProviderState, "disabled">; | 376 | readonly status: Exclude<ServerProviderState, "disabled">; |
apps/server/src/provider/Layers/CodexSessionRuntime.test.ts+2-1| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | import assert from "node:assert/strict"; | 1 | import assert from "node:assert/strict"; |
| 2 | 2 | ||
| 3 | import { Effect, Schema } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Schema from "effect/Schema"; | ||
| 4 | import { describe, it } from "vitest"; | 5 | import { describe, it } from "vitest"; |
| 5 | import { ThreadId } from "@t3tools/contracts"; | 6 | import { ThreadId } from "@t3tools/contracts"; |
| 6 | import * as CodexErrors from "effect-codex-app-server/errors"; | 7 | import * as CodexErrors from "effect-codex-app-server/errors"; |
apps/server/src/provider/Layers/CodexSessionRuntime.ts+30-14| ... | @@ -17,7 +17,17 @@ import { | ... | @@ -17,7 +17,17 @@ import { |
| 17 | TurnId, | 17 | TurnId, |
| 18 | } from "@t3tools/contracts"; | 18 | } from "@t3tools/contracts"; |
| 19 | import { normalizeModelSlug } from "@t3tools/shared/model"; | 19 | import { normalizeModelSlug } from "@t3tools/shared/model"; |
| 20 | import { Deferred, Effect, Exit, Layer, Queue, Ref, Scope, Random, Schema, Stream } from "effect"; | 20 | import * as DateTime from "effect/DateTime"; |
| 21 | import * as Deferred from "effect/Deferred"; | ||
| 22 | import * as Effect from "effect/Effect"; | ||
| 23 | import * as Exit from "effect/Exit"; | ||
| 24 | import * as Layer from "effect/Layer"; | ||
| 25 | import * as Queue from "effect/Queue"; | ||
| 26 | import * as Ref from "effect/Ref"; | ||
| 27 | import * as Scope from "effect/Scope"; | ||
| 28 | import * as Random from "effect/Random"; | ||
| 29 | import * as Schema from "effect/Schema"; | ||
| 30 | import * as Stream from "effect/Stream"; | ||
| 21 | import * as SchemaIssue from "effect/SchemaIssue"; | 31 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 22 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 32 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 23 | import * as CodexClient from "effect-codex-app-server/client"; | 33 | import * as CodexClient from "effect-codex-app-server/client"; |
| ... | @@ -653,11 +663,14 @@ function updateSession( | ... | @@ -653,11 +663,14 @@ function updateSession( |
| 653 | sessionRef: Ref.Ref<ProviderSession>, | 663 | sessionRef: Ref.Ref<ProviderSession>, |
| 654 | updates: Partial<ProviderSession>, | 664 | updates: Partial<ProviderSession>, |
| 655 | ): Effect.Effect<void> { | 665 | ): Effect.Effect<void> { |
| 656 | return Ref.update(sessionRef, (session) => ({ | 666 | return Effect.gen(function* () { |
| 657 | ...session, | 667 | const updatedAt = DateTime.formatIso(yield* DateTime.now); |
| 658 | ...updates, | 668 | yield* Ref.update(sessionRef, (session) => ({ |
| 659 | updatedAt: new Date().toISOString(), | 669 | ...session, |
| 660 | })); | 670 | ...updates, |
| 671 | updatedAt, | ||
| 672 | })); | ||
| 673 | }); | ||
| 661 | } | 674 | } |
| 662 | 675 | ||
| 663 | function parseThreadSnapshot( | 676 | function parseThreadSnapshot( |
| ... | @@ -724,7 +737,9 @@ export const makeCodexSessionRuntime = ( | ... | @@ -724,7 +737,9 @@ export const makeCodexSessionRuntime = ( |
| 724 | Effect.provide(clientContext), | 737 | Effect.provide(clientContext), |
| 725 | ); | 738 | ); |
| 726 | const serverNotifications = yield* Queue.unbounded<CodexServerNotification>(); | 739 | const serverNotifications = yield* Queue.unbounded<CodexServerNotification>(); |
| 740 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); | ||
| 727 | 741 | ||
| 742 | const sessionCreatedAt = yield* nowIso; | ||
| 728 | const initialSession = { | 743 | const initialSession = { |
| 729 | provider: PROVIDER, | 744 | provider: PROVIDER, |
| 730 | ...(options.providerInstanceId ? { providerInstanceId: options.providerInstanceId } : {}), | 745 | ...(options.providerInstanceId ? { providerInstanceId: options.providerInstanceId } : {}), |
| ... | @@ -734,22 +749,23 @@ export const makeCodexSessionRuntime = ( | ... | @@ -734,22 +749,23 @@ export const makeCodexSessionRuntime = ( |
| 734 | ...(options.model ? { model: options.model } : {}), | 749 | ...(options.model ? { model: options.model } : {}), |
| 735 | threadId: options.threadId, | 750 | threadId: options.threadId, |
| 736 | ...(options.resumeCursor !== undefined ? { resumeCursor: options.resumeCursor } : {}), | 751 | ...(options.resumeCursor !== undefined ? { resumeCursor: options.resumeCursor } : {}), |
| 737 | createdAt: new Date().toISOString(), | 752 | createdAt: sessionCreatedAt, |
| 738 | updatedAt: new Date().toISOString(), | 753 | updatedAt: sessionCreatedAt, |
| 739 | } satisfies ProviderSession; | 754 | } satisfies ProviderSession; |
| 740 | const sessionRef = yield* Ref.make<ProviderSession>(initialSession); | 755 | const sessionRef = yield* Ref.make<ProviderSession>(initialSession); |
| 741 | const offerEvent = (event: ProviderEvent) => Queue.offer(events, event).pipe(Effect.asVoid); | 756 | const offerEvent = (event: ProviderEvent) => Queue.offer(events, event).pipe(Effect.asVoid); |
| 742 | 757 | ||
| 743 | const emitEvent = (event: Omit<ProviderEvent, "id" | "provider" | "createdAt">) => | 758 | const emitEvent = (event: Omit<ProviderEvent, "id" | "provider" | "createdAt">) => |
| 744 | Effect.flatMap(Random.nextUUIDv4, (id) => | 759 | Effect.gen(function* () { |
| 745 | offerEvent({ | 760 | const id = yield* Random.nextUUIDv4; |
| 761 | return yield* offerEvent({ | ||
| 746 | id: EventId.make(id), | 762 | id: EventId.make(id), |
| 747 | provider: PROVIDER, | 763 | provider: PROVIDER, |
| 748 | ...(options.providerInstanceId ? { providerInstanceId: options.providerInstanceId } : {}), | 764 | ...(options.providerInstanceId ? { providerInstanceId: options.providerInstanceId } : {}), |
| 749 | createdAt: new Date().toISOString(), | 765 | createdAt: yield* nowIso, |
| 750 | ...event, | 766 | ...event, |
| 751 | }), | 767 | }); |
| 752 | ); | 768 | }); |
| 753 | const emitSessionEvent = (method: string, message: string) => | 769 | const emitSessionEvent = (method: string, message: string) => |
| 754 | emitEvent({ | 770 | emitEvent({ |
| 755 | kind: "session", | 771 | kind: "session", |
| ... | @@ -1177,7 +1193,7 @@ export const makeCodexSessionRuntime = ( | ... | @@ -1177,7 +1193,7 @@ export const makeCodexSessionRuntime = ( |
| 1177 | cwd: opened.cwd, | 1193 | cwd: opened.cwd, |
| 1178 | model: opened.model, | 1194 | model: opened.model, |
| 1179 | resumeCursor: { threadId: providerThreadId }, | 1195 | resumeCursor: { threadId: providerThreadId }, |
| 1180 | updatedAt: new Date().toISOString(), | 1196 | updatedAt: yield* nowIso, |
| 1181 | } satisfies ProviderSession; | 1197 | } satisfies ProviderSession; |
| 1182 | yield* Ref.set(sessionRef, session); | 1198 | yield* Ref.set(sessionRef, session); |
| 1183 | yield* emitSessionEvent("session/ready", "Codex App Server session ready."); | 1199 | yield* emitSessionEvent("session/ready", "Codex App Server session ready."); |
apps/server/src/provider/Layers/CursorAdapter.test.ts+14-6| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as path from "node:path"; | 2 | import * as path from "node:path"; |
| 2 | import * as os from "node:os"; | 3 | import * as os from "node:os"; |
| 3 | import { chmod, mkdtemp, readFile, writeFile } from "node:fs/promises"; | 4 | import { chmod, mkdtemp, readFile, writeFile } from "node:fs/promises"; |
| ... | @@ -5,7 +6,13 @@ import { fileURLToPath } from "node:url"; | ... | @@ -5,7 +6,13 @@ import { fileURLToPath } from "node:url"; |
| 5 | 6 | ||
| 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 7 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 7 | import { assert, it } from "@effect/vitest"; | 8 | import { assert, it } from "@effect/vitest"; |
| 8 | import { Context, Deferred, Effect, Fiber, Layer, Schema, Stream } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import * as Deferred from "effect/Deferred"; | ||
| 11 | import * as Effect from "effect/Effect"; | ||
| 12 | import * as Fiber from "effect/Fiber"; | ||
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as Schema from "effect/Schema"; | ||
| 15 | import * as Stream from "effect/Stream"; | ||
| 9 | import { createModelSelection } from "@t3tools/shared/model"; | 16 | import { createModelSelection } from "@t3tools/shared/model"; |
| 10 | 17 | ||
| 11 | import { | 18 | import { |
| ... | @@ -98,7 +105,7 @@ async function waitForFileContent(filePath: string, attempts = 40) { | ... | @@ -98,7 +105,7 @@ async function waitForFileContent(filePath: string, attempts = 40) { |
| 98 | return raw; | 105 | return raw; |
| 99 | } | 106 | } |
| 100 | } catch {} | 107 | } catch {} |
| 101 | await new Promise((resolve) => setTimeout(resolve, 50)); | 108 | await Effect.runPromise(Effect.yieldNow); |
| 102 | } | 109 | } |
| 103 | throw new Error(`Timed out waiting for file content at ${filePath}`); | 110 | throw new Error(`Timed out waiting for file content at ${filePath}`); |
| 104 | } | 111 | } |
| ... | @@ -113,10 +120,11 @@ async function waitForFileContent(filePath: string, attempts = 40) { | ... | @@ -113,10 +120,11 @@ async function waitForFileContent(filePath: string, attempts = 40) { |
| 113 | // tests assumed. | 120 | // tests assumed. |
| 114 | const makeResolveCursorSettings = Effect.gen(function* () { | 121 | const makeResolveCursorSettings = Effect.gen(function* () { |
| 115 | const serverSettings = yield* ServerSettingsService; | 122 | const serverSettings = yield* ServerSettingsService; |
| 116 | // @effect-diagnostics effect/returnEffectInGen:off | 123 | return yield* Effect.succeed( |
| 117 | return serverSettings.getSettings.pipe( | 124 | serverSettings.getSettings.pipe( |
| 118 | Effect.map((snapshot) => snapshot.providers.cursor), | 125 | Effect.map((snapshot) => snapshot.providers.cursor), |
| 119 | Effect.orDie, | 126 | Effect.orDie, |
| 127 | ), | ||
| 120 | ); | 128 | ); |
| 121 | }); | 129 | }); |
| 122 | 130 |
apps/server/src/provider/Layers/CursorAdapter.ts+23-21| ... | @@ -3,7 +3,6 @@ | ... | @@ -3,7 +3,6 @@ |
| 3 | * | 3 | * |
| 4 | * @module CursorAdapterLive | 4 | * @module CursorAdapterLive |
| 5 | */ | 5 | */ |
| 6 | import * as nodePath from "node:path"; | ||
| 7 | 6 | ||
| 8 | import { | 7 | import { |
| 9 | ApprovalRequestId, | 8 | ApprovalRequestId, |
| ... | @@ -22,21 +21,21 @@ import { | ... | @@ -22,21 +21,21 @@ import { |
| 22 | type ThreadId, | 21 | type ThreadId, |
| 23 | TurnId, | 22 | TurnId, |
| 24 | } from "@t3tools/contracts"; | 23 | } from "@t3tools/contracts"; |
| 25 | import { | 24 | import * as DateTime from "effect/DateTime"; |
| 26 | DateTime, | 25 | import * as Deferred from "effect/Deferred"; |
| 27 | Deferred, | 26 | import * as Effect from "effect/Effect"; |
| 28 | Effect, | 27 | import * as Exit from "effect/Exit"; |
| 29 | Exit, | 28 | import * as Fiber from "effect/Fiber"; |
| 30 | Fiber, | 29 | import * as FileSystem from "effect/FileSystem"; |
| 31 | FileSystem, | 30 | import * as Option from "effect/Option"; |
| 32 | Option, | 31 | import * as Path from "effect/Path"; |
| 33 | PubSub, | 32 | import * as PubSub from "effect/PubSub"; |
| 34 | Random, | 33 | import * as Random from "effect/Random"; |
| 35 | Scope, | 34 | import * as Schema from "effect/Schema"; |
| 36 | Semaphore, | 35 | import * as Scope from "effect/Scope"; |
| 37 | Stream, | 36 | import * as Semaphore from "effect/Semaphore"; |
| 38 | SynchronizedRef, | 37 | import * as Stream from "effect/Stream"; |
| 39 | } from "effect"; | 38 | import * as SynchronizedRef from "effect/SynchronizedRef"; |
| 40 | import { ChildProcessSpawner } from "effect/unstable/process"; | 39 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 41 | import type * as EffectAcpSchema from "effect-acp/schema"; | 40 | import type * as EffectAcpSchema from "effect-acp/schema"; |
| 42 | 41 | ||
| ... | @@ -306,6 +305,7 @@ export function makeCursorAdapter( | ... | @@ -306,6 +305,7 @@ export function makeCursorAdapter( |
| 306 | return Effect.gen(function* () { | 305 | return Effect.gen(function* () { |
| 307 | const boundInstanceId = options?.instanceId ?? ProviderInstanceId.make("cursor"); | 306 | const boundInstanceId = options?.instanceId ?? ProviderInstanceId.make("cursor"); |
| 308 | const fileSystem = yield* FileSystem.FileSystem; | 307 | const fileSystem = yield* FileSystem.FileSystem; |
| 308 | const path = yield* Path.Path; | ||
| 309 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 309 | const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 310 | const serverConfig = yield* Effect.service(ServerConfig); | 310 | const serverConfig = yield* Effect.service(ServerConfig); |
| 311 | const nativeEventLogger = | 311 | const nativeEventLogger = |
| ... | @@ -358,12 +358,12 @@ export function makeCursorAdapter( | ... | @@ -358,12 +358,12 @@ export function makeCursorAdapter( |
| 358 | ) => | 358 | ) => |
| 359 | Effect.gen(function* () { | 359 | Effect.gen(function* () { |
| 360 | if (!nativeEventLogger) return; | 360 | if (!nativeEventLogger) return; |
| 361 | const observedAt = new Date().toISOString(); | 361 | const observedAt = yield* nowIso; |
| 362 | yield* nativeEventLogger.write( | 362 | yield* nativeEventLogger.write( |
| 363 | { | 363 | { |
| 364 | observedAt, | 364 | observedAt, |
| 365 | event: { | 365 | event: { |
| 366 | id: crypto.randomUUID(), | 366 | id: yield* Random.nextUUIDv4, |
| 367 | kind: "notification", | 367 | kind: "notification", |
| 368 | provider: PROVIDER, | 368 | provider: PROVIDER, |
| 369 | createdAt: observedAt, | 369 | createdAt: observedAt, |
| ... | @@ -390,7 +390,7 @@ export function makeCursorAdapter( | ... | @@ -390,7 +390,7 @@ export function makeCursorAdapter( |
| 390 | method: string, | 390 | method: string, |
| 391 | ) => | 391 | ) => |
| 392 | Effect.gen(function* () { | 392 | Effect.gen(function* () { |
| 393 | const fingerprint = `${ctx.activeTurnId ?? "no-turn"}:${JSON.stringify(payload)}`; | 393 | const fingerprint = `${ctx.activeTurnId ?? "no-turn"}:${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(payload)}`; |
| 394 | if (ctx.lastPlanFingerprint === fingerprint) { | 394 | if (ctx.lastPlanFingerprint === fingerprint) { |
| 395 | return; | 395 | return; |
| 396 | } | 396 | } |
| ... | @@ -460,7 +460,7 @@ export function makeCursorAdapter( | ... | @@ -460,7 +460,7 @@ export function makeCursorAdapter( |
| 460 | }); | 460 | }); |
| 461 | } | 461 | } |
| 462 | 462 | ||
| 463 | const cwd = nodePath.resolve(input.cwd.trim()); | 463 | const cwd = path.resolve(input.cwd.trim()); |
| 464 | const cursorModelSelection = | 464 | const cursorModelSelection = |
| 465 | input.modelSelection?.instanceId === boundInstanceId ? input.modelSelection : undefined; | 465 | input.modelSelection?.instanceId === boundInstanceId ? input.modelSelection : undefined; |
| 466 | const existing = sessions.get(input.threadId); | 466 | const existing = sessions.get(input.threadId); |
| ... | @@ -638,7 +638,9 @@ export function makeCursorAdapter( | ... | @@ -638,7 +638,9 @@ export function makeCursorAdapter( |
| 638 | turnId: ctx?.activeTurnId, | 638 | turnId: ctx?.activeTurnId, |
| 639 | requestId: runtimeRequestId, | 639 | requestId: runtimeRequestId, |
| 640 | permissionRequest, | 640 | permissionRequest, |
| 641 | detail: permissionRequest.detail ?? JSON.stringify(params).slice(0, 2000), | 641 | detail: |
| 642 | permissionRequest.detail ?? | ||
| 643 | Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(params).slice(0, 2000), | ||
| 642 | args: params, | 644 | args: params, |
| 643 | source: "acp.jsonrpc", | 645 | source: "acp.jsonrpc", |
| 644 | method: "session/request_permission", | 646 | method: "session/request_permission", |
apps/server/src/provider/Layers/CursorProvider.test.ts+14-4| ... | @@ -1,7 +1,9 @@ | ... | @@ -1,7 +1,9 @@ |
| 1 | import * as NodeOS from "node:os"; | 1 | import * as NodeOS from "node:os"; |
| 2 | 2 | ||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { Effect, FileSystem, Path } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 5 | import { describe, expect, it } from "vitest"; | 7 | import { describe, expect, it } from "vitest"; |
| 6 | import type * as EffectAcpSchema from "effect-acp/schema"; | 8 | import type * as EffectAcpSchema from "effect-acp/schema"; |
| 7 | import type { CursorSettings, ServerProviderModel } from "@t3tools/contracts"; | 9 | import type { CursorSettings, ServerProviderModel } from "@t3tools/contracts"; |
| ... | @@ -68,12 +70,16 @@ const makeMockAgentWrapper = Effect.fn("makeMockAgentWrapper")(function* ( | ... | @@ -68,12 +70,16 @@ const makeMockAgentWrapper = Effect.fn("makeMockAgentWrapper")(function* ( |
| 68 | prefix: "cursor-provider-mock-", | 70 | prefix: "cursor-provider-mock-", |
| 69 | }); | 71 | }); |
| 70 | const wrapperPath = path.join(dir, "fake-agent.sh"); | 72 | const wrapperPath = path.join(dir, "fake-agent.sh"); |
| 73 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 74 | const bunCommand = JSON.stringify("bun"); | ||
| 75 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 76 | const mockAgentPathJson = JSON.stringify(mockAgentPath); | ||
| 71 | const envExports = Object.entries(extraEnv ?? {}) | 77 | const envExports = Object.entries(extraEnv ?? {}) |
| 72 | .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`) | 78 | .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`) |
| 73 | .join("\n"); | 79 | .join("\n"); |
| 74 | const script = `#!/bin/sh | 80 | const script = `#!/bin/sh |
| 75 | ${envExports} | 81 | ${envExports} |
| 76 | exec ${JSON.stringify("bun")} ${JSON.stringify(mockAgentPath)} "$@" | 82 | exec ${bunCommand} ${mockAgentPathJson} "$@" |
| 77 | `; | 83 | `; |
| 78 | yield* fileSystem.writeFileString(wrapperPath, script); | 84 | yield* fileSystem.writeFileString(wrapperPath, script); |
| 79 | yield* fileSystem.chmod(wrapperPath, 0o755); | 85 | yield* fileSystem.chmod(wrapperPath, 0o755); |
| ... | @@ -89,13 +95,17 @@ const makeMockAgentWithAboutWrapper = Effect.fn("makeMockAgentWithAboutWrapper") | ... | @@ -89,13 +95,17 @@ const makeMockAgentWithAboutWrapper = Effect.fn("makeMockAgentWithAboutWrapper") |
| 89 | prefix: "cursor-provider-about-mock-", | 95 | prefix: "cursor-provider-about-mock-", |
| 90 | }); | 96 | }); |
| 91 | const wrapperPath = path.join(dir, "fake-agent.sh"); | 97 | const wrapperPath = path.join(dir, "fake-agent.sh"); |
| 98 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 99 | const bunCommand = JSON.stringify("bun"); | ||
| 100 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 101 | const mockAgentPathJson = JSON.stringify(mockAgentPath); | ||
| 92 | const script = `#!/bin/sh | 102 | const script = `#!/bin/sh |
| 93 | if [ "$1" = "about" ]; then | 103 | if [ "$1" = "about" ]; then |
| 94 | printf 'CLI Version 2026.04.09-f2b0fcd\\n' | 104 | printf 'CLI Version 2026.04.09-f2b0fcd\\n' |
| 95 | printf 'User Email cursor@example.com\\n' | 105 | printf 'User Email cursor@example.com\\n' |
| 96 | exit 0 | 106 | exit 0 |
| 97 | fi | 107 | fi |
| 98 | exec ${JSON.stringify("bun")} ${JSON.stringify(mockAgentPath)} "$@" | 108 | exec ${bunCommand} ${mockAgentPathJson} "$@" |
| 99 | `; | 109 | `; |
| 100 | yield* fileSystem.writeFileString(wrapperPath, script); | 110 | yield* fileSystem.writeFileString(wrapperPath, script); |
| 101 | yield* fileSystem.chmod(wrapperPath, 0o755); | 111 | yield* fileSystem.chmod(wrapperPath, 0o755); |
| ... | @@ -118,7 +128,7 @@ const waitForFileContent = Effect.fn("waitForFileContent")(function* ( | ... | @@ -118,7 +128,7 @@ const waitForFileContent = Effect.fn("waitForFileContent")(function* ( |
| 118 | } | 128 | } |
| 119 | yield* Effect.sleep("50 millis"); | 129 | yield* Effect.sleep("50 millis"); |
| 120 | } | 130 | } |
| 121 | return yield* Effect.fail(new Error(`Timed out waiting for file content at ${filePath}`)); | 131 | return yield* Effect.die(`Timed out waiting for file content at ${filePath}`); |
| 122 | }); | 132 | }); |
| 123 | 133 | ||
| 124 | const makeProviderStatusEnvFixture = Effect.fn("makeProviderStatusEnvFixture")(function* () { | 134 | const makeProviderStatusEnvFixture = Effect.fn("makeProviderStatusEnvFixture")(function* () { |
apps/server/src/provider/Layers/CursorProvider.ts+35-26| ... | @@ -1,5 +1,4 @@ | ... | @@ -1,5 +1,4 @@ |
| 1 | import * as nodeOs from "node:os"; | 1 | import * as NodeOs from "node:os"; |
| 2 | |||
| 3 | import type { | 2 | import type { |
| 4 | CursorSettings, | 3 | CursorSettings, |
| 5 | ModelCapabilities, | 4 | ModelCapabilities, |
| ... | @@ -11,7 +10,15 @@ import type { | ... | @@ -11,7 +10,15 @@ import type { |
| 11 | } from "@t3tools/contracts"; | 10 | } from "@t3tools/contracts"; |
| 12 | import { ProviderDriverKind } from "@t3tools/contracts"; | 11 | import { ProviderDriverKind } from "@t3tools/contracts"; |
| 13 | import type * as EffectAcpSchema from "effect-acp/schema"; | 12 | import type * as EffectAcpSchema from "effect-acp/schema"; |
| 14 | import { Cause, Effect, Exit, FileSystem, Layer, Option, Path, Result } from "effect"; | 13 | import * as Cause from "effect/Cause"; |
| 14 | import * as DateTime from "effect/DateTime"; | ||
| 15 | import * as Effect from "effect/Effect"; | ||
| 16 | import * as Exit from "effect/Exit"; | ||
| 17 | import * as FileSystem from "effect/FileSystem"; | ||
| 18 | import * as Layer from "effect/Layer"; | ||
| 19 | import * as Option from "effect/Option"; | ||
| 20 | import * as Path from "effect/Path"; | ||
| 21 | import * as Result from "effect/Result"; | ||
| 15 | import { HttpClient } from "effect/unstable/http"; | 22 | import { HttpClient } from "effect/unstable/http"; |
| 16 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 23 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 17 | import { | 24 | import { |
| ... | @@ -58,38 +65,40 @@ export const CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES = { | ... | @@ -58,38 +65,40 @@ export const CURSOR_PARAMETERIZED_MODEL_PICKER_CAPABILITIES = { |
| 58 | 65 | ||
| 59 | export function buildInitialCursorProviderSnapshot( | 66 | export function buildInitialCursorProviderSnapshot( |
| 60 | cursorSettings: CursorSettings, | 67 | cursorSettings: CursorSettings, |
| 61 | ): ServerProviderDraft { | 68 | ): Effect.Effect<ServerProviderDraft> { |
| 62 | const checkedAt = new Date().toISOString(); | 69 | return Effect.gen(function* () { |
| 63 | const models = getCursorFallbackModels(cursorSettings); | 70 | const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); |
| 71 | const models = getCursorFallbackModels(cursorSettings); | ||
| 72 | |||
| 73 | if (!cursorSettings.enabled) { | ||
| 74 | return buildServerProvider({ | ||
| 75 | presentation: CURSOR_PRESENTATION, | ||
| 76 | enabled: false, | ||
| 77 | checkedAt, | ||
| 78 | models, | ||
| 79 | probe: { | ||
| 80 | installed: false, | ||
| 81 | version: null, | ||
| 82 | status: "warning", | ||
| 83 | auth: { status: "unknown" }, | ||
| 84 | message: "Cursor is disabled in T3 Code settings.", | ||
| 85 | }, | ||
| 86 | }); | ||
| 87 | } | ||
| 64 | 88 | ||
| 65 | if (!cursorSettings.enabled) { | ||
| 66 | return buildServerProvider({ | 89 | return buildServerProvider({ |
| 67 | presentation: CURSOR_PRESENTATION, | 90 | presentation: CURSOR_PRESENTATION, |
| 68 | enabled: false, | 91 | enabled: true, |
| 69 | checkedAt, | 92 | checkedAt, |
| 70 | models, | 93 | models, |
| 71 | probe: { | 94 | probe: { |
| 72 | installed: false, | 95 | installed: true, |
| 73 | version: null, | 96 | version: null, |
| 74 | status: "warning", | 97 | status: "warning", |
| 75 | auth: { status: "unknown" }, | 98 | auth: { status: "unknown" }, |
| 76 | message: "Cursor is disabled in T3 Code settings.", | 99 | message: "Checking Cursor Agent availability...", |
| 77 | }, | 100 | }, |
| 78 | }); | 101 | }); |
| 79 | } | ||
| 80 | |||
| 81 | return buildServerProvider({ | ||
| 82 | presentation: CURSOR_PRESENTATION, | ||
| 83 | enabled: true, | ||
| 84 | checkedAt, | ||
| 85 | models, | ||
| 86 | probe: { | ||
| 87 | installed: true, | ||
| 88 | version: null, | ||
| 89 | status: "warning", | ||
| 90 | auth: { status: "unknown" }, | ||
| 91 | message: "Checking Cursor Agent availability...", | ||
| 92 | }, | ||
| 93 | }); | 102 | }); |
| 94 | } | 103 | } |
| 95 | 104 | ||
| ... | @@ -854,7 +863,7 @@ function isCursorAboutJsonFormatUnsupported(result: CommandResult): boolean { | ... | @@ -854,7 +863,7 @@ function isCursorAboutJsonFormatUnsupported(result: CommandResult): boolean { |
| 854 | const readCursorCliConfigChannel = Effect.fn("readCursorCliConfigChannel")(function* () { | 863 | const readCursorCliConfigChannel = Effect.fn("readCursorCliConfigChannel")(function* () { |
| 855 | const fileSystem = yield* FileSystem.FileSystem; | 864 | const fileSystem = yield* FileSystem.FileSystem; |
| 856 | const path = yield* Path.Path; | 865 | const path = yield* Path.Path; |
| 857 | const configPath = path.join(nodeOs.homedir(), ".cursor", "cli-config.json"); | 866 | const configPath = path.join(NodeOs.homedir(), ".cursor", "cli-config.json"); |
| 858 | const raw = yield* fileSystem.readFileString(configPath).pipe(Effect.orElseSucceed(() => "")); | 867 | const raw = yield* fileSystem.readFileString(configPath).pipe(Effect.orElseSucceed(() => "")); |
| 859 | return parseCursorCliConfigChannel(raw); | 868 | return parseCursorCliConfigChannel(raw); |
| 860 | }); | 869 | }); |
| ... | @@ -1085,7 +1094,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")( | ... | @@ -1085,7 +1094,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")( |
| 1085 | never, | 1094 | never, |
| 1086 | ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path | 1095 | ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path |
| 1087 | > { | 1096 | > { |
| 1088 | const checkedAt = new Date().toISOString(); | 1097 | const checkedAt = DateTime.formatIso(yield* DateTime.now); |
| 1089 | const fallbackModels = getCursorFallbackModels(cursorSettings); | 1098 | const fallbackModels = getCursorFallbackModels(cursorSettings); |
| 1090 | 1099 | ||
| 1091 | if (!cursorSettings.enabled) { | 1100 | if (!cursorSettings.enabled) { |
apps/server/src/provider/Layers/EventNdjsonLogger.test.ts+2-1| ... | @@ -1,10 +1,11 @@ | ... | @@ -1,10 +1,11 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
| 4 | 5 | ||
| 5 | import { ThreadId } from "@t3tools/contracts"; | 6 | import { ThreadId } from "@t3tools/contracts"; |
| 6 | import { assert, describe, it } from "@effect/vitest"; | 7 | import { assert, describe, it } from "@effect/vitest"; |
| 7 | import { Effect } from "effect"; | 8 | import * as Effect from "effect/Effect"; |
| 8 | 9 | ||
| 9 | import { makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; | 10 | import { makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; |
| 10 | 11 |
apps/server/src/provider/Layers/EventNdjsonLogger.ts+6-1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | /** | 2 | /** |
| 2 | * Provider event logger helper. | 3 | * Provider event logger helper. |
| 3 | * | 4 | * |
| ... | @@ -10,7 +11,11 @@ import path from "node:path"; | ... | @@ -10,7 +11,11 @@ import path from "node:path"; |
| 10 | 11 | ||
| 11 | import type { ThreadId } from "@t3tools/contracts"; | 12 | import type { ThreadId } from "@t3tools/contracts"; |
| 12 | import { RotatingFileSink } from "@t3tools/shared/logging"; | 13 | import { RotatingFileSink } from "@t3tools/shared/logging"; |
| 13 | import { Effect, Exit, Logger, Scope, SynchronizedRef } from "effect"; | 14 | import * as Effect from "effect/Effect"; |
| 15 | import * as Exit from "effect/Exit"; | ||
| 16 | import * as Logger from "effect/Logger"; | ||
| 17 | import * as Scope from "effect/Scope"; | ||
| 18 | import * as SynchronizedRef from "effect/SynchronizedRef"; | ||
| 14 | 19 | ||
| 15 | import { toSafeThreadAttachmentSegment } from "../../attachmentStore.ts"; | 20 | import { toSafeThreadAttachmentSegment } from "../../attachmentStore.ts"; |
| 16 | 21 |
apps/server/src/provider/Layers/OpenCodeAdapter.test.ts+14-6| ... | @@ -1,8 +1,16 @@ | ... | @@ -1,8 +1,16 @@ |
| 1 | import assert from "node:assert/strict"; | 1 | import assert from "node:assert/strict"; |
| 2 | |||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { it } from "@effect/vitest"; | 3 | import { it } from "@effect/vitest"; |
| 5 | import { Context, Effect, Exit, Fiber, Layer, Option, Schema, Scope, Stream } from "effect"; | 4 | import * as Context from "effect/Context"; |
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as Exit from "effect/Exit"; | ||
| 7 | import * as Fiber from "effect/Fiber"; | ||
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 10 | import * as Schema from "effect/Schema"; | ||
| 11 | import * as Scope from "effect/Scope"; | ||
| 12 | import * as Stream from "effect/Stream"; | ||
| 13 | import * as TestClock from "effect/testing/TestClock"; | ||
| 6 | import { beforeEach } from "vitest"; | 14 | import { beforeEach } from "vitest"; |
| 7 | 15 | ||
| 8 | import { | 16 | import { |
| ... | @@ -216,8 +224,8 @@ beforeEach(() => { | ... | @@ -216,8 +224,8 @@ beforeEach(() => { |
| 216 | runtimeMock.reset(); | 224 | runtimeMock.reset(); |
| 217 | }); | 225 | }); |
| 218 | 226 | ||
| 219 | const sleep = (ms: number) => | 227 | const advanceTestClock = (ms: number) => |
| 220 | Effect.promise(() => new Promise<void>((resolve) => setTimeout(resolve, ms))); | 228 | TestClock.adjust(`${ms} millis`).pipe(Effect.andThen(Effect.yieldNow)); |
| 221 | 229 | ||
| 222 | it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { | 230 | it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { |
| 223 | it.effect("reuses a configured OpenCode server URL instead of spawning a local server", () => | 231 | it.effect("reuses a configured OpenCode server URL instead of spawning a local server", () => |
| ... | @@ -648,7 +656,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { | ... | @@ -648,7 +656,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { |
| 648 | threadId: asThreadId("thread-native-log"), | 656 | threadId: asThreadId("thread-native-log"), |
| 649 | runtimeMode: "full-access", | 657 | runtimeMode: "full-access", |
| 650 | }); | 658 | }); |
| 651 | yield* sleep(10); | 659 | yield* advanceTestClock(10); |
| 652 | return started; | 660 | return started; |
| 653 | }).pipe(Effect.provide(adapterLayer)); | 661 | }).pipe(Effect.provide(adapterLayer)); |
| 654 | 662 | ||
| ... | @@ -736,7 +744,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { | ... | @@ -736,7 +744,7 @@ it.layer(OpenCodeAdapterTestLayer)("OpenCodeAdapterLive", (it) => { |
| 736 | threadId: asThreadId("thread-native-log-failure"), | 744 | threadId: asThreadId("thread-native-log-failure"), |
| 737 | runtimeMode: "full-access", | 745 | runtimeMode: "full-access", |
| 738 | }); | 746 | }); |
| 739 | yield* sleep(10); | 747 | yield* advanceTestClock(10); |
| 740 | return { | 748 | return { |
| 741 | sessions: yield* adapter.listSessions(), | 749 | sessions: yield* adapter.listSessions(), |
| 742 | closeCallsDuringRun: [...runtimeMock.state.closeCalls], | 750 | closeCallsDuringRun: [...runtimeMock.state.closeCalls], |
apps/server/src/provider/Layers/OpenCodeAdapter.ts+52-39| ... | @@ -12,7 +12,15 @@ import { | ... | @@ -12,7 +12,15 @@ import { |
| 12 | TurnId, | 12 | TurnId, |
| 13 | type UserInputQuestion, | 13 | type UserInputQuestion, |
| 14 | } from "@t3tools/contracts"; | 14 | } from "@t3tools/contracts"; |
| 15 | import { Cause, Effect, Exit, Queue, Random, Ref, Scope, Stream } from "effect"; | 15 | import * as Cause from "effect/Cause"; |
| 16 | import * as DateTime from "effect/DateTime"; | ||
| 17 | import * as Effect from "effect/Effect"; | ||
| 18 | import * as Exit from "effect/Exit"; | ||
| 19 | import * as Queue from "effect/Queue"; | ||
| 20 | import * as Random from "effect/Random"; | ||
| 21 | import * as Ref from "effect/Ref"; | ||
| 22 | import * as Scope from "effect/Scope"; | ||
| 23 | import * as Stream from "effect/Stream"; | ||
| 16 | import type { OpencodeClient, Part, PermissionRequest, QuestionRequest } from "@opencode-ai/sdk/v2"; | 24 | import type { OpencodeClient, Part, PermissionRequest, QuestionRequest } from "@opencode-ai/sdk/v2"; |
| 17 | import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; | 25 | import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; |
| 18 | 26 | ||
| ... | @@ -40,6 +48,7 @@ import { | ... | @@ -40,6 +48,7 @@ import { |
| 40 | toOpenCodeQuestionAnswers, | 48 | toOpenCodeQuestionAnswers, |
| 41 | type OpenCodeServerConnection, | 49 | type OpenCodeServerConnection, |
| 42 | } from "../opencodeRuntime.ts"; | 50 | } from "../opencodeRuntime.ts"; |
| 51 | import * as Option from "effect/Option"; | ||
| 43 | 52 | ||
| 44 | const PROVIDER = ProviderDriverKind.make("opencode"); | 53 | const PROVIDER = ProviderDriverKind.make("opencode"); |
| 45 | 54 | ||
| ... | @@ -95,9 +104,7 @@ export interface OpenCodeAdapterLiveOptions { | ... | @@ -95,9 +104,7 @@ export interface OpenCodeAdapterLiveOptions { |
| 95 | readonly nativeEventLogger?: EventNdjsonLogger; | 104 | readonly nativeEventLogger?: EventNdjsonLogger; |
| 96 | } | 105 | } |
| 97 | 106 | ||
| 98 | function nowIso(): string { | 107 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 99 | return new Date().toISOString(); | ||
| 100 | } | ||
| 101 | 108 | ||
| 102 | /** | 109 | /** |
| 103 | * Map a tagged OpenCodeRuntimeError produced by {@link runOpenCodeSdk} into | 110 | * Map a tagged OpenCodeRuntimeError produced by {@link runOpenCodeSdk} into |
| ... | @@ -140,12 +147,14 @@ const buildEventBase = (input: { | ... | @@ -140,12 +147,14 @@ const buildEventBase = (input: { |
| 140 | "eventId" | "provider" | "threadId" | "createdAt" | "turnId" | "itemId" | "requestId" | "raw" | 147 | "eventId" | "provider" | "threadId" | "createdAt" | "turnId" | "itemId" | "requestId" | "raw" |
| 141 | > | 148 | > |
| 142 | > => | 149 | > => |
| 143 | Random.nextUUIDv4.pipe( | 150 | Effect.gen(function* () { |
| 144 | Effect.map((uuid) => ({ | 151 | const uuid = yield* Random.nextUUIDv4; |
| 152 | const createdAt = input.createdAt ?? (yield* nowIso); | ||
| 153 | return { | ||
| 145 | eventId: EventId.make(uuid), | 154 | eventId: EventId.make(uuid), |
| 146 | provider: PROVIDER, | 155 | provider: PROVIDER, |
| 147 | threadId: input.threadId, | 156 | threadId: input.threadId, |
| 148 | createdAt: input.createdAt ?? nowIso(), | 157 | createdAt, |
| 149 | ...(input.turnId ? { turnId: input.turnId } : {}), | 158 | ...(input.turnId ? { turnId: input.turnId } : {}), |
| 150 | ...(input.itemId ? { itemId: RuntimeItemId.make(input.itemId) } : {}), | 159 | ...(input.itemId ? { itemId: RuntimeItemId.make(input.itemId) } : {}), |
| 151 | ...(input.requestId ? { requestId: RuntimeRequestId.make(input.requestId) } : {}), | 160 | ...(input.requestId ? { requestId: RuntimeRequestId.make(input.requestId) } : {}), |
| ... | @@ -157,8 +166,8 @@ const buildEventBase = (input: { | ... | @@ -157,8 +166,8 @@ const buildEventBase = (input: { |
| 157 | }, | 166 | }, |
| 158 | } | 167 | } |
| 159 | : {}), | 168 | : {}), |
| 160 | })), | 169 | }; |
| 161 | ); | 170 | }); |
| 162 | 171 | ||
| 163 | function toToolLifecycleItemType(toolName: string): ToolLifecycleItemType { | 172 | function toToolLifecycleItemType(toolName: string): ToolLifecycleItemType { |
| 164 | const normalized = toolName.toLowerCase(); | 173 | const normalized = toolName.toLowerCase(); |
| ... | @@ -347,12 +356,13 @@ export function appendOpenCodeAssistantTextDelta( | ... | @@ -347,12 +356,13 @@ export function appendOpenCodeAssistantTextDelta( |
| 347 | }; | 356 | }; |
| 348 | } | 357 | } |
| 349 | 358 | ||
| 350 | function isoFromEpochMs(value: number | undefined): string | undefined { | 359 | const isoFromEpochMs = (value: number) => |
| 351 | if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) { | 360 | DateTime.make(value).pipe( |
| 352 | return undefined; | 361 | Option.match({ |
| 353 | } | 362 | onNone: () => undefined, |
| 354 | return new Date(value).toISOString(); | 363 | onSome: DateTime.formatIso, |
| 355 | } | 364 | }), |
| 365 | ); | ||
| 356 | 366 | ||
| 357 | function messageRoleForPart( | 367 | function messageRoleForPart( |
| 358 | context: OpenCodeSessionContext, | 368 | context: OpenCodeSessionContext, |
| ... | @@ -408,21 +418,24 @@ function updateProviderSession( | ... | @@ -408,21 +418,24 @@ function updateProviderSession( |
| 408 | readonly clearActiveTurnId?: boolean; | 418 | readonly clearActiveTurnId?: boolean; |
| 409 | readonly clearLastError?: boolean; | 419 | readonly clearLastError?: boolean; |
| 410 | }, | 420 | }, |
| 411 | ): ProviderSession { | 421 | ): Effect.Effect<ProviderSession> { |
| 412 | const nextSession = { | 422 | return Effect.gen(function* () { |
| 413 | ...context.session, | 423 | const updatedAt = yield* nowIso; |
| 414 | ...patch, | 424 | const nextSession = { |
| 415 | updatedAt: nowIso(), | 425 | ...context.session, |
| 416 | } as ProviderSession & Record<string, unknown>; | 426 | ...patch, |
| 417 | const mutableSession = nextSession as Record<string, unknown>; | 427 | updatedAt, |
| 418 | if (options?.clearActiveTurnId) { | 428 | } as ProviderSession & Record<string, unknown>; |
| 419 | delete mutableSession.activeTurnId; | 429 | const mutableSession = nextSession as Record<string, unknown>; |
| 420 | } | 430 | if (options?.clearActiveTurnId) { |
| 421 | if (options?.clearLastError) { | 431 | delete mutableSession.activeTurnId; |
| 422 | delete mutableSession.lastError; | 432 | } |
| 423 | } | 433 | if (options?.clearLastError) { |
| 424 | context.session = nextSession; | 434 | delete mutableSession.lastError; |
| 425 | return nextSession; | 435 | } |
| 436 | context.session = nextSession; | ||
| 437 | return nextSession; | ||
| 438 | }); | ||
| 426 | } | 439 | } |
| 427 | 440 | ||
| 428 | const stopOpenCodeContext = Effect.fn("stopOpenCodeContext")(function* ( | 441 | const stopOpenCodeContext = Effect.fn("stopOpenCodeContext")(function* ( |
| ... | @@ -592,8 +605,8 @@ export function makeOpenCodeAdapter( | ... | @@ -592,8 +605,8 @@ export function makeOpenCodeAdapter( |
| 592 | turnId, | 605 | turnId, |
| 593 | itemId: part.id, | 606 | itemId: part.id, |
| 594 | createdAt: | 607 | createdAt: |
| 595 | part.type === "text" || part.type === "reasoning" | 608 | (part.type === "text" || part.type === "reasoning") && part.time !== undefined |
| 596 | ? isoFromEpochMs(part.time?.start) | 609 | ? isoFromEpochMs(part.time.start) |
| 597 | : undefined, | 610 | : undefined, |
| 598 | raw, | 611 | raw, |
| 599 | })), | 612 | })), |
| ... | @@ -642,7 +655,7 @@ export function makeOpenCodeAdapter( | ... | @@ -642,7 +655,7 @@ export function makeOpenCodeAdapter( |
| 642 | 655 | ||
| 643 | const turnId = context.activeTurnId; | 656 | const turnId = context.activeTurnId; |
| 644 | yield* writeNativeEventBestEffort(context.session.threadId, { | 657 | yield* writeNativeEventBestEffort(context.session.threadId, { |
| 645 | observedAt: nowIso(), | 658 | observedAt: yield* nowIso, |
| 646 | event: { | 659 | event: { |
| 647 | provider: PROVIDER, | 660 | provider: PROVIDER, |
| 648 | threadId: context.session.threadId, | 661 | threadId: context.session.threadId, |
| ... | @@ -863,7 +876,7 @@ export function makeOpenCodeAdapter( | ... | @@ -863,7 +876,7 @@ export function makeOpenCodeAdapter( |
| 863 | 876 | ||
| 864 | case "session.status": { | 877 | case "session.status": { |
| 865 | if (event.properties.status.type === "busy") { | 878 | if (event.properties.status.type === "busy") { |
| 866 | updateProviderSession(context, { | 879 | yield* updateProviderSession(context, { |
| 867 | status: "running", | 880 | status: "running", |
| 868 | activeTurnId: turnId, | 881 | activeTurnId: turnId, |
| 869 | }); | 882 | }); |
| ... | @@ -887,7 +900,7 @@ export function makeOpenCodeAdapter( | ... | @@ -887,7 +900,7 @@ export function makeOpenCodeAdapter( |
| 887 | 900 | ||
| 888 | if (event.properties.status.type === "idle" && turnId) { | 901 | if (event.properties.status.type === "idle" && turnId) { |
| 889 | context.activeTurnId = undefined; | 902 | context.activeTurnId = undefined; |
| 890 | updateProviderSession(context, { status: "ready" }, { clearActiveTurnId: true }); | 903 | yield* updateProviderSession(context, { status: "ready" }, { clearActiveTurnId: true }); |
| 891 | yield* emit({ | 904 | yield* emit({ |
| 892 | ...(yield* buildEventBase({ | 905 | ...(yield* buildEventBase({ |
| 893 | threadId: context.session.threadId, | 906 | threadId: context.session.threadId, |
| ... | @@ -907,7 +920,7 @@ export function makeOpenCodeAdapter( | ... | @@ -907,7 +920,7 @@ export function makeOpenCodeAdapter( |
| 907 | const message = sessionErrorMessage(event.properties.error); | 920 | const message = sessionErrorMessage(event.properties.error); |
| 908 | const activeTurnId = context.activeTurnId; | 921 | const activeTurnId = context.activeTurnId; |
| 909 | context.activeTurnId = undefined; | 922 | context.activeTurnId = undefined; |
| 910 | updateProviderSession( | 923 | yield* updateProviderSession( |
| 911 | context, | 924 | context, |
| 912 | { | 925 | { |
| 913 | status: "error", | 926 | status: "error", |
| ... | @@ -1084,7 +1097,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1084,7 +1097,7 @@ export function makeOpenCodeAdapter( |
| 1084 | return raceWinner.session; | 1097 | return raceWinner.session; |
| 1085 | } | 1098 | } |
| 1086 | 1099 | ||
| 1087 | const createdAt = nowIso(); | 1100 | const createdAt = yield* nowIso; |
| 1088 | const session: ProviderSession = { | 1101 | const session: ProviderSession = { |
| 1089 | provider: PROVIDER, | 1102 | provider: PROVIDER, |
| 1090 | providerInstanceId: boundInstanceId, | 1103 | providerInstanceId: boundInstanceId, |
| ... | @@ -1185,7 +1198,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1185,7 +1198,7 @@ export function makeOpenCodeAdapter( |
| 1185 | context.activeTurnId = turnId; | 1198 | context.activeTurnId = turnId; |
| 1186 | context.activeAgent = agent ?? (input.interactionMode === "plan" ? "plan" : undefined); | 1199 | context.activeAgent = agent ?? (input.interactionMode === "plan" ? "plan" : undefined); |
| 1187 | context.activeVariant = variant; | 1200 | context.activeVariant = variant; |
| 1188 | updateProviderSession( | 1201 | yield* updateProviderSession( |
| 1189 | context, | 1202 | context, |
| 1190 | { | 1203 | { |
| 1191 | status: "running", | 1204 | status: "running", |
| ... | @@ -1223,7 +1236,7 @@ export function makeOpenCodeAdapter( | ... | @@ -1223,7 +1236,7 @@ export function makeOpenCodeAdapter( |
| 1223 | context.activeTurnId = undefined; | 1236 | context.activeTurnId = undefined; |
| 1224 | context.activeAgent = undefined; | 1237 | context.activeAgent = undefined; |
| 1225 | context.activeVariant = undefined; | 1238 | context.activeVariant = undefined; |
| 1226 | updateProviderSession( | 1239 | yield* updateProviderSession( |
| 1227 | context, | 1240 | context, |
| 1228 | { | 1241 | { |
| 1229 | status: "ready", | 1242 | status: "ready", |
apps/server/src/provider/Layers/OpenCodeProvider.test.ts+3-1| ... | @@ -2,7 +2,9 @@ import assert from "node:assert/strict"; | ... | @@ -2,7 +2,9 @@ import assert from "node:assert/strict"; |
| 2 | 2 | ||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { it } from "@effect/vitest"; | 4 | import { it } from "@effect/vitest"; |
| 5 | import { Effect, Layer, Schema } from "effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Layer from "effect/Layer"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 6 | import { beforeEach } from "vitest"; | 8 | import { beforeEach } from "vitest"; |
| 7 | 9 | ||
| 8 | import { OpenCodeSettings } from "@t3tools/contracts"; | 10 | import { OpenCodeSettings } from "@t3tools/contracts"; |
apps/server/src/provider/Layers/OpenCodeProvider.ts+35-31| ... | @@ -4,7 +4,10 @@ import { | ... | @@ -4,7 +4,10 @@ import { |
| 4 | type OpenCodeSettings, | 4 | type OpenCodeSettings, |
| 5 | type ServerProviderModel, | 5 | type ServerProviderModel, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Cause, Data, Effect } from "effect"; | 7 | import * as Cause from "effect/Cause"; |
| 8 | import * as Data from "effect/Data"; | ||
| 9 | import * as DateTime from "effect/DateTime"; | ||
| 10 | import * as Effect from "effect/Effect"; | ||
| 8 | 11 | ||
| 9 | import { createModelCapabilities } from "@t3tools/shared/model"; | 12 | import { createModelCapabilities } from "@t3tools/shared/model"; |
| 10 | import { | 13 | import { |
| ... | @@ -249,19 +252,38 @@ function flattenOpenCodeModels(input: OpenCodeInventory): ReadonlyArray<ServerPr | ... | @@ -249,19 +252,38 @@ function flattenOpenCodeModels(input: OpenCodeInventory): ReadonlyArray<ServerPr |
| 249 | 252 | ||
| 250 | export const makePendingOpenCodeProvider = ( | 253 | export const makePendingOpenCodeProvider = ( |
| 251 | openCodeSettings: OpenCodeSettings, | 254 | openCodeSettings: OpenCodeSettings, |
| 252 | ): ServerProviderDraft => { | 255 | ): Effect.Effect<ServerProviderDraft> => |
| 253 | const checkedAt = new Date().toISOString(); | 256 | Effect.gen(function* () { |
| 254 | const models = providerModelsFromSettings( | 257 | const checkedAt = yield* Effect.map(DateTime.now, DateTime.formatIso); |
| 255 | [], | 258 | const models = providerModelsFromSettings( |
| 256 | PROVIDER, | 259 | [], |
| 257 | openCodeSettings.customModels, | 260 | PROVIDER, |
| 258 | DEFAULT_OPENCODE_MODEL_CAPABILITIES, | 261 | openCodeSettings.customModels, |
| 259 | ); | 262 | DEFAULT_OPENCODE_MODEL_CAPABILITIES, |
| 263 | ); | ||
| 264 | |||
| 265 | if (!openCodeSettings.enabled) { | ||
| 266 | return buildServerProvider({ | ||
| 267 | presentation: OPENCODE_PRESENTATION, | ||
| 268 | enabled: false, | ||
| 269 | checkedAt, | ||
| 270 | models, | ||
| 271 | probe: { | ||
| 272 | installed: false, | ||
| 273 | version: null, | ||
| 274 | status: "warning", | ||
| 275 | auth: { status: "unknown" }, | ||
| 276 | message: | ||
| 277 | openCodeSettings.serverUrl.trim().length > 0 | ||
| 278 | ? "OpenCode is disabled in T3 Code settings. A server URL is configured." | ||
| 279 | : "OpenCode is disabled in T3 Code settings.", | ||
| 280 | }, | ||
| 281 | }); | ||
| 282 | } | ||
| 260 | 283 | ||
| 261 | if (!openCodeSettings.enabled) { | ||
| 262 | return buildServerProvider({ | 284 | return buildServerProvider({ |
| 263 | presentation: OPENCODE_PRESENTATION, | 285 | presentation: OPENCODE_PRESENTATION, |
| 264 | enabled: false, | 286 | enabled: true, |
| 265 | checkedAt, | 287 | checkedAt, |
| 266 | models, | 288 | models, |
| 267 | probe: { | 289 | probe: { |
| ... | @@ -269,28 +291,10 @@ export const makePendingOpenCodeProvider = ( | ... | @@ -269,28 +291,10 @@ export const makePendingOpenCodeProvider = ( |
| 269 | version: null, | 291 | version: null, |
| 270 | status: "warning", | 292 | status: "warning", |
| 271 | auth: { status: "unknown" }, | 293 | auth: { status: "unknown" }, |
| 272 | message: | 294 | message: "OpenCode provider status has not been checked in this session yet.", |
| 273 | openCodeSettings.serverUrl.trim().length > 0 | ||
| 274 | ? "OpenCode is disabled in T3 Code settings. A server URL is configured." | ||
| 275 | : "OpenCode is disabled in T3 Code settings.", | ||
| 276 | }, | 295 | }, |
| 277 | }); | 296 | }); |
| 278 | } | ||
| 279 | |||
| 280 | return buildServerProvider({ | ||
| 281 | presentation: OPENCODE_PRESENTATION, | ||
| 282 | enabled: true, | ||
| 283 | checkedAt, | ||
| 284 | models, | ||
| 285 | probe: { | ||
| 286 | installed: false, | ||
| 287 | version: null, | ||
| 288 | status: "warning", | ||
| 289 | auth: { status: "unknown" }, | ||
| 290 | message: "OpenCode provider status has not been checked in this session yet.", | ||
| 291 | }, | ||
| 292 | }); | 297 | }); |
| 293 | }; | ||
| 294 | 298 | ||
| 295 | export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatus")(function* ( | 299 | export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatus")(function* ( |
| 296 | openCodeSettings: OpenCodeSettings, | 300 | openCodeSettings: OpenCodeSettings, |
| ... | @@ -298,7 +302,7 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu | ... | @@ -298,7 +302,7 @@ export const checkOpenCodeProviderStatus = Effect.fn("checkOpenCodeProviderStatu |
| 298 | environment: NodeJS.ProcessEnv = process.env, | 302 | environment: NodeJS.ProcessEnv = process.env, |
| 299 | ): Effect.fn.Return<ServerProviderDraft, never, OpenCodeRuntime> { | 303 | ): Effect.fn.Return<ServerProviderDraft, never, OpenCodeRuntime> { |
| 300 | const openCodeRuntime = yield* OpenCodeRuntime; | 304 | const openCodeRuntime = yield* OpenCodeRuntime; |
| 301 | const checkedAt = new Date().toISOString(); | 305 | const checkedAt = DateTime.formatIso(yield* DateTime.now); |
| 302 | const customModels = openCodeSettings.customModels; | 306 | const customModels = openCodeSettings.customModels; |
| 303 | const isExternalServer = openCodeSettings.serverUrl.trim().length > 0; | 307 | const isExternalServer = openCodeSettings.serverUrl.trim().length > 0; |
| 304 | 308 |
apps/server/src/provider/Layers/ProviderAdapterRegistry.test.ts+4-1| ... | @@ -5,7 +5,10 @@ import { | ... | @@ -5,7 +5,10 @@ import { |
| 5 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { it, assert, vi } from "@effect/vitest"; | 6 | import { it, assert, vi } from "@effect/vitest"; |
| 7 | 7 | ||
| 8 | import { Effect, Layer, PubSub, Stream } from "effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Layer from "effect/Layer"; | ||
| 10 | import * as PubSub from "effect/PubSub"; | ||
| 11 | import * as Stream from "effect/Stream"; | ||
| 9 | 12 | ||
| 10 | import type { ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts"; | 13 | import type { ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts"; |
| 11 | import type { CodexAdapterShape } from "../Services/CodexAdapter.ts"; | 14 | import type { CodexAdapterShape } from "../Services/CodexAdapter.ts"; |
apps/server/src/provider/Layers/ProviderAdapterRegistry.ts+2-1| ... | @@ -20,7 +20,8 @@ import { | ... | @@ -20,7 +20,8 @@ import { |
| 20 | ProviderInstanceId, | 20 | ProviderInstanceId, |
| 21 | type ProviderDriverKind, | 21 | type ProviderDriverKind, |
| 22 | } from "@t3tools/contracts"; | 22 | } from "@t3tools/contracts"; |
| 23 | import { Effect, Layer } from "effect"; | 23 | import * as Effect from "effect/Effect"; |
| 24 | import * as Layer from "effect/Layer"; | ||
| 24 | 25 | ||
| 25 | import { ProviderUnsupportedError } from "../Errors.ts"; | 26 | import { ProviderUnsupportedError } from "../Errors.ts"; |
| 26 | import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts"; | 27 | import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts"; |
apps/server/src/provider/Layers/ProviderEventLoggers.ts+3-1| ... | @@ -27,7 +27,9 @@ | ... | @@ -27,7 +27,9 @@ |
| 27 | * | 27 | * |
| 28 | * @module provider/Layers/ProviderEventLoggers | 28 | * @module provider/Layers/ProviderEventLoggers |
| 29 | */ | 29 | */ |
| 30 | import { Context, Effect, Layer } from "effect"; | 30 | import * as Context from "effect/Context"; |
| 31 | import * as Effect from "effect/Effect"; | ||
| 32 | import * as Layer from "effect/Layer"; | ||
| 31 | 33 | ||
| 32 | import { ServerConfig } from "../../config.ts"; | 34 | import { ServerConfig } from "../../config.ts"; |
| 33 | import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; | 35 | import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; |
apps/server/src/provider/Layers/ProviderInstanceRegistryHydration.ts+3-1| ... | @@ -47,7 +47,9 @@ import { | ... | @@ -47,7 +47,9 @@ import { |
| 47 | type ProviderInstanceConfigMap, | 47 | type ProviderInstanceConfigMap, |
| 48 | ServerSettings, | 48 | ServerSettings, |
| 49 | } from "@t3tools/contracts"; | 49 | } from "@t3tools/contracts"; |
| 50 | import { Effect, Layer, Stream } from "effect"; | 50 | import * as Effect from "effect/Effect"; |
| 51 | import * as Layer from "effect/Layer"; | ||
| 52 | import * as Stream from "effect/Stream"; | ||
| 51 | 53 | ||
| 52 | import { ServerSettingsService } from "../../serverSettings.ts"; | 54 | import { ServerSettingsService } from "../../serverSettings.ts"; |
| 53 | import { BUILT_IN_DRIVERS, type BuiltInDriversEnv } from "../builtInDrivers.ts"; | 55 | import { BUILT_IN_DRIVERS, type BuiltInDriversEnv } from "../builtInDrivers.ts"; |
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts+2-1| ... | @@ -33,7 +33,8 @@ import { | ... | @@ -33,7 +33,8 @@ import { |
| 33 | type ProviderInstanceConfigMap, | 33 | type ProviderInstanceConfigMap, |
| 34 | ProviderInstanceId, | 34 | ProviderInstanceId, |
| 35 | } from "@t3tools/contracts"; | 35 | } from "@t3tools/contracts"; |
| 36 | import { Effect, Layer } from "effect"; | 36 | import * as Effect from "effect/Effect"; |
| 37 | import * as Layer from "effect/Layer"; | ||
| 37 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; | 38 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; |
| 38 | 39 | ||
| 39 | import { ServerConfig } from "../../config.ts"; | 40 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.ts+13-4| ... | @@ -40,7 +40,16 @@ import { | ... | @@ -40,7 +40,16 @@ import { |
| 40 | type ProviderDriverKind, | 40 | type ProviderDriverKind, |
| 41 | type ServerProvider, | 41 | type ServerProvider, |
| 42 | } from "@t3tools/contracts"; | 42 | } from "@t3tools/contracts"; |
| 43 | import { Context, Effect, Equal, Exit, Layer, PubSub, Ref, Schema, Scope, Stream } from "effect"; | 43 | import * as Context from "effect/Context"; |
| 44 | import * as Effect from "effect/Effect"; | ||
| 45 | import * as Equal from "effect/Equal"; | ||
| 46 | import * as Exit from "effect/Exit"; | ||
| 47 | import * as Layer from "effect/Layer"; | ||
| 48 | import * as PubSub from "effect/PubSub"; | ||
| 49 | import * as Ref from "effect/Ref"; | ||
| 50 | import * as Schema from "effect/Schema"; | ||
| 51 | import * as Scope from "effect/Scope"; | ||
| 52 | import * as Stream from "effect/Stream"; | ||
| 44 | 53 | ||
| 45 | import { buildUnavailableProviderSnapshot } from "../unavailableProviderSnapshot.ts"; | 54 | import { buildUnavailableProviderSnapshot } from "../unavailableProviderSnapshot.ts"; |
| 46 | import { | 55 | import { |
| ... | @@ -115,7 +124,7 @@ const buildEntry = <R>(input: { | ... | @@ -115,7 +124,7 @@ const buildEntry = <R>(input: { |
| 115 | if (!driver) { | 124 | if (!driver) { |
| 116 | return { | 125 | return { |
| 117 | kind: "unavailable" as const, | 126 | kind: "unavailable" as const, |
| 118 | snapshot: buildUnavailableProviderSnapshot({ | 127 | snapshot: yield* buildUnavailableProviderSnapshot({ |
| 119 | driverKind: entry.driver, | 128 | driverKind: entry.driver, |
| 120 | instanceId, | 129 | instanceId, |
| 121 | displayName: entry.displayName, | 130 | displayName: entry.displayName, |
| ... | @@ -137,7 +146,7 @@ const buildEntry = <R>(input: { | ... | @@ -137,7 +146,7 @@ const buildEntry = <R>(input: { |
| 137 | }); | 146 | }); |
| 138 | return { | 147 | return { |
| 139 | kind: "unavailable" as const, | 148 | kind: "unavailable" as const, |
| 140 | snapshot: buildUnavailableProviderSnapshot({ | 149 | snapshot: yield* buildUnavailableProviderSnapshot({ |
| 141 | driverKind: entry.driver, | 150 | driverKind: entry.driver, |
| 142 | instanceId, | 151 | instanceId, |
| 143 | displayName: entry.displayName, | 152 | displayName: entry.displayName, |
| ... | @@ -175,7 +184,7 @@ const buildEntry = <R>(input: { | ... | @@ -175,7 +184,7 @@ const buildEntry = <R>(input: { |
| 175 | yield* Scope.close(childScope, Exit.void).pipe(Effect.ignore); | 184 | yield* Scope.close(childScope, Exit.void).pipe(Effect.ignore); |
| 176 | return { | 185 | return { |
| 177 | kind: "unavailable" as const, | 186 | kind: "unavailable" as const, |
| 178 | snapshot: buildUnavailableProviderSnapshot({ | 187 | snapshot: yield* buildUnavailableProviderSnapshot({ |
| 179 | driverKind: entry.driver, | 188 | driverKind: entry.driver, |
| 180 | instanceId, | 189 | instanceId, |
| 181 | displayName: entry.displayName, | 190 | displayName: entry.displayName, |
apps/server/src/provider/Layers/ProviderRegistry.test.ts+23-26| ... | @@ -1,6 +1,15 @@ | ... | @@ -1,6 +1,15 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { describe, it, assert, live } from "@effect/vitest"; | 2 | import { describe, it, assert } from "@effect/vitest"; |
| 3 | import { Effect, Exit, Layer, PubSub, Ref, Schema, Scope, Sink, Stream } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Exit from "effect/Exit"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as PubSub from "effect/PubSub"; | ||
| 7 | import * as Ref from "effect/Ref"; | ||
| 8 | import * as Schema from "effect/Schema"; | ||
| 9 | import * as Scope from "effect/Scope"; | ||
| 10 | import * as Sink from "effect/Sink"; | ||
| 11 | import * as Stream from "effect/Stream"; | ||
| 12 | import * as TestClock from "effect/testing/TestClock"; | ||
| 4 | import * as CodexErrors from "effect-codex-app-server/errors"; | 13 | import * as CodexErrors from "effect-codex-app-server/errors"; |
| 5 | import { | 14 | import { |
| 6 | ClaudeSettings, | 15 | ClaudeSettings, |
| ... | @@ -696,7 +705,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T | ... | @@ -696,7 +705,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T |
| 696 | attempt < 50 && cachedProvider?.checkedAt !== refreshedProvider.checkedAt; | 705 | attempt < 50 && cachedProvider?.checkedAt !== refreshedProvider.checkedAt; |
| 697 | attempt += 1 | 706 | attempt += 1 |
| 698 | ) { | 707 | ) { |
| 699 | yield* Effect.sleep("10 millis"); | 708 | yield* TestClock.adjust("10 millis"); |
| 709 | yield* Effect.yieldNow; | ||
| 700 | cachedProvider = yield* readProviderStatusCache(filePath); | 710 | cachedProvider = yield* readProviderStatusCache(filePath); |
| 701 | } | 711 | } |
| 702 | 712 | ||
| ... | @@ -840,8 +850,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T | ... | @@ -840,8 +850,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T |
| 840 | const changes = yield* PubSub.unbounded<void>(); | 850 | const changes = yield* PubSub.unbounded<void>(); |
| 841 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]); | 851 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]); |
| 842 | const failNextList = yield* Ref.make(false); | 852 | const failNextList = yield* Ref.make(false); |
| 843 | const wait = (millis: number) => | 853 | const wait = () => Effect.yieldNow; |
| 844 | Effect.promise<void>(() => new Promise((resolve) => setTimeout(resolve, millis))); | ||
| 845 | const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, { | 854 | const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, { |
| 846 | getInstance: (instanceId) => | 855 | getInstance: (instanceId) => |
| 847 | Ref.get(instancesRef).pipe( | 856 | Ref.get(instancesRef).pipe( |
| ... | @@ -892,7 +901,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T | ... | @@ -892,7 +901,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T |
| 892 | !providers.some((provider) => provider.instanceId === claudeInstanceId); | 901 | !providers.some((provider) => provider.instanceId === claudeInstanceId); |
| 893 | attempt += 1 | 902 | attempt += 1 |
| 894 | ) { | 903 | ) { |
| 895 | yield* wait(10); | 904 | yield* wait(); |
| 896 | providers = yield* registry.getProviders; | 905 | providers = yield* registry.getProviders; |
| 897 | } | 906 | } |
| 898 | 907 | ||
| ... | @@ -917,7 +926,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T | ... | @@ -917,7 +926,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T |
| 917 | // assertions below fail. | 926 | // assertions below fail. |
| 918 | it.effect("propagates real Codex probe failures to the aggregator at boot", () => | 927 | it.effect("propagates real Codex probe failures to the aggregator at boot", () => |
| 919 | Effect.gen(function* () { | 928 | Effect.gen(function* () { |
| 920 | const missingBinary = `t3code_codex_missing_${process.pid}_${Date.now()}`; | 929 | const missingBinary = `t3code_codex_missing_`; |
| 921 | const serverSettings = yield* makeMutableServerSettingsService( | 930 | const serverSettings = yield* makeMutableServerSettingsService( |
| 922 | Schema.decodeSync(ServerSettings)( | 931 | Schema.decodeSync(ServerSettings)( |
| 923 | deepMerge(DEFAULT_SERVER_SETTINGS, { | 932 | deepMerge(DEFAULT_SERVER_SETTINGS, { |
| ... | @@ -1013,20 +1022,10 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T | ... | @@ -1013,20 +1022,10 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T |
| 1013 | // rebuilt instance's refresh (previous bug mode), the aggregator | 1022 | // rebuilt instance's refresh (previous bug mode), the aggregator |
| 1014 | // keeps the old snapshot and this test fails. | 1023 | // keeps the old snapshot and this test fails. |
| 1015 | // | 1024 | // |
| 1016 | // `live` (imported from `@effect/vitest`) is used instead of | 1025 | it.effect("re-probes when settings change the codex binaryPath", () => |
| 1017 | // `it.effect` so real timers coordinate the fibres that drive the | ||
| 1018 | // settings → reconcile → sync pipeline. Under `it.effect`'s | ||
| 1019 | // TestClock, `Effect.sleep` blocks until `TestClock.adjust`, which | ||
| 1020 | // would require this test to reach into the internals of the | ||
| 1021 | // reconcile pipeline to advance it step by step. | ||
| 1022 | // | ||
| 1023 | // The nested `it` handed to `it.layer(…, (it) => …)` is the | ||
| 1024 | // `MethodsNonLive` variant and therefore lacks `.live`; the | ||
| 1025 | // top-level `live` export from `@effect/vitest` is the equivalent. | ||
| 1026 | live("re-probes when settings change the codex binaryPath", () => | ||
| 1027 | Effect.gen(function* () { | 1026 | Effect.gen(function* () { |
| 1028 | const firstMissing = `t3code_codex_first_${process.pid}_${Date.now()}`; | 1027 | const firstMissing = `t3code_codex_first_`; |
| 1029 | const secondMissing = `t3code_codex_second_${process.pid}_${Date.now()}`; | 1028 | const secondMissing = `t3code_codex_second_`; |
| 1030 | const serverSettings = yield* makeMutableServerSettingsService( | 1029 | const serverSettings = yield* makeMutableServerSettingsService( |
| 1031 | Schema.decodeSync(ServerSettings)( | 1030 | Schema.decodeSync(ServerSettings)( |
| 1032 | deepMerge(DEFAULT_SERVER_SETTINGS, { | 1031 | deepMerge(DEFAULT_SERVER_SETTINGS, { |
| ... | @@ -1093,11 +1092,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T | ... | @@ -1093,11 +1092,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T |
| 1093 | }, | 1092 | }, |
| 1094 | }); | 1093 | }); |
| 1095 | 1094 | ||
| 1096 | // Poll with real timers (via `it.live`) until `checkedAt` | 1095 | // Poll with TestClock until `checkedAt` advances or we hit a |
| 1097 | // advances or we hit a generous 3-second ceiling. Anything | 1096 | // generous virtual 3-second ceiling. |
| 1098 | // slower than that is a regression — the real probe fails | ||
| 1099 | // fast on ENOENT, and the reconcile + sync pipeline is | ||
| 1100 | // purely in-process. | ||
| 1101 | const refreshed = yield* Effect.gen(function* () { | 1097 | const refreshed = yield* Effect.gen(function* () { |
| 1102 | for (let attempts = 0; attempts < 60; attempts += 1) { | 1098 | for (let attempts = 0; attempts < 60; attempts += 1) { |
| 1103 | const providers = yield* registry.getProviders; | 1099 | const providers = yield* registry.getProviders; |
| ... | @@ -1105,7 +1101,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T | ... | @@ -1105,7 +1101,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T |
| 1105 | if (codex !== undefined && codex.checkedAt !== initialCheckedAt) { | 1101 | if (codex !== undefined && codex.checkedAt !== initialCheckedAt) { |
| 1106 | return providers; | 1102 | return providers; |
| 1107 | } | 1103 | } |
| 1108 | yield* Effect.sleep("50 millis"); | 1104 | yield* TestClock.adjust("50 millis"); |
| 1105 | yield* Effect.yieldNow; | ||
| 1109 | } | 1106 | } |
| 1110 | return yield* registry.getProviders; | 1107 | return yield* registry.getProviders; |
| 1111 | }); | 1108 | }); |
apps/server/src/provider/Layers/ProviderRegistry.ts+9-1| ... | @@ -29,7 +29,15 @@ import { | ... | @@ -29,7 +29,15 @@ import { |
| 29 | type ServerProvider, | 29 | type ServerProvider, |
| 30 | type ServerProviderUpdateState, | 30 | type ServerProviderUpdateState, |
| 31 | } from "@t3tools/contracts"; | 31 | } from "@t3tools/contracts"; |
| 32 | import { Cause, Effect, Equal, FileSystem, Layer, Path, PubSub, Ref, Stream } from "effect"; | 32 | import * as Cause from "effect/Cause"; |
| 33 | import * as Effect from "effect/Effect"; | ||
| 34 | import * as Equal from "effect/Equal"; | ||
| 35 | import * as FileSystem from "effect/FileSystem"; | ||
| 36 | import * as Layer from "effect/Layer"; | ||
| 37 | import * as Path from "effect/Path"; | ||
| 38 | import * as PubSub from "effect/PubSub"; | ||
| 39 | import * as Ref from "effect/Ref"; | ||
| 40 | import * as Stream from "effect/Stream"; | ||
| 33 | import * as Semaphore from "effect/Semaphore"; | 41 | import * as Semaphore from "effect/Semaphore"; |
| 34 | 42 | ||
| 35 | import { ServerConfig } from "../../config.ts"; | 43 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/provider/Layers/ProviderService.test.ts+31-20| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
| ... | @@ -21,7 +22,17 @@ import { | ... | @@ -21,7 +22,17 @@ import { |
| 21 | import { createModelSelection } from "@t3tools/shared/model"; | 22 | import { createModelSelection } from "@t3tools/shared/model"; |
| 22 | import { it, assert, vi } from "@effect/vitest"; | 23 | import { it, assert, vi } from "@effect/vitest"; |
| 23 | 24 | ||
| 24 | import { Effect, Exit, Fiber, Layer, Metric, Option, PubSub, Ref, Scope, Stream } from "effect"; | 25 | import * as Effect from "effect/Effect"; |
| 26 | import * as Exit from "effect/Exit"; | ||
| 27 | import * as Fiber from "effect/Fiber"; | ||
| 28 | import * as Layer from "effect/Layer"; | ||
| 29 | import * as Metric from "effect/Metric"; | ||
| 30 | import * as Option from "effect/Option"; | ||
| 31 | import * as PubSub from "effect/PubSub"; | ||
| 32 | import * as Ref from "effect/Ref"; | ||
| 33 | import * as Scope from "effect/Scope"; | ||
| 34 | import * as Stream from "effect/Stream"; | ||
| 35 | import * as TestClock from "effect/testing/TestClock"; | ||
| 25 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 36 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 26 | 37 | ||
| 27 | import { | 38 | import { |
| ... | @@ -83,7 +94,7 @@ function makeFakeCodexAdapter(provider: ProviderDriverKind = CODEX_DRIVER) { | ... | @@ -83,7 +94,7 @@ function makeFakeCodexAdapter(provider: ProviderDriverKind = CODEX_DRIVER) { |
| 83 | 94 | ||
| 84 | const startSession = vi.fn((input: ProviderSessionStartInput) => | 95 | const startSession = vi.fn((input: ProviderSessionStartInput) => |
| 85 | Effect.sync(() => { | 96 | Effect.sync(() => { |
| 86 | const now = new Date().toISOString(); | 97 | const now = "2026-01-01T00:00:00.000Z"; |
| 87 | const session: ProviderSession = { | 98 | const session: ProviderSession = { |
| 88 | provider, | 99 | provider, |
| 89 | ...(input.providerInstanceId !== undefined | 100 | ...(input.providerInstanceId !== undefined |
| ... | @@ -246,8 +257,8 @@ function makeFakeCodexAdapter(provider: ProviderDriverKind = CODEX_DRIVER) { | ... | @@ -246,8 +257,8 @@ function makeFakeCodexAdapter(provider: ProviderDriverKind = CODEX_DRIVER) { |
| 246 | }; | 257 | }; |
| 247 | } | 258 | } |
| 248 | 259 | ||
| 249 | const sleep = (ms: number) => | 260 | const advanceTestClock = (ms: number) => |
| 250 | Effect.promise(() => new Promise<void>((resolve) => setTimeout(resolve, ms))); | 261 | TestClock.adjust(`${ms} millis`).pipe(Effect.andThen(Effect.yieldNow)); |
| 251 | 262 | ||
| 252 | const hasMetricSnapshot = ( | 263 | const hasMetricSnapshot = ( |
| 253 | snapshots: ReadonlyArray<Metric.Metric.Snapshot>, | 264 | snapshots: ReadonlyArray<Metric.Metric.Snapshot>, |
| ... | @@ -572,18 +583,18 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se | ... | @@ -572,18 +583,18 @@ it.effect("ProviderServiceLive writes canonical events to the emitting thread se |
| 572 | 583 | ||
| 573 | yield* Effect.gen(function* () { | 584 | yield* Effect.gen(function* () { |
| 574 | yield* ProviderService; | 585 | yield* ProviderService; |
| 575 | yield* sleep(10); | 586 | yield* advanceTestClock(10); |
| 576 | codex.emit({ | 587 | codex.emit({ |
| 577 | eventId: asEventId("evt-canonical-thread-segment"), | 588 | eventId: asEventId("evt-canonical-thread-segment"), |
| 578 | provider: ProviderDriverKind.make("codex"), | 589 | provider: ProviderDriverKind.make("codex"), |
| 579 | threadId: asThreadId("thread-canonical-thread-segment"), | 590 | threadId: asThreadId("thread-canonical-thread-segment"), |
| 580 | createdAt: new Date().toISOString(), | 591 | createdAt: "2026-01-01T00:00:00.000Z", |
| 581 | type: "turn.completed", | 592 | type: "turn.completed", |
| 582 | payload: { | 593 | payload: { |
| 583 | state: "completed", | 594 | state: "completed", |
| 584 | }, | 595 | }, |
| 585 | }); | 596 | }); |
| 586 | yield* sleep(20); | 597 | yield* advanceTestClock(20); |
| 587 | }).pipe(Effect.provide(providerLayer)); | 598 | }).pipe(Effect.provide(providerLayer)); |
| 588 | 599 | ||
| 589 | assert.equal(canonicalEvents.length, 1); | 600 | assert.equal(canonicalEvents.length, 1); |
| ... | @@ -704,7 +715,7 @@ it.effect( | ... | @@ -704,7 +715,7 @@ it.effect( |
| 704 | ...existing, | 715 | ...existing, |
| 705 | status: "ready", | 716 | status: "ready", |
| 706 | resumeCursor: updatedResumeCursor, | 717 | resumeCursor: updatedResumeCursor, |
| 707 | updatedAt: new Date(Date.now() + 1_000).toISOString(), | 718 | updatedAt: "2026-01-01T00:00:01.000Z", |
| 708 | })); | 719 | })); |
| 709 | return session; | 720 | return session; |
| 710 | }).pipe(Effect.provide(firstProviderLayer)); | 721 | }).pipe(Effect.provide(firstProviderLayer)); |
| ... | @@ -1403,20 +1414,20 @@ fanout.layer("ProviderServiceLive fanout", (it) => { | ... | @@ -1403,20 +1414,20 @@ fanout.layer("ProviderServiceLive fanout", (it) => { |
| 1403 | const consumer = yield* Stream.runForEach(provider.streamEvents, (event) => | 1414 | const consumer = yield* Stream.runForEach(provider.streamEvents, (event) => |
| 1404 | Ref.update(eventsRef, (current) => [...current, event]), | 1415 | Ref.update(eventsRef, (current) => [...current, event]), |
| 1405 | ).pipe(Effect.forkChild); | 1416 | ).pipe(Effect.forkChild); |
| 1406 | yield* sleep(50); | 1417 | yield* advanceTestClock(50); |
| 1407 | 1418 | ||
| 1408 | const completedEvent: LegacyProviderRuntimeEvent = { | 1419 | const completedEvent: LegacyProviderRuntimeEvent = { |
| 1409 | type: "turn.completed", | 1420 | type: "turn.completed", |
| 1410 | eventId: asEventId("evt-1"), | 1421 | eventId: asEventId("evt-1"), |
| 1411 | provider: ProviderDriverKind.make("codex"), | 1422 | provider: ProviderDriverKind.make("codex"), |
| 1412 | createdAt: new Date().toISOString(), | 1423 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1413 | threadId: session.threadId, | 1424 | threadId: session.threadId, |
| 1414 | turnId: asTurnId("turn-1"), | 1425 | turnId: asTurnId("turn-1"), |
| 1415 | status: "completed", | 1426 | status: "completed", |
| 1416 | }; | 1427 | }; |
| 1417 | 1428 | ||
| 1418 | fanout.codex.emit(completedEvent); | 1429 | fanout.codex.emit(completedEvent); |
| 1419 | yield* sleep(50); | 1430 | yield* advanceTestClock(50); |
| 1420 | 1431 | ||
| 1421 | const events = yield* Ref.get(eventsRef); | 1432 | const events = yield* Ref.get(eventsRef); |
| 1422 | yield* Fiber.interrupt(consumer); | 1433 | yield* Fiber.interrupt(consumer); |
| ... | @@ -1450,13 +1461,13 @@ fanout.layer("ProviderServiceLive fanout", (it) => { | ... | @@ -1450,13 +1461,13 @@ fanout.layer("ProviderServiceLive fanout", (it) => { |
| 1450 | Stream.runForEach((event) => Ref.update(receivedRef, (current) => [...current, event])), | 1461 | Stream.runForEach((event) => Ref.update(receivedRef, (current) => [...current, event])), |
| 1451 | Effect.forkChild, | 1462 | Effect.forkChild, |
| 1452 | ); | 1463 | ); |
| 1453 | yield* sleep(50); | 1464 | yield* advanceTestClock(50); |
| 1454 | 1465 | ||
| 1455 | fanout.codex.emit({ | 1466 | fanout.codex.emit({ |
| 1456 | type: "tool.started", | 1467 | type: "tool.started", |
| 1457 | eventId: asEventId("evt-seq-1"), | 1468 | eventId: asEventId("evt-seq-1"), |
| 1458 | provider: ProviderDriverKind.make("codex"), | 1469 | provider: ProviderDriverKind.make("codex"), |
| 1459 | createdAt: new Date().toISOString(), | 1470 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1460 | threadId: session.threadId, | 1471 | threadId: session.threadId, |
| 1461 | turnId: asTurnId("turn-1"), | 1472 | turnId: asTurnId("turn-1"), |
| 1462 | toolKind: "command", | 1473 | toolKind: "command", |
| ... | @@ -1466,7 +1477,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { | ... | @@ -1466,7 +1477,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { |
| 1466 | type: "tool.completed", | 1477 | type: "tool.completed", |
| 1467 | eventId: asEventId("evt-seq-2"), | 1478 | eventId: asEventId("evt-seq-2"), |
| 1468 | provider: ProviderDriverKind.make("codex"), | 1479 | provider: ProviderDriverKind.make("codex"), |
| 1469 | createdAt: new Date().toISOString(), | 1480 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1470 | threadId: session.threadId, | 1481 | threadId: session.threadId, |
| 1471 | turnId: asTurnId("turn-1"), | 1482 | turnId: asTurnId("turn-1"), |
| 1472 | toolKind: "command", | 1483 | toolKind: "command", |
| ... | @@ -1476,7 +1487,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { | ... | @@ -1476,7 +1487,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { |
| 1476 | type: "turn.completed", | 1487 | type: "turn.completed", |
| 1477 | eventId: asEventId("evt-seq-3"), | 1488 | eventId: asEventId("evt-seq-3"), |
| 1478 | provider: ProviderDriverKind.make("codex"), | 1489 | provider: ProviderDriverKind.make("codex"), |
| 1479 | createdAt: new Date().toISOString(), | 1490 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1480 | threadId: session.threadId, | 1491 | threadId: session.threadId, |
| 1481 | turnId: asTurnId("turn-1"), | 1492 | turnId: asTurnId("turn-1"), |
| 1482 | status: "completed", | 1493 | status: "completed", |
| ... | @@ -1515,14 +1526,14 @@ fanout.layer("ProviderServiceLive fanout", (it) => { | ... | @@ -1515,14 +1526,14 @@ fanout.layer("ProviderServiceLive fanout", (it) => { |
| 1515 | Stream.runForEach(() => Effect.fail("listener crash")), | 1526 | Stream.runForEach(() => Effect.fail("listener crash")), |
| 1516 | Effect.forkChild, | 1527 | Effect.forkChild, |
| 1517 | ); | 1528 | ); |
| 1518 | yield* sleep(50); | 1529 | yield* advanceTestClock(50); |
| 1519 | 1530 | ||
| 1520 | const events: ReadonlyArray<LegacyProviderRuntimeEvent> = [ | 1531 | const events: ReadonlyArray<LegacyProviderRuntimeEvent> = [ |
| 1521 | { | 1532 | { |
| 1522 | type: "tool.completed", | 1533 | type: "tool.completed", |
| 1523 | eventId: asEventId("evt-ordered-1"), | 1534 | eventId: asEventId("evt-ordered-1"), |
| 1524 | provider: ProviderDriverKind.make("codex"), | 1535 | provider: ProviderDriverKind.make("codex"), |
| 1525 | createdAt: new Date().toISOString(), | 1536 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1526 | threadId: session.threadId, | 1537 | threadId: session.threadId, |
| 1527 | turnId: asTurnId("turn-1"), | 1538 | turnId: asTurnId("turn-1"), |
| 1528 | toolKind: "command", | 1539 | toolKind: "command", |
| ... | @@ -1533,7 +1544,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { | ... | @@ -1533,7 +1544,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { |
| 1533 | type: "message.delta", | 1544 | type: "message.delta", |
| 1534 | eventId: asEventId("evt-ordered-2"), | 1545 | eventId: asEventId("evt-ordered-2"), |
| 1535 | provider: ProviderDriverKind.make("codex"), | 1546 | provider: ProviderDriverKind.make("codex"), |
| 1536 | createdAt: new Date().toISOString(), | 1547 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1537 | threadId: session.threadId, | 1548 | threadId: session.threadId, |
| 1538 | turnId: asTurnId("turn-1"), | 1549 | turnId: asTurnId("turn-1"), |
| 1539 | delta: "hello", | 1550 | delta: "hello", |
| ... | @@ -1542,7 +1553,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { | ... | @@ -1542,7 +1553,7 @@ fanout.layer("ProviderServiceLive fanout", (it) => { |
| 1542 | type: "turn.completed", | 1553 | type: "turn.completed", |
| 1543 | eventId: asEventId("evt-ordered-3"), | 1554 | eventId: asEventId("evt-ordered-3"), |
| 1544 | provider: ProviderDriverKind.make("codex"), | 1555 | provider: ProviderDriverKind.make("codex"), |
| 1545 | createdAt: new Date().toISOString(), | 1556 | createdAt: "2026-01-01T00:00:00.000Z", |
| 1546 | threadId: session.threadId, | 1557 | threadId: session.threadId, |
| 1547 | turnId: asTurnId("turn-1"), | 1558 | turnId: asTurnId("turn-1"), |
| 1548 | status: "completed", | 1559 | status: "completed", |
| ... | @@ -1758,7 +1769,7 @@ validation.layer("ProviderServiceLive validation", (it) => { | ... | @@ -1758,7 +1769,7 @@ validation.layer("ProviderServiceLive validation", (it) => { |
| 1758 | 1769 | ||
| 1759 | validation.codex.startSession.mockImplementationOnce((input: ProviderSessionStartInput) => | 1770 | validation.codex.startSession.mockImplementationOnce((input: ProviderSessionStartInput) => |
| 1760 | Effect.sync(() => { | 1771 | Effect.sync(() => { |
| 1761 | const now = new Date().toISOString(); | 1772 | const now = "2026-01-01T00:00:00.000Z"; |
| 1762 | return { | 1773 | return { |
| 1763 | provider: ProviderDriverKind.make("codex"), | 1774 | provider: ProviderDriverKind.make("codex"), |
| 1764 | status: "ready", | 1775 | status: "ready", |
apps/server/src/provider/Layers/ProviderService.ts+37-20| ... | @@ -24,7 +24,16 @@ import { | ... | @@ -24,7 +24,16 @@ import { |
| 24 | type ProviderRuntimeEvent, | 24 | type ProviderRuntimeEvent, |
| 25 | type ProviderSession, | 25 | type ProviderSession, |
| 26 | } from "@t3tools/contracts"; | 26 | } from "@t3tools/contracts"; |
| 27 | import { Cause, Effect, Layer, Option, PubSub, Ref, Schema, SchemaIssue, Stream } from "effect"; | 27 | import * as Cause from "effect/Cause"; |
| 28 | import * as DateTime from "effect/DateTime"; | ||
| 29 | import * as Effect from "effect/Effect"; | ||
| 30 | import * as Layer from "effect/Layer"; | ||
| 31 | import * as Option from "effect/Option"; | ||
| 32 | import * as PubSub from "effect/PubSub"; | ||
| 33 | import * as Ref from "effect/Ref"; | ||
| 34 | import * as Schema from "effect/Schema"; | ||
| 35 | import * as SchemaIssue from "effect/SchemaIssue"; | ||
| 36 | import * as Stream from "effect/Stream"; | ||
| 28 | 37 | ||
| 29 | import { | 38 | import { |
| 30 | increment, | 39 | increment, |
| ... | @@ -199,6 +208,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( | ... | @@ -199,6 +208,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( |
| 199 | const registry = yield* ProviderAdapterRegistry; | 208 | const registry = yield* ProviderAdapterRegistry; |
| 200 | const directory = yield* ProviderSessionDirectory; | 209 | const directory = yield* ProviderSessionDirectory; |
| 201 | const runtimeEventPubSub = yield* PubSub.unbounded<ProviderRuntimeEvent>(); | 210 | const runtimeEventPubSub = yield* PubSub.unbounded<ProviderRuntimeEvent>(); |
| 211 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); | ||
| 202 | 212 | ||
| 203 | const publishRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect<void> => | 213 | const publishRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect<void> => |
| 204 | Effect.succeed(event).pipe( | 214 | Effect.succeed(event).pipe( |
| ... | @@ -655,7 +665,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( | ... | @@ -655,7 +665,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( |
| 655 | ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}), | 665 | ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}), |
| 656 | activeTurnId: turn.turnId, | 666 | activeTurnId: turn.turnId, |
| 657 | lastRuntimeEvent: "provider.sendTurn", | 667 | lastRuntimeEvent: "provider.sendTurn", |
| 658 | lastRuntimeEventAt: new Date().toISOString(), | 668 | lastRuntimeEventAt: yield* nowIso, |
| 659 | }, | 669 | }, |
| 660 | }); | 670 | }); |
| 661 | yield* analytics.record("provider.turn.sent", { | 671 | yield* analytics.record("provider.turn.sent", { |
| ... | @@ -977,27 +987,34 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( | ... | @@ -977,27 +987,34 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( |
| 977 | ), | 987 | ), |
| 978 | ).pipe(Effect.map((sessionsByAdapter) => sessionsByAdapter.flatMap((sessions) => sessions))); | 988 | ).pipe(Effect.map((sessionsByAdapter) => sessionsByAdapter.flatMap((sessions) => sessions))); |
| 979 | yield* Effect.forEach(activeSessions, (session) => | 989 | yield* Effect.forEach(activeSessions, (session) => |
| 980 | upsertSessionBinding(session, session.threadId, { | 990 | Effect.flatMap(nowIso, (lastRuntimeEventAt) => |
| 981 | lastRuntimeEvent: "provider.stopAll", | 991 | upsertSessionBinding(session, session.threadId, { |
| 982 | lastRuntimeEventAt: new Date().toISOString(), | 992 | lastRuntimeEvent: "provider.stopAll", |
| 983 | }), | 993 | lastRuntimeEventAt, |
| 994 | }), | ||
| 995 | ), | ||
| 984 | ).pipe(Effect.asVoid); | 996 | ).pipe(Effect.asVoid); |
| 985 | yield* Effect.forEach(currentAdapters, ([, adapter]) => adapter.stopAll()).pipe(Effect.asVoid); | 997 | yield* Effect.forEach(currentAdapters, ([, adapter]) => adapter.stopAll()).pipe(Effect.asVoid); |
| 986 | const bindings = yield* directory.listBindings().pipe(Effect.orElseSucceed(() => [])); | 998 | const bindings = yield* directory.listBindings().pipe(Effect.orElseSucceed(() => [])); |
| 987 | yield* Effect.forEach(bindings, (binding) => { | 999 | yield* Effect.forEach(bindings, (binding) => |
| 988 | const providerInstanceId = dieOnMissingBindingInstanceId("ProviderService.stopAll", binding); | 1000 | Effect.gen(function* () { |
| 989 | return directory.upsert({ | 1001 | const providerInstanceId = dieOnMissingBindingInstanceId( |
| 990 | threadId: binding.threadId, | 1002 | "ProviderService.stopAll", |
| 991 | provider: binding.provider, | 1003 | binding, |
| 992 | providerInstanceId, | 1004 | ); |
| 993 | status: "stopped", | 1005 | return yield* directory.upsert({ |
| 994 | runtimePayload: { | 1006 | threadId: binding.threadId, |
| 995 | activeTurnId: null, | 1007 | provider: binding.provider, |
| 996 | lastRuntimeEvent: "provider.stopAll", | 1008 | providerInstanceId, |
| 997 | lastRuntimeEventAt: new Date().toISOString(), | 1009 | status: "stopped", |
| 998 | }, | 1010 | runtimePayload: { |
| 999 | }); | 1011 | activeTurnId: null, |
| 1000 | }).pipe(Effect.asVoid); | 1012 | lastRuntimeEvent: "provider.stopAll", |
| 1013 | lastRuntimeEventAt: yield* nowIso, | ||
| 1014 | }, | ||
| 1015 | }); | ||
| 1016 | }), | ||
| 1017 | ).pipe(Effect.asVoid); | ||
| 1001 | yield* analytics.record("provider.sessions.stopped_all", { | 1018 | yield* analytics.record("provider.sessions.stopped_all", { |
| 1002 | sessionCount: threadIds.length, | 1019 | sessionCount: threadIds.length, |
| 1003 | }); | 1020 | }); |
apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts+5-2| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import os from "node:os"; | 3 | import os from "node:os"; |
| 3 | import path from "node:path"; | 4 | import path from "node:path"; |
| ... | @@ -6,7 +7,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; | ... | @@ -6,7 +7,9 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 6 | import { ProviderDriverKind, ThreadId } from "@t3tools/contracts"; | 7 | import { ProviderDriverKind, ThreadId } from "@t3tools/contracts"; |
| 7 | import { it, assert } from "@effect/vitest"; | 8 | import { it, assert } from "@effect/vitest"; |
| 8 | import { assertSome } from "@effect/vitest/utils"; | 9 | import { assertSome } from "@effect/vitest/utils"; |
| 9 | import { Effect, Layer, Option } from "effect"; | 10 | import * as Effect from "effect/Effect"; |
| 11 | import * as Layer from "effect/Layer"; | ||
| 12 | import * as Option from "effect/Option"; | ||
| 10 | import * as SqlClient from "effect/unstable/sql/SqlClient"; | 13 | import * as SqlClient from "effect/unstable/sql/SqlClient"; |
| 11 | 14 | ||
| 12 | import { | 15 | import { |
| ... | @@ -209,7 +212,7 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL | ... | @@ -209,7 +212,7 @@ it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryL |
| 209 | adapterKey: "claudeAgent", | 212 | adapterKey: "claudeAgent", |
| 210 | runtimeMode: "full-access", | 213 | runtimeMode: "full-access", |
| 211 | status: "running", | 214 | status: "running", |
| 212 | lastSeenAt: new Date().toISOString(), | 215 | lastSeenAt: "2026-01-01T00:00:00.000Z", |
| 213 | resumeCursor: null, | 216 | resumeCursor: null, |
| 214 | runtimePayload: null, | 217 | runtimePayload: null, |
| 215 | }); | 218 | }); |
apps/server/src/provider/Layers/ProviderSessionDirectory.ts+6-2| ... | @@ -1,5 +1,9 @@ | ... | @@ -1,5 +1,9 @@ |
| 1 | import { defaultInstanceIdForDriver, ProviderDriverKind, type ThreadId } from "@t3tools/contracts"; | 1 | import { defaultInstanceIdForDriver, ProviderDriverKind, type ThreadId } from "@t3tools/contracts"; |
| 2 | import { Effect, Layer, Option, Schema } from "effect"; | 2 | import * as DateTime from "effect/DateTime"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 3 | 7 | ||
| 4 | import type { ProviderSessionRuntime } from "../../persistence/Services/ProviderSessionRuntime.ts"; | 8 | import type { ProviderSessionRuntime } from "../../persistence/Services/ProviderSessionRuntime.ts"; |
| 5 | import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts"; | 9 | import { ProviderSessionRuntimeRepository } from "../../persistence/Services/ProviderSessionRuntime.ts"; |
| ... | @@ -110,7 +114,7 @@ const makeProviderSessionDirectory = Effect.gen(function* () { | ... | @@ -110,7 +114,7 @@ const makeProviderSessionDirectory = Effect.gen(function* () { |
| 110 | }); | 114 | }); |
| 111 | } | 115 | } |
| 112 | 116 | ||
| 113 | const now = new Date().toISOString(); | 117 | const now = DateTime.formatIso(yield* DateTime.now); |
| 114 | const providerChanged = | 118 | const providerChanged = |
| 115 | existingRuntime !== undefined && existingRuntime.providerName !== binding.provider; | 119 | existingRuntime !== undefined && existingRuntime.providerName !== binding.provider; |
| 116 | const providerInstanceId = | 120 | const providerInstanceId = |
apps/server/src/provider/Layers/ProviderSessionReaper.test.ts+26-14| ... | @@ -6,7 +6,15 @@ import { | ... | @@ -6,7 +6,15 @@ import { |
| 6 | ProviderDriverKind, | 6 | ProviderDriverKind, |
| 7 | ProviderInstanceId, | 7 | ProviderInstanceId, |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { Effect, Exit, Layer, ManagedRuntime, Option, Scope, Stream } from "effect"; | 9 | import * as Clock from "effect/Clock"; |
| 10 | import * as DateTime from "effect/DateTime"; | ||
| 11 | import * as Effect from "effect/Effect"; | ||
| 12 | import * as Exit from "effect/Exit"; | ||
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as ManagedRuntime from "effect/ManagedRuntime"; | ||
| 15 | import * as Option from "effect/Option"; | ||
| 16 | import * as Scope from "effect/Scope"; | ||
| 17 | import * as Stream from "effect/Stream"; | ||
| 10 | import { afterEach, describe, expect, it, vi } from "vitest"; | 18 | import { afterEach, describe, expect, it, vi } from "vitest"; |
| 11 | 19 | ||
| 12 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; | 20 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; |
| ... | @@ -28,21 +36,25 @@ async function waitFor( | ... | @@ -28,21 +36,25 @@ async function waitFor( |
| 28 | predicate: () => boolean | Promise<boolean>, | 36 | predicate: () => boolean | Promise<boolean>, |
| 29 | timeoutMs = 2_000, | 37 | timeoutMs = 2_000, |
| 30 | ): Promise<void> { | 38 | ): Promise<void> { |
| 31 | const deadline = Date.now() + timeoutMs; | 39 | const deadline = (await Effect.runPromise(Clock.currentTimeMillis)) + timeoutMs; |
| 32 | const poll = async (): Promise<void> => { | 40 | const poll = async (): Promise<void> => { |
| 33 | if (await predicate()) { | 41 | if (await predicate()) { |
| 34 | return; | 42 | return; |
| 35 | } | 43 | } |
| 36 | if (Date.now() >= deadline) { | 44 | if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) { |
| 37 | throw new Error("Timed out waiting for expectation."); | 45 | throw new Error("Timed out waiting for expectation."); |
| 38 | } | 46 | } |
| 39 | await new Promise((resolve) => setTimeout(resolve, 10)); | 47 | await Effect.runPromise(Effect.yieldNow); |
| 40 | return poll(); | 48 | return poll(); |
| 41 | }; | 49 | }; |
| 42 | 50 | ||
| 43 | return poll(); | 51 | return poll(); |
| 44 | } | 52 | } |
| 45 | 53 | ||
| 54 | const drainFibers = Effect.forEach(Array.from({ length: 10 }), () => Effect.yieldNow, { | ||
| 55 | discard: true, | ||
| 56 | }); | ||
| 57 | |||
| 46 | const unsupported = () => Effect.die(new Error("Unsupported provider call in test")) as never; | 58 | const unsupported = () => Effect.die(new Error("Unsupported provider call in test")) as never; |
| 47 | 59 | ||
| 48 | function makeReadModel( | 60 | function makeReadModel( |
| ... | @@ -59,7 +71,7 @@ function makeReadModel( | ... | @@ -59,7 +71,7 @@ function makeReadModel( |
| 59 | } | null; | 71 | } | null; |
| 60 | }>, | 72 | }>, |
| 61 | ) { | 73 | ) { |
| 62 | const now = new Date().toISOString(); | 74 | const now = "2026-01-01T00:00:00.000Z"; |
| 63 | const projectId = ProjectId.make("project-provider-session-reaper"); | 75 | const projectId = ProjectId.make("project-provider-session-reaper"); |
| 64 | 76 | ||
| 65 | return { | 77 | return { |
| ... | @@ -207,7 +219,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -207,7 +219,7 @@ describe("ProviderSessionReaper", () => { |
| 207 | 219 | ||
| 208 | it("reaps stale persisted sessions without active turns", async () => { | 220 | it("reaps stale persisted sessions without active turns", async () => { |
| 209 | const threadId = ThreadId.make("thread-reaper-stale"); | 221 | const threadId = ThreadId.make("thread-reaper-stale"); |
| 210 | const now = new Date().toISOString(); | 222 | const now = "2026-01-01T00:00:00.000Z"; |
| 211 | const harness = await createHarness({ | 223 | const harness = await createHarness({ |
| 212 | readModel: makeReadModel([ | 224 | readModel: makeReadModel([ |
| 213 | { | 225 | { |
| ... | @@ -255,7 +267,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -255,7 +267,7 @@ describe("ProviderSessionReaper", () => { |
| 255 | it("skips stale sessions when the thread still has an active turn", async () => { | 267 | it("skips stale sessions when the thread still has an active turn", async () => { |
| 256 | const threadId = ThreadId.make("thread-reaper-active-turn"); | 268 | const threadId = ThreadId.make("thread-reaper-active-turn"); |
| 257 | const turnId = TurnId.make("turn-reaper-active"); | 269 | const turnId = TurnId.make("turn-reaper-active"); |
| 258 | const now = new Date().toISOString(); | 270 | const now = "2026-01-01T00:00:00.000Z"; |
| 259 | const harness = await createHarness({ | 271 | const harness = await createHarness({ |
| 260 | readModel: makeReadModel([ | 272 | readModel: makeReadModel([ |
| 261 | { | 273 | { |
| ... | @@ -293,7 +305,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -293,7 +305,7 @@ describe("ProviderSessionReaper", () => { |
| 293 | const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); | 305 | const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); |
| 294 | scope = await Effect.runPromise(Scope.make("sequential")); | 306 | scope = await Effect.runPromise(Scope.make("sequential")); |
| 295 | await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); | 307 | await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); |
| 296 | await new Promise((resolve) => setTimeout(resolve, 50)); | 308 | await Effect.runPromise(drainFibers); |
| 297 | 309 | ||
| 298 | expect(harness.stopSession).not.toHaveBeenCalled(); | 310 | expect(harness.stopSession).not.toHaveBeenCalled(); |
| 299 | const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); | 311 | const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); |
| ... | @@ -302,7 +314,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -302,7 +314,7 @@ describe("ProviderSessionReaper", () => { |
| 302 | 314 | ||
| 303 | it("does not reap sessions that are still within the inactivity threshold", async () => { | 315 | it("does not reap sessions that are still within the inactivity threshold", async () => { |
| 304 | const threadId = ThreadId.make("thread-reaper-fresh"); | 316 | const threadId = ThreadId.make("thread-reaper-fresh"); |
| 305 | const now = new Date().toISOString(); | 317 | const now = DateTime.formatIso(await Effect.runPromise(DateTime.now)); |
| 306 | const harness = await createHarness({ | 318 | const harness = await createHarness({ |
| 307 | readModel: makeReadModel([ | 319 | readModel: makeReadModel([ |
| 308 | { | 320 | { |
| ... | @@ -340,7 +352,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -340,7 +352,7 @@ describe("ProviderSessionReaper", () => { |
| 340 | const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); | 352 | const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); |
| 341 | scope = await Effect.runPromise(Scope.make("sequential")); | 353 | scope = await Effect.runPromise(Scope.make("sequential")); |
| 342 | await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); | 354 | await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); |
| 343 | await new Promise((resolve) => setTimeout(resolve, 50)); | 355 | await Effect.runPromise(drainFibers); |
| 344 | 356 | ||
| 345 | expect(harness.stopSession).not.toHaveBeenCalled(); | 357 | expect(harness.stopSession).not.toHaveBeenCalled(); |
| 346 | const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); | 358 | const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); |
| ... | @@ -349,7 +361,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -349,7 +361,7 @@ describe("ProviderSessionReaper", () => { |
| 349 | 361 | ||
| 350 | it("skips persisted sessions that are already marked stopped", async () => { | 362 | it("skips persisted sessions that are already marked stopped", async () => { |
| 351 | const threadId = ThreadId.make("thread-reaper-stopped"); | 363 | const threadId = ThreadId.make("thread-reaper-stopped"); |
| 352 | const now = new Date().toISOString(); | 364 | const now = "2026-01-01T00:00:00.000Z"; |
| 353 | const harness = await createHarness({ | 365 | const harness = await createHarness({ |
| 354 | readModel: makeReadModel([ | 366 | readModel: makeReadModel([ |
| 355 | { | 367 | { |
| ... | @@ -387,7 +399,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -387,7 +399,7 @@ describe("ProviderSessionReaper", () => { |
| 387 | const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); | 399 | const reaper = await runtime!.runPromise(Effect.service(ProviderSessionReaper)); |
| 388 | scope = await Effect.runPromise(Scope.make("sequential")); | 400 | scope = await Effect.runPromise(Scope.make("sequential")); |
| 389 | await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); | 401 | await Effect.runPromise(reaper.start().pipe(Scope.provide(scope))); |
| 390 | await new Promise((resolve) => setTimeout(resolve, 50)); | 402 | await Effect.runPromise(drainFibers); |
| 391 | 403 | ||
| 392 | expect(harness.stopSession).not.toHaveBeenCalled(); | 404 | expect(harness.stopSession).not.toHaveBeenCalled(); |
| 393 | const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); | 405 | const remaining = await runtime!.runPromise(repository.getByThreadId({ threadId })); |
| ... | @@ -397,7 +409,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -397,7 +409,7 @@ describe("ProviderSessionReaper", () => { |
| 397 | it("continues reaping other sessions when one stop attempt fails", async () => { | 409 | it("continues reaping other sessions when one stop attempt fails", async () => { |
| 398 | const failedThreadId = ThreadId.make("thread-reaper-stop-failure"); | 410 | const failedThreadId = ThreadId.make("thread-reaper-stop-failure"); |
| 399 | const reapedThreadId = ThreadId.make("thread-reaper-stop-success"); | 411 | const reapedThreadId = ThreadId.make("thread-reaper-stop-success"); |
| 400 | const now = new Date().toISOString(); | 412 | const now = "2026-01-01T00:00:00.000Z"; |
| 401 | const harness = await createHarness({ | 413 | const harness = await createHarness({ |
| 402 | readModel: makeReadModel([ | 414 | readModel: makeReadModel([ |
| 403 | { | 415 | { |
| ... | @@ -483,7 +495,7 @@ describe("ProviderSessionReaper", () => { | ... | @@ -483,7 +495,7 @@ describe("ProviderSessionReaper", () => { |
| 483 | it("continues reaping other sessions when one stop attempt defects", async () => { | 495 | it("continues reaping other sessions when one stop attempt defects", async () => { |
| 484 | const defectThreadId = ThreadId.make("thread-reaper-stop-defect"); | 496 | const defectThreadId = ThreadId.make("thread-reaper-stop-defect"); |
| 485 | const reapedThreadId = ThreadId.make("thread-reaper-stop-after-defect"); | 497 | const reapedThreadId = ThreadId.make("thread-reaper-stop-after-defect"); |
| 486 | const now = new Date().toISOString(); | 498 | const now = "2026-01-01T00:00:00.000Z"; |
| 487 | const harness = await createHarness({ | 499 | const harness = await createHarness({ |
| 488 | readModel: makeReadModel([ | 500 | readModel: makeReadModel([ |
| 489 | { | 501 | { |
apps/server/src/provider/Layers/ProviderSessionReaper.ts+7-2| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Duration, Effect, Layer, Option, Schedule } from "effect"; | 1 | import * as Clock from "effect/Clock"; |
| 2 | import * as Duration from "effect/Duration"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import * as Schedule from "effect/Schedule"; | ||
| 2 | 7 | ||
| 3 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; | 8 | import { ProjectionSnapshotQuery } from "../../orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 4 | import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts"; | 9 | import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts"; |
| ... | @@ -30,7 +35,7 @@ const makeProviderSessionReaper = (options?: ProviderSessionReaperLiveOptions) = | ... | @@ -30,7 +35,7 @@ const makeProviderSessionReaper = (options?: ProviderSessionReaperLiveOptions) = |
| 30 | 35 | ||
| 31 | const sweep = Effect.gen(function* () { | 36 | const sweep = Effect.gen(function* () { |
| 32 | const bindings = yield* directory.listBindings(); | 37 | const bindings = yield* directory.listBindings(); |
| 33 | const now = Date.now(); | 38 | const now = yield* Clock.currentTimeMillis; |
| 34 | let reapedCount = 0; | 39 | let reapedCount = 0; |
| 35 | 40 | ||
| 36 | for (const binding of bindings) { | 41 | for (const binding of bindings) { |
apps/server/src/provider/Layers/scopedSafeTeardown.test.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { it } from "@effect/vitest"; | 1 | import { it } from "@effect/vitest"; |
| 2 | import { Cause, Effect, Exit } from "effect"; | 2 | import * as Cause from "effect/Cause"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Exit from "effect/Exit"; | ||
| 3 | import { describe, expect } from "vitest"; | 5 | import { describe, expect } from "vitest"; |
| 4 | 6 | ||
| 5 | import { scopedSafeTeardown } from "./scopedSafeTeardown.ts"; | 7 | import { scopedSafeTeardown } from "./scopedSafeTeardown.ts"; |
apps/server/src/provider/Layers/scopedSafeTeardown.ts+3-1| ... | @@ -34,7 +34,9 @@ | ... | @@ -34,7 +34,9 @@ |
| 34 | * | 34 | * |
| 35 | * @module provider/Layers/scopedSafeTeardown | 35 | * @module provider/Layers/scopedSafeTeardown |
| 36 | */ | 36 | */ |
| 37 | import { Effect, Exit, Scope } from "effect"; | 37 | import * as Effect from "effect/Effect"; |
| 38 | import * as Exit from "effect/Exit"; | ||
| 39 | import * as Scope from "effect/Scope"; | ||
| 38 | 40 | ||
| 39 | /** | 41 | /** |
| 40 | * Run `effect` with a freshly made `Scope.Scope`, guaranteeing that | 42 | * Run `effect` with a freshly made `Scope.Scope`, guaranteeing that |
apps/server/src/provider/ProviderDriver.ts+3-1| ... | @@ -26,7 +26,9 @@ import type { | ... | @@ -26,7 +26,9 @@ import type { |
| 26 | ProviderInstanceEnvironment, | 26 | ProviderInstanceEnvironment, |
| 27 | ProviderInstanceId, | 27 | ProviderInstanceId, |
| 28 | } from "@t3tools/contracts"; | 28 | } from "@t3tools/contracts"; |
| 29 | import type { Effect, Schema, Scope } from "effect"; | 29 | import type * as Effect from "effect/Effect"; |
| 30 | import type * as Schema from "effect/Schema"; | ||
| 31 | import type * as Scope from "effect/Scope"; | ||
| 30 | 32 | ||
| 31 | import type { TextGenerationShape } from "../textGeneration/TextGeneration.ts"; | 33 | import type { TextGenerationShape } from "../textGeneration/TextGeneration.ts"; |
| 32 | import type { ProviderAdapterError, ProviderDriverError } from "./Errors.ts"; | 34 | import type { ProviderAdapterError, ProviderDriverError } from "./Errors.ts"; |
apps/server/src/provider/Services/ProviderAdapter.ts+2-2| ... | @@ -20,8 +20,8 @@ import type { | ... | @@ -20,8 +20,8 @@ import type { |
| 20 | ProviderTurnStartResult, | 20 | ProviderTurnStartResult, |
| 21 | TurnId, | 21 | TurnId, |
| 22 | } from "@t3tools/contracts"; | 22 | } from "@t3tools/contracts"; |
| 23 | import type { Effect } from "effect"; | 23 | import type * as Effect from "effect/Effect"; |
| 24 | import type { Stream } from "effect"; | 24 | import type * as Stream from "effect/Stream"; |
| 25 | 25 | ||
| 26 | export type ProviderSessionModelSwitchMode = "in-session" | "unsupported"; | 26 | export type ProviderSessionModelSwitchMode = "in-session" | "unsupported"; |
| 27 | 27 |
apps/server/src/provider/Services/ProviderAdapterRegistry.ts+5-2| ... | @@ -20,8 +20,11 @@ | ... | @@ -20,8 +20,11 @@ |
| 20 | * @module ProviderAdapterRegistry | 20 | * @module ProviderAdapterRegistry |
| 21 | */ | 21 | */ |
| 22 | import type { ProviderDriverKind, ProviderInstanceId } from "@t3tools/contracts"; | 22 | import type { ProviderDriverKind, ProviderInstanceId } from "@t3tools/contracts"; |
| 23 | import { Context } from "effect"; | 23 | import * as Context from "effect/Context"; |
| 24 | import type { Effect, PubSub, Scope, Stream } from "effect"; | 24 | import type * as Effect from "effect/Effect"; |
| 25 | import type * as PubSub from "effect/PubSub"; | ||
| 26 | import type * as Scope from "effect/Scope"; | ||
| 27 | import type * as Stream from "effect/Stream"; | ||
| 25 | 28 | ||
| 26 | import type { ProviderAdapterError, ProviderUnsupportedError } from "../Errors.ts"; | 29 | import type { ProviderAdapterError, ProviderUnsupportedError } from "../Errors.ts"; |
| 27 | import type { ProviderAdapterShape } from "./ProviderAdapter.ts"; | 30 | import type { ProviderAdapterShape } from "./ProviderAdapter.ts"; |
apps/server/src/provider/Services/ProviderInstanceRegistry.ts+5-2| ... | @@ -18,8 +18,11 @@ | ... | @@ -18,8 +18,11 @@ |
| 18 | * @module provider/Services/ProviderInstanceRegistry | 18 | * @module provider/Services/ProviderInstanceRegistry |
| 19 | */ | 19 | */ |
| 20 | import type { ProviderInstanceId, ServerProvider } from "@t3tools/contracts"; | 20 | import type { ProviderInstanceId, ServerProvider } from "@t3tools/contracts"; |
| 21 | import { Context } from "effect"; | 21 | import * as Context from "effect/Context"; |
| 22 | import type { Effect, PubSub, Scope, Stream } from "effect"; | 22 | import type * as Effect from "effect/Effect"; |
| 23 | import type * as PubSub from "effect/PubSub"; | ||
| 24 | import type * as Scope from "effect/Scope"; | ||
| 25 | import type * as Stream from "effect/Stream"; | ||
| 23 | 26 | ||
| 24 | import type { ProviderInstance } from "../ProviderDriver.ts"; | 27 | import type { ProviderInstance } from "../ProviderDriver.ts"; |
| 25 | 28 |
apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts+2-2| ... | @@ -29,8 +29,8 @@ | ... | @@ -29,8 +29,8 @@ |
| 29 | * @module provider/Services/ProviderInstanceRegistryMutator | 29 | * @module provider/Services/ProviderInstanceRegistryMutator |
| 30 | */ | 30 | */ |
| 31 | import type { ProviderInstanceConfigMap } from "@t3tools/contracts"; | 31 | import type { ProviderInstanceConfigMap } from "@t3tools/contracts"; |
| 32 | import { Context } from "effect"; | 32 | import * as Context from "effect/Context"; |
| 33 | import type { Effect } from "effect"; | 33 | import type * as Effect from "effect/Effect"; |
| 34 | 34 | ||
| 35 | export interface ProviderInstanceRegistryMutatorShape { | 35 | export interface ProviderInstanceRegistryMutatorShape { |
| 36 | /** | 36 | /** |
apps/server/src/provider/Services/ProviderRegistry.ts+3-2| ... | @@ -12,8 +12,9 @@ import type { | ... | @@ -12,8 +12,9 @@ import type { |
| 12 | ServerProvider, | 12 | ServerProvider, |
| 13 | ServerProviderUpdateState, | 13 | ServerProviderUpdateState, |
| 14 | } from "@t3tools/contracts"; | 14 | } from "@t3tools/contracts"; |
| 15 | import { Context } from "effect"; | 15 | import * as Context from "effect/Context"; |
| 16 | import type { Effect, Stream } from "effect"; | 16 | import type * as Effect from "effect/Effect"; |
| 17 | import type * as Stream from "effect/Stream"; | ||
| 17 | import type { ProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; | 18 | import type { ProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; |
| 18 | 19 | ||
| 19 | export type ProviderMaintenanceActionKind = "update"; | 20 | export type ProviderMaintenanceActionKind = "update"; |
apps/server/src/provider/Services/ProviderService.ts+3-2| ... | @@ -24,8 +24,9 @@ import type { | ... | @@ -24,8 +24,9 @@ import type { |
| 24 | ThreadId, | 24 | ThreadId, |
| 25 | ProviderTurnStartResult, | 25 | ProviderTurnStartResult, |
| 26 | } from "@t3tools/contracts"; | 26 | } from "@t3tools/contracts"; |
| 27 | import { Context } from "effect"; | 27 | import * as Context from "effect/Context"; |
| 28 | import type { Effect, Stream } from "effect"; | 28 | import type * as Effect from "effect/Effect"; |
| 29 | import type * as Stream from "effect/Stream"; | ||
| 29 | 30 | ||
| 30 | import type { ProviderServiceError } from "../Errors.ts"; | 31 | import type { ProviderServiceError } from "../Errors.ts"; |
| 31 | import type { ProviderAdapterCapabilities } from "./ProviderAdapter.ts"; | 32 | import type { ProviderAdapterCapabilities } from "./ProviderAdapter.ts"; |
apps/server/src/provider/Services/ProviderSessionDirectory.ts+3-2| ... | @@ -5,8 +5,9 @@ import type { | ... | @@ -5,8 +5,9 @@ import type { |
| 5 | RuntimeMode, | 5 | RuntimeMode, |
| 6 | ThreadId, | 6 | ThreadId, |
| 7 | } from "@t3tools/contracts"; | 7 | } from "@t3tools/contracts"; |
| 8 | import { Option, Context } from "effect"; | 8 | import * as Option from "effect/Option"; |
| 9 | import type { Effect } from "effect"; | 9 | import * as Context from "effect/Context"; |
| 10 | import type * as Effect from "effect/Effect"; | ||
| 10 | 11 | ||
| 11 | import type { | 12 | import type { |
| 12 | ProviderSessionDirectoryPersistenceError, | 13 | ProviderSessionDirectoryPersistenceError, |
apps/server/src/provider/Services/ProviderSessionReaper.ts+3-2| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { Context } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import type { Effect, Scope } from "effect"; | 2 | import type * as Effect from "effect/Effect"; |
| 3 | import type * as Scope from "effect/Scope"; | ||
| 3 | 4 | ||
| 4 | export interface ProviderSessionReaperShape { | 5 | export interface ProviderSessionReaperShape { |
| 5 | /** | 6 | /** |
apps/server/src/provider/Services/ServerProvider.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import type { ServerProvider } from "@t3tools/contracts"; | 1 | import type { ServerProvider } from "@t3tools/contracts"; |
| 2 | import type { Effect, Stream } from "effect"; | 2 | import type * as Effect from "effect/Effect"; |
| 3 | import type * as Stream from "effect/Stream"; | ||
| 3 | import type { ProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; | 4 | import type { ProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; |
| 4 | 5 | ||
| 5 | export interface ServerProviderShape { | 6 | export interface ServerProviderShape { |
apps/server/src/provider/acp/AcpAdapterSupport.ts+1-1| ... | @@ -3,7 +3,7 @@ import { | ... | @@ -3,7 +3,7 @@ import { |
| 3 | type ProviderDriverKind, | 3 | type ProviderDriverKind, |
| 4 | type ThreadId, | 4 | type ThreadId, |
| 5 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { Schema } from "effect"; | 6 | import * as Schema from "effect/Schema"; |
| 7 | import * as EffectAcpErrors from "effect-acp/errors"; | 7 | import * as EffectAcpErrors from "effect-acp/errors"; |
| 8 | 8 | ||
| 9 | import { | 9 | import { |
apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts+3-1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as path from "node:path"; | 2 | import * as path from "node:path"; |
| 2 | import * as os from "node:os"; | 3 | import * as os from "node:os"; |
| 3 | import { fileURLToPath } from "node:url"; | 4 | import { fileURLToPath } from "node:url"; |
| ... | @@ -5,7 +6,8 @@ import { mkdtempSync, readFileSync, rmSync } from "node:fs"; | ... | @@ -5,7 +6,8 @@ import { mkdtempSync, readFileSync, rmSync } from "node:fs"; |
| 5 | 6 | ||
| 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 7 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 7 | import { it } from "@effect/vitest"; | 8 | import { it } from "@effect/vitest"; |
| 8 | import { Effect, Stream } from "effect"; | 9 | import * as Effect from "effect/Effect"; |
| 10 | import * as Stream from "effect/Stream"; | ||
| 9 | import { describe, expect } from "vitest"; | 11 | import { describe, expect } from "vitest"; |
| 10 | 12 | ||
| 11 | import { AcpSessionRuntime, type AcpSessionRequestLogEvent } from "./AcpSessionRuntime.ts"; | 13 | import { AcpSessionRuntime, type AcpSessionRequestLogEvent } from "./AcpSessionRuntime.ts"; |
apps/server/src/provider/acp/AcpNativeLogging.ts+6-3| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | import type { ProviderDriverKind, ThreadId } from "@t3tools/contracts"; | 1 | import type { ProviderDriverKind, ThreadId } from "@t3tools/contracts"; |
| 2 | import { Cause, Effect } from "effect"; | 2 | import * as Cause from "effect/Cause"; |
| 3 | import * as DateTime from "effect/DateTime"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Random from "effect/Random"; | ||
| 3 | import type * as EffectAcpProtocol from "effect-acp/protocol"; | 6 | import type * as EffectAcpProtocol from "effect-acp/protocol"; |
| 4 | 7 | ||
| 5 | import type { EventNdjsonLogger } from "../Layers/EventNdjsonLogger.ts"; | 8 | import type { EventNdjsonLogger } from "../Layers/EventNdjsonLogger.ts"; |
| ... | @@ -14,12 +17,12 @@ function writeNativeAcpLog(input: { | ... | @@ -14,12 +17,12 @@ function writeNativeAcpLog(input: { |
| 14 | }): Effect.Effect<void, never> { | 17 | }): Effect.Effect<void, never> { |
| 15 | return Effect.gen(function* () { | 18 | return Effect.gen(function* () { |
| 16 | if (!input.nativeEventLogger) return; | 19 | if (!input.nativeEventLogger) return; |
| 17 | const observedAt = new Date().toISOString(); | 20 | const observedAt = DateTime.formatIso(yield* DateTime.now); |
| 18 | yield* input.nativeEventLogger.write( | 21 | yield* input.nativeEventLogger.write( |
| 19 | { | 22 | { |
| 20 | observedAt, | 23 | observedAt, |
| 21 | event: { | 24 | event: { |
| 22 | id: crypto.randomUUID(), | 25 | id: yield* Random.nextUUIDv4, |
| 23 | kind: input.kind, | 26 | kind: input.kind, |
| 24 | provider: input.provider, | 27 | provider: input.provider, |
| 25 | createdAt: observedAt, | 28 | createdAt: observedAt, |
apps/server/src/provider/acp/AcpSessionRuntime.ts+15-5| ... | @@ -1,4 +1,14 @@ | ... | @@ -1,4 +1,14 @@ |
| 1 | import { Cause, Deferred, Effect, Exit, Layer, Queue, Ref, Scope, Context, Stream } from "effect"; | 1 | import * as Cause from "effect/Cause"; |
| 2 | import * as Deferred from "effect/Deferred"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Exit from "effect/Exit"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Queue from "effect/Queue"; | ||
| 7 | import * as Ref from "effect/Ref"; | ||
| 8 | import * as Scope from "effect/Scope"; | ||
| 9 | import * as Context from "effect/Context"; | ||
| 10 | import * as Schema from "effect/Schema"; | ||
| 11 | import * as Stream from "effect/Stream"; | ||
| 2 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 12 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 3 | import * as EffectAcpClient from "effect-acp/client"; | 13 | import * as EffectAcpClient from "effect-acp/client"; |
| 4 | import * as EffectAcpErrors from "effect-acp/errors"; | 14 | import * as EffectAcpErrors from "effect-acp/errors"; |
| ... | @@ -248,7 +258,7 @@ const makeAcpSessionRuntime = ( | ... | @@ -248,7 +258,7 @@ const makeAcpSessionRuntime = ( |
| 248 | } | 258 | } |
| 249 | return yield* new EffectAcpErrors.AcpTransportError({ | 259 | return yield* new EffectAcpErrors.AcpTransportError({ |
| 250 | detail: "ACP session runtime has not been started", | 260 | detail: "ACP session runtime has not been started", |
| 251 | cause: new Error("ACP session runtime has not been started"), | 261 | cause: "ACP session runtime has not been started", |
| 252 | }); | 262 | }); |
| 253 | }); | 263 | }); |
| 254 | 264 | ||
| ... | @@ -267,7 +277,7 @@ const makeAcpSessionRuntime = ( | ... | @@ -267,7 +277,7 @@ const makeAcpSessionRuntime = ( |
| 267 | } | 277 | } |
| 268 | return yield* new EffectAcpErrors.AcpRequestError({ | 278 | return yield* new EffectAcpErrors.AcpRequestError({ |
| 269 | code: -32602, | 279 | code: -32602, |
| 270 | errorMessage: `Invalid value ${JSON.stringify(value)} for session config option "${configOption.id}": expected boolean`, | 280 | errorMessage: `Invalid value ${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(value)} for session config option "${configOption.id}": expected boolean`, |
| 271 | data: { | 281 | data: { |
| 272 | configId: configOption.id, | 282 | configId: configOption.id, |
| 273 | expectedType: "boolean", | 283 | expectedType: "boolean", |
| ... | @@ -278,7 +288,7 @@ const makeAcpSessionRuntime = ( | ... | @@ -278,7 +288,7 @@ const makeAcpSessionRuntime = ( |
| 278 | if (typeof value !== "string") { | 288 | if (typeof value !== "string") { |
| 279 | return yield* new EffectAcpErrors.AcpRequestError({ | 289 | return yield* new EffectAcpErrors.AcpRequestError({ |
| 280 | code: -32602, | 290 | code: -32602, |
| 281 | errorMessage: `Invalid value ${JSON.stringify(value)} for session config option "${configOption.id}": expected string`, | 291 | errorMessage: `Invalid value ${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(value)} for session config option "${configOption.id}": expected string`, |
| 282 | data: { | 292 | data: { |
| 283 | configId: configOption.id, | 293 | configId: configOption.id, |
| 284 | expectedType: "string", | 294 | expectedType: "string", |
| ... | @@ -292,7 +302,7 @@ const makeAcpSessionRuntime = ( | ... | @@ -292,7 +302,7 @@ const makeAcpSessionRuntime = ( |
| 292 | } | 302 | } |
| 293 | return yield* new EffectAcpErrors.AcpRequestError({ | 303 | return yield* new EffectAcpErrors.AcpRequestError({ |
| 294 | code: -32602, | 304 | code: -32602, |
| 295 | errorMessage: `Invalid value ${JSON.stringify(value)} for session config option "${configOption.id}": expected one of ${allowedValues.join(", ")}`, | 305 | errorMessage: `Invalid value ${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(value)} for session config option "${configOption.id}": expected one of ${allowedValues.join(", ")}`, |
| 296 | data: { | 306 | data: { |
| 297 | configId: configOption.id, | 307 | configId: configOption.id, |
| 298 | allowedValues, | 308 | allowedValues, |
apps/server/src/provider/acp/CursorAcpCliProbe.test.ts+12-7| ... | @@ -4,7 +4,8 @@ | ... | @@ -4,7 +4,8 @@ |
| 4 | */ | 4 | */ |
| 5 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 5 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 6 | import { it } from "@effect/vitest"; | 6 | import { it } from "@effect/vitest"; |
| 7 | import { Effect } from "effect"; | 7 | import * as Console from "effect/Console"; |
| 8 | import * as Effect from "effect/Effect"; | ||
| 8 | import { describe, expect } from "vitest"; | 9 | import { describe, expect } from "vitest"; |
| 9 | import type * as EffectAcpSchema from "effect-acp/schema"; | 10 | import type * as EffectAcpSchema from "effect-acp/schema"; |
| 10 | 11 | ||
| ... | @@ -44,12 +45,14 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", | ... | @@ -44,12 +45,14 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", |
| 44 | const runtime = yield* AcpSessionRuntime; | 45 | const runtime = yield* AcpSessionRuntime; |
| 45 | const started = yield* runtime.start(); | 46 | const started = yield* runtime.start(); |
| 46 | const result = started.sessionSetupResult; | 47 | const result = started.sessionSetupResult; |
| 47 | console.log("session/new result:", JSON.stringify(result, null, 2)); | 48 | // @effect-diagnostics-next-line preferSchemaOverJson:off |
| 49 | yield* Console.log("session/new result:", JSON.stringify(result, null, 2)); | ||
| 48 | 50 | ||
| 49 | expect(typeof started.sessionId).toBe("string"); | 51 | expect(typeof started.sessionId).toBe("string"); |
| 50 | 52 | ||
| 51 | const configOptions = result.configOptions; | 53 | const configOptions = result.configOptions; |
| 52 | console.log("session/new configOptions:", JSON.stringify(configOptions, null, 2)); | 54 | // @effect-diagnostics-next-line preferSchemaOverJson:off |
| 55 | yield* Console.log("session/new configOptions:", JSON.stringify(configOptions, null, 2)); | ||
| 53 | 56 | ||
| 54 | if (Array.isArray(configOptions)) { | 57 | if (Array.isArray(configOptions)) { |
| 55 | const modelConfig = configOptions.find((opt) => opt.category === "model"); | 58 | 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", | ... | @@ -59,9 +62,11 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", |
| 59 | opt.category === "model_option" || | 62 | opt.category === "model_option" || |
| 60 | opt.category === "model_config", | 63 | opt.category === "model_config", |
| 61 | ); | 64 | ); |
| 62 | console.log("Model config option:", JSON.stringify(modelConfig, null, 2)); | 65 | // @effect-diagnostics-next-line preferSchemaOverJson:off |
| 63 | console.log( | 66 | yield* Console.log("Model config option:", JSON.stringify(modelConfig, null, 2)); |
| 67 | yield* Console.log( | ||
| 64 | "Parameterized model config options:", | 68 | "Parameterized model config options:", |
| 69 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 65 | JSON.stringify(parameterizedOptions, null, 2), | 70 | JSON.stringify(parameterizedOptions, null, 2), |
| 66 | ); | 71 | ); |
| 67 | expect(modelConfig).toBeDefined(); | 72 | expect(modelConfig).toBeDefined(); |
| ... | @@ -107,8 +112,8 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", | ... | @@ -107,8 +112,8 @@ describe.runIf(process.env.T3_CURSOR_ACP_PROBE === "1")("Cursor ACP CLI probe", |
| 107 | 112 | ||
| 108 | const setResult: EffectAcpSchema.SetSessionConfigOptionResponse = | 113 | const setResult: EffectAcpSchema.SetSessionConfigOptionResponse = |
| 109 | yield* runtime.setConfigOption(modelConfigId, "gpt-5.4"); | 114 | yield* runtime.setConfigOption(modelConfigId, "gpt-5.4"); |
| 110 | 115 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 111 | console.log("session/set_config_option result:", JSON.stringify(setResult, null, 2)); | 116 | yield* Console.log("session/set_config_option result:", JSON.stringify(setResult, null, 2)); |
| 112 | 117 | ||
| 113 | if (Array.isArray(setResult.configOptions)) { | 118 | if (Array.isArray(setResult.configOptions)) { |
| 114 | const modelConfig = setResult.configOptions.find((opt) => opt.category === "model"); | 119 | const modelConfig = setResult.configOptions.find((opt) => opt.category === "model"); |
apps/server/src/provider/acp/CursorAcpExtension.ts+1-1| ... | @@ -3,7 +3,7 @@ | ... | @@ -3,7 +3,7 @@ |
| 3 | * Additional reference provided by the Cursor team: https://anysphere.enterprise.slack.com/files/U068SSJE141/F0APT1HSZRP/cursor-acp-extension-method-schemas.md | 3 | * Additional reference provided by the Cursor team: https://anysphere.enterprise.slack.com/files/U068SSJE141/F0APT1HSZRP/cursor-acp-extension-method-schemas.md |
| 4 | */ | 4 | */ |
| 5 | import type { UserInputQuestion } from "@t3tools/contracts"; | 5 | import type { UserInputQuestion } from "@t3tools/contracts"; |
| 6 | import { Schema } from "effect"; | 6 | import * as Schema from "effect/Schema"; |
| 7 | 7 | ||
| 8 | const CursorAskQuestionOption = Schema.Struct({ | 8 | const CursorAskQuestionOption = Schema.Struct({ |
| 9 | id: Schema.String, | 9 | id: Schema.String, |
apps/server/src/provider/acp/CursorAcpSupport.test.ts+4-6| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Effect } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import type * as EffectAcpSchema from "effect-acp/schema"; | 2 | import type * as EffectAcpSchema from "effect-acp/schema"; |
| 3 | import { describe, expect, it } from "vitest"; | 3 | import { describe, expect, it } from "vitest"; |
| 4 | 4 | ||
| ... | @@ -105,11 +105,9 @@ describe("applyCursorAcpModelSelection", () => { | ... | @@ -105,11 +105,9 @@ describe("applyCursorAcpModelSelection", () => { |
| 105 | { id: "fastMode", value: true }, | 105 | { id: "fastMode", value: true }, |
| 106 | ], | 106 | ], |
| 107 | mapError: ({ step, configId, cause }) => | 107 | mapError: ({ step, configId, cause }) => |
| 108 | new Error( | 108 | step === "set-config-option" |
| 109 | step === "set-config-option" | 109 | ? `failed to set config option ${configId}: ${cause.message}` |
| 110 | ? `failed to set config option ${configId}: ${cause.message}` | 110 | : `failed to set model: ${cause.message}`, |
| 111 | : `failed to set model: ${cause.message}`, | ||
| 112 | ), | ||
| 113 | }), | 111 | }), |
| 114 | ); | 112 | ); |
| 115 | 113 |
apps/server/src/provider/acp/CursorAcpSupport.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { type CursorSettings, type ProviderOptionSelection } from "@t3tools/contracts"; | 1 | import { type CursorSettings, type ProviderOptionSelection } from "@t3tools/contracts"; |
| 2 | import { Effect, Layer, Scope } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Scope from "effect/Scope"; | ||
| 3 | import { ChildProcessSpawner } from "effect/unstable/process"; | 5 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 4 | import type * as EffectAcpErrors from "effect-acp/errors"; | 6 | import type * as EffectAcpErrors from "effect-acp/errors"; |
| 5 | 7 |
apps/server/src/provider/builtInProviderCatalog.ts+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import type { ProviderDriverKind, ProviderInstanceId, ServerProvider } from "@t3tools/contracts"; | 1 | import type { ProviderDriverKind, ProviderInstanceId, ServerProvider } from "@t3tools/contracts"; |
| 2 | import type { Stream } from "effect"; | 2 | import type * as Stream from "effect/Stream"; |
| 3 | import type { ServerProviderShape } from "./Services/ServerProvider.ts"; | 3 | import type { ServerProviderShape } from "./Services/ServerProvider.ts"; |
| 4 | 4 | ||
| 5 | export type ProviderSnapshotSource = { | 5 | export type ProviderSnapshotSource = { |
apps/server/src/provider/makeManagedServerProvider.test.ts+10-5| ... | @@ -1,7 +1,12 @@ | ... | @@ -1,7 +1,12 @@ |
| 1 | import { describe, it, assert } from "@effect/vitest"; | 1 | import { describe, it, assert } from "@effect/vitest"; |
| 2 | import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts"; | 2 | import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts"; |
| 3 | import { createModelCapabilities } from "@t3tools/shared/model"; | 3 | import { createModelCapabilities } from "@t3tools/shared/model"; |
| 4 | import { Deferred, Effect, Fiber, PubSub, Ref, Stream } from "effect"; | 4 | import * as Deferred from "effect/Deferred"; |
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as Fiber from "effect/Fiber"; | ||
| 7 | import * as PubSub from "effect/PubSub"; | ||
| 8 | import * as Ref from "effect/Ref"; | ||
| 9 | import * as Stream from "effect/Stream"; | ||
| 5 | 10 | ||
| 6 | import { makeManagedServerProvider } from "./makeManagedServerProvider.ts"; | 11 | import { makeManagedServerProvider } from "./makeManagedServerProvider.ts"; |
| 7 | 12 | ||
| ... | @@ -108,7 +113,7 @@ describe("makeManagedServerProvider", () => { | ... | @@ -108,7 +113,7 @@ describe("makeManagedServerProvider", () => { |
| 108 | getSettings: Effect.succeed({ enabled: true }), | 113 | getSettings: Effect.succeed({ enabled: true }), |
| 109 | streamSettings: Stream.empty, | 114 | streamSettings: Stream.empty, |
| 110 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, | 115 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, |
| 111 | initialSnapshot: () => initialSnapshot, | 116 | initialSnapshot: () => Effect.succeed(initialSnapshot), |
| 112 | checkProvider: Ref.update(checkCalls, (count) => count + 1).pipe( | 117 | checkProvider: Ref.update(checkCalls, (count) => count + 1).pipe( |
| 113 | Effect.flatMap(() => Deferred.await(releaseCheck)), | 118 | Effect.flatMap(() => Deferred.await(releaseCheck)), |
| 114 | Effect.as(refreshedSnapshot), | 119 | Effect.as(refreshedSnapshot), |
| ... | @@ -150,7 +155,7 @@ describe("makeManagedServerProvider", () => { | ... | @@ -150,7 +155,7 @@ describe("makeManagedServerProvider", () => { |
| 150 | getSettings: Ref.get(settingsRef), | 155 | getSettings: Ref.get(settingsRef), |
| 151 | streamSettings: Stream.fromPubSub(settingsChanges), | 156 | streamSettings: Stream.fromPubSub(settingsChanges), |
| 152 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, | 157 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, |
| 153 | initialSnapshot: () => initialSnapshot, | 158 | initialSnapshot: () => Effect.succeed(initialSnapshot), |
| 154 | checkProvider: Ref.updateAndGet(checkCalls, (count) => count + 1).pipe( | 159 | checkProvider: Ref.updateAndGet(checkCalls, (count) => count + 1).pipe( |
| 155 | Effect.flatMap((count) => | 160 | Effect.flatMap((count) => |
| 156 | count === 1 | 161 | count === 1 |
| ... | @@ -192,7 +197,7 @@ describe("makeManagedServerProvider", () => { | ... | @@ -192,7 +197,7 @@ describe("makeManagedServerProvider", () => { |
| 192 | getSettings: Effect.succeed({ enabled: true }), | 197 | getSettings: Effect.succeed({ enabled: true }), |
| 193 | streamSettings: Stream.empty, | 198 | streamSettings: Stream.empty, |
| 194 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, | 199 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, |
| 195 | initialSnapshot: () => initialSnapshot, | 200 | initialSnapshot: () => Effect.succeed(initialSnapshot), |
| 196 | checkProvider: Deferred.await(releaseCheck).pipe(Effect.as(refreshedSnapshot)), | 201 | checkProvider: Deferred.await(releaseCheck).pipe(Effect.as(refreshedSnapshot)), |
| 197 | enrichSnapshot: ({ publishSnapshot }) => | 202 | enrichSnapshot: ({ publishSnapshot }) => |
| 198 | Deferred.await(releaseEnrichment).pipe( | 203 | Deferred.await(releaseEnrichment).pipe( |
| ... | @@ -233,7 +238,7 @@ describe("makeManagedServerProvider", () => { | ... | @@ -233,7 +238,7 @@ describe("makeManagedServerProvider", () => { |
| 233 | getSettings: Effect.succeed({ enabled: true }), | 238 | getSettings: Effect.succeed({ enabled: true }), |
| 234 | streamSettings: Stream.empty, | 239 | streamSettings: Stream.empty, |
| 235 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, | 240 | haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, |
| 236 | initialSnapshot: () => initialSnapshot, | 241 | initialSnapshot: () => Effect.succeed(initialSnapshot), |
| 237 | checkProvider: Ref.updateAndGet(refreshCount, (count) => count + 1).pipe( | 242 | checkProvider: Ref.updateAndGet(refreshCount, (count) => count + 1).pipe( |
| 238 | Effect.flatMap((count) => | 243 | Effect.flatMap((count) => |
| 239 | count === 1 | 244 | count === 1 |
apps/server/src/provider/makeManagedServerProvider.ts+10-3| ... | @@ -1,5 +1,12 @@ | ... | @@ -1,5 +1,12 @@ |
| 1 | import type { ServerProvider } from "@t3tools/contracts"; | 1 | import type { ServerProvider } from "@t3tools/contracts"; |
| 2 | import { Duration, Effect, Equal, Fiber, PubSub, Ref, Scope, Stream } from "effect"; | 2 | import * as Duration from "effect/Duration"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Equal from "effect/Equal"; | ||
| 5 | import * as Fiber from "effect/Fiber"; | ||
| 6 | import * as PubSub from "effect/PubSub"; | ||
| 7 | import * as Ref from "effect/Ref"; | ||
| 8 | import * as Scope from "effect/Scope"; | ||
| 9 | import * as Stream from "effect/Stream"; | ||
| 3 | import * as Semaphore from "effect/Semaphore"; | 10 | import * as Semaphore from "effect/Semaphore"; |
| 4 | 11 | ||
| 5 | import type { ServerProviderShape } from "./Services/ServerProvider.ts"; | 12 | import type { ServerProviderShape } from "./Services/ServerProvider.ts"; |
| ... | @@ -17,7 +24,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( | ... | @@ -17,7 +24,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( |
| 17 | readonly getSettings: Effect.Effect<Settings>; | 24 | readonly getSettings: Effect.Effect<Settings>; |
| 18 | readonly streamSettings: Stream.Stream<Settings>; | 25 | readonly streamSettings: Stream.Stream<Settings>; |
| 19 | readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean; | 26 | readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean; |
| 20 | readonly initialSnapshot: (settings: Settings) => ServerProvider; | 27 | readonly initialSnapshot: (settings: Settings) => Effect.Effect<ServerProvider>; |
| 21 | readonly checkProvider: Effect.Effect<ServerProvider, ServerSettingsError>; | 28 | readonly checkProvider: Effect.Effect<ServerProvider, ServerSettingsError>; |
| 22 | readonly enrichSnapshot?: (input: { | 29 | readonly enrichSnapshot?: (input: { |
| 23 | readonly settings: Settings; | 30 | readonly settings: Settings; |
| ... | @@ -33,7 +40,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( | ... | @@ -33,7 +40,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( |
| 33 | PubSub.shutdown, | 40 | PubSub.shutdown, |
| 34 | ); | 41 | ); |
| 35 | const initialSettings = yield* input.getSettings; | 42 | const initialSettings = yield* input.getSettings; |
| 36 | const initialSnapshot = input.initialSnapshot(initialSettings); | 43 | const initialSnapshot = yield* input.initialSnapshot(initialSettings); |
| 37 | const snapshotStateRef = yield* Ref.make<ProviderSnapshotState>({ | 44 | const snapshotStateRef = yield* Ref.make<ProviderSnapshotState>({ |
| 38 | snapshot: initialSnapshot, | 45 | snapshot: initialSnapshot, |
| 39 | enrichmentGeneration: 0, | 46 | enrichmentGeneration: 0, |
apps/server/src/provider/opencodeRuntime.ts+19-20| ... | @@ -11,27 +11,26 @@ import { | ... | @@ -11,27 +11,26 @@ import { |
| 11 | type QuestionAnswer, | 11 | type QuestionAnswer, |
| 12 | type QuestionRequest, | 12 | type QuestionRequest, |
| 13 | } from "@opencode-ai/sdk/v2"; | 13 | } from "@opencode-ai/sdk/v2"; |
| 14 | import { | 14 | import * as Cause from "effect/Cause"; |
| 15 | Cause, | 15 | import * as Context from "effect/Context"; |
| 16 | Context, | 16 | import * as Data from "effect/Data"; |
| 17 | Data, | 17 | import * as Deferred from "effect/Deferred"; |
| 18 | Deferred, | 18 | import * as Effect from "effect/Effect"; |
| 19 | Effect, | 19 | import * as Exit from "effect/Exit"; |
| 20 | Exit, | 20 | import * as Fiber from "effect/Fiber"; |
| 21 | Fiber, | 21 | import * as Layer from "effect/Layer"; |
| 22 | Layer, | 22 | import * as Option from "effect/Option"; |
| 23 | Option, | 23 | import * as P from "effect/Predicate"; |
| 24 | Predicate as P, | 24 | import * as Ref from "effect/Ref"; |
| 25 | Ref, | 25 | import * as Result from "effect/Result"; |
| 26 | Result, | 26 | import * as Scope from "effect/Scope"; |
| 27 | Scope, | 27 | import * as Schema from "effect/Schema"; |
| 28 | Stream, | 28 | import * as Stream from "effect/Stream"; |
| 29 | } from "effect"; | ||
| 30 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 29 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 31 | 30 | ||
| 32 | import { isWindowsCommandNotFound } from "../processRunner.ts"; | 31 | import { isWindowsCommandNotFound } from "../processRunner.ts"; |
| 33 | import { collectStreamAsString } from "./providerSnapshot.ts"; | 32 | import { collectStreamAsString } from "./providerSnapshot.ts"; |
| 34 | import { NetService } from "@t3tools/shared/Net"; | 33 | import * as NetService from "@t3tools/shared/Net"; |
| 35 | 34 | ||
| 36 | const OPENCODE_SERVER_READY_PREFIX = "opencode server listening"; | 35 | const OPENCODE_SERVER_READY_PREFIX = "opencode server listening"; |
| 37 | const DEFAULT_OPENCODE_SERVER_TIMEOUT_MS = 5_000; | 36 | const DEFAULT_OPENCODE_SERVER_TIMEOUT_MS = 5_000; |
| ... | @@ -66,7 +65,7 @@ export function openCodeRuntimeErrorDetail(cause: unknown): string { | ... | @@ -66,7 +65,7 @@ export function openCodeRuntimeErrorDetail(cause: unknown): string { |
| 66 | const status = (anyCause.response as { status?: number } | undefined)?.status; | 65 | const status = (anyCause.response as { status?: number } | undefined)?.status; |
| 67 | const body = anyCause.error ?? anyCause.data ?? anyCause.body; | 66 | const body = anyCause.error ?? anyCause.data ?? anyCause.body; |
| 68 | try { | 67 | try { |
| 69 | return `status=${status ?? "?"} body=${JSON.stringify(body ?? cause)}`; | 68 | return `status=${status ?? "?"} body=${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(body ?? cause)}`; |
| 70 | } catch { | 69 | } catch { |
| 71 | /* fall through */ | 70 | /* fall through */ |
| 72 | } | 71 | } |
| ... | @@ -270,7 +269,7 @@ function ensureRuntimeError( | ... | @@ -270,7 +269,7 @@ function ensureRuntimeError( |
| 270 | 269 | ||
| 271 | const makeOpenCodeRuntime = Effect.gen(function* () { | 270 | const makeOpenCodeRuntime = Effect.gen(function* () { |
| 272 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 271 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 273 | const netService = yield* NetService; | 272 | const netService = yield* NetService.NetService; |
| 274 | 273 | ||
| 275 | const runOpenCodeCommand: OpenCodeRuntimeShape["runOpenCodeCommand"] = (input) => | 274 | const runOpenCodeCommand: OpenCodeRuntimeShape["runOpenCodeCommand"] = (input) => |
| 276 | Effect.gen(function* () { | 275 | Effect.gen(function* () { |
| ... | @@ -337,7 +336,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () { | ... | @@ -337,7 +336,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () { |
| 337 | shell: process.platform === "win32", | 336 | shell: process.platform === "win32", |
| 338 | env: { | 337 | env: { |
| 339 | ...(input.environment ?? process.env), | 338 | ...(input.environment ?? process.env), |
| 340 | OPENCODE_CONFIG_CONTENT: JSON.stringify({}), | 339 | OPENCODE_CONFIG_CONTENT: Schema.encodeUnknownSync(Schema.UnknownFromJsonString)({}), |
| 341 | }, | 340 | }, |
| 342 | }), | 341 | }), |
| 343 | ) | 342 | ) |
apps/server/src/provider/providerMaintenance.test.ts+3-1| ... | @@ -1,10 +1,12 @@ | ... | @@ -1,10 +1,12 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { afterEach, describe, expect, it } from "@effect/vitest"; | 2 | import { afterEach, describe, expect, it } from "@effect/vitest"; |
| 2 | import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; | 3 | import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; |
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import os from "node:os"; | 5 | import os from "node:os"; |
| 5 | import path from "node:path"; | 6 | import path from "node:path"; |
| 6 | import { ProviderDriverKind } from "@t3tools/contracts"; | 7 | import { ProviderDriverKind } from "@t3tools/contracts"; |
| 7 | import { Effect, Random } from "effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Random from "effect/Random"; | ||
| 8 | import { | 10 | import { |
| 9 | clearLatestProviderVersionCacheForTests, | 11 | clearLatestProviderVersionCacheForTests, |
| 10 | createProviderVersionAdvisory, | 12 | createProviderVersionAdvisory, |
apps/server/src/provider/providerMaintenance.ts+5-1| ... | @@ -4,7 +4,11 @@ import { | ... | @@ -4,7 +4,11 @@ import { |
| 4 | type ServerProviderVersionAdvisory, | 4 | type ServerProviderVersionAdvisory, |
| 5 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { resolveCommandPath } from "@t3tools/shared/shell"; | 6 | import { resolveCommandPath } from "@t3tools/shared/shell"; |
| 7 | import { DateTime, Effect, FileSystem, Option, Schema } from "effect"; | 7 | import * as DateTime from "effect/DateTime"; |
| 8 | import * as Effect from "effect/Effect"; | ||
| 9 | import * as FileSystem from "effect/FileSystem"; | ||
| 10 | import * as Option from "effect/Option"; | ||
| 11 | import * as Schema from "effect/Schema"; | ||
| 8 | import { HttpClient, HttpClientRequest } from "effect/unstable/http"; | 12 | import { HttpClient, HttpClientRequest } from "effect/unstable/http"; |
| 9 | 13 | ||
| 10 | import { compareCliVersions } from "./cliVersion.ts"; | 14 | import { compareCliVersions } from "./cliVersion.ts"; |
apps/server/src/provider/providerMaintenanceCommandCoordinator.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Effect, Ref } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Ref from "effect/Ref"; | ||
| 2 | import * as Semaphore from "effect/Semaphore"; | 3 | import * as Semaphore from "effect/Semaphore"; |
| 3 | 4 | ||
| 4 | export interface ProviderMaintenanceCommandCoordinatorShape<E> { | 5 | export interface ProviderMaintenanceCommandCoordinatorShape<E> { |
apps/server/src/provider/providerMaintenanceRunner.test.ts+9-1| ... | @@ -6,7 +6,15 @@ import { | ... | @@ -6,7 +6,15 @@ import { |
| 6 | type ServerProviderUpdateState, | 6 | type ServerProviderUpdateState, |
| 7 | } from "@t3tools/contracts"; | 7 | } from "@t3tools/contracts"; |
| 8 | import { ServerProviderUpdateError } from "@t3tools/contracts"; | 8 | import { ServerProviderUpdateError } from "@t3tools/contracts"; |
| 9 | import { Cause, Effect, Exit, Fiber, Layer, Ref, Schema, Sink, Stream } from "effect"; | 9 | import * as Cause from "effect/Cause"; |
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Exit from "effect/Exit"; | ||
| 12 | import * as Fiber from "effect/Fiber"; | ||
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as Ref from "effect/Ref"; | ||
| 15 | import * as Schema from "effect/Schema"; | ||
| 16 | import * as Sink from "effect/Sink"; | ||
| 17 | import * as Stream from "effect/Stream"; | ||
| 10 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; | 18 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; |
| 11 | import { ChildProcessSpawner } from "effect/unstable/process"; | 19 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 12 | 20 |
apps/server/src/provider/providerMaintenanceRunner.ts+23-3| ... | @@ -7,7 +7,16 @@ import { | ... | @@ -7,7 +7,16 @@ import { |
| 7 | type ServerProviderUpdatedPayload, | 7 | type ServerProviderUpdatedPayload, |
| 8 | type ServerProviderUpdateState, | 8 | type ServerProviderUpdateState, |
| 9 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 10 | import { Cause, Context, DateTime, Duration, Effect, Layer, Option, Ref, Schema } from "effect"; | 10 | import * as Cause from "effect/Cause"; |
| 11 | import * as Context from "effect/Context"; | ||
| 12 | import * as Data from "effect/Data"; | ||
| 13 | import * as DateTime from "effect/DateTime"; | ||
| 14 | import * as Duration from "effect/Duration"; | ||
| 15 | import * as Effect from "effect/Effect"; | ||
| 16 | import * as Layer from "effect/Layer"; | ||
| 17 | import * as Option from "effect/Option"; | ||
| 18 | import * as Ref from "effect/Ref"; | ||
| 19 | import * as Schema from "effect/Schema"; | ||
| 11 | import { HttpClient } from "effect/unstable/http"; | 20 | import { HttpClient } from "effect/unstable/http"; |
| 12 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 21 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 13 | 22 | ||
| ... | @@ -45,6 +54,11 @@ export class ProviderMaintenanceRunner extends Context.Service< | ... | @@ -45,6 +54,11 @@ export class ProviderMaintenanceRunner extends Context.Service< |
| 45 | ProviderMaintenanceRunnerShape | 54 | ProviderMaintenanceRunnerShape |
| 46 | >()("t3/provider/ProviderMaintenanceRunner") {} | 55 | >()("t3/provider/ProviderMaintenanceRunner") {} |
| 47 | 56 | ||
| 57 | class ProviderMaintenanceCommandError extends Data.TaggedError("ProviderMaintenanceCommandError")<{ | ||
| 58 | readonly message: string; | ||
| 59 | readonly cause?: unknown; | ||
| 60 | }> {} | ||
| 61 | |||
| 48 | interface VerifiedProviderRefresh { | 62 | interface VerifiedProviderRefresh { |
| 49 | readonly providers: ReadonlyArray<ServerProvider>; | 63 | readonly providers: ReadonlyArray<ServerProvider>; |
| 50 | readonly verifiedProviders: ReadonlyArray<ServerProvider>; | 64 | readonly verifiedProviders: ReadonlyArray<ServerProvider>; |
| ... | @@ -65,7 +79,10 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR | ... | @@ -65,7 +79,10 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR |
| 65 | .pipe( | 79 | .pipe( |
| 66 | Effect.mapError( | 80 | Effect.mapError( |
| 67 | (cause) => | 81 | (cause) => |
| 68 | new Error(`Failed to run update command ${input.command}: ${cause.message}`), | 82 | new ProviderMaintenanceCommandError({ |
| 83 | message: `Failed to run update command ${input.command}: ${cause.message}`, | ||
| 84 | cause, | ||
| 85 | }), | ||
| 69 | ), | 86 | ), |
| 70 | ); | 87 | ); |
| 71 | yield* Effect.addFinalizer(() => child.kill().pipe(Effect.ignore)); | 88 | yield* Effect.addFinalizer(() => child.kill().pipe(Effect.ignore)); |
| ... | @@ -86,7 +103,10 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR | ... | @@ -86,7 +103,10 @@ const runProviderMaintenanceCommandWithSpawner = Effect.fn("ProviderMaintenanceR |
| 86 | ).pipe( | 103 | ).pipe( |
| 87 | Effect.mapError( | 104 | Effect.mapError( |
| 88 | (cause) => | 105 | (cause) => |
| 89 | new Error(cause instanceof Error ? cause.message : "Update command failed to run."), | 106 | new ProviderMaintenanceCommandError({ |
| 107 | message: cause instanceof Error ? cause.message : "Update command failed to run.", | ||
| 108 | cause, | ||
| 109 | }), | ||
| 90 | ), | 110 | ), |
| 91 | ); | 111 | ); |
| 92 | 112 |
apps/server/src/provider/providerSnapshot.ts+11-3| ... | @@ -8,7 +8,9 @@ import type { | ... | @@ -8,7 +8,9 @@ import type { |
| 8 | ServerProviderModel, | 8 | ServerProviderModel, |
| 9 | ServerProviderState, | 9 | ServerProviderState, |
| 10 | } from "@t3tools/contracts"; | 10 | } from "@t3tools/contracts"; |
| 11 | import { Effect, Stream } from "effect"; | 11 | import * as Effect from "effect/Effect"; |
| 12 | import * as Data from "effect/Data"; | ||
| 13 | import * as Stream from "effect/Stream"; | ||
| 12 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 14 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 13 | import { normalizeModelSlug } from "@t3tools/shared/model"; | 15 | import { normalizeModelSlug } from "@t3tools/shared/model"; |
| 14 | import { isWindowsCommandNotFound } from "../processRunner.ts"; | 16 | import { isWindowsCommandNotFound } from "../processRunner.ts"; |
| ... | @@ -25,6 +27,12 @@ export interface CommandResult { | ... | @@ -25,6 +27,12 @@ export interface CommandResult { |
| 25 | readonly code: number; | 27 | readonly code: number; |
| 26 | } | 28 | } |
| 27 | 29 | ||
| 30 | export class ProviderCommandExecutionError extends Data.TaggedError( | ||
| 31 | "ProviderCommandExecutionError", | ||
| 32 | )<{ | ||
| 33 | readonly message: string; | ||
| 34 | }> {} | ||
| 35 | |||
| 28 | export interface ProviderProbeResult { | 36 | export interface ProviderProbeResult { |
| 29 | readonly installed: boolean; | 37 | readonly installed: boolean; |
| 30 | readonly version: string | null; | 38 | readonly version: string | null; |
| ... | @@ -47,7 +55,7 @@ export function nonEmptyTrimmed(value: string | undefined): string | undefined { | ... | @@ -47,7 +55,7 @@ export function nonEmptyTrimmed(value: string | undefined): string | undefined { |
| 47 | return trimmed.length > 0 ? trimmed : undefined; | 55 | return trimmed.length > 0 ? trimmed : undefined; |
| 48 | } | 56 | } |
| 49 | 57 | ||
| 50 | export function isCommandMissingCause(error: Error): boolean { | 58 | export function isCommandMissingCause(error: { readonly message: string }): boolean { |
| 51 | const lower = error.message.toLowerCase(); | 59 | const lower = error.message.toLowerCase(); |
| 52 | return lower.includes("enoent") || lower.includes("notfound"); | 60 | return lower.includes("enoent") || lower.includes("notfound"); |
| 53 | } | 61 | } |
| ... | @@ -67,7 +75,7 @@ export const spawnAndCollect = (binaryPath: string, command: ChildProcess.Comman | ... | @@ -67,7 +75,7 @@ export const spawnAndCollect = (binaryPath: string, command: ChildProcess.Comman |
| 67 | 75 | ||
| 68 | const result: CommandResult = { stdout, stderr, code: exitCode }; | 76 | const result: CommandResult = { stdout, stderr, code: exitCode }; |
| 69 | if (isWindowsCommandNotFound(exitCode, stderr)) { | 77 | if (isWindowsCommandNotFound(exitCode, stderr)) { |
| 70 | return yield* Effect.fail(new Error(`spawn ${binaryPath} ENOENT`)); | 78 | return yield* new ProviderCommandExecutionError({ message: `spawn ${binaryPath} ENOENT` }); |
| 71 | } | 79 | } |
| 72 | return result; | 80 | return result; |
| 73 | }).pipe(Effect.scoped); | 81 | }).pipe(Effect.scoped); |
apps/server/src/provider/providerStatusCache.test.ts+2-1| ... | @@ -7,7 +7,8 @@ import { | ... | @@ -7,7 +7,8 @@ import { |
| 7 | } from "@t3tools/contracts"; | 7 | } from "@t3tools/contracts"; |
| 8 | import { createModelCapabilities } from "@t3tools/shared/model"; | 8 | import { createModelCapabilities } from "@t3tools/shared/model"; |
| 9 | import { assert, it } from "@effect/vitest"; | 9 | import { assert, it } from "@effect/vitest"; |
| 10 | import { Effect, FileSystem } from "effect"; | 10 | import * as Effect from "effect/Effect"; |
| 11 | import * as FileSystem from "effect/FileSystem"; | ||
| 11 | 12 | ||
| 12 | import { | 13 | import { |
| 13 | hydrateCachedProvider, | 14 | hydrateCachedProvider, |
apps/server/src/provider/providerStatusCache.ts+5-1| ... | @@ -4,7 +4,11 @@ import { | ... | @@ -4,7 +4,11 @@ import { |
| 4 | type ServerProvider, | 4 | type ServerProvider, |
| 5 | ServerProvider as ServerProviderSchema, | 5 | ServerProvider as ServerProviderSchema, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Cause, Effect, FileSystem, Path, Schema } from "effect"; | 7 | import * as Cause from "effect/Cause"; |
| 8 | import * as Effect from "effect/Effect"; | ||
| 9 | import * as FileSystem from "effect/FileSystem"; | ||
| 10 | import * as Path from "effect/Path"; | ||
| 11 | import * as Schema from "effect/Schema"; | ||
| 8 | 12 | ||
| 9 | import { writeFileStringAtomically } from "../atomicWrite.ts"; | 13 | import { writeFileStringAtomically } from "../atomicWrite.ts"; |
| 10 | 14 |
apps/server/src/provider/testUtils/providerAdapterRegistryMock.ts+5-1| ... | @@ -18,7 +18,11 @@ import { | ... | @@ -18,7 +18,11 @@ import { |
| 18 | ProviderDriverKind, | 18 | ProviderDriverKind, |
| 19 | type ProviderInstanceId, | 19 | type ProviderInstanceId, |
| 20 | } from "@t3tools/contracts"; | 20 | } from "@t3tools/contracts"; |
| 21 | import { Effect, PubSub, Record, Result, Stream } from "effect"; | 21 | import * as Effect from "effect/Effect"; |
| 22 | import * as PubSub from "effect/PubSub"; | ||
| 23 | import * as Record from "effect/Record"; | ||
| 24 | import * as Result from "effect/Result"; | ||
| 25 | import * as Stream from "effect/Stream"; | ||
| 22 | 26 | ||
| 23 | import { ProviderUnsupportedError, type ProviderAdapterError } from "../Errors.ts"; | 27 | import { ProviderUnsupportedError, type ProviderAdapterError } from "../Errors.ts"; |
| 24 | import type { ProviderAdapterShape } from "../Services/ProviderAdapter.ts"; | 28 | import type { ProviderAdapterShape } from "../Services/ProviderAdapter.ts"; |
apps/server/src/provider/unavailableProviderSnapshot.ts+36-29| ... | @@ -15,6 +15,8 @@ import { | ... | @@ -15,6 +15,8 @@ import { |
| 15 | type ProviderInstanceId, | 15 | type ProviderInstanceId, |
| 16 | type ServerProvider, | 16 | type ServerProvider, |
| 17 | } from "@t3tools/contracts"; | 17 | } from "@t3tools/contracts"; |
| 18 | import * as DateTime from "effect/DateTime"; | ||
| 19 | import * as Effect from "effect/Effect"; | ||
| 18 | 20 | ||
| 19 | import { buildServerProvider } from "./providerSnapshot.ts"; | 21 | import { buildServerProvider } from "./providerSnapshot.ts"; |
| 20 | 22 | ||
| ... | @@ -25,12 +27,15 @@ export interface UnavailableProviderSnapshotInput { | ... | @@ -25,12 +27,15 @@ export interface UnavailableProviderSnapshotInput { |
| 25 | readonly accentColor?: string | undefined; | 27 | readonly accentColor?: string | undefined; |
| 26 | readonly reason: string; | 28 | readonly reason: string; |
| 27 | /** | 29 | /** |
| 28 | * Optional override for `checkedAt`. Defaulted to `new Date()` so callers | 30 | * Optional override for `checkedAt`. Defaulted to the current Effect |
| 31 | * `DateTime` so callers | ||
| 29 | * (notably tests) don't have to pass it. | 32 | * (notably tests) don't have to pass it. |
| 30 | */ | 33 | */ |
| 31 | readonly checkedAt?: string; | 34 | readonly checkedAt?: string; |
| 32 | } | 35 | } |
| 33 | 36 | ||
| 37 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); | ||
| 38 | |||
| 34 | /** | 39 | /** |
| 35 | * Produce a `ServerProvider` snapshot representing a configured instance | 40 | * Produce a `ServerProvider` snapshot representing a configured instance |
| 36 | * whose driver the running build does not implement. The result is safe | 41 | * whose driver the running build does not implement. The result is safe |
| ... | @@ -39,34 +44,36 @@ export interface UnavailableProviderSnapshotInput { | ... | @@ -39,34 +44,36 @@ export interface UnavailableProviderSnapshotInput { |
| 39 | */ | 44 | */ |
| 40 | export function buildUnavailableProviderSnapshot( | 45 | export function buildUnavailableProviderSnapshot( |
| 41 | input: UnavailableProviderSnapshotInput, | 46 | input: UnavailableProviderSnapshotInput, |
| 42 | ): ServerProvider { | 47 | ): Effect.Effect<ServerProvider> { |
| 43 | const checkedAt = input.checkedAt ?? new Date().toISOString(); | 48 | return Effect.gen(function* () { |
| 44 | const displayName = input.displayName?.trim() || (input.driverKind as string); | 49 | const checkedAt = input.checkedAt ?? (yield* nowIso); |
| 50 | const displayName = input.displayName?.trim() || (input.driverKind as string); | ||
| 45 | 51 | ||
| 46 | const base = buildServerProvider({ | 52 | const base = buildServerProvider({ |
| 47 | presentation: { displayName }, | 53 | presentation: { displayName }, |
| 48 | enabled: false, | 54 | enabled: false, |
| 49 | checkedAt, | 55 | checkedAt, |
| 50 | models: [], | 56 | models: [], |
| 51 | skills: [], | 57 | skills: [], |
| 52 | probe: { | 58 | probe: { |
| 53 | installed: false, | 59 | installed: false, |
| 54 | version: null, | 60 | version: null, |
| 55 | status: "error", | 61 | status: "error", |
| 56 | auth: { status: "unknown" }, | 62 | auth: { status: "unknown" }, |
| 57 | message: input.reason, | 63 | message: input.reason, |
| 58 | }, | 64 | }, |
| 59 | }); | 65 | }); |
| 60 | 66 | ||
| 61 | return { | 67 | return { |
| 62 | ...base, | 68 | ...base, |
| 63 | instanceId: input.instanceId, | 69 | instanceId: input.instanceId, |
| 64 | ...(input.accentColor ? { accentColor: input.accentColor } : {}), | 70 | ...(input.accentColor ? { accentColor: input.accentColor } : {}), |
| 65 | driver: | 71 | driver: |
| 66 | typeof input.driverKind === "string" | 72 | typeof input.driverKind === "string" |
| 67 | ? ProviderDriverKind.make(input.driverKind) | 73 | ? ProviderDriverKind.make(input.driverKind) |
| 68 | : input.driverKind, | 74 | : input.driverKind, |
| 69 | availability: "unavailable", | 75 | availability: "unavailable", |
| 70 | unavailableReason: input.reason, | 76 | unavailableReason: input.reason, |
| 71 | }; | 77 | }; |
| 78 | }); | ||
| 72 | } | 79 | } |
apps/server/src/server.test.ts+59-51| ... | @@ -27,18 +27,17 @@ import { | ... | @@ -27,18 +27,17 @@ import { |
| 27 | } from "@t3tools/contracts"; | 27 | } from "@t3tools/contracts"; |
| 28 | import { assert, it } from "@effect/vitest"; | 28 | import { assert, it } from "@effect/vitest"; |
| 29 | import { assertFailure, assertInclude, assertTrue } from "@effect/vitest/utils"; | 29 | import { assertFailure, assertInclude, assertTrue } from "@effect/vitest/utils"; |
| 30 | import { | 30 | import * as Clock from "effect/Clock"; |
| 31 | Deferred, | 31 | import * as Deferred from "effect/Deferred"; |
| 32 | DateTime, | 32 | import * as DateTime from "effect/DateTime"; |
| 33 | Duration, | 33 | import * as Duration from "effect/Duration"; |
| 34 | Effect, | 34 | import * as Effect from "effect/Effect"; |
| 35 | FileSystem, | 35 | import * as FileSystem from "effect/FileSystem"; |
| 36 | Layer, | 36 | import * as Layer from "effect/Layer"; |
| 37 | ManagedRuntime, | 37 | import * as ManagedRuntime from "effect/ManagedRuntime"; |
| 38 | Option, | 38 | import * as Option from "effect/Option"; |
| 39 | Path, | 39 | import * as Path from "effect/Path"; |
| 40 | Stream, | 40 | import * as Stream from "effect/Stream"; |
| 41 | } from "effect"; | ||
| 42 | import { ChildProcessSpawner } from "effect/unstable/process"; | 41 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 43 | import { | 42 | import { |
| 44 | FetchHttpClient, | 43 | FetchHttpClient, |
| ... | @@ -92,6 +91,7 @@ import { | ... | @@ -92,6 +91,7 @@ import { |
| 92 | import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts"; | 91 | import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts"; |
| 93 | import { | 92 | import { |
| 94 | ProjectSetupScriptRunner, | 93 | ProjectSetupScriptRunner, |
| 94 | ProjectSetupScriptRunnerError, | ||
| 95 | type ProjectSetupScriptRunnerShape, | 95 | type ProjectSetupScriptRunnerShape, |
| 96 | } from "./project/Services/ProjectSetupScriptRunner.ts"; | 96 | } from "./project/Services/ProjectSetupScriptRunner.ts"; |
| 97 | import { | 97 | import { |
| ... | @@ -116,6 +116,7 @@ import { ServerSecretStoreLive } from "./auth/Layers/ServerSecretStore.ts"; | ... | @@ -116,6 +116,7 @@ import { ServerSecretStoreLive } from "./auth/Layers/ServerSecretStore.ts"; |
| 116 | import { ServerAuthLive } from "./auth/Layers/ServerAuth.ts"; | 116 | import { ServerAuthLive } from "./auth/Layers/ServerAuth.ts"; |
| 117 | import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; | 117 | import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; |
| 118 | import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts"; | 118 | import * as TraceDiagnostics from "./diagnostics/TraceDiagnostics.ts"; |
| 119 | import * as Data from "effect/Data"; | ||
| 119 | 120 | ||
| 120 | const defaultProjectId = ProjectId.make("project-default"); | 121 | const defaultProjectId = ProjectId.make("project-default"); |
| 121 | const defaultThreadId = ThreadId.make("thread-default"); | 122 | const defaultThreadId = ThreadId.make("thread-default"); |
| ... | @@ -137,7 +138,7 @@ const testEnvironmentDescriptor = { | ... | @@ -137,7 +138,7 @@ const testEnvironmentDescriptor = { |
| 137 | }, | 138 | }, |
| 138 | }; | 139 | }; |
| 139 | const makeDefaultOrchestrationReadModel = () => { | 140 | const makeDefaultOrchestrationReadModel = () => { |
| 140 | const now = new Date().toISOString(); | 141 | const now = "2026-01-01T00:00:00.000Z"; |
| 141 | return { | 142 | return { |
| 142 | snapshotSequence: 0, | 143 | snapshotSequence: 0, |
| 143 | updatedAt: now, | 144 | updatedAt: now, |
| ... | @@ -181,7 +182,7 @@ const makeDefaultOrchestrationReadModel = () => { | ... | @@ -181,7 +182,7 @@ const makeDefaultOrchestrationReadModel = () => { |
| 181 | const makeDefaultOrchestrationThreadShell = ( | 182 | const makeDefaultOrchestrationThreadShell = ( |
| 182 | overrides: Partial<OrchestrationThreadShell> = {}, | 183 | overrides: Partial<OrchestrationThreadShell> = {}, |
| 183 | ): OrchestrationThreadShell => { | 184 | ): OrchestrationThreadShell => { |
| 184 | const now = new Date().toISOString(); | 185 | const now = "2026-01-01T00:00:00.000Z"; |
| 185 | return { | 186 | return { |
| 186 | id: defaultThreadId, | 187 | id: defaultThreadId, |
| 187 | projectId: defaultProjectId, | 188 | projectId: defaultProjectId, |
| ... | @@ -302,15 +303,13 @@ const makeBrowserOtlpPayload = (spanName: string) => | ... | @@ -302,15 +303,13 @@ const makeBrowserOtlpPayload = (spanName: string) => |
| 302 | yield* Effect.promise(() => runtime.dispose()); | 303 | yield* Effect.promise(() => runtime.dispose()); |
| 303 | } | 304 | } |
| 304 | 305 | ||
| 305 | const request = yield* Effect.promise(() => | 306 | const request = yield* Effect.raceFirst( |
| 306 | Promise.race([ | 307 | Effect.promise(() => collector.firstRequest).pipe(Effect.orDie), |
| 307 | collector.firstRequest, | 308 | Effect.sleep(Duration.seconds(1)).pipe( |
| 308 | new Promise<never>((_, reject) => { | 309 | Effect.andThen(Effect.die(new Error("Timed out waiting for OTLP trace export"))), |
| 309 | setTimeout(() => reject(new Error("Timed out waiting for OTLP trace export")), 1_000); | 310 | ), |
| 310 | }), | ||
| 311 | ]), | ||
| 312 | ); | 311 | ); |
| 313 | 312 | // @effect-diagnostics-next-line preferSchemaOverJson:off | |
| 314 | return JSON.parse(request.body) as OtlpTracer.TraceData; | 313 | return JSON.parse(request.body) as OtlpTracer.TraceData; |
| 315 | }); | 314 | }); |
| 316 | 315 | ||
| ... | @@ -629,7 +628,7 @@ const buildAppUnderTest = (options?: { | ... | @@ -629,7 +628,7 @@ const buildAppUnderTest = (options?: { |
| 629 | snapshotSequence: 0, | 628 | snapshotSequence: 0, |
| 630 | projects: [], | 629 | projects: [], |
| 631 | threads: [], | 630 | threads: [], |
| 632 | updatedAt: new Date(0).toISOString(), | 631 | updatedAt: "1970-01-01T00:00:00.000Z", |
| 633 | }), | 632 | }), |
| 634 | getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), | 633 | getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), |
| 635 | getProjectShellById: () => Effect.succeed(Option.none()), | 634 | getProjectShellById: () => Effect.succeed(Option.none()), |
| ... | @@ -823,17 +822,23 @@ const bootstrapBearerSession = (credential = defaultDesktopBootstrapToken) => | ... | @@ -823,17 +822,23 @@ const bootstrapBearerSession = (credential = defaultDesktopBootstrapToken) => |
| 823 | }; | 822 | }; |
| 824 | }); | 823 | }); |
| 825 | 824 | ||
| 825 | class AuthenticationGetterError extends Data.TaggedError("AuthenticationGetterError")<{ | ||
| 826 | readonly message: string; | ||
| 827 | }> {} | ||
| 828 | |||
| 826 | const getAuthenticatedSessionCookieHeader = (credential = defaultDesktopBootstrapToken) => | 829 | const getAuthenticatedSessionCookieHeader = (credential = defaultDesktopBootstrapToken) => |
| 827 | Effect.gen(function* () { | 830 | Effect.gen(function* () { |
| 828 | const { response, cookie } = yield* bootstrapBrowserSession(credential); | 831 | const { response, cookie } = yield* bootstrapBrowserSession(credential); |
| 829 | if (!response.ok) { | 832 | if (!response.ok) { |
| 830 | return yield* Effect.fail( | 833 | return yield* new AuthenticationGetterError({ |
| 831 | new Error(`Expected bootstrap session response to succeed, got ${response.status}`), | 834 | message: `Expected bootstrap session response to succeed, got ${response.status}`, |
| 832 | ); | 835 | }); |
| 833 | } | 836 | } |
| 834 | 837 | ||
| 835 | if (!cookie) { | 838 | if (!cookie) { |
| 836 | return yield* Effect.fail(new Error("Expected bootstrap session response to set a cookie.")); | 839 | return yield* new AuthenticationGetterError({ |
| 840 | message: "Expected bootstrap session response to set a cookie.", | ||
| 841 | }); | ||
| 837 | } | 842 | } |
| 838 | 843 | ||
| 839 | return cookie.split(";")[0] ?? cookie; | 844 | return cookie.split(";")[0] ?? cookie; |
| ... | @@ -843,15 +848,15 @@ const getAuthenticatedBearerSessionToken = (credential = defaultDesktopBootstrap | ... | @@ -843,15 +848,15 @@ const getAuthenticatedBearerSessionToken = (credential = defaultDesktopBootstrap |
| 843 | Effect.gen(function* () { | 848 | Effect.gen(function* () { |
| 844 | const { response, body } = yield* bootstrapBearerSession(credential); | 849 | const { response, body } = yield* bootstrapBearerSession(credential); |
| 845 | if (!response.ok) { | 850 | if (!response.ok) { |
| 846 | return yield* Effect.fail( | 851 | return yield* new AuthenticationGetterError({ |
| 847 | new Error(`Expected bearer bootstrap response to succeed, got ${response.status}`), | 852 | message: `Expected bearer bootstrap response to succeed, got ${response.status}`, |
| 848 | ); | 853 | }); |
| 849 | } | 854 | } |
| 850 | 855 | ||
| 851 | if (!body.sessionToken) { | 856 | if (!body.sessionToken) { |
| 852 | return yield* Effect.fail( | 857 | return yield* new AuthenticationGetterError({ |
| 853 | new Error("Expected bearer bootstrap response to include a session token."), | 858 | message: "Expected bearer bootstrap response to include a session token.", |
| 854 | ); | 859 | }); |
| 855 | } | 860 | } |
| 856 | 861 | ||
| 857 | return body.sessionToken; | 862 | return body.sessionToken; |
| ... | @@ -1758,6 +1763,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -1758,6 +1763,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 1758 | "content-type": "application/json", | 1763 | "content-type": "application/json", |
| 1759 | origin: "http://localhost:5733", | 1764 | origin: "http://localhost:5733", |
| 1760 | }, | 1765 | }, |
| 1766 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1761 | body: HttpBody.text(JSON.stringify(payload), "application/json"), | 1767 | body: HttpBody.text(JSON.stringify(payload), "application/json"), |
| 1762 | }); | 1768 | }); |
| 1763 | 1769 | ||
| ... | @@ -1803,6 +1809,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -1803,6 +1809,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 1803 | ]); | 1809 | ]); |
| 1804 | assert.deepEqual(upstreamRequests, [ | 1810 | assert.deepEqual(upstreamRequests, [ |
| 1805 | { | 1811 | { |
| 1812 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1806 | body: JSON.stringify(payload), | 1813 | body: JSON.stringify(payload), |
| 1807 | contentType: "application/json", | 1814 | contentType: "application/json", |
| 1808 | }, | 1815 | }, |
| ... | @@ -1875,6 +1882,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -1875,6 +1882,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 1875 | cookie: yield* getAuthenticatedSessionCookieHeader(), | 1882 | cookie: yield* getAuthenticatedSessionCookieHeader(), |
| 1876 | "content-type": "application/json", | 1883 | "content-type": "application/json", |
| 1877 | }, | 1884 | }, |
| 1885 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 1878 | body: HttpBody.text(JSON.stringify(payload), "application/json"), | 1886 | body: HttpBody.text(JSON.stringify(payload), "application/json"), |
| 1879 | }); | 1887 | }); |
| 1880 | 1888 | ||
| ... | @@ -2182,7 +2190,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -2182,7 +2190,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 2182 | version: 1 as const, | 2190 | version: 1 as const, |
| 2183 | sequence: 2, | 2191 | sequence: 2, |
| 2184 | type: "ready" as const, | 2192 | type: "ready" as const, |
| 2185 | payload: { at: new Date().toISOString(), environment: testEnvironmentDescriptor }, | 2193 | payload: { at: "2026-01-01T00:00:00.000Z", environment: testEnvironmentDescriptor }, |
| 2186 | }); | 2194 | }); |
| 2187 | 2195 | ||
| 2188 | yield* buildAppUnderTest({ | 2196 | yield* buildAppUnderTest({ |
| ... | @@ -2370,7 +2378,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -2370,7 +2378,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 2370 | instanceId: ProviderInstanceId.make("codex"), | 2378 | instanceId: ProviderInstanceId.make("codex"), |
| 2371 | model: "gpt-5-codex", | 2379 | model: "gpt-5-codex", |
| 2372 | }, | 2380 | }, |
| 2373 | createdAt: new Date().toISOString(), | 2381 | createdAt: "2026-01-01T00:00:00.000Z", |
| 2374 | }), | 2382 | }), |
| 2375 | ), | 2383 | ), |
| 2376 | ); | 2384 | ); |
| ... | @@ -2913,11 +2921,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -2913,11 +2921,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 2913 | }); | 2921 | }); |
| 2914 | 2922 | ||
| 2915 | const wsUrl = yield* getWsServerUrl("/ws"); | 2923 | const wsUrl = yield* getWsServerUrl("/ws"); |
| 2916 | const startedAt = Date.now(); | 2924 | const startedAt = yield* Clock.currentTimeMillis; |
| 2917 | const result = yield* Effect.scoped( | 2925 | const result = yield* Effect.scoped( |
| 2918 | withWsRpcClient(wsUrl, (client) => client[WS_METHODS.vcsPull]({ cwd: "/tmp/repo" })), | 2926 | withWsRpcClient(wsUrl, (client) => client[WS_METHODS.vcsPull]({ cwd: "/tmp/repo" })), |
| 2919 | ); | 2927 | ); |
| 2920 | const elapsedMs = Date.now() - startedAt; | 2928 | const elapsedMs = (yield* Clock.currentTimeMillis) - startedAt; |
| 2921 | 2929 | ||
| 2922 | assert.equal(result.status, "pulled"); | 2930 | assert.equal(result.status, "pulled"); |
| 2923 | assertTrue(elapsedMs < 1_000); | 2931 | assertTrue(elapsedMs < 1_000); |
| ... | @@ -2982,7 +2990,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -2982,7 +2990,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 2982 | }); | 2990 | }); |
| 2983 | 2991 | ||
| 2984 | const wsUrl = yield* getWsServerUrl("/ws"); | 2992 | const wsUrl = yield* getWsServerUrl("/ws"); |
| 2985 | const startedAt = Date.now(); | 2993 | const startedAt = yield* Clock.currentTimeMillis; |
| 2986 | yield* Effect.scoped( | 2994 | yield* Effect.scoped( |
| 2987 | withWsRpcClient(wsUrl, (client) => | 2995 | withWsRpcClient(wsUrl, (client) => |
| 2988 | client[WS_METHODS.gitRunStackedAction]({ | 2996 | client[WS_METHODS.gitRunStackedAction]({ |
| ... | @@ -2992,7 +3000,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -2992,7 +3000,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 2992 | }).pipe(Stream.runCollect), | 3000 | }).pipe(Stream.runCollect), |
| 2993 | ), | 3001 | ), |
| 2994 | ); | 3002 | ); |
| 2995 | const elapsedMs = Date.now() - startedAt; | 3003 | const elapsedMs = (yield* Clock.currentTimeMillis) - startedAt; |
| 2996 | 3004 | ||
| 2997 | assertTrue(elapsedMs < 1_000); | 3005 | assertTrue(elapsedMs < 1_000); |
| 2998 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), | 3006 | }).pipe(Effect.provide(NodeHttpServer.layerTest)), |
| ... | @@ -3079,7 +3087,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3079,7 +3087,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3079 | 3087 | ||
| 3080 | it.effect("routes websocket rpc orchestration methods", () => | 3088 | it.effect("routes websocket rpc orchestration methods", () => |
| 3081 | Effect.gen(function* () { | 3089 | Effect.gen(function* () { |
| 3082 | const now = new Date().toISOString(); | 3090 | const now = "2026-01-01T00:00:00.000Z"; |
| 3083 | const snapshot = { | 3091 | const snapshot = { |
| 3084 | snapshotSequence: 1, | 3092 | snapshotSequence: 1, |
| 3085 | updatedAt: now, | 3093 | updatedAt: now, |
| ... | @@ -3292,7 +3300,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3292,7 +3300,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3292 | const threadId = ThreadId.make("thread-archive"); | 3300 | const threadId = ThreadId.make("thread-archive"); |
| 3293 | const effects: string[] = []; | 3301 | const effects: string[] = []; |
| 3294 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 3302 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 3295 | const now = new Date().toISOString(); | 3303 | const now = "2026-01-01T00:00:00.000Z"; |
| 3296 | 3304 | ||
| 3297 | yield* buildAppUnderTest({ | 3305 | yield* buildAppUnderTest({ |
| 3298 | layers: { | 3306 | layers: { |
| ... | @@ -3363,7 +3371,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3363,7 +3371,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3363 | const threadId = ThreadId.make("thread-archive-precheck"); | 3371 | const threadId = ThreadId.make("thread-archive-precheck"); |
| 3364 | const effects: string[] = []; | 3372 | const effects: string[] = []; |
| 3365 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 3373 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 3366 | const now = new Date().toISOString(); | 3374 | const now = "2026-01-01T00:00:00.000Z"; |
| 3367 | let archived = false; | 3375 | let archived = false; |
| 3368 | 3376 | ||
| 3369 | yield* buildAppUnderTest({ | 3377 | yield* buildAppUnderTest({ |
| ... | @@ -3494,7 +3502,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3494,7 +3502,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3494 | const threadId = ThreadId.make("thread-archive-stopped-session"); | 3502 | const threadId = ThreadId.make("thread-archive-stopped-session"); |
| 3495 | const effects: string[] = []; | 3503 | const effects: string[] = []; |
| 3496 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 3504 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 3497 | const now = new Date().toISOString(); | 3505 | const now = "2026-01-01T00:00:00.000Z"; |
| 3498 | 3506 | ||
| 3499 | yield* buildAppUnderTest({ | 3507 | yield* buildAppUnderTest({ |
| 3500 | layers: { | 3508 | layers: { |
| ... | @@ -3560,7 +3568,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3560,7 +3568,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3560 | const threadId = ThreadId.make("thread-archive-stop-failure"); | 3568 | const threadId = ThreadId.make("thread-archive-stop-failure"); |
| 3561 | const effects: string[] = []; | 3569 | const effects: string[] = []; |
| 3562 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 3570 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 3563 | const now = new Date().toISOString(); | 3571 | const now = "2026-01-01T00:00:00.000Z"; |
| 3564 | 3572 | ||
| 3565 | yield* buildAppUnderTest({ | 3573 | yield* buildAppUnderTest({ |
| 3566 | layers: { | 3574 | layers: { |
| ... | @@ -3637,7 +3645,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3637,7 +3645,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3637 | const threadId = ThreadId.make("thread-archive-stop-defect"); | 3645 | const threadId = ThreadId.make("thread-archive-stop-defect"); |
| 3638 | const effects: string[] = []; | 3646 | const effects: string[] = []; |
| 3639 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 3647 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 3640 | const now = new Date().toISOString(); | 3648 | const now = "2026-01-01T00:00:00.000Z"; |
| 3641 | 3649 | ||
| 3642 | yield* buildAppUnderTest({ | 3650 | yield* buildAppUnderTest({ |
| 3643 | layers: { | 3651 | layers: { |
| ... | @@ -3769,7 +3777,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3769,7 +3777,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3769 | }, | 3777 | }, |
| 3770 | }); | 3778 | }); |
| 3771 | 3779 | ||
| 3772 | const createdAt = new Date().toISOString(); | 3780 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 3773 | const wsUrl = yield* getWsServerUrl("/ws"); | 3781 | const wsUrl = yield* getWsServerUrl("/ws"); |
| 3774 | const response = yield* Effect.scoped( | 3782 | const response = yield* Effect.scoped( |
| 3775 | withWsRpcClient(wsUrl, (client) => | 3783 | withWsRpcClient(wsUrl, (client) => |
| ... | @@ -3864,7 +3872,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3864,7 +3872,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3864 | ); | 3872 | ); |
| 3865 | const runForThread = vi.fn( | 3873 | const runForThread = vi.fn( |
| 3866 | (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) => | 3874 | (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) => |
| 3867 | Effect.fail(new Error("pty unavailable")), | 3875 | Effect.fail(new ProjectSetupScriptRunnerError({ message: "pty unavailable" })), |
| 3868 | ); | 3876 | ); |
| 3869 | 3877 | ||
| 3870 | yield* buildAppUnderTest({ | 3878 | yield* buildAppUnderTest({ |
| ... | @@ -3886,7 +3894,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -3886,7 +3894,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 3886 | }, | 3894 | }, |
| 3887 | }); | 3895 | }); |
| 3888 | 3896 | ||
| 3889 | const createdAt = new Date().toISOString(); | 3897 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 3890 | const wsUrl = yield* getWsServerUrl("/ws"); | 3898 | const wsUrl = yield* getWsServerUrl("/ws"); |
| 3891 | const response = yield* Effect.scoped( | 3899 | const response = yield* Effect.scoped( |
| 3892 | withWsRpcClient(wsUrl, (client) => | 3900 | withWsRpcClient(wsUrl, (client) => |
| ... | @@ -4003,7 +4011,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -4003,7 +4011,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 4003 | }, | 4011 | }, |
| 4004 | }); | 4012 | }); |
| 4005 | 4013 | ||
| 4006 | const createdAt = new Date().toISOString(); | 4014 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 4007 | const wsUrl = yield* getWsServerUrl("/ws"); | 4015 | const wsUrl = yield* getWsServerUrl("/ws"); |
| 4008 | const response = yield* Effect.scoped( | 4016 | const response = yield* Effect.scoped( |
| 4009 | withWsRpcClient(wsUrl, (client) => | 4017 | withWsRpcClient(wsUrl, (client) => |
| ... | @@ -4087,7 +4095,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -4087,7 +4095,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 4087 | }, | 4095 | }, |
| 4088 | }); | 4096 | }); |
| 4089 | 4097 | ||
| 4090 | const createdAt = new Date().toISOString(); | 4098 | const createdAt = "2026-01-01T00:00:00.000Z"; |
| 4091 | const wsUrl = yield* getWsServerUrl("/ws"); | 4099 | const wsUrl = yield* getWsServerUrl("/ws"); |
| 4092 | const result = yield* Effect.scoped( | 4100 | const result = yield* Effect.scoped( |
| 4093 | withWsRpcClient(wsUrl, (client) => | 4101 | withWsRpcClient(wsUrl, (client) => |
| ... | @@ -4149,7 +4157,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -4149,7 +4157,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 4149 | history: "", | 4157 | history: "", |
| 4150 | exitCode: null, | 4158 | exitCode: null, |
| 4151 | exitSignal: null, | 4159 | exitSignal: null, |
| 4152 | updatedAt: new Date().toISOString(), | 4160 | updatedAt: "2026-01-01T00:00:00.000Z", |
| 4153 | }; | 4161 | }; |
| 4154 | 4162 | ||
| 4155 | yield* buildAppUnderTest({ | 4163 | yield* buildAppUnderTest({ |
apps/server/src/server.ts+5-8| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Effect, Layer } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 2 | import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; | 3 | import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; |
| 3 | 4 | ||
| 4 | import { ServerConfig } from "./config.ts"; | 5 | import { ServerConfig } from "./config.ts"; |
| ... | @@ -86,7 +87,7 @@ import { | ... | @@ -86,7 +87,7 @@ import { |
| 86 | orchestrationDispatchRouteLayer, | 87 | orchestrationDispatchRouteLayer, |
| 87 | orchestrationSnapshotRouteLayer, | 88 | orchestrationSnapshotRouteLayer, |
| 88 | } from "./orchestration/http.ts"; | 89 | } from "./orchestration/http.ts"; |
| 89 | import { NetService } from "@t3tools/shared/Net"; | 90 | import * as NetService from "@t3tools/shared/Net"; |
| 90 | import { disableTailscaleServe, ensureTailscaleServe } from "@t3tools/tailscale"; | 91 | import { disableTailscaleServe, ensureTailscaleServe } from "@t3tools/tailscale"; |
| 91 | 92 | ||
| 92 | const PtyAdapterLive = Layer.unwrap( | 93 | const PtyAdapterLive = Layer.unwrap( |
| ... | @@ -333,7 +334,7 @@ export const makeServerLayer = Layer.unwrap( | ... | @@ -333,7 +334,7 @@ export const makeServerLayer = Layer.unwrap( |
| 333 | return; | 334 | return; |
| 334 | } | 335 | } |
| 335 | 336 | ||
| 336 | const state = makePersistedServerRuntimeState({ | 337 | const state = yield* makePersistedServerRuntimeState({ |
| 337 | config, | 338 | config, |
| 338 | port: address.port, | 339 | port: address.port, |
| 339 | }); | 340 | }); |
| ... | @@ -418,8 +419,4 @@ export const makeServerLayer = Layer.unwrap( | ... | @@ -418,8 +419,4 @@ export const makeServerLayer = Layer.unwrap( |
| 418 | ); | 419 | ); |
| 419 | 420 | ||
| 420 | // Important: Only `ServerConfig` should be provided by the CLI layer!!! Don't let other requirements leak into the launch layer. | 421 | // Important: Only `ServerConfig` should be provided by the CLI layer!!! Don't let other requirements leak into the launch layer. |
| 421 | export const runServer = Layer.launch(makeServerLayer) satisfies Effect.Effect< | 422 | export const runServer = Layer.launch(makeServerLayer); |
| 422 | never, | ||
| 423 | any, | ||
| 424 | ServerConfig | ||
| 425 | >; |
apps/server/src/serverLifecycleEvents.test.ts+3-2| ... | @@ -1,7 +1,8 @@ | ... | @@ -1,7 +1,8 @@ |
| 1 | import { EnvironmentId } from "@t3tools/contracts"; | 1 | import { EnvironmentId } from "@t3tools/contracts"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { assertTrue } from "@effect/vitest/utils"; | 3 | import { assertTrue } from "@effect/vitest/utils"; |
| 4 | import { Effect, Option } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Option from "effect/Option"; | ||
| 5 | 6 | ||
| 6 | import { ServerLifecycleEvents, ServerLifecycleEventsLive } from "./serverLifecycleEvents.ts"; | 7 | import { ServerLifecycleEvents, ServerLifecycleEventsLive } from "./serverLifecycleEvents.ts"; |
| 7 | 8 | ||
| ... | @@ -37,7 +38,7 @@ it.effect( | ... | @@ -37,7 +38,7 @@ it.effect( |
| 37 | version: 1, | 38 | version: 1, |
| 38 | type: "ready", | 39 | type: "ready", |
| 39 | payload: { | 40 | payload: { |
| 40 | at: new Date().toISOString(), | 41 | at: "2026-01-01T00:00:00.000Z", |
| 41 | environment, | 42 | environment, |
| 42 | }, | 43 | }, |
| 43 | }) | 44 | }) |
apps/server/src/serverLifecycleEvents.ts+6-1| ... | @@ -1,5 +1,10 @@ | ... | @@ -1,5 +1,10 @@ |
| 1 | import type { ServerLifecycleStreamEvent } from "@t3tools/contracts"; | 1 | import type { ServerLifecycleStreamEvent } from "@t3tools/contracts"; |
| 2 | import { Effect, Layer, PubSub, Ref, Context, Stream } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as PubSub from "effect/PubSub"; | ||
| 5 | import * as Ref from "effect/Ref"; | ||
| 6 | import * as Context from "effect/Context"; | ||
| 7 | import * as Stream from "effect/Stream"; | ||
| 3 | 8 | ||
| 4 | type LifecycleEventInput = | 9 | type LifecycleEventInput = |
| 5 | | Omit<Extract<ServerLifecycleStreamEvent, { type: "welcome" }>, "sequence"> | 10 | | Omit<Extract<ServerLifecycleStreamEvent, { type: "welcome" }>, "sequence"> |
apps/server/src/serverLogger.ts+4-1| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | import { Effect, Logger, References, Layer } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Logger from "effect/Logger"; | ||
| 3 | import * as References from "effect/References"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 2 | 5 | ||
| 3 | import { ServerConfig } from "./config.ts"; | 6 | import { ServerConfig } from "./config.ts"; |
| 4 | 7 |
apps/server/src/serverRuntimeStartup.test.ts+6-1| ... | @@ -1,7 +1,12 @@ | ... | @@ -1,7 +1,12 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { DEFAULT_MODEL, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; | 2 | import { DEFAULT_MODEL, ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; |
| 3 | import { assert, it } from "@effect/vitest"; | 3 | import { assert, it } from "@effect/vitest"; |
| 4 | import { Deferred, Effect, Fiber, Option, Ref, Stream } from "effect"; | 4 | import * as Deferred from "effect/Deferred"; |
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as Fiber from "effect/Fiber"; | ||
| 7 | import * as Option from "effect/Option"; | ||
| 8 | import * as Ref from "effect/Ref"; | ||
| 9 | import * as Stream from "effect/Stream"; | ||
| 5 | 10 | ||
| 6 | import { ServerConfig } from "./config.ts"; | 11 | import { ServerConfig } from "./config.ts"; |
| 7 | import { | 12 | import { |
apps/server/src/serverRuntimeStartup.ts+16-17| ... | @@ -7,20 +7,19 @@ import { | ... | @@ -7,20 +7,19 @@ import { |
| 7 | ProviderInstanceId, | 7 | ProviderInstanceId, |
| 8 | ThreadId, | 8 | ThreadId, |
| 9 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 10 | import { | 10 | import * as Data from "effect/Data"; |
| 11 | Data, | 11 | import * as Deferred from "effect/Deferred"; |
| 12 | Deferred, | 12 | import * as Effect from "effect/Effect"; |
| 13 | Effect, | 13 | import * as Exit from "effect/Exit"; |
| 14 | Exit, | 14 | import * as Layer from "effect/Layer"; |
| 15 | Layer, | 15 | import * as Option from "effect/Option"; |
| 16 | Option, | 16 | import * as Path from "effect/Path"; |
| 17 | Path, | 17 | import * as Queue from "effect/Queue"; |
| 18 | Queue, | 18 | import * as Ref from "effect/Ref"; |
| 19 | Ref, | 19 | import * as Scope from "effect/Scope"; |
| 20 | Scope, | 20 | import * as Context from "effect/Context"; |
| 21 | Context, | 21 | import * as Console from "effect/Console"; |
| 22 | Console, | 22 | import * as DateTime from "effect/DateTime"; |
| 23 | } from "effect"; | ||
| 24 | 23 | ||
| 25 | import { ServerConfig } from "./config.ts"; | 24 | import { ServerConfig } from "./config.ts"; |
| 26 | import { Keybindings } from "./keybindings.ts"; | 25 | import { Keybindings } from "./keybindings.ts"; |
| ... | @@ -188,7 +187,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { | ... | @@ -188,7 +187,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { |
| 188 | let nextProjectDefaultModelSelection: ModelSelection; | 187 | let nextProjectDefaultModelSelection: ModelSelection; |
| 189 | 188 | ||
| 190 | if (Option.isNone(existingProject)) { | 189 | if (Option.isNone(existingProject)) { |
| 191 | const createdAt = new Date().toISOString(); | 190 | const createdAt = DateTime.formatIso(yield* DateTime.now); |
| 192 | nextProjectId = ProjectId.make(crypto.randomUUID()); | 191 | nextProjectId = ProjectId.make(crypto.randomUUID()); |
| 193 | const bootstrapProjectTitle = path.basename(serverConfig.cwd) || "project"; | 192 | const bootstrapProjectTitle = path.basename(serverConfig.cwd) || "project"; |
| 194 | nextProjectDefaultModelSelection = getAutoBootstrapDefaultModelSelection(); | 193 | nextProjectDefaultModelSelection = getAutoBootstrapDefaultModelSelection(); |
| ... | @@ -210,7 +209,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { | ... | @@ -210,7 +209,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { |
| 210 | const existingThreadId = | 209 | const existingThreadId = |
| 211 | yield* projectionReadModelQuery.getFirstActiveThreadIdByProjectId(nextProjectId); | 210 | yield* projectionReadModelQuery.getFirstActiveThreadIdByProjectId(nextProjectId); |
| 212 | if (Option.isNone(existingThreadId)) { | 211 | if (Option.isNone(existingThreadId)) { |
| 213 | const createdAt = new Date().toISOString(); | 212 | const createdAt = DateTime.formatIso(yield* DateTime.now); |
| 214 | const createdThreadId = ThreadId.make(crypto.randomUUID()); | 213 | const createdThreadId = ThreadId.make(crypto.randomUUID()); |
| 215 | yield* orchestrationEngine.dispatch({ | 214 | yield* orchestrationEngine.dispatch({ |
| 216 | type: "thread.create", | 215 | type: "thread.create", |
| ... | @@ -423,7 +422,7 @@ export const makeServerRuntimeStartup = Effect.gen(function* () { | ... | @@ -423,7 +422,7 @@ export const makeServerRuntimeStartup = Effect.gen(function* () { |
| 423 | version: 1, | 422 | version: 1, |
| 424 | type: "ready", | 423 | type: "ready", |
| 425 | payload: { | 424 | payload: { |
| 426 | at: new Date().toISOString(), | 425 | at: DateTime.formatIso(yield* DateTime.now), |
| 427 | environment: yield* serverEnvironment.getDescriptor, | 426 | environment: yield* serverEnvironment.getDescriptor, |
| 428 | }, | 427 | }, |
| 429 | }), | 428 | }), |
apps/server/src/serverRuntimeState.ts+14-9| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Effect, FileSystem, Option, Schema } from "effect"; | 1 | import * as DateTime from "effect/DateTime"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as FileSystem from "effect/FileSystem"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 2 | 6 | ||
| 3 | import { writeFileStringAtomically } from "./atomicWrite.ts"; | 7 | import { writeFileStringAtomically } from "./atomicWrite.ts"; |
| 4 | import { type ServerConfigShape } from "./config.ts"; | 8 | import { type ServerConfigShape } from "./config.ts"; |
| ... | @@ -30,14 +34,15 @@ const runtimeOriginForConfig = ( | ... | @@ -30,14 +34,15 @@ const runtimeOriginForConfig = ( |
| 30 | export const makePersistedServerRuntimeState = (input: { | 34 | export const makePersistedServerRuntimeState = (input: { |
| 31 | readonly config: Pick<ServerConfigShape, "host">; | 35 | readonly config: Pick<ServerConfigShape, "host">; |
| 32 | readonly port: number; | 36 | readonly port: number; |
| 33 | }): PersistedServerRuntimeState => ({ | 37 | }): Effect.Effect<PersistedServerRuntimeState> => |
| 34 | version: 1, | 38 | Effect.map(DateTime.now, (now) => ({ |
| 35 | pid: process.pid, | 39 | version: 1, |
| 36 | ...(input.config.host ? { host: input.config.host } : {}), | 40 | pid: process.pid, |
| 37 | port: input.port, | 41 | ...(input.config.host ? { host: input.config.host } : {}), |
| 38 | origin: runtimeOriginForConfig(input.config, input.port), | 42 | port: input.port, |
| 39 | startedAt: new Date().toISOString(), | 43 | origin: runtimeOriginForConfig(input.config, input.port), |
| 40 | }); | 44 | startedAt: DateTime.formatIso(now), |
| 45 | })); | ||
| 41 | 46 | ||
| 42 | export const persistServerRuntimeState = (input: { | 47 | export const persistServerRuntimeState = (input: { |
| 43 | readonly path: string; | 48 | readonly path: string; |
apps/server/src/serverSettings.test.ts+6-1| ... | @@ -8,7 +8,10 @@ import { | ... | @@ -8,7 +8,10 @@ import { |
| 8 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 9 | import { createModelSelection } from "@t3tools/shared/model"; | 9 | import { createModelSelection } from "@t3tools/shared/model"; |
| 10 | import { assert, it } from "@effect/vitest"; | 10 | import { assert, it } from "@effect/vitest"; |
| 11 | import { Effect, FileSystem, Layer, Schema } from "effect"; | 11 | import * as Effect from "effect/Effect"; |
| 12 | import * as FileSystem from "effect/FileSystem"; | ||
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as Schema from "effect/Schema"; | ||
| 12 | import { ServerConfig } from "./config.ts"; | 15 | import { ServerConfig } from "./config.ts"; |
| 13 | import { ServerSettingsLive, ServerSettingsService } from "./serverSettings.ts"; | 16 | import { ServerSettingsLive, ServerSettingsService } from "./serverSettings.ts"; |
| 14 | 17 | ||
| ... | @@ -439,6 +442,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { | ... | @@ -439,6 +442,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 439 | assert.equal(next.providers.codex.binaryPath, "/opt/homebrew/bin/codex"); | 442 | assert.equal(next.providers.codex.binaryPath, "/opt/homebrew/bin/codex"); |
| 440 | 443 | ||
| 441 | const raw = yield* fileSystem.readFileString(serverConfig.settingsPath); | 444 | const raw = yield* fileSystem.readFileString(serverConfig.settingsPath); |
| 445 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 442 | assert.deepEqual(JSON.parse(raw), { | 446 | assert.deepEqual(JSON.parse(raw), { |
| 443 | addProjectBaseDirectory: "~/Development", | 447 | addProjectBaseDirectory: "~/Development", |
| 444 | observability: { | 448 | observability: { |
| ... | @@ -490,6 +494,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { | ... | @@ -490,6 +494,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { |
| 490 | 494 | ||
| 491 | const raw = yield* fileSystem.readFileString(serverConfig.settingsPath); | 495 | const raw = yield* fileSystem.readFileString(serverConfig.settingsPath); |
| 492 | assert.notInclude(raw, "sk-or-secret"); | 496 | assert.notInclude(raw, "sk-or-secret"); |
| 497 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 493 | assert.deepEqual(JSON.parse(raw).providerInstances.codex_personal.environment, [ | 498 | assert.deepEqual(JSON.parse(raw).providerInstances.codex_personal.environment, [ |
| 494 | { | 499 | { |
| 495 | name: "OPENROUTER_API_KEY", | 500 | name: "OPENROUTER_API_KEY", |
apps/server/src/serverSettings.ts+17-19| ... | @@ -24,25 +24,23 @@ import { | ... | @@ -24,25 +24,23 @@ import { |
| 24 | ServerSettingsError, | 24 | ServerSettingsError, |
| 25 | type ServerSettingsPatch, | 25 | type ServerSettingsPatch, |
| 26 | } from "@t3tools/contracts"; | 26 | } from "@t3tools/contracts"; |
| 27 | import { | 27 | import * as Cache from "effect/Cache"; |
| 28 | Cache, | 28 | import * as Deferred from "effect/Deferred"; |
| 29 | Deferred, | 29 | import * as Duration from "effect/Duration"; |
| 30 | Duration, | 30 | import * as Effect from "effect/Effect"; |
| 31 | Effect, | 31 | import * as Exit from "effect/Exit"; |
| 32 | Exit, | 32 | import * as FileSystem from "effect/FileSystem"; |
| 33 | FileSystem, | 33 | import * as Layer from "effect/Layer"; |
| 34 | Layer, | 34 | import * as Path from "effect/Path"; |
| 35 | Path, | 35 | import * as Equal from "effect/Equal"; |
| 36 | Equal, | 36 | import * as PubSub from "effect/PubSub"; |
| 37 | PubSub, | 37 | import * as Ref from "effect/Ref"; |
| 38 | Ref, | 38 | import * as Schema from "effect/Schema"; |
| 39 | Schema, | 39 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 40 | SchemaIssue, | 40 | import * as Scope from "effect/Scope"; |
| 41 | Scope, | 41 | import * as Context from "effect/Context"; |
| 42 | Context, | 42 | import * as Stream from "effect/Stream"; |
| 43 | Stream, | 43 | import * as Cause from "effect/Cause"; |
| 44 | Cause, | ||
| 45 | } from "effect"; | ||
| 46 | import * as Semaphore from "effect/Semaphore"; | 44 | import * as Semaphore from "effect/Semaphore"; |
| 47 | import { writeFileStringAtomically } from "./atomicWrite.ts"; | 45 | import { writeFileStringAtomically } from "./atomicWrite.ts"; |
| 48 | import { ServerConfig } from "./config.ts"; | 46 | import { ServerConfig } from "./config.ts"; |
apps/server/src/sourceControl/AzureDevOpsCli.test.ts+9-3| ... | @@ -1,8 +1,10 @@ | ... | @@ -1,8 +1,10 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it, afterEach, describe, expect, vi } from "@effect/vitest"; | 2 | import { assert, it, afterEach, describe, expect, vi } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Layer, Option } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 4 | import { ChildProcessSpawner } from "effect/unstable/process"; | 7 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 5 | import type { VcsError } from "@t3tools/contracts"; | ||
| 6 | 8 | ||
| 7 | import * as VcsProcess from "../vcs/VcsProcess.ts"; | 9 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 8 | import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; | 10 | import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; |
| ... | @@ -35,6 +37,7 @@ describe("AzureDevOpsCli.layer", () => { | ... | @@ -35,6 +37,7 @@ describe("AzureDevOpsCli.layer", () => { |
| 35 | mockRun.mockReturnValueOnce( | 37 | mockRun.mockReturnValueOnce( |
| 36 | Effect.succeed( | 38 | Effect.succeed( |
| 37 | processOutput( | 39 | processOutput( |
| 40 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 38 | JSON.stringify({ | 41 | JSON.stringify({ |
| 39 | pullRequestId: 42, | 42 | pullRequestId: 42, |
| 40 | title: "Add Azure provider", | 43 | title: "Add Azure provider", |
| ... | @@ -91,6 +94,7 @@ describe("AzureDevOpsCli.layer", () => { | ... | @@ -91,6 +94,7 @@ describe("AzureDevOpsCli.layer", () => { |
| 91 | mockRun.mockReturnValueOnce( | 94 | mockRun.mockReturnValueOnce( |
| 92 | Effect.succeed( | 95 | Effect.succeed( |
| 93 | processOutput( | 96 | processOutput( |
| 97 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 94 | JSON.stringify([ | 98 | JSON.stringify([ |
| 95 | { | 99 | { |
| 96 | pullRequestId: 7, | 100 | pullRequestId: 7, |
| ... | @@ -149,6 +153,7 @@ describe("AzureDevOpsCli.layer", () => { | ... | @@ -149,6 +153,7 @@ describe("AzureDevOpsCli.layer", () => { |
| 149 | mockRun.mockReturnValueOnce( | 153 | mockRun.mockReturnValueOnce( |
| 150 | Effect.succeed( | 154 | Effect.succeed( |
| 151 | processOutput( | 155 | processOutput( |
| 156 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 152 | JSON.stringify({ | 157 | JSON.stringify({ |
| 153 | name: "repo", | 158 | name: "repo", |
| 154 | webUrl: "https://dev.azure.com/acme/project/_git/repo", | 159 | webUrl: "https://dev.azure.com/acme/project/_git/repo", |
| ... | @@ -181,6 +186,7 @@ describe("AzureDevOpsCli.layer", () => { | ... | @@ -181,6 +186,7 @@ describe("AzureDevOpsCli.layer", () => { |
| 181 | mockRun.mockReturnValueOnce( | 186 | mockRun.mockReturnValueOnce( |
| 182 | Effect.succeed( | 187 | Effect.succeed( |
| 183 | processOutput( | 188 | processOutput( |
| 189 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 184 | JSON.stringify({ | 190 | JSON.stringify({ |
| 185 | name: "repo", | 191 | name: "repo", |
| 186 | webUrl: "https://dev.azure.com/acme/project/_git/repo", | 192 | webUrl: "https://dev.azure.com/acme/project/_git/repo", |
| ... | @@ -231,7 +237,7 @@ describe("AzureDevOpsCli.layer", () => { | ... | @@ -231,7 +237,7 @@ describe("AzureDevOpsCli.layer", () => { |
| 231 | it.effect("creates pull requests using the body file as the Azure description", () => | 237 | it.effect("creates pull requests using the body file as the Azure description", () => |
| 232 | Effect.gen(function* () { | 238 | Effect.gen(function* () { |
| 233 | const fileSystem = yield* FileSystem.FileSystem; | 239 | const fileSystem = yield* FileSystem.FileSystem; |
| 234 | const bodyFile = `/tmp/t3code-azure-devops-cli-${Date.now()}.md`; | 240 | const bodyFile = `/tmp/t3code-azure-devops-cli-.md`; |
| 235 | yield* fileSystem.writeFileString(bodyFile, "Generated body"); | 241 | yield* fileSystem.writeFileString(bodyFile, "Generated body"); |
| 236 | mockRun.mockReturnValueOnce(Effect.succeed(processOutput("{}"))); | 242 | mockRun.mockReturnValueOnce(Effect.succeed(processOutput("{}"))); |
| 237 | 243 |
apps/server/src/sourceControl/AzureDevOpsCli.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Context, Effect, Layer, Result, Schema, SchemaIssue } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Result from "effect/Result"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 6 | import * as SchemaIssue from "effect/SchemaIssue"; | ||
| 2 | import { | 7 | import { |
| 3 | TrimmedNonEmptyString, | 8 | TrimmedNonEmptyString, |
| 4 | type SourceControlRepositoryVisibility, | 9 | type SourceControlRepositoryVisibility, |
apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.test.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer, Option } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 3 | 5 | ||
| 4 | import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; | 6 | import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; |
| 5 | import * as AzureDevOpsSourceControlProvider from "./AzureDevOpsSourceControlProvider.ts"; | 7 | import * as AzureDevOpsSourceControlProvider from "./AzureDevOpsSourceControlProvider.ts"; |
apps/server/src/sourceControl/AzureDevOpsSourceControlProvider.ts+7-6| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Effect, Layer } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 2 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; | 3 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; |
| 3 | 4 | ||
| 4 | import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; | 5 | import * as AzureDevOpsCli from "./AzureDevOpsCli.ts"; |
| ... | @@ -30,7 +31,7 @@ function parseAzureAuth(input: SourceControlProviderDiscovery.SourceControlAuthP | ... | @@ -30,7 +31,7 @@ function parseAzureAuth(input: SourceControlProviderDiscovery.SourceControlAuthP |
| 30 | }); | 31 | }); |
| 31 | } | 32 | } |
| 32 | 33 | ||
| 33 | if (account && account.length > 0) { | 34 | if (account !== undefined && account.length > 0) { |
| 34 | return SourceControlProviderDiscovery.providerAuth({ | 35 | return SourceControlProviderDiscovery.providerAuth({ |
| 35 | status: "authenticated", | 36 | status: "authenticated", |
| 36 | account, | 37 | account, |
| ... | @@ -90,7 +91,7 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* | ... | @@ -90,7 +91,7 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* |
| 90 | .listPullRequests({ | 91 | .listPullRequests({ |
| 91 | cwd: input.cwd, | 92 | cwd: input.cwd, |
| 92 | headSelector: input.headSelector, | 93 | headSelector: input.headSelector, |
| 93 | ...(source ? { source } : {}), | 94 | ...(source !== undefined ? { source } : {}), |
| 94 | state: input.state, | 95 | state: input.state, |
| 95 | ...(input.limit !== undefined ? { limit: input.limit } : {}), | 96 | ...(input.limit !== undefined ? { limit: input.limit } : {}), |
| 96 | }) | 97 | }) |
| ... | @@ -111,8 +112,8 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* | ... | @@ -111,8 +112,8 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* |
| 111 | cwd: input.cwd, | 112 | cwd: input.cwd, |
| 112 | baseBranch: input.baseRefName, | 113 | baseBranch: input.baseRefName, |
| 113 | headSelector: input.headSelector, | 114 | headSelector: input.headSelector, |
| 114 | ...(source ? { source } : {}), | 115 | ...(source !== undefined ? { source } : {}), |
| 115 | ...(input.target ? { target: input.target } : {}), | 116 | ...(input.target !== undefined ? { target: input.target } : {}), |
| 116 | title: input.title, | 117 | title: input.title, |
| 117 | bodyFile: input.bodyFile, | 118 | bodyFile: input.bodyFile, |
| 118 | }) | 119 | }) |
| ... | @@ -135,7 +136,7 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* | ... | @@ -135,7 +136,7 @@ export const make = Effect.fn("makeAzureDevOpsSourceControlProvider")(function* |
| 135 | .checkoutPullRequest({ | 136 | .checkoutPullRequest({ |
| 136 | cwd: input.cwd, | 137 | cwd: input.cwd, |
| 137 | reference: input.reference, | 138 | reference: input.reference, |
| 138 | ...(input.context ? { remoteName: input.context.remoteName } : {}), | 139 | ...(input.context !== undefined ? { remoteName: input.context.remoteName } : {}), |
| 139 | }) | 140 | }) |
| 140 | .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))), | 141 | .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))), |
| 141 | }); | 142 | }); |
apps/server/src/sourceControl/BitbucketApi.test.ts+8-1| ... | @@ -1,6 +1,11 @@ | ... | @@ -1,6 +1,11 @@ |
| 1 | import { assert, it, vi } from "@effect/vitest"; | 1 | import { assert, it, vi } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { ConfigProvider, DateTime, Effect, FileSystem, Layer, Option } from "effect"; | 3 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 4 | import * as DateTime from "effect/DateTime"; | ||
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Layer from "effect/Layer"; | ||
| 8 | import * as Option from "effect/Option"; | ||
| 4 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; | 9 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; |
| 5 | 10 | ||
| 6 | import * as BitbucketApi from "./BitbucketApi.ts"; | 11 | import * as BitbucketApi from "./BitbucketApi.ts"; |
| ... | @@ -423,6 +428,7 @@ it.effect("creates repositories through the Bitbucket REST API", () => { | ... | @@ -423,6 +428,7 @@ it.effect("creates repositories through the Bitbucket REST API", () => { |
| 423 | assert.ok(request); | 428 | assert.ok(request); |
| 424 | const rawBody = (request.body as { readonly body?: Uint8Array }).body; | 429 | const rawBody = (request.body as { readonly body?: Uint8Array }).body; |
| 425 | assert.ok(rawBody); | 430 | assert.ok(rawBody); |
| 431 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 426 | assert.deepStrictEqual(JSON.parse(new TextDecoder().decode(rawBody)), { | 432 | assert.deepStrictEqual(JSON.parse(new TextDecoder().decode(rawBody)), { |
| 427 | scm: "git", | 433 | scm: "git", |
| 428 | is_private: true, | 434 | is_private: true, |
| ... | @@ -458,6 +464,7 @@ it.effect("creates pull requests using the official REST payload shape", () => { | ... | @@ -458,6 +464,7 @@ it.effect("creates pull requests using the official REST payload shape", () => { |
| 458 | assert.ok(request); | 464 | assert.ok(request); |
| 459 | const rawBody = (request.body as { readonly body?: Uint8Array }).body; | 465 | const rawBody = (request.body as { readonly body?: Uint8Array }).body; |
| 460 | assert.ok(rawBody); | 466 | assert.ok(rawBody); |
| 467 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 461 | assert.deepStrictEqual(JSON.parse(new TextDecoder().decode(rawBody)), { | 468 | assert.deepStrictEqual(JSON.parse(new TextDecoder().decode(rawBody)), { |
| 462 | title: "Provider PR", | 469 | title: "Provider PR", |
| 463 | description: "PR body", | 470 | description: "PR body", |
apps/server/src/sourceControl/BitbucketApi.ts+7-1| ... | @@ -1,4 +1,10 @@ | ... | @@ -1,4 +1,10 @@ |
| 1 | import { Config, Context, Effect, FileSystem, Layer, Option, Schema } from "effect"; | 1 | import * as Config from "effect/Config"; |
| 2 | import * as Context from "effect/Context"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 2 | import { | 8 | import { |
| 3 | TrimmedNonEmptyString, | 9 | TrimmedNonEmptyString, |
| 4 | type SourceControlProviderAuth, | 10 | type SourceControlProviderAuth, |
apps/server/src/sourceControl/BitbucketSourceControlProvider.test.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer, Option } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 3 | 5 | ||
| 4 | import * as BitbucketApi from "./BitbucketApi.ts"; | 6 | import * as BitbucketApi from "./BitbucketApi.ts"; |
| 5 | import * as BitbucketSourceControlProvider from "./BitbucketSourceControlProvider.ts"; | 7 | import * as BitbucketSourceControlProvider from "./BitbucketSourceControlProvider.ts"; |
apps/server/src/sourceControl/BitbucketSourceControlProvider.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { Effect, Layer, Option } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as Option from "effect/Option"; | ||
| 2 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; | 4 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; |
| 3 | 5 | ||
| 4 | import * as BitbucketApi from "./BitbucketApi.ts"; | 6 | import * as BitbucketApi from "./BitbucketApi.ts"; |
apps/server/src/sourceControl/GitHubCli.test.ts+7-2| ... | @@ -1,7 +1,8 @@ | ... | @@ -1,7 +1,8 @@ |
| 1 | import { assert, it, afterEach, describe, expect, vi } from "@effect/vitest"; | 1 | import { assert, it, afterEach, describe, expect, vi } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import { ChildProcessSpawner } from "effect/unstable/process"; | 4 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 4 | import { VcsProcessExitError, type VcsError } from "@t3tools/contracts"; | 5 | import { VcsProcessExitError } from "@t3tools/contracts"; |
| 5 | 6 | ||
| 6 | import * as VcsProcess from "../vcs/VcsProcess.ts"; | 7 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 7 | import * as GitHubCli from "./GitHubCli.ts"; | 8 | import * as GitHubCli from "./GitHubCli.ts"; |
| ... | @@ -34,6 +35,7 @@ describe("GitHubCli.layer", () => { | ... | @@ -34,6 +35,7 @@ describe("GitHubCli.layer", () => { |
| 34 | mockRun.mockReturnValueOnce( | 35 | mockRun.mockReturnValueOnce( |
| 35 | Effect.succeed( | 36 | Effect.succeed( |
| 36 | processOutput( | 37 | processOutput( |
| 38 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 37 | JSON.stringify({ | 39 | JSON.stringify({ |
| 38 | number: 42, | 40 | number: 42, |
| 39 | title: "Add PR thread creation", | 41 | title: "Add PR thread creation", |
| ... | @@ -92,6 +94,7 @@ describe("GitHubCli.layer", () => { | ... | @@ -92,6 +94,7 @@ describe("GitHubCli.layer", () => { |
| 92 | mockRun.mockReturnValueOnce( | 94 | mockRun.mockReturnValueOnce( |
| 93 | Effect.succeed( | 95 | Effect.succeed( |
| 94 | processOutput( | 96 | processOutput( |
| 97 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 95 | JSON.stringify({ | 98 | JSON.stringify({ |
| 96 | number: 42, | 99 | number: 42, |
| 97 | title: " Add PR thread creation \n", | 100 | title: " Add PR thread creation \n", |
| ... | @@ -137,6 +140,7 @@ describe("GitHubCli.layer", () => { | ... | @@ -137,6 +140,7 @@ describe("GitHubCli.layer", () => { |
| 137 | mockRun.mockReturnValueOnce( | 140 | mockRun.mockReturnValueOnce( |
| 138 | Effect.succeed( | 141 | Effect.succeed( |
| 139 | processOutput( | 142 | processOutput( |
| 143 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 140 | JSON.stringify([ | 144 | JSON.stringify([ |
| 141 | { | 145 | { |
| 142 | number: 0, | 146 | number: 0, |
| ... | @@ -187,6 +191,7 @@ describe("GitHubCli.layer", () => { | ... | @@ -187,6 +191,7 @@ describe("GitHubCli.layer", () => { |
| 187 | mockRun.mockReturnValueOnce( | 191 | mockRun.mockReturnValueOnce( |
| 188 | Effect.succeed( | 192 | Effect.succeed( |
| 189 | processOutput( | 193 | processOutput( |
| 194 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 190 | JSON.stringify({ | 195 | JSON.stringify({ |
| 191 | nameWithOwner: "octocat/codething-mvp", | 196 | nameWithOwner: "octocat/codething-mvp", |
| 192 | url: "https://github.com/octocat/codething-mvp", | 197 | url: "https://github.com/octocat/codething-mvp", |
apps/server/src/sourceControl/GitHubCli.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Context, Effect, Layer, Result, Schema, SchemaIssue } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Result from "effect/Result"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 6 | import * as SchemaIssue from "effect/SchemaIssue"; | ||
| 2 | 7 | ||
| 3 | import { | 8 | import { |
| 4 | TrimmedNonEmptyString, | 9 | TrimmedNonEmptyString, |
apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts+4-1| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { DateTime, Effect, Layer, Option } from "effect"; | 2 | import * as DateTime from "effect/DateTime"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 3 | import { ChildProcessSpawner } from "effect/unstable/process"; | 6 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 4 | 7 | ||
| 5 | import * as VcsProcess from "../vcs/VcsProcess.ts"; | 8 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
apps/server/src/sourceControl/GitHubSourceControlProvider.ts+5-1| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Effect, Layer, Option, Result, Schema } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as Option from "effect/Option"; | ||
| 4 | import * as Result from "effect/Result"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 2 | import { | 6 | import { |
| 3 | SourceControlProviderError, | 7 | SourceControlProviderError, |
| 4 | type ChangeRequest, | 8 | type ChangeRequest, |
apps/server/src/sourceControl/GitLabCli.test.ts+15-2| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it, afterEach, expect, vi } from "@effect/vitest"; | 1 | import { assert, it, afterEach, expect, vi } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import { ChildProcessSpawner } from "effect/unstable/process"; | 4 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 4 | 5 | ||
| 5 | import { VcsProcessExitError } from "@t3tools/contracts"; | 6 | import { VcsProcessExitError } from "@t3tools/contracts"; |
| ... | @@ -38,6 +39,7 @@ layer("GitLabCli.layer", (it) => { | ... | @@ -38,6 +39,7 @@ layer("GitLabCli.layer", (it) => { |
| 38 | mockedRun.mockReturnValueOnce( | 39 | mockedRun.mockReturnValueOnce( |
| 39 | Effect.succeed( | 40 | Effect.succeed( |
| 40 | processOutput( | 41 | processOutput( |
| 42 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 41 | JSON.stringify({ | 43 | JSON.stringify({ |
| 42 | iid: 42, | 44 | iid: 42, |
| 43 | title: "Add MR thread creation", | 45 | title: "Add MR thread creation", |
| ... | @@ -89,6 +91,7 @@ layer("GitLabCli.layer", (it) => { | ... | @@ -89,6 +91,7 @@ layer("GitLabCli.layer", (it) => { |
| 89 | mockedRun.mockReturnValueOnce( | 91 | mockedRun.mockReturnValueOnce( |
| 90 | Effect.succeed( | 92 | Effect.succeed( |
| 91 | processOutput( | 93 | processOutput( |
| 94 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 92 | JSON.stringify([ | 95 | JSON.stringify([ |
| 93 | { | 96 | { |
| 94 | iid: 0, | 97 | iid: 0, |
| ... | @@ -154,6 +157,7 @@ layer("GitLabCli.layer", (it) => { | ... | @@ -154,6 +157,7 @@ layer("GitLabCli.layer", (it) => { |
| 154 | mockedRun.mockReturnValueOnce( | 157 | mockedRun.mockReturnValueOnce( |
| 155 | Effect.succeed( | 158 | Effect.succeed( |
| 156 | processOutput( | 159 | processOutput( |
| 160 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 157 | JSON.stringify({ | 161 | JSON.stringify({ |
| 158 | path_with_namespace: "octocat/t3code", | 162 | path_with_namespace: "octocat/t3code", |
| 159 | web_url: "https://gitlab.com/octocat/t3code", | 163 | web_url: "https://gitlab.com/octocat/t3code", |
| ... | @@ -219,10 +223,19 @@ layer("GitLabCli.layer", (it) => { | ... | @@ -219,10 +223,19 @@ layer("GitLabCli.layer", (it) => { |
| 219 | it.effect("creates repositories under an explicit namespace", () => | 223 | it.effect("creates repositories under an explicit namespace", () => |
| 220 | Effect.gen(function* () { | 224 | Effect.gen(function* () { |
| 221 | mockedRun | 225 | mockedRun |
| 222 | .mockReturnValueOnce(Effect.succeed(processOutput(JSON.stringify({ id: 1234 })))) | 226 | |
| 227 | .mockReturnValueOnce( | ||
| 228 | Effect.succeed( | ||
| 229 | processOutput( | ||
| 230 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 231 | JSON.stringify({ id: 1234 }), | ||
| 232 | ), | ||
| 233 | ), | ||
| 234 | ) | ||
| 223 | .mockReturnValueOnce( | 235 | .mockReturnValueOnce( |
| 224 | Effect.succeed( | 236 | Effect.succeed( |
| 225 | processOutput( | 237 | processOutput( |
| 238 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 226 | JSON.stringify({ | 239 | JSON.stringify({ |
| 227 | path_with_namespace: "octocat/t3code", | 240 | path_with_namespace: "octocat/t3code", |
| 228 | web_url: "https://gitlab.com/octocat/t3code", | 241 | web_url: "https://gitlab.com/octocat/t3code", |
apps/server/src/sourceControl/GitLabCli.ts+8-1| ... | @@ -1,4 +1,11 @@ | ... | @@ -1,4 +1,11 @@ |
| 1 | import { Context, Effect, Layer, Option, Result, Schema, SchemaIssue, type DateTime } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 5 | import * as Result from "effect/Result"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 7 | import * as SchemaIssue from "effect/SchemaIssue"; | ||
| 8 | import type * as DateTime from "effect/DateTime"; | ||
| 2 | 9 | ||
| 3 | import { TrimmedNonEmptyString, type SourceControlRepositoryVisibility } from "@t3tools/contracts"; | 10 | import { TrimmedNonEmptyString, type SourceControlRepositoryVisibility } from "@t3tools/contracts"; |
| 4 | 11 |
apps/server/src/sourceControl/GitLabSourceControlProvider.test.ts+3-1| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { Effect, Layer, Option } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 3 | 5 | ||
| 4 | import * as GitLabCli from "./GitLabCli.ts"; | 6 | import * as GitLabCli from "./GitLabCli.ts"; |
| 5 | import * as GitLabSourceControlProvider from "./GitLabSourceControlProvider.ts"; | 7 | import * as GitLabSourceControlProvider from "./GitLabSourceControlProvider.ts"; |
apps/server/src/sourceControl/GitLabSourceControlProvider.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { Effect, Layer, Option } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as Option from "effect/Option"; | ||
| 2 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; | 4 | import { SourceControlProviderError, type ChangeRequest } from "@t3tools/contracts"; |
| 3 | 5 | ||
| 4 | import * as GitLabCli from "./GitLabCli.ts"; | 6 | import * as GitLabCli from "./GitLabCli.ts"; |
apps/server/src/sourceControl/SourceControlDiscovery.test.ts+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { Effect, Layer, Option } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 4 | import { ChildProcessSpawner } from "effect/unstable/process"; | 6 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 5 | import { VcsProcessSpawnError } from "@t3tools/contracts"; | 7 | import { VcsProcessSpawnError } from "@t3tools/contracts"; |
| 6 | 8 |
apps/server/src/sourceControl/SourceControlDiscovery.ts+4-1| ... | @@ -3,7 +3,10 @@ import { | ... | @@ -3,7 +3,10 @@ import { |
| 3 | type VcsDiscoveryItem, | 3 | type VcsDiscoveryItem, |
| 4 | type VcsDriverKind, | 4 | type VcsDriverKind, |
| 5 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { Context, Effect, Layer, Option } from "effect"; | 6 | import * as Context from "effect/Context"; |
| 7 | import * as Effect from "effect/Effect"; | ||
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 7 | 10 | ||
| 8 | import { ServerConfig } from "../config.ts"; | 11 | import { ServerConfig } from "../config.ts"; |
| 9 | import * as VcsProcess from "../vcs/VcsProcess.ts"; | 12 | import * as VcsProcess from "../vcs/VcsProcess.ts"; |
apps/server/src/sourceControl/SourceControlProvider.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Context, Effect } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 2 | import type { | 3 | import type { |
| 3 | ChangeRequest, | 4 | ChangeRequest, |
| 4 | ChangeRequestState, | 5 | ChangeRequestState, |
apps/server/src/sourceControl/SourceControlProviderDiscovery.ts+2-1| ... | @@ -3,7 +3,8 @@ import type { | ... | @@ -3,7 +3,8 @@ import type { |
| 3 | SourceControlProviderDiscoveryItem, | 3 | SourceControlProviderDiscoveryItem, |
| 4 | SourceControlProviderKind, | 4 | SourceControlProviderKind, |
| 5 | } from "@t3tools/contracts"; | 5 | } from "@t3tools/contracts"; |
| 6 | import { Effect, Option } from "effect"; | 6 | import * as Effect from "effect/Effect"; |
| 7 | import * as Option from "effect/Option"; | ||
| 7 | 8 | ||
| 8 | import type * as VcsProcess from "../vcs/VcsProcess.ts"; | 9 | import type * as VcsProcess from "../vcs/VcsProcess.ts"; |
| 9 | 10 |
apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { DateTime, Effect, Layer, Option } from "effect"; | 3 | import * as DateTime from "effect/DateTime"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 4 | 7 | ||
| 5 | import { ServerConfig } from "../config.ts"; | 8 | import { ServerConfig } from "../config.ts"; |
| 6 | import type * as VcsDriver from "../vcs/VcsDriver.ts"; | 9 | import type * as VcsDriver from "../vcs/VcsDriver.ts"; |
apps/server/src/sourceControl/SourceControlProviderRegistry.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Cache, Context, Duration, Effect, Exit, Layer } from "effect"; | 1 | import * as Cache from "effect/Cache"; |
| 2 | import * as Context from "effect/Context"; | ||
| 3 | import * as Duration from "effect/Duration"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 2 | import { | 7 | import { |
| 3 | SourceControlProviderError, | 8 | SourceControlProviderError, |
| 4 | type SourceControlProviderDiscoveryItem, | 9 | type SourceControlProviderDiscoveryItem, |
apps/server/src/sourceControl/SourceControlRepositoryService.test.ts+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Layer } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 4 | import { ChildProcessSpawner } from "effect/unstable/process"; | 6 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 5 | 7 | ||
| 6 | import { GitCommandError, type SourceControlProviderError } from "@t3tools/contracts"; | 8 | import { GitCommandError, type SourceControlProviderError } from "@t3tools/contracts"; |
apps/server/src/sourceControl/SourceControlRepositoryService.ts+6-1| ... | @@ -1,5 +1,10 @@ | ... | @@ -1,5 +1,10 @@ |
| 1 | import * as NodeOS from "node:os"; | 1 | import * as NodeOS from "node:os"; |
| 2 | import { Context, Effect, FileSystem, Layer, Path, Schema } from "effect"; | 2 | import * as Context from "effect/Context"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 7 | import * as Schema from "effect/Schema"; | ||
| 3 | 8 | ||
| 4 | import { | 9 | import { |
| 5 | SourceControlRepositoryError, | 10 | SourceControlRepositoryError, |
apps/server/src/sourceControl/azureDevOpsPullRequests.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Cause, DateTime, Exit, Option, Result, Schema } from "effect"; | 1 | import * as Cause from "effect/Cause"; |
| 2 | import * as DateTime from "effect/DateTime"; | ||
| 3 | import * as Exit from "effect/Exit"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 5 | import * as Result from "effect/Result"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 2 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; | 7 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; |
| 3 | import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; | 8 | import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; |
| 4 | 9 |
apps/server/src/sourceControl/bitbucketPullRequests.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { DateTime, Option, Schema } from "effect"; | 1 | import * as DateTime from "effect/DateTime"; |
| 2 | import * as Option from "effect/Option"; | ||
| 3 | import * as Schema from "effect/Schema"; | ||
| 2 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; | 4 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; |
| 3 | 5 | ||
| 4 | export interface NormalizedBitbucketPullRequestRecord { | 6 | export interface NormalizedBitbucketPullRequestRecord { |
apps/server/src/sourceControl/gitHubPullRequests.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Cause, DateTime, Exit, Option, Result, Schema } from "effect"; | 1 | import * as Cause from "effect/Cause"; |
| 2 | import * as DateTime from "effect/DateTime"; | ||
| 3 | import * as Exit from "effect/Exit"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 5 | import * as Result from "effect/Result"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 2 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; | 7 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; |
| 3 | import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; | 8 | import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; |
| 4 | 9 |
apps/server/src/sourceControl/gitLabMergeRequests.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Cause, DateTime, Exit, Option, Result, Schema } from "effect"; | 1 | import * as Cause from "effect/Cause"; |
| 2 | import * as DateTime from "effect/DateTime"; | ||
| 3 | import * as Exit from "effect/Exit"; | ||
| 4 | import * as Option from "effect/Option"; | ||
| 5 | import * as Result from "effect/Result"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 2 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; | 7 | import { PositiveInt, TrimmedNonEmptyString } from "@t3tools/contracts"; |
| 3 | import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; | 8 | import { decodeJsonResult, formatSchemaError } from "@t3tools/shared/schemaJson"; |
| 4 | 9 |
apps/server/src/startupAccess.ts+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | import { networkInterfaces } from "node:os"; | 1 | import { networkInterfaces } from "node:os"; |
| 2 | 2 | ||
| 3 | import { QrCode } from "@t3tools/shared/qrCode"; | 3 | import { QrCode } from "@t3tools/shared/qrCode"; |
| 4 | import { Effect } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import { HttpServer } from "effect/unstable/http"; | 5 | import { HttpServer } from "effect/unstable/http"; |
| 6 | 6 | ||
| 7 | import { ServerConfig } from "./config.ts"; | 7 | import { ServerConfig } from "./config.ts"; |
apps/server/src/stream/collectUint8StreamText.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | 1 | import { assert, describe, it } from "@effect/vitest"; |
| 2 | import { Effect, Stream } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Stream from "effect/Stream"; | ||
| 3 | 4 | ||
| 4 | import { collectUint8StreamText } from "./collectUint8StreamText.ts"; | 5 | import { collectUint8StreamText } from "./collectUint8StreamText.ts"; |
| 5 | 6 |
apps/server/src/stream/collectUint8StreamText.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Effect, Stream } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Stream from "effect/Stream"; | ||
| 2 | 3 | ||
| 3 | export interface CollectedUint8StreamText { | 4 | export interface CollectedUint8StreamText { |
| 4 | readonly text: string; | 5 | readonly text: string; |
apps/server/src/telemetry/Identify.ts+5-1| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Effect, FileSystem, Path, Random, Schema } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import * as Path from "effect/Path"; | ||
| 4 | import * as Random from "effect/Random"; | ||
| 5 | import * as Schema from "effect/Schema"; | ||
| 2 | import * as Crypto from "node:crypto"; | 6 | import * as Crypto from "node:crypto"; |
| 3 | import { homedir } from "node:os"; | 7 | import { homedir } from "node:os"; |
| 4 | import { ServerConfig } from "../config.ts"; | 8 | import { ServerConfig } from "../config.ts"; |
apps/server/src/telemetry/Layers/AnalyticsService.test.ts+3-1| ... | @@ -1,7 +1,9 @@ | ... | @@ -1,7 +1,9 @@ |
| 1 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; | 1 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { assert, it } from "@effect/vitest"; | 3 | import { assert, it } from "@effect/vitest"; |
| 4 | import { ConfigProvider, Effect, Layer } from "effect"; | 4 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 5 | import * as Effect from "effect/Effect"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as HttpServer from "effect/unstable/http/HttpServer"; | 7 | import * as HttpServer from "effect/unstable/http/HttpServer"; |
| 6 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; | 8 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 7 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; | 9 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; |
apps/server/src/telemetry/Layers/AnalyticsService.ts+5-1| ... | @@ -7,7 +7,11 @@ | ... | @@ -7,7 +7,11 @@ |
| 7 | * @module AnalyticsServiceLive | 7 | * @module AnalyticsServiceLive |
| 8 | */ | 8 | */ |
| 9 | 9 | ||
| 10 | import { Config, DateTime, Effect, Layer, Ref } from "effect"; | 10 | import * as Config from "effect/Config"; |
| 11 | import * as DateTime from "effect/DateTime"; | ||
| 12 | import * as Effect from "effect/Effect"; | ||
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as Ref from "effect/Ref"; | ||
| 11 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; | 15 | import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; |
| 12 | 16 | ||
| 13 | import { ServerConfig } from "../../config.ts"; | 17 | import { ServerConfig } from "../../config.ts"; |
apps/server/src/telemetry/Services/AnalyticsService.ts+3-1| ... | @@ -6,7 +6,9 @@ | ... | @@ -6,7 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module AnalyticsService | 7 | * @module AnalyticsService |
| 8 | */ | 8 | */ |
| 9 | import { Effect, Layer, Context } from "effect"; | 9 | import * as Effect from "effect/Effect"; |
| 10 | import * as Layer from "effect/Layer"; | ||
| 11 | import * as Context from "effect/Context"; | ||
| 10 | 12 | ||
| 11 | export interface AnalyticsServiceShape { | 13 | export interface AnalyticsServiceShape { |
| 12 | /** | 14 | /** |
apps/server/src/terminal/Layers/BunPTY.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Effect, Layer } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 2 | import { PtyAdapter } from "../Services/PTY.ts"; | 3 | import { PtyAdapter } from "../Services/PTY.ts"; |
| 3 | import type { PtyAdapterShape, PtyExitEvent, PtyProcess } from "../Services/PTY.ts"; | 4 | import type { PtyAdapterShape, PtyExitEvent, PtyProcess } from "../Services/PTY.ts"; |
| 4 | 5 |
apps/server/src/terminal/Layers/Manager.test.ts+31-38| ... | @@ -1,5 +1,3 @@ | ... | @@ -1,5 +1,3 @@ |
| 1 | import path from "node:path"; | ||
| 2 | |||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 5 | import { | 3 | import { |
| ... | @@ -8,20 +6,19 @@ import { | ... | @@ -8,20 +6,19 @@ import { |
| 8 | type TerminalOpenInput, | 6 | type TerminalOpenInput, |
| 9 | type TerminalRestartInput, | 7 | type TerminalRestartInput, |
| 10 | } from "@t3tools/contracts"; | 8 | } from "@t3tools/contracts"; |
| 11 | import { | 9 | import * as Cause from "effect/Cause"; |
| 12 | Duration, | 10 | import * as Duration from "effect/Duration"; |
| 13 | Effect, | 11 | import * as Effect from "effect/Effect"; |
| 14 | Encoding, | 12 | import * as Encoding from "effect/Encoding"; |
| 15 | Exit, | 13 | import * as Exit from "effect/Exit"; |
| 16 | Fiber, | 14 | import * as Fiber from "effect/Fiber"; |
| 17 | FileSystem, | 15 | import * as FileSystem from "effect/FileSystem"; |
| 18 | Option, | 16 | import * as Path from "effect/Path"; |
| 19 | PlatformError, | 17 | import * as PlatformError from "effect/PlatformError"; |
| 20 | Ref, | 18 | import * as Ref from "effect/Ref"; |
| 21 | Schedule, | 19 | import * as Schedule from "effect/Schedule"; |
| 22 | Scope, | 20 | import * as Scope from "effect/Scope"; |
| 23 | } from "effect"; | 21 | import * as TestClock from "effect/testing/TestClock"; |
| 24 | import { TestClock } from "effect/testing"; | ||
| 25 | import { expect } from "vitest"; | 22 | import { expect } from "vitest"; |
| 26 | 23 | ||
| 27 | import type { TerminalManagerShape } from "../Services/Manager.ts"; | 24 | import type { TerminalManagerShape } from "../Services/Manager.ts"; |
| ... | @@ -130,20 +127,11 @@ class FakePtyAdapter implements PtyAdapterShape { | ... | @@ -130,20 +127,11 @@ class FakePtyAdapter implements PtyAdapterShape { |
| 130 | const waitFor = <E, R>( | 127 | const waitFor = <E, R>( |
| 131 | predicate: Effect.Effect<boolean, E, R>, | 128 | predicate: Effect.Effect<boolean, E, R>, |
| 132 | timeout: Duration.Input = 800, | 129 | timeout: Duration.Input = 800, |
| 133 | ): Effect.Effect<void, Error | E, R> => | 130 | ): Effect.Effect<void, E | Cause.TimeoutError, R> => |
| 134 | predicate.pipe( | 131 | predicate.pipe( |
| 135 | Effect.filterOrFail( | 132 | Effect.filter((done) => done), |
| 136 | (done) => done, | ||
| 137 | () => new Error("Condition not met"), | ||
| 138 | ), | ||
| 139 | Effect.retry(Schedule.spaced("15 millis")), | 133 | Effect.retry(Schedule.spaced("15 millis")), |
| 140 | Effect.timeoutOption(timeout), | 134 | Effect.timeout(timeout), |
| 141 | Effect.flatMap((result) => | ||
| 142 | Option.match(result, { | ||
| 143 | onNone: () => Effect.fail(new Error("Timed out waiting for condition")), | ||
| 144 | onSome: () => Effect.void, | ||
| 145 | }), | ||
| 146 | ), | ||
| 147 | ); | 135 | ); |
| 148 | 136 | ||
| 149 | function openInput(overrides: Partial<TerminalOpenInput> = {}): TerminalOpenInput { | 137 | function openInput(overrides: Partial<TerminalOpenInput> = {}): TerminalOpenInput { |
| ... | @@ -181,7 +169,7 @@ function multiTerminalHistoryLogName(threadId: string, terminalId: string): stri | ... | @@ -181,7 +169,7 @@ function multiTerminalHistoryLogName(threadId: string, terminalId: string): stri |
| 181 | } | 169 | } |
| 182 | 170 | ||
| 183 | function historyLogPath(logsDir: string, threadId = "thread-1"): string { | 171 | function historyLogPath(logsDir: string, threadId = "thread-1"): string { |
| 184 | return path.join(logsDir, historyLogName(threadId)); | 172 | return joinPath(logsDir, historyLogName(threadId)); |
| 185 | } | 173 | } |
| 186 | 174 | ||
| 187 | function multiTerminalHistoryLogPath( | 175 | function multiTerminalHistoryLogPath( |
| ... | @@ -189,7 +177,11 @@ function multiTerminalHistoryLogPath( | ... | @@ -189,7 +177,11 @@ function multiTerminalHistoryLogPath( |
| 189 | threadId = "thread-1", | 177 | threadId = "thread-1", |
| 190 | terminalId = "default", | 178 | terminalId = "default", |
| 191 | ): string { | 179 | ): string { |
| 192 | return path.join(logsDir, multiTerminalHistoryLogName(threadId, terminalId)); | 180 | return joinPath(logsDir, multiTerminalHistoryLogName(threadId, terminalId)); |
| 181 | } | ||
| 182 | |||
| 183 | function joinPath(...segments: ReadonlyArray<string>): string { | ||
| 184 | return segments.join("/"); | ||
| 193 | } | 185 | } |
| 194 | 186 | ||
| 195 | interface CreateManagerOptions { | 187 | interface CreateManagerOptions { |
| ... | @@ -217,12 +209,13 @@ const createManager = ( | ... | @@ -217,12 +209,13 @@ const createManager = ( |
| 217 | ): Effect.Effect< | 209 | ): Effect.Effect< |
| 218 | ManagerFixture, | 210 | ManagerFixture, |
| 219 | PlatformError.PlatformError, | 211 | PlatformError.PlatformError, |
| 220 | FileSystem.FileSystem | Scope.Scope | 212 | FileSystem.FileSystem | Path.Path | Scope.Scope |
| 221 | > => | 213 | > => |
| 222 | Effect.flatMap(Effect.service(FileSystem.FileSystem), (fs) => | 214 | Effect.flatMap(Effect.service(FileSystem.FileSystem), (fs) => |
| 223 | Effect.gen(function* () { | 215 | Effect.gen(function* () { |
| 216 | const pathService = yield* Path.Path; | ||
| 224 | const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-terminal-" }); | 217 | const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-terminal-" }); |
| 225 | const logsDir = path.join(baseDir, "userdata", "logs", "terminals"); | 218 | const logsDir = pathService.join(baseDir, "userdata", "logs", "terminals"); |
| 226 | const ptyAdapter = options.ptyAdapter ?? new FakePtyAdapter(); | 219 | const ptyAdapter = options.ptyAdapter ?? new FakePtyAdapter(); |
| 227 | 220 | ||
| 228 | const manager = yield* makeTerminalManagerWithOptions({ | 221 | const manager = yield* makeTerminalManagerWithOptions({ |
| ... | @@ -302,8 +295,8 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( | ... | @@ -302,8 +295,8 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( |
| 302 | if (process.platform === "win32") return; | 295 | if (process.platform === "win32") return; |
| 303 | 296 | ||
| 304 | const { manager, baseDir } = yield* createManager(); | 297 | const { manager, baseDir } = yield* createManager(); |
| 305 | const blockedRoot = path.join(baseDir, "blocked-root"); | 298 | const blockedRoot = joinPath(baseDir, "blocked-root"); |
| 306 | const blockedCwd = path.join(blockedRoot, "cwd"); | 299 | const blockedCwd = joinPath(blockedRoot, "cwd"); |
| 307 | yield* makeDirectory(blockedCwd); | 300 | yield* makeDirectory(blockedCwd); |
| 308 | yield* chmod(blockedRoot, 0o000); | 301 | yield* chmod(blockedRoot, 0o000); |
| 309 | 302 | ||
| ... | @@ -441,8 +434,8 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( | ... | @@ -441,8 +434,8 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( |
| 441 | it.effect("propagates explicit worktree metadata through snapshots and lifecycle events", () => | 434 | it.effect("propagates explicit worktree metadata through snapshots and lifecycle events", () => |
| 442 | Effect.gen(function* () { | 435 | Effect.gen(function* () { |
| 443 | const { manager, getEvents, baseDir } = yield* createManager(); | 436 | const { manager, getEvents, baseDir } = yield* createManager(); |
| 444 | const firstWorktreePath = path.join(baseDir, "worktrees", "feature-a"); | 437 | const firstWorktreePath = joinPath(baseDir, "worktrees", "feature-a"); |
| 445 | const secondWorktreePath = path.join(baseDir, "worktrees", "feature-b"); | 438 | const secondWorktreePath = joinPath(baseDir, "worktrees", "feature-b"); |
| 446 | yield* makeDirectory(firstWorktreePath); | 439 | yield* makeDirectory(firstWorktreePath); |
| 447 | yield* makeDirectory(secondWorktreePath); | 440 | yield* makeDirectory(secondWorktreePath); |
| 448 | const startedSnapshot = yield* manager.open( | 441 | const startedSnapshot = yield* manager.open( |
| ... | @@ -478,7 +471,7 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( | ... | @@ -478,7 +471,7 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( |
| 478 | it.effect("preserves worktree metadata when reopening an exited session", () => | 471 | it.effect("preserves worktree metadata when reopening an exited session", () => |
| 479 | Effect.gen(function* () { | 472 | Effect.gen(function* () { |
| 480 | const { manager, ptyAdapter, getEvents, baseDir } = yield* createManager(); | 473 | const { manager, ptyAdapter, getEvents, baseDir } = yield* createManager(); |
| 481 | const worktreePath = path.join(baseDir, "worktrees", "feature-a"); | 474 | const worktreePath = joinPath(baseDir, "worktrees", "feature-a"); |
| 482 | yield* makeDirectory(worktreePath); | 475 | yield* makeDirectory(worktreePath); |
| 483 | 476 | ||
| 484 | yield* manager.open( | 477 | yield* manager.open( |
| ... | @@ -816,7 +809,7 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( | ... | @@ -816,7 +809,7 @@ it.layer(NodeServices.layer, { excludeTestServices: true })("TerminalManager", ( |
| 816 | it.effect("migrates legacy transcript filenames to terminal-scoped history path on open", () => | 809 | it.effect("migrates legacy transcript filenames to terminal-scoped history path on open", () => |
| 817 | Effect.gen(function* () { | 810 | Effect.gen(function* () { |
| 818 | const { manager, logsDir } = yield* createManager(); | 811 | const { manager, logsDir } = yield* createManager(); |
| 819 | const legacyPath = path.join(logsDir, "thread-1.log"); | 812 | const legacyPath = joinPath(logsDir, "thread-1.log"); |
| 820 | const nextPath = historyLogPath(logsDir); | 813 | const nextPath = historyLogPath(logsDir); |
| 821 | yield* writeFileString(legacyPath, "legacy-line\n"); | 814 | yield* writeFileString(legacyPath, "legacy-line\n"); |
| 822 | 815 |
apps/server/src/terminal/Layers/Manager.ts+61-40| ... | @@ -1,5 +1,3 @@ | ... | @@ -1,5 +1,3 @@ |
| 1 | import path from "node:path"; | ||
| 2 | |||
| 3 | import { | 1 | import { |
| 4 | DEFAULT_TERMINAL_ID, | 2 | DEFAULT_TERMINAL_ID, |
| 5 | type TerminalEvent, | 3 | type TerminalEvent, |
| ... | @@ -7,20 +5,20 @@ import { | ... | @@ -7,20 +5,20 @@ import { |
| 7 | type TerminalSessionStatus, | 5 | type TerminalSessionStatus, |
| 8 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 9 | import { makeKeyedCoalescingWorker } from "@t3tools/shared/KeyedCoalescingWorker"; | 7 | import { makeKeyedCoalescingWorker } from "@t3tools/shared/KeyedCoalescingWorker"; |
| 10 | import { | 8 | import * as Effect from "effect/Effect"; |
| 11 | Effect, | 9 | import * as Encoding from "effect/Encoding"; |
| 12 | Encoding, | 10 | import * as Equal from "effect/Equal"; |
| 13 | Equal, | 11 | import * as Exit from "effect/Exit"; |
| 14 | Exit, | 12 | import * as Fiber from "effect/Fiber"; |
| 15 | Fiber, | 13 | import * as FileSystem from "effect/FileSystem"; |
| 16 | FileSystem, | 14 | import * as DateTime from "effect/DateTime"; |
| 17 | Layer, | 15 | import * as Layer from "effect/Layer"; |
| 18 | Option, | 16 | import * as Option from "effect/Option"; |
| 19 | Schema, | 17 | import * as Path from "effect/Path"; |
| 20 | Scope, | 18 | import * as Schema from "effect/Schema"; |
| 21 | Semaphore, | 19 | import * as Scope from "effect/Scope"; |
| 22 | SynchronizedRef, | 20 | import * as Semaphore from "effect/Semaphore"; |
| 23 | } from "effect"; | 21 | import * as SynchronizedRef from "effect/SynchronizedRef"; |
| 24 | 22 | ||
| 25 | import { ServerConfig } from "../../config.ts"; | 23 | import { ServerConfig } from "../../config.ts"; |
| 26 | import { | 24 | import { |
| ... | @@ -213,15 +211,25 @@ function normalizeShellCommand( | ... | @@ -213,15 +211,25 @@ function normalizeShellCommand( |
| 213 | return firstToken.replace(/^['"]|['"]$/g, ""); | 211 | return firstToken.replace(/^['"]|['"]$/g, ""); |
| 214 | } | 212 | } |
| 215 | 213 | ||
| 214 | function basename(command: string, platform: NodeJS.Platform): string { | ||
| 215 | const normalized = platform === "win32" ? command.replaceAll("/", "\\") : command; | ||
| 216 | const separator = platform === "win32" ? "\\" : "/"; | ||
| 217 | return normalized.slice(normalized.lastIndexOf(separator) + 1); | ||
| 218 | } | ||
| 219 | |||
| 220 | function joinWindowsPath(...segments: ReadonlyArray<string>): string { | ||
| 221 | return segments | ||
| 222 | .filter((segment) => segment.length > 0) | ||
| 223 | .join("\\") | ||
| 224 | .replace(/\\+/g, "\\"); | ||
| 225 | } | ||
| 226 | |||
| 216 | function shellCandidateFromCommand( | 227 | function shellCandidateFromCommand( |
| 217 | command: string | null, | 228 | command: string | null, |
| 218 | platform: NodeJS.Platform = process.platform, | 229 | platform: NodeJS.Platform = process.platform, |
| 219 | ): ShellCandidate | null { | 230 | ): ShellCandidate | null { |
| 220 | if (!command || command.length === 0) return null; | 231 | if (!command || command.length === 0) return null; |
| 221 | const shellName = | 232 | const shellName = basename(command, platform).toLowerCase(); |
| 222 | platform === "win32" | ||
| 223 | ? path.win32.basename(command).toLowerCase() | ||
| 224 | : path.basename(command).toLowerCase(); | ||
| 225 | if (platform === "win32" && (shellName === "pwsh.exe" || shellName === "powershell.exe")) { | 233 | if (platform === "win32" && (shellName === "pwsh.exe" || shellName === "powershell.exe")) { |
| 226 | return { shell: command, args: ["-NoLogo"] }; | 234 | return { shell: command, args: ["-NoLogo"] }; |
| 227 | } | 235 | } |
| ... | @@ -236,7 +244,7 @@ function windowsSystemRoot(env: NodeJS.ProcessEnv): string { | ... | @@ -236,7 +244,7 @@ function windowsSystemRoot(env: NodeJS.ProcessEnv): string { |
| 236 | } | 244 | } |
| 237 | 245 | ||
| 238 | function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { | 246 | function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { |
| 239 | return path.win32.join( | 247 | return joinWindowsPath( |
| 240 | windowsSystemRoot(env), | 248 | windowsSystemRoot(env), |
| 241 | "System32", | 249 | "System32", |
| 242 | "WindowsPowerShell", | 250 | "WindowsPowerShell", |
| ... | @@ -246,7 +254,7 @@ function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { | ... | @@ -246,7 +254,7 @@ function windowsPowerShellPath(env: NodeJS.ProcessEnv): string { |
| 246 | } | 254 | } |
| 247 | 255 | ||
| 248 | function windowsCmdPath(env: NodeJS.ProcessEnv): string { | 256 | function windowsCmdPath(env: NodeJS.ProcessEnv): string { |
| 249 | return path.win32.join(windowsSystemRoot(env), "System32", "cmd.exe"); | 257 | return joinWindowsPath(windowsSystemRoot(env), "System32", "cmd.exe"); |
| 250 | } | 258 | } |
| 251 | 259 | ||
| 252 | function formatShellCandidate(candidate: ShellCandidate): string { | 260 | function formatShellCandidate(candidate: ShellCandidate): string { |
| ... | @@ -714,8 +722,10 @@ const makeTerminalManager = Effect.fn("makeTerminalManager")(function* () { | ... | @@ -714,8 +722,10 @@ const makeTerminalManager = Effect.fn("makeTerminalManager")(function* () { |
| 714 | export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWithOptions")( | 722 | export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWithOptions")( |
| 715 | function* (options: TerminalManagerOptions) { | 723 | function* (options: TerminalManagerOptions) { |
| 716 | const fileSystem = yield* FileSystem.FileSystem; | 724 | const fileSystem = yield* FileSystem.FileSystem; |
| 725 | const path = yield* Path.Path; | ||
| 717 | const context = yield* Effect.context<never>(); | 726 | const context = yield* Effect.context<never>(); |
| 718 | const runFork = Effect.runForkWith(context); | 727 | const runFork = Effect.runForkWith(context); |
| 728 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); | ||
| 719 | 729 | ||
| 720 | const logsDir = options.logsDir; | 730 | const logsDir = options.logsDir; |
| 721 | const historyLineLimit = options.historyLineLimit ?? DEFAULT_HISTORY_LINE_LIMIT; | 731 | const historyLineLimit = options.historyLineLimit ?? DEFAULT_HISTORY_LINE_LIMIT; |
| ... | @@ -1160,6 +1170,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1160,6 +1170,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1160 | expectedPid: number, | 1170 | expectedPid: number, |
| 1161 | ) { | 1171 | ) { |
| 1162 | while (true) { | 1172 | while (true) { |
| 1173 | const updatedAt = yield* nowIso; | ||
| 1163 | const action: DrainProcessEventAction = yield* Effect.sync(() => { | 1174 | const action: DrainProcessEventAction = yield* Effect.sync(() => { |
| 1164 | if (session.pid !== expectedPid || !session.process || session.status !== "running") { | 1175 | if (session.pid !== expectedPid || !session.process || session.status !== "running") { |
| 1165 | session.pendingProcessEvents = []; | 1176 | session.pendingProcessEvents = []; |
| ... | @@ -1194,7 +1205,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1194,7 +1205,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1194 | historyLineLimit, | 1205 | historyLineLimit, |
| 1195 | ); | 1206 | ); |
| 1196 | } | 1207 | } |
| 1197 | session.updatedAt = new Date().toISOString(); | 1208 | session.updatedAt = updatedAt; |
| 1198 | 1209 | ||
| 1199 | return { | 1210 | return { |
| 1200 | type: "output", | 1211 | type: "output", |
| ... | @@ -1221,7 +1232,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1221,7 +1232,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1221 | session.exitSignal = Number.isInteger(nextEvent.event.signal) | 1232 | session.exitSignal = Number.isInteger(nextEvent.event.signal) |
| 1222 | ? nextEvent.event.signal | 1233 | ? nextEvent.event.signal |
| 1223 | : null; | 1234 | : null; |
| 1224 | session.updatedAt = new Date().toISOString(); | 1235 | session.updatedAt = updatedAt; |
| 1225 | 1236 | ||
| 1226 | return { | 1237 | return { |
| 1227 | type: "exit", | 1238 | type: "exit", |
| ... | @@ -1242,22 +1253,24 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1242,22 +1253,24 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1242 | yield* queuePersist(action.threadId, action.terminalId, action.history); | 1253 | yield* queuePersist(action.threadId, action.terminalId, action.history); |
| 1243 | } | 1254 | } |
| 1244 | 1255 | ||
| 1256 | const createdAt = yield* nowIso; | ||
| 1245 | yield* publishEvent({ | 1257 | yield* publishEvent({ |
| 1246 | type: "output", | 1258 | type: "output", |
| 1247 | threadId: action.threadId, | 1259 | threadId: action.threadId, |
| 1248 | terminalId: action.terminalId, | 1260 | terminalId: action.terminalId, |
| 1249 | createdAt: new Date().toISOString(), | 1261 | createdAt, |
| 1250 | data: action.data, | 1262 | data: action.data, |
| 1251 | }); | 1263 | }); |
| 1252 | continue; | 1264 | continue; |
| 1253 | } | 1265 | } |
| 1254 | 1266 | ||
| 1255 | yield* clearKillFiber(action.process); | 1267 | yield* clearKillFiber(action.process); |
| 1268 | const createdAt = yield* nowIso; | ||
| 1256 | yield* publishEvent({ | 1269 | yield* publishEvent({ |
| 1257 | type: "exited", | 1270 | type: "exited", |
| 1258 | threadId: action.threadId, | 1271 | threadId: action.threadId, |
| 1259 | terminalId: action.terminalId, | 1272 | terminalId: action.terminalId, |
| 1260 | createdAt: new Date().toISOString(), | 1273 | createdAt, |
| 1261 | exitCode: action.exitCode, | 1274 | exitCode: action.exitCode, |
| 1262 | exitSignal: action.exitSignal, | 1275 | exitSignal: action.exitSignal, |
| 1263 | }); | 1276 | }); |
| ... | @@ -1272,6 +1285,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1272,6 +1285,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1272 | const process = session.process; | 1285 | const process = session.process; |
| 1273 | if (!process) return; | 1286 | if (!process) return; |
| 1274 | 1287 | ||
| 1288 | const updatedAt = yield* nowIso; | ||
| 1275 | yield* modifyManagerState((state) => { | 1289 | yield* modifyManagerState((state) => { |
| 1276 | cleanupProcessHandles(session); | 1290 | cleanupProcessHandles(session); |
| 1277 | session.process = null; | 1291 | session.process = null; |
| ... | @@ -1282,7 +1296,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1282,7 +1296,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1282 | session.pendingProcessEvents = []; | 1296 | session.pendingProcessEvents = []; |
| 1283 | session.pendingProcessEventIndex = 0; | 1297 | session.pendingProcessEventIndex = 0; |
| 1284 | session.processEventDrainRunning = false; | 1298 | session.processEventDrainRunning = false; |
| 1285 | session.updatedAt = new Date().toISOString(); | 1299 | session.updatedAt = updatedAt; |
| 1286 | return [undefined, state] as const; | 1300 | return [undefined, state] as const; |
| 1287 | }); | 1301 | }); |
| 1288 | 1302 | ||
| ... | @@ -1361,6 +1375,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1361,6 +1375,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1361 | "terminal.cwd": input.cwd, | 1375 | "terminal.cwd": input.cwd, |
| 1362 | }); | 1376 | }); |
| 1363 | 1377 | ||
| 1378 | const startingAt = yield* nowIso; | ||
| 1364 | yield* modifyManagerState((state) => { | 1379 | yield* modifyManagerState((state) => { |
| 1365 | session.status = "starting"; | 1380 | session.status = "starting"; |
| 1366 | session.cwd = input.cwd; | 1381 | session.cwd = input.cwd; |
| ... | @@ -1373,7 +1388,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1373,7 +1388,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1373 | session.pendingProcessEvents = []; | 1388 | session.pendingProcessEvents = []; |
| 1374 | session.pendingProcessEventIndex = 0; | 1389 | session.pendingProcessEventIndex = 0; |
| 1375 | session.processEventDrainRunning = false; | 1390 | session.processEventDrainRunning = false; |
| 1376 | session.updatedAt = new Date().toISOString(); | 1391 | session.updatedAt = startingAt; |
| 1377 | return [undefined, state] as const; | 1392 | return [undefined, state] as const; |
| 1378 | }); | 1393 | }); |
| 1379 | 1394 | ||
| ... | @@ -1404,11 +1419,12 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1404,11 +1419,12 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1404 | runFork(drainProcessEvents(session, processPid)); | 1419 | runFork(drainProcessEvents(session, processPid)); |
| 1405 | }); | 1420 | }); |
| 1406 | 1421 | ||
| 1422 | const startedAt = yield* nowIso; | ||
| 1407 | yield* modifyManagerState((state) => { | 1423 | yield* modifyManagerState((state) => { |
| 1408 | session.process = ptyProcess; | 1424 | session.process = ptyProcess; |
| 1409 | session.pid = processPid; | 1425 | session.pid = processPid; |
| 1410 | session.status = "running"; | 1426 | session.status = "running"; |
| 1411 | session.updatedAt = new Date().toISOString(); | 1427 | session.updatedAt = startedAt; |
| 1412 | session.unsubscribeData = unsubscribeData; | 1428 | session.unsubscribeData = unsubscribeData; |
| 1413 | session.unsubscribeExit = unsubscribeExit; | 1429 | session.unsubscribeExit = unsubscribeExit; |
| 1414 | return [undefined, state] as const; | 1430 | return [undefined, state] as const; |
| ... | @@ -1418,7 +1434,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1418,7 +1434,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1418 | type: eventType, | 1434 | type: eventType, |
| 1419 | threadId: session.threadId, | 1435 | threadId: session.threadId, |
| 1420 | terminalId: session.terminalId, | 1436 | terminalId: session.terminalId, |
| 1421 | createdAt: new Date().toISOString(), | 1437 | createdAt: startedAt, |
| 1422 | snapshot: snapshot(session), | 1438 | snapshot: snapshot(session), |
| 1423 | }); | 1439 | }); |
| 1424 | }), | 1440 | }), |
| ... | @@ -1436,6 +1452,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1436,6 +1452,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1436 | yield* startKillEscalation(ptyProcess, session.threadId, session.terminalId); | 1452 | yield* startKillEscalation(ptyProcess, session.threadId, session.terminalId); |
| 1437 | } | 1453 | } |
| 1438 | 1454 | ||
| 1455 | const failedAt = yield* nowIso; | ||
| 1439 | yield* modifyManagerState((state) => { | 1456 | yield* modifyManagerState((state) => { |
| 1440 | session.status = "error"; | 1457 | session.status = "error"; |
| 1441 | session.pid = null; | 1458 | session.pid = null; |
| ... | @@ -1446,7 +1463,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1446,7 +1463,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1446 | session.pendingProcessEvents = []; | 1463 | session.pendingProcessEvents = []; |
| 1447 | session.pendingProcessEventIndex = 0; | 1464 | session.pendingProcessEventIndex = 0; |
| 1448 | session.processEventDrainRunning = false; | 1465 | session.processEventDrainRunning = false; |
| 1449 | session.updatedAt = new Date().toISOString(); | 1466 | session.updatedAt = failedAt; |
| 1450 | return [undefined, state] as const; | 1467 | return [undefined, state] as const; |
| 1451 | }); | 1468 | }); |
| 1452 | 1469 | ||
| ... | @@ -1457,7 +1474,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1457,7 +1474,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1457 | type: "error", | 1474 | type: "error", |
| 1458 | threadId: session.threadId, | 1475 | threadId: session.threadId, |
| 1459 | terminalId: session.terminalId, | 1476 | terminalId: session.terminalId, |
| 1460 | createdAt: new Date().toISOString(), | 1477 | createdAt: failedAt, |
| 1461 | message, | 1478 | message, |
| 1462 | }); | 1479 | }); |
| 1463 | yield* Effect.logError("failed to start terminal", { | 1480 | yield* Effect.logError("failed to start terminal", { |
| ... | @@ -1529,6 +1546,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1529,6 +1546,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1529 | return; | 1546 | return; |
| 1530 | } | 1547 | } |
| 1531 | 1548 | ||
| 1549 | const updatedAt = yield* nowIso; | ||
| 1532 | const event = yield* modifyManagerState((state) => { | 1550 | const event = yield* modifyManagerState((state) => { |
| 1533 | const liveSession: Option.Option<TerminalSessionState> = Option.fromNullishOr( | 1551 | const liveSession: Option.Option<TerminalSessionState> = Option.fromNullishOr( |
| 1534 | state.sessions.get(toSessionKey(session.threadId, session.terminalId)), | 1552 | state.sessions.get(toSessionKey(session.threadId, session.terminalId)), |
| ... | @@ -1543,14 +1561,14 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1543,14 +1561,14 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1543 | } | 1561 | } |
| 1544 | 1562 | ||
| 1545 | liveSession.value.hasRunningSubprocess = hasRunningSubprocess.value; | 1563 | liveSession.value.hasRunningSubprocess = hasRunningSubprocess.value; |
| 1546 | liveSession.value.updatedAt = new Date().toISOString(); | 1564 | liveSession.value.updatedAt = updatedAt; |
| 1547 | 1565 | ||
| 1548 | return [ | 1566 | return [ |
| 1549 | Option.some({ | 1567 | Option.some({ |
| 1550 | type: "activity" as const, | 1568 | type: "activity" as const, |
| 1551 | threadId: liveSession.value.threadId, | 1569 | threadId: liveSession.value.threadId, |
| 1552 | terminalId: liveSession.value.terminalId, | 1570 | terminalId: liveSession.value.terminalId, |
| 1553 | createdAt: new Date().toISOString(), | 1571 | createdAt: updatedAt, |
| 1554 | hasRunningSubprocess: hasRunningSubprocess.value, | 1572 | hasRunningSubprocess: hasRunningSubprocess.value, |
| 1555 | }), | 1573 | }), |
| 1556 | state, | 1574 | state, |
| ... | @@ -1629,6 +1647,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1629,6 +1647,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1629 | const history = yield* readHistory(input.threadId, terminalId); | 1647 | const history = yield* readHistory(input.threadId, terminalId); |
| 1630 | const cols = input.cols ?? DEFAULT_OPEN_COLS; | 1648 | const cols = input.cols ?? DEFAULT_OPEN_COLS; |
| 1631 | const rows = input.rows ?? DEFAULT_OPEN_ROWS; | 1649 | const rows = input.rows ?? DEFAULT_OPEN_ROWS; |
| 1650 | const createdAt = yield* nowIso; | ||
| 1632 | const session: TerminalSessionState = { | 1651 | const session: TerminalSessionState = { |
| 1633 | threadId: input.threadId, | 1652 | threadId: input.threadId, |
| 1634 | terminalId, | 1653 | terminalId, |
| ... | @@ -1643,7 +1662,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1643,7 +1662,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1643 | processEventDrainRunning: false, | 1662 | processEventDrainRunning: false, |
| 1644 | exitCode: null, | 1663 | exitCode: null, |
| 1645 | exitSignal: null, | 1664 | exitSignal: null, |
| 1646 | updatedAt: new Date().toISOString(), | 1665 | updatedAt: createdAt, |
| 1647 | cols, | 1666 | cols, |
| 1648 | rows, | 1667 | rows, |
| 1649 | process: null, | 1668 | process: null, |
| ... | @@ -1734,7 +1753,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1734,7 +1753,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1734 | if (liveSession.cols !== targetCols || liveSession.rows !== targetRows) { | 1753 | if (liveSession.cols !== targetCols || liveSession.rows !== targetRows) { |
| 1735 | liveSession.cols = targetCols; | 1754 | liveSession.cols = targetCols; |
| 1736 | liveSession.rows = targetRows; | 1755 | liveSession.rows = targetRows; |
| 1737 | liveSession.updatedAt = new Date().toISOString(); | 1756 | liveSession.updatedAt = yield* nowIso; |
| 1738 | liveSession.process.resize(targetCols, targetRows); | 1757 | liveSession.process.resize(targetCols, targetRows); |
| 1739 | } | 1758 | } |
| 1740 | 1759 | ||
| ... | @@ -1768,7 +1787,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1768,7 +1787,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1768 | } | 1787 | } |
| 1769 | session.cols = input.cols; | 1788 | session.cols = input.cols; |
| 1770 | session.rows = input.rows; | 1789 | session.rows = input.rows; |
| 1771 | session.updatedAt = new Date().toISOString(); | 1790 | session.updatedAt = yield* nowIso; |
| 1772 | yield* Effect.sync(() => process.resize(input.cols, input.rows)); | 1791 | yield* Effect.sync(() => process.resize(input.cols, input.rows)); |
| 1773 | }); | 1792 | }); |
| 1774 | 1793 | ||
| ... | @@ -1783,13 +1802,14 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1783,13 +1802,14 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1783 | session.pendingProcessEvents = []; | 1802 | session.pendingProcessEvents = []; |
| 1784 | session.pendingProcessEventIndex = 0; | 1803 | session.pendingProcessEventIndex = 0; |
| 1785 | session.processEventDrainRunning = false; | 1804 | session.processEventDrainRunning = false; |
| 1786 | session.updatedAt = new Date().toISOString(); | 1805 | const clearedAt = yield* nowIso; |
| 1806 | session.updatedAt = clearedAt; | ||
| 1787 | yield* persistHistory(input.threadId, terminalId, session.history); | 1807 | yield* persistHistory(input.threadId, terminalId, session.history); |
| 1788 | yield* publishEvent({ | 1808 | yield* publishEvent({ |
| 1789 | type: "cleared", | 1809 | type: "cleared", |
| 1790 | threadId: input.threadId, | 1810 | threadId: input.threadId, |
| 1791 | terminalId, | 1811 | terminalId, |
| 1792 | createdAt: new Date().toISOString(), | 1812 | createdAt: clearedAt, |
| 1793 | }); | 1813 | }); |
| 1794 | }), | 1814 | }), |
| 1795 | ); | 1815 | ); |
| ... | @@ -1808,6 +1828,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1808,6 +1828,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1808 | if (Option.isNone(existingSession)) { | 1828 | if (Option.isNone(existingSession)) { |
| 1809 | const cols = input.cols ?? DEFAULT_OPEN_COLS; | 1829 | const cols = input.cols ?? DEFAULT_OPEN_COLS; |
| 1810 | const rows = input.rows ?? DEFAULT_OPEN_ROWS; | 1830 | const rows = input.rows ?? DEFAULT_OPEN_ROWS; |
| 1831 | const createdAt = yield* nowIso; | ||
| 1811 | session = { | 1832 | session = { |
| 1812 | threadId: input.threadId, | 1833 | threadId: input.threadId, |
| 1813 | terminalId, | 1834 | terminalId, |
| ... | @@ -1822,7 +1843,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith | ... | @@ -1822,7 +1843,7 @@ export const makeTerminalManagerWithOptions = Effect.fn("makeTerminalManagerWith |
| 1822 | processEventDrainRunning: false, | 1843 | processEventDrainRunning: false, |
| 1823 | exitCode: null, | 1844 | exitCode: null, |
| 1824 | exitSignal: null, | 1845 | exitSignal: null, |
| 1825 | updatedAt: new Date().toISOString(), | 1846 | updatedAt: createdAt, |
| 1826 | cols, | 1847 | cols, |
| 1827 | rows, | 1848 | rows, |
| 1828 | process: null, | 1849 | process: null, |
apps/server/src/terminal/Layers/NodePTY.test.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { FileSystem, Path, Effect } from "effect"; | 1 | import * as FileSystem from "effect/FileSystem"; |
| 2 | import * as Path from "effect/Path"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 2 | import { assert, it } from "@effect/vitest"; | 4 | import { assert, it } from "@effect/vitest"; |
| 3 | 5 | ||
| 4 | import { ensureNodePtySpawnHelperExecutable } from "./NodePTY.ts"; | 6 | import { ensureNodePtySpawnHelperExecutable } from "./NodePTY.ts"; |
apps/server/src/terminal/Layers/NodePTY.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import { createRequire } from "node:module"; | 1 | import { createRequire } from "node:module"; |
| 2 | 2 | ||
| 3 | import { Effect, FileSystem, Layer, Path } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 4 | import { PtyAdapter } from "../Services/PTY.ts"; | 7 | import { PtyAdapter } from "../Services/PTY.ts"; |
| 5 | import { | 8 | import { |
| 6 | PtySpawnError, | 9 | PtySpawnError, |
apps/server/src/terminal/Services/Manager.ts+2-1| ... | @@ -23,7 +23,8 @@ import { | ... | @@ -23,7 +23,8 @@ import { |
| 23 | TerminalWriteInput, | 23 | TerminalWriteInput, |
| 24 | } from "@t3tools/contracts"; | 24 | } from "@t3tools/contracts"; |
| 25 | import type { PtyProcess } from "./PTY.ts"; | 25 | import type { PtyProcess } from "./PTY.ts"; |
| 26 | import { Effect, Context } from "effect"; | 26 | import * as Effect from "effect/Effect"; |
| 27 | import * as Context from "effect/Context"; | ||
| 27 | 28 | ||
| 28 | export { | 29 | export { |
| 29 | TerminalCwdError, | 30 | TerminalCwdError, |
apps/server/src/terminal/Services/PTY.ts+3-1| ... | @@ -6,7 +6,9 @@ | ... | @@ -6,7 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module PtyAdapter | 7 | * @module PtyAdapter |
| 8 | */ | 8 | */ |
| 9 | import { Effect, Schema, Context } from "effect"; | 9 | import * as Effect from "effect/Effect"; |
| 10 | import * as Schema from "effect/Schema"; | ||
| 11 | import * as Context from "effect/Context"; | ||
| 10 | 12 | ||
| 11 | /** | 13 | /** |
| 12 | * PtyError - Error type for PTY adapter operations. | 14 | * PtyError - Error type for PTY adapter operations. |
apps/server/src/textGeneration/ClaudeTextGeneration.test.ts+6-1| ... | @@ -1,7 +1,11 @@ | ... | @@ -1,7 +1,11 @@ |
| 1 | import { ClaudeSettings, ProviderInstanceId } from "@t3tools/contracts"; | 1 | import { ClaudeSettings, ProviderInstanceId } from "@t3tools/contracts"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { it } from "@effect/vitest"; | 3 | import { it } from "@effect/vitest"; |
| 4 | import { Effect, FileSystem, Layer, Path, Schema } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 7 | import * as Path from "effect/Path"; | ||
| 8 | import * as Schema from "effect/Schema"; | ||
| 5 | import { createModelSelection } from "@t3tools/shared/model"; | 9 | import { createModelSelection } from "@t3tools/shared/model"; |
| 6 | import { expect } from "vitest"; | 10 | import { expect } from "vitest"; |
| 7 | 11 | ||
| ... | @@ -287,6 +291,7 @@ it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGeneration", (it) => { | ... | @@ -287,6 +291,7 @@ it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGeneration", (it) => { |
| 287 | const claudeHome = path.join(process.cwd(), ".claude-work-test"); | 291 | const claudeHome = path.join(process.cwd(), ".claude-work-test"); |
| 288 | return yield* withFakeClaudeEnv( | 292 | return yield* withFakeClaudeEnv( |
| 289 | { | 293 | { |
| 294 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 290 | output: JSON.stringify({ | 295 | output: JSON.stringify({ |
| 291 | structured_output: { | 296 | structured_output: { |
| 292 | title: "Use Claude home", | 297 | title: "Use Claude home", |
apps/server/src/textGeneration/ClaudeTextGeneration.ts+10-3| ... | @@ -7,7 +7,10 @@ | ... | @@ -7,7 +7,10 @@ |
| 7 | * | 7 | * |
| 8 | * @module ClaudeTextGeneration | 8 | * @module ClaudeTextGeneration |
| 9 | */ | 9 | */ |
| 10 | import { Effect, Option, Schema, Stream } from "effect"; | 10 | import * as Effect from "effect/Effect"; |
| 11 | import * as Option from "effect/Option"; | ||
| 12 | import * as Schema from "effect/Schema"; | ||
| 13 | import * as Stream from "effect/Stream"; | ||
| 11 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 14 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 12 | 15 | ||
| 13 | import { type ClaudeSettings, type ModelSelection } from "@t3tools/contracts"; | 16 | import { type ClaudeSettings, type ModelSelection } from "@t3tools/contracts"; |
| ... | @@ -93,7 +96,9 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu | ... | @@ -93,7 +96,9 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu |
| 93 | outputSchemaJson: S; | 96 | outputSchemaJson: S; |
| 94 | modelSelection: ModelSelection; | 97 | modelSelection: ModelSelection; |
| 95 | }): Effect.fn.Return<S["Type"], TextGenerationError, S["DecodingServices"]> { | 98 | }): Effect.fn.Return<S["Type"], TextGenerationError, S["DecodingServices"]> { |
| 96 | const jsonSchemaStr = JSON.stringify(toJsonSchemaObject(outputSchemaJson)); | 99 | const jsonSchemaStr = Schema.encodeUnknownSync(Schema.UnknownFromJsonString)( |
| 100 | toJsonSchemaObject(outputSchemaJson), | ||
| 101 | ); | ||
| 97 | const caps = getClaudeModelCapabilities(modelSelection.model); | 102 | const caps = getClaudeModelCapabilities(modelSelection.model); |
| 98 | const descriptors = getProviderOptionDescriptors({ | 103 | const descriptors = getProviderOptionDescriptors({ |
| 99 | caps, | 104 | caps, |
| ... | @@ -126,7 +131,9 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu | ... | @@ -126,7 +131,9 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu |
| 126 | "--model", | 131 | "--model", |
| 127 | resolveClaudeApiModelId(modelSelection), | 132 | resolveClaudeApiModelId(modelSelection), |
| 128 | ...(cliEffort ? ["--effort", cliEffort] : []), | 133 | ...(cliEffort ? ["--effort", cliEffort] : []), |
| 129 | ...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []), | 134 | ...(Object.keys(settings).length > 0 |
| 135 | ? ["--settings", Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(settings)] | ||
| 136 | : []), | ||
| 130 | "--dangerously-skip-permissions", | 137 | "--dangerously-skip-permissions", |
| 131 | ], | 138 | ], |
| 132 | { | 139 | { |
apps/server/src/textGeneration/CodexTextGeneration.test.ts+15-5| ... | @@ -1,6 +1,11 @@ | ... | @@ -1,6 +1,11 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { it } from "@effect/vitest"; | 2 | import { it } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Layer, Path, Result, Schema } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 7 | import * as Result from "effect/Result"; | ||
| 8 | import * as Schema from "effect/Schema"; | ||
| 4 | import { createModelSelection } from "@t3tools/shared/model"; | 9 | import { createModelSelection } from "@t3tools/shared/model"; |
| 5 | import { expect } from "vitest"; | 10 | import { expect } from "vitest"; |
| 6 | 11 | ||
| ... | @@ -113,6 +118,7 @@ function makeFakeCodexBinary( | ... | @@ -113,6 +118,7 @@ function makeFakeCodexBinary( |
| 113 | : []), | 118 | : []), |
| 114 | ...(input.stdinMustContain !== undefined | 119 | ...(input.stdinMustContain !== undefined |
| 115 | ? [ | 120 | ? [ |
| 121 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 116 | `if ! printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustContain)} >/dev/null; then`, | 122 | `if ! printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustContain)} >/dev/null; then`, |
| 117 | ' printf "%s\\n" "stdin missing expected content" >&2', | 123 | ' printf "%s\\n" "stdin missing expected content" >&2', |
| 118 | ` exit 3`, | 124 | ` exit 3`, |
| ... | @@ -121,6 +127,7 @@ function makeFakeCodexBinary( | ... | @@ -121,6 +127,7 @@ function makeFakeCodexBinary( |
| 121 | : []), | 127 | : []), |
| 122 | ...(input.stdinMustNotContain !== undefined | 128 | ...(input.stdinMustNotContain !== undefined |
| 123 | ? [ | 129 | ? [ |
| 130 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 124 | `if printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustNotContain)} >/dev/null; then`, | 131 | `if printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustNotContain)} >/dev/null; then`, |
| 125 | ' printf "%s\\n" "stdin contained forbidden content" >&2', | 132 | ' printf "%s\\n" "stdin contained forbidden content" >&2', |
| 126 | ` exit 4`, | 133 | ` exit 4`, |
| ... | @@ -128,7 +135,10 @@ function makeFakeCodexBinary( | ... | @@ -128,7 +135,10 @@ function makeFakeCodexBinary( |
| 128 | ] | 135 | ] |
| 129 | : []), | 136 | : []), |
| 130 | ...(input.stderr !== undefined | 137 | ...(input.stderr !== undefined |
| 131 | ? [`printf "%s\\n" ${JSON.stringify(input.stderr)} >&2`] | 138 | ? [ |
| 139 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 140 | `printf "%s\\n" ${JSON.stringify(input.stderr)} >&2`, | ||
| 141 | ] | ||
| 132 | : []), | 142 | : []), |
| 133 | 'if [ -n "$output_path" ]; then', | 143 | 'if [ -n "$output_path" ]; then', |
| 134 | " cat > \"$output_path\" <<'__T3CODE_FAKE_CODEX_OUTPUT__'", | 144 | " cat > \"$output_path\" <<'__T3CODE_FAKE_CODEX_OUTPUT__'", |
| ... | @@ -415,7 +425,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { | ... | @@ -415,7 +425,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { |
| 415 | const fs = yield* FileSystem.FileSystem; | 425 | const fs = yield* FileSystem.FileSystem; |
| 416 | const path = yield* Path.Path; | 426 | const path = yield* Path.Path; |
| 417 | const { attachmentsDir } = yield* ServerConfig; | 427 | const { attachmentsDir } = yield* ServerConfig; |
| 418 | const attachmentId = `thread-branch-image-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; | 428 | const attachmentId = "thread-branch-image-attachment"; |
| 419 | const attachmentPath = path.join(attachmentsDir, `${attachmentId}.png`); | 429 | const attachmentPath = path.join(attachmentsDir, `${attachmentId}.png`); |
| 420 | yield* fs.makeDirectory(attachmentsDir, { recursive: true }); | 430 | yield* fs.makeDirectory(attachmentsDir, { recursive: true }); |
| 421 | yield* fs.writeFile(attachmentPath, Buffer.from("hello")); | 431 | yield* fs.writeFile(attachmentPath, Buffer.from("hello")); |
| ... | @@ -453,7 +463,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { | ... | @@ -453,7 +463,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { |
| 453 | const fs = yield* FileSystem.FileSystem; | 463 | const fs = yield* FileSystem.FileSystem; |
| 454 | const path = yield* Path.Path; | 464 | const path = yield* Path.Path; |
| 455 | const { attachmentsDir } = yield* ServerConfig; | 465 | const { attachmentsDir } = yield* ServerConfig; |
| 456 | const attachmentId = `thread-1-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; | 466 | const attachmentId = "thread-1-attachment"; |
| 457 | const imagePath = path.join(attachmentsDir, `${attachmentId}.png`); | 467 | const imagePath = path.join(attachmentsDir, `${attachmentId}.png`); |
| 458 | yield* fs.makeDirectory(attachmentsDir, { recursive: true }); | 468 | yield* fs.makeDirectory(attachmentsDir, { recursive: true }); |
| 459 | yield* fs.writeFile(imagePath, Buffer.from("hello")); | 469 | yield* fs.writeFile(imagePath, Buffer.from("hello")); |
| ... | @@ -502,7 +512,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { | ... | @@ -502,7 +512,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { |
| 502 | const fs = yield* FileSystem.FileSystem; | 512 | const fs = yield* FileSystem.FileSystem; |
| 503 | const path = yield* Path.Path; | 513 | const path = yield* Path.Path; |
| 504 | const { attachmentsDir } = yield* ServerConfig; | 514 | const { attachmentsDir } = yield* ServerConfig; |
| 505 | const missingAttachmentId = `thread-missing-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; | 515 | const missingAttachmentId = "thread-missing-attachment"; |
| 506 | const missingPath = path.join(attachmentsDir, `${missingAttachmentId}.png`); | 516 | const missingPath = path.join(attachmentsDir, `${missingAttachmentId}.png`); |
| 507 | yield* fs.remove(missingPath).pipe(Effect.catch(() => Effect.void)); | 517 | yield* fs.remove(missingPath).pipe(Effect.catch(() => Effect.void)); |
| 508 | 518 |
apps/server/src/textGeneration/CodexTextGeneration.ts+9-2| ... | @@ -1,4 +1,11 @@ | ... | @@ -1,4 +1,11 @@ |
| 1 | import { Effect, FileSystem, Option, Path, Random, Schema, Scope, Stream } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import * as Option from "effect/Option"; | ||
| 4 | import * as Path from "effect/Path"; | ||
| 5 | import * as Random from "effect/Random"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 7 | import * as Scope from "effect/Scope"; | ||
| 8 | import * as Stream from "effect/Stream"; | ||
| 2 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 9 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 3 | 10 | ||
| 4 | import { type CodexSettings, type ModelSelection } from "@t3tools/contracts"; | 11 | import { type CodexSettings, type ModelSelection } from "@t3tools/contracts"; |
| ... | @@ -152,7 +159,7 @@ export const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(func | ... | @@ -152,7 +159,7 @@ export const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(func |
| 152 | const schemaPath = yield* writeTempFile( | 159 | const schemaPath = yield* writeTempFile( |
| 153 | operation, | 160 | operation, |
| 154 | "codex-schema", | 161 | "codex-schema", |
| 155 | JSON.stringify(toJsonSchemaObject(outputSchemaJson)), | 162 | Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(toJsonSchemaObject(outputSchemaJson)), |
| 156 | ); | 163 | ); |
| 157 | const outputPath = yield* writeTempFile(operation, "codex-output", ""); | 164 | const outputPath = yield* writeTempFile(operation, "codex-output", ""); |
| 158 | 165 |
apps/server/src/textGeneration/CursorTextGeneration.test.ts+16-9| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as path from "node:path"; | 2 | import * as path from "node:path"; |
| 2 | import * as os from "node:os"; | 3 | import * as os from "node:os"; |
| 3 | import { fileURLToPath } from "node:url"; | 4 | import { fileURLToPath } from "node:url"; |
| ... | @@ -5,7 +6,11 @@ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync | ... | @@ -5,7 +6,11 @@ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync |
| 5 | 6 | ||
| 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 7 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 7 | import { it } from "@effect/vitest"; | 8 | import { it } from "@effect/vitest"; |
| 8 | import { Effect, Layer, Schema } from "effect"; | 9 | import * as Clock from "effect/Clock"; |
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Exit from "effect/Exit"; | ||
| 12 | import * as Layer from "effect/Layer"; | ||
| 13 | import * as Schema from "effect/Schema"; | ||
| 9 | import { createModelSelection } from "@t3tools/shared/model"; | 14 | import { createModelSelection } from "@t3tools/shared/model"; |
| 10 | import { expect } from "vitest"; | 15 | import { expect } from "vitest"; |
| 11 | 16 | ||
| ... | @@ -67,17 +72,19 @@ function withFakeAcpAgent<A, E, R>( | ... | @@ -67,17 +72,19 @@ function withFakeAcpAgent<A, E, R>( |
| 67 | } | 72 | } |
| 68 | 73 | ||
| 69 | function waitForFileContent(path: string): Effect.Effect<string> { | 74 | function waitForFileContent(path: string): Effect.Effect<string> { |
| 70 | return Effect.promise(async () => { | 75 | return Effect.gen(function* () { |
| 71 | const deadline = Date.now() + 5_000; | 76 | const deadline = (yield* Clock.currentTimeMillis) + 5_000; |
| 72 | for (;;) { | 77 | for (;;) { |
| 73 | try { | 78 | const result = yield* Effect.exit(Effect.sync(() => readFileSync(path, "utf8"))); |
| 74 | return readFileSync(path, "utf8"); | 79 | if (Exit.isSuccess(result)) { |
| 75 | } catch (error) { | 80 | return result.value; |
| 76 | if (Date.now() >= deadline) { | 81 | } |
| 77 | throw error instanceof Error ? error : new Error(String(error)); | 82 | { |
| 83 | if ((yield* Clock.currentTimeMillis) >= deadline) { | ||
| 84 | return yield* Effect.die(result.cause); | ||
| 78 | } | 85 | } |
| 79 | } | 86 | } |
| 80 | await new Promise((resolve) => setTimeout(resolve, 25)); | 87 | yield* Effect.sleep(25); |
| 81 | } | 88 | } |
| 82 | }); | 89 | }); |
| 83 | } | 90 | } |
apps/server/src/textGeneration/CursorTextGeneration.ts+4-1| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | import { Effect, Option, Ref, Schema } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Option from "effect/Option"; | ||
| 3 | import * as Ref from "effect/Ref"; | ||
| 4 | import * as Schema from "effect/Schema"; | ||
| 2 | import { ChildProcessSpawner } from "effect/unstable/process"; | 5 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 3 | 6 | ||
| 4 | import { type CursorSettings, type ModelSelection } from "@t3tools/contracts"; | 7 | import { type CursorSettings, type ModelSelection } from "@t3tools/contracts"; |
apps/server/src/textGeneration/OpenCodeTextGeneration.test.ts+6-3| ... | @@ -1,9 +1,12 @@ | ... | @@ -1,9 +1,12 @@ |
| 1 | import { OpenCodeSettings, ProviderInstanceId } from "@t3tools/contracts"; | 1 | import { OpenCodeSettings, ProviderInstanceId } from "@t3tools/contracts"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { it } from "@effect/vitest"; | 3 | import { it } from "@effect/vitest"; |
| 4 | import { Duration, Effect, Layer, Schema } from "effect"; | 4 | import * as Duration from "effect/Duration"; |
| 5 | import { TestClock } from "effect/testing"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import { NetService } from "@t3tools/shared/Net"; | 6 | import * as Layer from "effect/Layer"; |
| 7 | import * as Schema from "effect/Schema"; | ||
| 8 | import * as TestClock from "effect/testing/TestClock"; | ||
| 9 | import * as NetService from "@t3tools/shared/Net"; | ||
| 7 | import { beforeEach, expect } from "vitest"; | 10 | import { beforeEach, expect } from "vitest"; |
| 8 | 11 | ||
| 9 | import { ServerConfig } from "../config.ts"; | 12 | import { ServerConfig } from "../config.ts"; |
apps/server/src/textGeneration/OpenCodeTextGeneration.ts+5-1| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Effect, Exit, Fiber, Schema, Scope } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Exit from "effect/Exit"; | ||
| 3 | import * as Fiber from "effect/Fiber"; | ||
| 4 | import * as Schema from "effect/Schema"; | ||
| 5 | import * as Scope from "effect/Scope"; | ||
| 2 | import * as Semaphore from "effect/Semaphore"; | 6 | import * as Semaphore from "effect/Semaphore"; |
| 3 | 7 | ||
| 4 | import { | 8 | import { |
apps/server/src/textGeneration/TextGeneration.test.ts+4-1| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | import { it } from "@effect/vitest"; | 1 | import { it } from "@effect/vitest"; |
| 2 | import { Effect, PubSub, Result, Stream } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as PubSub from "effect/PubSub"; | ||
| 4 | import * as Result from "effect/Result"; | ||
| 5 | import * as Stream from "effect/Stream"; | ||
| 3 | import { describe, expect } from "vitest"; | 6 | import { describe, expect } from "vitest"; |
| 4 | 7 | ||
| 5 | import { ProviderInstanceId } from "@t3tools/contracts"; | 8 | import { ProviderInstanceId } from "@t3tools/contracts"; |
apps/server/src/textGeneration/TextGeneration.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { Context, Effect, Layer } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 2 | import type { ChatAttachment, ModelSelection, ProviderInstanceId } from "@t3tools/contracts"; | 4 | import type { ChatAttachment, ModelSelection, ProviderInstanceId } from "@t3tools/contracts"; |
| 3 | import { TextGenerationError } from "@t3tools/contracts"; | 5 | import { TextGenerationError } from "@t3tools/contracts"; |
| 4 | 6 |
apps/server/src/textGeneration/TextGenerationPolicy.ts+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Schema } from "effect"; | 1 | import * as Schema from "effect/Schema"; |
| 2 | 2 | ||
| 3 | export const TextGenerationPolicyKind = Schema.Literals([ | 3 | export const TextGenerationPolicyKind = Schema.Literals([ |
| 4 | "default", | 4 | "default", |
apps/server/src/textGeneration/TextGenerationPrompts.ts+1-1| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | * | 6 | * |
| 7 | * @module textGenerationPrompts | 7 | * @module textGenerationPrompts |
| 8 | */ | 8 | */ |
| 9 | import { Schema } from "effect"; | 9 | import * as Schema from "effect/Schema"; |
| 10 | import type { ChatAttachment } from "@t3tools/contracts"; | 10 | import type { ChatAttachment } from "@t3tools/contracts"; |
| 11 | 11 | ||
| 12 | import { limitSection } from "./TextGenerationUtils.ts"; | 12 | import { limitSection } from "./TextGenerationUtils.ts"; |
apps/server/src/textGeneration/TextGenerationUtils.ts+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import { Schema } from "effect"; | 1 | import * as Schema from "effect/Schema"; |
| 2 | 2 | ||
| 3 | import { TextGenerationError } from "@t3tools/contracts"; | 3 | import { TextGenerationError } from "@t3tools/contracts"; |
| 4 | 4 |
apps/server/src/vcs/GitVcsDriver.test.ts+5-1| ... | @@ -1,5 +1,9 @@ | ... | @@ -1,5 +1,9 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { Effect, FileSystem, Layer, Path, PlatformError } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as FileSystem from "effect/FileSystem"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Path from "effect/Path"; | ||
| 6 | import * as PlatformError from "effect/PlatformError"; | ||
| 3 | import { ChildProcessSpawner } from "effect/unstable/process"; | 7 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 4 | import { assert, it } from "@effect/vitest"; | 8 | import { assert, it } from "@effect/vitest"; |
| 5 | 9 |
apps/server/src/vcs/GitVcsDriver.ts+5-1| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Context, DateTime, Effect, Layer, Option } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as DateTime from "effect/DateTime"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 2 | import { ChildProcessSpawner } from "effect/unstable/process"; | 6 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 3 | 7 | ||
| 4 | import { | 8 | import { |
apps/server/src/vcs/GitVcsDriverCore.test.ts+6-1| ... | @@ -1,6 +1,11 @@ | ... | @@ -1,6 +1,11 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it, describe } from "@effect/vitest"; | 2 | import { assert, it, describe } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Layer, Path, PlatformError, Scope } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 7 | import * as PlatformError from "effect/PlatformError"; | ||
| 8 | import * as Scope from "effect/Scope"; | ||
| 4 | 9 | ||
| 5 | import { GitCommandError } from "@t3tools/contracts"; | 10 | import { GitCommandError } from "@t3tools/contracts"; |
| 6 | import { ServerConfig } from "../config.ts"; | 11 | import { ServerConfig } from "../config.ts"; |
apps/server/src/vcs/GitVcsDriverCore.ts+16-18| ... | @@ -1,21 +1,19 @@ | ... | @@ -1,21 +1,19 @@ |
| 1 | import { | 1 | import * as Cache from "effect/Cache"; |
| 2 | Cache, | 2 | import * as Data from "effect/Data"; |
| 3 | Data, | 3 | import * as DateTime from "effect/DateTime"; |
| 4 | DateTime, | 4 | import * as Duration from "effect/Duration"; |
| 5 | Duration, | 5 | import * as Effect from "effect/Effect"; |
| 6 | Effect, | 6 | import * as Exit from "effect/Exit"; |
| 7 | Exit, | 7 | import * as FileSystem from "effect/FileSystem"; |
| 8 | FileSystem, | 8 | import * as Option from "effect/Option"; |
| 9 | Option, | 9 | import * as Path from "effect/Path"; |
| 10 | Path, | 10 | import * as PlatformError from "effect/PlatformError"; |
| 11 | PlatformError, | 11 | import * as Ref from "effect/Ref"; |
| 12 | Ref, | 12 | import * as Result from "effect/Result"; |
| 13 | Result, | 13 | import * as Schema from "effect/Schema"; |
| 14 | Schema, | 14 | import * as Scope from "effect/Scope"; |
| 15 | Scope, | 15 | import * as Semaphore from "effect/Semaphore"; |
| 16 | Semaphore, | 16 | import * as Stream from "effect/Stream"; |
| 17 | Stream, | ||
| 18 | } from "effect"; | ||
| 19 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 17 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 20 | 18 | ||
| 21 | import { GitCommandError, type VcsRef } from "@t3tools/contracts"; | 19 | import { GitCommandError, type VcsRef } from "@t3tools/contracts"; |
apps/server/src/vcs/VcsDriver.ts+2-1| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { Context, type Effect } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import type * as Effect from "effect/Effect"; | ||
| 2 | 3 | ||
| 3 | import type { | 4 | import type { |
| 4 | VcsDriverCapabilities, | 5 | VcsDriverCapabilities, |
apps/server/src/vcs/VcsDriverRegistry.test.ts+2-1| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { assert, it, describe } from "@effect/vitest"; | 1 | import { assert, it, describe } from "@effect/vitest"; |
| 2 | import { Effect, Layer } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import { ChildProcessSpawner } from "effect/unstable/process"; | 4 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 4 | 5 | ||
| 5 | import * as VcsProcess from "./VcsProcess.ts"; | 6 | import * as VcsProcess from "./VcsProcess.ts"; |
apps/server/src/vcs/VcsDriverRegistry.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Cache, Context, Duration, Effect, Exit, Layer } from "effect"; | 1 | import * as Cache from "effect/Cache"; |
| 2 | import * as Context from "effect/Context"; | ||
| 3 | import * as Duration from "effect/Duration"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 2 | 7 | ||
| 3 | import type { VcsDriverKind, VcsError, VcsRepositoryIdentity } from "@t3tools/contracts"; | 8 | import type { VcsDriverKind, VcsError, VcsRepositoryIdentity } from "@t3tools/contracts"; |
| 4 | import { VcsUnsupportedOperationError } from "@t3tools/contracts"; | 9 | import { VcsUnsupportedOperationError } from "@t3tools/contracts"; |
apps/server/src/vcs/VcsProcess.ts+8-1| ... | @@ -1,4 +1,11 @@ | ... | @@ -1,4 +1,11 @@ |
| 1 | import { Duration, Context, Effect, Layer, Option, PlatformError, Sink, Stream } from "effect"; | 1 | import * as Duration from "effect/Duration"; |
| 2 | import * as Context from "effect/Context"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | import * as PlatformError from "effect/PlatformError"; | ||
| 7 | import * as Sink from "effect/Sink"; | ||
| 8 | import * as Stream from "effect/Stream"; | ||
| 2 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 9 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 3 | 10 | ||
| 4 | import { | 11 | import { |
apps/server/src/vcs/VcsProjectConfig.test.ts+5-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import { assert, it, describe } from "@effect/vitest"; | 1 | import { assert, it, describe } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { Effect, FileSystem, Layer, Path } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 4 | 7 | ||
| 5 | import * as VcsProjectConfig from "./VcsProjectConfig.ts"; | 8 | import * as VcsProjectConfig from "./VcsProjectConfig.ts"; |
| 6 | 9 | ||
| ... | @@ -38,6 +41,7 @@ describe("VcsProjectConfig", () => { | ... | @@ -38,6 +41,7 @@ describe("VcsProjectConfig", () => { |
| 38 | yield* fileSystem.makeDirectory(nested, { recursive: true }); | 41 | yield* fileSystem.makeDirectory(nested, { recursive: true }); |
| 39 | yield* fileSystem.writeFileString( | 42 | yield* fileSystem.writeFileString( |
| 40 | path.join(configDir, "vcs.json"), | 43 | path.join(configDir, "vcs.json"), |
| 44 | // @effect-diagnostics-next-line preferSchemaOverJson:off | ||
| 41 | JSON.stringify({ vcs: { kind: "jj" } }), | 45 | JSON.stringify({ vcs: { kind: "jj" } }), |
| 42 | ); | 46 | ); |
| 43 | 47 |
apps/server/src/vcs/VcsProjectConfig.ts+6-1| ... | @@ -1,4 +1,9 @@ | ... | @@ -1,4 +1,9 @@ |
| 1 | import { Context, Effect, FileSystem, Layer, Path, Schema } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as FileSystem from "effect/FileSystem"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Path from "effect/Path"; | ||
| 6 | import * as Schema from "effect/Schema"; | ||
| 2 | 7 | ||
| 3 | import { VcsDriverKind, type VcsDriverKind as VcsDriverKindType } from "@t3tools/contracts"; | 8 | import { VcsDriverKind, type VcsDriverKind as VcsDriverKindType } from "@t3tools/contracts"; |
| 4 | 9 |
apps/server/src/vcs/VcsProvisioningService.test.ts+4-1| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | import { assert, it } from "@effect/vitest"; | 1 | import { assert, it } from "@effect/vitest"; |
| 2 | import { DateTime, Effect, Layer, Option } from "effect"; | 2 | import * as DateTime from "effect/DateTime"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 3 | import { ChildProcessSpawner } from "effect/unstable/process"; | 6 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 4 | 7 | ||
| 5 | import * as VcsDriver from "./VcsDriver.ts"; | 8 | import * as VcsDriver from "./VcsDriver.ts"; |
apps/server/src/vcs/VcsProvisioningService.ts+3-1| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | import { Context, Effect, Layer } from "effect"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 2 | 4 | ||
| 3 | import { | 5 | import { |
| 4 | type VcsDriverKind, | 6 | type VcsDriverKind, |
apps/server/src/vcs/VcsStatusBroadcaster.test.ts+13-2| ... | @@ -1,6 +1,14 @@ | ... | @@ -1,6 +1,14 @@ |
| 1 | import { assert, it, describe } from "@effect/vitest"; | 1 | import { assert, it, describe } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { Deferred, Effect, Exit, FileSystem, Layer, Option, Path, Scope, Stream } from "effect"; | 3 | import * as Deferred from "effect/Deferred"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Exit from "effect/Exit"; | ||
| 6 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Layer from "effect/Layer"; | ||
| 8 | import * as Option from "effect/Option"; | ||
| 9 | import * as Path from "effect/Path"; | ||
| 10 | import * as Scope from "effect/Scope"; | ||
| 11 | import * as Stream from "effect/Stream"; | ||
| 4 | import type { | 12 | import type { |
| 5 | VcsStatusLocalResult, | 13 | VcsStatusLocalResult, |
| 6 | VcsStatusRemoteResult, | 14 | VcsStatusRemoteResult, |
| ... | @@ -46,6 +54,7 @@ function makeTestLayer(state: { | ... | @@ -46,6 +54,7 @@ function makeTestLayer(state: { |
| 46 | remoteInvalidationCalls: number; | 54 | remoteInvalidationCalls: number; |
| 47 | }) { | 55 | }) { |
| 48 | return VcsStatusBroadcaster.layer.pipe( | 56 | return VcsStatusBroadcaster.layer.pipe( |
| 57 | Layer.provideMerge(NodeServices.layer), | ||
| 49 | Layer.provide( | 58 | Layer.provide( |
| 50 | Layer.mock(GitWorkflowService.GitWorkflowService)({ | 59 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 51 | localStatus: () => | 60 | localStatus: () => |
| ... | @@ -185,6 +194,7 @@ describe("VcsStatusBroadcaster", () => { | ... | @@ -185,6 +194,7 @@ describe("VcsStatusBroadcaster", () => { |
| 185 | remoteInvalidationCalls: 0, | 194 | remoteInvalidationCalls: 0, |
| 186 | }; | 195 | }; |
| 187 | const testLayer = VcsStatusBroadcaster.layer.pipe( | 196 | const testLayer = VcsStatusBroadcaster.layer.pipe( |
| 197 | Layer.provideMerge(NodeServices.layer), | ||
| 188 | Layer.provide( | 198 | Layer.provide( |
| 189 | Layer.mock(GitWorkflowService.GitWorkflowService)({ | 199 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 190 | localStatus: (input) => | 200 | localStatus: (input) => |
| ... | @@ -231,7 +241,7 @@ describe("VcsStatusBroadcaster", () => { | ... | @@ -231,7 +241,7 @@ describe("VcsStatusBroadcaster", () => { |
| 231 | assert.deepStrictEqual(seenCwds, [realPath, realPath]); | 241 | assert.deepStrictEqual(seenCwds, [realPath, realPath]); |
| 232 | assert.equal(state.localStatusCalls, 1); | 242 | assert.equal(state.localStatusCalls, 1); |
| 233 | assert.equal(state.remoteStatusCalls, 1); | 243 | assert.equal(state.remoteStatusCalls, 1); |
| 234 | }).pipe(Effect.provide(Layer.mergeAll(testLayer, NodeServices.layer))); | 244 | }).pipe(Effect.provide(testLayer)); |
| 235 | }); | 245 | }); |
| 236 | 246 | ||
| 237 | it.effect("streams a local snapshot first and remote updates later", () => { | 247 | it.effect("streams a local snapshot first and remote updates later", () => { |
| ... | @@ -286,6 +296,7 @@ describe("VcsStatusBroadcaster", () => { | ... | @@ -286,6 +296,7 @@ describe("VcsStatusBroadcaster", () => { |
| 286 | let remoteInterruptedDeferred: Deferred.Deferred<void, never> | null = null; | 296 | let remoteInterruptedDeferred: Deferred.Deferred<void, never> | null = null; |
| 287 | let remoteStartedDeferred: Deferred.Deferred<void, never> | null = null; | 297 | let remoteStartedDeferred: Deferred.Deferred<void, never> | null = null; |
| 288 | const testLayer = VcsStatusBroadcaster.layer.pipe( | 298 | const testLayer = VcsStatusBroadcaster.layer.pipe( |
| 299 | Layer.provideMerge(NodeServices.layer), | ||
| 289 | Layer.provide( | 300 | Layer.provide( |
| 290 | Layer.mock(GitWorkflowService.GitWorkflowService)({ | 301 | Layer.mock(GitWorkflowService.GitWorkflowService)({ |
| 291 | localStatus: () => | 302 | localStatus: () => |
apps/server/src/vcs/VcsStatusBroadcaster.ts+24-26| ... | @@ -1,18 +1,15 @@ | ... | @@ -1,18 +1,15 @@ |
| 1 | import { realpathSync } from "node:fs"; | 1 | import * as Context from "effect/Context"; |
| 2 | 2 | import * as Duration from "effect/Duration"; | |
| 3 | import { | 3 | import * as Effect from "effect/Effect"; |
| 4 | Context, | 4 | import * as Exit from "effect/Exit"; |
| 5 | Duration, | 5 | import * as Fiber from "effect/Fiber"; |
| 6 | Effect, | 6 | import * as FileSystem from "effect/FileSystem"; |
| 7 | Exit, | 7 | import * as Layer from "effect/Layer"; |
| 8 | Fiber, | 8 | import * as PubSub from "effect/PubSub"; |
| 9 | Layer, | 9 | import * as Ref from "effect/Ref"; |
| 10 | PubSub, | 10 | import * as Scope from "effect/Scope"; |
| 11 | Ref, | 11 | import * as Stream from "effect/Stream"; |
| 12 | Scope, | 12 | import * as SynchronizedRef from "effect/SynchronizedRef"; |
| 13 | Stream, | ||
| 14 | SynchronizedRef, | ||
| 15 | } from "effect"; | ||
| 16 | import type { | 13 | import type { |
| 17 | GitManagerServiceError, | 14 | GitManagerServiceError, |
| 18 | VcsStatusInput, | 15 | VcsStatusInput, |
| ... | @@ -69,18 +66,17 @@ function fingerprintStatusPart(status: unknown): string { | ... | @@ -69,18 +66,17 @@ function fingerprintStatusPart(status: unknown): string { |
| 69 | return JSON.stringify(status); | 66 | return JSON.stringify(status); |
| 70 | } | 67 | } |
| 71 | 68 | ||
| 72 | function normalizeCwd(cwd: string): string { | 69 | const normalizeCwd = (cwd: string) => |
| 73 | try { | 70 | Effect.service(FileSystem.FileSystem).pipe( |
| 74 | return realpathSync.native(cwd); | 71 | Effect.flatMap((fs) => fs.realPath(cwd)), |
| 75 | } catch { | 72 | Effect.orElseSucceed(() => cwd), |
| 76 | return cwd; | 73 | ); |
| 77 | } | ||
| 78 | } | ||
| 79 | 74 | ||
| 80 | export const layer = Layer.effect( | 75 | export const layer = Layer.effect( |
| 81 | VcsStatusBroadcaster, | 76 | VcsStatusBroadcaster, |
| 82 | Effect.gen(function* () { | 77 | Effect.gen(function* () { |
| 83 | const workflow = yield* GitWorkflowService.GitWorkflowService; | 78 | const workflow = yield* GitWorkflowService.GitWorkflowService; |
| 79 | const fs = yield* FileSystem.FileSystem; | ||
| 84 | const changesPubSub = yield* Effect.acquireRelease( | 80 | const changesPubSub = yield* Effect.acquireRelease( |
| 85 | PubSub.unbounded<VcsStatusChange>(), | 81 | PubSub.unbounded<VcsStatusChange>(), |
| 86 | (pubsub) => PubSub.shutdown(pubsub), | 82 | (pubsub) => PubSub.shutdown(pubsub), |
| ... | @@ -195,10 +191,12 @@ export const layer = Layer.effect( | ... | @@ -195,10 +191,12 @@ export const layer = Layer.effect( |
| 195 | }, | 191 | }, |
| 196 | ); | 192 | ); |
| 197 | 193 | ||
| 194 | const withFileSystem = Effect.provideService(FileSystem.FileSystem, fs); | ||
| 195 | |||
| 198 | const getStatus: VcsStatusBroadcasterShape["getStatus"] = Effect.fn( | 196 | const getStatus: VcsStatusBroadcasterShape["getStatus"] = Effect.fn( |
| 199 | "VcsStatusBroadcaster.getStatus", | 197 | "VcsStatusBroadcaster.getStatus", |
| 200 | )(function* (input) { | 198 | )(function* (input) { |
| 201 | const cwd = normalizeCwd(input.cwd); | 199 | const cwd = yield* withFileSystem(normalizeCwd(input.cwd)); |
| 202 | const [local, remote] = yield* Effect.all([ | 200 | const [local, remote] = yield* Effect.all([ |
| 203 | getOrLoadLocalStatus(cwd), | 201 | getOrLoadLocalStatus(cwd), |
| 204 | getOrLoadRemoteStatus(cwd), | 202 | getOrLoadRemoteStatus(cwd), |
| ... | @@ -209,7 +207,7 @@ export const layer = Layer.effect( | ... | @@ -209,7 +207,7 @@ export const layer = Layer.effect( |
| 209 | const refreshLocalStatus: VcsStatusBroadcasterShape["refreshLocalStatus"] = Effect.fn( | 207 | const refreshLocalStatus: VcsStatusBroadcasterShape["refreshLocalStatus"] = Effect.fn( |
| 210 | "VcsStatusBroadcaster.refreshLocalStatus", | 208 | "VcsStatusBroadcaster.refreshLocalStatus", |
| 211 | )(function* (rawCwd) { | 209 | )(function* (rawCwd) { |
| 212 | const cwd = normalizeCwd(rawCwd); | 210 | const cwd = yield* withFileSystem(normalizeCwd(rawCwd)); |
| 213 | yield* workflow.invalidateLocalStatus(cwd); | 211 | yield* workflow.invalidateLocalStatus(cwd); |
| 214 | const local = yield* workflow.localStatus({ cwd }); | 212 | const local = yield* workflow.localStatus({ cwd }); |
| 215 | return yield* updateCachedLocalStatus(cwd, local, { publish: true }); | 213 | return yield* updateCachedLocalStatus(cwd, local, { publish: true }); |
| ... | @@ -226,7 +224,7 @@ export const layer = Layer.effect( | ... | @@ -226,7 +224,7 @@ export const layer = Layer.effect( |
| 226 | const refreshStatus: VcsStatusBroadcasterShape["refreshStatus"] = Effect.fn( | 224 | const refreshStatus: VcsStatusBroadcasterShape["refreshStatus"] = Effect.fn( |
| 227 | "VcsStatusBroadcaster.refreshStatus", | 225 | "VcsStatusBroadcaster.refreshStatus", |
| 228 | )(function* (rawCwd) { | 226 | )(function* (rawCwd) { |
| 229 | const cwd = normalizeCwd(rawCwd); | 227 | const cwd = yield* withFileSystem(normalizeCwd(rawCwd)); |
| 230 | const [local, remote] = yield* Effect.all([ | 228 | const [local, remote] = yield* Effect.all([ |
| 231 | refreshLocalStatus(cwd), | 229 | refreshLocalStatus(cwd), |
| 232 | refreshRemoteStatus(cwd), | 230 | refreshRemoteStatus(cwd), |
| ... | @@ -312,7 +310,7 @@ export const layer = Layer.effect( | ... | @@ -312,7 +310,7 @@ export const layer = Layer.effect( |
| 312 | const streamStatus: VcsStatusBroadcasterShape["streamStatus"] = (input) => | 310 | const streamStatus: VcsStatusBroadcasterShape["streamStatus"] = (input) => |
| 313 | Stream.unwrap( | 311 | Stream.unwrap( |
| 314 | Effect.gen(function* () { | 312 | Effect.gen(function* () { |
| 315 | const cwd = normalizeCwd(input.cwd); | 313 | const cwd = yield* withFileSystem(normalizeCwd(input.cwd)); |
| 316 | const subscription = yield* PubSub.subscribe(changesPubSub); | 314 | const subscription = yield* PubSub.subscribe(changesPubSub); |
| 317 | const initialLocal = yield* getOrLoadLocalStatus(cwd); | 315 | const initialLocal = yield* getOrLoadLocalStatus(cwd); |
| 318 | const initialRemote = (yield* getCachedStatus(cwd))?.remote?.value ?? null; | 316 | const initialRemote = (yield* getCachedStatus(cwd))?.remote?.value ?? null; |
apps/server/src/vcs/testing/VcsDriverContractHarness.ts+8-10| ... | @@ -1,14 +1,12 @@ | ... | @@ -1,14 +1,12 @@ |
| 1 | import { assert, it, describe } from "@effect/vitest"; | 1 | import { assert, it, describe } from "@effect/vitest"; |
| 2 | import { | 2 | import * as Effect from "effect/Effect"; |
| 3 | Effect, | 3 | import * as FileSystem from "effect/FileSystem"; |
| 4 | FileSystem, | 4 | import * as Layer from "effect/Layer"; |
| 5 | Layer, | 5 | import * as Path from "effect/Path"; |
| 6 | Path, | 6 | import type * as PlatformError from "effect/PlatformError"; |
| 7 | type PlatformError, | 7 | import type * as Scope from "effect/Scope"; |
| 8 | type Scope, | 8 | import * as DateTime from "effect/DateTime"; |
| 9 | DateTime, | 9 | import * as Option from "effect/Option"; |
| 10 | Option, | ||
| 11 | } from "effect"; | ||
| 12 | 10 | ||
| 13 | import type { VcsDriverKind } from "@t3tools/contracts"; | 11 | import type { VcsDriverKind } from "@t3tools/contracts"; |
| 14 | import * as VcsDriver from "../VcsDriver.ts"; | 12 | import * as VcsDriver from "../VcsDriver.ts"; |
apps/server/src/workspace/Layers/WorkspaceEntries.test.ts+38-7| ... | @@ -1,8 +1,13 @@ | ... | @@ -1,8 +1,13 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fsPromises from "node:fs/promises"; | 2 | import fsPromises from "node:fs/promises"; |
| 2 | |||
| 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 3 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 4 | import { it, afterEach, describe, expect, vi } from "@effect/vitest"; | 4 | import { it, afterEach, describe, expect, vi } from "@effect/vitest"; |
| 5 | import { Effect, FileSystem, Layer, Path, PlatformError } from "effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Fiber from "effect/Fiber"; | ||
| 7 | import * as FileSystem from "effect/FileSystem"; | ||
| 8 | import * as Layer from "effect/Layer"; | ||
| 9 | import * as Path from "effect/Path"; | ||
| 10 | import * as PlatformError from "effect/PlatformError"; | ||
| 6 | 11 | ||
| 7 | import { ServerConfig } from "../../config.ts"; | 12 | import { ServerConfig } from "../../config.ts"; |
| 8 | import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; | 13 | import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; |
| ... | @@ -222,25 +227,37 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { | ... | @@ -222,25 +227,37 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { |
| 222 | yield* writeTextFile(cwd, "src/components/Composer.tsx"); | 227 | yield* writeTextFile(cwd, "src/components/Composer.tsx"); |
| 223 | 228 | ||
| 224 | let rootReadCount = 0; | 229 | let rootReadCount = 0; |
| 230 | let releaseRootRead: (() => void) | undefined; | ||
| 231 | const rootReadGate = new Promise<void>((resolve) => { | ||
| 232 | releaseRootRead = resolve; | ||
| 233 | }); | ||
| 225 | const originalReaddir = fsPromises.readdir.bind(fsPromises); | 234 | const originalReaddir = fsPromises.readdir.bind(fsPromises); |
| 226 | vi.spyOn(fsPromises, "readdir").mockImplementation((async ( | 235 | vi.spyOn(fsPromises, "readdir").mockImplementation((async ( |
| 227 | ...args: Parameters<typeof fsPromises.readdir> | 236 | ...args: Parameters<typeof fsPromises.readdir> |
| 228 | ) => { | 237 | ) => { |
| 229 | if (args[0] === cwd) { | 238 | if (args[0] === cwd) { |
| 230 | rootReadCount += 1; | 239 | rootReadCount += 1; |
| 231 | await new Promise((resolve) => setTimeout(resolve, 20)); | 240 | await rootReadGate; |
| 232 | } | 241 | } |
| 233 | return originalReaddir(...args); | 242 | return originalReaddir(...args); |
| 234 | }) as typeof fsPromises.readdir); | 243 | }) as typeof fsPromises.readdir); |
| 235 | 244 | ||
| 236 | yield* Effect.all( | 245 | const searches = yield* Effect.all( |
| 237 | [ | 246 | [ |
| 238 | searchWorkspaceEntries({ cwd, query: "", limit: 100 }), | 247 | searchWorkspaceEntries({ cwd, query: "", limit: 100 }), |
| 239 | searchWorkspaceEntries({ cwd, query: "comp", limit: 100 }), | 248 | searchWorkspaceEntries({ cwd, query: "comp", limit: 100 }), |
| 240 | searchWorkspaceEntries({ cwd, query: "src", limit: 100 }), | 249 | searchWorkspaceEntries({ cwd, query: "src", limit: 100 }), |
| 241 | ], | 250 | ], |
| 242 | { concurrency: "unbounded" }, | 251 | { concurrency: "unbounded" }, |
| 243 | ); | 252 | ).pipe(Effect.forkScoped); |
| 253 | for (let attempt = 0; attempt < 50; attempt += 1) { | ||
| 254 | if (rootReadCount > 0) { | ||
| 255 | break; | ||
| 256 | } | ||
| 257 | yield* Effect.yieldNow; | ||
| 258 | } | ||
| 259 | releaseRootRead?.(); | ||
| 260 | yield* Fiber.join(searches); | ||
| 244 | 261 | ||
| 245 | expect(rootReadCount).toBe(1); | 262 | expect(rootReadCount).toBe(1); |
| 246 | }), | 263 | }), |
| ... | @@ -257,6 +274,10 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { | ... | @@ -257,6 +274,10 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { |
| 257 | 274 | ||
| 258 | let activeReads = 0; | 275 | let activeReads = 0; |
| 259 | let peakReads = 0; | 276 | let peakReads = 0; |
| 277 | let releaseReads: (() => void) | undefined; | ||
| 278 | const readsGate = new Promise<void>((resolve) => { | ||
| 279 | releaseReads = resolve; | ||
| 280 | }); | ||
| 260 | const originalReaddir = fsPromises.readdir.bind(fsPromises); | 281 | const originalReaddir = fsPromises.readdir.bind(fsPromises); |
| 261 | vi.spyOn(fsPromises, "readdir").mockImplementation((async ( | 282 | vi.spyOn(fsPromises, "readdir").mockImplementation((async ( |
| 262 | ...args: Parameters<typeof fsPromises.readdir> | 283 | ...args: Parameters<typeof fsPromises.readdir> |
| ... | @@ -265,7 +286,7 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { | ... | @@ -265,7 +286,7 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { |
| 265 | if (typeof target === "string" && target.startsWith(cwd)) { | 286 | if (typeof target === "string" && target.startsWith(cwd)) { |
| 266 | activeReads += 1; | 287 | activeReads += 1; |
| 267 | peakReads = Math.max(peakReads, activeReads); | 288 | peakReads = Math.max(peakReads, activeReads); |
| 268 | await new Promise((resolve) => setTimeout(resolve, 4)); | 289 | await readsGate; |
| 269 | try { | 290 | try { |
| 270 | return await originalReaddir(...args); | 291 | return await originalReaddir(...args); |
| 271 | } finally { | 292 | } finally { |
| ... | @@ -275,7 +296,17 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { | ... | @@ -275,7 +296,17 @@ it.layer(TestLayer)("WorkspaceEntriesLive", (it) => { |
| 275 | return originalReaddir(...args); | 296 | return originalReaddir(...args); |
| 276 | }) as typeof fsPromises.readdir); | 297 | }) as typeof fsPromises.readdir); |
| 277 | 298 | ||
| 278 | yield* searchWorkspaceEntries({ cwd, query: "", limit: 200 }); | 299 | const search = yield* searchWorkspaceEntries({ cwd, query: "", limit: 200 }).pipe( |
| 300 | Effect.forkScoped, | ||
| 301 | ); | ||
| 302 | for (let attempt = 0; attempt < 50; attempt += 1) { | ||
| 303 | if (activeReads > 0) { | ||
| 304 | break; | ||
| 305 | } | ||
| 306 | yield* Effect.yieldNow; | ||
| 307 | } | ||
| 308 | releaseReads?.(); | ||
| 309 | yield* Fiber.join(search); | ||
| 279 | 310 | ||
| 280 | expect(peakReads).toBeLessThanOrEqual(32); | 311 | expect(peakReads).toBeLessThanOrEqual(32); |
| 281 | }), | 312 | }), |
apps/server/src/workspace/Layers/WorkspaceEntries.ts+8-1| ... | @@ -1,8 +1,15 @@ | ... | @@ -1,8 +1,15 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as OS from "node:os"; | 2 | import * as OS from "node:os"; |
| 2 | import fsPromises from "node:fs/promises"; | 3 | import fsPromises from "node:fs/promises"; |
| 3 | import type { Dirent } from "node:fs"; | 4 | import type { Dirent } from "node:fs"; |
| 4 | 5 | ||
| 5 | import { Cache, DateTime, Duration, Effect, Exit, Layer, Path } from "effect"; | 6 | import * as Cache from "effect/Cache"; |
| 7 | import * as DateTime from "effect/DateTime"; | ||
| 8 | import * as Duration from "effect/Duration"; | ||
| 9 | import * as Effect from "effect/Effect"; | ||
| 10 | import * as Exit from "effect/Exit"; | ||
| 11 | import * as Layer from "effect/Layer"; | ||
| 12 | import * as Path from "effect/Path"; | ||
| 6 | 13 | ||
| 7 | import { type FilesystemBrowseInput, type ProjectEntry } from "@t3tools/contracts"; | 14 | import { type FilesystemBrowseInput, type ProjectEntry } from "@t3tools/contracts"; |
| 8 | import { isExplicitRelativePath, isWindowsAbsolutePath } from "@t3tools/shared/path"; | 15 | import { isExplicitRelativePath, isWindowsAbsolutePath } from "@t3tools/shared/path"; |
apps/server/src/workspace/Layers/WorkspaceFileSystem.test.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { it, describe, expect } from "@effect/vitest"; | 2 | import { it, describe, expect } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Layer, Path } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 4 | 7 | ||
| 5 | import { ServerConfig } from "../../config.ts"; | 8 | import { ServerConfig } from "../../config.ts"; |
| 6 | import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; | 9 | import * as VcsDriverRegistry from "../../vcs/VcsDriverRegistry.ts"; |
apps/server/src/workspace/Layers/WorkspaceFileSystem.ts+4-1| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | import { Effect, FileSystem, Layer, Path } from "effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as FileSystem from "effect/FileSystem"; | ||
| 3 | import * as Layer from "effect/Layer"; | ||
| 4 | import * as Path from "effect/Path"; | ||
| 2 | 5 | ||
| 3 | import { | 6 | import { |
| 4 | WorkspaceFileSystem, | 7 | WorkspaceFileSystem, |
apps/server/src/workspace/Layers/WorkspacePaths.test.ts+4-1| ... | @@ -1,6 +1,9 @@ | ... | @@ -1,6 +1,9 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { it, describe, expect } from "@effect/vitest"; | 2 | import { it, describe, expect } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Layer, Path } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Path from "effect/Path"; | ||
| 4 | 7 | ||
| 5 | import { WorkspacePaths } from "../Services/WorkspacePaths.ts"; | 8 | import { WorkspacePaths } from "../Services/WorkspacePaths.ts"; |
| 6 | import { WorkspacePathsLive } from "./WorkspacePaths.ts"; | 9 | import { WorkspacePathsLive } from "./WorkspacePaths.ts"; |
apps/server/src/workspace/Layers/WorkspacePaths.ts+4-1| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | import * as OS from "node:os"; | 1 | import * as OS from "node:os"; |
| 2 | import { Effect, FileSystem, Layer, Path } from "effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as FileSystem from "effect/FileSystem"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Path from "effect/Path"; | ||
| 3 | 6 | ||
| 4 | import { | 7 | import { |
| 5 | WorkspacePaths, | 8 | WorkspacePaths, |
apps/server/src/workspace/Services/WorkspaceEntries.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module WorkspaceEntries | 7 | * @module WorkspaceEntries |
| 8 | */ | 8 | */ |
| 9 | import { Schema, Context } from "effect"; | 9 | import * as Schema from "effect/Schema"; |
| 10 | import type { Effect } from "effect"; | 10 | import * as Context from "effect/Context"; |
| 11 | import type * as Effect from "effect/Effect"; | ||
| 11 | 12 | ||
| 12 | import type { | 13 | import type { |
| 13 | FilesystemBrowseInput, | 14 | FilesystemBrowseInput, |
apps/server/src/workspace/Services/WorkspaceFileSystem.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module WorkspaceFileSystem | 7 | * @module WorkspaceFileSystem |
| 8 | */ | 8 | */ |
| 9 | import { Schema, Context } from "effect"; | 9 | import * as Schema from "effect/Schema"; |
| 10 | import type { Effect } from "effect"; | 10 | import * as Context from "effect/Context"; |
| 11 | import type * as Effect from "effect/Effect"; | ||
| 11 | 12 | ||
| 12 | import type { ProjectWriteFileInput, ProjectWriteFileResult } from "@t3tools/contracts"; | 13 | import type { ProjectWriteFileInput, ProjectWriteFileResult } from "@t3tools/contracts"; |
| 13 | import { WorkspacePathOutsideRootError } from "./WorkspacePaths.ts"; | 14 | import { WorkspacePathOutsideRootError } from "./WorkspacePaths.ts"; |
apps/server/src/workspace/Services/WorkspacePaths.ts+3-2| ... | @@ -6,8 +6,9 @@ | ... | @@ -6,8 +6,9 @@ |
| 6 | * | 6 | * |
| 7 | * @module WorkspacePaths | 7 | * @module WorkspacePaths |
| 8 | */ | 8 | */ |
| 9 | import { Schema, Context } from "effect"; | 9 | import * as Schema from "effect/Schema"; |
| 10 | import type { Effect } from "effect"; | 10 | import * as Context from "effect/Context"; |
| 11 | import type * as Effect from "effect/Effect"; | ||
| 11 | 12 | ||
| 12 | export class WorkspaceRootNotExistsError extends Schema.TaggedErrorClass<WorkspaceRootNotExistsError>()( | 13 | export class WorkspaceRootNotExistsError extends Schema.TaggedErrorClass<WorkspaceRootNotExistsError>()( |
| 13 | "WorkspaceRootNotExistsError", | 14 | "WorkspaceRootNotExistsError", |
apps/server/src/ws.ts+89-75| ... | @@ -1,4 +1,13 @@ | ... | @@ -1,4 +1,13 @@ |
| 1 | import { Cause, Duration, Effect, Layer, Option, Queue, Ref, Schema, Stream } from "effect"; | 1 | import * as Cause from "effect/Cause"; |
| 2 | import * as DateTime from "effect/DateTime"; | ||
| 3 | import * as Duration from "effect/Duration"; | ||
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Layer from "effect/Layer"; | ||
| 6 | import * as Option from "effect/Option"; | ||
| 7 | import * as Queue from "effect/Queue"; | ||
| 8 | import * as Ref from "effect/Ref"; | ||
| 9 | import * as Schema from "effect/Schema"; | ||
| 10 | import * as Stream from "effect/Stream"; | ||
| 2 | import { | 11 | import { |
| 3 | type AuthAccessStreamEvent, | 12 | type AuthAccessStreamEvent, |
| 4 | AuthSessionId, | 13 | AuthSessionId, |
| ... | @@ -78,6 +87,8 @@ import { | ... | @@ -78,6 +87,8 @@ import { |
| 78 | } from "./auth/Services/SessionCredentialService.ts"; | 87 | } from "./auth/Services/SessionCredentialService.ts"; |
| 79 | import { respondToAuthError } from "./auth/http.ts"; | 88 | import { respondToAuthError } from "./auth/http.ts"; |
| 80 | 89 | ||
| 90 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); | ||
| 91 | |||
| 81 | function isThreadDetailEvent(event: OrchestrationEvent): event is Extract< | 92 | function isThreadDetailEvent(event: OrchestrationEvent): event is Extract< |
| 82 | OrchestrationEvent, | 93 | OrchestrationEvent, |
| 83 | { | 94 | { |
| ... | @@ -378,83 +389,86 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => | ... | @@ -378,83 +389,86 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => |
| 378 | readonly scriptId: string; | 389 | readonly scriptId: string; |
| 379 | readonly scriptName: string; | 390 | readonly scriptName: string; |
| 380 | readonly terminalId: string; | 391 | readonly terminalId: string; |
| 381 | }) => { | 392 | }) => |
| 382 | const payload = { | 393 | Effect.gen(function* () { |
| 383 | scriptId: input.scriptId, | 394 | const startedAt = yield* nowIso; |
| 384 | scriptName: input.scriptName, | 395 | const payload = { |
| 385 | terminalId: input.terminalId, | 396 | scriptId: input.scriptId, |
| 386 | worktreePath: input.worktreePath, | 397 | scriptName: input.scriptName, |
| 387 | }; | 398 | terminalId: input.terminalId, |
| 388 | return Effect.all([ | 399 | worktreePath: input.worktreePath, |
| 389 | appendSetupScriptActivity({ | 400 | }; |
| 390 | threadId: command.threadId, | 401 | yield* Effect.all([ |
| 391 | kind: "setup-script.requested", | 402 | appendSetupScriptActivity({ |
| 392 | summary: "Starting setup script", | 403 | threadId: command.threadId, |
| 393 | createdAt: input.requestedAt, | 404 | kind: "setup-script.requested", |
| 394 | payload, | 405 | summary: "Starting setup script", |
| 395 | tone: "info", | 406 | createdAt: input.requestedAt, |
| 396 | }), | 407 | payload, |
| 397 | appendSetupScriptActivity({ | 408 | tone: "info", |
| 398 | threadId: command.threadId, | 409 | }), |
| 399 | kind: "setup-script.started", | 410 | appendSetupScriptActivity({ |
| 400 | summary: "Setup script started", | 411 | threadId: command.threadId, |
| 401 | createdAt: new Date().toISOString(), | 412 | kind: "setup-script.started", |
| 402 | payload, | 413 | summary: "Setup script started", |
| 403 | tone: "info", | 414 | createdAt: startedAt, |
| 404 | }), | 415 | payload, |
| 405 | ]).pipe( | 416 | tone: "info", |
| 406 | Effect.asVoid, | 417 | }), |
| 407 | Effect.catch((error) => | 418 | ]).pipe( |
| 408 | Effect.logWarning( | 419 | Effect.asVoid, |
| 409 | "bootstrap turn start launched setup script but failed to record setup activity", | 420 | Effect.catch((error) => |
| 410 | { | 421 | Effect.logWarning( |
| 411 | threadId: command.threadId, | 422 | "bootstrap turn start launched setup script but failed to record setup activity", |
| 412 | worktreePath: input.worktreePath, | 423 | { |
| 413 | scriptId: input.scriptId, | 424 | threadId: command.threadId, |
| 414 | terminalId: input.terminalId, | 425 | worktreePath: input.worktreePath, |
| 415 | detail: error.message, | 426 | scriptId: input.scriptId, |
| 416 | }, | 427 | terminalId: input.terminalId, |
| 428 | detail: error.message, | ||
| 429 | }, | ||
| 430 | ), | ||
| 417 | ), | 431 | ), |
| 418 | ), | 432 | ); |
| 419 | ); | 433 | }); |
| 420 | }; | ||
| 421 | 434 | ||
| 422 | const runSetupProgram = () => | 435 | const runSetupProgram = () => |
| 423 | bootstrap?.runSetupScript && targetWorktreePath | 436 | Effect.gen(function* () { |
| 424 | ? (() => { | 437 | if (!bootstrap?.runSetupScript || !targetWorktreePath) { |
| 425 | const worktreePath = targetWorktreePath; | 438 | return; |
| 426 | const requestedAt = new Date().toISOString(); | 439 | } |
| 427 | return projectSetupScriptRunner | 440 | const worktreePath = targetWorktreePath; |
| 428 | .runForThread({ | 441 | const requestedAt = yield* nowIso; |
| 429 | threadId: command.threadId, | 442 | yield* projectSetupScriptRunner |
| 430 | ...(targetProjectId ? { projectId: targetProjectId } : {}), | 443 | .runForThread({ |
| 431 | ...(targetProjectCwd ? { projectCwd: targetProjectCwd } : {}), | 444 | threadId: command.threadId, |
| 432 | worktreePath, | 445 | ...(targetProjectId ? { projectId: targetProjectId } : {}), |
| 433 | }) | 446 | ...(targetProjectCwd ? { projectCwd: targetProjectCwd } : {}), |
| 434 | .pipe( | 447 | worktreePath, |
| 435 | Effect.matchEffect({ | 448 | }) |
| 436 | onFailure: (error) => | 449 | .pipe( |
| 437 | recordSetupScriptLaunchFailure({ | 450 | Effect.matchEffect({ |
| 438 | error, | 451 | onFailure: (error) => |
| 439 | requestedAt, | 452 | recordSetupScriptLaunchFailure({ |
| 440 | worktreePath, | 453 | error, |
| 441 | }), | 454 | requestedAt, |
| 442 | onSuccess: (setupResult) => { | 455 | worktreePath, |
| 443 | if (setupResult.status !== "started") { | ||
| 444 | return Effect.void; | ||
| 445 | } | ||
| 446 | return recordSetupScriptStarted({ | ||
| 447 | requestedAt, | ||
| 448 | worktreePath, | ||
| 449 | scriptId: setupResult.scriptId, | ||
| 450 | scriptName: setupResult.scriptName, | ||
| 451 | terminalId: setupResult.terminalId, | ||
| 452 | }); | ||
| 453 | }, | ||
| 454 | }), | 456 | }), |
| 455 | ); | 457 | onSuccess: (setupResult) => { |
| 456 | })() | 458 | if (setupResult.status !== "started") { |
| 457 | : Effect.void; | 459 | return Effect.void; |
| 460 | } | ||
| 461 | return recordSetupScriptStarted({ | ||
| 462 | requestedAt, | ||
| 463 | worktreePath, | ||
| 464 | scriptId: setupResult.scriptId, | ||
| 465 | scriptName: setupResult.scriptName, | ||
| 466 | terminalId: setupResult.terminalId, | ||
| 467 | }); | ||
| 468 | }, | ||
| 469 | }), | ||
| 470 | ); | ||
| 471 | }); | ||
| 458 | 472 | ||
| 459 | const bootstrapProgram = Effect.gen(function* () { | 473 | const bootstrapProgram = Effect.gen(function* () { |
| 460 | if (bootstrap?.createThread) { | 474 | if (bootstrap?.createThread) { |
| ... | @@ -597,7 +611,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => | ... | @@ -597,7 +611,7 @@ const makeWsRpcLayer = (currentSessionId: AuthSessionId) => |
| 597 | `session-stop-for-archive:${normalizedCommand.commandId}`, | 611 | `session-stop-for-archive:${normalizedCommand.commandId}`, |
| 598 | ), | 612 | ), |
| 599 | threadId: normalizedCommand.threadId, | 613 | threadId: normalizedCommand.threadId, |
| 600 | createdAt: new Date().toISOString(), | 614 | createdAt: yield* nowIso, |
| 601 | }); | 615 | }); |
| 602 | 616 | ||
| 603 | yield* dispatchNormalizedCommand(stopCommand); | 617 | yield* dispatchNormalizedCommand(stopCommand); |
apps/server/tsconfig.json+1-15| ... | @@ -3,21 +3,7 @@ | ... | @@ -3,21 +3,7 @@ |
| 3 | "compilerOptions": { | 3 | "compilerOptions": { |
| 4 | "composite": true, | 4 | "composite": true, |
| 5 | "types": ["node", "bun"], | 5 | "types": ["node", "bun"], |
| 6 | "lib": ["ESNext", "esnext.disposable"], | 6 | "lib": ["ESNext", "esnext.disposable"] |
| 7 | "plugins": [ | ||
| 8 | { | ||
| 9 | "name": "@effect/language-service", | ||
| 10 | "namespaceImportPackages": ["@effect/platform-node"], | ||
| 11 | "diagnosticSeverity": { | ||
| 12 | "importFromBarrel": "error", | ||
| 13 | "anyUnknownInErrorContext": "warning", | ||
| 14 | "instanceOfSchema": "warning", | ||
| 15 | "deterministicKeys": "warning", | ||
| 16 | "preferSchemaOverJson": "off", | ||
| 17 | "globalErrorInEffectFailure": "off" | ||
| 18 | } | ||
| 19 | } | ||
| 20 | ] | ||
| 21 | }, | 7 | }, |
| 22 | "include": ["src", "tsdown.config.ts", "scripts", "integration", "../../scripts/lib"] | 8 | "include": ["src", "tsdown.config.ts", "scripts", "integration", "../../scripts/lib"] |
| 23 | } | 9 | } |
apps/web/src/components/BranchToolbar.logic.ts+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import type { EnvironmentId, VcsRef, ProjectId } from "@t3tools/contracts"; | 1 | import type { EnvironmentId, VcsRef, ProjectId } from "@t3tools/contracts"; |
| 2 | import { Schema } from "effect"; | 2 | import * as Schema from "effect/Schema"; |
| 3 | export { | 3 | export { |
| 4 | dedupeRemoteBranchesWithLocalMatches, | 4 | dedupeRemoteBranchesWithLocalMatches, |
| 5 | deriveLocalBranchNameFromRemoteRef, | 5 | deriveLocalBranchNameFromRemoteRef, |
apps/web/src/components/ChatView.browser.tsx+1-1| ... | @@ -22,7 +22,7 @@ import { | ... | @@ -22,7 +22,7 @@ import { |
| 22 | import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime"; | 22 | import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime"; |
| 23 | import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model"; | 23 | import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model"; |
| 24 | import { RouterProvider, createMemoryHistory } from "@tanstack/react-router"; | 24 | import { RouterProvider, createMemoryHistory } from "@tanstack/react-router"; |
| 25 | import { Option } from "effect"; | 25 | import * as Option from "effect/Option"; |
| 26 | import { HttpResponse, http, ws } from "msw"; | 26 | import { HttpResponse, http, ws } from "msw"; |
| 27 | import { setupWorker } from "msw/browser"; | 27 | import { setupWorker } from "msw/browser"; |
| 28 | import { page } from "vitest/browser"; | 28 | import { page } from "vitest/browser"; |
apps/web/src/components/ChatView.logic.ts+1-1| ... | @@ -10,7 +10,7 @@ import { | ... | @@ -10,7 +10,7 @@ import { |
| 10 | } from "@t3tools/contracts"; | 10 | } from "@t3tools/contracts"; |
| 11 | import { type ChatMessage, type SessionPhase, type Thread, type ThreadSession } from "../types"; | 11 | import { type ChatMessage, type SessionPhase, type Thread, type ThreadSession } from "../types"; |
| 12 | import { type ComposerImageAttachment, type DraftThreadState } from "../composerDraftStore"; | 12 | import { type ComposerImageAttachment, type DraftThreadState } from "../composerDraftStore"; |
| 13 | import { Schema } from "effect"; | 13 | import * as Schema from "effect/Schema"; |
| 14 | import { selectThreadByRef, useStore } from "../store"; | 14 | import { selectThreadByRef, useStore } from "../store"; |
| 15 | import { | 15 | import { |
| 16 | filterTerminalContextsWithText, | 16 | filterTerminalContextsWithText, |
apps/web/src/components/CommandPalette.tsx+1-1| ... | @@ -13,7 +13,7 @@ import { | ... | @@ -13,7 +13,7 @@ import { |
| 13 | } from "@t3tools/contracts"; | 13 | } from "@t3tools/contracts"; |
| 14 | import { useQuery, useQueryClient } from "@tanstack/react-query"; | 14 | import { useQuery, useQueryClient } from "@tanstack/react-query"; |
| 15 | import { useNavigate, useParams } from "@tanstack/react-router"; | 15 | import { useNavigate, useParams } from "@tanstack/react-router"; |
| 16 | import { Option } from "effect"; | 16 | import * as Option from "effect/Option"; |
| 17 | import { | 17 | import { |
| 18 | ArrowDownIcon, | 18 | ArrowDownIcon, |
| 19 | ArrowLeftIcon, | 19 | ArrowLeftIcon, |
apps/web/src/components/GitActionsControl.tsx+1-1| ... | @@ -12,7 +12,7 @@ import type { | ... | @@ -12,7 +12,7 @@ import type { |
| 12 | } from "@t3tools/contracts"; | 12 | } from "@t3tools/contracts"; |
| 13 | import { useIsMutating, useMutation, useQueryClient } from "@tanstack/react-query"; | 13 | import { useIsMutating, useMutation, useQueryClient } from "@tanstack/react-query"; |
| 14 | import { useNavigate } from "@tanstack/react-router"; | 14 | import { useNavigate } from "@tanstack/react-router"; |
| 15 | import { Option } from "effect"; | 15 | import * as Option from "effect/Option"; |
| 16 | import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from "react"; | 16 | import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from "react"; |
| 17 | import { flushSync } from "react-dom"; | 17 | import { flushSync } from "react-dom"; |
| 18 | import { | 18 | import { |
apps/web/src/components/settings/ConnectionsSettings.tsx+1-1| ... | @@ -17,7 +17,7 @@ import { | ... | @@ -17,7 +17,7 @@ import { |
| 17 | type DesktopServerExposureState, | 17 | type DesktopServerExposureState, |
| 18 | type EnvironmentId, | 18 | type EnvironmentId, |
| 19 | } from "@t3tools/contracts"; | 19 | } from "@t3tools/contracts"; |
| 20 | import { DateTime } from "effect"; | 20 | import * as DateTime from "effect/DateTime"; |
| 21 | 21 | ||
| 22 | import { useCopyToClipboard } from "../../hooks/useCopyToClipboard"; | 22 | import { useCopyToClipboard } from "../../hooks/useCopyToClipboard"; |
| 23 | import { cn } from "../../lib/utils"; | 23 | import { cn } from "../../lib/utils"; |
apps/web/src/components/settings/DiagnosticsSettings.tsx+2-1| ... | @@ -9,7 +9,8 @@ import { | ... | @@ -9,7 +9,8 @@ import { |
| 9 | } from "lucide-react"; | 9 | } from "lucide-react"; |
| 10 | import { useCallback, useMemo, useState, type ReactNode } from "react"; | 10 | import { useCallback, useMemo, useState, type ReactNode } from "react"; |
| 11 | import type { ServerProcessDiagnosticsEntry, ServerProcessSignal } from "@t3tools/contracts"; | 11 | import type { ServerProcessDiagnosticsEntry, ServerProcessSignal } from "@t3tools/contracts"; |
| 12 | import { DateTime, Option } from "effect"; | 12 | import * as DateTime from "effect/DateTime"; |
| 13 | import * as Option from "effect/Option"; | ||
| 13 | 14 | ||
| 14 | import { ensureLocalApi } from "../../localApi"; | 15 | import { ensureLocalApi } from "../../localApi"; |
| 15 | import { cn } from "../../lib/utils"; | 16 | import { cn } from "../../lib/utils"; |
apps/web/src/components/settings/ProviderSettingsForm.tsx+1-1| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | "use client"; | 1 | "use client"; |
| 2 | 2 | ||
| 3 | import { useMemo, type ReactNode } from "react"; | 3 | import { useMemo, type ReactNode } from "react"; |
| 4 | import { Schema } from "effect"; | 4 | import * as Schema from "effect/Schema"; |
| 5 | import type { | 5 | import type { |
| 6 | ProviderSettingsFormAnnotation, | 6 | ProviderSettingsFormAnnotation, |
| 7 | ProviderSettingsFormControl, | 7 | ProviderSettingsFormControl, |
apps/web/src/components/settings/SettingsPanels.browser.tsx+3-2| ... | @@ -16,7 +16,8 @@ import { | ... | @@ -16,7 +16,8 @@ import { |
| 16 | type ServerProvider, | 16 | type ServerProvider, |
| 17 | type SourceControlDiscoveryResult, | 17 | type SourceControlDiscoveryResult, |
| 18 | } from "@t3tools/contracts"; | 18 | } from "@t3tools/contracts"; |
| 19 | import { DateTime, Option } from "effect"; | 19 | import * as DateTime from "effect/DateTime"; |
| 20 | import * as Option from "effect/Option"; | ||
| 20 | import { page } from "vitest/browser"; | 21 | import { page } from "vitest/browser"; |
| 21 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | 22 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
| 22 | import { render } from "vitest-browser-react"; | 23 | import { render } from "vitest-browser-react"; |
| ... | @@ -261,7 +262,7 @@ function createOutdatedProvider(driver: string): ServerProvider { | ... | @@ -261,7 +262,7 @@ function createOutdatedProvider(driver: string): ServerProvider { |
| 261 | } | 262 | } |
| 262 | 263 | ||
| 263 | function makeUtc(value: string) { | 264 | function makeUtc(value: string) { |
| 264 | return DateTime.makeUnsafe(Date.parse(value)); | 265 | return DateTime.makeUnsafe(value); |
| 265 | } | 266 | } |
| 266 | 267 | ||
| 267 | function makePairingLink(input: { | 268 | function makePairingLink(input: { |
apps/web/src/components/settings/SettingsPanels.tsx+1-1| ... | @@ -14,7 +14,7 @@ import { | ... | @@ -14,7 +14,7 @@ import { |
| 14 | import { scopeThreadRef } from "@t3tools/client-runtime"; | 14 | import { scopeThreadRef } from "@t3tools/client-runtime"; |
| 15 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; | 15 | import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; |
| 16 | import { createModelSelection } from "@t3tools/shared/model"; | 16 | import { createModelSelection } from "@t3tools/shared/model"; |
| 17 | import { Equal } from "effect"; | 17 | import * as Equal from "effect/Equal"; |
| 18 | import { APP_VERSION } from "../../branding"; | 18 | import { APP_VERSION } from "../../branding"; |
| 19 | import { | 19 | import { |
| 20 | canCheckForUpdate, | 20 | canCheckForUpdate, |
apps/web/src/components/settings/SourceControlSettings.tsx+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import { GitPullRequestIcon, RefreshCwIcon } from "lucide-react"; | 1 | import { GitPullRequestIcon, RefreshCwIcon } from "lucide-react"; |
| 2 | import { Option } from "effect"; | 2 | import * as Option from "effect/Option"; |
| 3 | import { type ReactNode } from "react"; | 3 | import { type ReactNode } from "react"; |
| 4 | import type { | 4 | import type { |
| 5 | SourceControlProviderKind, | 5 | SourceControlProviderKind, |
apps/web/src/components/settings/providerDriverMeta.ts+1-1| ... | @@ -5,7 +5,7 @@ import { | ... | @@ -5,7 +5,7 @@ import { |
| 5 | OpenCodeSettings, | 5 | OpenCodeSettings, |
| 6 | ProviderDriverKind, | 6 | ProviderDriverKind, |
| 7 | } from "@t3tools/contracts"; | 7 | } from "@t3tools/contracts"; |
| 8 | import type { Schema } from "effect"; | 8 | import type * as Schema from "effect/Schema"; |
| 9 | import { ClaudeAI, CursorIcon, type Icon, OpenAI, OpenCodeIcon } from "../Icons"; | 9 | import { ClaudeAI, CursorIcon, type Icon, OpenAI, OpenCodeIcon } from "../Icons"; |
| 10 | 10 | ||
| 11 | type ProviderSettingsSchema = { | 11 | type ProviderSettingsSchema = { |
apps/web/src/components/ui/sidebar.tsx+1-1| ... | @@ -19,7 +19,7 @@ import { Skeleton } from "~/components/ui/skeleton"; | ... | @@ -19,7 +19,7 @@ import { Skeleton } from "~/components/ui/skeleton"; |
| 19 | import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; | 19 | import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; |
| 20 | import { useIsMobile } from "~/hooks/useMediaQuery"; | 20 | import { useIsMobile } from "~/hooks/useMediaQuery"; |
| 21 | import { getLocalStorageItem, setLocalStorageItem } from "~/hooks/useLocalStorage"; | 21 | import { getLocalStorageItem, setLocalStorageItem } from "~/hooks/useLocalStorage"; |
| 22 | import { Schema } from "effect"; | 22 | import * as Schema from "effect/Schema"; |
| 23 | 23 | ||
| 24 | const SIDEBAR_COOKIE_NAME = "sidebar_state"; | 24 | const SIDEBAR_COOKIE_NAME = "sidebar_state"; |
| 25 | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; | 25 | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; |
apps/web/src/environments/primary/auth.ts+2-1| ... | @@ -16,7 +16,8 @@ import { | ... | @@ -16,7 +16,8 @@ import { |
| 16 | } from "../../pairingUrl"; | 16 | } from "../../pairingUrl"; |
| 17 | 17 | ||
| 18 | import { resolvePrimaryEnvironmentHttpUrl } from "./target"; | 18 | import { resolvePrimaryEnvironmentHttpUrl } from "./target"; |
| 19 | import { Data, Predicate } from "effect"; | 19 | import * as Data from "effect/Data"; |
| 20 | import * as Predicate from "effect/Predicate"; | ||
| 20 | 21 | ||
| 21 | export class BootstrapHttpError extends Data.TaggedError("BootstrapHttpError")<{ | 22 | export class BootstrapHttpError extends Data.TaggedError("BootstrapHttpError")<{ |
| 22 | readonly message: string; | 23 | readonly message: string; |
apps/web/src/hooks/useSettings.ts+1-1| ... | @@ -19,7 +19,7 @@ import { | ... | @@ -19,7 +19,7 @@ import { |
| 19 | UnifiedSettings, | 19 | UnifiedSettings, |
| 20 | } from "@t3tools/contracts/settings"; | 20 | } from "@t3tools/contracts/settings"; |
| 21 | import { ensureLocalApi } from "~/localApi"; | 21 | import { ensureLocalApi } from "~/localApi"; |
| 22 | import { Struct } from "effect"; | 22 | import * as Struct from "effect/Struct"; |
| 23 | import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; | 23 | import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; |
| 24 | import { applySettingsUpdated, getServerConfig, useServerSettings } from "~/rpc/serverState"; | 24 | import { applySettingsUpdated, getServerConfig, useServerSettings } from "~/rpc/serverState"; |
| 25 | 25 |
apps/web/src/lib/gitStatusState.ts+1-1| ... | @@ -4,7 +4,7 @@ import { | ... | @@ -4,7 +4,7 @@ import { |
| 4 | type GitManagerServiceError, | 4 | type GitManagerServiceError, |
| 5 | type VcsStatusResult, | 5 | type VcsStatusResult, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Cause } from "effect"; | 7 | import * as Cause from "effect/Cause"; |
| 8 | import { Atom } from "effect/unstable/reactivity"; | 8 | import { Atom } from "effect/unstable/reactivity"; |
| 9 | import { useEffect } from "react"; | 9 | import { useEffect } from "react"; |
| 10 | 10 |
apps/web/src/lib/processDiagnosticsState.ts+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import { useAtomValue } from "@effect/atom-react"; | 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import type { ServerProcessDiagnosticsResult } from "@t3tools/contracts"; | 2 | import type { ServerProcessDiagnosticsResult } from "@t3tools/contracts"; |
| 3 | import { Cause, Effect, Option } from "effect"; | 3 | import * as Cause from "effect/Cause"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 4 | import { AsyncResult, Atom } from "effect/unstable/reactivity"; | 6 | import { AsyncResult, Atom } from "effect/unstable/reactivity"; |
| 5 | import { useCallback } from "react"; | 7 | import { useCallback } from "react"; |
| 6 | 8 |
apps/web/src/lib/projectScriptKeybindings.ts+1-1| ... | @@ -4,7 +4,7 @@ import { | ... | @@ -4,7 +4,7 @@ import { |
| 4 | type KeybindingRule, | 4 | type KeybindingRule, |
| 5 | type ResolvedKeybindingsConfig, | 5 | type ResolvedKeybindingsConfig, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Schema } from "effect"; | 7 | import * as Schema from "effect/Schema"; |
| 8 | 8 | ||
| 9 | export const PROJECT_SCRIPT_KEYBINDING_INVALID_MESSAGE = "Invalid keybinding."; | 9 | export const PROJECT_SCRIPT_KEYBINDING_INVALID_MESSAGE = "Invalid keybinding."; |
| 10 | 10 |
apps/web/src/lib/providerReactQuery.ts+2-1| ... | @@ -5,7 +5,8 @@ import { | ... | @@ -5,7 +5,8 @@ import { |
| 5 | ThreadId, | 5 | ThreadId, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { queryOptions } from "@tanstack/react-query"; | 7 | import { queryOptions } from "@tanstack/react-query"; |
| 8 | import { Option, Schema } from "effect"; | 8 | import * as Option from "effect/Option"; |
| 9 | import * as Schema from "effect/Schema"; | ||
| 9 | import { ensureEnvironmentApi } from "../environmentApi"; | 10 | import { ensureEnvironmentApi } from "../environmentApi"; |
| 10 | 11 | ||
| 11 | interface CheckpointDiffQueryInput { | 12 | interface CheckpointDiffQueryInput { |
apps/web/src/lib/sourceControlDiscoveryState.ts+1-1| ... | @@ -7,7 +7,7 @@ import { | ... | @@ -7,7 +7,7 @@ import { |
| 7 | sourceControlDiscoveryStateAtom, | 7 | sourceControlDiscoveryStateAtom, |
| 8 | } from "@t3tools/client-runtime"; | 8 | } from "@t3tools/client-runtime"; |
| 9 | import { EnvironmentId, type SourceControlDiscoveryResult } from "@t3tools/contracts"; | 9 | import { EnvironmentId, type SourceControlDiscoveryResult } from "@t3tools/contracts"; |
| 10 | import { Effect } from "effect"; | 10 | import * as Effect from "effect/Effect"; |
| 11 | import { Atom } from "effect/unstable/reactivity"; | 11 | import { Atom } from "effect/unstable/reactivity"; |
| 12 | 12 | ||
| 13 | import { readPrimaryEnvironmentDescriptor } from "../environments/primary"; | 13 | import { readPrimaryEnvironmentDescriptor } from "../environments/primary"; |
apps/web/src/lib/traceDiagnosticsState.ts+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import { useAtomValue } from "@effect/atom-react"; | 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import type { ServerTraceDiagnosticsResult } from "@t3tools/contracts"; | 2 | import type { ServerTraceDiagnosticsResult } from "@t3tools/contracts"; |
| 3 | import { Cause, Effect, Option } from "effect"; | 3 | import * as Cause from "effect/Cause"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 4 | import { AsyncResult, Atom } from "effect/unstable/reactivity"; | 6 | import { AsyncResult, Atom } from "effect/unstable/reactivity"; |
| 5 | import { useCallback } from "react"; | 7 | import { useCallback } from "react"; |
| 6 | 8 |
apps/web/src/observability/clientTracing.ts+5-1| ... | @@ -1,4 +1,8 @@ | ... | @@ -1,4 +1,8 @@ |
| 1 | import { Exit, Layer, ManagedRuntime, Scope, Tracer } from "effect"; | 1 | import * as Exit from "effect/Exit"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as ManagedRuntime from "effect/ManagedRuntime"; | ||
| 4 | import * as Scope from "effect/Scope"; | ||
| 5 | import * as Tracer from "effect/Tracer"; | ||
| 2 | import { FetchHttpClient, HttpClient } from "effect/unstable/http"; | 6 | import { FetchHttpClient, HttpClient } from "effect/unstable/http"; |
| 3 | import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; | 7 | import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; |
| 4 | 8 |
apps/web/src/projectScripts.ts+1-1| ... | @@ -4,7 +4,7 @@ import { | ... | @@ -4,7 +4,7 @@ import { |
| 4 | type KeybindingCommand, | 4 | type KeybindingCommand, |
| 5 | type ProjectScript, | 5 | type ProjectScript, |
| 6 | } from "@t3tools/contracts"; | 6 | } from "@t3tools/contracts"; |
| 7 | import { Schema } from "effect"; | 7 | import * as Schema from "effect/Schema"; |
| 8 | 8 | ||
| 9 | function normalizeScriptId(value: string): string { | 9 | function normalizeScriptId(value: string): string { |
| 10 | const cleaned = value | 10 | const cleaned = value |
apps/web/src/rpc/protocol.ts+4-1| ... | @@ -1,5 +1,8 @@ | ... | @@ -1,5 +1,8 @@ |
| 1 | import { WsRpcGroup } from "@t3tools/contracts"; | 1 | import { WsRpcGroup } from "@t3tools/contracts"; |
| 2 | import { Duration, Effect, Layer, Schedule } from "effect"; | 2 | import * as Duration from "effect/Duration"; |
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import * as Schedule from "effect/Schedule"; | ||
| 3 | import { RpcClient, RpcSerialization } from "effect/unstable/rpc"; | 6 | import { RpcClient, RpcSerialization } from "effect/unstable/rpc"; |
| 4 | import * as Socket from "effect/unstable/socket/Socket"; | 7 | import * as Socket from "effect/unstable/socket/Socket"; |
| 5 | 8 |
apps/web/src/rpc/wsRpcClient.ts+2-1| ... | @@ -10,7 +10,8 @@ import { | ... | @@ -10,7 +10,8 @@ import { |
| 10 | WS_METHODS, | 10 | WS_METHODS, |
| 11 | } from "@t3tools/contracts"; | 11 | } from "@t3tools/contracts"; |
| 12 | import { applyGitStatusStreamEvent } from "@t3tools/shared/git"; | 12 | import { applyGitStatusStreamEvent } from "@t3tools/shared/git"; |
| 13 | import { Effect, Stream } from "effect"; | 13 | import * as Effect from "effect/Effect"; |
| 14 | import * as Stream from "effect/Stream"; | ||
| 14 | 15 | ||
| 15 | import { type WsRpcProtocolClient } from "./protocol"; | 16 | import { type WsRpcProtocolClient } from "./protocol"; |
| 16 | import { resetWsReconnectBackoff } from "./wsConnectionState"; | 17 | import { resetWsReconnectBackoff } from "./wsConnectionState"; |
apps/web/src/rpc/wsTransport.test.ts+1-1| ... | @@ -1,5 +1,5 @@ | ... | @@ -1,5 +1,5 @@ |
| 1 | import { DEFAULT_SERVER_SETTINGS, WS_METHODS } from "@t3tools/contracts"; | 1 | import { DEFAULT_SERVER_SETTINGS, WS_METHODS } from "@t3tools/contracts"; |
| 2 | import { Stream } from "effect"; | 2 | import * as Stream from "effect/Stream"; |
| 3 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | 3 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
| 4 | 4 | ||
| 5 | import { | 5 | import { |
apps/web/src/rpc/wsTransport.ts+9-11| ... | @@ -1,14 +1,12 @@ | ... | @@ -1,14 +1,12 @@ |
| 1 | import { | 1 | import * as Cause from "effect/Cause"; |
| 2 | Cause, | 2 | import * as Duration from "effect/Duration"; |
| 3 | Duration, | 3 | import * as Effect from "effect/Effect"; |
| 4 | Effect, | 4 | import * as Exit from "effect/Exit"; |
| 5 | Exit, | 5 | import * as Layer from "effect/Layer"; |
| 6 | Layer, | 6 | import * as ManagedRuntime from "effect/ManagedRuntime"; |
| 7 | ManagedRuntime, | 7 | import * as Option from "effect/Option"; |
| 8 | Option, | 8 | import * as Scope from "effect/Scope"; |
| 9 | Scope, | 9 | import * as Stream from "effect/Stream"; |
| 10 | Stream, | ||
| 11 | } from "effect"; | ||
| 12 | import { RpcClient } from "effect/unstable/rpc"; | 10 | import { RpcClient } from "effect/unstable/rpc"; |
| 13 | 11 | ||
| 14 | import { ClientTracingLive } from "../observability/clientTracing"; | 12 | import { ClientTracingLive } from "../observability/clientTracing"; |
apps/web/src/store.ts+1-1| ... | @@ -20,7 +20,7 @@ import type { | ... | @@ -20,7 +20,7 @@ import type { |
| 20 | } from "@t3tools/contracts"; | 20 | } from "@t3tools/contracts"; |
| 21 | import { isProviderDriverKind, ProviderDriverKind } from "@t3tools/contracts"; | 21 | import { isProviderDriverKind, ProviderDriverKind } from "@t3tools/contracts"; |
| 22 | import type { ThreadId, TurnId } from "@t3tools/contracts"; | 22 | import type { ThreadId, TurnId } from "@t3tools/contracts"; |
| 23 | import { Schema } from "effect"; | 23 | import * as Schema from "effect/Schema"; |
| 24 | import { resolveModelSlugForProvider } from "@t3tools/shared/model"; | 24 | import { resolveModelSlugForProvider } from "@t3tools/shared/model"; |
| 25 | import { create } from "zustand"; | 25 | import { create } from "zustand"; |
| 26 | import { | 26 | import { |
apps/web/test/wsRpcHarness.ts+5-1| ... | @@ -1,5 +1,9 @@ | ... | @@ -1,5 +1,9 @@ |
| 1 | import { Effect, Exit, PubSub, Scope, Stream } from "effect"; | ||
| 2 | import { ORCHESTRATION_WS_METHODS, WS_METHODS, WsRpcGroup } from "@t3tools/contracts"; | 1 | import { ORCHESTRATION_WS_METHODS, WS_METHODS, WsRpcGroup } from "@t3tools/contracts"; |
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | import * as Exit from "effect/Exit"; | ||
| 4 | import * as PubSub from "effect/PubSub"; | ||
| 5 | import * as Scope from "effect/Scope"; | ||
| 6 | import * as Stream from "effect/Stream"; | ||
| 3 | import { RpcMessage, RpcSerialization, RpcServer } from "effect/unstable/rpc"; | 7 | import { RpcMessage, RpcSerialization, RpcServer } from "effect/unstable/rpc"; |
| 4 | 8 | ||
| 5 | type RpcServerInstance = RpcServer.RpcServer<any>; | 9 | type RpcServerInstance = RpcServer.RpcServer<any>; |
apps/web/tsconfig.json+13-3| ... | @@ -15,12 +15,22 @@ | ... | @@ -15,12 +15,22 @@ |
| 15 | "plugins": [ | 15 | "plugins": [ |
| 16 | { | 16 | { |
| 17 | "name": "@effect/language-service", | 17 | "name": "@effect/language-service", |
| 18 | "namespaceImportPackages": ["@effect/platform-node"], | 18 | "namespaceImportPackages": ["@effect/platform-node", "effect"], |
| 19 | "diagnosticSeverity": { | 19 | "diagnosticSeverity": { |
| 20 | "importFromBarrel": "error", | 20 | "importFromBarrel": "error", |
| 21 | "anyUnknownInErrorContext": "error", | 21 | "anyUnknownInErrorContext": "error", |
| 22 | "instanceOfSchema": "warning", | 22 | "unsafeEffectTypeAssertion": "error", |
| 23 | "deterministicKeys": "warning" | 23 | "instanceOfSchema": "error", |
| 24 | "deterministicKeys": "error", | ||
| 25 | "strictEffectProvide": "off", | ||
| 26 | "missingEffectServiceDependency": "error", | ||
| 27 | "leakingRequirements": "error", | ||
| 28 | "globalErrorInEffectCatch": "error", | ||
| 29 | "globalErrorInEffectFailure": "error", | ||
| 30 | "unknownInEffectCatch": "error", | ||
| 31 | "strictBooleanExpressions": "off", | ||
| 32 | "preferSchemaOverJson": "error", | ||
| 33 | "schemaSyncInEffect": "error" | ||
| 24 | } | 34 | } |
| 25 | } | 35 | } |
| 26 | ] | 36 | ] |
bun.lock+1| ... | @@ -201,6 +201,7 @@ | ... | @@ -201,6 +201,7 @@ |
| 201 | }, | 201 | }, |
| 202 | "devDependencies": { | 202 | "devDependencies": { |
| 203 | "@effect/language-service": "catalog:", | 203 | "@effect/language-service": "catalog:", |
| 204 | "@effect/platform-node": "catalog:", | ||
| 204 | "@effect/vitest": "catalog:", | 205 | "@effect/vitest": "catalog:", |
| 205 | "@types/node": "catalog:", | 206 | "@types/node": "catalog:", |
| 206 | "typescript": "catalog:", | 207 | "typescript": "catalog:", |
packages/client-runtime/tsconfig.json+1-14| ... | @@ -1,18 +1,5 @@ | ... | @@ -1,18 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "extends": "../../tsconfig.base.json", | 2 | "extends": "../../tsconfig.base.json", |
| 3 | "compilerOptions": { | 3 | "compilerOptions": {}, |
| 4 | "plugins": [ | ||
| 5 | { | ||
| 6 | "name": "@effect/language-service", | ||
| 7 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 8 | "diagnosticSeverity": { | ||
| 9 | "importFromBarrel": "error", | ||
| 10 | "anyUnknownInErrorContext": "error", | ||
| 11 | "instanceOfSchema": "warning", | ||
| 12 | "deterministicKeys": "warning" | ||
| 13 | } | ||
| 14 | } | ||
| 15 | ] | ||
| 16 | }, | ||
| 17 | "include": ["src"] | 4 | "include": ["src"] |
| 18 | } | 5 | } |
packages/contracts/src/model.ts+2-2| ... | @@ -99,10 +99,10 @@ function coerceLegacyOptionsObjectToArray( | ... | @@ -99,10 +99,10 @@ function coerceLegacyOptionsObjectToArray( |
| 99 | const entries: Array<ProviderOptionSelection> = []; | 99 | const entries: Array<ProviderOptionSelection> = []; |
| 100 | for (const [rawKey, rawValue] of Object.entries(record)) { | 100 | for (const [rawKey, rawValue] of Object.entries(record)) { |
| 101 | const id = typeof rawKey === "string" ? rawKey.trim() : ""; | 101 | const id = typeof rawKey === "string" ? rawKey.trim() : ""; |
| 102 | if (!id) continue; | 102 | if (id.length === 0) continue; |
| 103 | if (typeof rawValue === "string") { | 103 | if (typeof rawValue === "string") { |
| 104 | const trimmed = rawValue.trim(); | 104 | const trimmed = rawValue.trim(); |
| 105 | if (trimmed) entries.push({ id, value: trimmed }); | 105 | if (trimmed.length > 0) entries.push({ id, value: trimmed }); |
| 106 | } else if (typeof rawValue === "boolean") { | 106 | } else if (typeof rawValue === "boolean") { |
| 107 | entries.push({ id, value: rawValue }); | 107 | entries.push({ id, value: rawValue }); |
| 108 | } | 108 | } |
packages/contracts/src/terminal.test.ts+10-6| ... | @@ -164,6 +164,8 @@ describe("TerminalCloseInput", () => { | ... | @@ -164,6 +164,8 @@ describe("TerminalCloseInput", () => { |
| 164 | }); | 164 | }); |
| 165 | 165 | ||
| 166 | describe("TerminalSessionSnapshot", () => { | 166 | describe("TerminalSessionSnapshot", () => { |
| 167 | const isoTimestamp = "2026-01-01T00:00:00.000Z"; | ||
| 168 | |||
| 167 | it("accepts running snapshots", () => { | 169 | it("accepts running snapshots", () => { |
| 168 | expect( | 170 | expect( |
| 169 | decodes(TerminalSessionSnapshot, { | 171 | decodes(TerminalSessionSnapshot, { |
| ... | @@ -176,20 +178,22 @@ describe("TerminalSessionSnapshot", () => { | ... | @@ -176,20 +178,22 @@ describe("TerminalSessionSnapshot", () => { |
| 176 | history: "hello\n", | 178 | history: "hello\n", |
| 177 | exitCode: null, | 179 | exitCode: null, |
| 178 | exitSignal: null, | 180 | exitSignal: null, |
| 179 | updatedAt: new Date().toISOString(), | 181 | updatedAt: isoTimestamp, |
| 180 | }), | 182 | }), |
| 181 | ).toBe(true); | 183 | ).toBe(true); |
| 182 | }); | 184 | }); |
| 183 | }); | 185 | }); |
| 184 | 186 | ||
| 185 | describe("TerminalEvent", () => { | 187 | describe("TerminalEvent", () => { |
| 188 | const isoTimestamp = "2026-01-01T00:00:00.000Z"; | ||
| 189 | |||
| 186 | it("accepts output events", () => { | 190 | it("accepts output events", () => { |
| 187 | expect( | 191 | expect( |
| 188 | decodes(TerminalEvent, { | 192 | decodes(TerminalEvent, { |
| 189 | type: "output", | 193 | type: "output", |
| 190 | threadId: "thread-1", | 194 | threadId: "thread-1", |
| 191 | terminalId: DEFAULT_TERMINAL_ID, | 195 | terminalId: DEFAULT_TERMINAL_ID, |
| 192 | createdAt: new Date().toISOString(), | 196 | createdAt: isoTimestamp, |
| 193 | data: "line\n", | 197 | data: "line\n", |
| 194 | }), | 198 | }), |
| 195 | ).toBe(true); | 199 | ).toBe(true); |
| ... | @@ -201,7 +205,7 @@ describe("TerminalEvent", () => { | ... | @@ -201,7 +205,7 @@ describe("TerminalEvent", () => { |
| 201 | type: "exited", | 205 | type: "exited", |
| 202 | threadId: "thread-1", | 206 | threadId: "thread-1", |
| 203 | terminalId: DEFAULT_TERMINAL_ID, | 207 | terminalId: DEFAULT_TERMINAL_ID, |
| 204 | createdAt: new Date().toISOString(), | 208 | createdAt: isoTimestamp, |
| 205 | exitCode: 0, | 209 | exitCode: 0, |
| 206 | exitSignal: null, | 210 | exitSignal: null, |
| 207 | }), | 211 | }), |
| ... | @@ -214,7 +218,7 @@ describe("TerminalEvent", () => { | ... | @@ -214,7 +218,7 @@ describe("TerminalEvent", () => { |
| 214 | type: "activity", | 218 | type: "activity", |
| 215 | threadId: "thread-1", | 219 | threadId: "thread-1", |
| 216 | terminalId: DEFAULT_TERMINAL_ID, | 220 | terminalId: DEFAULT_TERMINAL_ID, |
| 217 | createdAt: new Date().toISOString(), | 221 | createdAt: isoTimestamp, |
| 218 | hasRunningSubprocess: true, | 222 | hasRunningSubprocess: true, |
| 219 | }), | 223 | }), |
| 220 | ).toBe(true); | 224 | ).toBe(true); |
| ... | @@ -226,7 +230,7 @@ describe("TerminalEvent", () => { | ... | @@ -226,7 +230,7 @@ describe("TerminalEvent", () => { |
| 226 | type: "started", | 230 | type: "started", |
| 227 | threadId: "thread-1", | 231 | threadId: "thread-1", |
| 228 | terminalId: DEFAULT_TERMINAL_ID, | 232 | terminalId: DEFAULT_TERMINAL_ID, |
| 229 | createdAt: new Date().toISOString(), | 233 | createdAt: isoTimestamp, |
| 230 | snapshot: { | 234 | snapshot: { |
| 231 | threadId: "thread-1", | 235 | threadId: "thread-1", |
| 232 | terminalId: DEFAULT_TERMINAL_ID, | 236 | terminalId: DEFAULT_TERMINAL_ID, |
| ... | @@ -237,7 +241,7 @@ describe("TerminalEvent", () => { | ... | @@ -237,7 +241,7 @@ describe("TerminalEvent", () => { |
| 237 | history: "", | 241 | history: "", |
| 238 | exitCode: null, | 242 | exitCode: null, |
| 239 | exitSignal: null, | 243 | exitSignal: null, |
| 240 | updatedAt: new Date().toISOString(), | 244 | updatedAt: isoTimestamp, |
| 241 | }, | 245 | }, |
| 242 | }), | 246 | }), |
| 243 | ).toBe(true); | 247 | ).toBe(true); |
packages/contracts/src/terminal.ts+5-2| ... | @@ -170,10 +170,13 @@ export class TerminalCwdError extends Schema.TaggedErrorClass<TerminalCwdError>( | ... | @@ -170,10 +170,13 @@ export class TerminalCwdError extends Schema.TaggedErrorClass<TerminalCwdError>( |
| 170 | return `Terminal cwd does not exist: ${this.cwd}`; | 170 | return `Terminal cwd does not exist: ${this.cwd}`; |
| 171 | } | 171 | } |
| 172 | const causeMessage = | 172 | const causeMessage = |
| 173 | this.cause && typeof this.cause === "object" && "message" in this.cause | 173 | this.cause !== undefined && |
| 174 | this.cause !== null && | ||
| 175 | typeof this.cause === "object" && | ||
| 176 | "message" in this.cause | ||
| 174 | ? this.cause.message | 177 | ? this.cause.message |
| 175 | : undefined; | 178 | : undefined; |
| 176 | return causeMessage | 179 | return typeof causeMessage === "string" && causeMessage.length > 0 |
| 177 | ? `Failed to access terminal cwd: ${this.cwd} (${causeMessage})` | 180 | ? `Failed to access terminal cwd: ${this.cwd} (${causeMessage})` |
| 178 | : `Failed to access terminal cwd: ${this.cwd}`; | 181 | : `Failed to access terminal cwd: ${this.cwd}`; |
| 179 | } | 182 | } |
packages/contracts/tsconfig.json+1-14| ... | @@ -1,18 +1,5 @@ | ... | @@ -1,18 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "extends": "../../tsconfig.base.json", | 2 | "extends": "../../tsconfig.base.json", |
| 3 | "compilerOptions": { | 3 | "compilerOptions": {}, |
| 4 | "plugins": [ | ||
| 5 | { | ||
| 6 | "name": "@effect/language-service", | ||
| 7 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 8 | "diagnosticSeverity": { | ||
| 9 | "importFromBarrel": "error", | ||
| 10 | "anyUnknownInErrorContext": "error", | ||
| 11 | "instanceOfSchema": "warning", | ||
| 12 | "deterministicKeys": "warning" | ||
| 13 | } | ||
| 14 | } | ||
| 15 | ] | ||
| 16 | }, | ||
| 17 | "include": ["src"] | 4 | "include": ["src"] |
| 18 | } | 5 | } |
packages/effect-acp/test/examples/cursor-acp-client.example.ts+3-3| ... | @@ -48,7 +48,7 @@ const program = Effect.gen(function* () { | ... | @@ -48,7 +48,7 @@ const program = Effect.gen(function* () { |
| 48 | version: "0.0.0", | 48 | version: "0.0.0", |
| 49 | }, | 49 | }, |
| 50 | }); | 50 | }); |
| 51 | yield* Console.log("initialized", JSON.stringify(initialized, null, 4)); | 51 | yield* Console.log("initialized", initialized); |
| 52 | 52 | ||
| 53 | const session = yield* acp.agent.createSession({ | 53 | const session = yield* acp.agent.createSession({ |
| 54 | cwd: process.cwd(), | 54 | cwd: process.cwd(), |
| ... | @@ -61,7 +61,7 @@ const program = Effect.gen(function* () { | ... | @@ -61,7 +61,7 @@ const program = Effect.gen(function* () { |
| 61 | value: "claude-opus-4-6", | 61 | value: "claude-opus-4-6", |
| 62 | }); | 62 | }); |
| 63 | 63 | ||
| 64 | yield* Console.log("config", JSON.stringify(config, null, 4)); | 64 | yield* Console.log("config", config); |
| 65 | 65 | ||
| 66 | const result = yield* acp.agent.prompt({ | 66 | const result = yield* acp.agent.prompt({ |
| 67 | sessionId: session.sessionId, | 67 | sessionId: session.sessionId, |
| ... | @@ -73,7 +73,7 @@ const program = Effect.gen(function* () { | ... | @@ -73,7 +73,7 @@ const program = Effect.gen(function* () { |
| 73 | ], | 73 | ], |
| 74 | }); | 74 | }); |
| 75 | 75 | ||
| 76 | yield* Console.log("prompt result", JSON.stringify(result)); | 76 | yield* Console.log("prompt result", result); |
| 77 | yield* acp.agent.cancel({ sessionId: session.sessionId }); | 77 | yield* acp.agent.cancel({ sessionId: session.sessionId }); |
| 78 | }).pipe(Effect.provide(acpLayer)); | 78 | }).pipe(Effect.provide(acpLayer)); |
| 79 | }); | 79 | }); |
packages/effect-acp/tsconfig.json+1-14| ... | @@ -1,18 +1,5 @@ | ... | @@ -1,18 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "extends": "../../tsconfig.base.json", | 2 | "extends": "../../tsconfig.base.json", |
| 3 | "compilerOptions": { | 3 | "compilerOptions": {}, |
| 4 | "plugins": [ | ||
| 5 | { | ||
| 6 | "name": "@effect/language-service", | ||
| 7 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 8 | "diagnosticSeverity": { | ||
| 9 | "importFromBarrel": "error", | ||
| 10 | "anyUnknownInErrorContext": "error", | ||
| 11 | "instanceOfSchema": "warning", | ||
| 12 | "deterministicKeys": "warning" | ||
| 13 | } | ||
| 14 | } | ||
| 15 | ] | ||
| 16 | }, | ||
| 17 | "include": ["src", "scripts", "test"] | 4 | "include": ["src", "scripts", "test"] |
| 18 | } | 5 | } |
packages/effect-codex-app-server/src/client.ts+20-20| ... | @@ -17,6 +17,8 @@ import { | ... | @@ -17,6 +17,8 @@ import { |
| 17 | } from "./_internal/shared.ts"; | 17 | } from "./_internal/shared.ts"; |
| 18 | import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts"; | 18 | import { makeChildStdio, makeTerminationError } from "./_internal/stdio.ts"; |
| 19 | 19 | ||
| 20 | const DEFAULT_APP_SERVER_FORCE_KILL_AFTER = "2 seconds" as const; | ||
| 21 | |||
| 20 | export interface CodexAppServerClientOptions { | 22 | export interface CodexAppServerClientOptions { |
| 21 | readonly logIncoming?: boolean; | 23 | readonly logIncoming?: boolean; |
| 22 | readonly logOutgoing?: boolean; | 24 | readonly logOutgoing?: boolean; |
| ... | @@ -273,26 +275,24 @@ export const layerCommand = ( | ... | @@ -273,26 +275,24 @@ export const layerCommand = ( |
| 273 | > => | 275 | > => |
| 274 | Layer.effect( | 276 | Layer.effect( |
| 275 | CodexAppServerClient, | 277 | CodexAppServerClient, |
| 276 | Effect.acquireRelease( | 278 | Effect.gen(function* () { |
| 277 | Effect.gen(function* () { | 279 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 278 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 280 | const command = ChildProcess.make(options.command, [...(options.args ?? [])], { |
| 279 | const command = ChildProcess.make(options.command, [...(options.args ?? [])], { | 281 | ...(options.cwd ? { cwd: options.cwd } : {}), |
| 280 | ...(options.cwd ? { cwd: options.cwd } : {}), | 282 | ...(options.env ? { env: { ...process.env, ...options.env } } : {}), |
| 281 | ...(options.env ? { env: { ...process.env, ...options.env } } : {}), | 283 | forceKillAfter: DEFAULT_APP_SERVER_FORCE_KILL_AFTER, |
| 282 | shell: process.platform === "win32", | 284 | shell: process.platform === "win32", |
| 283 | }); | 285 | }); |
| 284 | return yield* spawner.spawn(command).pipe( | 286 | return yield* spawner.spawn(command).pipe( |
| 285 | Effect.mapError( | 287 | Effect.mapError( |
| 286 | (cause) => | 288 | (cause) => |
| 287 | new CodexError.CodexAppServerSpawnError({ | 289 | new CodexError.CodexAppServerSpawnError({ |
| 288 | command: [options.command, ...(options.args ?? [])].join(" "), | 290 | command: [options.command, ...(options.args ?? [])].join(" "), |
| 289 | cause, | 291 | cause, |
| 290 | }), | 292 | }), |
| 291 | ), | 293 | ), |
| 292 | ); | 294 | ); |
| 293 | }), | 295 | }).pipe( |
| 294 | (handle) => handle.kill().pipe(Effect.orDie), | ||
| 295 | ).pipe( | ||
| 296 | Effect.flatMap((handle) => | 296 | Effect.flatMap((handle) => |
| 297 | make(makeChildStdio(handle), options, makeTerminationError(handle)), | 297 | make(makeChildStdio(handle), options, makeTerminationError(handle)), |
| 298 | ), | 298 | ), |
packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts+3-3| ... | @@ -45,17 +45,17 @@ const program = Effect.gen(function* () { | ... | @@ -45,17 +45,17 @@ const program = Effect.gen(function* () { |
| 45 | optOutNotificationMethods: null, | 45 | optOutNotificationMethods: null, |
| 46 | }, | 46 | }, |
| 47 | }); | 47 | }); |
| 48 | yield* Console.log("initialize", JSON.stringify(initialized, null, 2)); | 48 | yield* Console.log("initialize", initialized); |
| 49 | 49 | ||
| 50 | yield* client.notify("initialized", undefined); | 50 | yield* client.notify("initialized", undefined); |
| 51 | 51 | ||
| 52 | const account = yield* client.request("account/read", {}); | 52 | const account = yield* client.request("account/read", {}); |
| 53 | yield* Console.log("account/read", JSON.stringify(account, null, 2)); | 53 | yield* Console.log("account/read", account); |
| 54 | 54 | ||
| 55 | const skills = yield* client.request("skills/list", { | 55 | const skills = yield* client.request("skills/list", { |
| 56 | cwds: [process.cwd()], | 56 | cwds: [process.cwd()], |
| 57 | }); | 57 | }); |
| 58 | yield* Console.log("skills/list", JSON.stringify(skills, null, 2)); | 58 | yield* Console.log("skills/list", skills); |
| 59 | }).pipe(Effect.provide(codexLayer)); | 59 | }).pipe(Effect.provide(codexLayer)); |
| 60 | }); | 60 | }); |
| 61 | 61 |
packages/effect-codex-app-server/tsconfig.json+1-14| ... | @@ -1,18 +1,5 @@ | ... | @@ -1,18 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "extends": "../../tsconfig.base.json", | 2 | "extends": "../../tsconfig.base.json", |
| 3 | "compilerOptions": { | 3 | "compilerOptions": {}, |
| 4 | "plugins": [ | ||
| 5 | { | ||
| 6 | "name": "@effect/language-service", | ||
| 7 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 8 | "diagnosticSeverity": { | ||
| 9 | "importFromBarrel": "error", | ||
| 10 | "anyUnknownInErrorContext": "error", | ||
| 11 | "instanceOfSchema": "warning", | ||
| 12 | "deterministicKeys": "warning" | ||
| 13 | } | ||
| 14 | } | ||
| 15 | ] | ||
| 16 | }, | ||
| 17 | "include": ["src", "scripts", "test"] | 4 | "include": ["src", "scripts", "test"] |
| 18 | } | 5 | } |
packages/shared/package.json+1| ... | @@ -95,6 +95,7 @@ | ... | @@ -95,6 +95,7 @@ |
| 95 | }, | 95 | }, |
| 96 | "devDependencies": { | 96 | "devDependencies": { |
| 97 | "@effect/language-service": "catalog:", | 97 | "@effect/language-service": "catalog:", |
| 98 | "@effect/platform-node": "catalog:", | ||
| 98 | "@effect/vitest": "catalog:", | 99 | "@effect/vitest": "catalog:", |
| 99 | "@types/node": "catalog:", | 100 | "@types/node": "catalog:", |
| 100 | "typescript": "catalog:", | 101 | "typescript": "catalog:", |
packages/shared/src/Net.test.ts+12-10| ... | @@ -3,7 +3,7 @@ import * as NodeNet from "node:net"; | ... | @@ -3,7 +3,7 @@ import * as NodeNet from "node:net"; |
| 3 | import { assert, describe, it } from "@effect/vitest"; | 3 | import { assert, describe, it } from "@effect/vitest"; |
| 4 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | 5 | ||
| 6 | import * as Net from "./Net.ts"; | 6 | import * as NetService from "./Net.ts"; |
| 7 | 7 | ||
| 8 | const closeServer = (server: NodeNet.Server) => | 8 | const closeServer = (server: NodeNet.Server) => |
| 9 | Effect.sync(() => { | 9 | Effect.sync(() => { |
| ... | @@ -19,19 +19,21 @@ const getPort = (server: NodeNet.Server): number => { | ... | @@ -19,19 +19,21 @@ const getPort = (server: NodeNet.Server): number => { |
| 19 | return typeof address === "object" && address !== null ? address.port : 0; | 19 | return typeof address === "object" && address !== null ? address.port : 0; |
| 20 | }; | 20 | }; |
| 21 | 21 | ||
| 22 | const openServer = (host?: string): Effect.Effect<NodeNet.Server, Net.NetError> => | 22 | const openServer = (host?: string): Effect.Effect<NodeNet.Server, NetService.NetError> => |
| 23 | Effect.callback<NodeNet.Server, Net.NetError>((resume) => { | 23 | Effect.callback<NodeNet.Server, NetService.NetError>((resume) => { |
| 24 | const server = NodeNet.createServer(); | 24 | const server = NodeNet.createServer(); |
| 25 | let settled = false; | 25 | let settled = false; |
| 26 | 26 | ||
| 27 | const settle = (effect: Effect.Effect<NodeNet.Server, Net.NetError>) => { | 27 | const settle = (effect: Effect.Effect<NodeNet.Server, NetService.NetError>) => { |
| 28 | if (settled) return; | 28 | if (settled) return; |
| 29 | settled = true; | 29 | settled = true; |
| 30 | resume(effect); | 30 | resume(effect); |
| 31 | }; | 31 | }; |
| 32 | 32 | ||
| 33 | server.once("error", (cause) => { | 33 | server.once("error", (cause) => { |
| 34 | settle(Effect.fail(new Net.NetError({ message: "Failed to open test server", cause }))); | 34 | settle( |
| 35 | Effect.fail(new NetService.NetError({ message: "Failed to open test server", cause })), | ||
| 36 | ); | ||
| 35 | }); | 37 | }); |
| 36 | 38 | ||
| 37 | if (host) { | 39 | if (host) { |
| ... | @@ -43,11 +45,11 @@ const openServer = (host?: string): Effect.Effect<NodeNet.Server, Net.NetError> | ... | @@ -43,11 +45,11 @@ const openServer = (host?: string): Effect.Effect<NodeNet.Server, Net.NetError> |
| 43 | return closeServer(server); | 45 | return closeServer(server); |
| 44 | }); | 46 | }); |
| 45 | 47 | ||
| 46 | it.layer(Net.NetService.layer)("NetService", (it) => { | 48 | it.layer(NetService.layer)("NetService", (it) => { |
| 47 | describe("Net helpers", () => { | 49 | describe("Net helpers", () => { |
| 48 | it.effect("reserveLoopbackPort returns a positive loopback port", () => | 50 | it.effect("reserveLoopbackPort returns a positive loopback port", () => |
| 49 | Effect.gen(function* () { | 51 | Effect.gen(function* () { |
| 50 | const net = yield* Net.NetService; | 52 | const net = yield* NetService.NetService; |
| 51 | const port = yield* net.reserveLoopbackPort(); | 53 | const port = yield* net.reserveLoopbackPort(); |
| 52 | 54 | ||
| 53 | assert.ok(port > 0); | 55 | assert.ok(port > 0); |
| ... | @@ -59,7 +61,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { | ... | @@ -59,7 +61,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { |
| 59 | openServer("127.0.0.1"), | 61 | openServer("127.0.0.1"), |
| 60 | (server) => | 62 | (server) => |
| 61 | Effect.gen(function* () { | 63 | Effect.gen(function* () { |
| 62 | const net = yield* Net.NetService; | 64 | const net = yield* NetService.NetService; |
| 63 | const port = getPort(server); | 65 | const port = getPort(server); |
| 64 | 66 | ||
| 65 | const available = yield* net.isPortAvailableOnLoopback(port); | 67 | const available = yield* net.isPortAvailableOnLoopback(port); |
| ... | @@ -71,7 +73,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { | ... | @@ -71,7 +73,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { |
| 71 | 73 | ||
| 72 | it.effect("findAvailablePort returns preferred when it is free", () => | 74 | it.effect("findAvailablePort returns preferred when it is free", () => |
| 73 | Effect.gen(function* () { | 75 | Effect.gen(function* () { |
| 74 | const net = yield* Net.NetService; | 76 | const net = yield* NetService.NetService; |
| 75 | const preferred = yield* net.reserveLoopbackPort(); | 77 | const preferred = yield* net.reserveLoopbackPort(); |
| 76 | 78 | ||
| 77 | const resolved = yield* net.findAvailablePort(preferred); | 79 | const resolved = yield* net.findAvailablePort(preferred); |
| ... | @@ -84,7 +86,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { | ... | @@ -84,7 +86,7 @@ it.layer(Net.NetService.layer)("NetService", (it) => { |
| 84 | openServer(), | 86 | openServer(), |
| 85 | (server) => | 87 | (server) => |
| 86 | Effect.gen(function* () { | 88 | Effect.gen(function* () { |
| 87 | const net = yield* Net.NetService; | 89 | const net = yield* NetService.NetService; |
| 88 | const preferred = getPort(server); | 90 | const preferred = getPort(server); |
| 89 | 91 | ||
| 90 | const resolved = yield* net.findAvailablePort(preferred); | 92 | const resolved = yield* net.findAvailablePort(preferred); |
packages/shared/src/Net.ts+16-19| ... | @@ -4,22 +4,21 @@ import * as Data from "effect/Data"; | ... | @@ -4,22 +4,21 @@ import * as Data from "effect/Data"; |
| 4 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as Context from "effect/Context"; | 6 | import * as Context from "effect/Context"; |
| 7 | import * as Predicate from "effect/Predicate"; | ||
| 7 | 8 | ||
| 8 | export class NetError extends Data.TaggedError("NetError")<{ | 9 | export class NetError extends Data.TaggedError("NetError")<{ |
| 9 | readonly message: string; | 10 | readonly message: string; |
| 10 | readonly cause?: unknown; | 11 | readonly cause?: unknown; |
| 11 | }> {} | 12 | }> {} |
| 12 | 13 | ||
| 13 | function isErrnoExceptionWithCode(cause: unknown): cause is { | 14 | const isErrnoExceptionWithCode = ( |
| 15 | cause: unknown, | ||
| 16 | ): cause is { | ||
| 14 | readonly code: string; | 17 | readonly code: string; |
| 15 | } { | 18 | } => |
| 16 | return ( | 19 | Predicate.isObject(cause) && |
| 17 | typeof cause === "object" && | 20 | Predicate.hasProperty(cause, "code") && |
| 18 | cause !== null && | 21 | Predicate.isString(cause.code); |
| 19 | "code" in cause && | ||
| 20 | typeof (cause as { readonly code: unknown }).code === "string" | ||
| 21 | ); | ||
| 22 | } | ||
| 23 | 22 | ||
| 24 | const closeServer = (server: NodeNet.Server) => { | 23 | const closeServer = (server: NodeNet.Server) => { |
| 25 | try { | 24 | try { |
| ... | @@ -85,6 +84,13 @@ export interface NetServiceShape { | ... | @@ -85,6 +84,13 @@ export interface NetServiceShape { |
| 85 | readonly findAvailablePort: (preferred: number) => Effect.Effect<number, NetError>; | 84 | readonly findAvailablePort: (preferred: number) => Effect.Effect<number, NetError>; |
| 86 | } | 85 | } |
| 87 | 86 | ||
| 87 | /** | ||
| 88 | * NetService - Service tag for startup networking helpers. | ||
| 89 | */ | ||
| 90 | export class NetService extends Context.Service<NetService, NetServiceShape>()( | ||
| 91 | "@t3tools/shared/Net/NetService", | ||
| 92 | ) {} | ||
| 93 | |||
| 88 | export const make = () => { | 94 | export const make = () => { |
| 89 | /** | 95 | /** |
| 90 | * Returns true when a TCP server can bind to {host, port}. | 96 | * Returns true when a TCP server can bind to {host, port}. |
| ... | @@ -175,13 +181,4 @@ export const make = () => { | ... | @@ -175,13 +181,4 @@ export const make = () => { |
| 175 | } satisfies NetServiceShape; | 181 | } satisfies NetServiceShape; |
| 176 | }; | 182 | }; |
| 177 | 183 | ||
| 178 | /** | 184 | export const layer = Layer.sync(NetService, make); |
| 179 | * NetService - Service tag for startup networking helpers. | ||
| 180 | */ | ||
| 181 | export class NetService extends Context.Service<NetService, NetServiceShape>()( | ||
| 182 | "@t3tools/shared/Net/NetService", | ||
| 183 | ) { | ||
| 184 | static readonly layer = Layer.sync(NetService, make); | ||
| 185 | } | ||
| 186 | |||
| 187 | export const layer = NetService.layer; |
packages/shared/src/logging.ts+1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import fs from "node:fs"; | 2 | import fs from "node:fs"; |
| 2 | import path from "node:path"; | 3 | import path from "node:path"; |
| 3 | 4 |
packages/shared/src/observability.test.ts+108-114| ... | @@ -1,11 +1,10 @@ | ... | @@ -1,11 +1,10 @@ |
| 1 | import fs from "node:fs"; | ||
| 2 | import os from "node:os"; | ||
| 3 | import path from "node:path"; | ||
| 4 | |||
| 5 | import { assert, describe, it } from "@effect/vitest"; | 1 | import { assert, describe, it } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | ||
| 6 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 8 | import * as Logger from "effect/Logger"; | 6 | import * as Logger from "effect/Logger"; |
| 7 | import * as Path from "effect/Path"; | ||
| 9 | import * as References from "effect/References"; | 8 | import * as References from "effect/References"; |
| 10 | import * as Schema from "effect/Schema"; | 9 | import * as Schema from "effect/Schema"; |
| 11 | import * as Tracer from "effect/Tracer"; | 10 | import * as Tracer from "effect/Tracer"; |
| ... | @@ -57,13 +56,14 @@ const makeRecord = (name: string, suffix = ""): TraceRecord => ({ | ... | @@ -57,13 +56,14 @@ const makeRecord = (name: string, suffix = ""): TraceRecord => ({ |
| 57 | }, | 56 | }, |
| 58 | }); | 57 | }); |
| 59 | 58 | ||
| 60 | const readTraceRecords = (tracePath: string) => | 59 | const readTraceRecords = Effect.fn("readTraceRecords")(function* (tracePath: string) { |
| 61 | fs | 60 | const fileSystem = yield* FileSystem.FileSystem; |
| 62 | .readFileSync(tracePath, "utf8") | 61 | return (yield* fileSystem.readFileString(tracePath)) |
| 63 | .trim() | 62 | .trim() |
| 64 | .split("\n") | 63 | .split("\n") |
| 65 | .filter((line) => line.length > 0) | 64 | .filter((line) => line.length > 0) |
| 66 | .map((line) => decodeTraceRecordLine(line)); | 65 | .map((line) => decodeTraceRecordLine(line)); |
| 66 | }); | ||
| 67 | 67 | ||
| 68 | const makeTestLayer = (tracePath: string) => | 68 | const makeTestLayer = (tracePath: string) => |
| 69 | Layer.mergeAll( | 69 | Layer.mergeAll( |
| ... | @@ -106,9 +106,11 @@ describe("observability", () => { | ... | @@ -106,9 +106,11 @@ describe("observability", () => { |
| 106 | }); | 106 | }); |
| 107 | 107 | ||
| 108 | it("normalizes invalid dates without throwing", () => { | 108 | it("normalizes invalid dates without throwing", () => { |
| 109 | // @effect-diagnostics-next-line globalDate:off | ||
| 110 | const invalidDate = new Date("not-a-real-date"); | ||
| 109 | assert.deepStrictEqual( | 111 | assert.deepStrictEqual( |
| 110 | compactTraceAttributes({ | 112 | compactTraceAttributes({ |
| 111 | invalidDate: new Date("not-a-real-date"), | 113 | invalidDate, |
| 112 | }), | 114 | }), |
| 113 | { | 115 | { |
| 114 | invalidDate: "Invalid Date", | 116 | invalidDate: "Invalid Date", |
| ... | @@ -119,122 +121,116 @@ describe("observability", () => { | ... | @@ -119,122 +121,116 @@ describe("observability", () => { |
| 119 | it.effect("flushes buffered trace records on close", () => | 121 | it.effect("flushes buffered trace records on close", () => |
| 120 | Effect.scoped( | 122 | Effect.scoped( |
| 121 | Effect.gen(function* () { | 123 | Effect.gen(function* () { |
| 122 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-trace-sink-")); | 124 | const fileSystem = yield* FileSystem.FileSystem; |
| 125 | const path = yield* Path.Path; | ||
| 126 | const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-trace-sink-" }); | ||
| 123 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); | 127 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); |
| 124 | 128 | ||
| 125 | try { | 129 | const sink = yield* makeTraceSink({ |
| 126 | const sink = yield* makeTraceSink({ | 130 | filePath: tracePath, |
| 127 | filePath: tracePath, | 131 | maxBytes: 1024, |
| 128 | maxBytes: 1024, | 132 | maxFiles: 2, |
| 129 | maxFiles: 2, | 133 | batchWindowMs: 10_000, |
| 130 | batchWindowMs: 10_000, | 134 | }); |
| 131 | }); | 135 | |
| 132 | 136 | sink.push(makeRecord("alpha")); | |
| 133 | sink.push(makeRecord("alpha")); | 137 | sink.push(makeRecord("beta")); |
| 134 | sink.push(makeRecord("beta")); | 138 | yield* sink.close(); |
| 135 | yield* sink.close(); | 139 | |
| 136 | 140 | const lines = yield* readTraceRecords(tracePath); | |
| 137 | const lines = readTraceRecords(tracePath); | 141 | |
| 138 | 142 | assert.equal(lines.length, 2); | |
| 139 | assert.equal(lines.length, 2); | 143 | assert.equal(lines[0]?.name, "alpha"); |
| 140 | assert.equal(lines[0]?.name, "alpha"); | 144 | assert.equal(lines[1]?.name, "beta"); |
| 141 | assert.equal(lines[1]?.name, "beta"); | 145 | }).pipe(Effect.provide(NodeServices.layer)), |
| 142 | } finally { | ||
| 143 | fs.rmSync(tempDir, { recursive: true, force: true }); | ||
| 144 | } | ||
| 145 | }), | ||
| 146 | ), | 146 | ), |
| 147 | ); | 147 | ); |
| 148 | 148 | ||
| 149 | it.effect("rotates the trace file when the configured max size is exceeded", () => | 149 | it.effect("rotates the trace file when the configured max size is exceeded", () => |
| 150 | Effect.scoped( | 150 | Effect.scoped( |
| 151 | Effect.gen(function* () { | 151 | Effect.gen(function* () { |
| 152 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-trace-sink-")); | 152 | const fileSystem = yield* FileSystem.FileSystem; |
| 153 | const path = yield* Path.Path; | ||
| 154 | const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-trace-sink-" }); | ||
| 153 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); | 155 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); |
| 154 | 156 | ||
| 155 | try { | 157 | const sink = yield* makeTraceSink({ |
| 156 | const sink = yield* makeTraceSink({ | 158 | filePath: tracePath, |
| 157 | filePath: tracePath, | 159 | maxBytes: 180, |
| 158 | maxBytes: 180, | 160 | maxFiles: 2, |
| 159 | maxFiles: 2, | 161 | batchWindowMs: 10_000, |
| 160 | batchWindowMs: 10_000, | 162 | }); |
| 161 | }); | 163 | |
| 162 | 164 | for (let index = 0; index < 8; index += 1) { | |
| 163 | for (let index = 0; index < 8; index += 1) { | 165 | sink.push(makeRecord("rotate", `${index}-${"x".repeat(48)}`)); |
| 164 | sink.push(makeRecord("rotate", `${index}-${"x".repeat(48)}`)); | 166 | yield* sink.flush; |
| 165 | yield* sink.flush; | ||
| 166 | } | ||
| 167 | yield* sink.close(); | ||
| 168 | |||
| 169 | const matchingFiles = fs | ||
| 170 | .readdirSync(tempDir) | ||
| 171 | .filter( | ||
| 172 | (entry) => | ||
| 173 | entry === "shared.trace.ndjson" || entry.startsWith("shared.trace.ndjson."), | ||
| 174 | ) | ||
| 175 | .toSorted(); | ||
| 176 | |||
| 177 | assert.equal( | ||
| 178 | matchingFiles.some((entry) => entry === "shared.trace.ndjson.1"), | ||
| 179 | true, | ||
| 180 | ); | ||
| 181 | assert.equal( | ||
| 182 | matchingFiles.some((entry) => entry === "shared.trace.ndjson.3"), | ||
| 183 | false, | ||
| 184 | ); | ||
| 185 | } finally { | ||
| 186 | fs.rmSync(tempDir, { recursive: true, force: true }); | ||
| 187 | } | 167 | } |
| 188 | }), | 168 | yield* sink.close(); |
| 169 | |||
| 170 | const matchingFiles = (yield* fileSystem.readDirectory(tempDir)) | ||
| 171 | .filter( | ||
| 172 | (entry) => entry === "shared.trace.ndjson" || entry.startsWith("shared.trace.ndjson."), | ||
| 173 | ) | ||
| 174 | .toSorted(); | ||
| 175 | |||
| 176 | assert.equal( | ||
| 177 | matchingFiles.some((entry) => entry === "shared.trace.ndjson.1"), | ||
| 178 | true, | ||
| 179 | ); | ||
| 180 | assert.equal( | ||
| 181 | matchingFiles.some((entry) => entry === "shared.trace.ndjson.3"), | ||
| 182 | false, | ||
| 183 | ); | ||
| 184 | }).pipe(Effect.provide(NodeServices.layer)), | ||
| 189 | ), | 185 | ), |
| 190 | ); | 186 | ); |
| 191 | 187 | ||
| 192 | it.effect("drops only the invalid trace record when serialization fails", () => | 188 | it.effect("drops only the invalid trace record when serialization fails", () => |
| 193 | Effect.scoped( | 189 | Effect.scoped( |
| 194 | Effect.gen(function* () { | 190 | Effect.gen(function* () { |
| 195 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-trace-sink-")); | 191 | const fileSystem = yield* FileSystem.FileSystem; |
| 192 | const path = yield* Path.Path; | ||
| 193 | const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-trace-sink-" }); | ||
| 196 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); | 194 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); |
| 197 | 195 | ||
| 198 | try { | 196 | const sink = yield* makeTraceSink({ |
| 199 | const sink = yield* makeTraceSink({ | 197 | filePath: tracePath, |
| 200 | filePath: tracePath, | 198 | maxBytes: 1024, |
| 201 | maxBytes: 1024, | 199 | maxFiles: 2, |
| 202 | maxFiles: 2, | 200 | batchWindowMs: 10_000, |
| 203 | batchWindowMs: 10_000, | 201 | }); |
| 204 | }); | 202 | |
| 205 | 203 | const circular: Array<unknown> = []; | |
| 206 | const circular: Array<unknown> = []; | 204 | circular.push(circular); |
| 207 | circular.push(circular); | 205 | |
| 208 | 206 | sink.push(makeRecord("alpha")); | |
| 209 | sink.push(makeRecord("alpha")); | 207 | sink.push({ |
| 210 | sink.push({ | 208 | ...makeRecord("invalid"), |
| 211 | ...makeRecord("invalid"), | 209 | attributes: { |
| 212 | attributes: { | 210 | circular, |
| 213 | circular, | 211 | }, |
| 214 | }, | 212 | } as TraceRecord); |
| 215 | } as TraceRecord); | 213 | sink.push(makeRecord("beta")); |
| 216 | sink.push(makeRecord("beta")); | 214 | yield* sink.close(); |
| 217 | yield* sink.close(); | 215 | |
| 218 | 216 | const lines = yield* readTraceRecords(tracePath); | |
| 219 | const lines = readTraceRecords(tracePath); | 217 | |
| 220 | 218 | assert.deepStrictEqual( | |
| 221 | assert.deepStrictEqual( | 219 | lines.map((line) => line.name), |
| 222 | lines.map((line) => line.name), | 220 | ["alpha", "beta"], |
| 223 | ["alpha", "beta"], | 221 | ); |
| 224 | ); | 222 | }).pipe(Effect.provide(NodeServices.layer)), |
| 225 | } finally { | ||
| 226 | fs.rmSync(tempDir, { recursive: true, force: true }); | ||
| 227 | } | ||
| 228 | }), | ||
| 229 | ), | 223 | ), |
| 230 | ); | 224 | ); |
| 231 | 225 | ||
| 232 | it.effect("writes nested spans to disk and captures log messages as span events", () => | 226 | it.effect("writes nested spans to disk and captures log messages as span events", () => |
| 233 | Effect.gen(function* () { | 227 | Effect.scoped( |
| 234 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-local-tracer-")); | 228 | Effect.gen(function* () { |
| 235 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); | 229 | const fileSystem = yield* FileSystem.FileSystem; |
| 230 | const path = yield* Path.Path; | ||
| 231 | const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-local-tracer-" }); | ||
| 232 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); | ||
| 236 | 233 | ||
| 237 | try { | ||
| 238 | yield* Effect.scoped( | 234 | yield* Effect.scoped( |
| 239 | Effect.gen(function* () { | 235 | Effect.gen(function* () { |
| 240 | const program = Effect.gen(function* () { | 236 | const program = Effect.gen(function* () { |
| ... | @@ -254,7 +250,7 @@ describe("observability", () => { | ... | @@ -254,7 +250,7 @@ describe("observability", () => { |
| 254 | }), | 250 | }), |
| 255 | ); | 251 | ); |
| 256 | 252 | ||
| 257 | const records = readTraceRecords(tracePath); | 253 | const records = yield* readTraceRecords(tracePath); |
| 258 | assert.equal(records.length, 2); | 254 | assert.equal(records.length, 2); |
| 259 | 255 | ||
| 260 | const parent = records.find((record) => record.name === "parent-span"); | 256 | const parent = records.find((record) => record.name === "parent-span"); |
| ... | @@ -281,18 +277,18 @@ describe("observability", () => { | ... | @@ -281,18 +277,18 @@ describe("observability", () => { |
| 281 | child.events.some((event) => event.attributes["effect.logLevel"] === "INFO"), | 277 | child.events.some((event) => event.attributes["effect.logLevel"] === "INFO"), |
| 282 | true, | 278 | true, |
| 283 | ); | 279 | ); |
| 284 | } finally { | 280 | }).pipe(Effect.provide(NodeServices.layer)), |
| 285 | fs.rmSync(tempDir, { recursive: true, force: true }); | 281 | ), |
| 286 | } | ||
| 287 | }), | ||
| 288 | ); | 282 | ); |
| 289 | 283 | ||
| 290 | it.effect("serializes interrupted spans with an interrupted exit status", () => | 284 | it.effect("serializes interrupted spans with an interrupted exit status", () => |
| 291 | Effect.gen(function* () { | 285 | Effect.scoped( |
| 292 | const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-local-tracer-")); | 286 | Effect.gen(function* () { |
| 293 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); | 287 | const fileSystem = yield* FileSystem.FileSystem; |
| 288 | const path = yield* Path.Path; | ||
| 289 | const tempDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-local-tracer-" }); | ||
| 290 | const tracePath = path.join(tempDir, "shared.trace.ndjson"); | ||
| 294 | 291 | ||
| 295 | try { | ||
| 296 | yield* Effect.scoped( | 292 | yield* Effect.scoped( |
| 297 | Effect.exit( | 293 | Effect.exit( |
| 298 | Effect.interrupt.pipe( | 294 | Effect.interrupt.pipe( |
| ... | @@ -302,13 +298,11 @@ describe("observability", () => { | ... | @@ -302,13 +298,11 @@ describe("observability", () => { |
| 302 | ), | 298 | ), |
| 303 | ); | 299 | ); |
| 304 | 300 | ||
| 305 | const records = readTraceRecords(tracePath); | 301 | const records = yield* readTraceRecords(tracePath); |
| 306 | assert.equal(records.length, 1); | 302 | assert.equal(records.length, 1); |
| 307 | assert.equal(records[0]?.name, "interrupt-span"); | 303 | assert.equal(records[0]?.name, "interrupt-span"); |
| 308 | assert.equal(records[0]?.exit?._tag, "Interrupted"); | 304 | assert.equal(records[0]?.exit?._tag, "Interrupted"); |
| 309 | } finally { | 305 | }).pipe(Effect.provide(NodeServices.layer)), |
| 310 | fs.rmSync(tempDir, { recursive: true, force: true }); | 306 | ), |
| 311 | } | ||
| 312 | }), | ||
| 313 | ); | 307 | ); |
| 314 | }); | 308 | }); |
packages/shared/src/schemaJson.ts+18| ... | @@ -84,6 +84,10 @@ export const fromLenientJsonString = new SchemaTransformation.Transformation( | ... | @@ -84,6 +84,10 @@ export const fromLenientJsonString = new SchemaTransformation.Transformation( |
| 84 | SchemaGetter.stringifyJson(), | 84 | SchemaGetter.stringifyJson(), |
| 85 | ); | 85 | ); |
| 86 | 86 | ||
| 87 | export const prettyJsonString = SchemaGetter.parseJson<string>().compose( | ||
| 88 | SchemaGetter.stringifyJson({ space: 2 }), | ||
| 89 | ); | ||
| 90 | |||
| 87 | /** | 91 | /** |
| 88 | * Build a schema that decodes a lenient JSON string into `A`. | 92 | * Build a schema that decodes a lenient JSON string into `A`. |
| 89 | * | 93 | * |
| ... | @@ -92,3 +96,17 @@ export const fromLenientJsonString = new SchemaTransformation.Transformation( | ... | @@ -92,3 +96,17 @@ export const fromLenientJsonString = new SchemaTransformation.Transformation( |
| 92 | */ | 96 | */ |
| 93 | export const fromLenientJson = <S extends Schema.Top>(schema: S) => | 97 | export const fromLenientJson = <S extends Schema.Top>(schema: S) => |
| 94 | Schema.String.pipe(Schema.decodeTo(schema, fromLenientJsonString)); | 98 | Schema.String.pipe(Schema.decodeTo(schema, fromLenientJsonString)); |
| 99 | |||
| 100 | /** | ||
| 101 | * Build a JSON string schema that encodes with stable 2-space formatting. | ||
| 102 | * | ||
| 103 | * Decode behavior matches `Schema.fromJsonString(schema)`. Encode behavior | ||
| 104 | * keeps the transformation schema-based while preserving human-readable JSON. | ||
| 105 | */ | ||
| 106 | export const fromJsonStringPretty = <S extends Schema.Top>(schema: S) => | ||
| 107 | Schema.fromJsonString(schema).pipe( | ||
| 108 | Schema.encode({ | ||
| 109 | decode: prettyJsonString, | ||
| 110 | encode: prettyJsonString, | ||
| 111 | }), | ||
| 112 | ); |
packages/shared/src/shell.ts+1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import * as NodeOS from "node:os"; | 2 | import * as NodeOS from "node:os"; |
| 2 | import { execFileSync } from "node:child_process"; | 3 | import { execFileSync } from "node:child_process"; |
| 3 | import { accessSync, constants, statSync } from "node:fs"; | 4 | import { accessSync, constants, statSync } from "node:fs"; |
packages/shared/tsconfig.json+1-14| ... | @@ -1,18 +1,5 @@ | ... | @@ -1,18 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "extends": "../../tsconfig.base.json", | 2 | "extends": "../../tsconfig.base.json", |
| 3 | "compilerOptions": { | 3 | "compilerOptions": {}, |
| 4 | "plugins": [ | ||
| 5 | { | ||
| 6 | "name": "@effect/language-service", | ||
| 7 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 8 | "diagnosticSeverity": { | ||
| 9 | "importFromBarrel": "error", | ||
| 10 | "anyUnknownInErrorContext": "error", | ||
| 11 | "instanceOfSchema": "warning", | ||
| 12 | "deterministicKeys": "warning" | ||
| 13 | } | ||
| 14 | } | ||
| 15 | ] | ||
| 16 | }, | ||
| 17 | "include": ["src"] | 4 | "include": ["src"] |
| 18 | } | 5 | } |
packages/ssh/src/command.test.ts+1-1| ... | @@ -7,7 +7,7 @@ import * as Layer from "effect/Layer"; | ... | @@ -7,7 +7,7 @@ import * as Layer from "effect/Layer"; |
| 7 | import * as Result from "effect/Result"; | 7 | import * as Result from "effect/Result"; |
| 8 | import * as Sink from "effect/Sink"; | 8 | import * as Sink from "effect/Sink"; |
| 9 | import * as Stream from "effect/Stream"; | 9 | import * as Stream from "effect/Stream"; |
| 10 | import { TestClock } from "effect/testing"; | 10 | import * as TestClock from "effect/testing/TestClock"; |
| 11 | import { ChildProcessSpawner } from "effect/unstable/process"; | 11 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 12 | 12 | ||
| 13 | import { | 13 | import { |
packages/ssh/src/tunnel.test.ts+4-4| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | 1 | import { assert, describe, it } from "@effect/vitest"; |
| 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 2 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 3 | import { NetService } from "@t3tools/shared/Net"; | 3 | import * as NetService from "@t3tools/shared/Net"; |
| 4 | import * as Duration from "effect/Duration"; | 4 | import * as Duration from "effect/Duration"; |
| 5 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Fiber from "effect/Fiber"; | 6 | import * as Fiber from "effect/Fiber"; |
| ... | @@ -8,7 +8,7 @@ import * as Layer from "effect/Layer"; | ... | @@ -8,7 +8,7 @@ import * as Layer from "effect/Layer"; |
| 8 | import * as Result from "effect/Result"; | 8 | import * as Result from "effect/Result"; |
| 9 | import * as Sink from "effect/Sink"; | 9 | import * as Sink from "effect/Sink"; |
| 10 | import * as Stream from "effect/Stream"; | 10 | import * as Stream from "effect/Stream"; |
| 11 | import { TestClock } from "effect/testing"; | 11 | import * as TestClock from "effect/testing/TestClock"; |
| 12 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; | 12 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; |
| 13 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 13 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 14 | 14 | ||
| ... | @@ -74,7 +74,7 @@ const testHttpClient = HttpClient.make((request) => | ... | @@ -74,7 +74,7 @@ const testHttpClient = HttpClient.make((request) => |
| 74 | 74 | ||
| 75 | const hangingHttpClient = HttpClient.make(() => Effect.never); | 75 | const hangingHttpClient = HttpClient.make(() => Effect.never); |
| 76 | 76 | ||
| 77 | const testNetService = NetService.of({ | 77 | const testNetService = NetService.NetService.of({ |
| 78 | canListenOnHost: () => Effect.succeed(true), | 78 | canListenOnHost: () => Effect.succeed(true), |
| 79 | isPortAvailableOnLoopback: () => Effect.succeed(true), | 79 | isPortAvailableOnLoopback: () => Effect.succeed(true), |
| 80 | reserveLoopbackPort: () => Effect.succeed(41_773), | 80 | reserveLoopbackPort: () => Effect.succeed(41_773), |
| ... | @@ -270,7 +270,7 @@ describe("ssh tunnel scripts", () => { | ... | @@ -270,7 +270,7 @@ describe("ssh tunnel scripts", () => { |
| 270 | NodeServices.layer, | 270 | NodeServices.layer, |
| 271 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), | 271 | Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 272 | Layer.succeed(HttpClient.HttpClient, testHttpClient), | 272 | Layer.succeed(HttpClient.HttpClient, testHttpClient), |
| 273 | Layer.succeed(NetService, testNetService), | 273 | Layer.succeed(NetService.NetService, testNetService), |
| 274 | SshPasswordPrompt.disabledLayer, | 274 | SshPasswordPrompt.disabledLayer, |
| 275 | SshEnvironmentManager.layer(), | 275 | SshEnvironmentManager.layer(), |
| 276 | ); | 276 | ); |
packages/ssh/src/tunnel.ts+6-6| ... | @@ -2,7 +2,7 @@ import type { | ... | @@ -2,7 +2,7 @@ import type { |
| 2 | DesktopSshEnvironmentBootstrap, | 2 | DesktopSshEnvironmentBootstrap, |
| 3 | DesktopSshEnvironmentTarget, | 3 | DesktopSshEnvironmentTarget, |
| 4 | } from "@t3tools/contracts"; | 4 | } from "@t3tools/contracts"; |
| 5 | import { type NetError, NetService } from "@t3tools/shared/Net"; | 5 | import * as NetService from "@t3tools/shared/Net"; |
| 6 | import { fromLenientJson } from "@t3tools/shared/schemaJson"; | 6 | import { fromLenientJson } from "@t3tools/shared/schemaJson"; |
| 7 | import * as Context from "effect/Context"; | 7 | import * as Context from "effect/Context"; |
| 8 | import * as Deferred from "effect/Deferred"; | 8 | import * as Deferred from "effect/Deferred"; |
| ... | @@ -82,7 +82,7 @@ type SshEnvironmentEffectContext = | ... | @@ -82,7 +82,7 @@ type SshEnvironmentEffectContext = |
| 82 | | FileSystem.FileSystem | 82 | | FileSystem.FileSystem |
| 83 | | Path.Path | 83 | | Path.Path |
| 84 | | HttpClient.HttpClient | 84 | | HttpClient.HttpClient |
| 85 | | NetService | 85 | | NetService.NetService |
| 86 | | SshPasswordPrompt; | 86 | | SshPasswordPrompt; |
| 87 | 87 | ||
| 88 | type SshEnvironmentEffectError = | 88 | type SshEnvironmentEffectError = |
| ... | @@ -92,7 +92,7 @@ type SshEnvironmentEffectError = | ... | @@ -92,7 +92,7 @@ type SshEnvironmentEffectError = |
| 92 | | SshPairingError | 92 | | SshPairingError |
| 93 | | SshReadinessError | 93 | | SshReadinessError |
| 94 | | SshPasswordPromptError | 94 | | SshPasswordPromptError |
| 95 | | NetError; | 95 | | NetService.NetError; |
| 96 | 96 | ||
| 97 | function makeSshTunnelCancelledError(target: DesktopSshEnvironmentTarget): SshCommandError { | 97 | function makeSshTunnelCancelledError(target: DesktopSshEnvironmentTarget): SshCommandError { |
| 98 | return new SshCommandError({ | 98 | return new SshCommandError({ |
| ... | @@ -903,7 +903,7 @@ export const fetchLoopbackSshJson = Effect.fn("ssh/tunnel.fetchLoopbackSshJson") | ... | @@ -903,7 +903,7 @@ export const fetchLoopbackSshJson = Effect.fn("ssh/tunnel.fetchLoopbackSshJson") |
| 903 | }); | 903 | }); |
| 904 | 904 | ||
| 905 | const reserveLocalTunnelPort = Effect.fn("ssh/tunnel.reserveLocalTunnelPort")(function* () { | 905 | const reserveLocalTunnelPort = Effect.fn("ssh/tunnel.reserveLocalTunnelPort")(function* () { |
| 906 | const net = yield* NetService; | 906 | const net = yield* NetService.NetService; |
| 907 | return yield* net.reserveLoopbackPort(); | 907 | return yield* net.reserveLoopbackPort(); |
| 908 | }); | 908 | }); |
| 909 | 909 | ||
| ... | @@ -923,7 +923,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: | ... | @@ -923,7 +923,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: |
| 923 | | FileSystem.FileSystem | 923 | | FileSystem.FileSystem |
| 924 | | Path.Path | 924 | | Path.Path |
| 925 | | HttpClient.HttpClient | 925 | | HttpClient.HttpClient |
| 926 | | NetService | 926 | | NetService.NetService |
| 927 | | Scope.Scope | 927 | | Scope.Scope |
| 928 | > { | 928 | > { |
| 929 | const hostSpec = yield* buildSshHostSpecEffect(input.resolvedTarget); | 929 | const hostSpec = yield* buildSshHostSpecEffect(input.resolvedTarget); |
| ... | @@ -1076,7 +1076,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: | ... | @@ -1076,7 +1076,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: |
| 1076 | ), | 1076 | ), |
| 1077 | Effect.tapError((cause) => | 1077 | Effect.tapError((cause) => |
| 1078 | Effect.gen(function* () { | 1078 | Effect.gen(function* () { |
| 1079 | const net = yield* NetService; | 1079 | const net = yield* NetService.NetService; |
| 1080 | const processRunningExit = yield* Effect.exit(child.isRunning); | 1080 | const processRunningExit = yield* Effect.exit(child.isRunning); |
| 1081 | const localPortAvailableExit = yield* Effect.exit( | 1081 | const localPortAvailableExit = yield* Effect.exit( |
| 1082 | net.canListenOnHost(input.localPort, "127.0.0.1"), | 1082 | net.canListenOnHost(input.localPort, "127.0.0.1"), |
packages/ssh/tsconfig.json+1-14| ... | @@ -1,18 +1,5 @@ | ... | @@ -1,18 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "extends": "../../tsconfig.base.json", | 2 | "extends": "../../tsconfig.base.json", |
| 3 | "compilerOptions": { | 3 | "compilerOptions": {}, |
| 4 | "plugins": [ | ||
| 5 | { | ||
| 6 | "name": "@effect/language-service", | ||
| 7 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 8 | "diagnosticSeverity": { | ||
| 9 | "importFromBarrel": "error", | ||
| 10 | "anyUnknownInErrorContext": "error", | ||
| 11 | "instanceOfSchema": "warning", | ||
| 12 | "deterministicKeys": "warning" | ||
| 13 | } | ||
| 14 | } | ||
| 15 | ] | ||
| 16 | }, | ||
| 17 | "include": ["src"] | 4 | "include": ["src"] |
| 18 | } | 5 | } |
packages/tailscale/tsconfig.json+1-13| ... | @@ -1,17 +1,5 @@ | ... | @@ -1,17 +1,5 @@ |
| 1 | { | 1 | { |
| 2 | "extends": "../../tsconfig.base.json", | 2 | "extends": "../../tsconfig.base.json", |
| 3 | "compilerOptions": { | 3 | "compilerOptions": {}, |
| 4 | "plugins": [ | ||
| 5 | { | ||
| 6 | "name": "@effect/language-service", | ||
| 7 | "diagnosticSeverity": { | ||
| 8 | "importFromBarrel": "error", | ||
| 9 | "anyUnknownInErrorContext": "warning", | ||
| 10 | "instanceOfSchema": "warning", | ||
| 11 | "deterministicKeys": "warning" | ||
| 12 | } | ||
| 13 | } | ||
| 14 | ] | ||
| 15 | }, | ||
| 16 | "include": ["src"] | 4 | "include": ["src"] |
| 17 | } | 5 | } |
scripts/apply-web-brand-assets.ts+4-1| ... | @@ -2,7 +2,10 @@ | ... | @@ -2,7 +2,10 @@ |
| 2 | 2 | ||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Effect, FileSystem, Option, Path } from "effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Option from "effect/Option"; | ||
| 8 | import * as Path from "effect/Path"; | ||
| 6 | import { Argument, Command } from "effect/unstable/cli"; | 9 | import { Argument, Command } from "effect/unstable/cli"; |
| 7 | import { resolveWebIconOverrides, type WebAssetBrand } from "./lib/brand-assets.ts"; | 10 | import { resolveWebIconOverrides, type WebAssetBrand } from "./lib/brand-assets.ts"; |
| 8 | 11 |
scripts/build-desktop-artifact.test.ts+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { ConfigProvider, Effect, Option } from "effect"; | 3 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 4 | 6 | ||
| 5 | import { | 7 | import { |
| 6 | resolveBuildOptions, | 8 | resolveBuildOptions, |
scripts/build-desktop-artifact.ts+10-12| ... | @@ -10,18 +10,16 @@ import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; | ... | @@ -10,18 +10,16 @@ import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; |
| 10 | 10 | ||
| 11 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 11 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 12 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 12 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 13 | import { | 13 | import * as Config from "effect/Config"; |
| 14 | Config, | 14 | import * as Data from "effect/Data"; |
| 15 | Data, | 15 | import * as Effect from "effect/Effect"; |
| 16 | Effect, | 16 | import * as FileSystem from "effect/FileSystem"; |
| 17 | FileSystem, | 17 | import * as Layer from "effect/Layer"; |
| 18 | Layer, | 18 | import * as Logger from "effect/Logger"; |
| 19 | Logger, | 19 | import * as Option from "effect/Option"; |
| 20 | Option, | 20 | import * as Path from "effect/Path"; |
| 21 | Path, | 21 | import * as Schema from "effect/Schema"; |
| 22 | Schema, | 22 | import * as Stream from "effect/Stream"; |
| 23 | Stream, | ||
| 24 | } from "effect"; | ||
| 25 | import { Command, Flag } from "effect/unstable/cli"; | 23 | import { Command, Flag } from "effect/unstable/cli"; |
| 26 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 24 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 27 | 25 |
scripts/dev-runner.test.ts+2-1| ... | @@ -1,7 +1,8 @@ | ... | @@ -1,7 +1,8 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import * as NodeOS from "node:os"; | 2 | import * as NodeOS from "node:os"; |
| 3 | import { assert, describe, it } from "@effect/vitest"; | 3 | import { assert, describe, it } from "@effect/vitest"; |
| 4 | import { Effect, Path } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Path from "effect/Path"; | ||
| 5 | 6 | ||
| 6 | import { | 7 | import { |
| 7 | checkPortAvailabilityOnHosts, | 8 | checkPortAvailabilityOnHosts, |
scripts/dev-runner.ts+16-8| ... | @@ -4,8 +4,16 @@ import * as NodeOS from "node:os"; | ... | @@ -4,8 +4,16 @@ import * as NodeOS from "node:os"; |
| 4 | 4 | ||
| 5 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 5 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 7 | import { NetService } from "@t3tools/shared/Net"; | 7 | import * as NetService from "@t3tools/shared/Net"; |
| 8 | import { Config, Data, Effect, Hash, Layer, Logger, Option, Path, Schema } from "effect"; | 8 | import * as Config from "effect/Config"; |
| 9 | import * as Data from "effect/Data"; | ||
| 10 | import * as Effect from "effect/Effect"; | ||
| 11 | import * as Hash from "effect/Hash"; | ||
| 12 | import * as Layer from "effect/Layer"; | ||
| 13 | import * as Logger from "effect/Logger"; | ||
| 14 | import * as Option from "effect/Option"; | ||
| 15 | import * as Path from "effect/Path"; | ||
| 16 | import * as Schema from "effect/Schema"; | ||
| 9 | import { Argument, Command, Flag } from "effect/unstable/cli"; | 17 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 10 | import { ChildProcess } from "effect/unstable/process"; | 18 | import { ChildProcess } from "effect/unstable/process"; |
| 11 | 19 | ||
| ... | @@ -238,22 +246,22 @@ export function checkPortAvailabilityOnHosts<R>( | ... | @@ -238,22 +246,22 @@ export function checkPortAvailabilityOnHosts<R>( |
| 238 | }); | 246 | }); |
| 239 | } | 247 | } |
| 240 | 248 | ||
| 241 | const defaultCheckPortAvailability: PortAvailabilityCheck<NetService> = (port) => | 249 | const defaultCheckPortAvailability: PortAvailabilityCheck<NetService.NetService> = (port) => |
| 242 | Effect.gen(function* () { | 250 | Effect.gen(function* () { |
| 243 | const net = yield* NetService; | 251 | const net = yield* NetService.NetService; |
| 244 | return yield* checkPortAvailabilityOnHosts(port, DEV_PORT_PROBE_HOSTS, (candidatePort, host) => | 252 | return yield* checkPortAvailabilityOnHosts(port, DEV_PORT_PROBE_HOSTS, (candidatePort, host) => |
| 245 | net.canListenOnHost(candidatePort, host), | 253 | net.canListenOnHost(candidatePort, host), |
| 246 | ); | 254 | ); |
| 247 | }); | 255 | }); |
| 248 | 256 | ||
| 249 | interface FindFirstAvailableOffsetInput<R = NetService> { | 257 | interface FindFirstAvailableOffsetInput<R = NetService.NetService> { |
| 250 | readonly startOffset: number; | 258 | readonly startOffset: number; |
| 251 | readonly requireServerPort: boolean; | 259 | readonly requireServerPort: boolean; |
| 252 | readonly requireWebPort: boolean; | 260 | readonly requireWebPort: boolean; |
| 253 | readonly checkPortAvailability?: PortAvailabilityCheck<R>; | 261 | readonly checkPortAvailability?: PortAvailabilityCheck<R>; |
| 254 | } | 262 | } |
| 255 | 263 | ||
| 256 | export function findFirstAvailableOffset<R = NetService>({ | 264 | export function findFirstAvailableOffset<R = NetService.NetService>({ |
| 257 | startOffset, | 265 | startOffset, |
| 258 | requireServerPort, | 266 | requireServerPort, |
| 259 | requireWebPort, | 267 | requireWebPort, |
| ... | @@ -300,7 +308,7 @@ export function findFirstAvailableOffset<R = NetService>({ | ... | @@ -300,7 +308,7 @@ export function findFirstAvailableOffset<R = NetService>({ |
| 300 | }); | 308 | }); |
| 301 | } | 309 | } |
| 302 | 310 | ||
| 303 | interface ResolveModePortOffsetsInput<R = NetService> { | 311 | interface ResolveModePortOffsetsInput<R = NetService.NetService> { |
| 304 | readonly mode: DevMode; | 312 | readonly mode: DevMode; |
| 305 | readonly startOffset: number; | 313 | readonly startOffset: number; |
| 306 | readonly hasExplicitServerPort: boolean; | 314 | readonly hasExplicitServerPort: boolean; |
| ... | @@ -308,7 +316,7 @@ interface ResolveModePortOffsetsInput<R = NetService> { | ... | @@ -308,7 +316,7 @@ interface ResolveModePortOffsetsInput<R = NetService> { |
| 308 | readonly checkPortAvailability?: PortAvailabilityCheck<R>; | 316 | readonly checkPortAvailability?: PortAvailabilityCheck<R>; |
| 309 | } | 317 | } |
| 310 | 318 | ||
| 311 | export function resolveModePortOffsets<R = NetService>({ | 319 | export function resolveModePortOffsets<R = NetService.NetService>({ |
| 312 | mode, | 320 | mode, |
| 313 | startOffset, | 321 | startOffset, |
| 314 | hasExplicitServerPort, | 322 | hasExplicitServerPort, |
scripts/merge-update-manifests.test.ts+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, describe, it } from "@effect/vitest"; | 2 | import { assert, describe, it } from "@effect/vitest"; |
| 3 | import { Effect, FileSystem, Path } from "effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as FileSystem from "effect/FileSystem"; | ||
| 5 | import * as Path from "effect/Path"; | ||
| 4 | import { Command, CliError } from "effect/unstable/cli"; | 6 | import { Command, CliError } from "effect/unstable/cli"; |
| 5 | 7 | ||
| 6 | import { | 8 | import { |
scripts/merge-update-manifests.ts+5-1| ... | @@ -2,7 +2,11 @@ | ... | @@ -2,7 +2,11 @@ |
| 2 | 2 | ||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Effect, FileSystem, Option, Path, Schema } from "effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as FileSystem from "effect/FileSystem"; | ||
| 7 | import * as Option from "effect/Option"; | ||
| 8 | import * as Path from "effect/Path"; | ||
| 9 | import * as Schema from "effect/Schema"; | ||
| 6 | import { Argument, Command, Flag } from "effect/unstable/cli"; | 10 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 7 | 11 | ||
| 8 | import { | 12 | import { |
scripts/mock-update-server.test.ts+5-2| ... | @@ -1,7 +1,10 @@ | ... | @@ -1,7 +1,10 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { NodeHttpServer } from "@effect/platform-node"; | 2 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; |
| 3 | import { assert, it } from "@effect/vitest"; | 3 | import { assert, it } from "@effect/vitest"; |
| 4 | import { Effect, FileSystem, Layer, Path } from "effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 7 | import * as Path from "effect/Path"; | ||
| 5 | import { HttpClient, HttpRouter } from "effect/unstable/http"; | 8 | import { HttpClient, HttpRouter } from "effect/unstable/http"; |
| 6 | 9 | ||
| 7 | import { makeMockUpdateRouteLayer } from "./mock-update-server.ts"; | 10 | import { makeMockUpdateRouteLayer } from "./mock-update-server.ts"; |
scripts/mock-update-server.ts+8-2| ... | @@ -1,8 +1,14 @@ | ... | @@ -1,8 +1,14 @@ |
| 1 | // @effect-diagnostics-next-line nodeBuiltinImport:off - NodeHttpServer.layer takes `NodeHttp.createServer` as arg | ||
| 1 | import * as NodeHttp from "node:http"; | 2 | import * as NodeHttp from "node:http"; |
| 2 | 3 | ||
| 3 | import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"; | 4 | import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; |
| 5 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | ||
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Config, Effect, FileSystem, Layer, Path } from "effect"; | 7 | import * as Config from "effect/Config"; |
| 8 | import * as Effect from "effect/Effect"; | ||
| 9 | import * as FileSystem from "effect/FileSystem"; | ||
| 10 | import * as Layer from "effect/Layer"; | ||
| 11 | import * as Path from "effect/Path"; | ||
| 6 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; | 12 | import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; |
| 7 | 13 | ||
| 8 | interface MockUpdateServerConfig { | 14 | interface MockUpdateServerConfig { |
scripts/notify-discord-release.ts+7-1| ... | @@ -2,7 +2,13 @@ | ... | @@ -2,7 +2,13 @@ |
| 2 | 2 | ||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Config, Data, DateTime, Effect, Layer, Logger, Schema } from "effect"; | 5 | import * as Config from "effect/Config"; |
| 6 | import * as Data from "effect/Data"; | ||
| 7 | import * as DateTime from "effect/DateTime"; | ||
| 8 | import * as Effect from "effect/Effect"; | ||
| 9 | import * as Layer from "effect/Layer"; | ||
| 10 | import * as Logger from "effect/Logger"; | ||
| 11 | import * as Schema from "effect/Schema"; | ||
| 6 | import { Argument, Command, Flag } from "effect/unstable/cli"; | 12 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 7 | import { | 13 | import { |
| 8 | FetchHttpClient, | 14 | FetchHttpClient, |
scripts/release-smoke.ts+4-1| ... | @@ -1,3 +1,4 @@ | ... | @@ -1,3 +1,4 @@ |
| 1 | // @effect-diagnostics nodeBuiltinImport:off | ||
| 1 | import { execFileSync } from "node:child_process"; | 2 | import { execFileSync } from "node:child_process"; |
| 2 | import { | 3 | import { |
| 3 | cpSync, | 4 | cpSync, |
| ... | @@ -11,6 +12,8 @@ import { | ... | @@ -11,6 +12,8 @@ import { |
| 11 | import { tmpdir } from "node:os"; | 12 | import { tmpdir } from "node:os"; |
| 12 | import { dirname, join, resolve } from "node:path"; | 13 | import { dirname, join, resolve } from "node:path"; |
| 13 | import { fileURLToPath } from "node:url"; | 14 | import { fileURLToPath } from "node:url"; |
| 15 | import * as Console from "effect/Console"; | ||
| 16 | import * as Effect from "effect/Effect"; | ||
| 14 | 17 | ||
| 15 | const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); | 18 | const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); |
| 16 | 19 | ||
| ... | @@ -389,7 +392,7 @@ try { | ... | @@ -389,7 +392,7 @@ try { |
| 389 | "Windows release smoke unexpectedly removed the x64 builder debug fixture.", | 392 | "Windows release smoke unexpectedly removed the x64 builder debug fixture.", |
| 390 | ); | 393 | ); |
| 391 | 394 | ||
| 392 | console.log("Release smoke checks passed."); | 395 | Effect.runSync(Console.log("Release smoke checks passed.")); |
| 393 | } finally { | 396 | } finally { |
| 394 | rmSync(tempRoot, { recursive: true, force: true }); | 397 | rmSync(tempRoot, { recursive: true, force: true }); |
| 395 | } | 398 | } |
scripts/resolve-nightly-release.ts+8-2| ... | @@ -2,7 +2,13 @@ | ... | @@ -2,7 +2,13 @@ |
| 2 | 2 | ||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Config, Effect, FileSystem, Option, Path, Schema } from "effect"; | 5 | import * as Console from "effect/Console"; |
| 6 | import * as Config from "effect/Config"; | ||
| 7 | import * as Effect from "effect/Effect"; | ||
| 8 | import * as FileSystem from "effect/FileSystem"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 10 | import * as Path from "effect/Path"; | ||
| 11 | import * as Schema from "effect/Schema"; | ||
| 6 | import { Command, Flag } from "effect/unstable/cli"; | 12 | import { Command, Flag } from "effect/unstable/cli"; |
| 7 | 13 | ||
| 8 | interface NightlyReleaseMetadata { | 14 | interface NightlyReleaseMetadata { |
| ... | @@ -93,7 +99,7 @@ const writeOutput = Effect.fn("writeOutput")(function* ( | ... | @@ -93,7 +99,7 @@ const writeOutput = Effect.fn("writeOutput")(function* ( |
| 93 | yield* fs.writeFileString(githubOutputPath, serialized, { flag: "a" }); | 99 | yield* fs.writeFileString(githubOutputPath, serialized, { flag: "a" }); |
| 94 | } else { | 100 | } else { |
| 95 | for (const [key, value] of entries) { | 101 | for (const [key, value] of entries) { |
| 96 | console.log(`${key}=${value}`); | 102 | yield* Console.log(`${key}=${value}`); |
| 97 | } | 103 | } |
| 98 | } | 104 | } |
| 99 | }); | 105 | }); |
scripts/resolve-previous-release-tag.ts+7-1| ... | @@ -2,7 +2,13 @@ | ... | @@ -2,7 +2,13 @@ |
| 2 | 2 | ||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Array, Config, Effect, FileSystem, Schema, Stream, String } from "effect"; | 5 | import * as Array from "effect/Array"; |
| 6 | import * as Config from "effect/Config"; | ||
| 7 | import * as Effect from "effect/Effect"; | ||
| 8 | import * as FileSystem from "effect/FileSystem"; | ||
| 9 | import * as Schema from "effect/Schema"; | ||
| 10 | import * as Stream from "effect/Stream"; | ||
| 11 | import * as String from "effect/String"; | ||
| 6 | import { Command, Flag } from "effect/unstable/cli"; | 12 | import { Command, Flag } from "effect/unstable/cli"; |
| 7 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 13 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| 8 | 14 |
scripts/tsconfig.json+1-6| ... | @@ -3,12 +3,7 @@ | ... | @@ -3,12 +3,7 @@ |
| 3 | "compilerOptions": { | 3 | "compilerOptions": { |
| 4 | "composite": true, | 4 | "composite": true, |
| 5 | "types": ["node"], | 5 | "types": ["node"], |
| 6 | "lib": ["ESNext", "esnext.disposable"], | 6 | "lib": ["ESNext", "esnext.disposable"] |
| 7 | "plugins": [ | ||
| 8 | { | ||
| 9 | "name": "@effect/language-service" | ||
| 10 | } | ||
| 11 | ] | ||
| 12 | }, | 7 | }, |
| 13 | "include": ["**/*.ts"] | 8 | "include": ["**/*.ts"] |
| 14 | } | 9 | } |
scripts/update-release-package-versions.test.ts+11-13| ... | @@ -1,8 +1,14 @@ | ... | @@ -1,8 +1,14 @@ |
| 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 1 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 2 | import { assert, it } from "@effect/vitest"; | 2 | import { assert, it } from "@effect/vitest"; |
| 3 | import { ConfigProvider, Effect, FileSystem, Layer, Path, Schema, SchemaGetter } from "effect"; | 3 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 4 | import * as Effect from "effect/Effect"; | ||
| 5 | import * as FileSystem from "effect/FileSystem"; | ||
| 6 | import * as Layer from "effect/Layer"; | ||
| 7 | import * as Path from "effect/Path"; | ||
| 8 | import * as Schema from "effect/Schema"; | ||
| 4 | import { Command, CliError } from "effect/unstable/cli"; | 9 | import { Command, CliError } from "effect/unstable/cli"; |
| 5 | import * as TestConsole from "effect/testing/TestConsole"; | 10 | import * as TestConsole from "effect/testing/TestConsole"; |
| 11 | import { fromJsonStringPretty } from "@t3tools/shared/schemaJson"; | ||
| 6 | 12 | ||
| 7 | import { | 13 | import { |
| 8 | releasePackageFiles, | 14 | releasePackageFiles, |
| ... | @@ -13,17 +19,9 @@ import { | ... | @@ -13,17 +19,9 @@ import { |
| 13 | const ScriptTestLayer = Layer.mergeAll(NodeServices.layer, TestConsole.layer); | 19 | const ScriptTestLayer = Layer.mergeAll(NodeServices.layer, TestConsole.layer); |
| 14 | const runCli = Command.runWith(updateReleasePackageVersionsCommand, { version: "0.0.0" }); | 20 | const runCli = Command.runWith(updateReleasePackageVersionsCommand, { version: "0.0.0" }); |
| 15 | const PackageJsonSchema = Schema.Record(Schema.String, Schema.Unknown); | 21 | const PackageJsonSchema = Schema.Record(Schema.String, Schema.Unknown); |
| 16 | const PrettyJsonString = SchemaGetter.parseJson<string>().compose( | 22 | const PackageJsonPrettyJson = fromJsonStringPretty(PackageJsonSchema); |
| 17 | SchemaGetter.stringifyJson({ space: 2 }), | 23 | const decodePackageJson = Schema.decodeEffect(PackageJsonPrettyJson); |
| 18 | ); | 24 | const encodePackageJson = Schema.encodeEffect(PackageJsonPrettyJson); |
| 19 | const PackageJsonPrettyJson = Schema.fromJsonString(PackageJsonSchema).pipe( | ||
| 20 | Schema.encode({ | ||
| 21 | decode: PrettyJsonString, | ||
| 22 | encode: PrettyJsonString, | ||
| 23 | }), | ||
| 24 | ); | ||
| 25 | const decodePackageJson = Schema.decodeUnknownEffect(PackageJsonPrettyJson); | ||
| 26 | const encodePackageJson = Schema.encodeSync(PackageJsonPrettyJson); | ||
| 27 | 25 | ||
| 28 | const writePackageJsonFixtures = Effect.fn("writePackageJsonFixtures")(function* ( | 26 | const writePackageJsonFixtures = Effect.fn("writePackageJsonFixtures")(function* ( |
| 29 | rootDir: string, | 27 | rootDir: string, |
| ... | @@ -37,7 +35,7 @@ const writePackageJsonFixtures = Effect.fn("writePackageJsonFixtures")(function* | ... | @@ -37,7 +35,7 @@ const writePackageJsonFixtures = Effect.fn("writePackageJsonFixtures")(function* |
| 37 | yield* fs.makeDirectory(path.dirname(filePath), { recursive: true }); | 35 | yield* fs.makeDirectory(path.dirname(filePath), { recursive: true }); |
| 38 | yield* fs.writeFileString( | 36 | yield* fs.writeFileString( |
| 39 | filePath, | 37 | filePath, |
| 40 | `${encodePackageJson({ | 38 | `${yield* encodePackageJson({ |
| 41 | name: relativePath, | 39 | name: relativePath, |
| 42 | version, | 40 | version, |
| 43 | private: true, | 41 | private: true, |
scripts/update-release-package-versions.ts+9-10| ... | @@ -2,8 +2,15 @@ | ... | @@ -2,8 +2,15 @@ |
| 2 | 2 | ||
| 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { Config, Console, Effect, FileSystem, Option, Path, Schema, SchemaGetter } from "effect"; | 5 | import * as Config from "effect/Config"; |
| 6 | import * as Console from "effect/Console"; | ||
| 7 | import * as Effect from "effect/Effect"; | ||
| 8 | import * as FileSystem from "effect/FileSystem"; | ||
| 9 | import * as Option from "effect/Option"; | ||
| 10 | import * as Path from "effect/Path"; | ||
| 11 | import * as Schema from "effect/Schema"; | ||
| 6 | import { Argument, Command, Flag } from "effect/unstable/cli"; | 12 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 13 | import { fromJsonStringPretty } from "@t3tools/shared/schemaJson"; | ||
| 7 | 14 | ||
| 8 | export const releasePackageFiles = [ | 15 | export const releasePackageFiles = [ |
| 9 | "apps/server/package.json", | 16 | "apps/server/package.json", |
| ... | @@ -17,15 +24,7 @@ interface UpdateReleasePackageVersionsOptions { | ... | @@ -17,15 +24,7 @@ interface UpdateReleasePackageVersionsOptions { |
| 17 | } | 24 | } |
| 18 | 25 | ||
| 19 | const PackageJsonSchema = Schema.Record(Schema.String, Schema.Unknown); | 26 | const PackageJsonSchema = Schema.Record(Schema.String, Schema.Unknown); |
| 20 | const PrettyJsonString = SchemaGetter.parseJson<string>().compose( | 27 | const PackageJsonPrettyJson = fromJsonStringPretty(PackageJsonSchema); |
| 21 | SchemaGetter.stringifyJson({ space: 2 }), | ||
| 22 | ); | ||
| 23 | const PackageJsonPrettyJson = Schema.fromJsonString(PackageJsonSchema).pipe( | ||
| 24 | Schema.encode({ | ||
| 25 | decode: PrettyJsonString, | ||
| 26 | encode: PrettyJsonString, | ||
| 27 | }), | ||
| 28 | ); | ||
| 29 | const decodePackageJson = Schema.decodeUnknownEffect(PackageJsonPrettyJson); | 28 | const decodePackageJson = Schema.decodeUnknownEffect(PackageJsonPrettyJson); |
| 30 | const encodePackageJson = Schema.encodeSync(PackageJsonPrettyJson); | 29 | const encodePackageJson = Schema.encodeSync(PackageJsonPrettyJson); |
| 31 | 30 |
tsconfig.base.json+34-1| ... | @@ -17,6 +17,39 @@ | ... | @@ -17,6 +17,39 @@ |
| 17 | "skipLibCheck": true, | 17 | "skipLibCheck": true, |
| 18 | "allowSyntheticDefaultImports": true, | 18 | "allowSyntheticDefaultImports": true, |
| 19 | "esModuleInterop": true, | 19 | "esModuleInterop": true, |
| 20 | "resolveJsonModule": true | 20 | "resolveJsonModule": true, |
| 21 | "plugins": [ | ||
| 22 | { | ||
| 23 | "name": "@effect/language-service", | ||
| 24 | "namespaceImportPackages": ["@effect/platform-node", "effect"], | ||
| 25 | "diagnosticSeverity": { | ||
| 26 | "importFromBarrel": "error", | ||
| 27 | "anyUnknownInErrorContext": "error", | ||
| 28 | "unsafeEffectTypeAssertion": "error", | ||
| 29 | "instanceOfSchema": "error", | ||
| 30 | "deterministicKeys": "error", | ||
| 31 | "strictEffectProvide": "off", | ||
| 32 | "missingEffectServiceDependency": "error", | ||
| 33 | "leakingRequirements": "error", | ||
| 34 | "globalErrorInEffectCatch": "error", | ||
| 35 | "globalErrorInEffectFailure": "error", | ||
| 36 | "unknownInEffectCatch": "error", | ||
| 37 | "strictBooleanExpressions": "off", | ||
| 38 | "preferSchemaOverJson": "error", | ||
| 39 | "schemaSyncInEffect": "error", | ||
| 40 | "cryptoRandomUUID": "error", | ||
| 41 | "cryptoRandomUUIDInEffect": "error", | ||
| 42 | "nodeBuiltinImport": "error", | ||
| 43 | "globalDate": "error", | ||
| 44 | "globalDateInEffect": "error", | ||
| 45 | "globalConsole": "error", | ||
| 46 | "globalConsoleInEffect": "error", | ||
| 47 | "globalRandom": "error", | ||
| 48 | "globalRandomInEffect": "error", | ||
| 49 | "globalTimers": "error", | ||
| 50 | "globalTimersInEffect": "error" | ||
| 51 | } | ||
| 52 | } | ||
| 53 | ] | ||
| 21 | } | 54 | } |
| 22 | } | 55 | } |