From 3080f57dbc779d24db566d45f88d597dac39593f Mon Sep 17 00:00:00 2001 From: AI Bot Date: Thu, 4 Jun 2026 14:56:44 +0800 Subject: [PATCH] feat: show codex app server drift on android --- .../com/hyzq/boss/DeviceDetailActivity.java | 26 +++++++++ .../com/hyzq/boss/WechatSurfaceMapper.java | 54 +++++++++++++++++++ .../hyzq/boss/DeviceDetailActivityTest.java | 52 ++++++++++++++++++ docs/architecture/ai_handoff_index_cn.md | 4 +- .../api_and_service_inventory_cn.md | 2 + .../current_runtime_and_deploy_status_cn.md | 4 +- 6 files changed, 138 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java b/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java index 426934d..32de6b7 100644 --- a/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java @@ -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, "默认执行模式", diff --git a/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java b/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java index 0ccbd82..3a68a2e 100644 --- a/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java +++ b/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java @@ -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); } diff --git a/android/app/src/test/java/com/hyzq/boss/DeviceDetailActivityTest.java b/android/app/src/test/java/com/hyzq/boss/DeviceDetailActivityTest.java index 372044f..c2baeb3 100644 --- a/android/app/src/test/java/com/hyzq/boss/DeviceDetailActivityTest.java +++ b/android/app/src/test/java/com/hyzq/boss/DeviceDetailActivityTest.java @@ -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; diff --git a/docs/architecture/ai_handoff_index_cn.md b/docs/architecture/ai_handoff_index_cn.md index 244483d..06eb0eb 100644 --- a/docs/architecture/ai_handoff_index_cn.md +++ b/docs/architecture/ai_handoff_index_cn.md @@ -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、本地绝对路径或未清洗密钥。 - 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。 -- 第二十九批另补 `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 聊天。 -- 同批新增 `protocolDriftSummary` 协议漂移摘要:设备详情页会显示兼容/告警、失败探针数、官方文档跟进项和 Boss Broker 兜底策略。该字段来自 discovery errors 的 method 级安全归一,不保存错误原文、线程 ID、用户正文或内部 prompt;后续 Codex Server 更新时优先看这个摘要决定是否需要补 runner 或前台展示。 +- 第二十九批另补 `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` 协议漂移摘要:Web 与原生 Android 设备详情页会显示兼容/告警、失败探针数、官方文档跟进项和 Boss Broker 兜底策略。该字段来自 discovery errors 的 method 级安全归一,不保存错误原文、线程 ID、用户正文或内部 prompt;后续 Codex Server 更新时优先看这个摘要决定是否需要补 runner 或前台展示。 - 第二十批另补 `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。 - 第二十二批另补 `fileSystemGovernanceSummary / commandSessionSummary` 文件系统与命令会话治理能力摘要:设备详情页会显示 file read/write/remove/watch 与 command stdin / resize / terminate / stream 等能力分组;这些字段只读,不在 heartbeat 中调用任何文件读写或命令控制 API。 diff --git a/docs/architecture/api_and_service_inventory_cn.md b/docs/architecture/api_and_service_inventory_cn.md index 0681aa7..c462f9f 100644 --- a/docs/architecture/api_and_service_inventory_cn.md +++ b/docs/architecture/api_and_service_inventory_cn.md @@ -79,6 +79,8 @@ - 当前根页导航: - `MainActivity` 会记住最近一次停留的 `会话 / 设备 / 我的` tab - 根页返回逻辑已改成“先回会话 tab,再按一次返回进入后台” +- 当前设备详情页: + - `DeviceDetailActivity` 已同步展示 Codex App Server 连接态、线程协作口径和协议漂移摘要;线程协作固定表达为 Boss Broker 受控协作,协议漂移只显示兼容/告警、失败探针数量、文档跟进数量和 Boss Broker 兜底,不渲染错误原文、线程 ID、用户正文或内部 prompt - 当前会话列表: - 已切到“线程 = 会话窗口” - 主标题显示线程名 diff --git a/docs/architecture/current_runtime_and_deploy_status_cn.md b/docs/architecture/current_runtime_and_deploy_status_cn.md index ee708db..732f505 100644 --- a/docs/architecture/current_runtime_and_deploy_status_cn.md +++ b/docs/architecture/current_runtime_and_deploy_status_cn.md @@ -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 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 会写入 `codexAppServer.metadata.threadCollaborationSummary`;设备详情页会显示 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.threadCollaborationSummary`;Web 与原生 Android 设备详情页都会显示 Boss Broker 可用性、协作事件 handler 可用性、协作模式数量和“非原生私聊”状态。该字段用于提醒产品和运维:当前可做的是 Boss 受控线程协作,不是 Codex 原生线程互聊。 +- 当前 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 会把已验证进入当前协议快照的 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。