- 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.
23 lines
974 B
Swift
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."
|
|
}
|
|
}
|
|
}
|