feat: show codex app server drift on android
This commit is contained in:
@@ -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,
|
||||
"默认执行模式",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user