diff --git a/docs/plans/state.json b/docs/plans/state.json index e6d548c..349546b 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -3,6 +3,18 @@ "updated_at": "2026-02-23", "description": "Tracks the status of all Flynn plans and implementation phases", "plans": { + "dashboard-docker-dependencies-profile-aware-detection": { + "status": "completed", + "date": "2026-02-23", + "updated": "2026-02-23", + "summary": "Fixed false `not-found` Whisper status in dashboard docker dependency cards by querying docker compose with the `voice` profile enabled (`--profile voice`) for both `config --services` and `ps` probes.", + "files_modified": [ + "src/gateway/handlers/dockerDependencies.ts", + "src/gateway/handlers/dockerDependencies.test.ts", + "docs/plans/state.json" + ], + "test_status": "pnpm test:run src/gateway/handlers/dockerDependencies.test.ts + pnpm typecheck passing" + }, "minimal-tui-submitted-line-dedupe": { "status": "completed", "date": "2026-02-23", diff --git a/src/gateway/handlers/dockerDependencies.test.ts b/src/gateway/handlers/dockerDependencies.test.ts index 9d1bdca..4e8ab88 100644 --- a/src/gateway/handlers/dockerDependencies.test.ts +++ b/src/gateway/handlers/dockerDependencies.test.ts @@ -16,11 +16,13 @@ function createConfig(endpoint: string, enabled = true): Config { describe('listDockerDependencyStatuses', () => { it('reports whisper as running when compose ps shows active container', async () => { + const seenCalls: string[][] = []; const runner = async (args: string[]) => { - if (args[0] === 'config') { + seenCalls.push(args); + if (args.includes('config')) { return { stdout: 'flynn\nwhisper-server\n', stderr: '' }; } - if (args[0] === 'ps') { + if (args.includes('ps')) { return { stdout: JSON.stringify([{ Name: 'flynn-whisper-server-1', @@ -48,14 +50,16 @@ describe('listDockerDependencyStatuses', () => { statusText: 'Up 4 minutes (healthy)', containerName: 'flynn-whisper-server-1', }); + expect(seenCalls[0]).toEqual(['--profile', 'voice', 'config', '--services']); + expect(seenCalls[1]).toEqual(['--profile', 'voice', 'ps', 'whisper-server', '--format', 'json']); }); it('reports whisper as defined but not started when no container exists yet', async () => { const runner = async (args: string[]) => { - if (args[0] === 'config') { + if (args.includes('config')) { return { stdout: 'flynn\nwhisper-server\n', stderr: '' }; } - if (args[0] === 'ps') { + if (args.includes('ps')) { return { stdout: '[]', stderr: '' }; } throw new Error(`Unexpected args: ${args.join(' ')}`); @@ -76,7 +80,7 @@ describe('listDockerDependencyStatuses', () => { it('reports whisper service as missing when compose file does not define it', async () => { const runner = async (args: string[]) => { - if (args[0] === 'config') { + if (args.includes('config')) { return { stdout: 'flynn\n', stderr: '' }; } throw new Error(`Unexpected args: ${args.join(' ')}`); @@ -109,10 +113,10 @@ describe('listDockerDependencyStatuses', () => { it('marks whisper as not configured for non-local transcription endpoints', async () => { const runner = async (args: string[]) => { - if (args[0] === 'config') { + if (args.includes('config')) { return { stdout: 'whisper-server\n', stderr: '' }; } - if (args[0] === 'ps') { + if (args.includes('ps')) { return { stdout: '[]', stderr: '' }; } throw new Error(`Unexpected args: ${args.join(' ')}`); diff --git a/src/gateway/handlers/dockerDependencies.ts b/src/gateway/handlers/dockerDependencies.ts index 0791549..456b718 100644 --- a/src/gateway/handlers/dockerDependencies.ts +++ b/src/gateway/handlers/dockerDependencies.ts @@ -30,6 +30,11 @@ interface ComposePsEntry { } const WHISPER_SERVICE = 'whisper-server'; +const WHISPER_PROFILE = 'voice'; + +function withWhisperProfile(args: string[]): string[] { + return ['--profile', WHISPER_PROFILE, ...args]; +} function defaultRunner(args: string[]): Promise { return execFile('docker', ['compose', '-f', 'docker-compose.yml', ...args], { @@ -154,7 +159,7 @@ export async function listDockerDependencyStatuses( let services: string[]; try { - const response = await runner(['config', '--services']); + const response = await runner(withWhisperProfile(['config', '--services'])); services = parseServiceList(response.stdout); } catch (error) { return [{ @@ -174,7 +179,7 @@ export async function listDockerDependencyStatuses( } try { - const response = await runner(['ps', WHISPER_SERVICE, '--format', 'json']); + const response = await runner(withWhisperProfile(['ps', WHISPER_SERVICE, '--format', 'json'])); const rows = parseComposePsOutput(response.stdout) .filter((entry) => (entry.Service ?? '') === WHISPER_SERVICE || !entry.Service);