feat: surface codex app-server approval progress

This commit is contained in:
AI Bot
2026-05-31 03:36:07 +08:00
parent b9d3cca2e7
commit 4800352e22
12 changed files with 691 additions and 5 deletions

View File

@@ -1296,6 +1296,74 @@ public final class BossUi {
return card;
}
JSONArray warnings = progress == null ? null : progress.optJSONArray("warnings");
if (warnings != null && warnings.length() > 0) {
card.addView(divider(context));
card.addView(sectionTitle(context, "安全提醒"));
int shown = 0;
for (int i = 0; i < warnings.length(); i += 1) {
JSONObject warning = warnings.optJSONObject(i);
String message = warning == null ? "" : warning.optString("message", "").trim();
if (TextUtils.isEmpty(message)) {
continue;
}
card.addView(detailRow(context, "!", message, "", false));
shown += 1;
if (shown >= 3) {
break;
}
}
}
JSONArray approvals = progress == null ? null : progress.optJSONArray("approvals");
if (approvals != null && approvals.length() > 0) {
card.addView(divider(context));
card.addView(sectionTitle(context, "审批状态"));
int shown = 0;
for (int i = 0; i < approvals.length(); i += 1) {
JSONObject approval = approvals.optJSONObject(i);
String label = approval == null ? "" : approval.optString("label", "").trim();
if (TextUtils.isEmpty(label)) {
continue;
}
String status = approval.optString("status", "").trim();
String detail = approval.optString("detail", "").trim();
String riskLevel = approval.optString("riskLevel", "").trim();
String rowLabel = TextUtils.isEmpty(status) ? label : label + " · " + status;
card.addView(detailRow(context, "", rowLabel, riskLevel, false));
if (!TextUtils.isEmpty(detail)) {
card.addView(detailRow(context, "", detail, "", false, true));
}
shown += 1;
if (shown >= 4) {
break;
}
}
}
JSONArray fileChanges = progress == null ? null : progress.optJSONArray("fileChanges");
if (fileChanges != null && fileChanges.length() > 0) {
card.addView(divider(context));
card.addView(sectionTitle(context, "文件变更"));
int shown = 0;
for (int i = 0; i < fileChanges.length(); i += 1) {
JSONObject fileChange = fileChanges.optJSONObject(i);
String path = fileChange == null ? "" : fileChange.optString("path", "").trim();
if (TextUtils.isEmpty(path)) {
continue;
}
String kind = fileChange.optString("kind", "").trim();
card.addView(detailRow(context, "", path, kind, false));
shown += 1;
if (shown >= 6) {
break;
}
}
if (fileChanges.length() > shown) {
card.addView(detailRow(context, "", "再显示 " + (fileChanges.length() - shown) + "", "", false, true));
}
}
card.addView(divider(context));
card.addView(sectionTitle(context, "分支详情"));
JSONObject branch = progress == null ? null : progress.optJSONObject("branch");

View File

@@ -904,6 +904,60 @@ public class ProjectDetailActivityUiTest {
assertTrue(viewTreeContainsText(messageView, "Mendelexplorer"));
}
@Test
public void executionProgressMessageRendersCodexApprovalAndFileChangeSections() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "thread-approval")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "Boss开发主线程");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
JSONObject message = new JSONObject()
.put("id", "progress-approval-1")
.put("sender", "master")
.put("senderLabel", "主 Agent")
.put("body", "执行进度")
.put("kind", "execution_progress")
.put("sentAt", "2026-05-31T10:16:00+08:00")
.put("executionProgress", new JSONObject()
.put("status", "running")
.put("steps", new JSONArray()
.put(new JSONObject().put("text", "等待 Codex 审批事件").put("status", "running")))
.put("approvals", new JSONArray()
.put(new JSONObject()
.put("label", "命令执行审批")
.put("status", "resolved")
.put("detail", "需要确认命令执行")
.put("riskLevel", "medium")))
.put("warnings", new JSONArray()
.put(new JSONObject()
.put("message", "检测到需要用户确认的命令执行。")
.put("severity", "warning")))
.put("fileChanges", new JSONArray()
.put(new JSONObject()
.put("path", "src/app/page.tsx")
.put("kind", "update")
.put("status", "updated"))));
View messageView = ReflectionHelpers.callInstanceMethod(
activity,
"buildMessageView",
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
);
assertTrue(viewTreeContainsText(messageView, "安全提醒"));
assertTrue(viewTreeContainsText(messageView, "检测到需要用户确认的命令执行。"));
assertTrue(viewTreeContainsText(messageView, "审批状态"));
assertTrue(viewTreeContainsText(messageView, "命令执行审批 · resolved"));
assertTrue(viewTreeContainsText(messageView, "需要确认命令执行"));
assertTrue(viewTreeContainsText(messageView, "文件变更"));
assertTrue(viewTreeContainsText(messageView, "src/app/page.tsx"));
assertFalse(viewTreeContainsText(messageView, "sk-secret"));
assertFalse(viewTreeContainsText(messageView, "diff"));
}
@Test
public void nativeRemoteExecutionProgressDoesNotRenderCodexSections() throws Exception {
Intent intent = new Intent()