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