diff --git a/packages/provider-muse/src/server/status.test.ts b/packages/provider-muse/src/server/status.test.ts index 04f8e1e630d08..2bc5c03da9ff7 100644 --- a/packages/provider-muse/src/server/status.test.ts +++ b/packages/provider-muse/src/server/status.test.ts @@ -2,8 +2,13 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import { MuseSettings } from "../settings.ts"; import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; import * as FileSystem from "effect/FileSystem"; import * as Schema from "effect/Schema"; +import * as Sink from "effect/Sink"; +import * as Stream from "effect/Stream"; +import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; +import * as TestClock from "effect/testing/TestClock"; import { vi } from "vite-plus/test"; import { writeFakeCli } from "@t3tools/provider-testing/fakeCli"; @@ -54,6 +59,25 @@ const fakeCli = (source = 'process.stdout.write("muse 1.0.3-R2198.1\\n");') => return writeFakeCli({ directory, name: "muse", source }); }); +const slowVersionSpawner = (delayMs: number) => + ChildProcessSpawner.make(() => + Effect.succeed( + ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(900_000_001), + exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)).pipe(Effect.delay(delayMs)), + isRunning: Effect.succeed(false), + kill: () => Effect.void, + unref: Effect.succeed(Effect.void), + stdin: Sink.drain, + stdout: Stream.succeed(new TextEncoder().encode("muse 1.4.12-R6909.1\n")), + stderr: Stream.empty, + all: Stream.empty, + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + }), + ), + ); + it.layer(NodeServices.layer)("Muse status", (it) => { it.effect("does not expose CLI stderr or start an SDK host after a failed version check", () => Effect.scoped( @@ -128,4 +152,44 @@ it.layer(NodeServices.layer)("Muse status", (it) => { }), ), ); + + it.effect("waits for a version check that takes several seconds to answer", () => + Effect.gen(function* () { + const host = makeHost(metaCatalog); + const probing = yield* checkMuseProviderStatus( + settings({ enabled: true, binaryPath: "muse" }), + undefined, + undefined, + async () => host, + ).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, slowVersionSpawner(6_000)), + Effect.forkChild({ startImmediately: true }), + ); + yield* TestClock.adjust("6 seconds"); + const snapshot = yield* Fiber.join(probing); + expect(snapshot.status).toBe("ready"); + expect(snapshot.version).toBe("1.4.12-R6909.1"); + }), + ); + + it.effect("stops a version check after fifteen seconds without starting the SDK", () => + Effect.gen(function* () { + const createHost = vi.fn(async () => makeHost(metaCatalog)); + const probing = yield* checkMuseProviderStatus( + settings({ enabled: true, binaryPath: "muse" }), + undefined, + undefined, + createHost, + ).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, slowVersionSpawner(16_000)), + Effect.forkChild({ startImmediately: true }), + ); + yield* TestClock.adjust("15 seconds"); + const snapshot = yield* Fiber.join(probing); + expect(snapshot.status).toBe("error"); + expect(snapshot.message).toBe("Muse Code CLI version check timed out."); + expect(snapshot.version).toBeNull(); + expect(createHost).not.toHaveBeenCalled(); + }), + ); }); diff --git a/packages/provider-muse/src/server/status.ts b/packages/provider-muse/src/server/status.ts index bcd910b3573b7..9fd25c7b7874c 100644 --- a/packages/provider-muse/src/server/status.ts +++ b/packages/provider-muse/src/server/status.ts @@ -13,13 +13,15 @@ import { discoverMuseModels, museModelCapabilities } from "./modelCatalog.ts"; import { buildServerProvider, COMPACT_SLASH_COMMAND, - DEFAULT_TIMEOUT_MS, isCommandMissingCause, providerModelsFromSettings, spawnAndCollect, type ProviderProbeResult, } from "@t3tools/provider-core/server/snapshotProbe"; +// Muse can take 4-9 seconds to start inside a Windows job object. +const MUSE_VERSION_PROBE_TIMEOUT = "15 seconds"; + const MUSE_PRESENTATION = { displayName: "Muse Code", showInteractionModeToggle: false, @@ -81,7 +83,7 @@ export const checkMuseProviderStatus = Effect.fn("checkMuseProviderStatus")(func ...(cwd ? { cwd } : {}), }), ); - }).pipe(Effect.timeoutOption(DEFAULT_TIMEOUT_MS), Effect.result); + }).pipe(Effect.timeoutOption(MUSE_VERSION_PROBE_TIMEOUT), Effect.result); if (Result.isFailure(versionResult)) { const missing = isCommandMissingCause(versionResult.failure);