feat: map codex realtime thread status
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user