feat(asr): introduce ASRProvider protocol + Volcengine wrapper
Phase C-1 of provider abstraction (mirrors LLM-side B-1/B-2/B-3).
- Add ASRProvider / ASRStreamingSession / ASRProviderInfo / ASRMode /
ASRError to OpenLessCore. Streaming and batch shapes share a single
protocol; non-self entry throws .unsupportedMode.
- ASRStreamingSession inherits AudioConsumer; sendAudio(_:) gets a
default impl that forwards to consume(pcmChunk:) — least-invasive
bridge, no changes to existing AudioConsumer / Recorder /
BufferingAudioConsumer.
- VolcengineASRProvider wraps existing VolcengineStreamingASR via a
thin adapter (VolcengineStreamingSessionAdapter). Zero modifications
to VolcengineStreamingASR / VolcengineFrame / VolcengineCredentials.
- DictationCoordinator now drives ASR through ASRProvider:
makeASRProvider() returns VolcengineASRProvider when creds exist,
nil otherwise (silent mock fallback preserved). beginSession,
endSession, handleCancel, failSession all route through
asrSession: ASRStreamingSession.
- Add OpenLessASRTests target with VolcengineASRProvider info /
unsupportedMode coverage, plus ASRProviderInfo / ASRError /
ASRMode value-shape tests.
C-3 (settings UI) will switch makeASRProvider() to read
vault.activeASRProviderId; partial results stream is wired but
empty-finished pending an internal Volcengine partial hook.