Files
Jarvis/App/Voice/RejectReasonCopy.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

23 lines
974 B
Swift

import Foundation
import AgentOrchestrator // RejectReason
/// Single source of truth for D-10 user-facing copy. `VoiceOrchestratorAdapter`
/// (HUD banner — voice path) and `AppDelegate.handleTextOutcome` (Bus toast —
/// text path) both read from here so the two paths stay byte-identical.
///
/// Phase 9 / Plan 4. D-10 cardinal: never silently drop a user submission —
/// every `SubmitOutcome.rejected` reason MUST surface to the user with the
/// same wording regardless of whether the submission came from voice or text.
enum RejectReasonCopy {
static func body(for reason: RejectReason) -> String {
switch reason {
case .turnInFlight:
return "Already thinking — wait or say cancel."
case .providerUnavailable:
return "Provider unavailable — check Anthropic key or Ollama daemon."
case .configError:
return "Config error — see ~/Library/Logs/Jarvis/system.log."
}
}
}