chore: checkpoint Boss app v2.5.11

This commit is contained in:
AI Bot
2026-06-08 12:22:50 +08:00
parent bddbe8b5ba
commit 3b51641d99
78 changed files with 5706 additions and 954 deletions

View File

@@ -260,6 +260,34 @@ public class BossApiClientDispatchPlansTest {
assertEquals("GET", connection.requestMethodValue);
}
@Test
public void getProjectDetailUsesExtendedReadTimeoutForChatPages() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/thread-1"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
BossApiClient.ApiResponse response = apiClient.getProjectDetail("thread-1");
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/thread-1", apiClient.lastPath);
assertEquals("GET", connection.requestMethodValue);
assertEquals(12000, connection.connectTimeoutValue);
assertEquals(30000, connection.readTimeoutValue);
}
@Test
public void getProjectMessagesUsesExtendedReadTimeoutForRealtimeRefreshes() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/thread-1/messages"));
RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
BossApiClient.ApiResponse response = apiClient.getProjectMessages("thread-1");
assertEquals(200, response.statusCode);
assertEquals("/api/v1/projects/thread-1/messages", apiClient.lastPath);
assertEquals("GET", connection.requestMethodValue);
assertEquals(12000, connection.connectTimeoutValue);
assertEquals(30000, connection.readTimeoutValue);
}
@Test
public void createMasterAgentMemoryWritesStructuredPayload() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/memories"));

View File

@@ -57,6 +57,7 @@ public class ProjectDetailActivityRealtimeTest {
);
drainRealtimeDebounce(activity);
waitFor(() -> activity.messageReloadCount == 1);
assertEquals(0, activity.reloadCount);
assertEquals(1, activity.messageReloadCount);
}
@@ -144,7 +145,8 @@ public class ProjectDetailActivityRealtimeTest {
);
drainRealtimeDebounce(activity);
assertEquals(1, activity.reloadCount);
waitFor(() -> activity.loadCallCount == 1);
assertEquals(1, activity.loadCallCount);
assertEquals(0, activity.messageReloadCount);
}
@@ -172,7 +174,8 @@ public class ProjectDetailActivityRealtimeTest {
);
drainRealtimeDebounce(activity);
assertEquals(1, activity.reloadCount);
waitFor(() -> activity.loadCallCount == 1);
assertEquals(1, activity.loadCallCount);
assertEquals(0, activity.messageReloadCount);
}
@@ -211,6 +214,7 @@ public class ProjectDetailActivityRealtimeTest {
);
drainRealtimeDebounce(activity);
waitFor(() -> activity.messageReloadCount == 1);
assertEquals(0, activity.reloadCount);
assertEquals(1, activity.messageReloadCount);
}
@@ -441,7 +445,8 @@ public class ProjectDetailActivityRealtimeTest {
);
drainRealtimeDebounce(activity);
assertEquals(1, activity.reloadCount);
waitFor(() -> activity.loadCallCount == 1);
assertEquals(1, activity.loadCallCount);
}
@Test

View File

@@ -1532,6 +1532,42 @@ public class ProjectDetailActivityUiTest {
assertFalse(viewTreeContainsText(messageView, "Codex"));
}
@Test
public void failedExecutionProgressWithoutBranchDoesNotRenderExecutorWaitingFallback() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "juyuwan")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "juyuwan");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
JSONObject message = new JSONObject()
.put("id", "failed-progress-no-branch")
.put("sender", "master")
.put("senderLabel", "主 Agent")
.put("body", "执行进度:失败")
.put("kind", "execution_progress")
.put("sentAt", "2026-06-07T14:24:00+08:00")
.put("executionProgress", new JSONObject()
.put("title", "进度")
.put("controlMode", "codex_thread")
.put("status", "failed")
.put("phase", "terminal_failed")
.put("steps", new JSONArray()
.put(new JSONObject().put("text", "等待目标线程回复").put("status", "failed"))));
View messageView = ReflectionHelpers.callInstanceMethod(
activity,
"buildMessageView",
ReflectionHelpers.ClassParameter.from(JSONObject.class, message)
);
assertTrue(viewTreeContainsText(messageView, "当前状态:执行失败"));
assertFalse(viewTreeContainsText(messageView, "等待执行器回写"));
assertFalse(viewTreeContainsText(messageView, "分支详情"));
}
@Test
public void completedReplyResponseRendersImmediatelyWithoutReloadingProjectDetail() throws Exception {
Intent intent = new Intent()
@@ -1603,6 +1639,24 @@ public class ProjectDetailActivityUiTest {
assertEquals(0, fakeApiClient.projectDetailCallCount);
}
@Test
public void realtimeMessageReloadDoesNotShowSwipeRefreshSpinner() {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "juyuwan")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "juyuwan");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
ReflectionHelpers.setField(activity, "apiClient", new SlowProjectMessagesApiClient());
activity.triggerRealtimeReload(false);
Shadows.shadowOf(activity.getMainLooper()).idle();
SwipeRefreshLayout refreshLayout = activity.findViewById(R.id.screen_refresh_layout);
assertFalse(refreshLayout.isRefreshing());
}
@Test
public void masterAgentHeaderUsesWechatMoreMenuLabel() {
Intent intent = new Intent()
@@ -2458,6 +2512,30 @@ public class ProjectDetailActivityUiTest {
}
}
private static final class SlowProjectMessagesApiClient extends BossApiClient {
SlowProjectMessagesApiClient() {
super(new InMemorySharedPreferences(), "https://boss.hyzq.net");
}
@Override
public ApiResponse getProjectMessages(String projectId) throws java.io.IOException, org.json.JSONException {
try {
Thread.sleep(250L);
} catch (InterruptedException error) {
Thread.currentThread().interrupt();
}
return new ApiResponse(
200,
new JSONObject()
.put("ok", true)
.put("project", new JSONObject()
.put("id", projectId)
.put("name", "juyuwan")
.put("messages", new JSONArray()))
);
}
}
private static final class RecordingConversationActionApiClient extends BossApiClient {
int markConversationReadCount;
String lastMarkedProjectId;

View File

@@ -1,10 +1,15 @@
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Intent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.json.JSONObject;
import org.json.JSONArray;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@@ -68,6 +73,61 @@ public class SkillInventoryActivityTest {
assertEquals(0, activity.reloadCount);
}
@Test
public void renderSkillsShowsManagementDispatchWorkspaceWhenLifecycleRequestsAreAvailable() throws Exception {
Intent intent = new Intent()
.putExtra(SkillInventoryActivity.EXTRA_DEVICE_ID, "device-1")
.putExtra(SkillInventoryActivity.EXTRA_DEVICE_NAME, "Mac Studio");
TestSkillInventoryActivity activity = Robolectric
.buildActivity(TestSkillInventoryActivity.class, intent)
.setup()
.get();
ReflectionHelpers.callInstanceMethod(
activity,
"renderSkills",
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildSkillPayload()),
ReflectionHelpers.ClassParameter.from(JSONObject.class, buildLifecyclePayload())
);
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "Skill 管理分发"));
assertTrue(viewTreeContainsText(content, "安装远端 Skill"));
assertTrue(viewTreeContainsText(content, "分配权限"));
assertTrue(viewTreeContainsText(content, "Skill 请求状态"));
assertTrue(viewTreeContainsText(content, "更新下发"));
assertTrue(viewTreeContainsText(content, "版本锁定"));
}
private static JSONObject buildSkillPayload() throws Exception {
return new JSONObject()
.put("device", new JSONObject()
.put("id", "device-1")
.put("name", "Mac Studio"))
.put("skills", new JSONArray()
.put(new JSONObject()
.put("skillId", "device-1:boss-server-debug")
.put("name", "boss-server-debug")
.put("description", "服务器排障")
.put("category", "ops")
.put("path", "/Users/kris/.codex/skills/boss-server-debug/SKILL.md")
.put("invocation", "使用 boss-server-debug")
.put("updatedAt", "2026-06-08T10:00:00+08:00")));
}
private static JSONObject buildLifecyclePayload() throws Exception {
return new JSONObject()
.put("ok", true)
.put("requests", new JSONArray()
.put(new JSONObject()
.put("requestId", "skill-request-1")
.put("action", "update")
.put("status", "queued")
.put("deviceId", "device-1")
.put("skillId", "device-1:boss-server-debug")
.put("requestedAt", "2026-06-08T10:01:00+08:00")));
}
public static class TestSkillInventoryActivity extends SkillInventoryActivity {
private boolean reloadEnabled;
private int reloadCount;
@@ -81,4 +141,23 @@ public class SkillInventoryActivityTest {
setRefreshing(false);
}
}
private static boolean viewTreeContainsText(View root, String expectedText) {
if (root instanceof TextView) {
CharSequence text = ((TextView) root).getText();
if (text != null && text.toString().contains(expectedText)) {
return true;
}
}
if (!(root instanceof ViewGroup)) {
return false;
}
ViewGroup group = (ViewGroup) root;
for (int index = 0; index < group.getChildCount(); index += 1) {
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
return true;
}
}
return false;
}
}