Files
Jarvis/App/Voice/VoiceBusEmitterAdapter.swift
james.maes 75a4ed6991 feat(09-04): replace NullVoiceAdapters with real voice adapter triad + lock install order (D-09, D-10, BLOCKER-1, BLOCKER-2, WARNING-5)
- New App/Voice/{VoiceOrchestratorAdapter,VoiceTTSAdapter,VoiceBusEmitterAdapter,RejectReasonCopy}.swift
  replaces NullOrchestratorAdapter / NullTTSAdapter / NullBusEmitterAdapter (D-09).
- VoiceOrchestratorAdapter forwards submit/cancelAndSubmit to AgentOrchestrator,
  appends user-side text to TurnTranscriptStore (BLOCKER-1), enqueues a HUDBanner
  on every SubmitOutcome.rejected case via shared RejectReasonCopy (D-10).
- VoiceTTSAdapter wraps an optional TTSEngineActor (engine construction lands
  in a follow-on plan; nil-engine path no-ops gracefully).
- VoiceBusEmitterAdapter wraps an OutboundBatcher whose sink is the WebviewBridge
  so audio-level RMS reaches the HUD's RingMesh pulse.
- AppDelegate.installVoice now uses the production adapters; install order
  in applicationWillFinishLaunching reordered to vision -> agent -> voice
  with voiceInstallTask awaiting agentInstallTask?.value (WARNING-5).
- New broadcaster .voice subscriber in installAgent: per-turn assistantText
  accumulator + turnSourceWasVoice filter so text-originated turns NEVER drive
  emitTurnEnded on the voice adapter (BLOCKER-2).
- SubmitOutcome.superseded extended with newTurnId so cancelAndSubmit callers
  can append user text under the new turn id (BLOCKER-1 user-side closure on
  the cancel path).
- VoiceSubscriberTextTurnFilterTests proves text turns never trigger
  emitTurnEnded; VoiceOrchestratorAdapterTests covers RejectReasonCopy +
  banner id mapping + VoiceBusEmitterAdapter forwarding.
- New scripts/check-install-order.sh + scripts/check-no-null-voice-adapters.sh
  catch regressions structurally.
2026-05-01 20:39:19 -05:00

17 lines
548 B
Swift

import Foundation
import Voice // BusOutboundEmitter
import Bus // OutboundBatcher
/// Production adapter bridging `Voice.BusOutboundEmitter` to `OutboundBatcher`.
/// Routes the ~30 Hz audio-level RMS values from `AudioLevelEmitter` into the
/// batcher so the HUD's RingMesh pulses with the mic level.
///
/// Phase 9 / Plan 4 / D-09: replaces `NullBusEmitterAdapter`.
struct VoiceBusEmitterAdapter: BusOutboundEmitter {
let batcher: OutboundBatcher
func postAudio(_ rms: Float) async {
await batcher.postAudio(rms)
}
}