feat: add warning/error message type and skip TTS in voice mode

Add `type: "warning"` to context compression and memory optimization
notifications on the Go server. Propagate messageType through the
Android data layer (Entity → Mapper → Domain) so voice mode can
detect warning/error messages and display them without reading aloud.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
KoheiYamashita 2026-02-21 04:16:36 +09:00
parent a66be29993
commit 1e5d740bf2
6 changed files with 16 additions and 6 deletions

View file

@ -10,5 +10,6 @@ data class MessageEntity(
val sender: String, val sender: String,
val imagePathList: String?, val imagePathList: String?,
val timestamp: Long, val timestamp: Long,
val status: String val status: String,
val messageType: String? = null
) )

View file

@ -36,7 +36,8 @@ object MessageMapper {
sender = MessageSender.valueOf(entity.sender), sender = MessageSender.valueOf(entity.sender),
images = images, images = images,
timestamp = entity.timestamp, timestamp = entity.timestamp,
status = MessageStatus.valueOf(entity.status) status = MessageStatus.valueOf(entity.status),
messageType = entity.messageType
) )
} }
@ -47,7 +48,8 @@ object MessageMapper {
sender = MessageSender.AGENT.name, sender = MessageSender.AGENT.name,
imagePathList = null, imagePathList = null,
timestamp = System.currentTimeMillis(), timestamp = System.currentTimeMillis(),
status = MessageStatus.RECEIVED.name status = MessageStatus.RECEIVED.name,
messageType = dto.type
) )
} }

View file

@ -6,7 +6,8 @@ data class ChatMessage(
val sender: MessageSender, val sender: MessageSender,
val images: List<ImageData> = emptyList(), val images: List<ImageData> = emptyList(),
val timestamp: Long, val timestamp: Long,
val status: MessageStatus val status: MessageStatus,
val messageType: String? = null
) )
enum class MessageSender { USER, AGENT } enum class MessageSender { USER, AGENT }

View file

@ -97,11 +97,15 @@ class VoiceModeManager(
msg.status == MessageStatus.RECEIVED msg.status == MessageStatus.RECEIVED
) { ) {
spokenIds.add(msg.id) spokenIds.add(msg.id)
if (msg.messageType == "warning" || msg.messageType == "error") {
_state.update { it.copy(responseText = msg.content) }
} else {
speechQueue.send(msg.content) speechQueue.send(msg.content)
} }
} }
} }
} }
}
try { try {
while (isActive) { while (isActive) {

View file

@ -144,7 +144,7 @@ fun VoiceModeOverlay(
) )
} }
if (state.responseText.isNotEmpty() && state.phase == VoicePhase.SPEAKING) { if (state.responseText.isNotEmpty() && (state.phase == VoicePhase.SPEAKING || state.phase == VoicePhase.THINKING)) {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = state.responseText, text = state.responseText,

View file

@ -663,6 +663,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M
Channel: opts.Channel, Channel: opts.Channel,
ChatID: opts.ChatID, ChatID: opts.ChatID,
Content: "⚠️ Context window exceeded. Compressing history and retrying...", Content: "⚠️ Context window exceeded. Compressing history and retrying...",
Type: "warning",
}) })
} }
@ -947,6 +948,7 @@ func (al *AgentLoop) maybeSummarize(sessionKey, channel, chatID string) {
Channel: channel, Channel: channel,
ChatID: chatID, ChatID: chatID,
Content: "⚠️ Memory threshold reached. Optimizing conversation history...", Content: "⚠️ Memory threshold reached. Optimizing conversation history...",
Type: "warning",
}) })
} }
al.summarizeSession(sessionKey) al.summarizeSession(sessionKey)