feat: surface codex account runtime notices

This commit is contained in:
AI Bot
2026-06-01 17:40:03 +08:00
parent 26b5e97614
commit defa3da185
11 changed files with 505 additions and 5 deletions

View File

@@ -1437,6 +1437,65 @@ public final class BossUi {
}
}
JSONObject accountStatus = progress == null ? null : progress.optJSONObject("accountStatus");
JSONObject modelVerification = progress == null ? null : progress.optJSONObject("modelVerification");
JSONArray verifications = modelVerification == null ? null : modelVerification.optJSONArray("verifications");
boolean hasAccountStatus = accountStatus != null ||
(verifications != null && verifications.length() > 0);
if (hasAccountStatus) {
card.addView(divider(context));
card.addView(sectionTitle(context, "账号状态"));
if (accountStatus != null) {
String authMode = accountStatus.optString("authMode", "").trim();
String planType = accountStatus.optString("planType", "").trim();
if (!TextUtils.isEmpty(authMode) || !TextUtils.isEmpty(planType)) {
String label = !TextUtils.isEmpty(authMode) && !TextUtils.isEmpty(planType)
? "认证 " + authMode + " · " + planType
: !TextUtils.isEmpty(authMode) ? "认证 " + authMode : "套餐 " + planType;
card.addView(detailRow(context, "", label, "", false));
}
String limitName = accountStatus.optString("limitName", "").trim();
int usedPercent = accountStatus.optInt("usedPercent", -1);
if (!TextUtils.isEmpty(limitName) || usedPercent >= 0) {
String label = "额度";
if (!TextUtils.isEmpty(limitName)) {
label += " " + limitName;
}
if (usedPercent >= 0) {
label += " · " + usedPercent + "%";
}
card.addView(detailRow(context, "", label, "", false));
}
int windowDurationMins = accountStatus.optInt("windowDurationMins", 0);
if (windowDurationMins > 0) {
card.addView(detailRow(context, "", "窗口 " + windowDurationMins + " 分钟", "", false, true));
}
String creditsBalance = accountStatus.optString("creditsBalance", "").trim();
if (!TextUtils.isEmpty(creditsBalance)) {
card.addView(detailRow(context, "", "余额 " + creditsBalance, "", false, true));
}
}
if (verifications != null && verifications.length() > 0) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < verifications.length(); i += 1) {
String verification = verifications.optString(i, "").trim();
if (TextUtils.isEmpty(verification)) {
continue;
}
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(verification);
if (builder.length() > 120) {
break;
}
}
if (builder.length() > 0) {
card.addView(detailRow(context, "", "模型校验 " + builder, "", false));
}
}
}
JSONObject modelRoute = progress == null ? null : progress.optJSONObject("modelRoute");
JSONObject tokenUsage = progress == null ? null : progress.optJSONObject("tokenUsage");
JSONArray mcpServers = progress == null ? null : progress.optJSONArray("mcpServers");

View File

@@ -1069,6 +1069,58 @@ public class ProjectDetailActivityUiTest {
assertFalse(viewTreeContainsText(messageView, "sk-secret"));
}
@Test
public void executionProgressMessageRendersCodexAccountStatusAndVerificationSections() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "thread-account")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "Boss开发主线程");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
JSONObject message = new JSONObject()
.put("id", "progress-account-1")
.put("sender", "master")
.put("senderLabel", "主 Agent")
.put("body", "执行进度")
.put("kind", "execution_progress")
.put("sentAt", "2026-06-01T10:28:00+08:00")
.put("executionProgress", new JSONObject()
.put("status", "running")
.put("steps", new JSONArray()
.put(new JSONObject().put("text", "同步 Codex 账号运行态").put("status", "running")))
.put("accountStatus", new JSONObject()
.put("authMode", "chatgpt")
.put("planType", "team")
.put("limitName", "Codex")
.put("usedPercent", 88)
.put("windowDurationMins", 180)
.put("resetsAt", 1770003600)
.put("creditsBalance", "120.5")
.put("hasCredits", true)
.put("unlimitedCredits", false)
.put("accessToken", "sk-secret-should-not-render"))
.put("modelVerification", new JSONObject()
.put("verifications", new JSONArray().put("trustedAccessForCyber"))
.put("turnId", "turn-secret-should-not-render")));
View messageView = ReflectionHelpers.callInstanceMethod(
activity,
"buildMessageView",
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
);
assertTrue(viewTreeContainsText(messageView, "账号状态"));
assertTrue(viewTreeContainsText(messageView, "认证 chatgpt · team"));
assertTrue(viewTreeContainsText(messageView, "额度 Codex · 88%"));
assertTrue(viewTreeContainsText(messageView, "窗口 180 分钟"));
assertTrue(viewTreeContainsText(messageView, "余额 120.5"));
assertTrue(viewTreeContainsText(messageView, "模型校验 trustedAccessForCyber"));
assertFalse(viewTreeContainsText(messageView, "sk-secret"));
assertFalse(viewTreeContainsText(messageView, "turn-secret"));
}
@Test
public void executionProgressMessageRendersCodexThreadGoalSettingsAndCompactionSections() throws Exception {
Intent intent = new Intent()