feat: show codex app server summaries on android

This commit is contained in:
AI Bot
2026-06-04 15:08:27 +08:00
parent 3080f57dbc
commit a5d44b0cac
6 changed files with 212 additions and 9 deletions

View File

@@ -187,6 +187,64 @@ public class DeviceDetailActivity extends BossScreenActivity {
null,
null
));
if (WechatSurfaceMapper.hasCodexAppServerMetadata(device)) {
appendContent(BossUi.buildWechatMenuRow(
this,
"模型",
WechatSurfaceMapper.deviceCodexModelSummary(device),
null,
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"扩展",
WechatSurfaceMapper.deviceCodexExtensionSummary(device),
null,
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"治理",
WechatSurfaceMapper.deviceCodexGovernanceSummary(device),
null,
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"账号",
WechatSurfaceMapper.deviceCodexAccountSummary(device),
null,
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"线程",
WechatSurfaceMapper.deviceCodexThreadSummary(device),
null,
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"轮次",
WechatSurfaceMapper.deviceCodexTurnSummary(device),
null,
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"线程操作",
WechatSurfaceMapper.deviceCodexThreadActionSummary(device),
null,
null,
null
));
}
appendContent(BossUi.buildWechatMenuRow(
this,
"线程协作",

View File

@@ -252,6 +252,10 @@ public final class WechatSurfaceMapper {
return resolveDeviceCapability(device, "codexAppServer") != null;
}
public static boolean hasCodexAppServerMetadata(JSONObject device) {
return resolveCodexAppServerMetadata(device) != null;
}
public static String deviceCodexAppServerStatusLabel(JSONObject device) {
JSONObject capability = resolveDeviceCapability(device, "codexAppServer");
boolean connected = capability != null && capability.optBoolean("connected", false);
@@ -270,6 +274,64 @@ public final class WechatSurfaceMapper {
return "最近上报 " + lastSeenAt;
}
public static String deviceCodexModelSummary(JSONObject device) {
JSONObject metadata = resolveCodexAppServerMetadata(device);
int modelCount = metadataArrayLength(metadata, "models");
if (modelCount <= 0) {
return "未发现";
}
return modelCount + ""
+ " · 默认 " + metadataText(metadata, "defaultModelId")
+ " · 快速 " + metadataText(metadata, "fastModelId")
+ " · 深度 " + metadataText(metadata, "deepModelId");
}
public static String deviceCodexExtensionSummary(JSONObject device) {
JSONObject metadata = resolveCodexAppServerMetadata(device);
return "Skill " + metadataArrayLength(metadata, "skills") + ""
+ " · Plugin " + metadataArrayLength(metadata, "plugins") + ""
+ " · App " + metadataArrayLength(metadata, "apps") + "";
}
public static String deviceCodexGovernanceSummary(JSONObject device) {
JSONObject metadata = resolveCodexAppServerMetadata(device);
return "实验特性 " + metadataArrayLength(metadata, "experimentalFeatures") + ""
+ " · 协作模式 " + metadataArrayLength(metadata, "collaborationModes") + ""
+ " · MCP " + metadataArrayLength(metadata, "mcpServers") + ""
+ " · 权限 " + metadataArrayLength(metadata, "permissionProfiles") + "";
}
public static String deviceCodexAccountSummary(JSONObject device) {
JSONObject metadata = resolveCodexAppServerMetadata(device);
JSONObject accountSummary = metadata == null ? null : metadata.optJSONObject("accountSummary");
JSONObject rateLimitSummary = metadata == null ? null : metadata.optJSONObject("rateLimitSummary");
return objectText(accountSummary, "authMode")
+ " · 套餐 " + objectText(accountSummary, "planType")
+ " · 额度 " + objectInt(rateLimitSummary, "maxUsedPercent") + "%";
}
public static String deviceCodexThreadSummary(JSONObject device) {
JSONObject summary = resolveCodexAppServerMetadataObject(device, "threadSummary");
return objectInt(summary, "threadCount") + ""
+ " · 已加载 " + objectInt(summary, "loadedThreadCount") + ""
+ " · 活跃 " + objectInt(summary, "activeThreadCount") + "";
}
public static String deviceCodexTurnSummary(JSONObject device) {
JSONObject summary = resolveCodexAppServerMetadataObject(device, "threadTurnSummary");
return objectInt(summary, "totalTurnCount") + ""
+ " · 运行中 " + objectInt(summary, "runningTurnCount") + ""
+ " · 完成 " + objectInt(summary, "completedTurnCount") + "";
}
public static String deviceCodexThreadActionSummary(JSONObject device) {
JSONObject summary = resolveCodexAppServerMetadataObject(device, "threadActionSummary");
return objectInt(summary, "actionCount") + ""
+ " · 生命周期 " + objectInt(summary, "lifecycleActionCount") + ""
+ " · 活跃干预 " + objectInt(summary, "liveTurnActionCount") + ""
+ " · " + (summary != null && summary.optBoolean("shellActionAvailable", false) ? "Shell 可用" : "Shell 不可用");
}
public static String deviceCodexThreadCollaborationSummary(JSONObject device) {
JSONObject summary = resolveCodexAppServerMetadataObject(device, "threadCollaborationSummary");
if (summary == null) {
@@ -528,11 +590,43 @@ public final class WechatSurfaceMapper {
}
private static JSONObject resolveCodexAppServerMetadataObject(JSONObject device, String key) {
JSONObject capability = resolveDeviceCapability(device, "codexAppServer");
JSONObject metadata = capability == null ? null : capability.optJSONObject("metadata");
JSONObject metadata = resolveCodexAppServerMetadata(device);
return metadata == null ? null : metadata.optJSONObject(key);
}
private static JSONObject resolveCodexAppServerMetadata(JSONObject device) {
JSONObject capability = resolveDeviceCapability(device, "codexAppServer");
return capability == null ? null : capability.optJSONObject("metadata");
}
private static int metadataArrayLength(JSONObject metadata, String key) {
if (metadata == null) {
return 0;
}
JSONArray array = metadata.optJSONArray(key);
return array == null ? 0 : array.length();
}
private static String metadataText(JSONObject metadata, String key) {
if (metadata == null) {
return "未知";
}
String value = metadata.optString(key, "").trim();
return value.isEmpty() ? "未知" : value;
}
private static String objectText(JSONObject object, String key) {
if (object == null) {
return "未知";
}
String value = object.optString(key, "").trim();
return value.isEmpty() ? "未知" : value;
}
private static int objectInt(JSONObject object, String key) {
return object == null ? 0 : object.optInt(key, 0);
}
public static RootTopAction rootTopAction(String activeTab, boolean refreshing) {
return rootTopAction(activeTab, refreshing, false);
}

View File

@@ -97,6 +97,20 @@ public class DeviceDetailActivityTest {
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "Codex App Server"));
assertTrue(viewTreeContainsText(content, "已连接"));
assertTrue(viewTreeContainsText(content, "模型"));
assertTrue(viewTreeContainsText(content, "2 个 · 默认 gpt-5.4 · 快速 gpt-5.4-mini · 深度 gpt-5.4"));
assertTrue(viewTreeContainsText(content, "扩展"));
assertTrue(viewTreeContainsText(content, "Skill 1 个 · Plugin 1 个 · App 1 个"));
assertTrue(viewTreeContainsText(content, "治理"));
assertTrue(viewTreeContainsText(content, "实验特性 2 个 · 协作模式 2 个 · MCP 2 个 · 权限 1 个"));
assertTrue(viewTreeContainsText(content, "账号"));
assertTrue(viewTreeContainsText(content, "chatgpt · 套餐 pro · 额度 42%"));
assertTrue(viewTreeContainsText(content, "线程"));
assertTrue(viewTreeContainsText(content, "3 个 · 已加载 2 个 · 活跃 1 个"));
assertTrue(viewTreeContainsText(content, "轮次"));
assertTrue(viewTreeContainsText(content, "3 个 · 运行中 1 个 · 完成 2 个"));
assertTrue(viewTreeContainsText(content, "线程操作"));
assertTrue(viewTreeContainsText(content, "11 项 · 生命周期 5 项 · 活跃干预 2 项 · Shell 可用"));
assertTrue(viewTreeContainsText(content, "线程协作"));
assertTrue(viewTreeContainsText(content, "Boss Broker 可用 · 协作事件可处理 · 2 种模式 · 非原生私聊"));
assertTrue(viewTreeContainsText(content, "协议漂移"));
@@ -372,6 +386,42 @@ public class DeviceDetailActivityTest {
.put("connected", true)
.put("lastSeenAt", "2026-06-04T10:00:00+08:00")
.put("metadata", new JSONObject()
.put("models", new JSONArray()
.put(new JSONObject().put("id", "gpt-5.4"))
.put(new JSONObject().put("id", "gpt-5.4-mini")))
.put("defaultModelId", "gpt-5.4")
.put("fastModelId", "gpt-5.4-mini")
.put("deepModelId", "gpt-5.4")
.put("skills", new JSONArray().put(new JSONObject().put("name", "image2-ui-prototype")))
.put("plugins", new JSONArray().put(new JSONObject().put("id", "github")))
.put("apps", new JSONArray().put(new JSONObject().put("id", "canva")))
.put("experimentalFeatures", new JSONArray()
.put(new JSONObject().put("name", "multi_agent"))
.put(new JSONObject().put("name", "apps")))
.put("collaborationModes", new JSONArray()
.put(new JSONObject().put("id", "solo"))
.put(new JSONObject().put("id", "plan")))
.put("mcpServers", new JSONArray()
.put(new JSONObject().put("name", "github"))
.put(new JSONObject().put("name", "figma")))
.put("permissionProfiles", new JSONArray().put(new JSONObject().put("id", ":workspace")))
.put("accountSummary", new JSONObject()
.put("authMode", "chatgpt")
.put("planType", "pro"))
.put("rateLimitSummary", new JSONObject().put("maxUsedPercent", 42))
.put("threadSummary", new JSONObject()
.put("threadCount", 3)
.put("loadedThreadCount", 2)
.put("activeThreadCount", 1))
.put("threadTurnSummary", new JSONObject()
.put("totalTurnCount", 3)
.put("runningTurnCount", 1)
.put("completedTurnCount", 2))
.put("threadActionSummary", new JSONObject()
.put("actionCount", 11)
.put("lifecycleActionCount", 5)
.put("liveTurnActionCount", 2)
.put("shellActionAvailable", true))
.put("threadCollaborationSummary", new JSONObject()
.put("bossBrokerAvailable", true)
.put("collabToolCallHandlerAvailable", true)