feat: surface codex thread config progress

This commit is contained in:
AI Bot
2026-06-01 17:18:28 +08:00
parent 591638f35f
commit 26b5e97614
10 changed files with 540 additions and 5 deletions

View File

@@ -1353,6 +1353,90 @@ public final class BossUi {
}
}
JSONObject threadGoal = progress == null ? null : progress.optJSONObject("threadGoal");
JSONObject threadSettings = progress == null ? null : progress.optJSONObject("threadSettings");
JSONObject compaction = progress == null ? null : progress.optJSONObject("compaction");
boolean hasThreadConfig = threadGoal != null || threadSettings != null || compaction != null;
if (hasThreadConfig) {
card.addView(divider(context));
card.addView(sectionTitle(context, "线程配置"));
if (threadGoal != null) {
String status = threadGoal.optString("status", "").trim();
String objective = threadGoal.optString("objective", "").trim();
if ("cleared".equals(status)) {
card.addView(detailRow(context, "", "目标已清除", "", false));
} else if (!TextUtils.isEmpty(status) || !TextUtils.isEmpty(objective)) {
card.addView(detailRow(
context,
"",
TextUtils.isEmpty(status) ? "目标" : "目标 " + status,
"",
false
));
if (!TextUtils.isEmpty(objective)) {
card.addView(detailRow(context, "", objective, "", false, true));
}
int tokensUsed = threadGoal.optInt("tokensUsed", 0);
int tokenBudget = threadGoal.optInt("tokenBudget", 0);
if (tokensUsed > 0 || tokenBudget > 0) {
StringBuilder budget = new StringBuilder();
budget.append("预算 ");
if (tokensUsed > 0) {
budget.append(String.format(Locale.US, "%,d", tokensUsed));
} else {
budget.append("0");
}
if (tokenBudget > 0) {
budget.append(" / ").append(String.format(Locale.US, "%,d", tokenBudget));
}
card.addView(detailRow(context, "", budget.toString(), "", false, true));
}
}
}
if (threadSettings != null) {
String model = threadSettings.optString("model", "").trim();
String provider = threadSettings.optString("modelProvider", "").trim();
if (!TextUtils.isEmpty(model)) {
card.addView(detailRow(
context,
"",
TextUtils.isEmpty(provider) ? "模型 " + model : "模型 " + model + " · " + provider,
"",
false
));
}
String approval = threadSettings.optString("approvalPolicy", "").trim();
String sandbox = threadSettings.optString("sandboxPolicy", "").trim();
if (!TextUtils.isEmpty(approval) || !TextUtils.isEmpty(sandbox)) {
String label = !TextUtils.isEmpty(approval) && !TextUtils.isEmpty(sandbox)
? "审批 " + approval + " · 沙箱 " + sandbox
: !TextUtils.isEmpty(approval) ? "审批 " + approval : "沙箱 " + sandbox;
card.addView(detailRow(context, "", label, "", false));
}
String collaborationMode = threadSettings.optString("collaborationMode", "").trim();
String serviceTier = threadSettings.optString("serviceTier", "").trim();
if (!TextUtils.isEmpty(collaborationMode) || !TextUtils.isEmpty(serviceTier)) {
String label = !TextUtils.isEmpty(collaborationMode) && !TextUtils.isEmpty(serviceTier)
? "协作 " + collaborationMode + " · " + serviceTier
: !TextUtils.isEmpty(collaborationMode) ? "协作 " + collaborationMode : "服务 " + serviceTier;
card.addView(detailRow(context, "", label, "", false));
}
}
if (compaction != null) {
String message = compaction.optString("message", "").trim();
String status = compaction.optString("status", "").trim();
if (!TextUtils.isEmpty(message) || !TextUtils.isEmpty(status)) {
card.addView(detailRow(
context,
"",
TextUtils.isEmpty(message) ? "上下文压缩 " + status : message,
"",
false
));
}
}
}
JSONObject modelRoute = progress == null ? null : progress.optJSONObject("modelRoute");
JSONObject tokenUsage = progress == null ? null : progress.optJSONObject("tokenUsage");
JSONArray mcpServers = progress == null ? null : progress.optJSONArray("mcpServers");

View File

@@ -1069,6 +1069,61 @@ public class ProjectDetailActivityUiTest {
assertFalse(viewTreeContainsText(messageView, "sk-secret"));
}
@Test
public void executionProgressMessageRendersCodexThreadGoalSettingsAndCompactionSections() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "thread-config")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "Boss开发主线程");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
JSONObject message = new JSONObject()
.put("id", "progress-thread-config-1")
.put("sender", "master")
.put("senderLabel", "主 Agent")
.put("body", "执行进度")
.put("kind", "execution_progress")
.put("sentAt", "2026-06-01T10:25:00+08:00")
.put("executionProgress", new JSONObject()
.put("status", "running")
.put("steps", new JSONArray()
.put(new JSONObject().put("text", "同步 Codex 线程配置").put("status", "running")))
.put("threadGoal", new JSONObject()
.put("objective", "完成 App Server 线程目标同步")
.put("status", "active")
.put("tokenBudget", 120000)
.put("tokensUsed", 4800)
.put("timeUsedSeconds", 600))
.put("threadSettings", new JSONObject()
.put("model", "gpt-5.5")
.put("modelProvider", "openai")
.put("approvalPolicy", "on-request")
.put("sandboxPolicy", "workspaceWrite")
.put("collaborationMode", "plan")
.put("serviceTier", "fast")
.put("cwd", "/Users/kris/code/boss/secret-project"))
.put("compaction", new JSONObject()
.put("status", "completed")
.put("message", "上下文已压缩")));
View messageView = ReflectionHelpers.callInstanceMethod(
activity,
"buildMessageView",
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
);
assertTrue(viewTreeContainsText(messageView, "线程配置"));
assertTrue(viewTreeContainsText(messageView, "目标 active"));
assertTrue(viewTreeContainsText(messageView, "完成 App Server 线程目标同步"));
assertTrue(viewTreeContainsText(messageView, "模型 gpt-5.5 · openai"));
assertTrue(viewTreeContainsText(messageView, "审批 on-request · 沙箱 workspaceWrite"));
assertTrue(viewTreeContainsText(messageView, "协作 plan · fast"));
assertTrue(viewTreeContainsText(messageView, "上下文已压缩"));
assertFalse(viewTreeContainsText(messageView, "/Users/kris"));
}
@Test
public void nativeRemoteExecutionProgressDoesNotRenderCodexSections() throws Exception {
Intent intent = new Intent()