fix: persist master-agent wait state in chat

This commit is contained in:
kris
2026-03-31 22:58:13 +08:00
parent 5c69eaa26d
commit c3ee76909d
4 changed files with 202 additions and 21 deletions

View File

@@ -253,6 +253,132 @@ public class ProjectDetailActivityUiTest {
assertEquals("...", headerAction.getText().toString());
}
@Test
public void renderProjectKeepsMasterAgentWaitingStateVisibleInMessageFlow() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "主 Agent");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
ReflectionHelpers.setField(activity, "conversationInfoReady", true);
ReflectionHelpers.setField(activity, "currentScreenTitle", "主 Agent");
ReflectionHelpers.setField(activity, "currentScreenSubtitle", "单聊会话");
ReflectionHelpers.setField(activity, "masterAgentReplyWaiting", true);
ReflectionHelpers.setField(activity, "masterAgentReplyTimedOut", false);
ReflectionHelpers.setField(activity, "masterAgentReplyBaselineMessageId", "msg-user-1");
JSONObject project = new JSONObject()
.put("project", new JSONObject()
.put("id", "master-agent")
.put("name", "主 Agent")
.put("messages", new JSONArray()
.put(new JSONObject().put("id", "msg-user-1").put("sender", "user"))));
ReflectionHelpers.callInstanceMethod(
activity,
"renderProject",
ReflectionHelpers.ClassParameter.from(JSONObject.class, project),
ReflectionHelpers.ClassParameter.from(JSONArray.class, null),
ReflectionHelpers.ClassParameter.from(JSONObject.class, null)
);
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "主 Agent 思考中"));
assertFalse(viewTreeContainsText(content, "重试等待"));
assertFalse(ReflectionHelpers.getField(activity, "masterAgentReplyTimedOut"));
}
@Test
public void renderProjectShowsRetryEntryAfterMasterAgentWaitTimesOut() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "主 Agent");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
ReflectionHelpers.setField(activity, "conversationInfoReady", true);
ReflectionHelpers.setField(activity, "currentScreenTitle", "主 Agent");
ReflectionHelpers.setField(activity, "currentScreenSubtitle", "单聊会话");
ReflectionHelpers.setField(activity, "masterAgentReplyWaiting", false);
ReflectionHelpers.setField(activity, "masterAgentReplyTimedOut", true);
ReflectionHelpers.setField(activity, "masterAgentReplyBaselineMessageId", "msg-user-1");
JSONObject project = new JSONObject()
.put("project", new JSONObject()
.put("id", "master-agent")
.put("name", "主 Agent")
.put("messages", new JSONArray()
.put(new JSONObject().put("id", "msg-user-1").put("sender", "user"))));
ReflectionHelpers.callInstanceMethod(
activity,
"renderProject",
ReflectionHelpers.ClassParameter.from(JSONObject.class, project),
ReflectionHelpers.ClassParameter.from(JSONArray.class, null),
ReflectionHelpers.ClassParameter.from(JSONObject.class, null)
);
View content = activity.findViewById(R.id.screen_content);
View retryButton = findClickableViewContainingText(content, "重试等待");
assertNotNull(retryButton);
assertTrue(viewTreeContainsText(content, "主 Agent 回复超时"));
retryButton.performClick();
assertTrue(ReflectionHelpers.<Boolean>getField(activity, "masterAgentReplyWaiting"));
assertFalse(ReflectionHelpers.<Boolean>getField(activity, "masterAgentReplyTimedOut"));
assertEquals(1, activity.replyWaitPollCount);
assertEquals("msg-user-1", activity.lastReplyWaitBaselineMessageId);
assertFalse(activity.lastReplyWaitIncludeDispatchPlans);
}
@Test
public void renderProjectClearsMasterAgentWaitStateAfterNewReplyArrives() throws Exception {
Intent intent = new Intent()
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "主 Agent");
TestProjectDetailActivity activity = Robolectric
.buildActivity(TestProjectDetailActivity.class, intent)
.setup()
.get();
ReflectionHelpers.setField(activity, "conversationInfoReady", true);
ReflectionHelpers.setField(activity, "currentScreenTitle", "主 Agent");
ReflectionHelpers.setField(activity, "currentScreenSubtitle", "单聊会话");
ReflectionHelpers.setField(activity, "masterAgentReplyWaiting", true);
ReflectionHelpers.setField(activity, "masterAgentReplyTimedOut", true);
ReflectionHelpers.setField(activity, "masterAgentReplyBaselineMessageId", "msg-user-1");
JSONObject project = new JSONObject()
.put("project", new JSONObject()
.put("id", "master-agent")
.put("name", "主 Agent")
.put("messages", new JSONArray()
.put(new JSONObject().put("id", "msg-user-1").put("sender", "user"))
.put(new JSONObject().put("id", "msg-thread-1").put("sender", "assistant"))
.put(new JSONObject().put("id", "msg-thread-2").put("sender", "assistant"))));
ReflectionHelpers.callInstanceMethod(
activity,
"renderProject",
ReflectionHelpers.ClassParameter.from(JSONObject.class, project),
ReflectionHelpers.ClassParameter.from(JSONArray.class, null),
ReflectionHelpers.ClassParameter.from(JSONObject.class, null)
);
View content = activity.findViewById(R.id.screen_content);
assertFalse(viewTreeContainsText(content, "主 Agent 思考中"));
assertFalse(viewTreeContainsText(content, "主 Agent 回复超时"));
assertFalse(ReflectionHelpers.<Boolean>getField(activity, "masterAgentReplyWaiting"));
assertFalse(ReflectionHelpers.<Boolean>getField(activity, "masterAgentReplyTimedOut"));
assertEquals(null, ReflectionHelpers.getField(activity, "masterAgentReplyBaselineMessageId"));
}
@Test
public void outgoingAttachmentMetaPrefersTimeOnly() throws Exception {
Intent intent = new Intent()
@@ -521,10 +647,21 @@ public class ProjectDetailActivityUiTest {
}
public static class TestProjectDetailActivity extends ProjectDetailActivity {
int replyWaitPollCount;
String lastReplyWaitBaselineMessageId;
boolean lastReplyWaitIncludeDispatchPlans;
@Override
boolean shouldLoadOnCreate() {
return false;
}
@Override
protected void enqueueReplyWaitPoll(String baselineMessageId, boolean includeDispatchPlans) {
replyWaitPollCount += 1;
lastReplyWaitBaselineMessageId = baselineMessageId;
lastReplyWaitIncludeDispatchPlans = includeDispatchPlans;
}
}
private static final class InMemorySharedPreferences implements SharedPreferences {