feat: surface codex tool activity progress
This commit is contained in:
@@ -1598,6 +1598,47 @@ public final class BossUi {
|
||||
}
|
||||
}
|
||||
|
||||
JSONArray toolActivities = progress == null ? null : progress.optJSONArray("toolActivities");
|
||||
if (toolActivities != null && toolActivities.length() > 0) {
|
||||
card.addView(divider(context));
|
||||
card.addView(sectionTitle(context, "工具活动"));
|
||||
int shown = 0;
|
||||
for (int i = 0; i < toolActivities.length(); i += 1) {
|
||||
JSONObject activity = toolActivities.optJSONObject(i);
|
||||
String kind = activity == null ? "" : activity.optString("kind", "").trim();
|
||||
String name = activity == null ? "" : activity.optString("name", "").trim();
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
continue;
|
||||
}
|
||||
String status = activity.optString("status", "").trim();
|
||||
String detail = activity.optString("detail", "").trim();
|
||||
String label;
|
||||
if ("mcp".equals(kind)) {
|
||||
label = "MCP " + name;
|
||||
} else if ("dynamic".equals(kind)) {
|
||||
label = "动态工具 " + name;
|
||||
} else if ("web_search".equals(kind)) {
|
||||
label = "搜索 " + name;
|
||||
} else if ("image_view".equals(kind)) {
|
||||
label = "图片 " + name;
|
||||
} else if ("review".equals(kind)) {
|
||||
label = "Review " + name;
|
||||
} else if ("command".equals(kind)) {
|
||||
label = "命令 " + name;
|
||||
} else {
|
||||
label = "工具 " + name;
|
||||
}
|
||||
if (!TextUtils.isEmpty(status)) {
|
||||
label += " · " + status;
|
||||
}
|
||||
card.addView(detailRow(context, "◇", label, detail, "failed".equals(status)));
|
||||
shown += 1;
|
||||
if (shown >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSONArray warnings = progress == null ? null : progress.optJSONArray("warnings");
|
||||
if (warnings != null && warnings.length() > 0) {
|
||||
card.addView(divider(context));
|
||||
|
||||
@@ -1226,6 +1226,65 @@ public class ProjectDetailActivityUiTest {
|
||||
assertFalse(viewTreeContainsText(messageView, "sk-secret-should-not-render"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executionProgressMessageRendersCodexToolActivitySection() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "tool-activity")
|
||||
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "Boss开发主线程");
|
||||
TestProjectDetailActivity activity = Robolectric
|
||||
.buildActivity(TestProjectDetailActivity.class, intent)
|
||||
.setup()
|
||||
.get();
|
||||
|
||||
JSONObject message = new JSONObject()
|
||||
.put("id", "progress-tool-activity-1")
|
||||
.put("sender", "master")
|
||||
.put("senderLabel", "主 Agent")
|
||||
.put("body", "执行进度")
|
||||
.put("kind", "execution_progress")
|
||||
.put("sentAt", "2026-06-01T11:40:00+08:00")
|
||||
.put("executionProgress", new JSONObject()
|
||||
.put("status", "running")
|
||||
.put("steps", new JSONArray()
|
||||
.put(new JSONObject().put("text", "同步 Codex 工具活动").put("status", "running")))
|
||||
.put("toolActivities", new JSONArray()
|
||||
.put(new JSONObject()
|
||||
.put("kind", "mcp")
|
||||
.put("name", "github/pull_request/list")
|
||||
.put("status", "failed")
|
||||
.put("detail", "token=[redacted] failed")
|
||||
.put("arguments", new JSONObject().put("token", "sk-secret-should-not-render")))
|
||||
.put(new JSONObject()
|
||||
.put("kind", "web_search")
|
||||
.put("name", "openPage")
|
||||
.put("status", "running")
|
||||
.put("detail", "Codex App Server ThreadItem")
|
||||
.put("url", "https://example.com/private?token=sk-secret-should-not-render"))
|
||||
.put(new JSONObject()
|
||||
.put("kind", "command")
|
||||
.put("name", "commandExecution")
|
||||
.put("status", "completed")
|
||||
.put("detail", "exit 0 · 2345ms")
|
||||
.put("command", "cat /Users/kris/.ssh/id_rsa"))));
|
||||
|
||||
View messageView = ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"buildMessageView",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
|
||||
);
|
||||
|
||||
assertTrue(viewTreeContainsText(messageView, "工具活动"));
|
||||
assertTrue(viewTreeContainsText(messageView, "MCP github/pull_request/list · failed"));
|
||||
assertTrue(viewTreeContainsText(messageView, "搜索 openPage · running"));
|
||||
assertTrue(viewTreeContainsText(messageView, "Codex App Server ThreadItem"));
|
||||
assertTrue(viewTreeContainsText(messageView, "命令 commandExecution · completed"));
|
||||
assertTrue(viewTreeContainsText(messageView, "exit 0 · 2345ms"));
|
||||
assertFalse(viewTreeContainsText(messageView, "sk-secret-should-not-render"));
|
||||
assertFalse(viewTreeContainsText(messageView, "https://example.com/private"));
|
||||
assertFalse(viewTreeContainsText(messageView, "/Users/kris"));
|
||||
assertFalse(viewTreeContainsText(messageView, "id_rsa"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeRemoteExecutionProgressDoesNotRenderCodexSections() throws Exception {
|
||||
Intent intent = new Intent()
|
||||
|
||||
Reference in New Issue
Block a user