feat(polish): add context safeguards, layered prompts, and output validation

Use redacted cursor neighborhood and pause-aware chunks for more natural polish,
validate protected terms with retry/local fallback, and structure bilingual prompts
for consistency and provider prefix caching.
This commit is contained in:
Rocky
2026-07-29 17:45:11 +08:00
parent 2d44423f4c
commit 34be2e8dd1
40 changed files with 1827 additions and 183 deletions
@@ -24,6 +24,7 @@ final class KeyboardFlowCoordinator {
private let wakeLockView: () -> UIView?
private let openHostApp: (String) -> Void
private let detectAndStoreAppContext: () -> Void
private let fieldContextProvider: () -> FlowFieldContext?
private let scheduleAutoClearError: () -> Void
private let refreshConfigFromAppGroup: () -> Void
@@ -71,6 +72,7 @@ final class KeyboardFlowCoordinator {
wakeLockView: @escaping () -> UIView?,
openHostApp: @escaping (String) -> Void,
detectAndStoreAppContext: @escaping () -> Void,
fieldContextProvider: @escaping () -> FlowFieldContext?,
scheduleAutoClearError: @escaping () -> Void,
refreshConfigFromAppGroup: @escaping () -> Void
) {
@@ -80,6 +82,7 @@ final class KeyboardFlowCoordinator {
self.wakeLockView = wakeLockView
self.openHostApp = openHostApp
self.detectAndStoreAppContext = detectAndStoreAppContext
self.fieldContextProvider = fieldContextProvider
self.scheduleAutoClearError = scheduleAutoClearError
self.refreshConfigFromAppGroup = refreshConfigFromAppGroup
}
@@ -552,12 +555,15 @@ final class KeyboardFlowCoordinator {
utteranceId: currentUtteranceId,
commandSeq: nextCommandSeq(),
action: action,
localeId: state.localeId
localeId: state.localeId,
fieldContext: action == .stopRecording ? fieldContextProvider() : nil
)
FlowSessionBridge.writeCommand(command)
debug(
"command \(action.rawValue) seq=\(command.commandSeq) " +
"utterance=\(currentUtteranceId.uuidString)"
"utterance=\(currentUtteranceId.uuidString) contextChars=" +
"\(command.fieldContext?.precedingText?.count ?? 0)/" +
"\(command.fieldContext?.followingText?.count ?? 0)"
)
// Start of one traceable utterance: everything the host logs afterwards
// belongs to this `utterance=` id until the matching keyboard.insert.