diff --git a/android/app/src/main/java/com/hyzq/boss/BossUi.java b/android/app/src/main/java/com/hyzq/boss/BossUi.java index 27f5b93..51a2192 100644 --- a/android/app/src/main/java/com/hyzq/boss/BossUi.java +++ b/android/app/src/main/java/com/hyzq/boss/BossUi.java @@ -1339,6 +1339,19 @@ public final class BossUi { titleRow.addView(pin); card.addView(titleRow); + String phase = progress == null ? "" : progress.optString("phase", "").trim(); + if (!TextUtils.isEmpty(phase)) { + TextView phaseView = secondaryText(context, "当前状态:" + executionPhaseLabel(phase)); + phaseView.setPadding(0, dp(context, 8), 0, 0); + card.addView(phaseView); + } + String lastProgressAt = progress == null ? "" : progress.optString("lastProgressAt", "").trim(); + if (!TextUtils.isEmpty(lastProgressAt)) { + TextView lastProgressView = secondaryText(context, "最后更新:" + lastProgressAt); + lastProgressView.setPadding(0, dp(context, 4), 0, 0); + card.addView(lastProgressView); + } + JSONArray steps = progress == null ? null : progress.optJSONArray("steps"); if (steps == null || steps.length() == 0) { card.addView(progressLine(context, "等待执行进度回写", "running")); @@ -1931,6 +1944,37 @@ public final class BossUi { return card; } + private static String executionPhaseLabel(String phase) { + switch (phase) { + case "queued": + return "等待设备接收"; + case "claimed": + return "设备已接收"; + case "executor_starting": + return "正在启动执行器"; + case "turn_started": + return "已写入目标线程"; + case "awaiting_reply": + return "等待线程回复"; + case "completing": + return "正在回写结果"; + case "completed": + return "已完成"; + case "recoverable_failed": + return "可恢复失败"; + case "terminal_failed": + return "执行失败"; + case "timed_out": + return "执行超时"; + case "canceled": + return "已取消"; + case "needs_user_action": + return "需要用户处理"; + default: + return phase; + } + } + private static View progressLine(Context context, String text, String status) { LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); diff --git a/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java b/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java index 8d2be64..291997c 100644 --- a/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java +++ b/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java @@ -948,6 +948,42 @@ public class ProjectDetailActivityUiTest { assertTrue(viewTreeContainsText(messageView, "Mendel(explorer)")); } + @Test + public void executionProgressMessageRendersPhaseExplanation() throws Exception { + Intent intent = new Intent() + .putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "thread-phase") + .putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "Boss开发主线程"); + TestProjectDetailActivity activity = Robolectric + .buildActivity(TestProjectDetailActivity.class, intent) + .setup() + .get(); + + JSONObject message = new JSONObject() + .put("id", "progress-phase-1") + .put("sender", "master") + .put("senderLabel", "主 Agent") + .put("body", "执行进度") + .put("kind", "execution_progress") + .put("sentAt", "2026-06-06T10:16:00+08:00") + .put("executionProgress", new JSONObject() + .put("status", "running") + .put("phase", "awaiting_reply") + .put("lastProgressAt", "2026-06-06T10:15:30+08:00") + .put("steps", new JSONArray() + .put(new JSONObject() + .put("text", "等待目标线程回复") + .put("status", "running")))); + + View messageView = ReflectionHelpers.callInstanceMethod( + activity, + "buildMessageView", + ReflectionHelpers.ClassParameter.from(JSONObject.class, message) + ); + + assertTrue(viewTreeContainsText(messageView, "当前状态:等待线程回复")); + assertTrue(viewTreeContainsText(messageView, "最后更新:2026-06-06T10:15:30+08:00")); + } + @Test public void executionProgressMessageRendersCodexApprovalAndFileChangeSections() throws Exception { Intent intent = new Intent()