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,
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( appendContent(BossUi.buildWechatMenuRow(
this, this,
"默认执行模式", "默认执行模式",

View File

@@ -248,6 +248,54 @@ public final class WechatSurfaceMapper {
return builder.toString(); 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) { public static String devicePreferredExecutionModeLabel(JSONObject device) {
return "gui".equals(device == null ? "" : device.optString("preferredExecutionMode", "")) ? "GUI" : "CLI"; return "gui".equals(device == null ? "" : device.optString("preferredExecutionMode", "")) ? "GUI" : "CLI";
} }
@@ -479,6 +527,12 @@ public final class WechatSurfaceMapper {
return capabilities.optJSONObject(capabilityKey); 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) { public static RootTopAction rootTopAction(String activeTab, boolean refreshing) {
return rootTopAction(activeTab, refreshing, false); return rootTopAction(activeTab, refreshing, false);
} }

View File

@@ -76,6 +76,33 @@ public class DeviceDetailActivityTest {
assertTrue(viewTreeContainsText(content, "未连接")); 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 @Test
public void renderDeviceShowsProjectScopedConflictCardAndActions() throws Exception { public void renderDeviceShowsProjectScopedConflictCardAndActions() throws Exception {
TestDeviceDetailActivity activity = Robolectric TestDeviceDetailActivity activity = Robolectric
@@ -333,6 +360,31 @@ public class DeviceDetailActivityTest {
return payload; 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 { public static class TestDeviceDetailActivity extends DeviceDetailActivity {
boolean reloadEnabled = true; boolean reloadEnabled = true;
int reloadCount; int reloadCount;

View File

@@ -157,8 +157,8 @@
- App Server runner 已把 plan、diff、item、approval、warning、file change、thread status、realtime、model route、token usage、MCP、remote control、thread goal、settings、compaction、account、model verification、collab、tool activity、reasoning summary、image generation、hook、Windows sandbox 和 stream delta 归一到 Boss `execution_progress` 卡片;字段白名单只保留安全摘要,不保存 SDP、音频原始数据、raw item、remote installationId、cwd、turnId、配置文件路径、collab 源/目标线程 ID、receiverThreadIds、collab prompt、agentsStates 私有消息、共享 Skill 根绝对路径、hook key/command/sourcePath/statusMessage/hash/error message、tool arguments/result/contentItems、web URL token、命令正文/输出、raw reasoning content、reasoning item id、imageGeneration revisedPrompt/result、Windows sandbox sourcePath/samplePaths、本地绝对路径或未清洗密钥。 - App Server runner 已把 plan、diff、item、approval、warning、file change、thread status、realtime、model route、token usage、MCP、remote control、thread goal、settings、compaction、account、model verification、collab、tool activity、reasoning summary、image generation、hook、Windows sandbox 和 stream delta 归一到 Boss `execution_progress` 卡片;字段白名单只保留安全摘要,不保存 SDP、音频原始数据、raw item、remote installationId、cwd、turnId、配置文件路径、collab 源/目标线程 ID、receiverThreadIds、collab prompt、agentsStates 私有消息、共享 Skill 根绝对路径、hook key/command/sourcePath/statusMessage/hash/error message、tool arguments/result/contentItems、web URL token、命令正文/输出、raw reasoning content、reasoning item id、imageGeneration revisedPrompt/result、Windows sandbox sourcePath/samplePaths、本地绝对路径或未清洗密钥。
- Heartbeat discovery 已能缓存 `model/list / skills/list / skills/extraRoots/set / hooks/list / plugin/list / app/list / modelProvider/capabilities/read / experimentalFeature/list / collaborationMode/list / permissionProfile/list / mcpServerStatus/list / account/read / account/rateLimits/read / config/read / configRequirements/read / externalAgentConfig/detect / thread/list / thread/loaded/list / thread/turns/list` 的能力摘要。`thread/turns/list` 固定使用 `itemsView=summary`,只额外提取最终 `agentMessage` 安全摘要,并合并进 `projectCandidates.recentAssistantMessages` 让 Codex Desktop 自己产生的新回复反向同步到 Boss APP不保存用户正文、reasoning 原文、命令输出、原始 items、内部 prompt 或系统提示词。同批已补 `turn/steer` 活跃 turn 干预和 `POST /api/v1/projects/[projectId]/thread-collaboration` 服务端线程协作排队入口。 - Heartbeat discovery 已能缓存 `model/list / skills/list / skills/extraRoots/set / hooks/list / plugin/list / app/list / modelProvider/capabilities/read / experimentalFeature/list / collaborationMode/list / permissionProfile/list / mcpServerStatus/list / account/read / account/rateLimits/read / config/read / configRequirements/read / externalAgentConfig/detect / thread/list / thread/loaded/list / thread/turns/list` 的能力摘要。`thread/turns/list` 固定使用 `itemsView=summary`,只额外提取最终 `agentMessage` 安全摘要,并合并进 `projectCandidates.recentAssistantMessages` 让 Codex Desktop 自己产生的新回复反向同步到 Boss APP不保存用户正文、reasoning 原文、命令输出、原始 items、内部 prompt 或系统提示词。同批已补 `turn/steer` 活跃 turn 干预和 `POST /api/v1/projects/[projectId]/thread-collaboration` 服务端线程协作排队入口。
- 第十九批另补 `threadActionSummary` 线程操作能力摘要:设备详情页会显示 archive / unarchive / fork / compact / rollback / rename / metadata / steer / interrupt / shell / unsubscribe 等能力分组;该字段只读,不在 heartbeat 中调用任何会改变线程状态的 App Server API。 - 第十九批另补 `threadActionSummary` 线程操作能力摘要:设备详情页会显示 archive / unarchive / fork / compact / rollback / rename / metadata / steer / interrupt / shell / unsubscribe 等能力分组;该字段只读,不在 heartbeat 中调用任何会改变线程状态的 App Server API。
- 第二十九批另补 `threadCollaborationSummary` 线程协作口径:设备详情页会显示 Boss Broker、协作事件 handler、协作模式数量和“非原生私聊”状态。本机 0.136.0-alpha.2 生成 schema 已确认 `app/list``app/list/updated``configRequirements/read``mcpServerStatus/list``ThreadItem.contextCompaction`,但未声明 `collaborationMode/list``thread/turns/list``ThreadItem.collabToolCall`;因此当前产品层把线程间协作定义为 Boss 受控 Broker + App Server 注入/执行链路,不把它表述成 Codex 原生任意线程 P2P 聊天。 - 第二十九批另补 `threadCollaborationSummary` 线程协作口径:Web 与原生 Android 设备详情页会显示 Boss Broker、协作事件 handler、协作模式数量和“非原生私聊”状态。本机 0.136.0-alpha.2 生成 schema 已确认 `app/list``app/list/updated``configRequirements/read``mcpServerStatus/list``ThreadItem.contextCompaction`,但未声明 `collaborationMode/list``thread/turns/list``ThreadItem.collabToolCall`;因此当前产品层把线程间协作定义为 Boss 受控 Broker + App Server 注入/执行链路,不把它表述成 Codex 原生任意线程 P2P 聊天。
- 同批新增 `protocolDriftSummary` 协议漂移摘要:设备详情页会显示兼容/告警、失败探针数、官方文档跟进项和 Boss Broker 兜底策略。该字段来自 discovery errors 的 method 级安全归一,不保存错误原文、线程 ID、用户正文或内部 prompt后续 Codex Server 更新时优先看这个摘要决定是否需要补 runner 或前台展示。 - 同批新增 `protocolDriftSummary` 协议漂移摘要:Web 与原生 Android 设备详情页会显示兼容/告警、失败探针数、官方文档跟进项和 Boss Broker 兜底策略。该字段来自 discovery errors 的 method 级安全归一,不保存错误原文、线程 ID、用户正文或内部 prompt后续 Codex Server 更新时优先看这个摘要决定是否需要补 runner 或前台展示。
- 第二十批另补 `pluginGovernanceSummary` 插件治理能力摘要:设备详情页会显示 install / uninstall / read / skill-read / share 等能力分组;该字段只读,不在 heartbeat 中调用任何插件安装、卸载或共享写 API。 - 第二十批另补 `pluginGovernanceSummary` 插件治理能力摘要:设备详情页会显示 install / uninstall / read / skill-read / share 等能力分组;该字段只读,不在 heartbeat 中调用任何插件安装、卸载或共享写 API。
- 第二十一批另补 `accountGovernanceSummary / configGovernanceSummary` 账号与配置治理能力摘要:设备详情页会显示 login / logout / token refresh / add credits nudge / config write / MCP reload / Skill config write 等能力分组;这些字段只读,不在 heartbeat 中调用任何账号或配置写 API。 - 第二十一批另补 `accountGovernanceSummary / configGovernanceSummary` 账号与配置治理能力摘要:设备详情页会显示 login / logout / token refresh / add credits nudge / config write / MCP reload / Skill config write 等能力分组;这些字段只读,不在 heartbeat 中调用任何账号或配置写 API。
- 第二十二批另补 `fileSystemGovernanceSummary / commandSessionSummary` 文件系统与命令会话治理能力摘要:设备详情页会显示 file read/write/remove/watch 与 command stdin / resize / terminate / stream 等能力分组;这些字段只读,不在 heartbeat 中调用任何文件读写或命令控制 API。 - 第二十二批另补 `fileSystemGovernanceSummary / commandSessionSummary` 文件系统与命令会话治理能力摘要:设备详情页会显示 file read/write/remove/watch 与 command stdin / resize / terminate / stream 等能力分组;这些字段只读,不在 heartbeat 中调用任何文件读写或命令控制 API。

View File

@@ -79,6 +79,8 @@
- 当前根页导航: - 当前根页导航:
- `MainActivity` 会记住最近一次停留的 `会话 / 设备 / 我的` tab - `MainActivity` 会记住最近一次停留的 `会话 / 设备 / 我的` tab
- 根页返回逻辑已改成“先回会话 tab再按一次返回进入后台” - 根页返回逻辑已改成“先回会话 tab再按一次返回进入后台”
- 当前设备详情页:
- `DeviceDetailActivity` 已同步展示 Codex App Server 连接态、线程协作口径和协议漂移摘要;线程协作固定表达为 Boss Broker 受控协作,协议漂移只显示兼容/告警、失败探针数量、文档跟进数量和 Boss Broker 兜底,不渲染错误原文、线程 ID、用户正文或内部 prompt
- 当前会话列表: - 当前会话列表:
- 已切到“线程 = 会话窗口” - 已切到“线程 = 会话窗口”
- 主标题显示线程名 - 主标题显示线程名

View File

@@ -43,8 +43,8 @@
- 当前 App Server 能力发现已新增 turn 运行态摘要local-agent 会在 heartbeat discovery 中对非归档可见线程拉取 `thread/turns/list`,请求固定 `itemsView=summary`,并把总轮次、运行中轮次、完成轮次、最新 turn 更新时间、每个线程的最近 turn 状态和最终 `agentMessage` 安全摘要写入设备 `codexAppServer.metadata.threadTurnSummary`设备详情页会显示“轮次”摘要。该链路不保存用户正文、reasoning 原文、命令输出、原始 items、内部 prompt 或系统提示词。 - 当前 App Server 能力发现已新增 turn 运行态摘要local-agent 会在 heartbeat discovery 中对非归档可见线程拉取 `thread/turns/list`,请求固定 `itemsView=summary`,并把总轮次、运行中轮次、完成轮次、最新 turn 更新时间、每个线程的最近 turn 状态和最终 `agentMessage` 安全摘要写入设备 `codexAppServer.metadata.threadTurnSummary`设备详情页会显示“轮次”摘要。该链路不保存用户正文、reasoning 原文、命令输出、原始 items、内部 prompt 或系统提示词。
- 当前 App Server discovery 还会把最终 `agentMessage` 合并进 heartbeat `projectCandidates.recentAssistantMessages`。服务端已有 `codexThreadRef` 匹配时会把 Codex Desktop 自己产生的新回复反向同步到 Boss APP 对应会话,并刷新 preview、lastMessageAt 和未读数;已有本地扫描候选的 folder/thread 映射优先保留App Server 只补充最新回复摘要。 - 当前 App Server discovery 还会把最终 `agentMessage` 合并进 heartbeat `projectCandidates.recentAssistantMessages`。服务端已有 `codexThreadRef` 匹配时会把 Codex Desktop 自己产生的新回复反向同步到 Boss APP 对应会话,并刷新 preview、lastMessageAt 和未读数;已有本地扫描候选的 folder/thread 映射优先保留App Server 只补充最新回复摘要。
- 当前 App Server 能力发现已新增线程操作能力摘要local-agent 会把已验证进入当前协议快照的 archive / unarchive / fork / compact / rollback / rename / metadata / steer / interrupt / shell / unsubscribe 写入设备 `codexAppServer.metadata.threadActionSummary`;设备详情页会显示“线程操作”。该字段只读,不在 heartbeat 中调用任何线程写 API。 - 当前 App Server 能力发现已新增线程操作能力摘要local-agent 会把已验证进入当前协议快照的 archive / unarchive / fork / compact / rollback / rename / metadata / steer / interrupt / shell / unsubscribe 写入设备 `codexAppServer.metadata.threadActionSummary`;设备详情页会显示“线程操作”。该字段只读,不在 heartbeat 中调用任何线程写 API。
- 当前 App Server 能力发现已新增线程协作口径摘要local-agent 会写入 `codexAppServer.metadata.threadCollaborationSummary`;设备详情页会显示 Boss Broker 可用性、协作事件 handler 可用性、协作模式数量和“非原生私聊”状态。该字段用于提醒产品和运维:当前可做的是 Boss 受控线程协作,不是 Codex 原生线程互聊。 - 当前 App Server 能力发现已新增线程协作口径摘要local-agent 会写入 `codexAppServer.metadata.threadCollaborationSummary`Web 与原生 Android 设备详情页会显示 Boss Broker 可用性、协作事件 handler 可用性、协作模式数量和“非原生私聊”状态。该字段用于提醒产品和运维:当前可做的是 Boss 受控线程协作,不是 Codex 原生线程互聊。
- 当前 App Server 能力发现已新增协议漂移摘要local-agent 会写入 `codexAppServer.metadata.protocolDriftSummary`;设备详情页会显示“协议漂移:兼容/告警 · 失败探针 N 个 · 文档跟进 N 项 · Boss Broker 兜底”。该字段把运行时 discovery 失败 method、官方文档跟进项和当前兜底策略拆开展示避免 Codex Server 更新后只靠原始日志判断协议是否漂移。 - 当前 App Server 能力发现已新增协议漂移摘要local-agent 会写入 `codexAppServer.metadata.protocolDriftSummary`Web 与原生 Android 设备详情页会显示“协议漂移:兼容/告警 · 失败探针 N 个 · 文档跟进 N 项 · Boss Broker 兜底”。该字段把运行时 discovery 失败 method、官方文档跟进项和当前兜底策略拆开展示避免 Codex Server 更新后只靠原始日志判断协议是否漂移。
- 当前 App Server 能力发现已新增插件治理能力摘要local-agent 会把已验证进入当前协议快照的 install / uninstall / read / skill-read / share 写入设备 `codexAppServer.metadata.pluginGovernanceSummary`;设备详情页会显示“插件治理”。该字段只读,不在 heartbeat 中调用任何插件写 API。 - 当前 App Server 能力发现已新增插件治理能力摘要local-agent 会把已验证进入当前协议快照的 install / uninstall / read / skill-read / share 写入设备 `codexAppServer.metadata.pluginGovernanceSummary`;设备详情页会显示“插件治理”。该字段只读,不在 heartbeat 中调用任何插件写 API。
- 当前 App Server 能力发现已新增账号与配置治理能力摘要local-agent 会把已验证进入当前协议快照的 login / logout / token refresh / add credits nudge / config write / MCP reload / Skill config write 写入设备 `codexAppServer.metadata.accountGovernanceSummary / configGovernanceSummary`;设备详情页会显示“账号治理 / 配置治理”。这些字段只读,不在 heartbeat 中调用任何账号或配置写 API。 - 当前 App Server 能力发现已新增账号与配置治理能力摘要local-agent 会把已验证进入当前协议快照的 login / logout / token refresh / add credits nudge / config write / MCP reload / Skill config write 写入设备 `codexAppServer.metadata.accountGovernanceSummary / configGovernanceSummary`;设备详情页会显示“账号治理 / 配置治理”。这些字段只读,不在 heartbeat 中调用任何账号或配置写 API。
- 当前 App Server 能力发现已新增文件系统与命令会话治理能力摘要local-agent 会把已验证进入当前协议快照的文件读写、目录、元数据、复制、删除、监听、命令 stdin、PTY resize、terminate 和输出流能力写入设备 `codexAppServer.metadata.fileSystemGovernanceSummary / commandSessionSummary`;设备详情页会显示“文件治理 / 命令会话”。这些字段只读,不在 heartbeat 中调用任何文件或命令控制 API。 - 当前 App Server 能力发现已新增文件系统与命令会话治理能力摘要local-agent 会把已验证进入当前协议快照的文件读写、目录、元数据、复制、删除、监听、命令 stdin、PTY resize、terminate 和输出流能力写入设备 `codexAppServer.metadata.fileSystemGovernanceSummary / commandSessionSummary`;设备详情页会显示“文件治理 / 命令会话”。这些字段只读,不在 heartbeat 中调用任何文件或命令控制 API。