feat: map codex realtime thread status

This commit is contained in:
AI Bot
2026-05-31 03:54:43 +08:00
parent f333676c36
commit cee1e7938e
12 changed files with 505 additions and 7 deletions

View File

@@ -1296,6 +1296,63 @@ public final class BossUi {
return card;
}
JSONObject threadStatus = progress == null ? null : progress.optJSONObject("threadStatus");
if (threadStatus != null) {
String type = threadStatus.optString("type", "").trim();
boolean waitingOnApproval = threadStatus.optBoolean("waitingOnApproval", false);
boolean waitingOnUserInput = threadStatus.optBoolean("waitingOnUserInput", false);
if (!TextUtils.isEmpty(type) || waitingOnApproval || waitingOnUserInput) {
card.addView(divider(context));
card.addView(sectionTitle(context, "线程状态"));
String label = "active".equals(type) ? "活跃" :
"idle".equals(type) ? "空闲" :
"systemError".equals(type) ? "系统异常" :
"notLoaded".equals(type) ? "未加载" : type;
card.addView(detailRow(context, "", TextUtils.isEmpty(label) ? "状态待同步" : label, "", false));
if (waitingOnApproval) {
card.addView(detailRow(context, "", "等待审批", "", false, true));
}
if (waitingOnUserInput) {
card.addView(detailRow(context, "", "等待用户输入", "", false, true));
}
}
}
JSONObject realtime = progress == null ? null : progress.optJSONObject("realtime");
if (realtime != null) {
String status = realtime.optString("status", "").trim();
if (!TextUtils.isEmpty(status)) {
card.addView(divider(context));
card.addView(sectionTitle(context, "实时状态"));
String closeReason = realtime.optString("closeReason", "").trim();
String lastError = realtime.optString("lastError", "").trim();
String statusLabel = "started".equals(status) ? "已启动" :
"streaming".equals(status) ? "同步中" :
"closed".equals(status) ? "已关闭" :
"error".equals(status) ? "异常" : status;
String trailing = !TextUtils.isEmpty(lastError) ? lastError : closeReason;
card.addView(detailRow(
context,
"",
TextUtils.isEmpty(trailing) ? statusLabel : statusLabel + " · " + trailing,
"",
"error".equals(status)
));
String transcript = realtime.optString("transcriptPreview", "").trim();
if (!TextUtils.isEmpty(transcript)) {
card.addView(detailRow(context, "", transcript, "", false, true));
}
int audioChunkCount = realtime.optInt("audioChunkCount", 0);
int itemCount = realtime.optInt("itemCount", 0);
if (audioChunkCount > 0) {
card.addView(detailRow(context, "", "音频片段 " + audioChunkCount, "", false, true));
}
if (itemCount > 0) {
card.addView(detailRow(context, "", "实时事件 " + itemCount, "", false, true));
}
}
}
JSONArray warnings = progress == null ? null : progress.optJSONArray("warnings");
if (warnings != null && warnings.length() > 0) {
card.addView(divider(context));

View File

@@ -958,6 +958,62 @@ public class ProjectDetailActivityUiTest {
assertFalse(viewTreeContainsText(messageView, "diff"));
}
@Test
public void executionProgressMessageRendersCodexThreadStatusAndRealtimeSections() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "thread-realtime")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "Boss开发主线程");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
JSONObject message = new JSONObject()
.put("id", "progress-realtime-1")
.put("sender", "master")
.put("senderLabel", "主 Agent")
.put("body", "执行进度")
.put("kind", "execution_progress")
.put("sentAt", "2026-05-31T10:20:00+08:00")
.put("executionProgress", new JSONObject()
.put("status", "running")
.put("steps", new JSONArray()
.put(new JSONObject().put("text", "监听 Codex realtime 事件").put("status", "running")))
.put("threadStatus", new JSONObject()
.put("type", "active")
.put("activeFlags", new JSONArray()
.put("waitingOnApproval")
.put("waitingOnUserInput"))
.put("waitingOnApproval", true)
.put("waitingOnUserInput", true))
.put("realtime", new JSONObject()
.put("status", "closed")
.put("sessionId", "rt-session-1")
.put("version", "v2")
.put("transcriptRole", "assistant")
.put("transcriptPreview", "正在分析 Codex App Server 实时事件。")
.put("audioChunkCount", 1)
.put("itemCount", 1)
.put("closeReason", "completed")));
View messageView = ReflectionHelpers.callInstanceMethod(
activity,
"buildMessageView",
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
);
assertTrue(viewTreeContainsText(messageView, "线程状态"));
assertTrue(viewTreeContainsText(messageView, "活跃"));
assertTrue(viewTreeContainsText(messageView, "等待审批"));
assertTrue(viewTreeContainsText(messageView, "等待用户输入"));
assertTrue(viewTreeContainsText(messageView, "实时状态"));
assertTrue(viewTreeContainsText(messageView, "已关闭 · completed"));
assertTrue(viewTreeContainsText(messageView, "正在分析 Codex App Server 实时事件。"));
assertTrue(viewTreeContainsText(messageView, "音频片段 1"));
assertFalse(viewTreeContainsText(messageView, "audio-secret-payload"));
assertFalse(viewTreeContainsText(messageView, "v=0"));
}
@Test
public void nativeRemoteExecutionProgressDoesNotRenderCodexSections() throws Exception {
Intent intent = new Intent()