From e539d650cf35d88d4da694bf9afa0a2986292975 Mon Sep 17 00:00:00 2001 From: m57 <41645758+Hy4ri@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:26:45 +0300 Subject: [PATCH] fix(#1215): show omitted process output count (#1232) --- .../m57/hermescontrol/ui/chat/ToolBubble.kt | 7 +++++ .../ui/chat/tool/ToolRenderer.kt | 2 ++ .../hermescontrol/ui/chat/tool/ToolView.kt | 2 ++ .../ui/chat/tool/ToolViewBuilder.kt | 1 + .../ui/chat/tool/render/TaskRenderers.kt | 12 ++++++++ app/src/main/res/values-ar/strings.xml | 1 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-zh/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../ui/chat/tool/ToolViewBuilderTest.kt | 29 +++++++++++++++++++ 10 files changed, 57 insertions(+) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt index ce0f49b6..5ed735bd 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt @@ -495,6 +495,13 @@ private fun ExpandedToolContent( } } + view.outputCut?.let { omitted -> + Text( + text = stringResource(R.string.chat_tool_output_omitted, omitted), + style = MaterialTheme.typography.labelSmall.copy(color = statusColors.warning), + ) + } + // ── Duration footer ── view.durationLabel?.let { Text( diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolRenderer.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolRenderer.kt index 09a23609..3216b66f 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolRenderer.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolRenderer.kt @@ -41,6 +41,8 @@ internal interface ToolRenderer { */ internal data class ToolViewExtras( val detailLabel: String? = null, + /** Process output characters omitted by the backend. */ + val outputCut: Long? = null, val stdout: String? = null, val stderr: String? = null, val exitCode: Int? = null, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolView.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolView.kt index 112d6039..f8c1394d 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolView.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolView.kt @@ -35,6 +35,8 @@ data class ToolView( val detail: String = "", /** Optional section label above [detail] (e.g. "Search results"). */ val detailLabel: String? = null, + /** Process output characters omitted by the backend. */ + val outputCut: Long? = null, /** Compact count, e.g. "3 results". */ val countLabel: String? = null, /** Human duration, e.g. "1.2s". */ diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilder.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilder.kt index d4b436fe..a95d672b 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilder.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilder.kt @@ -106,6 +106,7 @@ object ToolViewBuilder { subtitle = subtitle, detail = clampedDetail, detailLabel = extras.detailLabel, + outputCut = extras.outputCut, countLabel = countLabel?.let { (count, noun) -> "$count ${ToolCounts.pluralizeNoun(noun, count)}" }, durationLabel = ToolJson.durationLabel(call.result), stdout = clampedStdout, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/render/TaskRenderers.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/render/TaskRenderers.kt index 0d137c92..7beb9887 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/render/TaskRenderers.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/tool/render/TaskRenderers.kt @@ -3,9 +3,12 @@ package com.m57.hermescontrol.ui.chat.tool.render import com.m57.hermescontrol.ui.chat.tool.ToolCall import com.m57.hermescontrol.ui.chat.tool.ToolJson import com.m57.hermescontrol.ui.chat.tool.ToolRenderer +import com.m57.hermescontrol.ui.chat.tool.ToolViewExtras +import com.m57.hermescontrol.ui.chat.tool.ToolViewStatus import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.longOrNull /** `cronjob`: job listings with schedules, or the mutated job's key fields. */ internal object CronjobRenderer : ToolRenderer { @@ -214,6 +217,15 @@ internal object SessionSearchRenderer : ToolRenderer { /** `process`: background session listings and lifecycle acks. */ internal object ProcessRenderer : ToolRenderer { + override fun extras( + call: ToolCall, + status: ToolViewStatus, + ): ToolViewExtras { + if (status == ToolViewStatus.RUNNING) return ToolViewExtras.NONE + val count = (call.result?.get("output_cut") as? JsonPrimitive)?.takeUnless { it.isString }?.longOrNull + return ToolViewExtras(outputCut = count?.takeIf { it > 0 }) + } + override fun subtitle(call: ToolCall): String { val action = ToolJson.firstString(call.args, listOf("action")) val procId = ToolJson.firstString(call.args, listOf("session_id")) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 69e49508..2051265b 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -165,6 +165,7 @@ اكتب رسالة\u2026 خطأ في التنفيذ: %1$s رمز الخروج: %1$d +تم حذف %1$d حرف من مخرجات العملية أداة عرض ملف JSON الأولي عرض المخرجات المعالجة diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index adbf0164..cff47d0a 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -162,6 +162,7 @@ 메시지 입력… 실행 오류: %1$s 종료 코드: %1$d + 프로세스 출력에서 %1$d자 생략됨 도구 Raw JSON 보기 파싱된 결과 보기 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index dc9370f7..c4dcb11c 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -165,6 +165,7 @@ 输入消息… 执行错误:%1$s 退出码:%1$d + 进程输出省略了 %1$d 个字符 工具 显示原始 JSON 显示解析输出 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 206f0415..9b8a1b77 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -196,6 +196,7 @@ Type a message\u2026 Execution Error: %1$s Exit Code: %1$d + %1$d characters omitted from process output Tool Show Raw JSON Show Parsed Output diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilderTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilderTest.kt index 425b3464..b6ecac48 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilderTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/tool/ToolViewBuilderTest.kt @@ -308,6 +308,35 @@ class ToolViewBuilderTest { assertEquals("poll: proc-1", view.subtitle) assertTrue(view.detail.contains("Status: running")) assertTrue(view.detail.contains("listening on port 8080")) + assertEquals(null, view.outputCut) + } + + @Test + fun `process output reports backend omitted character count for each action`() { + listOf("poll", "wait", "log", "kill").forEach { action -> + val outputKey = if (action == "poll") "output_preview" else "output" + val view = + build( + "process_manage", + """{"action":"$action","session_id":"proc-1"}""", + """{"status":"exited","$outputKey":"partial output","output_cut":42}""", + ) + + assertTrue(view.detail.contains("partial output")) + assertEquals(42L, view.outputCut) + } + } + + @Test + fun `process output without positive numeric omission has no notice`() { + listOf("{}", """{"output_cut":0}""", """{"output_cut":"unknown"}""").forEach { metadata -> + val fields = metadata.removePrefix("{").removeSuffix("}") + val result = """{"output":"complete"${if (fields.isNotEmpty()) ",$fields" else ""}}""" + val view = build("process", """{"action":"wait"}""", result) + + assertEquals("complete", view.detail) + assertEquals(null, view.outputCut) + } } @Test