feat: show codex app server drift on android

This commit is contained in:
AI Bot
2026-06-04 14:56:44 +08:00
parent 0eaf78c3c2
commit 3080f57dbc
6 changed files with 138 additions and 4 deletions

View File

@@ -178,6 +178,32 @@ public class DeviceDetailActivity extends BossScreenActivity {
null,
null
));
if (WechatSurfaceMapper.hasCodexAppServerCapability(device)) {
appendContent(BossUi.buildWechatMenuRow(
this,
"Codex App Server",
WechatSurfaceMapper.deviceCodexAppServerStatusLabel(device),
WechatSurfaceMapper.deviceCodexAppServerDetailLabel(device),
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"线程协作",
WechatSurfaceMapper.deviceCodexThreadCollaborationSummary(device),
null,
null,
null
));
appendContent(BossUi.buildWechatMenuRow(
this,
"协议漂移",
WechatSurfaceMapper.deviceCodexProtocolDriftSummary(device),
null,
null,
null
));
}
appendContent(BossUi.buildWechatMenuRow(
this,
"默认执行模式",

View File

@@ -248,6 +248,54 @@ public final class WechatSurfaceMapper {
return builder.toString();
}
public static boolean hasCodexAppServerCapability(JSONObject device) {
return resolveDeviceCapability(device, "codexAppServer") != null;
}
public static String deviceCodexAppServerStatusLabel(JSONObject device) {
JSONObject capability = resolveDeviceCapability(device, "codexAppServer");
boolean connected = capability != null && capability.optBoolean("connected", false);
return connected ? "已连接" : "未连接";
}
public static String deviceCodexAppServerDetailLabel(JSONObject device) {
JSONObject capability = resolveDeviceCapability(device, "codexAppServer");
if (capability == null) {
return "等待 boss-agent 上报 App Server 能力";
}
String lastSeenAt = capability.optString("lastSeenAt", "").trim();
if (lastSeenAt.isEmpty()) {
return "线程、模型、Skill 与协议探测";
}
return "最近上报 " + lastSeenAt;
}
public static String deviceCodexThreadCollaborationSummary(JSONObject device) {
JSONObject summary = resolveCodexAppServerMetadataObject(device, "threadCollaborationSummary");
if (summary == null) {
return "等待线程协作探测";
}
String broker = summary.optBoolean("bossBrokerAvailable", false) ? "Boss Broker 可用" : "Boss Broker 不可用";
String handler = summary.optBoolean("collabToolCallHandlerAvailable", false) ? "协作事件可处理" : "协作事件不可处理";
String modes = summary.optInt("collaborationModeCount", 0) + " 种模式";
String direct = summary.optBoolean("directThreadChatSupported", false) ? "原生私聊" : "非原生私聊";
return broker + " · " + handler + " · " + modes + " · " + direct;
}
public static String deviceCodexProtocolDriftSummary(JSONObject device) {
JSONObject summary = resolveCodexAppServerMetadataObject(device, "protocolDriftSummary");
if (summary == null) {
return "等待协议探测";
}
String drift = "compatible".equals(summary.optString("driftLevel", "")) ? "兼容" : "告警";
String failedProbeCount = "失败探针 " + summary.optInt("failedProbeCount", 0) + "";
String docFollowupCount = "文档跟进 " + summary.optInt("docFollowupCount", 0) + "";
String fallbackStrategy = summary.optString("fallbackStrategy", "").contains("Boss Broker")
? "Boss Broker 兜底"
: "App Server 兜底";
return drift + " · " + failedProbeCount + " · " + docFollowupCount + " · " + fallbackStrategy;
}
public static String devicePreferredExecutionModeLabel(JSONObject device) {
return "gui".equals(device == null ? "" : device.optString("preferredExecutionMode", "")) ? "GUI" : "CLI";
}
@@ -479,6 +527,12 @@ public final class WechatSurfaceMapper {
return capabilities.optJSONObject(capabilityKey);
}
private static JSONObject resolveCodexAppServerMetadataObject(JSONObject device, String key) {
JSONObject capability = resolveDeviceCapability(device, "codexAppServer");
JSONObject metadata = capability == null ? null : capability.optJSONObject("metadata");
return metadata == null ? null : metadata.optJSONObject(key);
}
public static RootTopAction rootTopAction(String activeTab, boolean refreshing) {
return rootTopAction(activeTab, refreshing, false);
}

View File

@@ -76,6 +76,33 @@ public class DeviceDetailActivityTest {
assertTrue(viewTreeContainsText(content, "未连接"));
}
@Test
public void renderDeviceShowsCodexAppServerProtocolAndCollaborationSummary() throws Exception {
TestDeviceDetailActivity activity = Robolectric
.buildActivity(
TestDeviceDetailActivity.class,
new Intent()
.putExtra(DeviceDetailActivity.EXTRA_DEVICE_ID, "device-1")
.putExtra(DeviceDetailActivity.EXTRA_DEVICE_NAME, "Mac Studio")
)
.setup()
.get();
ReflectionHelpers.callInstanceMethod(
activity,
"renderDevice",
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildCodexAppServerPayload())
);
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "Codex App Server"));
assertTrue(viewTreeContainsText(content, "已连接"));
assertTrue(viewTreeContainsText(content, "线程协作"));
assertTrue(viewTreeContainsText(content, "Boss Broker 可用 · 协作事件可处理 · 2 种模式 · 非原生私聊"));
assertTrue(viewTreeContainsText(content, "协议漂移"));
assertTrue(viewTreeContainsText(content, "兼容 · 失败探针 0 个 · 文档跟进 3 项 · Boss Broker 兜底"));
}
@Test
public void renderDeviceShowsProjectScopedConflictCardAndActions() throws Exception {
TestDeviceDetailActivity activity = Robolectric
@@ -333,6 +360,31 @@ public class DeviceDetailActivityTest {
return payload;
}
private static JSONObject buildCodexAppServerPayload() throws Exception {
JSONObject payload = buildDevicePayload();
JSONObject capabilities = payload
.getJSONObject("workspace")
.getJSONObject("selectedDevice")
.getJSONObject("capabilities");
capabilities.put(
"codexAppServer",
new JSONObject()
.put("connected", true)
.put("lastSeenAt", "2026-06-04T10:00:00+08:00")
.put("metadata", new JSONObject()
.put("threadCollaborationSummary", new JSONObject()
.put("bossBrokerAvailable", true)
.put("collabToolCallHandlerAvailable", true)
.put("directThreadChatSupported", false)
.put("collaborationModeCount", 2))
.put("protocolDriftSummary", new JSONObject()
.put("driftLevel", "compatible")
.put("failedProbeCount", 0)
.put("docFollowupCount", 3)
.put("fallbackStrategy", "Boss Broker + App Server 注入/执行"))));
return payload;
}
public static class TestDeviceDetailActivity extends DeviceDetailActivity {
boolean reloadEnabled = true;
int reloadCount;