From 9065dcef54dda7257f5e6e111bdb465d19fe0b0a Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 19 Mar 2026 14:01:09 +0700 Subject: [PATCH] Fix daemon startup blocking on model downloads --- .../server/src/server/bootstrap.smoke.test.ts | 47 ++++++++++++++++++- .../server/speech/providers/local/runtime.ts | 30 ------------ 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/packages/server/src/server/bootstrap.smoke.test.ts b/packages/server/src/server/bootstrap.smoke.test.ts index 3baf76765..9d94db05d 100644 --- a/packages/server/src/server/bootstrap.smoke.test.ts +++ b/packages/server/src/server/bootstrap.smoke.test.ts @@ -3,13 +3,17 @@ import path from "node:path"; import { mkdir, mkdtemp, rm } from "node:fs/promises"; import { Writable } from "node:stream"; import pino from "pino"; -import { describe, expect, test } from "vitest"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { createPaseoDaemon, parseListenString, type PaseoDaemonConfig } from "./bootstrap.js"; import { createTestPaseoDaemon } from "./test-utils/paseo-daemon.js"; import { createTestAgentClients } from "./test-utils/fake-agent-client.js"; describe("paseo daemon bootstrap", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + test("starts and serves health endpoint", async () => { const daemonHandle = await createTestPaseoDaemon({ openai: { apiKey: "test-openai-api-key" }, @@ -77,6 +81,47 @@ describe("paseo daemon bootstrap", () => { } }); + test("does not block daemon start on local speech model downloads", async () => { + const originalFetch = globalThis.fetch; + let releaseFetch: ((value: Response) => void) | null = null; + const fetchGate = new Promise((resolve) => { + releaseFetch = resolve; + }); + vi.stubGlobal("fetch", vi.fn(() => fetchGate)); + + const daemonHandle = await createTestPaseoDaemon({ + speech: { + providers: { + dictationStt: { provider: "local", explicit: true, enabled: true }, + voiceTurnDetection: { provider: "local", explicit: true, enabled: false }, + voiceStt: { provider: "local", explicit: true, enabled: false }, + voiceTts: { provider: "local", explicit: true, enabled: false }, + }, + local: { + modelsDir: path.join(os.tmpdir(), `paseo-missing-models-${Date.now()}`), + models: { + dictationStt: "parakeet-tdt-0.6b-v3-int8", + voiceStt: "parakeet-tdt-0.6b-v3-int8", + voiceTts: "kokoro-en-v0_19", + }, + }, + }, + }); + + try { + const response = await originalFetch(`http://127.0.0.1:${daemonHandle.port}/api/health`); + expect(response.ok).toBe(true); + } finally { + releaseFetch?.( + new Response(null, { + status: 500, + statusText: "test cleanup", + }) + ); + await daemonHandle.close(); + } + }); + test("parses Windows named pipes as managed IPC listen targets", () => { expect(parseListenString(String.raw`\\.\pipe\paseo-managed-test`)).toEqual({ type: "pipe", diff --git a/packages/server/src/server/speech/providers/local/runtime.ts b/packages/server/src/server/speech/providers/local/runtime.ts index 55b9a49e6..ceadba856 100644 --- a/packages/server/src/server/speech/providers/local/runtime.ts +++ b/packages/server/src/server/speech/providers/local/runtime.ts @@ -6,7 +6,6 @@ import type { RequestedSpeechProviders } from "../../speech-types.js"; import type { TurnDetectionProvider } from "../../turn-detection-provider.js"; import { PocketTtsOnnxTTS } from "./pocket/pocket-tts-onnx.js"; import { - ensureLocalSpeechModels, getLocalSpeechModelDir, DEFAULT_LOCAL_STT_MODEL, DEFAULT_LOCAL_TTS_MODEL, @@ -203,35 +202,6 @@ export async function initializeLocalSpeechServices(params: { models: localModels, }); - if (requiredLocalModelIds.length > 0 && localConfig) { - try { - logger.info( - { - modelsDir: localConfig.modelsDir, - modelIds: requiredLocalModelIds, - }, - "Ensuring local speech models" - ); - await ensureLocalSpeechModels({ - modelsDir: localConfig.modelsDir, - modelIds: requiredLocalModelIds, - logger, - }); - } catch (err) { - logger.warn( - { - err, - modelsDir: localConfig.modelsDir, - modelIds: requiredLocalModelIds, - hint: - "Use `paseo speech models` to inspect status and " + - "`paseo speech download --model ` to fetch missing models.", - }, - "Local speech model bootstrap failed" - ); - } - } - const localSttEngines = new Map(); const getLocalSttEngine = async (