diff --git a/apps/desktop/src/app/DesktopConnectionCompatibilityHintStore.ts b/apps/desktop/src/app/DesktopConnectionCompatibilityHintStore.ts index 0af0afba5f..f66d72c72b 100644 --- a/apps/desktop/src/app/DesktopConnectionCompatibilityHintStore.ts +++ b/apps/desktop/src/app/DesktopConnectionCompatibilityHintStore.ts @@ -5,7 +5,7 @@ import { } from "@t3tools/client-runtime/platform"; import type { EnvironmentId } from "@t3tools/contracts"; import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as Effect from "effect/Effect"; import * as Base64 from "effect/encoding/Base64"; import * as Hex from "effect/encoding/Hex"; @@ -42,14 +42,14 @@ export const make = Effect.fn("desktop.connectionCompatibilityHintStore.make")(f }) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; - const crypto = yield* Crypto.Crypto; + const crypto = yield* Digest.Digest; const filename = path.join(input.directory, "connection-compatibility-hints-v1.sqlite"); const keyFor = Effect.fn("desktop.connectionCompatibilityHintStore.key")(function* ( environmentId: EnvironmentId, routeBinding: string, ) { const encoded = yield* encodeKey([environmentId, routeBinding]); - return Hex.encode(yield* crypto.digest("SHA-256", new TextEncoder().encode(encoded))); + return Hex.encode(yield* crypto.digest(Digest.sha256, new TextEncoder().encode(encoded))); }); const withDatabase = ( operation: "read" | "write", diff --git a/apps/desktop/src/jones/previewCompanion/CompanionConfig.ts b/apps/desktop/src/jones/previewCompanion/CompanionConfig.ts index 392e9faaf1..0b0676702c 100644 --- a/apps/desktop/src/jones/previewCompanion/CompanionConfig.ts +++ b/apps/desktop/src/jones/previewCompanion/CompanionConfig.ts @@ -1,6 +1,6 @@ import { DesktopCompanionConfig, type DesktopCompanionConfigureInput } from "@t3tools/contracts"; import * as Context from "effect/Context"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; @@ -40,7 +40,7 @@ const make = Effect.gen(function* () { ); const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; - const crypto = yield* Crypto.Crypto; + const crypto = yield* SecureRandom.SecureRandom; const identity = yield* DesktopAppIdentity.DesktopAppIdentity; const directory = yield* identity.resolveUserDataPath.pipe( Effect.mapError(() => new CompanionConfigError({ operation: "read" })), @@ -57,7 +57,7 @@ const make = Effect.gen(function* () { ); const write = Effect.fn("CompanionConfig.write")( function* (config: DesktopCompanionConfig) { - const id = yield* crypto.randomUUIDv4(); + const id = yield* crypto.uuidV4(); const temporary = `${target}.${id}.tmp`; const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(DesktopCompanionConfig))( config, @@ -79,7 +79,7 @@ const make = Effect.gen(function* () { enabled: false, environmentId: null, hostId: yield* crypto - .randomUUIDv4() + .uuidV4() .pipe(Effect.mapError(() => new CompanionConfigError({ operation: "identity" }))), label: "Browser host", browserOnly: productLocked, diff --git a/apps/mobile/src/features/workstreams/Controls.tsx b/apps/mobile/src/features/workstreams/Controls.tsx index 8fc782039b..3f5b5df6b1 100644 --- a/apps/mobile/src/features/workstreams/Controls.tsx +++ b/apps/mobile/src/features/workstreams/Controls.tsx @@ -1,6 +1,6 @@ import { workstreamPageUrl } from "./gateway"; import { WorkstreamColorPicker } from "../../jones/workstreamAppearance/WorkstreamColorPicker"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import { runtime } from "../../lib/runtime"; import { useEffect, useLayoutEffect, useRef, useState } from "react"; @@ -373,8 +373,8 @@ export function MobileWorkstreamControls({ generation={snapshot.data.binding.serverGeneration} createCommandId={() => runtime.runPromise( - Crypto.Crypto.pipe( - Effect.flatMap((crypto) => crypto.randomUUIDv4()), + SecureRandom.SecureRandom.pipe( + Effect.flatMap((crypto) => crypto.uuidV4()), Effect.map((id) => `t3-color-${id}`), ), ) diff --git a/apps/mobile/src/features/workstreams/useWorkstreams.tsx b/apps/mobile/src/features/workstreams/useWorkstreams.tsx index 9b2f56d7b5..44894b4da5 100644 --- a/apps/mobile/src/features/workstreams/useWorkstreams.tsx +++ b/apps/mobile/src/features/workstreams/useWorkstreams.tsx @@ -1,3 +1,4 @@ +import * as Digest from "effect/crypto/Digest"; import { WorkstreamOperationsClient, supportsWorkstreamOperations, @@ -33,7 +34,7 @@ import { type WorkstreamCommand, type WorkstreamReceipt, } from "@t3tools/contracts"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; @@ -266,7 +267,9 @@ export function useMobileWorkstreams(threads: readonly WorkstreamThreadLike[]) { ), }, () => - runtime.runPromise(Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4()))), + runtime.runPromise( + SecureRandom.SecureRandom.pipe(Effect.flatMap((crypto) => crypto.uuidV4())), + ), ); mobileOperationClients.set(connectionKey, client); } @@ -551,7 +554,9 @@ export function useMobileWorkstreams(threads: readonly WorkstreamThreadLike[]) { Date.now, (bytes) => runtime.runPromise( - Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.digest("SHA-256", bytes))), + Digest.Digest.pipe( + Effect.flatMap((digest) => digest.digest(Digest.sha256, bytes)), + ), ), ); })().catch((cause: unknown) => { @@ -848,7 +853,7 @@ export function useMobileWorkstreams(threads: readonly WorkstreamThreadLike[]) { return await (async () => { for (const action of actions) { const command_id = await runtime.runPromise( - Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4())), + SecureRandom.SecureRandom.pipe(Effect.flatMap((crypto) => crypto.uuidV4())), ); assertCurrent(snapshot); const command: WorkstreamCommand = { diff --git a/apps/mobile/vite.config.ts b/apps/mobile/vite.config.ts index c478effb32..a15b4bf2e1 100644 --- a/apps/mobile/vite.config.ts +++ b/apps/mobile/vite.config.ts @@ -1,3 +1,5 @@ +import { typecheckTask } from "../../scripts/jones/tsrs/task-cache.ts"; +import packageJson from "./package.json" with { type: "json" }; import * as NodeModule from "node:module"; import * as NodeURL from "node:url"; import { defineConfig, normalizePath } from "vite-plus"; diff --git a/apps/server/src/environment/ServerEnvironment.test.ts b/apps/server/src/environment/ServerEnvironment.test.ts index c639c49f9b..66cf5f9227 100644 --- a/apps/server/src/environment/ServerEnvironment.test.ts +++ b/apps/server/src/environment/ServerEnvironment.test.ts @@ -384,7 +384,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))("ServerEnvironmen ])("concurrent initializers recover a $name environment id file", ({ content }) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; - const crypto = yield* Crypto.Crypto; + const crypto = yield* SecureRandom.SecureRandom; const baseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-server-environment-concurrent-test-", }); @@ -425,17 +425,17 @@ it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))("ServerEnvironmen ), ), }), - Effect.provideService(Crypto.Crypto, { + Effect.provideService(SecureRandom.SecureRandom, { ...crypto, - randomUUIDv4: (() => + uuidV4: (() => Effect.gen(function* () { - const id = yield* crypto.randomUUIDv4(); + const id = yield* crypto.uuidV4(); if (--remaining === 0) { yield* Deferred.succeed(bothGenerated, undefined); } yield* Deferred.await(bothGenerated); return id; - })) as Crypto.Crypto["randomUUIDv4"], + })) as SecureRandom.SecureRandom["Service"]["uuidV4"], }), ); diff --git a/apps/server/src/jones/codexFailover/CodexFailover.integration.test.ts b/apps/server/src/jones/codexFailover/CodexFailover.integration.test.ts index d9586326ad..1a7782f0cb 100644 --- a/apps/server/src/jones/codexFailover/CodexFailover.integration.test.ts +++ b/apps/server/src/jones/codexFailover/CodexFailover.integration.test.ts @@ -16,7 +16,7 @@ import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import * as HostProcess from "@t3tools/shared/HostProcess"; import * as CodexSchema from "effect-codex-app-server/schema"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; @@ -196,7 +196,7 @@ const scenario = (mode: "chain" | "all-unavailable") => Effect.provide(Codex.layerAppServerClientFactory), ); const adapterDependencies = yield* Effect.context< - | Crypto.Crypto + | SecureRandom.SecureRandom | FileSystem.FileSystem | Path.Path | IdAllocator.IdAllocatorV2 diff --git a/apps/server/src/jones/codexFailover/CodexFailoverEvidence.adapter.test.ts b/apps/server/src/jones/codexFailover/CodexFailoverEvidence.adapter.test.ts index f4e4d49473..2fdbfd00e5 100644 --- a/apps/server/src/jones/codexFailover/CodexFailoverEvidence.adapter.test.ts +++ b/apps/server/src/jones/codexFailover/CodexFailoverEvidence.adapter.test.ts @@ -19,7 +19,7 @@ import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter" import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; import * as CodexReplay from "effect-codex-app-server/replay"; import type { ServerNotification__CodexErrorInfo } from "effect-codex-app-server/schema"; -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -359,7 +359,7 @@ const openHarness = Effect.fn("CodexFailoverEvidence.adapter.openHarness")(funct settings: DEFAULT_CODEX_SETTINGS, environment: {}, clientFactory: makeCodexReplayClientFactory({ transcript }), - crypto: yield* Crypto.Crypto, + digest: yield* Digest.Digest, fileSystem, idAllocator: yield* IdAllocator.IdAllocatorV2, serverConfig, diff --git a/apps/server/src/jones/goals/GoalControl.ts b/apps/server/src/jones/goals/GoalControl.ts index 88e9888377..e3e9883ccf 100644 --- a/apps/server/src/jones/goals/GoalControl.ts +++ b/apps/server/src/jones/goals/GoalControl.ts @@ -7,7 +7,7 @@ import { } from "@t3tools/contracts"; import type { ProviderAdapterV2SessionRuntime } from "@t3tools/provider-core/server/ProviderAdapter"; import * as Context from "effect/Context"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; @@ -95,7 +95,7 @@ const make = Effect.gen(function* () { const threads = yield* ThreadManagement.ThreadManagementService; const sessions = yield* Effect.serviceOption(Sessions.ProviderSessionManagerV2); const starts = yield* Effect.serviceOption(GoalStart.GoalStartDispatch); - const crypto = yield* Crypto.Crypto; + const crypto = yield* SecureRandom.SecureRandom; const lock = yield* Semaphore.make(1); const bindings = new Map(); const execute = (scope: Invocation.McpInvocationScope, input: ThreadGoalInput) => @@ -184,7 +184,7 @@ const make = Effect.gen(function* () { !sameBinding(binding.provider, provider) ) { if (mutation) return answer("stale", "read_current_goal_before_mutation"); - const token = yield* crypto.randomUUIDv4().pipe(Effect.orDie); + const token = yield* crypto.uuidV4().pipe(Effect.orDie); const retain = binding !== undefined && sameConversation(binding, provider, runtime); // Preserve rejected keys across an ownership change so durable command dedupe // cannot attribute an old conversation's run to its replacement. diff --git a/apps/server/src/jones/nativeCreation/NativeCreationProviderExecutor.test.ts b/apps/server/src/jones/nativeCreation/NativeCreationProviderExecutor.test.ts index 3beb4c550b..4eab8f70da 100644 --- a/apps/server/src/jones/nativeCreation/NativeCreationProviderExecutor.test.ts +++ b/apps/server/src/jones/nativeCreation/NativeCreationProviderExecutor.test.ts @@ -35,7 +35,7 @@ import { } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; import * as Layer from "effect/Layer"; -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as TestClock from "effect/testing/TestClock"; import * as AuthSessions from "../../persistence/AuthSessions.ts"; import { runMigrations } from "../../persistence/Migrations.ts"; @@ -662,7 +662,7 @@ const scenario = (mode: Mode) => const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({ instanceId: binding.providerInstanceId, settings, - crypto: yield* Crypto.Crypto, + digest: yield* Digest.Digest, environment: {}, fileSystem: fs, idAllocator: allocator, diff --git a/apps/server/src/jones/provider/codex/CodexLateSubagentStart.test.ts b/apps/server/src/jones/provider/codex/CodexLateSubagentStart.test.ts index 4b9264dd8e..b4114962d8 100644 --- a/apps/server/src/jones/provider/codex/CodexLateSubagentStart.test.ts +++ b/apps/server/src/jones/provider/codex/CodexLateSubagentStart.test.ts @@ -15,7 +15,7 @@ import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderS import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import * as CodexClient from "effect-codex-app-server/client"; import * as CodexReplay from "effect-codex-app-server/replay"; -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -223,7 +223,7 @@ describe("Codex late subagent reader progress", () => { instanceId: modelSelection.instanceId, settings: yield* Schema.decodeEffect(CodexSettings)({}), environment: {}, - crypto: yield* Crypto.Crypto, + digest: yield* Digest.Digest, fileSystem, idAllocator: yield* IdAllocator.IdAllocatorV2, serverConfig: yield* makeReplayServerConfig(scenario).pipe( diff --git a/apps/server/src/jones/provider/codex/CodexPendingStartReplay.test.ts b/apps/server/src/jones/provider/codex/CodexPendingStartReplay.test.ts index 86ac6b69b8..e94b749ff7 100644 --- a/apps/server/src/jones/provider/codex/CodexPendingStartReplay.test.ts +++ b/apps/server/src/jones/provider/codex/CodexPendingStartReplay.test.ts @@ -15,7 +15,7 @@ import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderS import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import * as CodexClient from "effect-codex-app-server/client"; import * as CodexReplay from "effect-codex-app-server/replay"; -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -399,7 +399,7 @@ describe("Codex pre-ACK item reader progress", () => { instanceId: modelSelection.instanceId, settings: yield* Schema.decodeEffect(CodexSettings)({}), environment: {}, - crypto: yield* Crypto.Crypto, + digest: yield* Digest.Digest, fileSystem, idAllocator: { ...idAllocator, diff --git a/apps/server/src/jones/provider/opencode/OpenCodeCreationPolicy.test.ts b/apps/server/src/jones/provider/opencode/OpenCodeCreationPolicy.test.ts index a65b770c1c..a1192ca566 100644 --- a/apps/server/src/jones/provider/opencode/OpenCodeCreationPolicy.test.ts +++ b/apps/server/src/jones/provider/opencode/OpenCodeCreationPolicy.test.ts @@ -6,7 +6,7 @@ import { ThreadId, } from "@t3tools/contracts"; import * as HostProcess from "@t3tools/shared/HostProcess"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; @@ -415,11 +415,8 @@ const syntheticClient = OpenCode2Client.layer.pipe( ), ); const syntheticCrypto = Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size), - digest: (_algorithm, bytes) => Effect.succeed(bytes), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size)), ); const serverInput = { ...ownerInput, serverUrl: "", serverPassword: "", environment: {} }; diff --git a/apps/server/src/jones/terminal/ShellIdlePrompt.ts b/apps/server/src/jones/terminal/ShellIdlePrompt.ts index 577a8b8765..cf3bf04c8f 100644 --- a/apps/server/src/jones/terminal/ShellIdlePrompt.ts +++ b/apps/server/src/jones/terminal/ShellIdlePrompt.ts @@ -1,4 +1,4 @@ -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; @@ -51,7 +51,7 @@ export class ShellIdlePrompt { /** Child-local startup hooks preserve the user's files; unsupported shells never expire. */ export const makeShellIdlePromptIntegration = Effect.fn("terminal.makeShellIdlePromptIntegration")( function* () { - const crypto = yield* Crypto.Crypto; + const crypto = yield* SecureRandom.SecureRandom; const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const directory = yield* fs.makeTempDirectoryScoped({ prefix: "jones-shell-idle-" }); @@ -109,7 +109,7 @@ preexec_functions+=(__jones_idle_busy) env: NodeJS.ProcessEnv, ) { if (shellName !== "bash" && shellName !== "zsh") return { env, tracker: undefined }; - const token = yield* crypto.randomUUIDv4(); + const token = yield* crypto.uuidV4(); const tracker = new ShellIdlePrompt(token); if (shellName === "bash") { // PS0 is emitted immediately before execution, including shell builtins. diff --git a/apps/server/src/jones/threadConfiguration/ThreadSelfConfiguration.ts b/apps/server/src/jones/threadConfiguration/ThreadSelfConfiguration.ts index 67abe8a548..69c1686d80 100644 --- a/apps/server/src/jones/threadConfiguration/ThreadSelfConfiguration.ts +++ b/apps/server/src/jones/threadConfiguration/ThreadSelfConfiguration.ts @@ -12,7 +12,7 @@ import { import { makeOwnerSelectionStore } from "./ownerSelectionStore.ts"; import { makeJonesTablePresence } from "../persistence/JonesTablePresence.ts"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Context from "effect/Context"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; @@ -84,8 +84,8 @@ const make = Effect.gen(function* () { const transitions = yield* Switch.ProviderSwitchServiceV2; const sql = yield* SqlClient.SqlClient; const ownerSelections = makeOwnerSelectionStore(sql, makeJonesTablePresence(sql)); - const crypto = yield* Crypto.Crypto; - const generationNamespace = yield* crypto.randomUUIDv4(); + const crypto = yield* SecureRandom.SecureRandom; + const generationNamespace = yield* crypto.uuidV4(); let nextGeneration = 0; const residentGenerations = new WeakMap(); const revision = Effect.fn("ThreadSelfConfiguration.revision")(function* (threadId: ThreadId) { diff --git a/apps/server/src/jones/threadResultDependencies/DependencyReactor.ts b/apps/server/src/jones/threadResultDependencies/DependencyReactor.ts index 4583d4b745..8bf99b724a 100644 --- a/apps/server/src/jones/threadResultDependencies/DependencyReactor.ts +++ b/apps/server/src/jones/threadResultDependencies/DependencyReactor.ts @@ -7,7 +7,7 @@ import { type ThreadRequestObservation, } from "@t3tools/contracts"; import * as Context from "effect/Context"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; @@ -32,7 +32,7 @@ export class DependencyReactor extends Context.Service< >()("t3/jones/threadResultDependencies/DependencyReactor") {} const make = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; - const crypto = yield* Crypto.Crypto; + const crypto = yield* SecureRandom.SecureRandom; const store = makeDependencyStore(sql, makeJonesTablePresence(sql)); const changes = yield* Changes.DependencyChanges; const watcher = yield* RequestWatch.RequestWatch; @@ -42,12 +42,12 @@ const make = Effect.gen(function* () { const running = new Map }>(); const mutate = makeCommit(sql, store, changes); const wake = Effect.fn("DependencyReactor.wake")(function* (dependency: Dependency) { - const messageId = MessageId.make(yield* crypto.randomUUIDv4()); + const messageId = MessageId.make(yield* crypto.uuidV4()); const value = notification(dependency); yield* orchestrator .dispatch({ type: "message.dispatch", - commandId: CommandId.make(yield* crypto.randomUUIDv4()), + commandId: CommandId.make(yield* crypto.uuidV4()), messageId, threadId: dependency.waiterThreadId, text: `${value.summary}\n\n${value.detail}`, diff --git a/apps/server/src/jones/workMode/Settlement.test.ts b/apps/server/src/jones/workMode/Settlement.test.ts index 99cdc41fe7..f3f15bf3e2 100644 --- a/apps/server/src/jones/workMode/Settlement.test.ts +++ b/apps/server/src/jones/workMode/Settlement.test.ts @@ -7,7 +7,7 @@ import { type ServerSettings as ContractServerSettings, } from "@t3tools/contracts"; import { WORK_MODE_SENTINEL } from "@t3tools/shared/jones/workMode"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -178,11 +178,8 @@ const makeHarness = Effect.fn("makeWorkModeSettlementHarness")(function* (option ), Layer.succeed(ServerActivation.ServerActivation, Deferred.await(activation)), Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size).fill(1), - digest: (_algorithm, data) => Effect.succeed(data), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size).fill(1)), ), FileSystem.layerNoop({ exists: () => Effect.succeed(false) }), ); diff --git a/apps/server/src/jones/workstreams/operations/childIds.ts b/apps/server/src/jones/workstreams/operations/childIds.ts index 708fe4a05c..816b39bcb1 100644 --- a/apps/server/src/jones/workstreams/operations/childIds.ts +++ b/apps/server/src/jones/workstreams/operations/childIds.ts @@ -1,4 +1,4 @@ -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as Effect from "effect/Effect"; import * as Hex from "effect/encoding/Hex"; @@ -28,9 +28,9 @@ export const workstreamChildId = Effect.fn("WorkstreamOperations.childId")(funct ) { throw new Error("Invalid workstream child ID input."); } - const crypto = yield* Crypto.Crypto; + const crypto = yield* Digest.Digest; const bytes = yield* crypto.digest( - "SHA-256", + Digest.sha256, new TextEncoder().encode( `jwo/1\0${input.environment_id}\0${input.operation_id}\0${input.request_sha256}`, ), diff --git a/apps/server/src/jones/workstreams/operations/service.ts b/apps/server/src/jones/workstreams/operations/service.ts index cd64a43dc4..a62e2d388b 100644 --- a/apps/server/src/jones/workstreams/operations/service.ts +++ b/apps/server/src/jones/workstreams/operations/service.ts @@ -17,7 +17,7 @@ import { type WorkstreamSetupStatus, } from "@t3tools/contracts/jones/workstreamOperations"; import * as Context from "effect/Context"; -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -147,7 +147,7 @@ const sameContext = (context: WorkstreamReadContext, binding: T3WorkstreamBindin const make = Effect.gen(function* () { const ports = yield* Ports; - const crypto = yield* Crypto.Crypto; + const crypto = yield* Digest.Digest; const scope = yield* Scope.Scope; const gateway = ports.gateway; const admission = yield* Semaphore.make(1); @@ -155,7 +155,7 @@ const make = Effect.gen(function* () { const locks = new Map(); const assignments = new Map(); const digest = (body: string) => - crypto.digest("SHA-256", new TextEncoder().encode(body)).pipe(Effect.map(Hex.encode)); + crypto.digest(Digest.sha256, new TextEncoder().encode(body)).pipe(Effect.map(Hex.encode)); const queue: { priority: number; run: Effect.Effect }[] = []; let writing = false; const write = Effect.fn("WorkstreamOperations.write")(function* (entry: Entry, child: Child) { @@ -924,7 +924,7 @@ const make = Effect.gen(function* () { kind, attempt, }).pipe( - Effect.provideService(Crypto.Crypto, crypto), + Effect.provideService(Digest.Digest, crypto), Effect.mapError(() => new OperationInternalError({})), ), ); diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index d3bbbe5e92..310cadd8d5 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -15415,7 +15415,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { attachmentsDir, fileSystem, path: yield* Path.Path, - crypto: yield* Crypto.Crypto, + random: yield* SecureRandom.SecureRandom, idAllocator, continuationRequests: { offer: (request) => @@ -16139,7 +16139,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { attachmentsDir, fileSystem, path: yield* Path.Path, - crypto: yield* Crypto.Crypto, + random: yield* SecureRandom.SecureRandom, idAllocator, continuationRequests: { offer: (request) => @@ -16341,7 +16341,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { attachmentsDir, fileSystem, path: yield* Path.Path, - crypto: yield* Crypto.Crypto, + random: yield* SecureRandom.SecureRandom, idAllocator, continuationRequests: { offer: () => Effect.void, @@ -16414,7 +16414,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { const fileSystem = yield* FileSystem.FileSystem; const idAllocator = yield* IdAllocator.IdAllocatorV2; const path = yield* Path.Path; - const crypto = yield* Crypto.Crypto; + const random = yield* SecureRandom.SecureRandom; const attachmentsDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-claude-retirement-", }); @@ -16506,7 +16506,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { attachmentsDir, fileSystem, path, - crypto, + random, idAllocator, queryRunner: { allocateSessionId: Effect.sync(() => @@ -17581,7 +17581,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { attachmentsDir, fileSystem, path: yield* Path.Path, - crypto: yield* Crypto.Crypto, + random: yield* SecureRandom.SecureRandom, idAllocator, queryRunner: { allocateSessionId: Effect.sync(() => diff --git a/apps/server/src/orchestration-v2/ThreadPullRequestService.test.ts b/apps/server/src/orchestration-v2/ThreadPullRequestService.test.ts index 33f75086f5..c048f888e3 100644 --- a/apps/server/src/orchestration-v2/ThreadPullRequestService.test.ts +++ b/apps/server/src/orchestration-v2/ThreadPullRequestService.test.ts @@ -514,11 +514,8 @@ describe("ThreadPullRequestServiceV2 reads", () => { }), Layer.succeed(ServerActivation.ServerActivation, Deferred.await(activation)), Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size).fill(1), - digest: (_algorithm, data) => Effect.succeed(data), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size).fill(1)), ), FileSystem.layerNoop({}), ); @@ -654,11 +651,8 @@ describe("ThreadPullRequestServiceV2 reads", () => { }), Layer.succeed(ServerActivation.ServerActivation, Deferred.await(activation)), Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size).fill(1), - digest: (_algorithm, data) => Effect.succeed(data), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size).fill(1)), ), FileSystem.layerNoop({}), ); @@ -889,11 +883,8 @@ describe("ThreadPullRequestServiceV2 reads", () => { }), Layer.succeed(ServerActivation.ServerActivation, Deferred.await(activation)), Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size).fill(1), - digest: (_algorithm, data) => Effect.succeed(data), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size).fill(1)), ), FileSystem.layerNoop({}), ); diff --git a/apps/server/src/terminal/Manager.test.ts b/apps/server/src/terminal/Manager.test.ts index 7d86aa2893..0e3140ec30 100644 --- a/apps/server/src/terminal/Manager.test.ts +++ b/apps/server/src/terminal/Manager.test.ts @@ -1,3 +1,4 @@ +import type * as SecureRandom from "effect/crypto/SecureRandom"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; @@ -262,7 +263,11 @@ const createManager = ( ): Effect.Effect< ManagerFixture, PlatformError.PlatformError, - FileSystem.FileSystem | Path.Path | Scope.Scope | ProcessRunner.ProcessRunner | Crypto.Crypto + | FileSystem.FileSystem + | Path.Path + | Scope.Scope + | ProcessRunner.ProcessRunner + | SecureRandom.SecureRandom > => Effect.flatMap(Effect.service(FileSystem.FileSystem), (fs) => Effect.gen(function* () { diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 43a74c066d..5b2a05d49c 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -3536,7 +3536,7 @@ type WsLayerServices = | ChildProcessSpawner.ChildProcessSpawner | CodexInstallation.CodexInstallation | CommandOutputHub.CommandOutputHub - | Crypto.Crypto + | SecureRandom.SecureRandom | DatabaseStorage.DatabaseStorage | DeviceService.DeviceService | DirectEndpoints.DirectEndpoints diff --git a/apps/web/src/components/workstreams/WorkstreamSidebarSection.tsx b/apps/web/src/components/workstreams/WorkstreamSidebarSection.tsx index 3a1ab7cc45..b02374829c 100644 --- a/apps/web/src/components/workstreams/WorkstreamSidebarSection.tsx +++ b/apps/web/src/components/workstreams/WorkstreamSidebarSection.tsx @@ -10,7 +10,7 @@ import { workstreamBindingKey, resolveWorkstreamCompletionAuthority, } from "@t3tools/client-runtime/state/workstreams"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import { ChevronDownIcon, ChevronUpIcon, GripVerticalIcon, MoreHorizontalIcon } from "lucide-react"; import { useLayoutEffect, useRef, useState, type ReactNode, type DragEvent } from "react"; @@ -46,8 +46,8 @@ import { export const workstreamCommandId = () => runtime.runPromise( - Crypto.Crypto.pipe( - Effect.flatMap((crypto) => crypto.randomUUIDv4()), + SecureRandom.SecureRandom.pipe( + Effect.flatMap((crypto) => crypto.uuidV4()), Effect.map((uuid) => `t3-workstream-${uuid}`), ), ); diff --git a/apps/web/src/jones/fleetUpdates/FleetEnrollmentControls.tsx b/apps/web/src/jones/fleetUpdates/FleetEnrollmentControls.tsx index 740f9b4acd..91a9f2e434 100644 --- a/apps/web/src/jones/fleetUpdates/FleetEnrollmentControls.tsx +++ b/apps/web/src/jones/fleetUpdates/FleetEnrollmentControls.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { useAtomValue } from "@effect/atom-react"; import type { EnvironmentId } from "@t3tools/contracts"; import type { FleetHostStatus } from "@t3tools/contracts/jones/fleet-updates"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import { runtime } from "../../lib/runtime"; import { FleetEnrollmentStatus, type FleetHostReadiness } from "./FleetEnrollmentStatus"; @@ -78,7 +78,7 @@ export function FleetEnrollmentControls({ const fresh = await fleetHost(environmentId, { action: "status" }); const selected = { enrollmentId: await runtime.runPromise( - Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4())), + SecureRandom.SecureRandom.pipe(Effect.flatMap((crypto) => crypto.uuidV4())), ), environmentId, enabled: true, diff --git a/apps/web/src/jones/updates/localDesktopUpdate.ts b/apps/web/src/jones/updates/localDesktopUpdate.ts index 808cb6d7f4..0a1be70aee 100644 --- a/apps/web/src/jones/updates/localDesktopUpdate.ts +++ b/apps/web/src/jones/updates/localDesktopUpdate.ts @@ -1,7 +1,7 @@ import { routeLaptopDownload, routeLaptopInstall } from "../installStatus/entrypoints"; import { jonesBuildLabel, jonesBuildLabels } from "./buildVersion"; import { outcomeBuildRefs } from "./buildPairs"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import { runtime } from "../../lib/runtime"; import { toastManager } from "../../components/ui/toast"; @@ -195,7 +195,7 @@ async function prepareLocalFleetCampaign( "The staged desktop source is unavailable; enrolled updates cannot be prepared.", ); const campaignId = await runtime.runPromise( - Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4())), + SecureRandom.SecureRandom.pipe(Effect.flatMap((crypto) => crypto.uuidV4())), ); const prepared = await bridge.fleetUpdates({ action: "prepare", diff --git a/knip.jsonc b/knip.jsonc index 8beb2d8249..4afb67de4a 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -30,6 +30,8 @@ // These fixture scripts are injected into a browser page by path. "fixtures/*/*.js", "knip-schemas.ts", + // Retained release CLI; publication workflows stay disabled for this sync. + "resolve-nightly-release.ts", "linux-after-pack.ts", "mobile-behavior/run.ts", "mobile-native-client.ts", diff --git a/packages/client-runtime/package.json b/packages/client-runtime/package.json index fb307dd61e..9536555e80 100644 --- a/packages/client-runtime/package.json +++ b/packages/client-runtime/package.json @@ -626,6 +626,22 @@ "./jones/descendant-approval": { "types": "./src/jones/descendantApproval/index.ts", "default": "./src/jones/descendantApproval/index.ts" + }, + "./voice-review/fixtures": { + "types": "./src/voiceReview/fixtures.ts", + "default": "./src/voiceReview/fixtures.ts" + }, + "./conversations": { + "types": "./src/conversations/index.ts", + "default": "./src/conversations/index.ts" + }, + "./voice-review": { + "types": "./src/voice-review.ts", + "default": "./src/voice-review.ts" + }, + "./state/workstreams": { + "types": "./src/state/workstreams/index.ts", + "default": "./src/state/workstreams/index.ts" } }, "scripts": { diff --git a/packages/client-runtime/src/authorization/layer.test.ts b/packages/client-runtime/src/authorization/layer.test.ts index c4324d236d..646a900cf9 100644 --- a/packages/client-runtime/src/authorization/layer.test.ts +++ b/packages/client-runtime/src/authorization/layer.test.ts @@ -1,3 +1,4 @@ +import { AuthAdministrativeScopes } from "@t3tools/contracts"; import { AuthStandardClientScopes, EnvironmentId, diff --git a/packages/client-runtime/src/composerThreadItems.test.ts b/packages/client-runtime/src/composerThreadItems.test.ts index 564226839a..bea4bcc346 100644 --- a/packages/client-runtime/src/composerThreadItems.test.ts +++ b/packages/client-runtime/src/composerThreadItems.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vite-plus/test"; import { matchComposerEnvironmentItems, - matchComposerThreadItems, + matchUpstreamComposerThreadItems as matchComposerThreadItems, type ComposerThreadCandidate, } from "./composerThreadItems.ts"; diff --git a/packages/client-runtime/src/connection/driver.ts b/packages/client-runtime/src/connection/driver.ts index 250b2cf6c4..fa13057b95 100644 --- a/packages/client-runtime/src/connection/driver.ts +++ b/packages/client-runtime/src/connection/driver.ts @@ -59,6 +59,10 @@ export class ConnectionDriver extends Context.Service< entry: ConnectionCatalogEntry, reportProgress: (progress: ConnectionDriverProgress) => Effect.Effect, ) => Effect.Effect; + readonly preflightForRecovery?: ( + entry: ConnectionCatalogEntry, + route: ConnectionRoute, + ) => Effect.Effect; readonly preflight: ( entry: ConnectionCatalogEntry, route: ConnectionRoute, @@ -195,28 +199,37 @@ export const make = Effect.gen(function* () { return yield* connectOverRoutes( entry, (route) => checkRoute(entry, route), - Effect.fnUntraced(function* (route) { - const prepared = yield* resolver.prepare(routeEntry(entry, route)); - yield* reportProgress({ stage: "opening", prepared }); - const session = yield* sessions.connect(prepared); - yield* reportProgress({ stage: "synchronizing", prepared }); - yield* session.ready; - // The server closes a socket when its session expires, unless the same key renewed it. - if (prepared.httpAuthorization?._tag === "Dpop") { - yield* remote - .renewDpopBeforeExpiry({ - expectedEnvironmentId: prepared.environmentId, - expiresAtEpochMs: prepared.httpAuthorization.expiresAtEpochMs, - }) - .pipe( - Effect.catch((error) => - Effect.logWarning("Could not renew the connection's credential.", { error }), - ), - Effect.forkScoped, - ); - } - return { prepared, session } satisfies EnvironmentConnectionLease; - }), + (route, hasFallback) => + withRouteAttemptBudget( + hasFallback, + Effect.gen(function* () { + const prepared = yield* resolver.prepare(routeEntry(entry, route)); + yield* reportProgress({ stage: "opening", prepared }); + const session = yield* sessions.connect(prepared); + return { prepared, session } satisfies EnvironmentConnectionLease; + }), + (lease) => + Effect.gen(function* () { + const { prepared, session } = lease; + yield* reportProgress({ stage: "synchronizing", prepared }); + yield* session.ready; + // The server closes a socket when its session expires, unless the same key renewed it. + if (prepared.httpAuthorization?._tag === "Dpop") { + yield* remote + .renewDpopBeforeExpiry({ + expectedEnvironmentId: prepared.environmentId, + expiresAtEpochMs: prepared.httpAuthorization.expiresAtEpochMs, + }) + .pipe( + Effect.catch((error) => + Effect.logWarning("Could not renew the connection's credential.", { error }), + ), + Effect.forkScoped, + ); + } + return lease; + }), + ), ); }); @@ -236,7 +249,10 @@ export const make = Effect.gen(function* () { Effect.withSpan("ConnectionDriver.preflight"), ); - return ConnectionDriver.of({ connect, preflight, checkHost }); + const preflightForRecovery = (entry: ConnectionCatalogEntry, route: ConnectionRoute) => + preflightBetterRoute(entry, route, checkRoute(entry, route), resolver.prepare); + + return ConnectionDriver.of({ connect, preflight, preflightForRecovery, checkHost }); }); export const layer = Layer.effect(ConnectionDriver, make); diff --git a/packages/client-runtime/src/connection/supervisor.ts b/packages/client-runtime/src/connection/supervisor.ts index 944206b0a2..9b71a15c3c 100644 --- a/packages/client-runtime/src/connection/supervisor.ts +++ b/packages/client-runtime/src/connection/supervisor.ts @@ -334,6 +334,48 @@ export const make = Effect.fn("EnvironmentSupervisor.make")(function* ( routes: new Map(), })); + const recovery = makeBetterRouteRecovery({ + owner: options?.betterRouteEntryOwner ?? { + withCurrentEntry: (use) => use(() => Ref.getUnsafe(currentEntry)), + }, + identity: Option.isSome(cloudSession) ? cloudSession.value.identity : Effect.succeedNone, + readState: () => ({ + lease: currentLease, + desired: Ref.getUnsafe(intent).desired, + cooldowns: new Map([ + ...Ref.getUnsafe(routeCooldowns), + ...[...Ref.getUnsafe(silentRoutes).routes].map(([id, state]) => [id, state.until] as const), + ]), + }), + preflight: Effect.fn("EnvironmentSupervisor.preflightRecovery")(function* (entry, route) { + const generation = Ref.getUnsafe(silentRoutes).generation; + const result = yield* driver.preflight(entry, route); + const now = yield* Clock.monotonicTimeNanos; + yield* Ref.update(silentRoutes, (previous) => { + if (previous.generation !== generation) return previous; + const routes = new Map(previous.routes); + const id = connectionRouteId(route.target); + if (result === "silent") routes.set(id, backOffSilentRoute(previous.routes.get(id), now)); + else routes.delete(id); + return { generation, routes }; + }); + if ( + result === "unchecked" && + route.target._tag === "RelayConnectionTarget" && + driver.preflightForRecovery !== undefined + ) { + return yield* driver.preflightForRecovery(entry, route); + } + return result === "ready"; + }), + offer: (observation) => { + Queue.offerUnsafe(signals, { _tag: "BetterRouteAvailable", observation }); + }, + select: (routeId) => { + preferredRouteId = Option.some(routeId); + }, + }); + const attemptEntry = Effect.gen(function* () { const latest = yield* Ref.get(currentEntry); const routes = connectionRoutes(latest); @@ -385,44 +427,6 @@ export const make = Effect.fn("EnvironmentSupervisor.make")(function* ( }), ); - const checkBetterRoutes = Effect.fnUntraced(function* (lease: ConnectedLease) { - const latest = yield* Ref.get(currentEntry); - const current = yield* routeIndex(lease); - if (current <= 0) return; - const now = yield* Clock.monotonicTimeNanos; - const cooldowns = yield* Ref.get(routeCooldowns); - const silent = yield* Ref.get(silentRoutes); - const better = connectionRoutes(latest) - .slice(0, current) - .filter((route) => { - const id = connectionRouteId(route.target); - return (cooldowns.get(id) ?? 0n) <= now && (silent.routes.get(id)?.until ?? 0n) <= now; - }); - const results = yield* Effect.forEach( - better, - (route: ConnectionRoute) => driver.preflight(latest, route), - { concurrency: "unbounded" }, - ); - yield* Ref.update(silentRoutes, (previous) => { - if (previous.generation !== silent.generation) return previous; - const routes = new Map(previous.routes); - for (const [position, route] of better.entries()) { - const id = connectionRouteId(route.target); - if (results[position] !== "silent") routes.delete(id); - else routes.set(id, backOffSilentRoute(previous.routes.get(id), now)); - } - return { generation: previous.generation, routes }; - }); - const index = results.indexOf("ready"); - if (index === -1) return; - const live = yield* SubscriptionRef.get(session); - if (Option.isNone(live) || live.value !== lease.session) return; - yield* signal({ - _tag: "BetterRouteAvailable", - routeId: connectionRouteId(better[index]!.target), - }); - }); - const betterRouteChecks = yield* Queue.dropping(1); const requestBetterRouteCheck = (lease: ConnectedLease) => routeIndex(lease).pipe( @@ -973,6 +977,7 @@ export const make = Effect.fn("EnvironmentSupervisor.make")(function* ( connectionRouteId(route.target) === connectionRouteId(active.lease.prepared.target), ), }; + currentLease = connectedLease; const connectedAt = yield* Clock.currentTimeMillis; yield* SubscriptionRef.set(prepared, Option.some(connectedLease.prepared)); yield* SubscriptionRef.set(session, Option.some(connectedLease.session)); diff --git a/packages/client-runtime/src/conversations/http.test.ts b/packages/client-runtime/src/conversations/http.test.ts index c7d5219bc9..2f1b96260c 100644 --- a/packages/client-runtime/src/conversations/http.test.ts +++ b/packages/client-runtime/src/conversations/http.test.ts @@ -113,6 +113,7 @@ describe("conversation library HTTP transport", () => { }, }; const remoteAuthorization = RemoteEnvironmentAuthorization.of({ + renewDpopBeforeExpiry: () => Effect.die("Unexpected socket renewal in HTTP fixture"), authorizeBearer: () => Effect.die("unused"), authorizeDpop: () => Effect.die("unused"), authorizeDpopHttp: () => diff --git a/packages/client-runtime/src/jones/connection/betterRouteRecovery.test.ts b/packages/client-runtime/src/jones/connection/betterRouteRecovery.test.ts index f645252143..b487b7e4a8 100644 --- a/packages/client-runtime/src/jones/connection/betterRouteRecovery.test.ts +++ b/packages/client-runtime/src/jones/connection/betterRouteRecovery.test.ts @@ -1,3 +1,5 @@ +import { RemoteEnvironmentAuthorization } from "../../authorization/service.ts"; +import { unusedAuthorization } from "./unusedAuthorization.ts"; import { EnvironmentId, ORCHESTRATION_PROTOCOL_VERSION } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; import * as Deferred from "effect/Deferred"; @@ -78,6 +80,7 @@ const makeHarness = Effect.fn("ConnectRecoveryTest.makeHarness")(function* (opti const released: Array = []; const closures: Array> = []; const driver = yield* ConnectionDriver.make.pipe( + Effect.provideService(RemoteEnvironmentAuthorization, unusedAuthorization), Effect.provideService(ConnectionResolver.ConnectionResolver, { prepare: (catalog) => Effect.gen(function* () { @@ -119,6 +122,7 @@ const makeHarness = Effect.fn("ConnectRecoveryTest.makeHarness")(function* (opti subscribeServerConfig: () => Stream.die("No config stream in this test."), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Deferred.await(closed), } satisfies RpcSession.RpcSession; }), @@ -229,7 +233,7 @@ describe("Connect preferred route recovery through the production driver and sup Effect.gen(function* () { const h = yield* makeHarness(); h.restore(); - expect(yield* h.driver.preflight(entry, connectionRoutes(entry)[0]!)).toBe(true); + expect(yield* h.driver.preflightForRecovery!(entry, connectionRoutes(entry)[0]!)).toBe(true); expect(h.prepared).toHaveLength(1); expect(h.sockets).toHaveLength(0); }), @@ -492,7 +496,9 @@ describe("Connect preferred route recovery through the production driver and sup Effect.gen(function* () { const h = yield* makeHarness(); h.restore({ preparation: Effect.never }); - const fiber = yield* Effect.forkChild(h.driver.preflight(entry, connectionRoutes(entry)[0]!)); + const fiber = yield* Effect.forkChild( + h.driver.preflightForRecovery!(entry, connectionRoutes(entry)[0]!), + ); yield* Deferred.await(h.preflightStarted); yield* Fiber.interrupt(fiber); expect(h.cancelled()).toBe(1); @@ -504,7 +510,7 @@ describe("Connect preferred route recovery through the production driver and sup Effect.gen(function* () { const h = yield* makeHarness(); h.restore(); - expect(yield* h.driver.preflight(entry, connectionRoutes(entry)[1]!)).toBe(false); + expect(yield* h.driver.preflightForRecovery!(entry, connectionRoutes(entry)[1]!)).toBe(false); const secureTarget = new BearerConnectionTarget({ environmentId, label: preferred.label, @@ -536,7 +542,9 @@ describe("Connect preferred route recovery through the production driver and sup ], enabled: true, }; - expect(yield* h.driver.preflight(clearEntry, connectionRoutes(clearEntry)[1]!)).toBe(false); + expect( + yield* h.driver.preflightForRecovery!(clearEntry, connectionRoutes(clearEntry)[1]!), + ).toBe(false); expect(h.prepared).toHaveLength(0); expect(h.sockets).toHaveLength(0); }), @@ -587,17 +595,19 @@ describe("Connect preferred route recovery through the production driver and sup ), }; h.restore(); - const silent = yield* Effect.forkChild(h.driver.preflight(entry, publicRoute)); + const silent = yield* Effect.forkChild(h.driver.preflightForRecovery!(entry, publicRoute)); yield* TestClock.adjust("2 seconds"); expect(yield* Fiber.join(silent)).toBe(false); expect(h.prepared).toHaveLength(0); descriptorId = environmentId; - const answered = yield* Effect.forkChild(h.driver.preflight(entry, publicRoute)); + const answered = yield* Effect.forkChild( + h.driver.preflightForRecovery!(entry, publicRoute), + ); yield* TestClock.adjust("2 seconds"); expect(yield* Fiber.join(answered)).toBe(true); expect(h.prepared).toHaveLength(1); h.restore({ preparation: Effect.never }); - const bounded = yield* Effect.forkChild(h.driver.preflight(entry, publicRoute)); + const bounded = yield* Effect.forkChild(h.driver.preflightForRecovery!(entry, publicRoute)); yield* TestClock.adjust("2 seconds"); yield* drain; yield* TestClock.adjust("13 seconds"); diff --git a/packages/client-runtime/src/jones/connection/betterRouteRecovery.ts b/packages/client-runtime/src/jones/connection/betterRouteRecovery.ts index 8705c76661..e0f19c141f 100644 --- a/packages/client-runtime/src/jones/connection/betterRouteRecovery.ts +++ b/packages/client-runtime/src/jones/connection/betterRouteRecovery.ts @@ -105,7 +105,7 @@ export function makeBetterRouteRecovery(input: { ); return ( candidate >= 0 && - candidate < fallback && + candidate < (fallback === -1 ? routes.length : fallback) && (state.cooldowns.get(connectionRouteId(observation.route.target)) ?? 0n) <= now ); }; diff --git a/packages/client-runtime/src/jones/connection/routeAttemptBudget.test.ts b/packages/client-runtime/src/jones/connection/routeAttemptBudget.test.ts index fdaaaaa457..435d210509 100644 --- a/packages/client-runtime/src/jones/connection/routeAttemptBudget.test.ts +++ b/packages/client-runtime/src/jones/connection/routeAttemptBudget.test.ts @@ -1,3 +1,5 @@ +import { RemoteEnvironmentAuthorization } from "../../authorization/service.ts"; +import { unusedAuthorization } from "./unusedAuthorization.ts"; import { EnvironmentId } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; import * as Deferred from "effect/Deferred"; @@ -74,6 +76,7 @@ const makeHarness = Effect.fn("RouteBudgetTest.makeHarness")(function* (options? ), ); const driver = yield* ConnectionDriver.make.pipe( + Effect.provideService(RemoteEnvironmentAuthorization, unusedAuthorization), Effect.provideService( ConnectionResolver.ConnectionResolver, ConnectionResolver.ConnectionResolver.of({ @@ -119,6 +122,7 @@ const makeHarness = Effect.fn("RouteBudgetTest.makeHarness")(function* (options? subscribeServerConfig: () => Stream.die(new Error("Config stream is not used.")), ready: options?.ready?.(prepared.target) ?? Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, } satisfies RpcSession.RpcSession; }), diff --git a/packages/client-runtime/src/jones/connection/unusedAuthorization.ts b/packages/client-runtime/src/jones/connection/unusedAuthorization.ts new file mode 100644 index 0000000000..66d3492a12 --- /dev/null +++ b/packages/client-runtime/src/jones/connection/unusedAuthorization.ts @@ -0,0 +1,17 @@ +import * as Effect from "effect/Effect"; +import { RemoteEnvironmentAuthorization } from "../../authorization/service.ts"; + +export const unusedAuthorization = RemoteEnvironmentAuthorization.of({ + authorizeBearer: Effect.fn("Fixture.authorizeBearer")(() => + Effect.die("Unexpected bearer authorization in route fixture"), + ), + authorizeDpop: Effect.fn("Fixture.authorizeDpop")(() => + Effect.die("Unexpected DPoP authorization in route fixture"), + ), + authorizeDpopHttp: Effect.fn("Fixture.authorizeDpopHttp")(() => + Effect.die("Unexpected HTTP authorization in route fixture"), + ), + renewDpopBeforeExpiry: Effect.fn("Fixture.renewDpopBeforeExpiry")(() => + Effect.die("Unexpected renewal in non-DPoP route fixture"), + ), +}); diff --git a/packages/client-runtime/src/jones/fleetUpdates/hostBridge.test.ts b/packages/client-runtime/src/jones/fleetUpdates/hostBridge.test.ts index ba96de0421..12cbf8c907 100644 --- a/packages/client-runtime/src/jones/fleetUpdates/hostBridge.test.ts +++ b/packages/client-runtime/src/jones/fleetUpdates/hostBridge.test.ts @@ -62,6 +62,7 @@ it.effect("authenticates relay status reads and target-bound plan directive PUTs rejectedAccessToken?: string; }> = []; const remote = RemoteEnvironmentAuthorization.of({ + renewDpopBeforeExpiry: () => Effect.die("Unexpected socket renewal in HTTP fixture"), authorizeBearer: () => Effect.die("Unexpected bearer authorization"), authorizeDpop: () => Effect.die("Status reads must not replace the socket"), authorizeDpopHttp: (input) => diff --git a/packages/client-runtime/src/jones/fleetUpdates/updateBridge.test.ts b/packages/client-runtime/src/jones/fleetUpdates/updateBridge.test.ts index b1d24e8b99..a47908d70c 100644 --- a/packages/client-runtime/src/jones/fleetUpdates/updateBridge.test.ts +++ b/packages/client-runtime/src/jones/fleetUpdates/updateBridge.test.ts @@ -146,6 +146,7 @@ describe("qualified update-only connection", () => { rejectedAccessToken?: string; }> = []; const authorization = RemoteEnvironmentAuthorization.of({ + renewDpopBeforeExpiry: () => Effect.die("Unexpected socket renewal in HTTP fixture"), authorizeBearer: () => Effect.die("Unexpected bearer authorization"), authorizeDpop: () => Effect.die("Update reads must not replace the socket"), authorizeDpopHttp: (input) => diff --git a/packages/client-runtime/src/jones/importedHistory/commands.test.ts b/packages/client-runtime/src/jones/importedHistory/commands.test.ts index 3ef5dc9065..4534afd08a 100644 --- a/packages/client-runtime/src/jones/importedHistory/commands.test.ts +++ b/packages/client-runtime/src/jones/importedHistory/commands.test.ts @@ -4,6 +4,8 @@ import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import { importedHistoryCanonicalJson } from "@t3tools/shared/jones/importedHistoryCanonical"; import { importedHistoryIdentity } from "./commands.ts"; +const encodeImportedHistoryStart = Schema.encodeEffect(ImportedHistoryStart); + it.effect("binds schema-encoded message command and delivery to fixed SHA256 golden bytes", () => Effect.gen(function* () { const command: ImportedHistoryStart = { @@ -26,7 +28,7 @@ it.effect("binds schema-encoded message command and delivery to fixed SHA256 gol }, }, }; - const encoded = yield* Schema.encodeEffect(ImportedHistoryStart)(command); + const encoded = yield* encodeImportedHistoryStart(command); expect(importedHistoryCanonicalJson(encoded)).toBe( '{"commandId":"command:test","delivery":{"command":{"attachments":[],"commandId":"command:test","createdBy":"user","creationSource":"web","dispatchMode":{"type":"start_immediately"},"messageId":"message:test","text":"Synthetic π 😀","threadId":"thread:test","type":"message.dispatch"},"type":"message"},"reviewedBasis":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","threadId":"thread:test","type":"thread.imported-history.start"}', ); diff --git a/packages/client-runtime/src/jones/importedHistory/commands.ts b/packages/client-runtime/src/jones/importedHistory/commands.ts index f38ab2c269..85c3b6d99a 100644 --- a/packages/client-runtime/src/jones/importedHistory/commands.ts +++ b/packages/client-runtime/src/jones/importedHistory/commands.ts @@ -8,7 +8,7 @@ import { } from "@t3tools/contracts"; import { importedHistoryCanonicalJson } from "@t3tools/shared/jones/importedHistoryCanonical"; import * as Effect from "effect/Effect"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as SubscriptionRef from "effect/SubscriptionRef"; @@ -71,10 +71,12 @@ const request = Effect.fn("jonesImportedHistory.request")(function* ( }); }); +const encodeImportedHistoryStart = Schema.encodeEffect(ImportedHistoryStart); + export const importedHistoryIdentity = Effect.fn("jonesImportedHistory.identity")(function* ( command: ImportedHistoryStart, ) { - const encoded = yield* Schema.encodeEffect(ImportedHistoryStart)(command); + const encoded = yield* encodeImportedHistoryStart(command); const digest = (value: unknown) => Effect.tryPromise(async () => { const bytes = new TextEncoder().encode(importedHistoryCanonicalJson(value)); @@ -87,7 +89,10 @@ export const importedHistoryIdentity = Effect.fn("jonesImportedHistory.identity" }); export function createImportedHistoryCommands( - runtime: Atom.AtomRuntime, + runtime: Atom.AtomRuntime< + EnvironmentRegistry | HttpClient.HttpClient | SecureRandom.SecureRandom | R, + E + >, ) { return { prepare: createEnvironmentCommand(runtime, { diff --git a/packages/client-runtime/src/jones/importedHistory/continuation.ts b/packages/client-runtime/src/jones/importedHistory/continuation.ts index 56481e427e..6b2fe66569 100644 --- a/packages/client-runtime/src/jones/importedHistory/continuation.ts +++ b/packages/client-runtime/src/jones/importedHistory/continuation.ts @@ -161,10 +161,12 @@ export function resolveImportedHistoryOutcome( }; return unknown(importedHistoryReasonText(outcome.reason) ?? "The choice needs reconciliation."); } +const encodeImportedHistoryStart = Schema.encodeSync(ImportedHistoryStart); + export function encodeImportedHistoryCorrelation(value: ImportedHistoryCorrelation): string { return importedHistoryCanonicalJson({ ...value, - command: Schema.encodeSync(ImportedHistoryStart)(value.command), + command: encodeImportedHistoryStart(value.command), }); } export function decodeImportedHistoryCorrelation(raw: string): ImportedHistoryCorrelation { diff --git a/packages/client-runtime/src/jones/installPlan/model.ts b/packages/client-runtime/src/jones/installPlan/model.ts index cd153bde7f..30e60f2f55 100644 --- a/packages/client-runtime/src/jones/installPlan/model.ts +++ b/packages/client-runtime/src/jones/installPlan/model.ts @@ -50,12 +50,14 @@ function equal(left: unknown, right: unknown): boolean { ); } +const isReceipt = Schema.is(ReceiptSchema); + /** Validate against the original request first, then against the last accepted receipt. */ export function validateReceipt( prev: JonesOperationStartInput | JonesOperationReceipt, next: JonesOperationReceipt, ): boolean { - if (!Schema.is(ReceiptSchema)(next)) return false; + if (!isReceipt(next)) return false; if (!Number.isFinite(Date.parse(next.updatedAt))) return false; if (!["refused", "fenced"].includes(next.phase) && next.acceptedAt === undefined) return false; for (const key of [ diff --git a/packages/client-runtime/src/jones/pullRequestReadCompatibility.test.ts b/packages/client-runtime/src/jones/pullRequestReadCompatibility.test.ts index 5f748c9e36..f5438b3fee 100644 --- a/packages/client-runtime/src/jones/pullRequestReadCompatibility.test.ts +++ b/packages/client-runtime/src/jones/pullRequestReadCompatibility.test.ts @@ -56,9 +56,10 @@ function session(client: WsRpcProtocolClient): RpcSession { client[WS_METHODS.pullRequestsInvalidate] ?? (() => Effect.void), }, initialConfig: client[WS_METHODS.serverGetConfig]?.({}).pipe(Effect.orDie) ?? Effect.never, - subscribeServerConfig: (input) => client.subscribeServerConfig(input), + subscribeServerConfig: (input = {}) => client.subscribeServerConfig(input), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, }; } diff --git a/packages/client-runtime/src/jones/rpcObservationCompatibility.test.ts b/packages/client-runtime/src/jones/rpcObservationCompatibility.test.ts index e87d093135..008e2f27b7 100644 --- a/packages/client-runtime/src/jones/rpcObservationCompatibility.test.ts +++ b/packages/client-runtime/src/jones/rpcObservationCompatibility.test.ts @@ -34,9 +34,10 @@ function session(client: WsRpcProtocolClient): RpcSession.RpcSession { return { client, initialConfig: Effect.never, - subscribeServerConfig: (input) => client.subscribeServerConfig(input), + subscribeServerConfig: (input = {}) => client.subscribeServerConfig(input), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, }; } diff --git a/packages/client-runtime/src/jones/runtimeDetachCompatibility.test.ts b/packages/client-runtime/src/jones/runtimeDetachCompatibility.test.ts index eded18d13d..eaa2d8c966 100644 --- a/packages/client-runtime/src/jones/runtimeDetachCompatibility.test.ts +++ b/packages/client-runtime/src/jones/runtimeDetachCompatibility.test.ts @@ -16,7 +16,7 @@ import { type ProjectMutation, } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; @@ -34,11 +34,8 @@ import { v2Now, v2Projection, v2ThreadId } from "../state/orchestrationV2TestFix import { stopThreadSession } from "../operations/commands.ts"; const layerTestCrypto = Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size), - digest: (_algorithm, data) => Effect.succeed(data), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size)), ); const TARGET = new PrimaryConnectionTarget({ @@ -118,9 +115,10 @@ const makeSupervisor = Effect.fn("TestEnvironmentCommands.makeSupervisor")(funct }, }, } as never), - subscribeServerConfig: (input) => client.subscribeServerConfig(input), + subscribeServerConfig: (input = {}) => client.subscribeServerConfig(input), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, }; return EnvironmentSupervisor.EnvironmentSupervisor.of({ diff --git a/packages/client-runtime/src/jones/settlementNotifications/fetchSettlementOrigins.test.ts b/packages/client-runtime/src/jones/settlementNotifications/fetchSettlementOrigins.test.ts index 4890658b97..fea78a8b37 100644 --- a/packages/client-runtime/src/jones/settlementNotifications/fetchSettlementOrigins.test.ts +++ b/packages/client-runtime/src/jones/settlementNotifications/fetchSettlementOrigins.test.ts @@ -151,6 +151,7 @@ it.effect( }; const expectedBase = direct ? prepared.httpBaseUrl : "https://renewed-relay.test"; const authorization = RemoteEnvironmentAuthorization.of({ + renewDpopBeforeExpiry: () => Effect.die("Unexpected socket renewal in HTTP fixture"), authorizeBearer: () => Effect.die("Unexpected socket authorization"), authorizeDpop: () => Effect.die("Unexpected socket authorization"), authorizeDpopHttp: ({ expectedEnvironmentId }) => { diff --git a/packages/client-runtime/src/jones/shellPublication/shellPublicationHarness.ts b/packages/client-runtime/src/jones/shellPublication/shellPublicationHarness.ts index 43ec2235c4..5ca1e01e8a 100644 --- a/packages/client-runtime/src/jones/shellPublication/shellPublicationHarness.ts +++ b/packages/client-runtime/src/jones/shellPublication/shellPublicationHarness.ts @@ -94,6 +94,7 @@ export const makeShellPublicationHarness = Effect.fn("TestShellPublication.make" subscribeServerConfig: () => Stream.empty, ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, }; const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ @@ -131,18 +132,18 @@ export const makeShellPublicationHarness = Effect.fn("TestShellPublication.make" } const env = environments.get(ids[0]!)!; const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeedNone, - saveShell: () => Effect.void, - loadThread: () => Effect.succeedNone, - saveThread: () => Effect.void, - removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeedNone, - saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeedNone, - saveVcsRefs: () => Effect.void, - removeVcsRefs: () => Effect.void, - clearVcsRefs: () => Effect.void, - clear: () => Effect.void, + loadShell: Effect.fn("Fixture.loadShell")(() => Effect.succeedNone), + saveShell: Effect.fn("Fixture.saveShell")(() => Effect.void), + loadThread: Effect.fn("Fixture.loadThread")(() => Effect.succeedNone), + saveThread: Effect.fn("Fixture.saveThread")(() => Effect.void), + removeThread: Effect.fn("Fixture.removeThread")(() => Effect.void), + loadServerConfig: Effect.fn("Fixture.loadServerConfig")(() => Effect.succeedNone), + saveServerConfig: Effect.fn("Fixture.saveServerConfig")(() => Effect.void), + loadVcsRefs: Effect.fn("Fixture.loadVcsRefs")(() => Effect.succeedNone), + saveVcsRefs: Effect.fn("Fixture.saveVcsRefs")(() => Effect.void), + removeVcsRefs: Effect.fn("Fixture.removeVcsRefs")(() => Effect.void), + clearVcsRefs: Effect.fn("Fixture.clearVcsRefs")(() => Effect.void), + clear: Effect.fn("Fixture.clear")(() => Effect.void), }); const environmentRegistry = EnvironmentRegistry.EnvironmentRegistry.of({ entries: yield* SubscriptionRef.make>( @@ -150,25 +151,38 @@ export const makeShellPublicationHarness = Effect.fn("TestShellPublication.make" ), networkStatus: yield* SubscriptionRef.make("online"), start: Effect.void, - register: () => Effect.die("Unexpected registration"), - registerPlatform: () => Effect.die("Unexpected registration"), - reconcilePlatform: () => Effect.die("Unexpected reconciliation"), - remove: () => Effect.die("Unexpected removal"), - removeRoute: () => Effect.die("Unexpected removal"), - reorderRoutes: () => Effect.die("Unexpected reorder"), - removeRelayEnvironments: () => Effect.die("Unexpected removal"), - retryNow: () => Effect.void, - setEnabled: () => Effect.die("Unexpected toggle"), - setCompatibility: () => Effect.die("Unexpected compatibility"), - recordUpdateCompatibility: () => Effect.die("Unexpected compatibility"), - state: (id) => SubscriptionRef.get(environments.get(id)!.supervisor.state), + register: Effect.fn("Fixture.register")(() => Effect.die("Unexpected registration")), + assertKnownAddress: Effect.fn("Fixture.assertKnownAddress")(() => + Effect.die("Unexpected address assertion"), + ), + reconcilePlatform: Effect.fn("Fixture.reconcilePlatform")(() => + Effect.die("Unexpected reconciliation"), + ), + remove: Effect.fn("Fixture.remove")(() => Effect.die("Unexpected removal")), + removeRoute: Effect.fn("Fixture.removeRoute")(() => Effect.die("Unexpected removal")), + reorderRoutes: Effect.fn("Fixture.reorderRoutes")(() => Effect.die("Unexpected reorder")), + removeRelayEnvironments: Effect.fn("Fixture.removeRelayEnvironments")(() => + Effect.die("Unexpected removal"), + ), + retryNow: Effect.fn("Fixture.retryNow")(() => Effect.void), + setEnabled: Effect.fn("Fixture.setEnabled")(() => Effect.die("Unexpected toggle")), + setCompatibility: Effect.fn("Fixture.setCompatibility")(() => + Effect.die("Unexpected compatibility"), + ), + recordUpdateCompatibility: Effect.fn("Fixture.recordUpdateCompatibility")(() => + Effect.die("Unexpected compatibility"), + ), + state: Effect.fn("Fixture.state")((id) => + SubscriptionRef.get(environments.get(id)!.supervisor.state), + ), stateChanges: (id) => SubscriptionRef.changes(environments.get(id)!.supervisor.state), - run: (id, effect) => + run: Effect.fn("Fixture.run")((id, effect) => Effect.provideService( effect, EnvironmentSupervisor.EnvironmentSupervisor, environments.get(id)!.supervisor, ), + ), runStream: (id, stream) => Stream.provideService( stream, @@ -189,8 +203,9 @@ export const makeShellPublicationHarness = Effect.fn("TestShellPublication.make" Layer.succeed( ShellSnapshotLoader.ShellSnapshotLoader, ShellSnapshotLoader.ShellSnapshotLoader.of({ - load: (prepared) => + load: Effect.fn("Fixture.load")((prepared) => Effect.sync(() => environments.get(prepared.environmentId)!.httpSnapshot), + ), }), ), ), @@ -209,6 +224,7 @@ export const makeShellPublicationHarness = Effect.fn("TestShellPublication.make" ), }); const threads = createEnvironmentThreadShellAtoms({ + hiddenThreadTagsAtom: () => Atom.make(null), catalogValueAtom: catalog, snapshotAtom: lifecycle.snapshotAtom, }); diff --git a/packages/client-runtime/src/jones/sync/commandIsolation.test.ts b/packages/client-runtime/src/jones/sync/commandIsolation.test.ts index 3455bf4ba7..622df805c3 100644 --- a/packages/client-runtime/src/jones/sync/commandIsolation.test.ts +++ b/packages/client-runtime/src/jones/sync/commandIsolation.test.ts @@ -12,7 +12,7 @@ import { type OrchestrationV2Command, } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; @@ -83,11 +83,8 @@ describe("stop observation isolation evidence", () => { ), } as EnvironmentRegistry.EnvironmentRegistry["Service"]), Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size), - digest: (_algorithm, data) => Effect.succeed(data), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size)), ), ), ); diff --git a/packages/client-runtime/src/jones/threadMonitor/guardedSettle.test.ts b/packages/client-runtime/src/jones/threadMonitor/guardedSettle.test.ts index 79fcccd044..81afd4533d 100644 --- a/packages/client-runtime/src/jones/threadMonitor/guardedSettle.test.ts +++ b/packages/client-runtime/src/jones/threadMonitor/guardedSettle.test.ts @@ -8,7 +8,7 @@ import { type OrchestrationV2Command, } from "@t3tools/contracts"; import { THREAD_MONITOR_GUARDED_SETTLE_CAPABILITY } from "@t3tools/contracts/jones/guardedThreadSettlement"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; @@ -26,11 +26,8 @@ import { monitorReplyDispatchMode } from "./actions.ts"; import { settleThread, startThreadTurn } from "../../operations/commands.ts"; const crypto = Layer.succeed( - Crypto.Crypto, - Crypto.make({ - randomBytes: (size) => new Uint8Array(size), - digest: (_algorithm, bytes) => Effect.succeed(bytes), - }), + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size)), ); const makeSupervisor = Effect.fnUntraced(function* ( capability: () => string | undefined, @@ -59,9 +56,10 @@ const makeSupervisor = Effect.fnUntraced(function* ( }, } as never), ), - subscribeServerConfig: (input) => client.subscribeServerConfig(input), + subscribeServerConfig: (input = {}) => client.subscribeServerConfig(input), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, }; return EnvironmentSupervisor.EnvironmentSupervisor.of({ diff --git a/packages/client-runtime/src/jones/threadShellCompatibility.test.ts b/packages/client-runtime/src/jones/threadShellCompatibility.test.ts index 99547b6cf5..a87789c3a4 100644 --- a/packages/client-runtime/src/jones/threadShellCompatibility.test.ts +++ b/packages/client-runtime/src/jones/threadShellCompatibility.test.ts @@ -42,7 +42,11 @@ function makeHarness(environmentIds: ReadonlyArray = [environment registry: AtomRegistry.make(), snapshotAtom, catalogValueAtom, - threads: createEnvironmentThreadShellAtoms({ catalogValueAtom, snapshotAtom }), + threads: createEnvironmentThreadShellAtoms({ + hiddenThreadTagsAtom: () => Atom.make(null), + catalogValueAtom, + snapshotAtom, + }), }; } @@ -390,7 +394,7 @@ describe("delegated requests remain reachable", () => { }); it.each(["settled", null] as const)( - "reveals C in an A → B → C remote chain with A %s, without transitive badges", + "reveals C in an A → B → C remote chain with A %s, with upstream transitive approval badges", (settledOverride) => { const thirdId = EnvironmentId.make("third-environment"); const { registry, threads, snapshotAtom } = makeHarness([ @@ -422,7 +426,7 @@ describe("delegated requests remain reachable", () => { expect( registry.get(threads.threadShellAtom({ environmentId, threadId: a.id })) ?.pendingSubagentRequest, - ).toBeNull(); + ).toBe("approval"); expect( registry.get( threads.threadShellAtom({ environmentId: remoteEnvironmentId, threadId: b.id }), @@ -484,4 +488,42 @@ describe("delegated requests remain reachable", () => { } }, ); + it("propagates a live input through remote ancestors and clears every badge after response", () => { + const thirdId = EnvironmentId.make("input-third-environment"); + const { registry, threads, snapshotAtom } = makeHarness([ + environmentId, + remoteEnvironmentId, + thirdId, + ]); + const a = v2ThreadShell; + const b = { ...delegate(environmentId, a), id: ThreadId.make("input-b") }; + const c = { + ...delegate(remoteEnvironmentId, b), + id: ThreadId.make("input-c"), + pendingRuntimeRequest: { ...approval, kind: "user_input" as const }, + }; + registry.set(snapshotAtom(environmentId), { ...v2ShellSnapshot, threads: [a] }); + registry.set(snapshotAtom(remoteEnvironmentId), { ...v2ShellSnapshot, threads: [b] }); + registry.set(snapshotAtom(thirdId), { ...v2ShellSnapshot, threads: [c] }); + const dispose = registry.mount(threads.navigationThreadShellsAtom); + const refs = [ + { environmentId, threadId: a.id }, + { environmentId: remoteEnvironmentId, threadId: b.id }, + ]; + try { + for (const ref of refs) + expect(registry.get(threads.threadShellAtom(ref))?.pendingSubagentRequest).toBe( + "user_input", + ); + registry.set(snapshotAtom(thirdId), { + ...v2ShellSnapshot, + threads: [{ ...c, pendingRuntimeRequest: null }], + }); + for (const ref of refs) + expect(registry.get(threads.threadShellAtom(ref))?.pendingSubagentRequest).toBeNull(); + } finally { + dispose(); + registry.dispose(); + } + }); }); diff --git a/packages/client-runtime/src/jones/updates/jonesUpdates.test.ts b/packages/client-runtime/src/jones/updates/jonesUpdates.test.ts index 24b0bbced7..bd89cd187b 100644 --- a/packages/client-runtime/src/jones/updates/jonesUpdates.test.ts +++ b/packages/client-runtime/src/jones/updates/jonesUpdates.test.ts @@ -142,6 +142,7 @@ it.effect( } as unknown as EnvironmentRegistry["Service"]); const authorizations: Array = []; const authorization = RemoteEnvironmentAuthorization.of({ + renewDpopBeforeExpiry: () => Effect.die("Unexpected socket renewal in HTTP fixture"), authorizeBearer: () => Effect.die("Unexpected bearer authorization"), authorizeDpop: () => Effect.die("Polling must not replace the socket"), authorizeDpopHttp: (input) => diff --git a/packages/client-runtime/src/operations/commands.test.ts b/packages/client-runtime/src/operations/commands.test.ts index 30a26dedc9..5e15869468 100644 --- a/packages/client-runtime/src/operations/commands.test.ts +++ b/packages/client-runtime/src/operations/commands.test.ts @@ -1,3 +1,4 @@ +import * as DateTime from "effect/DateTime"; import { ChatAttachmentId, CheckpointId, @@ -63,6 +64,11 @@ const layerTestRandom = Layer.succeed( SecureRandom.make((size) => new Uint8Array(size)), ); +const layerTestCrypto = Layer.succeed( + SecureRandom.SecureRandom, + SecureRandom.make((size) => new Uint8Array(size)), +); + const TARGET = new PrimaryConnectionTarget({ environmentId: EnvironmentId.make("environment-1"), label: "Test environment", @@ -82,6 +88,7 @@ const makeSupervisor = Effect.fn("TestEnvironmentCommands.makeSupervisor")(funct readonly launches?: OrchestrationV2ThreadLaunchInput[]; readonly projection?: OrchestrationV2ThreadProjection; readonly projectionRequests?: ThreadId[]; + readonly advertiseServerResolvedCommandContext?: boolean; readonly advertiseThreadStop?: boolean; readonly advertiseQueuedRunStart?: boolean; readonly advertiseQueuedToolBoundaryDelivery?: boolean; @@ -136,6 +143,7 @@ const makeSupervisor = Effect.fn("TestEnvironmentCommands.makeSupervisor")(funct environment: { capabilities: { repositoryIdentity: true, + serverResolvedCommandContext: input.advertiseServerResolvedCommandContext === true, ...(input.advertiseQueuedRunStart === undefined ? {} : { queuedRunStart: input.advertiseQueuedRunStart }), @@ -384,7 +392,12 @@ describe("V2 environment commands", () => { Effect.gen(function* () { const commands: OrchestrationV2Command[] = []; const projectionRequests: ThreadId[] = []; - const supervisor = yield* makeSupervisor({ commands, projects: [], projectionRequests }); + const supervisor = yield* makeSupervisor({ + commands, + projects: [], + projectionRequests, + advertiseServerResolvedCommandContext: true, + }); for (const [mode, expectedType] of [ ["queue", "queue_after_active"], diff --git a/packages/client-runtime/src/operations/commands.ts b/packages/client-runtime/src/operations/commands.ts index d000db11c4..b3ad0733ec 100644 --- a/packages/client-runtime/src/operations/commands.ts +++ b/packages/client-runtime/src/operations/commands.ts @@ -1,3 +1,4 @@ +import { resolveDefaultWorktreeBaseBranch } from "@t3tools/shared/git"; import { requireMonitorGuardedSettle } from "../jones/threadMonitor/guardedSettle.ts"; import type { GuardedThreadSettlement } from "@t3tools/contracts/jones/guardedThreadSettlement"; import { requireQueuedRunStart } from "../jones/queue/commands.ts"; @@ -805,28 +806,85 @@ export const prepareThreadTurn = Effect.fn("EnvironmentCommands.prepareThreadTur }); } - const dispatchMode = - requestedMode === "queue" + const capabilities = (yield* getInitialServerConfig()).environment.capabilities; + const serverResolvesCommandContext = capabilities.serverResolvedCommandContext === true; + const creationSource = input.creationSource ?? "web"; + const queuedToolBoundaryEligible = + input.allowQueuedToolBoundaryDelivery !== false && + (creationSource === "web" || creationSource === "mobile") && + (requestedMode === "queue" || requestedMode === "auto") && + capabilities.queuedToolBoundaryDelivery === true; + const projection = serverResolvesCommandContext ? null : yield* getProjection(input.threadId); + const activeRun = projection?.runs.findLast( + (run) => + run.status === "preparing" || + run.status === "starting" || + run.status === "running" || + run.status === "waiting", + ); + const activeProviderThread = + activeRun === undefined + ? undefined + : projection?.providerThreads.find((thread) => thread.id === activeRun.providerThreadId); + const activeProviderSession = + activeProviderThread?.providerSessionId === null || + activeProviderThread?.providerSessionId === undefined + ? undefined + : projection?.providerSessions.find( + (session) => session.id === activeProviderThread.providerSessionId, + ); + const turnCapabilities = activeProviderSession?.capabilities.turns; + const dispatchMode = serverResolvesCommandContext + ? requestedMode === "queue" ? ({ type: "queue_after_active" } as const) - : ({ type: "start_immediately" } as const); - return yield* dispatch({ - type: "message.dispatch", - commandId, - createdBy: "user", - creationSource: input.creationSource ?? "web", - threadId: input.threadId, - messageId: input.message.messageId, - text: input.message.text, - ...(context ? { context } : {}), - ...(input.message.idleCompaction === undefined - ? {} - : { idleCompaction: input.message.idleCompaction }), - attachments, - ...(input.titleSeed === undefined ? {} : { titleSeed: input.titleSeed }), - ...(input.modelSelection === undefined ? {} : { modelSelection: input.modelSelection }), - ...(input.sourceProposedPlan === undefined ? {} : { sourcePlanRef: input.sourceProposedPlan }), - ...(requestedMode !== "queue" ? { deliveryIntent: requestedMode } : {}), - dispatchMode, + : ({ type: "start_immediately" } as const) + : activeRun === undefined + ? ({ type: "start_immediately" } as const) + : requestedMode === "steer" + ? ({ type: "steer_active", targetRunId: activeRun.id } as const) + : requestedMode === "restart" + ? ({ type: "restart_active", targetRunId: activeRun.id } as const) + : requestedMode === "queue" + ? ({ type: "queue_after_active" } as const) + : turnCapabilities?.supportsActiveSteering === true + ? ({ type: "steer_active", targetRunId: activeRun.id } as const) + : turnCapabilities?.supportsQueuedMessages === true + ? ({ type: "queue_after_active" } as const) + : turnCapabilities?.supportsSteeringByInterruptRestart === true + ? ({ type: "restart_active", targetRunId: activeRun.id } as const) + : ({ type: "queue_after_active" } as const); + const shouldSendTitleSeed = + input.titleSeed !== undefined && + (serverResolvesCommandContext || projection?.messages.length === 0); + return freezePreparedTurnRequest({ + method: ORCHESTRATION_V2_WS_METHODS.dispatchCommand, + payload: { + type: "message.dispatch", + commandId, + createdBy: "user", + creationSource: input.creationSource ?? "web", + threadId: input.threadId, + messageId: input.message.messageId, + text: input.message.text, + ...(context ? { context } : {}), + ...(input.message.idleCompaction === undefined + ? {} + : { idleCompaction: input.message.idleCompaction }), + attachments, + ...(shouldSendTitleSeed ? { titleSeed: input.titleSeed } : {}), + ...(input.modelSelection === undefined ? {} : { modelSelection: input.modelSelection }), + ...(input.modelSelectionExplicit === undefined + ? {} + : { modelSelectionExplicit: input.modelSelectionExplicit }), + ...(input.sourceProposedPlan === undefined + ? {} + : { sourcePlanRef: input.sourceProposedPlan }), + ...(serverResolvesCommandContext && requestedMode !== "queue" + ? { deliveryIntent: requestedMode } + : {}), + dispatchMode, + ...(queuedToolBoundaryEligible ? { queuedToolBoundaryEligible: true } : {}), + }, }); }); diff --git a/packages/client-runtime/src/rpc/client.ts b/packages/client-runtime/src/rpc/client.ts index ec5f55f9a6..5e39321368 100644 --- a/packages/client-runtime/src/rpc/client.ts +++ b/packages/client-runtime/src/rpc/client.ts @@ -471,7 +471,7 @@ export function subscribeDynamicWithProducer makeInput(producer), (_session, stream, producer) => stream.pipe(Stream.map((value) => [producer, value] as const)), options, - ); + ).pipe(Stream.bufferArray({ capacity: 16 })); } export function subscribe( diff --git a/packages/client-runtime/src/rpc/session.test.ts b/packages/client-runtime/src/rpc/session.test.ts index d639ff61b1..d03e40f261 100644 --- a/packages/client-runtime/src/rpc/session.test.ts +++ b/packages/client-runtime/src/rpc/session.test.ts @@ -1461,3 +1461,78 @@ describe("RpcSessionFactory", () => { }), ); }); +const LEGACY_SERVER_CONFIG = { + ...ENCODED_SERVER_CONFIG, + environment: { + ...ENCODED_SERVER_CONFIG.environment, + capabilities: { + repositoryIdentity: true, + }, + }, +}; + +it.effect.each([ + { capability: true, outcome: "success" }, + { capability: true, outcome: "failure" }, + { capability: false, outcome: "success" }, + { capability: false, outcome: "failure" }, +])( + "identifies the environment on a parentless $outcome probe span with capability=$capability", + ({ capability, outcome }) => + Effect.gen(function* () { + const spans: Tracer.NativeSpan[] = []; + const tracer = Tracer.make({ + span(options) { + const span = new Tracer.NativeSpan(options); + spans.push(span); + return span; + }, + }); + const { factory, sockets } = yield* makeFactory(); + const session = yield* factory.connect(PREPARED); + const readyFiber = yield* Effect.forkChild(session.ready); + const socket = yield* awaitSocket(sockets); + socket.open(); + yield* completeInitialConfig( + socket, + capability ? ENCODED_SERVER_CONFIG : LEGACY_SERVER_CONFIG, + ); + yield* Fiber.join(readyFiber); + + const probeFiber = yield* session.probe.pipe( + Effect.withTracer(tracer), + Effect.exit, + Effect.forkChild, + ); + const probeRequest = yield* awaitRequest(socket, 1); + expect(probeRequest.tag).toBe( + capability ? WS_METHODS.serverProbe : WS_METHODS.serverGetConfig, + ); + if (outcome === "success") { + socket.serverMessage( + encodeJson({ + _tag: "Exit", + requestId: probeRequest.id, + exit: { _tag: "Success", value: capability ? {} : LEGACY_SERVER_CONFIG }, + }), + ); + } else { + socket.close(1006, "Probe transport failure"); + } + + const result = yield* Fiber.join(probeFiber); + expect(result._tag).toBe(outcome === "success" ? "Success" : "Failure"); + const probeSpans = spans.filter( + (span) => span.name === "clientRuntime.connection.rpcSession.probe", + ); + expect(probeSpans).toHaveLength(1); + const span = probeSpans[0]!; + expect(Option.isNone(span.parent)).toBe(true); + expect(span.status).toMatchObject({ + _tag: "Ended", + exit: { _tag: result._tag }, + }); + expect(span.attributes.get("connection.environment.id")).toBe(PREPARED.environmentId); + expect(Array.from(span.attributes.values())).not.toContain(PREPARED.socketUrl); + }).pipe(Effect.scoped), +); diff --git a/packages/client-runtime/src/rpc/session.ts b/packages/client-runtime/src/rpc/session.ts index 890a4aceda..04d4a84928 100644 --- a/packages/client-runtime/src/rpc/session.ts +++ b/packages/client-runtime/src/rpc/session.ts @@ -405,8 +405,11 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* ( ), ); const probe = initialConfig.pipe( - Effect.flatMap(() => - protocolClient[WS_METHODS.serverProbe]({}).pipe(Effect.mapError(mapRpcError)), + Effect.flatMap((config) => + (config.environment.capabilities.connectionProbe === true + ? protocolClient[WS_METHODS.serverProbe]({}) + : protocolClient[WS_METHODS.serverGetConfig]({}) + ).pipe(Effect.mapError(mapRpcError)), ), Effect.asVoid, Effect.withSpan("clientRuntime.connection.rpcSession.probe", { diff --git a/packages/client-runtime/src/state/commandPermissions.test.ts b/packages/client-runtime/src/state/commandPermissions.test.ts index adfad46a41..d409934b56 100644 --- a/packages/client-runtime/src/state/commandPermissions.test.ts +++ b/packages/client-runtime/src/state/commandPermissions.test.ts @@ -31,7 +31,7 @@ import { Atom, AtomRegistry, AsyncResult } from "effect/reactivity"; import { EnvironmentRegistry } from "../connection/registry.ts"; import { createCommandPermissions } from "./commandPermissions.ts"; import { createEnvironmentRpcCommand } from "./runtime.ts"; -import { AVAILABLE_CONNECTION_STATE } from "../connection/model.ts"; +import { AVAILABLE_CONNECTION_STATE, PrimaryConnectionTarget } from "../connection/model.ts"; import { createPeerLinkEnvironmentAtoms } from "./peerLinks.ts"; vi.mock("./session.ts", () => ({ @@ -356,7 +356,12 @@ it.effect( ), }; const supervisor = { - target: { environmentId: env }, + target: new PrimaryConnectionTarget({ + environmentId: env, + label: "Synthetic peer", + httpBaseUrl: "https://peer.example.test", + wsBaseUrl: "wss://peer.example.test", + }), session: yield* SubscriptionRef.make(Option.some({ client } as unknown as RpcSession)), state: yield* SubscriptionRef.make({ ...AVAILABLE_CONNECTION_STATE, diff --git a/packages/client-runtime/src/state/models.ts b/packages/client-runtime/src/state/models.ts index 71366e0ba7..60a1b1ddb9 100644 --- a/packages/client-runtime/src/state/models.ts +++ b/packages/client-runtime/src/state/models.ts @@ -191,23 +191,7 @@ export function presentThreadShell( pendingSubagentRequest: PendingSubagentRequest | null = null, ): EnvironmentThreadShell { const updatedAt = iso(thread.updatedAt); - const latestRun = - thread.latestRunId === null - ? null - : ({ - runId: thread.latestRunId, - status: thread.status === "idle" ? "completed" : thread.status, - requestedAt: nullableIso(thread.latestRunRequestedAt ?? null), - startedAt: nullableIso(thread.latestRunStartedAt ?? null), - completedAt: - thread.latestRunCompletedAt === undefined - ? thread.status === "idle" || terminalRunStatus(thread.status) - ? updatedAt - : null - : nullableIso(thread.latestRunCompletedAt), - assistantMessageId: null, - } satisfies ThreadRunSummary); - const runtime = shellRuntime(thread); + const attention = shellAttentionInput(thread, pendingSubagentRequest); return { ...(thread.runtimeIdentity === undefined ? {} : { runtimeIdentity: thread.runtimeIdentity }), environmentId, @@ -238,12 +222,8 @@ export function presentThreadShell( ...(thread.latestUserAuthoredMessageAt === undefined ? {} : { latestUserAuthoredMessageAt: nullableIso(thread.latestUserAuthoredMessageAt) }), - hasPendingApprovals: - thread.pendingRuntimeRequest !== null && - thread.pendingRuntimeRequest.kind !== "user_input" && - thread.pendingRuntimeRequest.kind !== "auth_refresh", - // Async questions count too, so a thread with any open question leaves Working. - hasPendingUserInput: thread.pendingRuntimeRequest?.kind === "user_input", + hasPendingApprovals: attention.hasPendingApprovals, + hasPendingUserInput: attention.hasPendingUserInput, pendingSubagentRequest, pendingGuestMessageCount: thread.pendingGuestMessageCount ?? 0, hasActionableProposedPlan: thread.hasActionableProposedPlan, diff --git a/packages/client-runtime/src/state/olderSettledThreads.test.ts b/packages/client-runtime/src/state/olderSettledThreads.test.ts index 571a8fbaaa..39e13ed8d8 100644 --- a/packages/client-runtime/src/state/olderSettledThreads.test.ts +++ b/packages/client-runtime/src/state/olderSettledThreads.test.ts @@ -302,6 +302,7 @@ const makeAtoms = Effect.fn(function* () { initialConfig: Effect.succeed({} as never), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, } as unknown as RpcSession.RpcSession; const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ diff --git a/packages/client-runtime/src/state/pullRequestRouting.ts b/packages/client-runtime/src/state/pullRequestRouting.ts index be0acd7161..0944d4fa08 100644 --- a/packages/client-runtime/src/state/pullRequestRouting.ts +++ b/packages/client-runtime/src/state/pullRequestRouting.ts @@ -537,12 +537,12 @@ export function createPullRequestRouter() { ) : routed ).pipe( - Effect.catch((error) => - !isRateLimited(error) && - input.allowStale !== false && - (tag === WS_METHODS.pullRequestsSummary || tag === WS_METHODS.pullRequestsDetail) - ? request(tag, input) - : Effect.fail(error), + Effect.catchIf( + (error) => + !isRateLimited(error) && + input.allowStale !== false && + (tag === WS_METHODS.pullRequestsSummary || tag === WS_METHODS.pullRequestsDetail), + () => request(tag, input), ), ); }); diff --git a/packages/client-runtime/src/state/serverConfigProjection.test.ts b/packages/client-runtime/src/state/serverConfigProjection.test.ts index a291715a5b..6c5a941c99 100644 --- a/packages/client-runtime/src/state/serverConfigProjection.test.ts +++ b/packages/client-runtime/src/state/serverConfigProjection.test.ts @@ -1,3 +1,4 @@ +import { ProviderInstanceId } from "@t3tools/contracts"; import { DEFAULT_SERVER_SETTINGS, ServerSettings, diff --git a/packages/client-runtime/src/state/serverTokenAccounting.test.ts b/packages/client-runtime/src/state/serverTokenAccounting.test.ts index b822783bee..1ee99dc845 100644 --- a/packages/client-runtime/src/state/serverTokenAccounting.test.ts +++ b/packages/client-runtime/src/state/serverTokenAccounting.test.ts @@ -78,9 +78,10 @@ const makeHarness = Effect.fn("ServerTokenAccountingTest.makeHarness")(function* const session: RpcSession = { client, initialConfig: Effect.succeed(config), - subscribeServerConfig: (input) => client.subscribeServerConfig(input), + subscribeServerConfig: (input = {}) => client.subscribeServerConfig(input), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, }; const sessionRef = yield* SubscriptionRef.make( diff --git a/packages/client-runtime/src/state/session.ts b/packages/client-runtime/src/state/session.ts index 62515bf149..d9cd8d14ed 100644 --- a/packages/client-runtime/src/state/session.ts +++ b/packages/client-runtime/src/state/session.ts @@ -2,7 +2,6 @@ import { pipe } from "effect/Function"; import type { AuthSessionState, EnvironmentId, ServerConfig } from "@t3tools/contracts"; import * as Clock from "effect/Clock"; import * as Cause from "effect/Cause"; -import * as Result from "effect/Result"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; @@ -44,8 +43,6 @@ function initialConfigOption( const DEFAULT_SESSION_STATE_TIMEOUT_MS = 6_000; const MAX_SESSION_STATE_TIMEOUT_MS = 60_000; -const SESSION_STATE_RETRY_BASE_MS = 2_000; -const SESSION_STATE_RETRY_MAX_MS = 60_000; export const fetchEnvironmentSessionState = Effect.fn( "clientRuntime.state.fetchEnvironmentSessionState", @@ -137,6 +134,33 @@ function makeEnvironmentSessionAtoms( // This is only the bootstrap config captured when a transport session is // established. Consumers that need current provider/settings state must use // createServerEnvironmentAtoms(...).configValueAtom instead. + const recoverySignalAtom = runtime.atom( + Stream.unwrap( + Effect.gen(function* () { + const registry = yield* EnvironmentRegistry.EnvironmentRegistry; + const wakeups = yield* Effect.serviceOption(ConnectionWakeups); + const foreground = Option.isSome(wakeups) + ? wakeups.value.changes.pipe( + Stream.filter( + (reason) => + reason === "application-active" || + reason === "application-active-probe" || + reason === "application-active-long", + ), + ) + : Stream.empty; + return Stream.merge( + foreground, + SubscriptionRef.changes(registry.networkStatus).pipe( + Stream.drop(1), + Stream.filter((status) => status === "online"), + ), + ).pipe(Stream.mapEffect(() => Clock.currentTimeMillis)); + }), + ), + { initialValue: -Infinity }, + ); + const initialConfigValueAtom = Atom.family((environmentId: EnvironmentId) => Atom.make((get): ServerConfig | null => pipe( @@ -220,11 +244,9 @@ function makeEnvironmentSessionAtoms( consecutiveFailures = 0; }), ), - Effect.tapError(() => { - const delayMs = Math.min( - SESSION_STATE_RETRY_BASE_MS * 2 ** consecutiveFailures, - SESSION_STATE_RETRY_MAX_MS, - ); + Effect.tapError((error) => { + const delayMs = SESSION_RETRY_DELAYS_MS[consecutiveFailures]; + if (delayMs === undefined || !isTransientSessionFailure(error)) return Effect.void; consecutiveFailures += 1; return Effect.sleep(delayMs).pipe( Effect.andThen(Effect.sync(() => get.refreshSelf())), diff --git a/packages/client-runtime/src/state/shell-sync.test.ts b/packages/client-runtime/src/state/shell-sync.test.ts index 3a933057ea..c10a49c21d 100644 --- a/packages/client-runtime/src/state/shell-sync.test.ts +++ b/packages/client-runtime/src/state/shell-sync.test.ts @@ -1237,3 +1237,407 @@ describe("environment shell synchronization", () => { }), ); }); + +it.effect.each([true, false])( + "CR-R3: omission ahead of removal invalidates a stale family row (shell row present: %s)", + (present) => + Effect.gen(function* () { + const h = yield* makePersistenceHarness(() => Effect.void, { shellThreadRefresh: true }); + const waiting = { + ...v2ThreadShell, + pendingBackgroundTasks: [ + { + taskId: "jones-dependency:answer", + kind: "monitor" as const, + awaited: true as const, + description: "Waiting for VPS review", + }, + ], + }; + const families = [{ snapshotSequence: 10, threads: [waiting] }]; + yield* h.send( + { + kind: "snapshot", + snapshot: { + ...LIVE_SHELL_SNAPSHOT, + snapshotSequence: 10, + threads: present ? [waiting] : [], + }, + }, + () => true, + ); + yield* h.send( + { kind: "thread.omitted", sequence: 12, location: "active", threadId: waiting.id }, + () => true, + ); + const missing = yield* h.send( + { kind: "thread.removed", sequence: 11, location: "active", threadId: waiting.id }, + () => true, + ); + const state = yield* SubscriptionRef.get(h.state); + expect(state.removedAt.get(waiting.id)).toBe(12); + expect(missing.snapshotSequence).toBe(11); + expect(missing.threads).toEqual([]); + expect( + threadFamilyRowsMissingFromShell({ + shellThreads: missing.threads, + families, + removedAt: state.removedAt, + }), + ).toEqual([]); + const cleared = { ...waiting, pendingBackgroundTasks: [] }; + yield* h.send( + { kind: "thread.refreshed", sequence: 13, location: "active", thread: cleared }, + () => true, + ); + const current = yield* h.send( + { kind: "thread.omitted", sequence: 12, location: "active", threadId: waiting.id }, + () => true, + ); + expect(current.threads).toEqual([cleared]); + expect(current.snapshotSequence).toBe(11); + }), +); + +it.effect( + "CR-R3: persists quiet equal-sequence omission and fences stale rows across same-session reconnect", + () => + Effect.gen(function* () { + const saved = yield* Ref.make>([]); + yield* Effect.scoped( + Effect.gen(function* () { + const h = yield* makePersistenceHarness( + (_id, snapshot) => Ref.update(saved, (values) => [...values, snapshot]), + { shellThreadRefresh: true }, + ); + yield* Queue.take(h.subscribeInputs); + yield* h.send( + { kind: "snapshot", snapshot: { ...LIVE_SHELL_SNAPSHOT, snapshotSequence: 10 } }, + () => true, + ); + const omitted = { + kind: "thread.omitted" as const, + sequence: 12, + location: "active" as const, + threadId: v2ThreadShell.id, + }; + const missing = yield* h.send(omitted, (snapshot) => snapshot.threads.length === 0); + expect(missing.snapshotSequence).toBe(10); + expect((yield* SubscriptionRef.get(h.state)).removedAt.get(v2ThreadShell.id)).toBe(12); + yield* Queue.offer(h.wakeups, "application-active"); + expect(yield* Queue.take(h.subscribeInputs)).toMatchObject({ + afterSequence: 10, + acceptThreadRefresh: true, + }); + yield* h.send( + { kind: "thread.refreshed", sequence: 11, location: "active", thread: v2ThreadShell }, + () => true, + ); + const replay = yield* h.send( + { kind: "thread.updated", sequence: 11, location: "active", thread: v2ThreadShell }, + () => true, + ); + expect(replay.snapshotSequence).toBe(11); + expect(replay.threads).toEqual([]); + yield* h.send({ ...omitted, sequence: 11 }, () => true); + yield* h.send( + { kind: "thread.updated", sequence: 12, location: "active", thread: v2ThreadShell }, + () => true, + ); + const quiet = yield* h.send(omitted, () => true); + expect(quiet.snapshotSequence).toBe(12); + expect(quiet.threads).toEqual([]); + yield* h.send({ kind: "synchronized" }, () => true); + expect((yield* SubscriptionRef.get(h.state)).status).toBe("live"); + }), + ); + expect((yield* Ref.get(saved)).at(-1)?.threads).toEqual([]); + expect((yield* Ref.get(saved)).at(-1)?.snapshotSequence).toBe(12); + }), +); + +it.effect.each([undefined, false, true])( + "negotiates shell refresh support only when advertised as true (%s)", + (shellThreadRefresh) => + Effect.gen(function* () { + const h = yield* makePersistenceHarness( + () => Effect.void, + shellThreadRefresh === undefined ? {} : { shellThreadRefresh }, + ); + const input = yield* Queue.take(h.subscribeInputs); + if (shellThreadRefresh === true) expect(input).toMatchObject({ acceptThreadRefresh: true }); + else expect(input).not.toHaveProperty("acceptThreadRefresh"); + }), +); + +it.effect("applies and persists quiet equal-sequence resume refreshes before synchronization", () => + Effect.gen(function* () { + const saved = yield* Ref.make>([]); + yield* Effect.scoped( + Effect.gen(function* () { + const h = yield* makePersistenceHarness( + (_id, snapshot) => Ref.update(saved, (values) => [...values, snapshot]), + { shellThreadRefresh: true }, + ); + yield* Queue.take(h.subscribeInputs); + yield* h.send({ kind: "synchronized" }, () => true); + const generation = (yield* SubscriptionRef.get(h.state)).generation; + yield* Queue.offer(h.wakeups, "application-active"); + expect(yield* Queue.take(h.subscribeInputs)).toMatchObject({ + afterSequence: 1, + acceptThreadRefresh: true, + }); + expect((yield* SubscriptionRef.get(h.state)).status).toBe("synchronizing"); + const task = { + taskId: "jones-dependency:answer", + kind: "monitor", + awaited: true as const, + description: "Waiting for VPS review", + } as const; + const refreshed = yield* h.send( + { + kind: "thread.refreshed", + sequence: 1, + location: "active", + thread: { ...v2ThreadShell, pendingBackgroundTasks: [task] }, + }, + (snapshot) => snapshot.threads[0]?.pendingBackgroundTasks?.length === 1, + ); + expect(refreshed.snapshotSequence).toBe(1); + expect((yield* SubscriptionRef.get(h.state)).generation).toBe(generation); + expect((yield* SubscriptionRef.get(h.state)).status).toBe("synchronizing"); + yield* h.send({ kind: "synchronized" }, () => true); + expect((yield* SubscriptionRef.get(h.state)).status).toBe("live"); + }), + ); + expect( + (yield* Ref.get(saved)).at(-1)?.threads[0]?.pendingBackgroundTasks?.[0]?.description, + ).toBe("Waiting for VPS review"); + expect((yield* Ref.get(saved)).at(-1)?.snapshotSequence).toBe(1); + }), +); + +it.effect.each(["removed", "archived"] as const)( + "fences equal-sequence refreshes of a %s thread and resets removal facts with a snapshot", + (removal) => + Effect.gen(function* () { + const h = yield* makePersistenceHarness(() => Effect.void, { shellThreadRefresh: true }); + yield* h.send( + removal === "removed" + ? { + kind: "thread.removed", + sequence: 2, + location: "active", + threadId: v2ThreadShell.id, + } + : { + kind: "thread.updated", + sequence: 2, + location: "archive", + thread: v2ThreadShell, + }, + (snapshot) => snapshot.threads.length === 0, + ); + expect((yield* SubscriptionRef.get(h.state)).removedAt.get(v2ThreadShell.id)).toBe(2); + const ignored = yield* h.send( + { + kind: "thread.refreshed", + sequence: 2, + location: "active", + thread: v2ThreadShell, + }, + () => true, + ); + expect(ignored.threads).toEqual([]); + const generation = (yield* SubscriptionRef.get(h.state)).generation; + yield* h.send( + { kind: "snapshot", snapshot: { ...LIVE_SHELL_SNAPSHOT, snapshotSequence: 5 } }, + (snapshot) => snapshot.snapshotSequence === 5, + ); + expect((yield* SubscriptionRef.get(h.state)).generation).toBeGreaterThan(generation); + expect((yield* SubscriptionRef.get(h.state)).removedAt.size).toBe(0); + const stale = yield* h.send( + { + kind: "thread.refreshed", + sequence: 4, + location: "active", + thread: { ...v2ThreadShell, title: "Stale before snapshot" }, + }, + () => true, + ); + expect(stale.threads[0]?.title).toBe(v2ThreadShell.title); + const current = yield* h.send( + { + kind: "thread.refreshed", + sequence: 5, + location: "active", + thread: { ...v2ThreadShell, title: "Current after snapshot" }, + }, + (snapshot) => snapshot.threads[0]?.title === "Current after snapshot", + ); + expect(current.snapshotSequence).toBe(5); + }), +); + +it.effect.each(["updated", "removed", "archived"] as const)( + "preserves refresh 12 across the same thread's %s replay 11 and stale refresh 11", + (kind) => + Effect.gen(function* () { + const h = yield* makePersistenceHarness(() => Effect.void, { shellThreadRefresh: true }); + yield* h.send( + { kind: "snapshot", snapshot: { ...LIVE_SHELL_SNAPSHOT, snapshotSequence: 10 } }, + () => true, + ); + const waiting = { + ...v2ThreadShell, + pendingBackgroundTasks: [ + { + taskId: "jones-dependency:answer", + kind: "monitor" as const, + awaited: true as const, + description: "Waiting for VPS review", + }, + ], + }; + yield* h.send( + { kind: "thread.refreshed", sequence: 12, location: "active", thread: waiting }, + () => true, + ); + const stale = yield* h.send( + { kind: "thread.refreshed", sequence: 11, location: "active", thread: v2ThreadShell }, + () => true, + ); + expect(stale.snapshotSequence).toBe(10); + expect(stale.threads).toEqual([waiting]); + const replay = yield* h.send( + kind === "removed" + ? { + kind: "thread.removed", + sequence: 11, + location: "active", + threadId: waiting.id, + } + : { + kind: "thread.updated", + sequence: 11, + location: kind === "archived" ? "archive" : "active", + thread: v2ThreadShell, + }, + (snapshot) => snapshot.snapshotSequence === 11, + ); + expect(replay.threads).toEqual([waiting]); + expect((yield* SubscriptionRef.get(h.state)).removedAt.has(waiting.id)).toBe(false); + const removed = yield* h.send( + { kind: "thread.removed", sequence: 12, location: "active", threadId: waiting.id }, + () => true, + ); + expect(removed.threads).toEqual([]); + expect((yield* SubscriptionRef.get(h.state)).removedAt.get(waiting.id)).toBe(12); + const suppressed = yield* h.send( + { kind: "thread.refreshed", sequence: 12, location: "active", thread: waiting }, + () => true, + ); + expect(suppressed.threads).toEqual([]); + }), +); + +it.effect.each(["snapshot", "new producer"] as const)( + "resets refresh and removal fences with an authoritative %s", + (reset) => + Effect.gen(function* () { + const h = yield* makePersistenceHarness(() => Effect.void, { shellThreadRefresh: true }); + yield* Queue.take(h.subscribeInputs); + yield* h.send( + { kind: "snapshot", snapshot: { ...LIVE_SHELL_SNAPSHOT, snapshotSequence: 10 } }, + () => true, + ); + yield* h.send( + { + kind: "thread.refreshed", + sequence: 12, + location: "active", + thread: { ...v2ThreadShell, title: "Newer materialization" }, + }, + () => true, + ); + const otherId = ThreadId.make("removed-in-old-generation"); + yield* h.send( + { kind: "thread.removed", sequence: 11, location: "active", threadId: otherId }, + () => true, + ); + expect((yield* SubscriptionRef.get(h.state)).removedAt.get(otherId)).toBe(11); + const generation = (yield* SubscriptionRef.get(h.state)).generation; + if (reset === "new producer") { + const currentSession = Option.getOrThrow(yield* SubscriptionRef.get(h.supervisor.session)); + yield* SubscriptionRef.set(h.supervisor.session, Option.some({ ...currentSession })); + expect(yield* Queue.take(h.subscribeInputs)).not.toHaveProperty("afterSequence"); + } + const authoritative = yield* h.send( + { kind: "snapshot", snapshot: { ...LIVE_SHELL_SNAPSHOT, snapshotSequence: 5 } }, + () => true, + ); + expect(authoritative.threads[0]?.title).toBe(v2ThreadShell.title); + expect((yield* SubscriptionRef.get(h.state)).generation).toBeGreaterThan(generation); + expect((yield* SubscriptionRef.get(h.state)).removedAt.size).toBe(0); + const refreshed = yield* h.send( + { + kind: "thread.refreshed", + sequence: 6, + location: "active", + thread: { ...v2ThreadShell, title: "New generation refresh" }, + }, + () => true, + ); + expect(refreshed.snapshotSequence).toBe(5); + expect(refreshed.threads[0]?.title).toBe("New generation refresh"); + const restored = yield* h.send( + { + kind: "thread.refreshed", + sequence: 6, + location: "active", + thread: { ...v2ThreadShell, id: otherId }, + }, + () => true, + ); + expect(restored.threads.some((thread) => thread.id === otherId)).toBe(true); + }), +); + +it.effect("retains ahead-of-replay fences across enrichment and same-session resume", () => + Effect.gen(function* () { + const h = yield* makePersistenceHarness(() => Effect.void, { shellThreadRefresh: true }); + yield* Queue.take(h.subscribeInputs); + yield* h.send( + { kind: "snapshot", snapshot: { ...LIVE_SHELL_SNAPSHOT, snapshotSequence: 10 } }, + () => true, + ); + const waiting = { ...v2ThreadShell, title: "Current waiting state" }; + yield* h.send( + { kind: "thread.refreshed", sequence: 12, location: "active", thread: waiting }, + () => true, + ); + yield* h.send( + { kind: "snapshot", snapshot: LIVE_SHELL_SNAPSHOT, resolvedRepositoryIdentityRoots: [] }, + () => true, + ); + yield* Queue.offer(h.wakeups, "application-active"); + expect(yield* Queue.take(h.subscribeInputs)).toMatchObject({ afterSequence: 10 }); + const replay = yield* h.send( + { kind: "thread.updated", sequence: 11, location: "active", thread: v2ThreadShell }, + () => true, + ); + expect(replay.snapshotSequence).toBe(11); + expect(replay.threads).toEqual([waiting]); + const caughtUp = yield* h.send( + { kind: "thread.updated", sequence: 12, location: "active", thread: v2ThreadShell }, + () => true, + ); + expect(caughtUp.snapshotSequence).toBe(12); + expect(caughtUp.threads).toEqual([waiting]); + const newer = yield* h.send( + { kind: "thread.updated", sequence: 13, location: "active", thread: v2ThreadShell }, + () => true, + ); + expect(newer.threads).toEqual([v2ThreadShell]); + }), +); diff --git a/packages/client-runtime/src/state/shell.ts b/packages/client-runtime/src/state/shell.ts index cc2f61bb3e..6d31570123 100644 --- a/packages/client-runtime/src/state/shell.ts +++ b/packages/client-runtime/src/state/shell.ts @@ -1,3 +1,4 @@ +import { applyShellStreamEvent } from "./shellReducer.ts"; import { ORCHESTRATION_V2_WS_METHODS, type EnvironmentId, @@ -217,52 +218,70 @@ export const makeEnvironmentShellState = Effect.fn("EnvironmentShellState.make") items: ReadonlyArray, pendingLinkIds?: ReadonlySet, ) { - const initial = yield* SubscriptionRef.get(state); - let waiting = yield* Ref.get(awaitingCompletion); - let next = initial; - let receivedSnapshot = false; - let deltas: Array = []; - const flushDeltas = () => { - if (deltas.length > 0 && Option.isSome(next.snapshot)) { - let sequence = next.snapshot.value.snapshotSequence; - let removedAt: Map | undefined; - for (const delta of deltas) { - if (delta.sequence <= sequence) continue; - switch (delta.kind) { - case "thread.removed": - removedAt ??= new Map(next.removedAt); - removedAt.set(delta.threadId, delta.sequence); + let changed: OrchestrationV2ShellSnapshot | undefined; + yield* producerGuard.commit(producer, state, (initial) => { + let waiting = awaitingCompletion; + let next = initial; + let receivedSnapshot = false; + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + const item = items[itemIndex]!; + if ( + refreshedAt.size === 0 && + Option.isSome(next.snapshot) && + item.kind !== "snapshot" && + item.kind !== "synchronized" && + item.kind !== "thread.refreshed" && + item.kind !== "thread.omitted" + ) { + const batch: ShellStreamEvent[] = []; + let sequence = next.snapshot.value.snapshotSequence; + let removals: Map | undefined; + while (itemIndex < items.length) { + const delta = items[itemIndex]!; + if ( + delta.kind === "snapshot" || + delta.kind === "synchronized" || + delta.kind === "thread.refreshed" || + delta.kind === "thread.omitted" + ) break; - case "thread.updated": - case "project.updated": - case "project.removed": - break; - default: - continue; + batch.push(delta); + if ( + delta.sequence > sequence && + (delta.kind === "thread.removed" || + delta.kind === "thread.updated" || + delta.kind === "project.updated" || + delta.kind === "project.removed") + ) { + if ( + delta.kind === "thread.removed" || + (delta.kind === "thread.updated" && delta.location === "archive") + ) { + removals ??= new Map(next.removedAt); + removals.set( + delta.kind === "thread.removed" ? delta.threadId : delta.thread.id, + delta.sequence, + ); + } + sequence = delta.sequence; + } + itemIndex++; } - sequence = delta.sequence; + itemIndex--; + next = Object.assign({}, next, { + snapshot: Option.some(applyShellStreamEvents(next.snapshot.value, batch)), + removedAt: removals ?? next.removedAt, + status: waiting ? "synchronizing" : "live", + error: Option.none(), + }); + continue; } - next = { - ...next, - removedAt: removedAt ?? next.removedAt, - snapshot: Option.some(applyShellStreamEvents(next.snapshot.value, deltas)), - status: waiting ? "synchronizing" : "live", - error: Option.none(), - }; - } - deltas = []; - }; - for (const item of items) { - if (item.kind !== "snapshot" && item.kind !== "synchronized") { - deltas.push(item); - continue; - } - // Snapshots can reset the cursor; completion markers change the sync status. - flushDeltas(); - if (item.kind === "synchronized") { - waiting = false; - if (Option.isSome(next.snapshot)) { - next = { ...next, status: "live", error: Option.none() }; + if (item.kind === "synchronized") { + waiting = false; + if (Option.isSome(next.snapshot)) { + next = Object.assign({}, next, { status: "live", error: Option.none() }); + } + continue; } const authoritative = item.kind === "snapshot" && item.resolvedRepositoryIdentityRoots === undefined; @@ -319,39 +338,11 @@ export const makeEnvironmentShellState = Effect.fn("EnvironmentShellState.make") : new Map(next.removedAt).set(removal.threadId, removal.sequence), }; } - const authoritative = item.resolvedRepositoryIdentityRoots === undefined; - const nextSnapshot = mergeShellSnapshotProjects( - Option.getOrNull(next.snapshot), - item.snapshot, - item.resolvedRepositoryIdentityRoots === undefined - ? undefined - : { resolvedRepositoryIdentityRoots: item.resolvedRepositoryIdentityRoots }, - ); - receivedSnapshot = true; - next = { - snapshot: Option.some(nextSnapshot), - status: waiting ? "synchronizing" : "live", - error: Option.none(), - generation: authoritative ? ++lastShellGeneration : next.generation, - removedAt: authoritative ? EMPTY_REMOVALS : next.removedAt, - }; - } - flushDeltas(); - yield* Ref.set(awaitingCompletion, waiting); - if (next === initial) return; - if (pendingLinkIds !== undefined && Option.isSome(next.snapshot)) { - pendingLinkRows = new Set( - next.snapshot.value.threads.filter((thread) => pendingLinkIds.has(thread.id)), - ); - } - if (Option.isSome(next.snapshot)) { - yield* Ref.set(latestLiveSnapshot, next.snapshot); - } - yield* SubscriptionRef.set(state, next); - if (receivedSnapshot) { - const session = yield* Ref.get(activeSubscriptionSession); - if (session !== null) { - yield* Ref.set(lastAuthoritativeSession, session); + awaitingCompletion = waiting; + if (pendingLinkIds !== undefined && Option.isSome(next.snapshot)) { + pendingLinkRows = new Set( + next.snapshot.value.threads.filter((thread) => pendingLinkIds.has(thread.id)), + ); } if (Option.isSome(next.snapshot)) latestLiveSnapshot = next.snapshot; if (receivedSnapshot) lastAuthoritativeSession = producer.session; @@ -437,18 +428,36 @@ export const makeEnvironmentShellState = Effect.fn("EnvironmentShellState.make") yield* Effect.forkScoped( subscribeDynamicWithProducer( ORCHESTRATION_V2_WS_METHODS.subscribeShell, - Effect.fn("EnvironmentShellState.makeSubscribeInput")(function* (session) { - yield* Ref.set(activeSubscriptionSession, session); - const { omitIdleSubagents, omitOlderSettled } = yield* session.initialConfig.pipe( + Effect.fn("EnvironmentShellState.makeSubscribeInput")(function* (producer) { + yield* producerGuard.begin(producer); + const session = producer.session; + const { + supportsCompletionMarker, + omitIdleSubagents, + omitOlderSettled, + refreshSubscription, + } = yield* session.initialConfig.pipe( Effect.map((config) => ({ + supportsCompletionMarker: config.shellResumeCompletionMarker === true, + refreshSubscription: shellRefreshSubscription(config), omitIdleSubagents: config.shellThreadFamilies === true, omitOlderSettled: config.shellOlderSettled === true && config.shellThreadFamilies === true, })), - Effect.orElseSucceed(() => ({ omitIdleSubagents: false, omitOlderSettled: false })), + Effect.orElseSucceed(() => ({ + supportsCompletionMarker: false, + refreshSubscription: {}, + omitIdleSubagents: false, + omitOlderSettled: false, + })), + ); + yield* snapshotWriteLock.withPermit( + producerGuard.commit(producer, state, (current) => { + awaitingCompletion = supportsCompletionMarker; + return { ...current, status: "synchronizing" as const, error: Option.none() }; + }), ); - yield* Ref.set(awaitingCompletion, true); - yield* setSynchronizing; + if (!producerGuard.isCurrent(producer)) return yield* Effect.never; // Foreground resubscriptions on the same live session can resume from // the in-memory cursor. A new session reloads the authoritative HTTP @@ -505,12 +514,24 @@ export const makeEnvironmentShellState = Effect.fn("EnvironmentShellState.make") ...(omitOlderSettled ? { omitOlderSettled: true as const } : {}), }; if (!canResume || Option.isNone(current.snapshot)) { - return { ...omission, requestCompletionMarker: true as const }; + return { + ...omission, + ...(supportsCompletionMarker ? { requestCompletionMarker: true as const } : {}), + }; + } + if (!supportsCompletionMarker) { + yield* snapshotWriteLock.withPermit( + producerGuard.commit(producer, state, (value) => ({ + ...value, + status: "live" as const, + error: Option.none(), + })), + ); } return { afterSequence: current.snapshot.value.snapshotSequence, ...omission, - requestCompletionMarker: true as const, + ...(supportsCompletionMarker ? { requestCompletionMarker: true as const } : {}), }; }), { diff --git a/packages/client-runtime/src/state/shellReducer.test.ts b/packages/client-runtime/src/state/shellReducer.test.ts index f98a14c3ed..bf956321db 100644 --- a/packages/client-runtime/src/state/shellReducer.test.ts +++ b/packages/client-runtime/src/state/shellReducer.test.ts @@ -1,3 +1,4 @@ +import type { OrchestrationV2ShellSnapshot } from "@t3tools/contracts"; import { ProjectId, ThreadId, type ThreadPullRequestLink } from "@t3tools/contracts"; import { describe, expect, it } from "vite-plus/test"; @@ -387,7 +388,10 @@ describe("applyShellStreamEvents", () => { { kind: "project.removed", sequence: 18, projectId: v2Project.id }, { kind: "project.updated", sequence: 19, project: v2Project }, ]; - const sequential = events.reduce(applyShellStreamEvent, seed); + const sequential = events.reduce( + (snapshot, event) => applyShellStreamEvent(snapshot, event), + seed, + ); const next = applyShellStreamEvents(seed, events); expect(next).toEqual(sequential); expect(next.threads.map((row) => row.id)).toEqual([unchanged.id, a.id, archived.id, added.id]); @@ -410,7 +414,10 @@ describe("applyShellStreamEvents", () => { const seed = { ...v2ShellSnapshot, threads: [a, b], snapshotSequence: 4 }; const next = applyShellStreamEvents(seed, [update(5, { ...a }), update(6, { ...b })]); expect(next).toEqual( - [update(5, { ...a }), update(6, { ...b })].reduce(applyShellStreamEvent, seed), + [update(5, { ...a }), update(6, { ...b })].reduce( + (snapshot, event) => applyShellStreamEvent(snapshot, event), + seed, + ), ); expect(next.threads).toBe(seed.threads); expect(next.projects).toBe(seed.projects); @@ -426,7 +433,10 @@ describe("applyShellStreamEvents", () => { update(1, { ...a, title: "Changed" }), ]; expect(applyShellStreamEvents(v2ShellSnapshot, events)).toEqual( - events.reduce(applyShellStreamEvent, v2ShellSnapshot), + events.reduce( + (snapshot, event) => applyShellStreamEvent(snapshot, event), + v2ShellSnapshot, + ), ); }); }); diff --git a/packages/client-runtime/src/state/threadCommands.test.ts b/packages/client-runtime/src/state/threadCommands.test.ts index baca5d0bd1..c61fea0b54 100644 --- a/packages/client-runtime/src/state/threadCommands.test.ts +++ b/packages/client-runtime/src/state/threadCommands.test.ts @@ -8,7 +8,6 @@ import { ORCHESTRATION_V2_WS_METHODS, ProjectId, ProviderInstanceId, - RunId, ThreadId, type OrchestrationV2Command, type OrchestrationV2ShellSnapshot, @@ -119,6 +118,11 @@ const makeHarness = Effect.fn("TestThreadCommands.makeHarness")(function* (optio const historyController = yield* Effect.service( ThreadHistoryController.ThreadHistoryController, ).pipe(Effect.provide(ThreadHistoryController.layer)); + yield* historyController.register(ENVIRONMENT_ID, THREAD_ID, { + loadEarlier: () => options?.loadEarlier?.() ?? Effect.die("unused"), + reconcilePendingWork: () => + options?.reconcilePendingWork?.(THREAD_ID) ?? Queue.offer(reconciled, THREAD_ID), + }); const runtime = Atom.runtime( Layer.mergeAll( Layer.succeed(EnvironmentRegistry.EnvironmentRegistry, { @@ -129,16 +133,7 @@ const makeHarness = Effect.fn("TestThreadCommands.makeHarness")(function* (optio SecureRandom.SecureRandom, SecureRandom.make((size) => new Uint8Array(size)), ), - Layer.succeed( - ThreadHistoryController.ThreadHistoryController, - ThreadHistoryController.ThreadHistoryController.of({ - register: () => Effect.die("unused"), - unregister: () => Effect.void, - loadEarlier: () => options?.loadEarlier?.() ?? Effect.die("unused"), - reconcilePendingWork: (_environmentId, threadId) => - options?.reconcilePendingWork?.(threadId) ?? Queue.offer(reconciled, threadId), - }), - ), + Layer.succeed(ThreadHistoryController.ThreadHistoryController, historyController), ), ); const snapshotAtom = Atom.family((_environmentId: EnvironmentId) => Atom.make(SNAPSHOT)); diff --git a/packages/client-runtime/src/state/threadCommands.ts b/packages/client-runtime/src/state/threadCommands.ts index 534999828e..8ed17d625e 100644 --- a/packages/client-runtime/src/state/threadCommands.ts +++ b/packages/client-runtime/src/state/threadCommands.ts @@ -1,3 +1,9 @@ +import { + type StartThreadTurnInput, + type PreparedThreadTurn, + promoteQueuedBatch, + type PromoteQueuedBatchInput, +} from "../operations/commands.ts"; import type { ThreadId } from "@t3tools/contracts"; import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Effect from "effect/Effect"; diff --git a/packages/client-runtime/src/state/threadFamilies.test.ts b/packages/client-runtime/src/state/threadFamilies.test.ts index 2b2a4a6a12..e5079ccf95 100644 --- a/packages/client-runtime/src/state/threadFamilies.test.ts +++ b/packages/client-runtime/src/state/threadFamilies.test.ts @@ -133,6 +133,7 @@ describe("createThreadFamilyAtoms", () => { initialConfig: Effect.succeed({ shellThreadFamilies: true } as never), ready: Effect.void, probe: Effect.void, + inboundFrames: Effect.succeed(0), closed: Effect.never, } as unknown as RpcSession.RpcSession; const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({ diff --git a/packages/client-runtime/src/state/threadShell.test.ts b/packages/client-runtime/src/state/threadShell.test.ts index 88b6b8aac2..f71f232458 100644 --- a/packages/client-runtime/src/state/threadShell.test.ts +++ b/packages/client-runtime/src/state/threadShell.test.ts @@ -115,7 +115,7 @@ describe("v2 thread shell lists", () => { registry.dispose(); }); - it("keeps a live subagent question inside the parent without flagging its thread", () => { + it("keeps a live subagent question inside the parent with Jones input attention", () => { const { registry, threads, snapshotAtom } = makeHarness(); const root = v2ThreadShell; const child = { @@ -142,7 +142,7 @@ describe("v2 thread shell lists", () => { expect( registry.get(threads.threadShellAtom({ environmentId, threadId: root.id })) ?.pendingSubagentRequest, - ).toBeNull(); + ).toBe("user_input"); registry.set(snapshotAtom(environmentId), { ...v2ShellSnapshot, threads: [root, { ...child, status: "queued" as const }], @@ -380,7 +380,9 @@ describe("v2 thread shell lists", () => { { ...helper, pendingRuntimeRequest: { ...approval, kind: "user_input" as const } }, ], }); - expect(registry.get(threads.threadShellAtom(parentRef))?.pendingSubagentRequest).toBeNull(); + expect(registry.get(threads.threadShellAtom(parentRef))?.pendingSubagentRequest).toBe( + "user_input", + ); expect(registry.get(inputs).map((thread) => thread.id)).toEqual([helper.id]); // A task whose parent is gone there is listed on its own, as is one whose diff --git a/packages/client-runtime/src/state/threadShell.ts b/packages/client-runtime/src/state/threadShell.ts index 9c68274b7d..ef2e8a89d1 100644 --- a/packages/client-runtime/src/state/threadShell.ts +++ b/packages/client-runtime/src/state/threadShell.ts @@ -11,11 +11,7 @@ import type { ScopedThreadRef, ThreadId, } from "@t3tools/contracts"; -import { - isListedThreadLineage, - isProviderNativeSubagentThread, - OrchestrationV2ThreadShell, -} from "@t3tools/contracts"; +import { isListedThreadLineage, OrchestrationV2ThreadShell } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; import { Atom } from "effect/reactivity"; @@ -46,35 +42,6 @@ const EMPTY_THREAD_REFS_BY_PROJECT: ReadonlyMap< ReadonlyArray > = new Map(); -/** - * Local subagents and linked tasks in `delegatedUnderParent` show under their parent. - * Missing parents and asking tasks without a reachable request badge stay listed. - */ -export function isShownUnderParent( - ref: ScopedThreadRef, - lineage: OrchestrationV2ThreadShell["lineage"], - delegatedUnderParent: ReadonlySet, -): boolean { - return lineage.relationshipToParent === "subagent" || delegatedUnderParent.has(threadKey(ref)); -} - -const sameRequests = ( - left: ReadonlyMap, - right: ReadonlyMap, -) => - thread.activityRunStatus !== undefined - ? thread.activityRunStatus !== null - : thread.status === "preparing" || - thread.status === "starting" || - thread.status === "running" || - thread.status === "waiting"; - -/** - * Whether a thread shows under its parent rather than in thread lists: a - * subagent, a side chat, or a task a linked environment delegated here whose parent the - * client lists (`delegatedUnderParent`, by thread key). A task whose parent - * the client cannot list stays in the lists, so its work is never out of reach. - */ export function isShownUnderParent( ref: ScopedThreadRef, lineage: OrchestrationV2ThreadShell["lineage"], @@ -83,23 +50,6 @@ export function isShownUnderParent( return !isListedThreadLineage(lineage) || delegatedUnderParent.has(threadKey(ref)); } -/** Provider-native - subagents ask on their parent, so they are skipped. */ -function subagentRequest(thread: OrchestrationV2ThreadShell): PendingSubagentRequest | null { - const kind = thread.pendingRuntimeRequest?.kind; - if ( - kind === undefined || - kind === "auth_refresh" || - (thread.lineage.relationshipToParent !== "subagent" && thread.delegatedFrom === undefined) || - isProviderNativeSubagentThread(thread) || - !isChildRunLive(thread) - ) { - return null; - } - return kind === "user_input" ? "user_input" : "approval"; -} - -/** A side chat asks its user directly, so questions roll up to the parent as well as approvals. */ function sideChatRequest(thread: OrchestrationV2ThreadShell): PendingSubagentRequest | null { const kind = thread.pendingRuntimeRequest?.kind; if (kind === undefined || kind === "auth_refresh" || thread.lineage.sideChat !== true) { @@ -108,57 +58,6 @@ function sideChatRequest(thread: OrchestrationV2ThreadShell): PendingSubagentReq return kind === "user_input" ? "user_input" : "approval"; } -/** - * The threads to flag for a subagent's live approval, up its - * subagent chain and through a side chat to its parent. `delegated` are tasks this - * environment's threads delegated to linked environments, with what each one, or its - * own subagents, asks. - */ -function pendingSubagentRequestsByThread( - threads: ReadonlyArray, - delegated: ReadonlyArray<{ - readonly parentThreadId: ThreadId; - readonly request: PendingSubagentRequest; - }> = [], -): ReadonlyMap { - const requests = new Map(); - let byId: ReadonlyMap | undefined; - const flag = ( - firstParentId: ThreadId | null, - request: PendingSubagentRequest, - seen: Set, - ) => { - // The coordinator answers child questions; only approvals need user attention. - if (request === "user_input") return; - byId ??= new Map(threads.map((thread) => [thread.id, thread] as const)); - let parentId = firstParentId; - while (parentId !== null && !seen.has(parentId)) { - seen.add(parentId); - if (requests.get(parentId) !== "approval") requests.set(parentId, request); - const parent = byId.get(parentId); - parentId = - parent?.lineage.relationshipToParent === "subagent" || parent?.lineage.sideChat === true - ? parent.lineage.parentThreadId - : null; - } - }; - for (const child of threads) { - const parentId = child.lineage.parentThreadId; - if (child.lineage.sideChat === true) { - const request = sideChatRequest(child); - if (parentId !== null && request !== null && requests.get(parentId) !== "approval") { - requests.set(parentId, request); - } - continue; - } - if (child.lineage.relationshipToParent !== "subagent") continue; - const request = subagentRequest(child); - if (request !== null) flag(parentId, request, new Set([child.id])); - } - for (const task of delegated) flag(task.parentThreadId, task.request, new Set()); - return requests.size === 0 ? EMPTY_SUBAGENT_REQUESTS : requests; -} - const sameRequests = ( left: ReadonlyMap, right: ReadonlyMap, @@ -684,12 +583,15 @@ export function createEnvironmentThreadShellAtoms(input: { for (const environmentId of environmentIds) { for (const thread of get(environmentThreadsAtom(environmentId))) { const parent = thread.delegatedFrom; + if (parent === undefined) continue; if ( - parent !== undefined && - environmentIds.has(parent.environmentId) && - // An archived parent leaves navigation; its task there stays listed on its own. - get(environmentThreadIndexAtom(parent.environmentId)).get(parent.threadId)?.archivedAt === - null + isDelegatedTaskShownUnderParent( + thread, + environmentIds.has(parent.environmentId) + ? get(environmentThreadIndexAtom(parent.environmentId)) + : null, + get(localSubagentRequestsAtom(environmentId)), + ) ) { next.add(threadKey({ environmentId, threadId: thread.id })); } diff --git a/packages/client-runtime/src/state/threadSort.ts b/packages/client-runtime/src/state/threadSort.ts index 21a94165b1..e3d3067919 100644 --- a/packages/client-runtime/src/state/threadSort.ts +++ b/packages/client-runtime/src/state/threadSort.ts @@ -201,3 +201,20 @@ export function sortActiveThreadsByOrderKey< ); }); } + +export function planPinnedMove(input: { + readonly orderedIds: readonly string[]; + readonly keysById: ReadonlyMap; + readonly movedId: string; + readonly direction: "up" | "down"; +}): ReadonlyArray<{ readonly id: string; readonly orderKey: string }> | null { + const { orderedIds, keysById, movedId, direction } = input; + const from = orderedIds.indexOf(movedId); + if (from === -1) return null; + const to = direction === "up" ? from - 1 : from + 1; + if (to < 0 || to >= orderedIds.length) return null; + const newOrder = [...orderedIds]; + newOrder.splice(from, 1); + newOrder.splice(to, 0, movedId); + return planPinnedReorder({ orderedIds: newOrder, keysById, movedId }); +} diff --git a/packages/client-runtime/src/state/threads-sync.test.ts b/packages/client-runtime/src/state/threads-sync.test.ts index 213b66ab66..3807acfc94 100644 --- a/packages/client-runtime/src/state/threads-sync.test.ts +++ b/packages/client-runtime/src/state/threads-sync.test.ts @@ -81,10 +81,13 @@ const BASE_PROJECTION: OrchestrationV2ThreadProjection = { type TestThreadInput = OrchestrationV2ThreadStreamItem | Error; -function testSession(client: WsRpcProtocolClient): RpcSession.RpcSession { +function testSession( + client: WsRpcProtocolClient, + initialConfig: RpcSession.RpcSession["initialConfig"] = Effect.succeed({} as never), +): RpcSession.RpcSession { return { client, - initialConfig: Effect.succeed({} as never), + initialConfig, subscribeServerConfig: (input = {}) => client.subscribeServerConfig(input), ready: Effect.void, probe: Effect.void, @@ -131,6 +134,7 @@ const makeHarness = Effect.fn("TestEnvironmentThreads.makeHarness")(function* (o const latest = yield* Ref.make(EMPTY_ENVIRONMENT_THREAD_STATE); const retryCount = yield* Ref.make(0); const subscriptionCount = yield* SubscriptionRef.make(0); + const subscriptions = yield* Queue.unbounded(); const loaderCalls = yield* Ref.make(0); const lastSubscribeAfterSequence = yield* Ref.make(undefined); const lastRequestCompletionMarker = yield* Ref.make(false); @@ -164,6 +168,11 @@ const makeHarness = Effect.fn("TestEnvironmentThreads.makeHarness")(function* (o Effect.andThen(Ref.set(lastAcceptBoundedSnapshot, input.acceptBoundedSnapshot)), Effect.andThen(Ref.set(lastAcceptCompactTurnItems, input.acceptCompactTurnItems)), Effect.andThen(SubscriptionRef.update(subscriptionCount, (count) => count + 1)), + Effect.andThen( + SubscriptionRef.get(subscriptionCount).pipe( + Effect.flatMap((count) => Queue.offer(subscriptions, count)), + ), + ), Effect.as( options?.completionMarker === true ? streamFrom(inputs) @@ -175,7 +184,7 @@ const makeHarness = Effect.fn("TestEnvironmentThreads.makeHarness")(function* (o options?.getTurnItem?.(input) ?? Effect.die("Unexpected getTurnItem request"), } as unknown as WsRpcProtocolClient; const supervisorSession = yield* SubscriptionRef.make>( - Option.some(testSession(client)), + Option.some(testSession(client, options?.initialConfig)), ); const prepared = yield* SubscriptionRef.make>( Option.some(PREPARED), @@ -296,7 +305,10 @@ const makeHarness = Effect.fn("TestEnvironmentThreads.makeHarness")(function* (o savedThreads, removedThreads, wakeups, - replaceSession: SubscriptionRef.set(supervisorSession, Option.some(testSession(client))), + replaceSession: SubscriptionRef.set( + supervisorSession, + Option.some(testSession(client, options?.initialConfig)), + ), }; }); diff --git a/packages/client-runtime/src/state/threads.ts b/packages/client-runtime/src/state/threads.ts index 970ec6cd8d..1089354407 100644 --- a/packages/client-runtime/src/state/threads.ts +++ b/packages/client-runtime/src/state/threads.ts @@ -1,3 +1,8 @@ +import { + makeProducerGuard, + applyProducerBatches, + type SubscriptionProducer, +} from "../jones/sync/producerGuard.ts"; import { isOrchestrationV2WorkActive, ORCHESTRATION_V2_WS_METHODS, @@ -32,7 +37,7 @@ import * as ConnectionWakeups from "../connection/wakeups.ts"; import * as Persistence from "../platform/persistence.ts"; import { runCachePersistence } from "./cachePersistence.ts"; import * as ManagedRelay from "../relay/managedRelay.ts"; -import { request, subscribeDynamic } from "../rpc/client.ts"; +import { request, subscribeDynamicWithProducer } from "../rpc/client.ts"; import { parseThreadKey, threadKey } from "./entities.ts"; import { applyOrchestrationV2ProjectionEvent } from "./orchestrationV2Projection.ts"; import { THREAD_SNAPSHOT_IDLE_TTL_MS } from "./threadRetention.ts"; @@ -242,6 +247,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make sequence: initialSequence, }; const state = yield* SubscriptionRef.make(initialState); + const producerGuard = makeProducerGuard(supervisor.session); const canLoadHistory = Option.isSome(httpClient) && Option.isSome(historyController); const acceptsBoundedSocketSnapshots = yield* Ref.make(canLoadHistory); const lastSequence = yield* SubscriptionRef.make(initialSequence); @@ -252,7 +258,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make acceptsBoundedSnapshots: canLoadHistory, }; if (resumeCache?.owner === owner) resumeCache.snapshot = committed; - const awaitingCompletion = yield* Ref.make(false); + let awaitingCompletion = false; const applyLock = yield* Semaphore.make(1); // Save only completed data/cursor updates. A canceled scope must not cache // a cursor whose event has not reached the data yet. @@ -375,23 +381,22 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make }, ); const setDisconnected = Effect.gen(function* () { - yield* Ref.set(awaitingCompletion, false); + awaitingCompletion = false; yield* SubscriptionRef.update(state, (current) => ({ ...current, status: current.status === "deleted" ? current.status : statusWithoutLiveData(current.data), })); }); - const setStreamError = (message: string) => - Ref.set(awaitingCompletion, false).pipe( - Effect.andThen( - SubscriptionRef.update(state, (current) => ({ - ...current, - status: - current.status === "deleted" ? current.status : statusWithoutLiveData(current.data), - error: Option.some(message), - })), - ), - ); + const setStreamError = (message: string, producer: SubscriptionProducer) => + SubscriptionRef.update(state, (current) => { + if (!producerGuard.isCurrent(producer)) return current; + awaitingCompletion = false; + return { + ...current, + status: current.status === "deleted" ? current.status : statusWithoutLiveData(current.data), + error: Option.some(message), + }; + }); const setThread = Effect.fn("EnvironmentThreadState.setThread")(function* ( thread: OrchestrationV2ThreadProjection, @@ -399,13 +404,15 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make readonly resetHistory?: boolean; readonly history?: ThreadHistoryMeta; }, + producer?: SubscriptionProducer, ) { - const waiting = yield* Ref.get(awaitingCompletion); + const waiting = awaitingCompletion; const sequence = yield* SubscriptionRef.get(lastSequence); // Atomic with concurrent history meta updates: never get-then-set the whole // state when only the projection changes. Bounded installs pass history so // projection + cursor persist together in one enqueue. const next = yield* SubscriptionRef.updateAndGet(state, (previous) => { + if (producer !== undefined && !producerGuard.isCurrent(producer)) return previous; const history = options?.history !== undefined ? options.history @@ -428,6 +435,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make // Active projections can update many times per second and retain large tool // payloads. Persist once the run settles so cache encoding stays off the // streaming path. Progressive meta rides along when the window is incomplete. + if (producer !== undefined && !producerGuard.isCurrent(producer)) return; if (shouldPersistThread(thread, next.history)) { const snapshotSequence = yield* SubscriptionRef.get(lastSequence); yield* queuePersist( @@ -448,7 +456,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make })); const setDeleted = Effect.fn("EnvironmentThreadState.setDeleted")(function* () { - yield* Ref.set(awaitingCompletion, false); + awaitingCompletion = false; yield* SubscriptionRef.set(state, { data: Option.none(), status: "deleted", @@ -478,6 +486,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make >; const applyEventsLocked = Effect.fn("EnvironmentThreadState.applyEventsLocked")(function* ( items: ReadonlyArray, + producer: SubscriptionProducer, ) { const appliedSequence = yield* SubscriptionRef.get(lastSequence); let sequence = appliedSequence; @@ -502,7 +511,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make yield* SubscriptionRef.set(lastSequence, sequence); if (fresh.length === 0) return; - const waiting = yield* Ref.get(awaitingCompletion); + const waiting = awaitingCompletion; // Apply against the latest projection/history in one update so a concurrent // loadEarlier merge (or history-meta patch) cannot be clobbered by a stale // get-then-set rebuild. @@ -568,6 +577,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make const result = yield* SubscriptionRef.modify( state, (current): readonly [EventApplyResult, EnvironmentThreadState] => { + if (!producerGuard.isCurrent(producer)) return [{ _tag: "noop" }, current]; let result: EventApplyResult = { _tag: "noop" }; for (const item of fresh) { const [applied, next] = applyEvent(current, item); @@ -599,14 +609,17 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make const applyItemLocked = Effect.fn("EnvironmentThreadState.applyItemLocked")(function* ( item: OrchestrationV2ThreadStreamItem, + producer: SubscriptionProducer, ) { if (item.kind === "synchronized") { - yield* Ref.set(awaitingCompletion, false); - yield* SubscriptionRef.update(state, (current) => - Option.isSome(current.data) && current.status !== "deleted" && Option.isNone(current.error) + yield* producerGuard.commit(producer, state, (current) => { + awaitingCompletion = false; + return Option.isSome(current.data) && + current.status !== "deleted" && + Option.isNone(current.error) ? { ...current, status: "live" as const, error: Option.none() } - : current, - ); + : current; + }); return; } @@ -630,18 +643,21 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make }, } : { resetHistory: true }, + producer, ); return; } - yield* applyEventsLocked([item]); + yield* applyEventsLocked([item], producer); }); const applyItems = Effect.fn("EnvironmentThreadState.applyItems")(function* ( + producer: SubscriptionProducer, items: ReadonlyArray, ) { yield* applyLock.withPermits(1)( Effect.gen(function* () { + if (!producerGuard.isCurrent(producer)) return; let events: SequencedItem[] = []; for (const item of items) { if ( @@ -651,12 +667,12 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make events.push(item); continue; } - yield* applyEventsLocked(events); + yield* applyEventsLocked(events, producer); events = []; - yield* applyItemLocked(item); + yield* applyItemLocked(item, producer); } - yield* applyEventsLocked(events); - yield* remember; + yield* applyEventsLocked(events, producer); + if (producerGuard.isCurrent(producer)) yield* remember; }), ); }); @@ -761,7 +777,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make ? "History did not advance." : "Work history did not advance.", ); - const waiting = yield* Ref.get(awaitingCompletion); + const waiting = awaitingCompletion; const result = yield* applyLock.withPermits(1)( SubscriptionRef.modify( state, @@ -934,9 +950,12 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make yield* markSynchronizing; yield* Effect.forkScoped( - subscribeDynamic( + subscribeDynamicWithProducer( ORCHESTRATION_V2_WS_METHODS.subscribeThread, - Effect.fn("EnvironmentThreadState.makeSubscribeInput")(function* (session) { + Effect.fn("EnvironmentThreadState.makeSubscribeInput")(function* (producer) { + yield* producerGuard.begin(producer); + const session = producer.session; + yield* Effect.ignore(session.initialConfig); let current = yield* SubscriptionRef.get(state); // A prior definitive miss (or delete event) already cleared this thread. // Park the subscription attempt without opening the socket so we do not @@ -945,8 +964,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make return yield* Effect.never; } - yield* Effect.ignore(session.initialConfig); - yield* Ref.set(awaitingCompletion, true); + awaitingCompletion = true; yield* markSynchronizing; yield* Ref.set(resumingLive, false); @@ -967,6 +985,7 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make ); const httpResult: ThreadSnapshotLoader.ThreadSnapshotLoadResult = yield* snapshotLoader.load(prepared, threadId); + if (!producerGuard.isCurrent(producer)) return yield* Effect.never; switch (httpResult._tag) { case "present": { if (canLoadHistory && httpResult.history !== undefined) { @@ -989,8 +1008,8 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make latestLocalTurnOrdinal: httpResult.history.latestLocalTurnOrdinal ?? null, } : EMPTY_THREAD_HISTORY_META; - yield* setThread(httpResult.snapshot.projection, { history }); - yield* remember; + yield* setThread(httpResult.snapshot.projection, { history }, producer); + if (producerGuard.isCurrent(producer)) yield* remember; }), ); current = yield* SubscriptionRef.get(state); @@ -1028,12 +1047,13 @@ export const makeEnvironmentThreadState = Effect.fn("EnvironmentThreadState.make }; }), { - onDefect: () => setStreamError("Could not synchronize the thread."), - onExpectedFailure: (cause) => setStreamError(formatThreadError(cause)), + onDefect: (_cause, producer) => + setStreamError("Could not synchronize the thread.", producer), + onExpectedFailure: (cause, producer) => setStreamError(formatThreadError(cause), producer), retryExpectedFailureAfter: "250 millis", resubscribe: foregroundResubscriptions, }, - ).pipe(Stream.runForEachArray(applyItems)), + ).pipe(Stream.runForEachArray((items) => applyProducerBatches(items, applyItems))), ); return state; diff --git a/packages/client-runtime/src/state/workstreams/placements.ts b/packages/client-runtime/src/state/workstreams/placements.ts index 4c78e6584e..6b8711c36b 100644 --- a/packages/client-runtime/src/state/workstreams/placements.ts +++ b/packages/client-runtime/src/state/workstreams/placements.ts @@ -38,6 +38,9 @@ export type WorkstreamSha256 = (bytes: Uint8Array) => Promise; const browserSha256: WorkstreamSha256 = async (bytes) => new Uint8Array(await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes))); +const decodePlacementLoadRequest = Schema.decodeSync(T3PlacementLoadRequest); +const decodePlacementResult = Schema.decodeSync(T3PlacementResult); + export async function loadLiveT3Placements( metadata: T3WorkstreamListResult, identities: readonly T3PlacementIdentity[], @@ -47,13 +50,13 @@ export async function loadLiveT3Placements( ): Promise { if (metadata.source !== "live" || metadata.stale || metadata.nextCursor !== null) throw new Error("Placement metadata is not current."); - Schema.decodeSync(T3PlacementLoadRequest)({ identities }, { onExcessProperty: "error" }); + decodePlacementLoadRequest({ identities }, { onExcessProperty: "error" }); const digest = await sha256(new TextEncoder().encode(t3PlacementInventoryJson(identities))); if (digest.length !== 32) throw new Error("Invalid SHA-256 digest length."); const inventoryDigest = Array.from(digest, (value) => value.toString(16).padStart(2, "0")).join( "", ); - const result = Schema.decodeSync(T3PlacementResult)(await load(), { + const result = decodePlacementResult(await load(), { onExcessProperty: "error", }); if (new TextEncoder().encode(JSON.stringify(result)).length > WORKSTREAM_MAX_RESPONSE_BYTES) diff --git a/packages/client-runtime/src/voice-review.test.ts b/packages/client-runtime/src/voice-review.test.ts index acf4d25b9b..dc4a7d9256 100644 --- a/packages/client-runtime/src/voice-review.test.ts +++ b/packages/client-runtime/src/voice-review.test.ts @@ -121,6 +121,7 @@ it.effect( }), }); const remoteAuthorization = RemoteEnvironmentAuthorization.of({ + renewDpopBeforeExpiry: () => Effect.die("Unexpected socket renewal in HTTP fixture"), authorizeBearer: () => Effect.die("unexpected"), authorizeDpop: () => Effect.die("unexpected"), authorizeDpopHttp: () => @@ -177,6 +178,7 @@ it.effect("signs the exact encoded Voice Review and registry request URLs", () = }), }); const remoteAuthorization = RemoteEnvironmentAuthorization.of({ + renewDpopBeforeExpiry: () => Effect.die("Unexpected socket renewal in HTTP fixture"), authorizeBearer: () => Effect.die("unexpected"), authorizeDpop: () => Effect.die("unexpected"), authorizeDpopHttp: () => diff --git a/packages/client-runtime/src/voice-review.ts b/packages/client-runtime/src/voice-review.ts index 68d9a0ea97..a96508040e 100644 --- a/packages/client-runtime/src/voice-review.ts +++ b/packages/client-runtime/src/voice-review.ts @@ -194,6 +194,8 @@ export const fetchVoiceReviewDiagnostics = Effect.fn("clientRuntime.voiceReview. }, ); +const decodeThreadRegistryAssociation = Schema.decodeEffect(ThreadRegistryAssociationPayload); + export const correctThreadRegistryAssociation = Effect.fn( "clientRuntime.voiceReview.correctAssociation", )(function* ( @@ -201,7 +203,7 @@ export const correctThreadRegistryAssociation = Effect.fn( readonly payload: typeof ThreadRegistryAssociationPayload.Type; }, ) { - const payload = yield* Schema.decodeEffect(ThreadRegistryAssociationPayload)(input.payload); + const payload = yield* decodeThreadRegistryAssociation(input.payload); return yield* unwrapReviewError( executeAuthenticatedEnvironmentHttpRequest({ ...input, diff --git a/packages/contracts/src/device.test.ts b/packages/contracts/src/device.test.ts index c91bd9d268..b2163f1969 100644 --- a/packages/contracts/src/device.test.ts +++ b/packages/contracts/src/device.test.ts @@ -110,3 +110,38 @@ describe("device boot errors", () => { expect(message).not.toContain("Try again"); }); }); + +const decodeDirectAccess = Schema.decodeUnknownSync(DeviceDirectAccess); + +it("accepts an optional desktop SSH target and rejects option or whitespace injection", () => { + const parse = Schema.decodeUnknownSync(SshDeviceHostConfig); + expect( + parse({ id: "mini", label: "Mini", target: "server-mini" }).directSshTarget, + ).toBeUndefined(); + expect( + parse({ id: "mini", label: "Mini", target: "server-mini", directSshTarget: "laptop-mini" }) + .directSshTarget, + ).toBe("laptop-mini"); + for (const directSshTarget of [ + "-oProxyCommand=command", + "mini command", + "mini\ncommand", + "user@mini", + "mini:22", + "[::1]", + "m".repeat(256), + ]) + expect(() => + parse({ id: "mini", label: "Mini", target: "server-mini", directSshTarget }), + ).toThrow(); + expect(() => + decodeDirectAccess({ + target: "mini", + gatewayPort: 65536, + owner: "owner", + generation: "gen", + grant: "grant", + expiresAt: 1, + }), + ).toThrow(); +}); diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts index d5015b9048..1f731c294d 100644 --- a/packages/contracts/src/ipc.ts +++ b/packages/contracts/src/ipc.ts @@ -310,6 +310,8 @@ export interface DesktopUpdateState { notAppliedVersion?: string; /** Off only because a managed policy rules out this channel; switching channels turns it on. */ channelBlockedByPolicy?: true; + jones?: JonesUpdateState; + jonesPreparation?: JonesDesktopPreparationState; } export interface DesktopUpdateReleaseNote { @@ -1054,10 +1056,61 @@ export const DesktopMediaReadSchema = Schema.Struct({ }); export type DesktopMediaRead = typeof DesktopMediaReadSchema.Type; +export const DesktopPreviewAutomationClickInputSchema = Schema.Struct({ + tabId: DesktopPreviewTabIdSchema, + deadlineMs: Schema.optional(Schema.Number), + input: PreviewAutomationClickInput, +}); + +export const DesktopPreviewAutomationTypeInputSchema = Schema.Struct({ + tabId: DesktopPreviewTabIdSchema, + deadlineMs: Schema.optional(Schema.Number), + input: PreviewAutomationTypeInput, +}); + +export const DesktopPreviewAutomationPressInputSchema = Schema.Struct({ + tabId: DesktopPreviewTabIdSchema, + deadlineMs: Schema.optional(Schema.Number), + input: PreviewAutomationPressInput, +}); + +export const DesktopPreviewAutomationScrollInputSchema = Schema.Struct({ + tabId: DesktopPreviewTabIdSchema, + deadlineMs: Schema.optional(Schema.Number), + input: PreviewAutomationScrollInput, +}); + +export const DesktopPreviewAutomationEvaluateInputSchema = Schema.Struct({ + tabId: DesktopPreviewTabIdSchema, + deadlineMs: Schema.optional(Schema.Number), + input: PreviewAutomationEvaluateInput, +}); + +export const DesktopPreviewAutomationWaitForInputSchema = Schema.Struct({ + tabId: DesktopPreviewTabIdSchema, + deadlineMs: Schema.optional(Schema.Number), + input: PreviewAutomationWaitForInput, +}); + export const SystemSettingsPaneSchema = Schema.Literals(["full-disk-access"]); export type SystemSettingsPane = typeof SystemSettingsPaneSchema.Type; export interface DesktopBridge { + getPreviewAutomationRuntimeIdentity?: () => Promise; + getConnectionCatalogSnapshot?: () => Promise; + listConnectionCompatibilityHints?: () => Promise< + ReadonlyArray<{ + readonly environmentId: EnvironmentId; + readonly routeBinding: string; + readonly kind: "outdated-update"; + }> + >; + setConnectionCompatibilityHint?: (input: { + readonly environmentId: EnvironmentId; + readonly routeBinding: string; + readonly kind: "outdated-update" | null; + }) => Promise; + threadMonitor?: DesktopThreadMonitorMainBridge; previewCompanion?: DesktopCompanionBridge; getAppBranding: () => DesktopAppBranding | null; @@ -1167,9 +1220,15 @@ export interface DesktopBridge { minimumVersion?: string, ) => Promise; checkForUpdate: () => Promise; - downloadUpdate: () => Promise; - /** With a version, installs only that downloaded build. */ - installUpdate: (expectedVersion?: string) => Promise; + downloadUpdate: (selection?: JonesUpdateDownloadInput) => Promise; + discardUpdate?: (stagedHandle: string) => Promise; + /** Optional: older desktop builds cannot prepare Jones updates in the background. */ + setJonesBackgroundPreparation?: (enabled: boolean) => Promise; + installUpdate: (stagedHandle?: string, campaignId?: string) => Promise; + fleetUpdates?: (request: FleetDesktopRequest) => Promise; + /** Older desktop builds lack install-plan support. */ + installPlan?: (request: JonesInstallPlanRequest) => Promise; + onInstallPlan?: (listener: (state: JonesInstallPlanState) => void) => () => void; onUpdateState: (listener: (state: DesktopUpdateState) => void) => () => void; cliCommand: { getState: () => Promise; diff --git a/packages/contracts/src/jones/environmentHttpGroups.ts b/packages/contracts/src/jones/environmentHttpGroups.ts index 761fd5a3ee..12db879839 100644 --- a/packages/contracts/src/jones/environmentHttpGroups.ts +++ b/packages/contracts/src/jones/environmentHttpGroups.ts @@ -168,6 +168,7 @@ export type EnvironmentConversationLibraryError = export const EnvironmentConversationLibraryErrorCode = LibraryErrorCodeSchema; export const EnvironmentWorkstreamOperationConflictError = WorkstreamOperationConflictError.pipe( + (schema) => schema.annotate({ httpApiStatus: 409 }), HttpApiSchema.status(409), ); export type EnvironmentWorkstreamOperationConflictError = diff --git a/packages/contracts/src/jones/fleetUpdates/http.ts b/packages/contracts/src/jones/fleetUpdates/http.ts index 7a8173f044..04e39b76ae 100644 --- a/packages/contracts/src/jones/fleetUpdates/http.ts +++ b/packages/contracts/src/jones/fleetUpdates/http.ts @@ -27,7 +27,7 @@ export function makeFleetUpdatesHttpGroup({ error: [ EnvironmentScopeRequiredError, EnvironmentInternalError, - FleetHostError.pipe(HttpApiSchema.status(409)), + FleetHostError.annotate({ httpApiStatus: 409 }).pipe(HttpApiSchema.status(409)), ], success: FleetHostStatus, } as const; diff --git a/packages/contracts/src/jones/upstreamWireCompatibility.test.ts b/packages/contracts/src/jones/upstreamWireCompatibility.test.ts index 5dcfd77f2b..7bed7f2463 100644 --- a/packages/contracts/src/jones/upstreamWireCompatibility.test.ts +++ b/packages/contracts/src/jones/upstreamWireCompatibility.test.ts @@ -1,8 +1,16 @@ +import { ClientSettingsSchema } from "../settings.ts"; import * as Schema from "effect/Schema"; import { describe, expect, it } from "vite-plus/test"; -import { OrchestrationV2ThreadHistoryPage } from "../orchestrationV2.ts"; +import { + OrchestrationV2ThreadHistoryPage, + OrchestrationV2TurnItem, + OrchestrationV2Checkpoint, +} from "../orchestrationV2.ts"; +const turnItemCodec = Schema.toCodecJson(OrchestrationV2TurnItem); +const encodeTurnItem = Schema.encodeSync(turnItemCodec); +const decodeTurnItem = Schema.decodeUnknownSync(turnItemCodec); const codec = Schema.toCodecJson(OrchestrationV2ThreadHistoryPage); const decode = Schema.decodeUnknownSync(codec); const encode = Schema.encodeSync(codec); @@ -96,3 +104,79 @@ describe("Jones fields on imported upstream wire codecs", () => { expect(decoded.items[0]?.item).not.toHaveProperty("remoteChild"); }); }); + +describe("foundation upgrade compatibility", () => { + it("decodes old design settings while preserving Jones client defaults", () => { + const decode = Schema.decodeUnknownSync(ClientSettingsSchema); + for (const redesignEnabled of [true, false]) { + for (const legacySidebarEnabled of [true, false]) { + expect(decode({ redesignEnabled, legacySidebarEnabled })).toEqual({ + ...decode({}), + legacySidebarEnabled, + }); + expect(decode({ redesignEnabled, legacySidebarEnabled }).promptsDefaultStage).toBe( + "pending", + ); + } + } + }); + + it("keeps reason and linkStatus required for new upstream link records", () => { + const decode = decodeTurnItem; + const command = { + id: "item:link", + type: "link_request", + threadId: item.threadId, + runId: item.runId, + nodeId: item.nodeId, + providerThreadId: null, + providerTurnId: null, + nativeItemRef: null, + parentItemId: null, + ordinal: 1, + status: "completed", + title: null, + startedAt: null, + completedAt: null, + updatedAt: item.updatedAt, + reason: "Synthetic linked workspace", + linkStatus: "pending", + }; + expect(encodeTurnItem(decode(command))).toEqual(command); + const { reason: _reason, ...withoutReason } = command; + const { linkStatus: _status, ...withoutStatus } = command; + expect(() => decode(withoutReason)).toThrow(); + expect(() => decode(withoutStatus)).toThrow(); + }); + + it("round trips a capped checkpoint prefix with totals for the full file set", () => { + const codec = Schema.toCodecJson(OrchestrationV2Checkpoint); + const files = Array.from({ length: 200 }, (_, index) => ({ + path: `file-${index}.ts`, + kind: "modified", + additions: 1, + deletions: 0, + })); + const wire = { + id: "checkpoint:1", + threadId: "thread:parent", + scopeId: "scope:1", + runId: "run:parent", + nodeId: "node:parent", + parentCheckpointId: null, + ordinalWithinScope: 1, + appRunOrdinal: 1, + ref: "refs/t3/checkpoint-1", + status: "ready", + files, + filesTotal: { count: 231, additions: 300, deletions: 40 }, + capturedAt: "2026-10-25T00:00:00.000Z", + }; + const value = Schema.decodeSync(codec)(wire); + expect(value.files).toHaveLength(200); + expect(value.filesTotal?.count).toBe(231); + expect(Schema.encodeSync(codec)(value)).toEqual(wire); + const { filesTotal: _totals, ...oldWire } = wire; + expect(Schema.encodeSync(codec)(Schema.decodeSync(codec)(oldWire))).toEqual(oldWire); + }); +}); diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index f51d44eda4..9ad862f06b 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -773,6 +773,14 @@ export function orchestrationV2WorktreePending( ); } +export const OrchestrationV2ProviderSettlement = Schema.Struct({ + runAttemptId: RunAttemptId, + providerTurnId: ProviderTurnId, + status: Schema.Literals(["completed", "interrupted", "failed", "cancelled"]), + completedAt: Schema.DateTimeUtc, +}); +export type OrchestrationV2ProviderSettlement = typeof OrchestrationV2ProviderSettlement.Type; + export const OrchestrationV2RunAttempt = Schema.Struct({ id: RunAttemptId, // Absence preserves historical replay; null requires attributed provider evidence. @@ -2403,6 +2411,15 @@ export const OrchestrationV2RunJson = OrchestrationV2Run.mapFields((fields) => ( })); export type OrchestrationV2RunJson = typeof OrchestrationV2RunJson.Type; +export const OrchestrationV2ProviderSettlementJson = OrchestrationV2ProviderSettlement.mapFields( + (fields) => ({ + ...fields, + completedAt: Schema.DateTimeUtcFromString, + }), +); +export type OrchestrationV2ProviderSettlementJson = + typeof OrchestrationV2ProviderSettlementJson.Type; + export const OrchestrationV2RunAttemptJson = OrchestrationV2RunAttempt.mapFields((fields) => ({ ...fields, providerSettlement: Schema.optional(Schema.NullOr(OrchestrationV2ProviderSettlementJson)), @@ -3583,9 +3600,25 @@ const OrchestrationV2InternalCommand = Schema.Union([ title: Schema.optional(TrimmedNonEmptyString), driver: ProviderDriverKind, modelSelection: ModelSelection, - remoteChild: OrchestrationV2RemoteTaskChild, + remoteChild: Schema.optional(OrchestrationV2RemoteTaskChild), completionWake: Schema.optional(Schema.Literals(["always", "settled_only"])), }), + /** Records a nonterminal hold; approval remains owned by the receiving environment. */ + Schema.Struct({ + type: Schema.Literal("delegated_task.remote.hold"), + commandId: CommandId, + parentThreadId: ThreadId, + taskId: NodeId, + progress: Schema.String, + }), + /** Attaches the acknowledged remote child to its reservation, including after restart. */ + Schema.Struct({ + type: Schema.Literal("delegated_task.remote.bind"), + commandId: CommandId, + parentThreadId: ThreadId, + taskId: NodeId, + remoteChild: OrchestrationV2RemoteTaskChild, + }), /** * Completes a delegated task that ran in a linked environment, with what its * thread there ended as. The parent wakes exactly as for a child here. A diff --git a/packages/contracts/src/orchestratorMcp.ts b/packages/contracts/src/orchestratorMcp.ts index 2f1e39ff04..707e92d33a 100644 --- a/packages/contracts/src/orchestratorMcp.ts +++ b/packages/contracts/src/orchestratorMcp.ts @@ -540,6 +540,8 @@ export const OrchestratorMcpThreadReadResult = Schema.Struct({ export type OrchestratorMcpThreadReadResult = typeof OrchestratorMcpThreadReadResult.Type; export const OrchestratorMcpThreadSendInput = Schema.Struct({ + awaitResult: Schema.optional(Schema.Boolean), + resultRequest: Schema.optional(ThreadResultRequestDescriptor), environmentId: OrchestratorMcpEnvironmentTarget, threadId: ThreadId, message: OrchestratorMcpPrompt, @@ -561,6 +563,9 @@ export const OrchestratorMcpThreadSendResult = Schema.Struct({ export type OrchestratorMcpThreadSendResult = typeof OrchestratorMcpThreadSendResult.Type; export const OrchestratorMcpThreadWaitInput = Schema.Struct({ + childProgressForRunId: Schema.optional(RunId), + observeBlockingQuestions: Schema.optional(Schema.Boolean), + questionResumeToken: Schema.optional(Schema.String.check(Schema.isMaxLength(16_384))), environmentId: OrchestratorMcpEnvironmentTarget, threadId: ThreadId, runId: Schema.optional(RunId), diff --git a/packages/contracts/src/server.ts b/packages/contracts/src/server.ts index b301e7084a..10723ff0ca 100644 --- a/packages/contracts/src/server.ts +++ b/packages/contracts/src/server.ts @@ -1,3 +1,4 @@ +import { TrustedT3PlacementEnvironment } from "./workstreamPlacements.ts"; import { QualifiedQuota } from "./providerQueue.ts"; import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index ea325b1b77..3213e9acd5 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -1,3 +1,9 @@ +import { + threadActivitySettingsFields, + threadActivitySettingsPatchFields, +} from "./jones/threadActivitySettings.ts"; +import { AgentDecisionSnapshotAccessEnabled } from "./jones/decisionSnapshotAccess.ts"; +import { WorkModeEnabled } from "./jones/workMode.ts"; import { EasDeviceHostConfigs, SimlockDeviceHostConfigs, SshDeviceHostConfigs } from "./device.ts"; import { AuthOrchestrationOperateScope, diff --git a/packages/contracts/src/threadMetadataMcp.ts b/packages/contracts/src/threadMetadataMcp.ts index e309149e77..d023ed4dba 100644 --- a/packages/contracts/src/threadMetadataMcp.ts +++ b/packages/contracts/src/threadMetadataMcp.ts @@ -40,7 +40,12 @@ const ThreadMetadataClientRequestId = TrimmedNonEmptyString.check(Schema.isMaxLe ) .annotate({ description: "Stable idempotency key to reuse when retrying this mutation." }); -export const ThreadMetadataMcpAction = Schema.Literals(["rename", "regenerate_title"]); +export const ThreadMetadataMcpAction = Schema.Literals([ + "rename", + "regenerate_title", + "block_thread_messages", + "allow_thread_messages", +]); export type ThreadMetadataMcpAction = typeof ThreadMetadataMcpAction.Type; export const ThreadMetadataMcpUpdateInput = Schema.Struct({ diff --git a/packages/shared/package.json b/packages/shared/package.json index 018c9cd782..2f152b63e7 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -626,6 +626,10 @@ "./dateTime": { "types": "./src/dateTime.ts", "import": "./src/dateTime.ts" + }, + "./composerContextLegacySend": { + "types": "./src/composerContextLegacySend.ts", + "import": "./src/composerContextLegacySend.ts" } }, "scripts": { diff --git a/packages/shared/src/composerContextLegacySend.ts b/packages/shared/src/composerContextLegacySend.ts new file mode 100644 index 0000000000..7691eda952 --- /dev/null +++ b/packages/shared/src/composerContextLegacySend.ts @@ -0,0 +1 @@ +export { serializeLegacyContextMessage } from "./jones/composerContextLegacySend.ts"; diff --git a/packages/shared/src/jones/composerContextLegacySend.test.ts b/packages/shared/src/jones/composerContextLegacySend.test.ts new file mode 100644 index 0000000000..c55941cfd8 --- /dev/null +++ b/packages/shared/src/jones/composerContextLegacySend.test.ts @@ -0,0 +1,173 @@ +import { ComposerContextId, type ComposerContextRecord } from "@t3tools/contracts"; +import { describe, expect, it } from "vite-plus/test"; + +import { upgradeLegacyContextMessage } from "../composerContextLegacy.ts"; +import { formatComposerContextReference } from "../composerContextReferences.ts"; +import { serializeLegacyContextMessage } from "./composerContextLegacySend.ts"; + +const terminal = { + version: 1, + contextId: ComposerContextId.make("terminal_t1"), + kind: "terminal", + label: "Terminal 1 lines 3-4", + terminalId: "terminal-1", + terminalLabel: "Terminal 1", + lineStart: 3, + lineEnd: 4, + text: "boom\nagain", +} satisfies ComposerContextRecord; + +const review = { + version: 1, + contextId: ComposerContextId.make("review-comment_rc1"), + kind: "review-comment", + label: "b.ts L4", + sectionId: "file:a/b.ts", + sectionTitle: "File comment", + filePath: "a/b.ts", + startIndex: 3, + endIndex: 3, + rangeLabel: "L4", + text: "Why this branch?", + diff: "const x = 1;", + fenceLanguage: "ts", +} satisfies ComposerContextRecord; + +const annotation = { + version: 1, + contextId: ComposerContextId.make("preview-annotation_ann1"), + kind: "preview-annotation", + label: "Fix the checkout button", + annotationId: "ann1", + pageUrl: "https://example.com/checkout", + pageTitle: "Checkout", + comment: "Make this button clearer", + targetSummary: "1 selected element", + styleChanges: ["color: red → blue"], + elements: [ + { + pageUrl: "https://example.com/checkout", + pageTitle: "Checkout", + tagName: "button", + selector: "#submit-order", + htmlPreview: '', + componentName: "SubmitOrderButton", + source: { + functionName: "SubmitOrderButton", + fileName: "src/Checkout.tsx", + lineNumber: 42, + columnNumber: 7, + }, + styles: "color: red;", + }, + ], +} satisfies ComposerContextRecord; + +describe("serializeLegacyContextMessage", () => { + it("carries terminal payloads an older server would otherwise discard", () => { + const text = `Look at ${formatComposerContextReference(terminal)} please`; + const legacy = serializeLegacyContextMessage({ text, records: [terminal] }); + + expect(legacy).not.toContain("t3-context://"); + expect(legacy).toContain("boom"); + + const upgraded = upgradeLegacyContextMessage(legacy); + expect(upgraded.records).toHaveLength(1); + expect(upgraded.records[0]).toMatchObject({ + kind: "terminal", + terminalLabel: "Terminal 1", + lineStart: 3, + lineEnd: 4, + text: "boom\nagain", + }); + }); + + it("inlines a review comment with its diff intact", () => { + const text = `See ${formatComposerContextReference(review)} here`; + const legacy = serializeLegacyContextMessage({ text, records: [review] }); + expect(legacy).not.toContain("t3-context://"); + + const upgraded = upgradeLegacyContextMessage(legacy); + expect(upgraded.records[0]).toMatchObject({ + kind: "review-comment", + filePath: "a/b.ts", + rangeLabel: "L4", + text: "Why this branch?", + diff: "const x = 1;", + }); + }); + + it.each([" ", "\n", "\n\n"])( + "preserves the separator between a skill and a trailing PR through an older server: %j", + (separator) => { + const pullRequest = { + ...review, + label: "#11440", + sectionId: "pull-request:11440", + sectionTitle: "Pull request", + filePath: "PR #11440", + rangeLabel: "summary", + text: "Audit Mobile Photo Import", + diff: "", + }; + const text = `$pr-audit${separator}${formatComposerContextReference(pullRequest)}`; + const upgraded = upgradeLegacyContextMessage( + serializeLegacyContextMessage({ text, records: [pullRequest] }), + ); + + expect(upgraded.records).toHaveLength(1); + expect(upgraded.text).toBe( + `$pr-audit${separator}${formatComposerContextReference(upgraded.records[0]!)}`, + ); + }, + ); + + it("retains picked-element details for preview annotations sent through an older server", () => { + const text = `Update ${formatComposerContextReference(annotation)}`; + const upgraded = upgradeLegacyContextMessage( + serializeLegacyContextMessage({ text, records: [annotation] }), + ); + + expect(upgraded.records[0]).toMatchObject({ + kind: "preview-annotation", + pageUrl: "https://example.com/checkout", + elements: [ + { + selector: "#submit-order", + htmlPreview: '', + source: { + fileName: "src/Checkout.tsx", + lineNumber: 42, + columnNumber: 7, + }, + }, + ], + }); + }); + + it("appends review comments the text never referenced", () => { + const legacy = serializeLegacyContextMessage({ text: "just prose", records: [review] }); + + expect(legacy).toContain("Why this branch?"); + const upgraded = upgradeLegacyContextMessage(legacy); + expect(upgraded.records).toHaveLength(1); + expect(upgraded.records[0]).toMatchObject({ + kind: "review-comment", + filePath: "a/b.ts", + text: "Why this branch?", + }); + }); + + it("numbers terminal output within the declared line range", () => { + const ranged = { + ...terminal, + lineStart: 3, + lineEnd: 4, + text: "boom\nagain\n", + }; + const legacy = serializeLegacyContextMessage({ text: "look", records: [ranged] }); + + expect(legacy).toContain("3 | boom\n 4 | again"); + expect(legacy).not.toContain("5 |"); + }); +}); diff --git a/packages/shared/src/jones/composerContextLegacySend.ts b/packages/shared/src/jones/composerContextLegacySend.ts new file mode 100644 index 0000000000..c0529fdfa7 --- /dev/null +++ b/packages/shared/src/jones/composerContextLegacySend.ts @@ -0,0 +1,150 @@ +import type { ComposerContextRecord, ElementContextDetails } from "@t3tools/contracts"; + +import { collectComposerContextReferences } from "../composerContextReferences.ts"; + +/** Embeds context in message text for older servers that drop records and cannot resolve inline links. */ +export function serializeLegacyContextMessage(input: { + text: string; + records: ReadonlyArray; +}): string { + const recordsById = new Map(input.records.map((record) => [record.contextId, record])); + const used = new Set(); + + let text = input.text; + const occurrences = collectComposerContextReferences(text); + for (const occurrence of [...occurrences].reverse()) { + const record = recordsById.get(occurrence.contextId); + if (!record) continue; + used.add(occurrence.contextId); + const replacement = + record.kind === "review-comment" ? renderReviewComment(record) : inlineLabel(record); + text = `${text.slice(0, occurrence.start)}${replacement}${text.slice(occurrence.end)}`; + } + + const blocks = [ + renderBlock( + "terminal_context", + input.records.filter((record) => record.kind === "terminal").map(renderTerminalEntry), + ), + renderBlock( + "element_context", + input.records.filter((record) => record.kind === "element").map(renderElementEntry), + ), + ...input.records + .filter((record) => record.kind === "preview-annotation") + .map((record) => `\n${renderPreviewBody(record)}\n`), + ...input.records + .filter((record) => record.kind === "review-comment" && !used.has(record.contextId)) + .map(renderReviewComment), + ].filter((block) => block.length > 0); + + return [text.trimEnd(), ...blocks].filter((part) => part.length > 0).join("\n\n"); +} + +function inlineLabel(record: ComposerContextRecord): string { + if (record.kind === "terminal" && "terminalLabel" in record) { + const slug = record.terminalLabel.trim().toLowerCase().replace(/\s+/g, "-"); + const range = + record.lineStart === record.lineEnd + ? `${record.lineStart}` + : `${record.lineStart}-${record.lineEnd}`; + return `@${slug}:${range}`; + } + return record.label; +} + +function renderBlock(tag: string, entries: ReadonlyArray): string { + const body = entries.filter((entry) => entry.length > 0).join("\n"); + return body.length === 0 ? "" : `<${tag}>\n${body}\n`; +} + +function indent(value: string): string { + return value + .split("\n") + .map((line) => (line.length === 0 ? "" : ` ${line}`)) + .join("\n"); +} + +function renderTerminalEntry(record: ComposerContextRecord): string { + if (!("terminalLabel" in record)) return ""; + const range = + record.lineStart === record.lineEnd + ? `line ${record.lineStart}` + : `lines ${record.lineStart}-${record.lineEnd}`; + const body = record.text + .split("\n") + // A trailing newline would otherwise number a line past the range the label declares. + .slice(0, record.lineEnd - record.lineStart + 1) + .map((line, index) => `${record.lineStart + index} | ${line}`) + .join("\n"); + return `- ${record.terminalLabel} ${range}:\n${indent(body)}`; +} + +function renderElementEntry(record: ComposerContextRecord): string { + if (!("tagName" in record)) return ""; + return renderElementDetailsEntry(record); +} + +function renderElementDetailsEntry(record: ElementContextDetails): string { + const lines: string[] = []; + if (record.pageUrl) lines.push(`url: ${record.pageUrl}`); + if (record.selector) lines.push(`selector: ${record.selector}`); + const source = record.source; + if (source?.fileName) { + const position = [source.lineNumber, source.columnNumber].filter( + (part) => part !== null && part !== undefined, + ); + lines.push(`source: ${[source.fileName, ...position].join(":")}`); + } + if (record.htmlPreview) lines.push(`html:\n${indent(record.htmlPreview)}`); + if (record.styles) lines.push(`styles:\n${indent(record.styles)}`); + return `- <${record.componentName ?? record.tagName}>:\n${indent(lines.join("\n"))}`; +} + +function renderPreviewBody(record: ComposerContextRecord): string { + if (!("annotationId" in record)) return ""; + const lines = [`Id: ${record.annotationId}`, `Page: ${record.pageUrl || record.pageTitle || ""}`]; + if (record.comment) lines.push(`Comment: ${record.comment}`); + if (record.targetSummary) lines.push(`Targets: ${record.targetSummary}`); + if (record.styleChanges.length > 0) { + lines.push("Requested visual changes:", ...record.styleChanges.map((change) => `- ${change}`)); + } + if (record.elements && record.elements.length > 0) { + lines.push( + "", + record.elements.map(renderElementDetailsEntry).join("\n"), + "", + ); + } + return lines.join("\n"); +} + +function escapeAttribute(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function renderReviewComment(record: ComposerContextRecord): string { + if (!("sectionId" in record)) return record.label; + const attributes = [ + `sectionId="${escapeAttribute(record.sectionId)}"`, + `sectionTitle="${escapeAttribute(record.sectionTitle)}"`, + `filePath="${escapeAttribute(record.filePath)}"`, + `rangeLabel="${escapeAttribute(record.rangeLabel)}"`, + `startIndex="${record.startIndex}"`, + `endIndex="${record.endIndex}"`, + ].join(" "); + // The fence must be longer than any run of backticks inside the diff so the body stays intact. + const longestRun = Math.max( + 0, + ...[...record.diff.matchAll(/`+/g)].map((match) => match[0].length), + ); + const fence = "`".repeat(Math.max(3, longestRun + 1)); + const body = record.diff + ? `${record.text}\n\n${fence}${record.fenceLanguage ?? "diff"}\n${record.diff}\n${fence}` + : record.text; + return `\n${body}\n`; +} diff --git a/packages/shared/src/jones/sqliteWriterAdmission.fixture.ts b/packages/shared/src/jones/sqliteWriterAdmission.fixture.ts index 11c938b700..cf5bfcc6b7 100644 --- a/packages/shared/src/jones/sqliteWriterAdmission.fixture.ts +++ b/packages/shared/src/jones/sqliteWriterAdmission.fixture.ts @@ -319,62 +319,60 @@ export type Fixture = { release: () => void; }; -// oxlint-disable-next-line t3code/prefer-effect-fn -- Generic test scope wrapper preserves the caller's result and error types without adding a span. -export const withCheckpointClient = ( +export const withCheckpointClient = Effect.fnUntraced(function* ( checkpointFor: (filename: string) => NodeWorkerThreads.Worker | undefined, use: (fixture: Fixture) => Effect.Effect, -) => - Effect.gen(function* () { - requireCapabilities(); - const directory = yield* Effect.acquireRelease(Effect.sync(scratchDirectory), (path) => - Effect.sync(() => { - NodeFS.rmSync(path, { recursive: true, force: true }); - assert.isFalse( - NodeFS.existsSync(path), - "the creating fixture must remove its exact scratch root", - ); - }), - ); - const filename = NodePath.join(directory, "synthetic.sqlite"); - return yield* Effect.gen(function* () { - const sql = yield* SqlClient.SqlClient; - const worker = checkpointFor(filename); - assert.isDefined(worker, "unavailable coverage: real checkpoint worker did not activate"); - assert.deepEqual(yield* sql`PRAGMA wal_autocheckpoint`, [{ wal_autocheckpoint: 0 }]); - assert.deepEqual(yield* sql`PRAGMA journal_mode = WAL`, [{ journal_mode: "wal" }]); - const journal = observeWorker(worker!); - yield* Effect.addFinalizer(() => Effect.sync(journal.close)); - const traffic = checkpointTraffic(worker!); - const releases: Array<() => void> = []; - const release = () => { - for (const cleanup of releases.splice(0)) cleanup(); - traffic.release(); - }; - const fixture: Fixture = { - sql, +) { + requireCapabilities(); + const directory = yield* Effect.acquireRelease(Effect.sync(scratchDirectory), (path) => + Effect.sync(() => { + NodeFS.rmSync(path, { recursive: true, force: true }); + assert.isFalse( + NodeFS.existsSync(path), + "the creating fixture must remove its exact scratch root", + ); + }), + ); + const filename = NodePath.join(directory, "synthetic.sqlite"); + return yield* Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + const worker = checkpointFor(filename); + assert.isDefined(worker, "unavailable coverage: real checkpoint worker did not activate"); + assert.deepEqual(yield* sql`PRAGMA wal_autocheckpoint`, [{ wal_autocheckpoint: 0 }]); + assert.deepEqual(yield* sql`PRAGMA journal_mode = WAL`, [{ journal_mode: "wal" }]); + const journal = observeWorker(worker!); + yield* Effect.addFinalizer(() => Effect.sync(journal.close)); + const traffic = checkpointTraffic(worker!); + const releases: Array<() => void> = []; + const release = () => { + for (const cleanup of releases.splice(0)) cleanup(); + traffic.release(); + }; + const fixture: Fixture = { + sql, + filename, + worker: worker!, + journal, + traffic, + own: (release) => { + releases.push(release); + }, + release, + }; + // The inner body releases holds BEFORE child/scope finalizers can await + // masked cleanup. The layer then stops workers before closing its database. + return yield* Effect.scoped(use(fixture).pipe(Effect.ensuring(Effect.sync(release)))); + }).pipe( + Effect.provide( + SqliteClient.layer({ filename, - worker: worker!, - journal, - traffic, - own: (release) => { - releases.push(release); - }, - release, - }; - // The inner body releases holds BEFORE child/scope finalizers can await - // masked cleanup. The layer then stops workers before closing its database. - return yield* Effect.scoped(use(fixture).pipe(Effect.ensuring(Effect.sync(release)))); - }).pipe( - Effect.provide( - SqliteClient.layer({ - filename, - checkpointWorker: true, - readerWorker: true, - readerFailureMode: "fail", - }), - ), - ); - }).pipe(Effect.scoped); + checkpointWorker: true, + readerWorker: true, + readerFailureMode: "fail", + }), + ), + ); +}, Effect.scoped); export const pinSmallWal = Effect.fn("sqliteWriterAdmission.pinSmallWal")(function* ( fixture: Fixture, diff --git a/packages/shared/src/jones/threadAttention.ts b/packages/shared/src/jones/threadAttention.ts index 75d9d0d5df..fcdde3a7da 100644 --- a/packages/shared/src/jones/threadAttention.ts +++ b/packages/shared/src/jones/threadAttention.ts @@ -294,7 +294,7 @@ export function isDelegatedTaskShownUnderParent( const from = task.delegatedFrom; if (from === undefined || parents === null) return false; let row = parents.get(from.threadId); - if (row === undefined) return false; + if (row === undefined || row.archivedAt !== null) return false; if (subagentRequest(task) === null && !taskRequests.has(task.id)) return true; const seen = new Set(); while (row.lineage.relationshipToParent === "subagent") { @@ -334,10 +334,21 @@ export function pendingSubagentRequestsByThread( if (requests.get(parentId) !== "approval") requests.set(parentId, request); const parent = byId.get(parentId); parentId = - parent?.lineage.relationshipToParent === "subagent" ? parent.lineage.parentThreadId : null; + parent?.lineage.relationshipToParent === "subagent" || parent?.lineage.sideChat === true + ? parent.lineage.parentThreadId + : null; } }; for (const child of threads) { + if (child.lineage.sideChat === true) { + const kind = child.pendingRuntimeRequest?.kind; + const parentId = child.lineage.parentThreadId; + if (parentId !== null && kind !== undefined && kind !== "auth_refresh") { + const request = kind === "user_input" ? "user_input" : "approval"; + if (requests.get(parentId) !== "approval") requests.set(parentId, request); + } + continue; + } if (child.lineage.relationshipToParent !== "subagent") continue; const request = subagentRequest(child); if (request !== null) flag(child.lineage.parentThreadId, request, new Set([child.id])); diff --git a/packages/shared/src/jones/threadResultDependencies/pendingTasks.test.ts b/packages/shared/src/jones/threadResultDependencies/pendingTasks.test.ts index 2e8c9ffc16..ac94f5b0f6 100644 --- a/packages/shared/src/jones/threadResultDependencies/pendingTasks.test.ts +++ b/packages/shared/src/jones/threadResultDependencies/pendingTasks.test.ts @@ -157,6 +157,7 @@ describe("result dependencies in pending background work", () => { number: 720, url: "https://github.com/Jones-Systems/Jones-Code/pull/720", source: "agent", + snapshot: null, watch: { startedAt: "2026-10-26T00:00:00.000Z", headSha: null, diff --git a/packages/shared/src/nodeSqliteClient.ts b/packages/shared/src/nodeSqliteClient.ts index 20d948cebb..7a245b9d23 100644 --- a/packages/shared/src/nodeSqliteClient.ts +++ b/packages/shared/src/nodeSqliteClient.ts @@ -46,7 +46,17 @@ export interface SqliteClientConfig { readonly spanAttributes?: Record | undefined; /** Ignored for in-memory databases, which cannot share a second connection. */ readonly readerWorker?: boolean | undefined; - /** Ignored for in-memory databases. */ + readonly readerPool?: SqliteReaderPoolOptions | undefined; + readonly readerStartupTimeout?: Duration.Input | undefined; + /** + * Fail marked reads when an enabled reader is unavailable instead of running + * them on the synchronous writer. Ignored when readerWorker is disabled or + * the database is in memory; existing writer transactions retain affinity. + */ + readonly readerFailureMode?: "fallback" | "fail" | undefined; + /** + * Ignored for in-memory databases. + */ readonly checkpointWorker?: boolean | undefined; } @@ -322,7 +332,10 @@ const READER_POOL_SIZE = 4; // Bind transactions to one worker; fail after its exit instead of resuming outside the transaction on a replacement. const makeReader = Effect.fnUntraced(function* ( options: SqliteClientConfig, - pool: { unavailable: boolean }, + slotId = 0, + management?: Semaphore.Semaphore, + onUnprovedRetirement: () => void = () => {}, + onUnavailable: () => void = () => {}, ) { let current: | { @@ -336,6 +349,12 @@ const makeReader = Effect.fnUntraced(function* ( readonly exited: () => boolean; } | undefined; + const clock = yield* Clock.Clock; + let unavailable = false; + let closed = false; + let retiring: Promise | undefined; + let retryAt = 0n; + let retryDelayMs = 1000; let nextRequestId = 0; let generation = 0; let retirementProved = true; @@ -566,7 +585,9 @@ const makeReader = Effect.fnUntraced(function* ( }), ); - const startupTimeout = Duration.seconds(10); + const startupTimeout = Duration.fromInputUnsafe( + options.readerStartupTimeout ?? Duration.seconds(10), + ); const turnOff = (reader: typeof current, reason: string, permanent = true) => Effect.suspend(() => { @@ -599,9 +620,15 @@ const makeReader = Effect.fnUntraced(function* ( }); } // @effect-diagnostics-next-line effectSucceedWithVoid:off - The connection result distinguishes undefined from void. - if (pool.unavailable) return Effect.succeed(undefined); - pool.unavailable = true; - return Effect.logWarning("SQLite reader worker unavailable; reads use the main connection", { + if (unavailable) return Effect.succeed(undefined); + unavailable = permanent; + if (permanent) onUnavailable(); + if (!permanent) { + // Retry on later demand only, after cleanup and a capped exponential cooldown. + retryAt = clock.monotonicTimeNanosUnsafe() + BigInt(retryDelayMs) * 1000000n; + retryDelayMs = Math.min(retryDelayMs * 2, 30000); + } + return Effect.logWarning("SQLite reader worker unavailable", { reason, readerFailureMode: options.readerFailureMode ?? "fallback", }).pipe(Effect.as(undefined)); @@ -609,7 +636,9 @@ const makeReader = Effect.fnUntraced(function* ( const acquire = Effect.suspend((): Effect.Effect => { // @effect-diagnostics-next-line effectSucceedWithVoid:off - The connection result distinguishes undefined from void. - if (pool.unavailable) return Effect.succeed(undefined); + if (closed || unavailable || retiring !== undefined) return Effect.succeed(undefined); + // @effect-diagnostics-next-line effectSucceedWithVoid:off - The connection result distinguishes undefined from void. + if (clock.monotonicTimeNanosUnsafe() < retryAt) return Effect.succeed(undefined); let reader: NonNullable; try { reader = current ?? start(); @@ -628,7 +657,7 @@ const makeReader = Effect.fnUntraced(function* ( }), (startup): Effect.Effect => // A late ready event must not hand out a worker retired by another acquire timeout. - pool.unavailable || current !== reader + closed || unavailable || current !== reader ? // @effect-diagnostics-next-line effectSucceedWithVoid:off - The connection result distinguishes undefined from void. Effect.succeed(undefined) : Option.isSome(startup) && startup.value.type === "ready" @@ -1339,28 +1368,75 @@ const make = Effect.fn("makeWithDatabase")(function* ( const semaphore = yield* Semaphore.make(1); const connection = yield* makeConnection; + const readersEnabled = + options.readerWorker !== false && + isFileDatabase && + (options.readerWorker === true || options.readerPool !== undefined); + const pool = + readersEnabled && options.readerPool !== undefined + ? yield* Effect.gen(function* () { + const management = yield* Semaphore.make(1); + const slots = []; + for (let slotId = 0; slotId < options.readerPool!.size; slotId++) { + slots.push( + yield* makeReader(options, slotId, management, () => { + readerRetirementUncertain = true; + }), + ); + } + return yield* makeReaderPool(options.readerPool!, slots, readerAdmission); + }) + : undefined; // A reader that cannot start turns off the whole pool, so routing never picks a dead slot. const readerPool = { unavailable: false }; + const legacyReaderPolicy = + options.readerStartupTimeout !== undefined || options.readerFailureMode !== undefined; + const readerManagement = yield* Semaphore.make(1); const readers = - options.readerWorker && isFileDatabase - ? yield* Effect.forEach(Array.from({ length: READER_POOL_SIZE }), () => - Effect.map( - Effect.all([makeReader(options, readerPool), Semaphore.make(1)]), - ([acquire, lock]) => ({ - acquire, - lock, - claims: 0, - }), - ), + options.readerWorker && isFileDatabase && pool === undefined + ? yield* Effect.forEach( + Array.from({ length: legacyReaderPolicy ? 1 : READER_POOL_SIZE }), + (_, index) => + Effect.map( + Effect.all([ + Effect.map( + makeReader( + options, + index, + legacyReaderPolicy ? readerManagement : undefined, + undefined, + () => { + readerPool.unavailable = true; + }, + ), + (slot) => slot.acquire, + ), + Semaphore.make(1), + ]), + ([acquire, lock]) => ({ + acquire, + lock, + claims: 0, + }), + ), ) : []; const writer = { name: "writer" as const, lock: semaphore, connection }; let activeWriter: ConnectionOwner | undefined; // Callers must run route uninterruptibly and pass `claimed` a finalizer that runs `release` once. + type RouteTarget = { + readonly name: Acquired["connection"]; + readonly lock: Semaphore.Semaphore; + readonly connection: Connection; + }; const route = (claimed: (release: Effect.Effect) => Effect.Effect) => Effect.withFiber((fiber) => { - if (readers.length === 0 || readerPool.unavailable || !Context.get(fiber.context, ReadOnly)) { + if ( + readers.length === 0 || + (readerPool.unavailable && options.readerFailureMode !== "fail") || + !Context.get(fiber.context, ReadOnly) + ) { return Effect.succeed(writer); } // Least claimed wins and ties go to the lowest slot, so idle slots never start a worker. @@ -1374,10 +1450,22 @@ const make = Effect.fn("makeWithDatabase")(function* ( reader.claims--; }), ), - Effect.map(reader.acquire, (readerConnection) => + Effect.flatMap(reader.acquire, (readerConnection): Effect.Effect => readerConnection === undefined - ? writer - : { name: "reader" as const, lock: reader.lock, connection: readerConnection }, + ? options.readerFailureMode === "fail" + ? Effect.fail( + failed( + new Error("Database reader is unavailable"), + readerFailureMessages.open, + "open", + ), + ) + : Effect.succeed(writer) + : Effect.succeed({ + name: "reader" as const, + lock: reader.lock, + connection: readerConnection, + }), ), ); }); @@ -1393,7 +1481,7 @@ const make = Effect.fn("makeWithDatabase")(function* ( const writerBorrowBatchSize = 16; let writerBorrowsSinceYield = 0; - const borrower: Borrower = (use) => + const legacyBorrower: Borrower = (use) => Effect.uninterruptibleMask((restore) => { let release: Effect.Effect = Effect.void; const claimed = (next: Effect.Effect) => @@ -1468,7 +1556,7 @@ const make = Effect.fn("makeWithDatabase")(function* ( Effect.withFiber((fiber) => Scope.addFinalizer(Context.getUnsafe(fiber.context, Scope.Scope), release), ); - const transactionAcquirer = Effect.uninterruptible(route(claimedUntilScopeCloses)).pipe( + const legacyTransactionAcquirer = Effect.uninterruptible(route(claimedUntilScopeCloses)).pipe( Effect.flatMap((target) => Effect.uninterruptibleMask((restore) => { const fiber = Fiber.getCurrent()!; @@ -1500,7 +1588,50 @@ const make = Effect.fn("makeWithDatabase")(function* ( ), ); - const client = yield* Client.make({ + const wantsPool = Effect.withFiber((fiber) => + Effect.succeed(pool !== undefined && fiber.getRef(ReadOnly) !== undefined), + ); + const poolAcquire = Effect.uninterruptibleMask((restore) => + Effect.withFiber((fiber) => { + const pending = fiber.getRef(TransactionAcquire); + const clock = fiber.getRef(Clock.Clock); + const start = clock.monotonicTimeNanosUnsafe(); + const acquire = pool!.acquire(fiber.getRef(ReadPriority), fiber); + return (pending?.restore ?? fiber.getRef(ReadOnly) ?? restore)(acquire).pipe( + Effect.tap(() => + Effect.sync(() => { + const acquisition: Acquired = { + connection: "reader", + lockWaitMs: Number(clock.monotonicTimeNanosUnsafe() - start) / 1e6, + }; + if (pending !== undefined) pending.acquired = acquisition; + else if ( + fiber.getRef(References.TracerEnabled) && + fiber.getRef(Statement.SpanPropagationEnabled) + ) { + annotateAcquired(fiber, acquisition); + } + }), + ), + Effect.map((lease) => lease.connection), + ); + }), + ); + const borrower: Borrower = (use) => + Effect.flatMap(wantsPool, (enabled) => + enabled ? Effect.scoped(Effect.flatMap(poolAcquire, use)) : legacyBorrower(use), + ); + const transactionAcquirer: Effect.Effect = Effect.suspend(() => + Effect.flatMap(Effect.serviceOption(client.transactionService), (transaction) => + Option.isSome(transaction) + ? Effect.succeed(transaction.value[0]) + : Effect.flatMap(wantsPool, (enabled) => + enabled ? poolAcquire : legacyTransactionAcquirer, + ), + ), + ); + + const client: Client.SqlClient = yield* Client.make({ driver: "node:sqlite", acquirer: transactionAcquirer, borrower, diff --git a/packages/shared/src/t3McpToolPresentation.ts b/packages/shared/src/t3McpToolPresentation.ts index 73353fb4b5..62c7e07a33 100644 --- a/packages/shared/src/t3McpToolPresentation.ts +++ b/packages/shared/src/t3McpToolPresentation.ts @@ -338,6 +338,10 @@ const T3_MCP_TOOLS: Readonly> = { ["Read", "Reading", "Read", "thread configuration"], "thread-configuration", ), + t3_thread_self_configure: tool( + ["Save", "Saving", "Saved", "future thread settings"], + "thread-self-configure", + ), t3_thread_configure: tool(["Use", "Using", "Used", "thread model settings"], "thread-configure"), t3_thread_transfer: tool(["Use", "Using", "Used", "thread transfers"], "thread-fork"), t3_thread_fork: tool(["Fork", "Forking", "Requested a fork of", "this thread"], "thread-fork"), diff --git a/patches/effect@4.1.2.patch b/patches/effect@4.1.2.patch new file mode 100644 index 0000000000..dd0c532ca1 --- /dev/null +++ b/patches/effect@4.1.2.patch @@ -0,0 +1,345 @@ +diff --git a/dist/rpc/RpcClient.js b/dist/rpc/RpcClient.js +--- a/dist/rpc/RpcClient.js ++++ b/dist/rpc/RpcClient.js +@@ -49 +49,2 @@ +-export const makeNoSerialization = /*#__PURE__*/Effect.fnUntraced(function* (group, options) { ++export const makeNoSerialization = (group, options) => makeNoSerializationInternal(group, options).pipe(Effect.map(({ client, write }) => ({ client, write }))); ++const makeNoSerializationInternal = /*#__PURE__*/Effect.fnUntraced(function* (group, options) { +@@ -62,6 +63,5 @@ +- const clearEntries = Effect.fnUntraced(function* (exit) { +- // Resuming callers can synchronously start new requests. +- const pending = Array.from(entries.values()); +- entries.clear(); +- // Once detached, every entry must complete even if delivery is interrupted. +- for (const entry of pending) { ++ const clearEntries = (exit, requestIds = entries.keys()) => Effect.sync(() => { ++ const pending = Array.from(requestIds, id => [id, entries.get(id)]); ++ for (const [id] of pending) entries.delete(id); ++ for (const [, entry] of pending) { ++ if (!entry) continue; +@@ -69 +69,3 @@ +- yield* exit._tag === "Success" ? Queue.end(entry.queue) : Queue.failCause(entry.queue, exit.cause); ++ if (exit._tag === "Success") Queue.endUnsafe(entry.queue); ++ else Queue.failCauseUnsafe(entry.queue, exit.cause); ++ entry.cancelDelivery(); +@@ -74 +76 @@ +- }, Effect.uninterruptible); ++ }); +@@ -168,0 +171,74 @@ ++ // The terminal protocol permits eight unacknowledged chunks. Include the ++ // active delivery until acceptance; the consumer queue is unchanged. ++ const pending = []; ++ let pendingChunks = 0; ++ let stopped = false; ++ let ending = false; ++ const wakeDelivery = Latch.makeUnsafe(); ++ let deliveryStarted = false; ++ let deliveryFiber; ++ const clearDelivery = () => { ++ stopped = true; ++ pending.length = 0; ++ pendingChunks = 0; ++ }; ++ const cancelDelivery = () => { ++ clearDelivery(); ++ deliveryFiber?.interruptUnsafe(); ++ }; ++ const deliver = Effect.gen(function* () { ++ while (!stopped) { ++ const message = pending.shift(); ++ if (!message) { ++ wakeDelivery.closeUnsafe(); ++ yield* wakeDelivery.await; ++ continue; ++ } ++ if (message._tag === "Exit") { ++ entries.delete(id); ++ clearDelivery(); ++ yield* message.exit._tag === "Success" ? Queue.end(queue) : Queue.failCause(queue, message.exit.cause); ++ return; ++ } ++ const dropped = yield* Queue.offerAll(queue, message.values); ++ if (stopped || dropped.length > 0) return; ++ pendingChunks -= 1; ++ // Release the slot before ACK: a synchronous sender may replenish it. ++ if (supportsAck) { ++ yield* options.onFromClient({ ++ message: { _tag: "Ack", requestId: id }, ++ context ++ }); ++ } ++ } ++ }).pipe(Effect.catchCause(cause => { ++ clearDelivery(); ++ return Queue.failCause(queue, cause).pipe(Effect.asVoid); ++ })); ++ const write = message => Effect.suspend(() => { ++ if (stopped || ending) return Effect.void; ++ if (message._tag === "Chunk") { ++ if (pendingChunks >= 8) { ++ Queue.failCauseUnsafe(queue, Cause.die(new RpcClientError({ ++ reason: new RpcClientDefect({ ++ message: "RPC stream exceeded its pending response limit", ++ cause: new Error("Received more than eight unaccepted stream chunks") ++ }) ++ }))); ++ entries.delete(id); ++ cancelDelivery(); ++ return sendInterrupt(id, [], context).pipe(Effect.forkIn(scope, { startImmediately: true }), Effect.asVoid); ++ } ++ pendingChunks += 1; ++ } else { ++ ending = true; ++ } ++ pending.push(message); ++ wakeDelivery.openUnsafe(); ++ if (deliveryStarted) return Effect.void; ++ deliveryStarted = true; ++ return deliver.pipe(Effect.interruptible, Effect.forkIn(scope, { startImmediately: true }), Effect.tap(fiber => Effect.sync(() => { ++ deliveryFiber = fiber; ++ if (stopped) fiber.interruptUnsafe(); ++ })), Effect.asVoid); ++ }); +@@ -169,0 +246 @@ ++ cancelDelivery(); +@@ -180 +257,3 @@ +- context ++ context, ++ write, ++ cancelDelivery +@@ -250,10 +329,4 @@ +- return Queue.offerAll(entry.queue, message.values).pipe(supportsAck ? Effect.flatMap(dropped => +- // values are dropped when the queue was shutdown by an interrupted +- // consumer, so the server should not be asked for more +- dropped.length > 0 ? Effect.void : options.onFromClient({ +- message: { +- _tag: "Ack", +- requestId: message.requestId +- }, +- context: entry.context +- })) : identity, Effect.catchCause(cause => Queue.failCause(entry.queue, cause))); ++ if (!supportsAck) { ++ return Queue.offerAll(entry.queue, message.values).pipe(Effect.catchCause(cause => Queue.failCause(entry.queue, cause))); ++ } ++ return entry.write(message); +@@ -265,0 +339 @@ ++ if (entry._tag === "Queue" && supportsAck) return entry.write(message); +@@ -302 +376,2 @@ +- write ++ write, ++ abort: clearEntries +@@ -339,2 +414,3 @@ +- write +- } = yield* makeNoSerialization(group, { ++ write, ++ abort ++ } = yield* makeNoSerializationInternal(group, { +@@ -378,2 +454 @@ +- const entry = entries.get(message.requestId); +- if (!entry) return Effect.void; ++ // Buffered chunks can be accepted after Exit removed the decode entry. +@@ -468,6 +543 @@ +- return Effect.forEach(requestIds, requestId => write({ +- _tag: "Exit", +- clientId: 0, +- requestId, +- exit: exit +- })).pipe(Effect.uninterruptible); ++ return abort(exit, requestIds); +diff --git a/dist/rpc/RpcGroup.d.ts b/dist/rpc/RpcGroup.d.ts +--- a/dist/rpc/RpcGroup.d.ts ++++ b/dist/rpc/RpcGroup.d.ts +@@ -144 +144,4 @@ +-export type HandlersServices = keyof Handlers extends infer K ? K extends keyof Handlers & string ? HandlerServices : never : never; ++export type HandlersServices = keyof Handlers extends infer K ? K extends keyof Handlers & string ? K extends keyof RpcsByTag ? RpcsByTag[K] extends infer Current extends Rpc.Any ? HandlerServices : never : HandlerServices : never : never; ++type RpcsByTag = { ++ readonly [Current in Rpcs as Current["_tag"]]: Current; ++}; +diff --git a/src/rpc/RpcClient.ts b/src/rpc/RpcClient.ts +--- a/src/rpc/RpcClient.ts ++++ b/src/rpc/RpcClient.ts +@@ -279 +279,4 @@ +-> = Effect.fnUntraced(function*( ++> = (group, options) => ++ makeNoSerializationInternal(group, options).pipe(Effect.map(({ client, write }) => ({ client, write }))) ++ ++const makeNoSerializationInternal = Effect.fnUntraced(function*( +@@ -303,0 +307 @@ ++ type StreamResponse = Extract, { readonly _tag: "Chunk" | "Exit" }> +@@ -314,0 +319,2 @@ ++ readonly write: (message: StreamResponse) => Effect.Effect ++ readonly cancelDelivery: () => void +@@ -327,6 +333,9 @@ +- const clearEntries = Effect.fnUntraced(function*(exit: Exit.Exit) { +- // Resuming callers can synchronously start new requests. +- const pending = Array.from(entries.values()) +- entries.clear() +- // Once detached, every entry must complete even if delivery is interrupted. +- for (const entry of pending) { ++ const clearEntries = ( ++ exit: Exit.Exit, ++ requestIds: Iterable = entries.keys() ++ ) => Effect.sync(() => { ++ // Detach the captured entries before resuming any caller, which can synchronously request again. ++ const pending = Array.from(requestIds, (id) => [id, entries.get(id)] as const) ++ for (const [id] of pending) entries.delete(id) ++ for (const [, entry] of pending) { ++ if (!entry) continue +@@ -334,3 +343,3 @@ +- yield* (exit._tag === "Success" +- ? Queue.end(entry.queue as any) +- : Queue.failCause(entry.queue as any, exit.cause)) as Effect.Effect ++ if (exit._tag === "Success") Queue.endUnsafe(entry.queue) ++ else Queue.failCauseUnsafe(entry.queue, exit.cause) ++ entry.cancelDelivery() +@@ -341 +350 @@ +- }, Effect.uninterruptible) ++ }) +@@ -492,0 +502,86 @@ ++ // The terminal protocol permits eight unacknowledged chunks. Include the ++ // active delivery until acceptance; the consumer queue is unchanged. ++ const pending: Array = [] ++ let pendingChunks = 0 ++ let stopped = false ++ let ending = false ++ const wakeDelivery = Latch.makeUnsafe() ++ let deliveryStarted = false ++ let deliveryFiber: Fiber.Fiber | undefined ++ const clearDelivery = () => { ++ stopped = true ++ pending.length = 0 ++ pendingChunks = 0 ++ } ++ const cancelDelivery = () => { ++ clearDelivery() ++ deliveryFiber?.interruptUnsafe() ++ } ++ const deliver = Effect.gen(function*() { ++ while (!stopped) { ++ const message = pending.shift() ++ if (!message) { ++ wakeDelivery.closeUnsafe() ++ yield* wakeDelivery.await ++ continue ++ } ++ if (message._tag === "Exit") { ++ entries.delete(id) ++ clearDelivery() ++ yield* message.exit._tag === "Success" ++ ? Queue.end(queue) ++ : Queue.failCause(queue, message.exit.cause) ++ return ++ } ++ const dropped = yield* Queue.offerAll(queue, message.values) ++ if (stopped || dropped.length > 0) return ++ pendingChunks -= 1 ++ // Release the slot before ACK: a synchronous sender may replenish it. ++ if (supportsAck) { ++ yield* options.onFromClient({ ++ message: { _tag: "Ack", requestId: id }, ++ context ++ }) ++ } ++ } ++ }).pipe( ++ Effect.catchCause((cause) => { ++ clearDelivery() ++ return Queue.failCause(queue, cause).pipe(Effect.asVoid) ++ }) ++ ) ++ const write = (message: StreamResponse): Effect.Effect => Effect.suspend(() => { ++ if (stopped || ending) return Effect.void ++ if (message._tag === "Chunk") { ++ if (pendingChunks >= 8) { ++ Queue.failCauseUnsafe(queue, Cause.die(new RpcClientError({ ++ reason: new RpcClientDefect({ ++ message: "RPC stream exceeded its pending response limit", ++ cause: new Error("Received more than eight unaccepted stream chunks") ++ }) ++ }))) ++ entries.delete(id) ++ cancelDelivery() ++ return sendInterrupt(id, [], context).pipe( ++ Effect.forkIn(scope, { startImmediately: true }), ++ Effect.asVoid ++ ) ++ } ++ pendingChunks += 1 ++ } else { ++ ending = true ++ } ++ pending.push(message) ++ wakeDelivery.openUnsafe() ++ if (deliveryStarted) return Effect.void ++ deliveryStarted = true ++ return deliver.pipe( ++ Effect.interruptible, ++ Effect.forkIn(scope, { startImmediately: true }), ++ Effect.tap((fiber) => Effect.sync(() => { ++ deliveryFiber = fiber ++ if (stopped) fiber.interruptUnsafe() ++ })), ++ Effect.asVoid ++ ) ++ }) +@@ -495,0 +591 @@ ++ cancelDelivery() +@@ -514 +610,3 @@ +- context ++ context, ++ write, ++ cancelDelivery +@@ -606,13 +704,6 @@ +- return Queue.offerAll(entry.queue, message.values).pipe( +- supportsAck +- ? Effect.flatMap((dropped) => +- // values are dropped when the queue was shutdown by an interrupted +- // consumer, so the server should not be asked for more +- dropped.length > 0 ? Effect.void : options.onFromClient({ +- message: { _tag: "Ack", requestId: message.requestId }, +- context: entry.context +- }) +- ) +- : identity, +- Effect.catchCause((cause) => Queue.failCause(entry.queue, cause)) +- ) ++ if (!supportsAck) { ++ return Queue.offerAll(entry.queue, message.values).pipe( ++ Effect.catchCause((cause) => Queue.failCause(entry.queue, cause)) ++ ) ++ } ++ return entry.write(message) +@@ -623,0 +715 @@ ++ if (entry._tag === "Queue" && supportsAck) return entry.write(message) +@@ -660 +752 @@ +- return { client, write } as const ++ return { client, write, abort: clearEntries } as const +@@ -718 +810 @@ +- const { client, write } = yield* makeNoSerialization(group, { ++ const { client, write, abort } = yield* makeNoSerializationInternal(group, { +@@ -761,2 +853 @@ +- const entry = entries.get(message.requestId) +- if (!entry) return Effect.void ++ // Buffered chunks can be accepted after Exit removed the decode entry. +@@ -841,4 +932 @@ +- return Effect.forEach( +- requestIds, +- (requestId) => write({ _tag: "Exit", clientId: 0, requestId, exit: exit as any }) +- ).pipe(Effect.uninterruptible) ++ return abort(exit, requestIds) +diff --git a/src/rpc/RpcGroup.ts b/src/rpc/RpcGroup.ts +--- a/src/rpc/RpcGroup.ts ++++ b/src/rpc/RpcGroup.ts +@@ -215 +215,5 @@ +- K extends keyof Handlers & string ? HandlerServices : never : ++ K extends keyof Handlers & string ? ++ K extends keyof RpcsByTag ? ++ RpcsByTag[K] extends infer Current extends Rpc.Any ? HandlerServices : never ++ : HandlerServices ++ : never : +@@ -216,0 +221,5 @@ ++ ++// Each handler looks up its own RPC by tag. Passing the whole union to every ++// handler costs handlers x RPCs instantiations, which exhausts the checker's ++// per-expression budget for a group of about 200 RPCs. ++type RpcsByTag = { readonly [Current in Rpcs as Current["_tag"]]: Current } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da7669b75a..3ee4c5a253 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -378,6 +378,7 @@ patchedDependencies: '@react-navigation/native-stack@7.21.0': 990d575444c4af2ce8218b3b7a8280fe244c03abbdb32b822a2e862f791dbf42 app-builder-lib@26.16.1: 0fbe9ae51783f5768ebe197d12d1fdb7d477a08c92e5db2f6356bcce62613c53 dbus-next@0.10.2: cfff57561b0ee59b5addb3b2e6c6f20906e967507a530ab67e8db8108e520ba4 + effect@4.1.2: 41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475 expo-audio@58.0.9: 530af071088a809fe1a97ce2b2269c78335fbb5dcd3893d6937ff5c304c85a0a expo-modules-core@58.0.22: f0b0fd100550ed76e5c25b4d13eb7c9ff62825f8f197c53edf7bf447c50c9fb7 expo-network@58.0.3: cd09b6d1744dde4efa081b5619ebcff38877d98f7502a51cab121629dece8237 @@ -439,7 +440,7 @@ importers: version: 0.13.0 '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@napi-rs/keyring': specifier: ^1.3.0 version: 1.3.0 @@ -463,7 +464,7 @@ importers: version: 0.10.2(patch_hash=cfff57561b0ee59b5addb3b2e6c6f20906e967507a530ab67e8db8108e520ba4) effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) electron: specifier: 44.10.0 version: 44.10.0(supports-color@8.1.1) @@ -491,7 +492,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -546,7 +547,7 @@ importers: version: 0.4.0(patch_hash=7a808b92da8460750b85818552e13be3fb73d111691c7db76965970351e124e4)(@tsc-rs/typescript@0.8.0)(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0)(supports-color@8.1.1) '@effect/atom-react': specifier: 4.1.2 - version: 4.1.2(effect@4.1.2)(react@19.3.0)(scheduler@0.28.0) + version: 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react@19.3.0)(scheduler@0.28.0) '@expo-google-fonts/dm-sans': specifier: ^0.4.2 version: 0.4.2 @@ -621,7 +622,7 @@ importers: version: 8.0.3 effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) expo: specifier: 58.0.14 version: 58.0.14(277a83826bb26ba15256633fefeb0bc2) @@ -805,10 +806,10 @@ importers: version: 7.29.7(supports-color@8.1.1) '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@pierre/trees': specifier: 1.0.0-beta.6 version: 1.0.0-beta.6(@pierre/theme@2.0.0)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(shiki@4.2.0) @@ -847,10 +848,10 @@ importers: version: 1.2.0(zod@4.6.5) '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/platform-node-shared': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) '@ff-labs/fff-node': specifier: 0.11.0 version: 0.11.0(patch_hash=cc71fcec015f4d39bc96ae2e2bf6b0ca678124c722c66a7841efce9d34b5983c) @@ -922,7 +923,7 @@ importers: version: 8.0.3 effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) ffi-rs: specifier: 1.3.2 version: 1.3.2 @@ -962,10 +963,10 @@ importers: devDependencies: '@effect/graphql-generator': specifier: 'catalog:' - version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2) + version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@opencode-ai/sdk': specifier: ^1.3.15 version: 1.15.13 @@ -1064,7 +1065,7 @@ importers: version: 3.2.2(react@19.3.0) '@effect/atom-react': specifier: 4.1.2 - version: 4.1.2(effect@4.1.2)(react@19.3.0)(scheduler@0.28.0) + version: 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react@19.3.0)(scheduler@0.28.0) '@fontsource-variable/inter': specifier: ^5.3.0 version: 5.3.0 @@ -1160,7 +1161,7 @@ importers: version: 3.4.16 effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) entities: specifier: 8.1.0 version: 8.1.0 @@ -1263,10 +1264,10 @@ importers: devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@tailwindcss/vite': specifier: 4.3.3 version: 4.3.3(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)) @@ -1341,7 +1342,7 @@ importers: dependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@t3tools/contracts': specifier: workspace:* version: link:../packages/contracts @@ -1353,11 +1354,11 @@ importers: version: link:../packages/shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1372,28 +1373,28 @@ importers: dependencies: '@alchemy.run/axiom': specifier: 'catalog:' - version: 2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2) + version: 2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@alchemy.run/cloudflare': specifier: 'catalog:' - version: 2.0.0-beta.85(3c6bb1d0d4de1cb5d4932cf1224d79e6) + version: 2.0.0-beta.85(a30443a7d9d825a27172692bf6ff5259) '@alchemy.run/drizzle': specifier: 'catalog:' - version: 2.0.0-beta.85(0f2f0b2f6e85ad5bbc9df78a7c15a609) + version: 2.0.0-beta.85(89dcdfb192cc3ca5576df6838ba0e2c0) '@alchemy.run/planetscale': specifier: 'catalog:' - version: 2.0.0-beta.85(@effect/sql-pg@4.1.2(effect@4.1.2))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)(pg@8.23.0) + version: 2.0.0-beta.85(99fefaf926729b222ec880bde912b2e8) '@clerk/backend': specifier: 3.25.0 version: 3.25.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@distilled.cloud/cloudflare': specifier: 'catalog:' - version: 1.0.0-rc.18(effect@4.1.2) + version: 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@effect/sql-pg': specifier: 4.1.2 - version: 4.1.2(effect@4.1.2) + version: 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@effect/sql-sqlite-do': specifier: 'catalog:' - version: 4.1.2(effect@4.1.2) + version: 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@noble/curves': specifier: 'catalog:' version: 1.9.1 @@ -1411,26 +1412,26 @@ importers: version: link:../../packages/shared alchemy: specifier: 2.0.0-beta.85 - version: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + version: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) drizzle-orm: specifier: 1.0.0-rc.5-ab785fc - version: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.1.1(effect@4.1.2))(@effect/sql-pg@4.1.2(effect@4.1.2))(@effect/sql-sqlite-do@4.1.2(effect@4.1.2))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/sql.js@1.4.11)(bun-types@1.3.14)(effect@4.1.2)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(sql.js@1.15.0)(zod@4.6.5) + version: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.1.1(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/sql-pg@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/sql-sqlite-do@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/sql.js@1.4.11)(bun-types@1.3.14)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(sql.js@1.15.0)(zod@4.6.5) effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@alchemy.run/test': specifier: 'catalog:' - version: 2.0.0-beta.85(patch_hash=29c0539ea71d9cc372f0b16558640599beb9bc609605feb2a493da268d72713e)(071253407c4f3cb63d401833ee1dda26) + version: 2.0.0-beta.85(patch_hash=29c0539ea71d9cc372f0b16558640599beb9bc609605feb2a493da268d72713e)(138f41681746310e26e23ac62b112bdb) '@cloudflare/workers-types': specifier: ^4.20260601.1 version: 4.20260604.1 '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@electric-sql/pglite': specifier: 0.3.15 version: 0.3.15 @@ -1454,16 +1455,16 @@ importers: dependencies: '@alchemy.run/axiom': specifier: 'catalog:' - version: 2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2) + version: 2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@alchemy.run/cloudflare': specifier: 'catalog:' - version: 2.0.0-beta.85(3c6bb1d0d4de1cb5d4932cf1224d79e6) + version: 2.0.0-beta.85(a30443a7d9d825a27172692bf6ff5259) alchemy: specifier: 2.0.0-beta.85 - version: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + version: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) jose: specifier: 'catalog:' version: 6.2.2 @@ -1473,10 +1474,10 @@ importers: version: 4.20260604.1 '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1494,14 +1495,14 @@ importers: version: 1.89.0 effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) vite-plus: specifier: 1.4.0 version: 1.4.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) @@ -1552,7 +1553,7 @@ importers: version: link:../source-control-definitions effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) jszip: specifier: 3.10.1 version: 3.10.1 @@ -1562,7 +1563,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/three': specifier: 0.180.0 version: 0.180.0 @@ -1574,11 +1575,11 @@ importers: dependencies: effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) vite-plus: specifier: 1.4.0 version: 1.4.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) @@ -1587,17 +1588,17 @@ importers: dependencies: effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/openapi-generator': specifier: 'catalog:' - version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2)(encoding@0.1.13) + version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(encoding@0.1.13) '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1609,17 +1610,17 @@ importers: dependencies: effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/openapi-generator': specifier: 'catalog:' - version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2)(encoding@0.1.13) + version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(encoding@0.1.13) '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1643,14 +1644,14 @@ importers: version: 8.0.3 effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1680,14 +1681,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1711,14 +1712,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1745,17 +1746,17 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) yaml: specifier: ^2.9.0 version: 2.9.0 devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1782,14 +1783,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1816,14 +1817,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1853,11 +1854,11 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -1884,14 +1885,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1909,7 +1910,7 @@ importers: version: 1.15.13 '@opencode/client': specifier: 2.0.28 - version: 2.0.28(effect@4.1.2) + version: 2.0.28(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@opencode/protocol': specifier: 2.0.28 version: 2.0.28 @@ -1927,14 +1928,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1958,14 +1959,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/provider-testing': specifier: workspace:* version: link:../provider-testing @@ -1980,7 +1981,7 @@ importers: dependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@t3tools/contracts': specifier: workspace:* version: link:../contracts @@ -1992,11 +1993,11 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -2008,14 +2009,14 @@ importers: version: link:../contracts effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -2048,14 +2049,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -2076,7 +2077,7 @@ importers: version: link:../contracts effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) ffi-rs: specifier: 1.3.2 version: 1.3.2 @@ -2128,10 +2129,10 @@ importers: devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/mdast': specifier: ^4.0.4 version: 4.0.4 @@ -2161,14 +2162,14 @@ importers: version: 8.0.3 effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -2195,14 +2196,14 @@ importers: version: link:../source-control-core effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -2223,11 +2224,11 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -2275,14 +2276,14 @@ importers: version: link:../source-control-core effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -2309,11 +2310,11 @@ importers: version: link:../source-control-core effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -2340,17 +2341,17 @@ importers: version: link:../source-control-core effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/graphql-generator': specifier: 'catalog:' - version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2) + version: 4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -2374,11 +2375,11 @@ importers: version: link:../source-control-core effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@t3tools/source-control-testing': specifier: workspace:* version: link:../source-control-testing @@ -2393,7 +2394,7 @@ importers: dependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@t3tools/contracts': specifier: workspace:* version: link:../contracts @@ -2405,7 +2406,7 @@ importers: version: link:../source-control-core effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@types/node': specifier: 24.12.4 @@ -2421,14 +2422,14 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -2443,11 +2444,11 @@ importers: version: link:../shared effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/node': specifier: 24.12.4 version: 24.12.4 @@ -2459,7 +2460,7 @@ importers: dependencies: '@effect/platform-node': specifier: 4.1.2 - version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + version: 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) '@electron/asar': specifier: ^4.3.0 version: 4.3.0 @@ -2480,7 +2481,7 @@ importers: version: 0.21.32 effect: specifier: 4.1.2 - version: 4.1.2 + version: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) playwright-core: specifier: 1.60.0 version: 1.60.0 @@ -2496,7 +2497,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.1.2 - version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + version: 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) '@types/plist': specifier: 'catalog:' version: 3.0.5 @@ -12630,27 +12631,27 @@ snapshots: dependencies: json-schema: 0.4.0 - '@alchemy.run/acme@2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)': + '@alchemy.run/acme@2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/acme': 1.0.0-rc.18(effect@4.1.2) - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - effect: 4.1.2 + '@distilled.cloud/acme': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@alchemy.run/axiom@2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)': + '@alchemy.run/axiom@2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/axiom': 1.0.0-rc.18(effect@4.1.2) - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - effect: 4.1.2 + '@distilled.cloud/axiom': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@alchemy.run/cloudflare-runtime@2.0.0-beta.85(@distilled.cloud/cloudflare@1.0.0-rc.18(effect@4.1.2))(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(rolldown@1.2.13)': + '@alchemy.run/cloudflare-runtime@2.0.0-beta.85(@distilled.cloud/cloudflare@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(rolldown@1.2.13)': dependencies: '@alchemy.run/node-utils': 2.0.0-beta.85 '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261026.1) - '@distilled.cloud/cloudflare': 1.0.0-rc.18(effect@4.1.2) + '@distilled.cloud/cloudflare': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@puppeteer/browsers': 3.2.6(yauzl@3.4.0) capnp-es: 0.0.16(@tsc-rs/typescript@0.8.0) - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) magic-string: 0.30.21 mime: 4.1.1 sharp: 0.35.5(@types/node@24.12.4) @@ -12658,8 +12659,8 @@ snapshots: workerd: 1.20261026.1 yauzl: 3.4.0 optionalDependencies: - '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) - '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) + '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) rolldown: 1.2.13 vite: '@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' transitivePeerDependencies: @@ -12667,27 +12668,27 @@ snapshots: - proxy-agent - typescript - '@alchemy.run/cloudflare@2.0.0-beta.85(3c6bb1d0d4de1cb5d4932cf1224d79e6)': - dependencies: - '@alchemy.run/acme': 2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2) - '@alchemy.run/cloudflare-runtime': 2.0.0-beta.85(@distilled.cloud/cloudflare@1.0.0-rc.18(effect@4.1.2))(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(rolldown@1.2.13) - '@alchemy.run/docker': 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2) - '@alchemy.run/sql': 2.0.0-beta.85(@effect/sql-pg@4.1.2(effect@4.1.2))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)(pg@8.23.0) - '@distilled.cloud/aws': 1.0.0-rc.18(effect@4.1.2) - '@distilled.cloud/aws4signer': 1.0.0-rc.18(effect@4.1.2) - '@distilled.cloud/cloudflare': 1.0.0-rc.18(effect@4.1.2) - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - '@distilled.cloud/iceberg': 1.0.0-rc.18(effect@4.1.2) - '@effect/sql-d1': 4.1.1(effect@4.1.2) - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - effect: 4.1.2 + '@alchemy.run/cloudflare@2.0.0-beta.85(a30443a7d9d825a27172692bf6ff5259)': + dependencies: + '@alchemy.run/acme': 2.0.0-beta.85(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@alchemy.run/cloudflare-runtime': 2.0.0-beta.85(@distilled.cloud/cloudflare@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(rolldown@1.2.13) + '@alchemy.run/docker': 2.0.0-beta.85(45b412ed477696545549426d55456d0f) + '@alchemy.run/sql': 2.0.0-beta.85(99fefaf926729b222ec880bde912b2e8) + '@distilled.cloud/aws': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@distilled.cloud/aws4signer': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@distilled.cloud/cloudflare': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@distilled.cloud/iceberg': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@effect/sql-d1': 4.1.1(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) pathe: 2.0.3 picomatch: 4.0.7 rolldown: 1.2.13 tinyglobby: 0.2.17 optionalDependencies: - '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) - '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) + '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) vite: '@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' transitivePeerDependencies: - '@effect/sql-libsql' @@ -12699,25 +12700,25 @@ snapshots: - proxy-agent - typescript - '@alchemy.run/docker@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)': + '@alchemy.run/docker@2.0.0-beta.85(45b412ed477696545549426d55456d0f)': dependencies: - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - effect: 4.1.2 + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) rolldown: 1.2.13 optionalDependencies: - '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) + '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) - '@alchemy.run/drizzle@2.0.0-beta.85(0f2f0b2f6e85ad5bbc9df78a7c15a609)': + '@alchemy.run/drizzle@2.0.0-beta.85(89dcdfb192cc3ca5576df6838ba0e2c0)': dependencies: - '@alchemy.run/sql': 2.0.0-beta.85(@effect/sql-pg@4.1.2(effect@4.1.2))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)(pg@8.23.0) - '@effect/sql-d1': 4.1.1(effect@4.1.2) - '@effect/sql-sqlite-do': 4.1.2(effect@4.1.2) - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - drizzle-orm: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.1.1(effect@4.1.2))(@effect/sql-pg@4.1.2(effect@4.1.2))(@effect/sql-sqlite-do@4.1.2(effect@4.1.2))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/sql.js@1.4.11)(bun-types@1.3.14)(effect@4.1.2)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(sql.js@1.15.0)(zod@4.6.5) - effect: 4.1.2 + '@alchemy.run/sql': 2.0.0-beta.85(99fefaf926729b222ec880bde912b2e8) + '@effect/sql-d1': 4.1.1(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@effect/sql-sqlite-do': 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + drizzle-orm: 1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.1.1(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/sql-pg@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/sql-sqlite-do@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/sql.js@1.4.11)(bun-types@1.3.14)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(sql.js@1.15.0)(zod@4.6.5) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) optionalDependencies: - '@alchemy.run/cloudflare': 2.0.0-beta.85(3c6bb1d0d4de1cb5d4932cf1224d79e6) - '@effect/sql-pg': 4.1.2(effect@4.1.2) + '@alchemy.run/cloudflare': 2.0.0-beta.85(a30443a7d9d825a27172692bf6ff5259) + '@effect/sql-pg': 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) drizzle-kit: 1.0.0-rc.5-ab785fc transitivePeerDependencies: - mysql2 @@ -12728,13 +12729,13 @@ snapshots: chokidar: 5.0.0 rolldown: 1.2.13 - '@alchemy.run/planetscale@2.0.0-beta.85(@effect/sql-pg@4.1.2(effect@4.1.2))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)(pg@8.23.0)': + '@alchemy.run/planetscale@2.0.0-beta.85(99fefaf926729b222ec880bde912b2e8)': dependencies: - '@alchemy.run/sql': 2.0.0-beta.85(@effect/sql-pg@4.1.2(effect@4.1.2))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)(pg@8.23.0) - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - '@distilled.cloud/planetscale': 1.0.0-rc.18(effect@4.1.2) - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - effect: 4.1.2 + '@alchemy.run/sql': 2.0.0-beta.85(99fefaf926729b222ec880bde912b2e8) + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@distilled.cloud/planetscale': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) optionalDependencies: pg: 8.23.0 transitivePeerDependencies: @@ -12747,21 +12748,21 @@ snapshots: '@types/react': 19.3.0 react-devtools-core: 6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6) - '@alchemy.run/sql@2.0.0-beta.85(@effect/sql-pg@4.1.2(effect@4.1.2))(alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)))(effect@4.1.2)(pg@8.23.0)': + '@alchemy.run/sql@2.0.0-beta.85(99fefaf926729b222ec880bde912b2e8)': dependencies: - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - effect: 4.1.2 + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) optionalDependencies: - '@effect/sql-pg': 4.1.2(effect@4.1.2) + '@effect/sql-pg': 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) pg: 8.23.0 - '@alchemy.run/test@2.0.0-beta.85(patch_hash=29c0539ea71d9cc372f0b16558640599beb9bc609605feb2a493da268d72713e)(071253407c4f3cb63d401833ee1dda26)': + '@alchemy.run/test@2.0.0-beta.85(patch_hash=29c0539ea71d9cc372f0b16558640599beb9bc609605feb2a493da268d72713e)(138f41681746310e26e23ac62b112bdb)': dependencies: - '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) - effect: 4.1.2 + '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) optionalDependencies: - '@effect/vitest': 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) - alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) + '@effect/vitest': 4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) + alchemy: 2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)) vitest: 5.0.5(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0) transitivePeerDependencies: - bufferutil @@ -13729,45 +13730,45 @@ snapshots: '@dimforge/rapier3d-compat@0.12.0': {} - '@distilled.cloud/acme@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/acme@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - effect: 4.1.2 + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@distilled.cloud/aws4signer@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/aws4signer@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - effect: 4.1.2 + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@distilled.cloud/aws@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/aws@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/aws4signer': 1.0.0-rc.18(effect@4.1.2) - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - effect: 4.1.2 + '@distilled.cloud/aws4signer': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@distilled.cloud/axiom@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/axiom@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - effect: 4.1.2 + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@distilled.cloud/cloudflare@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/cloudflare@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - effect: 4.1.2 + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@distilled.cloud/core@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/core@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@distilled.cloud/iceberg@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/iceberg@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - effect: 4.1.2 + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@distilled.cloud/planetscale@1.0.0-rc.18(effect@4.1.2)': + '@distilled.cloud/planetscale@1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2) - effect: 4.1.2 + '@distilled.cloud/core': 1.0.0-rc.18(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) '@dnd-kit/accessibility@3.1.1(react@19.3.0)': dependencies: @@ -13803,64 +13804,64 @@ snapshots: '@drizzle-team/brocli@0.12.0': {} - '@effect/atom-react@4.1.2(effect@4.1.2)(react@19.3.0)(scheduler@0.28.0)': + '@effect/atom-react@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react@19.3.0)(scheduler@0.28.0)': dependencies: - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) react: 19.3.0 scheduler: 0.28.0 - '@effect/graphql-generator@4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2)': + '@effect/graphql-generator@4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) - effect: 4.1.2 + '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@effect/openapi-generator@4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2)(encoding@0.1.13)': + '@effect/openapi-generator@4.1.2(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(encoding@0.1.13)': dependencies: - '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) - effect: 4.1.2 + '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) swagger2openapi: 7.0.8(encoding@0.1.13) transitivePeerDependencies: - encoding - '@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6)': + '@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6)': dependencies: - '@effect/platform-node-shared': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) - effect: 4.1.2 + '@effect/platform-node-shared': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node-shared@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6)': + '@effect/platform-node-shared@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6)': dependencies: '@types/ws': 8.18.2 - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6)': + '@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6)': dependencies: - '@effect/platform-node-shared': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) - effect: 4.1.2 + '@effect/platform-node-shared': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) redis: 6.2.1 undici: 8.11.2 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/sql-d1@4.1.1(effect@4.1.2)': + '@effect/sql-d1@4.1.1(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: '@cloudflare/workers-types': 5.20261026.1 - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@effect/sql-pg@4.1.2(effect@4.1.2)': + '@effect/sql-pg@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) - '@effect/sql-sqlite-do@4.1.2(effect@4.1.2)': + '@effect/sql-sqlite-do@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) '@effect/tsgo-darwin-arm64@0.57.0': optional: true @@ -13893,9 +13894,9 @@ snapshots: '@effect/tsgo-win32-arm64': 0.57.0 '@effect/tsgo-win32-x64': 0.57.0 - '@effect/vitest@4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)': + '@effect/vitest@4.1.2(patch_hash=299510b71c36537a832a67e9d160a5f112a5f326a29e95c41cb45fc8234e47c7)(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0)': dependencies: - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) vite-plus: 1.4.0(@astrojs/compiler-rs@0.5.1)(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(bufferutil@4.1.0)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.1.0(@noble/hashes@1.8.0))(playwright@1.60.0)(terser@5.48.0)(unrun@0.2.39)(utf-8-validate@6.0.6)(yaml@2.9.0) transitivePeerDependencies: - '@arethetypeswrong/core' @@ -15349,22 +15350,22 @@ snapshots: dependencies: cross-spawn: 7.0.6 - '@opencode/client@2.0.28(effect@4.1.2)': + '@opencode/client@2.0.28(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))': dependencies: '@opencode/protocol': 2.0.28 '@opencode/schema': 2.0.28 optionalDependencies: - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) '@opencode/protocol@2.0.28': dependencies: '@opencode/schema': 2.0.28 - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) '@opencode/schema@2.0.28': dependencies: '@standard-schema/spec': 1.1.0 - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) '@oslojs/asn1@1.0.0': dependencies: @@ -17371,12 +17372,12 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)): + alchemy@2.0.0-beta.85(@effect/platform-bun@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6))(@effect/platform-node@4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0))(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6))(ws@8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)): dependencies: '@alchemy.run/node-utils': 2.0.0-beta.85 '@alchemy.run/sigil': 0.1.0-alpha.1(@types/react@19.3.0)(react-devtools-core@6.1.5(bufferutil@4.1.0)(utf-8-validate@6.0.6)) capnweb: 0.12.0 - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) fflate: 0.8.3 pathe: 2.0.3 picomatch: 4.0.7 @@ -17384,8 +17385,8 @@ snapshots: tinyglobby: 0.2.17 yaml: 2.9.0 optionalDependencies: - '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(utf-8-validate@6.0.6) - '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2)(redis@6.2.1)(utf-8-validate@6.0.6) + '@effect/platform-bun': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(utf-8-validate@6.0.6) + '@effect/platform-node': 4.1.2(bufferutil@4.1.0)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(redis@6.2.1)(utf-8-validate@6.0.6) vite: '@voidzero-dev/vite-plus-core@1.4.0(@tsc-rs/typescript@0.8.0)(@types/node@24.12.4)(@volar/typescript@2.4.28(@tsc-rs/typescript@0.8.0))(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(unrun@0.2.39)(yaml@2.9.0)' ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: @@ -18333,17 +18334,17 @@ snapshots: get-tsconfig: 4.14.3 jiti: 2.7.0 - drizzle-orm@1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.1.1(effect@4.1.2))(@effect/sql-pg@4.1.2(effect@4.1.2))(@effect/sql-sqlite-do@4.1.2(effect@4.1.2))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/sql.js@1.4.11)(bun-types@1.3.14)(effect@4.1.2)(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(sql.js@1.15.0)(zod@4.6.5): + drizzle-orm@1.0.0-rc.5-ab785fc(@cloudflare/workers-types@4.20260604.1)(@effect/sql-d1@4.1.1(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/sql-pg@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@effect/sql-sqlite-do@4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)))(@electric-sql/pglite@0.3.15)(@libsql/client@0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/sql.js@1.4.11)(bun-types@1.3.14)(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475))(expo-sqlite@58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0))(pg@8.23.0)(sql.js@1.15.0)(zod@4.6.5): optionalDependencies: '@cloudflare/workers-types': 4.20260604.1 - '@effect/sql-d1': 4.1.1(effect@4.1.2) - '@effect/sql-pg': 4.1.2(effect@4.1.2) - '@effect/sql-sqlite-do': 4.1.2(effect@4.1.2) + '@effect/sql-d1': 4.1.1(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@effect/sql-pg': 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) + '@effect/sql-sqlite-do': 4.1.2(effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475)) '@electric-sql/pglite': 0.3.15 '@libsql/client': 0.17.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) '@types/sql.js': 1.4.11 bun-types: 1.3.14 - effect: 4.1.2 + effect: 4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475) expo-sqlite: 58.0.13(expo@58.0.14)(react-native@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.88.0(@babel/core@7.29.7(supports-color@8.1.1))(bufferutil@4.1.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(supports-color@8.1.1)(utf-8-validate@6.0.6))(react@19.3.0) pg: 8.23.0 sql.js: 1.15.0 @@ -18365,7 +18366,7 @@ snapshots: ee-first@1.1.1: {} - effect@4.1.2: {} + effect@4.1.2(patch_hash=41134f71f824e18c1e538966686023be0e0fd21d6e59ad4b3e574ab7e906d475): {} ejs@3.1.10: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index df77b847c3..e8d0d1c186 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -366,6 +366,7 @@ packageExtensions: vite-plus: "catalog:" patchedDependencies: + effect@4.1.2: patches/effect@4.1.2.patch react-native-image-viewing@0.2.2: patches/react-native-image-viewing@0.2.2.patch mdast-util-math@3.0.0: patches/mdast-util-math@3.0.0.patch micromark-extension-llm-math@3.1.0: patches/micromark-extension-llm-math@3.1.0.patch diff --git a/scripts/jones/boundary/fragments/connection-rpc-isolation.json b/scripts/jones/boundary/fragments/connection-rpc-isolation.json index 22ffbfe4cc..7d043474f6 100644 --- a/scripts/jones/boundary/fragments/connection-rpc-isolation.json +++ b/scripts/jones/boundary/fragments/connection-rpc-isolation.json @@ -9,7 +9,9 @@ "packages/client-runtime/src/jones/connection/betterRouteRecovery.ts", "packages/client-runtime/src/jones/connection/betterRouteRecovery.test.ts", "packages/client-runtime/src/jones/sync/producerGuard.ts", - "packages/client-runtime/src/jones/sync/commandIsolation.test.ts" + "packages/client-runtime/src/jones/sync/commandIsolation.test.ts", + "patches/effect@4.1.2.patch", + "packages/client-runtime/src/jones/connection/unusedAuthorization.ts" ], "hookPaths": [ "packages/client-runtime/src/connection/driver.ts", @@ -18,7 +20,8 @@ "packages/client-runtime/src/connection/supervisor.ts", "packages/client-runtime/src/connection/registry.ts", "packages/client-runtime/src/rpc/client.ts", - "packages/client-runtime/src/state/shell.ts" + "packages/client-runtime/src/state/shell.ts", + "packages/client-runtime/src/state/threads.ts" ], "tombstones": [], "allowedImports": [ @@ -49,6 +52,10 @@ { "from": "packages/client-runtime/src/state/shell.ts", "to": "packages/client-runtime/src/jones/sync/producerGuard.ts" + }, + { + "from": "packages/client-runtime/src/state/threads.ts", + "to": "packages/client-runtime/src/jones/sync/producerGuard.ts" } ] } diff --git a/scripts/jones/incremental-typecheck/incrementalTypecheck.ts b/scripts/jones/incremental-typecheck/incrementalTypecheck.ts index 973ca3cb71..f867ee0e04 100644 --- a/scripts/jones/incremental-typecheck/incrementalTypecheck.ts +++ b/scripts/jones/incremental-typecheck/incrementalTypecheck.ts @@ -1,3 +1,4 @@ +import * as Digest from "effect/crypto/Digest"; import * as Scope from "effect/Scope"; import { configurationInputs } from "../tsrs/config-inputs.ts"; export { configurationInputs } from "../tsrs/config-inputs.ts"; @@ -27,7 +28,7 @@ export { import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as FileSystem from "effect/FileSystem"; import type * as PlatformError from "effect/PlatformError"; import * as Stream from "effect/Stream"; @@ -374,11 +375,11 @@ export async function sharedIdentity( // revalidation calls this afresh; no metadata or bindings survive a sweep. const path = hostPath(); const fs = Effect.runSync(Effect.provide(FileSystem.FileSystem, NodeFileSystem.layer)); - const crypto = Effect.runSync(Effect.provide(Crypto.Crypto, NodeCrypto.layer)); + const crypto = Effect.runSync(Effect.provide(Digest.Digest, NodeCrypto.layer)); const hash = (bytes: string | Uint8Array) => Buffer.from( Effect.runSync( - crypto.digest("SHA-256", typeof bytes === "string" ? Buffer.from(bytes) : bytes), + crypto.digest(Digest.sha256, typeof bytes === "string" ? Buffer.from(bytes) : bytes), ), ).toString("hex"); const row = (label: string, valueHash: string) => `${JSON.stringify(label)}\0${valueHash}\n`; @@ -536,7 +537,7 @@ export async function checkProject(options: CheckProjectOptions): Promise crypto.randomUUIDv4()).pipe( + Effect.flatMap(SecureRandom.SecureRandom, (crypto) => crypto.uuidV4()).pipe( Effect.provide(NodeCrypto.layer), ), ); diff --git a/scripts/jones/tsrs/build-info.ts b/scripts/jones/tsrs/build-info.ts index e7c1a12109..978faa330a 100644 --- a/scripts/jones/tsrs/build-info.ts +++ b/scripts/jones/tsrs/build-info.ts @@ -1,6 +1,6 @@ import * as HostProcess from "@t3tools/shared/HostProcess"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; -import * as Crypto from "effect/Crypto"; +import * as SecureRandom from "effect/crypto/SecureRandom"; import * as Clock from "effect/Clock"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; @@ -490,7 +490,7 @@ export async function runWithBuildInfo(options: StatefulCheck): Promise }), ); const nonce = Effect.runSync( - Effect.flatMap(Crypto.Crypto, (crypto) => crypto.randomUUIDv4()).pipe( + Effect.flatMap(SecureRandom.SecureRandom, (crypto) => crypto.uuidV4()).pipe( Effect.provide(NodeCrypto.layer), ), ); diff --git a/scripts/jones/tsrs/identity.node-test.mjs b/scripts/jones/tsrs/identity.node-test.mjs index e24fbbeecb..e818543117 100644 --- a/scripts/jones/tsrs/identity.node-test.mjs +++ b/scripts/jones/tsrs/identity.node-test.mjs @@ -627,7 +627,7 @@ NodeTest.test( ); NodeAssert.ok( diagnosticLines - .filter((line) => /error TS\d+/.test(line)) + .filter((line) => /error TS\d+/.test(line) && !line.includes("TS377047")) .every((line) => /TS377017|TS377067/.test(line)), diagnostics, ); @@ -636,6 +636,10 @@ NodeTest.test( context.provider ? 0 : suggestions.length, diagnostics, ); + NodeAssert.ok( + fnSuggestions.every((line) => line.includes("error TS377047")), + diagnostics, + ); for (const file of files) { const lines = diagnosticLines.filter((line) => line.includes(`${file}(`)); NodeAssert.equal( diff --git a/scripts/jones/tsrs/seed-files.ts b/scripts/jones/tsrs/seed-files.ts index 7f3e959d26..abf980b1d9 100644 --- a/scripts/jones/tsrs/seed-files.ts +++ b/scripts/jones/tsrs/seed-files.ts @@ -2,7 +2,7 @@ import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem"; import * as NodePath from "@effect/platform-node/NodePath"; import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; import * as ByteSize from "effect/ByteSize"; -import * as Crypto from "effect/Crypto"; +import * as Digest from "effect/crypto/Digest"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; import * as Scope from "effect/Scope"; @@ -57,8 +57,8 @@ export function errorCode(error: unknown): string | undefined { export function digest(bytes: string | Buffer): string { return Buffer.from( Effect.runSync( - Effect.flatMap(Crypto.Crypto, (crypto) => - crypto.digest("SHA-256", typeof bytes === "string" ? Buffer.from(bytes) : bytes), + Effect.flatMap(Digest.Digest, (crypto) => + crypto.digest(Digest.sha256, typeof bytes === "string" ? Buffer.from(bytes) : bytes), ).pipe(Effect.provide(NodeCrypto.layer)), ), ).toString("hex");