chahinebrini 685782b538 fix(coach): dynamische Sprache (Text-Detection + App-Locale-Fallback)
LLM-Prompt (message.post + sos-stream):
- LANG_INSTRUCTIONS Map raus, ersetzt durch dynamische Instruktion
  'Reply in {detectedFromUser} ... fallback: {appLang}'
- Lyra matcht jetzt die Sprache der letzten User-Message (per
  detectLang Unicode-Detection); App-Locale ist nur noch Fallback
- Instruktion doppelt eingehängt (Anfang + Ende des System-Prompts)
  gegen recency bias bei langen deutschen Prompts

TTS (speak dispatcher + speak-cartesia + speak-elevenlabs):
- Kein 'de'-Default mehr für language. detectLang(text, locale) leitet
  Sprache primär aus dem Antwort-Text ab (Arabic/Cyrillic/CJK/Turkish-
  Letters), Locale als Fallback
- Cartesia + ElevenLabs: language/language_code nur senden wenn
  ableitbar, sonst Provider auto-detect statt erzwungenem 'de'
- speak-cartesia: sonic-2 → sonic-3 (Multi-Lang, war beim Dispatcher-
  Fix gestern vergessen worden)
- Google: en-US neutraler Fallback statt de-DE-Bias

Neu: server/utils/detect-lang.ts
2026-05-31 00:12:40 +02:00

63 lines
2.6 KiB
Swift

import Foundation
/// Wrapper um das `rebreak-supervise-magic` Go-binary aus
/// `ops/mdm/supervise-magic/bin/`. Spawnt es als child-process + streamt
/// stdout zeilenweise in die UI.
enum SuperviseRunner {
enum RunnerError: Error, LocalizedError {
case binaryMissing
case nonZeroExit(Int32)
var errorDescription: String? {
switch self {
case .binaryMissing:
return "supervise-magic Binary nicht gefunden. Bitte aus `ops/mdm/supervise-magic/` via `make build` bauen oder REBREAK_SUPERVISE_MAGIC_BIN setzen."
case .nonZeroExit(let code):
return "supervise-magic ist mit Exit-Code \(code) abgebrochen."
}
}
}
/// Pre-Flight Check: liest FMI/SDP-Status + IsSupervised.
/// Returnt das geparste output. Mit -v für detaillierte logs.
@MainActor
static func check(onLine: @escaping (String) -> Void) async throws -> ProcessRunner.Result {
guard let bin = Paths.firstExecutable(in: Paths.superviseMagicCandidates) else {
throw RunnerError.binaryMissing
}
return try await ProcessRunner.stream(bin, arguments: ["-v", "check"], onLine: onLine)
}
/// Schreibt CloudConfigurationDetails.plist auf das iPhone + reboot.
/// supervise-magic macht die ganze MobileBackup2-Sandwich-Logik.
@MainActor
static func supervise(
organizationName: String = "ReBreak",
force: Bool = true,
verbose: Bool = false,
onLine: @escaping (String) -> Void
) async throws -> ProcessRunner.Result {
guard let bin = Paths.firstExecutable(in: Paths.superviseMagicCandidates) else {
throw RunnerError.binaryMissing
}
// -yes ist Pflicht: ohne TTY-Pipe hängt der Bestätigungs-Prompt sonst endlos.
var args: [String] = verbose ? ["-v", "-yes"] : ["-yes"]
if force { args.append("-force") }
args.append(contentsOf: ["-org", organizationName, "supervise"])
let result = try await ProcessRunner.stream(bin, arguments: args, onLine: onLine)
if result.exitCode != 0 {
throw RunnerError.nonZeroExit(result.exitCode)
}
return result
}
/// Reverse-Operation für Tests / Recovery.
@MainActor
static func unsupervise(onLine: @escaping (String) -> Void) async throws -> ProcessRunner.Result {
guard let bin = Paths.firstExecutable(in: Paths.superviseMagicCandidates) else {
throw RunnerError.binaryMissing
}
return try await ProcessRunner.stream(bin, arguments: ["-v", "-yes", "unsupervise"], onLine: onLine)
}
}