diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ea6bbfe..a9cf5b5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -54,6 +54,7 @@
+
diff --git a/android/app/src/main/java/com/hyzq/boss/BossApiClient.java b/android/app/src/main/java/com/hyzq/boss/BossApiClient.java
index 0f8c94e..594c94b 100644
--- a/android/app/src/main/java/com/hyzq/boss/BossApiClient.java
+++ b/android/app/src/main/java/com/hyzq/boss/BossApiClient.java
@@ -265,6 +265,32 @@ public class BossApiClient {
return requestWithRestore("GET", "/api/v1/projects/" + encode(projectId) + "/memories", null);
}
+ public ApiResponse getMasterAgentEvolution() throws IOException, JSONException {
+ return requestWithRestore("GET", "/api/v1/master-agent/evolution", null);
+ }
+
+ public ApiResponse updateMasterAgentEvolutionMode(String mode) throws IOException, JSONException {
+ JSONObject payload = new JSONObject();
+ payload.put("mode", mode);
+ return requestWithRestore("POST", "/api/v1/master-agent/evolution/config", payload);
+ }
+
+ public ApiResponse approveMasterAgentEvolutionProposal(String proposalId) throws IOException, JSONException {
+ return requestWithRestore(
+ "POST",
+ "/api/v1/master-agent/evolution/proposals/" + encode(proposalId) + "/approve",
+ new JSONObject()
+ );
+ }
+
+ public ApiResponse rejectMasterAgentEvolutionProposal(String proposalId) throws IOException, JSONException {
+ return requestWithRestore(
+ "POST",
+ "/api/v1/master-agent/evolution/proposals/" + encode(proposalId) + "/reject",
+ new JSONObject()
+ );
+ }
+
public ApiResponse createMasterAgentMemory(String projectId, JSONObject payload) throws IOException, JSONException {
return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/memories", payload);
}
diff --git a/android/app/src/main/java/com/hyzq/boss/MainActivity.java b/android/app/src/main/java/com/hyzq/boss/MainActivity.java
index b494b95..765c1a9 100644
--- a/android/app/src/main/java/com/hyzq/boss/MainActivity.java
+++ b/android/app/src/main/java/com/hyzq/boss/MainActivity.java
@@ -1813,6 +1813,14 @@ public class MainActivity extends AppCompatActivity {
case "security":
intent = new Intent(this, SecurityActivity.class);
break;
+ case "master_agent_prompt":
+ intent = new Intent(this, MasterAgentPromptActivity.class);
+ intent.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_ID, "master-agent");
+ intent.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME, "主 Agent");
+ break;
+ case "master_agent_evolution":
+ intent = new Intent(this, MasterAgentEvolutionActivity.class);
+ break;
case "ai_accounts":
intent = new Intent(this, AiAccountsActivity.class);
break;
diff --git a/android/app/src/main/java/com/hyzq/boss/MasterAgentEvolutionActivity.java b/android/app/src/main/java/com/hyzq/boss/MasterAgentEvolutionActivity.java
new file mode 100644
index 0000000..5bef503
--- /dev/null
+++ b/android/app/src/main/java/com/hyzq/boss/MasterAgentEvolutionActivity.java
@@ -0,0 +1,304 @@
+package com.hyzq.boss;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class MasterAgentEvolutionActivity extends BossScreenActivity {
+ private static final long REALTIME_RELOAD_THROTTLE_MS = 900L;
+
+ private LinearLayout contentRoot;
+ private @Nullable BossRealtimeClient realtimeClient;
+ private long lastRealtimeReloadAt;
+ private boolean contentLoaded;
+ private @Nullable String currentMode;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ configureScreen("主 Agent 自动进化", "信号、提案与生效规则");
+ setHeaderAction("刷新", v -> reload());
+ contentRoot = new LinearLayout(this);
+ contentRoot.setOrientation(LinearLayout.VERTICAL);
+ replaceContent(contentRoot);
+ realtimeClient = new BossRealtimeClient(apiClient, this::handleRealtimeEvent);
+ reload();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateRealtimeSubscription();
+ }
+
+ @Override
+ protected void onPause() {
+ stopRealtimeUpdates();
+ super.onPause();
+ }
+
+ @Override
+ protected void onDestroy() {
+ stopRealtimeUpdates();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void reload() {
+ setRefreshing(true);
+ executor.execute(() -> {
+ try {
+ BossApiClient.ApiResponse response = apiClient.getMasterAgentEvolution();
+ if (!response.ok()) {
+ throw new IllegalStateException(response.message());
+ }
+ runOnUiThread(() -> renderDashboard(response.json));
+ } catch (Exception error) {
+ runOnUiThread(() -> {
+ setRefreshing(false);
+ contentLoaded = false;
+ replaceContent(BossUi.buildEmptyCard(this, "自动进化加载失败:" + error.getMessage()));
+ });
+ }
+ });
+ }
+
+ private void updateRealtimeSubscription() {
+ if (apiClient != null && apiClient.hasSessionHints() && realtimeClient != null) {
+ realtimeClient.start();
+ return;
+ }
+ stopRealtimeUpdates();
+ }
+
+ private void stopRealtimeUpdates() {
+ if (realtimeClient != null) {
+ realtimeClient.stop();
+ }
+ }
+
+ void handleRealtimeEvent(BossRealtimeEvent event) {
+ if (event == null || !"master_agent.settings.updated".equals(event.eventName)) {
+ return;
+ }
+ long now = System.currentTimeMillis();
+ if (now - lastRealtimeReloadAt < REALTIME_RELOAD_THROTTLE_MS) {
+ return;
+ }
+ lastRealtimeReloadAt = now;
+ runOnUiThread(this::reload);
+ }
+
+ private void renderDashboard(JSONObject payload) {
+ JSONObject config = payload.optJSONObject("config");
+ JSONArray signals = payload.optJSONArray("signals");
+ JSONArray proposals = payload.optJSONArray("proposals");
+ JSONArray rules = payload.optJSONArray("rules");
+
+ currentMode = config == null ? "controlled" : config.optString("mode", "controlled");
+ boolean autoApplyLowRiskRules = config != null && config.optBoolean("autoApplyLowRiskRules", false);
+
+ replaceContent(contentRoot);
+ contentRoot.removeAllViews();
+
+ contentRoot.addView(BossUi.buildSimpleProfileHeader(
+ this,
+ "主 Agent 自动进化",
+ "最近在学什么、打算怎么改、已经生效了什么",
+ "支持在这里切换 controlled / autonomous,并直接审核待处理提案。"
+ ));
+
+ contentRoot.addView(BossUi.buildSoftPanel(
+ this,
+ "当前模式",
+ "autonomous".equals(currentMode) ? "完全自我进化" : "受控自动进化",
+ autoApplyLowRiskRules ? "低风险提案会自动采纳。" : "所有提案都需要人工确认。"
+ ));
+
+ Button controlledButton = BossUi.buildMiniActionButton(this, "切到受控模式", false);
+ controlledButton.setEnabled(!"controlled".equals(currentMode));
+ controlledButton.setOnClickListener(v -> switchMode("controlled"));
+ Button autonomousButton = BossUi.buildMiniActionButton(this, "切到全自动模式", true);
+ autonomousButton.setEnabled(!"autonomous".equals(currentMode));
+ autonomousButton.setOnClickListener(v -> switchMode("autonomous"));
+ contentRoot.addView(BossUi.buildInlineActionRow(this, controlledButton, autonomousButton));
+
+ contentRoot.addView(BossUi.buildWechatMenuRow(
+ this,
+ "待处理提案",
+ String.valueOf(countPendingProposals(proposals)) + " 条",
+ "待审核的策略变更会在这里集中展示。",
+ null,
+ null
+ ));
+ renderPendingProposals(proposals);
+
+ contentRoot.addView(BossUi.buildWechatMenuRow(
+ this,
+ "最近信号",
+ signals == null ? "0 条" : signals.length() + " 条",
+ "主 Agent 最近捕获到的问题和自我修正线索。",
+ null,
+ null
+ ));
+ renderSignals(signals);
+
+ contentRoot.addView(BossUi.buildWechatMenuRow(
+ this,
+ "已生效规则",
+ rules == null ? "0 条" : rules.length() + " 条",
+ "已经落进系统并开始影响主 Agent 行为的规则。",
+ null,
+ null
+ ));
+ renderRules(rules);
+
+ contentLoaded = true;
+ setRefreshing(false);
+ }
+
+ private void renderPendingProposals(@Nullable JSONArray proposals) {
+ if (proposals == null || proposals.length() == 0) {
+ contentRoot.addView(BossUi.buildEmptyCard(this, "当前没有待审批提案。"));
+ return;
+ }
+ boolean rendered = false;
+ for (int i = 0; i < proposals.length(); i++) {
+ JSONObject proposal = proposals.optJSONObject(i);
+ if (proposal == null || !"pending_review".equals(proposal.optString("status", ""))) {
+ continue;
+ }
+ rendered = true;
+ String proposalId = proposal.optString("proposalId", "");
+ contentRoot.addView(BossUi.buildWechatMenuRow(
+ this,
+ proposal.optString("title", "待审批提案"),
+ proposal.optString("summary", "暂无摘要"),
+ proposal.optString("proposalType", "-")
+ + " · " + proposal.optString("riskLevel", "-")
+ + " · " + proposal.optString("createdAt", "-"),
+ null,
+ null
+ ));
+ Button rejectButton = BossUi.buildMiniActionButton(this, "拒绝", false);
+ rejectButton.setOnClickListener(v -> reviewProposal(proposalId, false));
+ Button approveButton = BossUi.buildMiniActionButton(this, "批准", true);
+ approveButton.setOnClickListener(v -> reviewProposal(proposalId, true));
+ contentRoot.addView(BossUi.buildInlineActionRow(this, rejectButton, approveButton));
+ }
+ if (!rendered) {
+ contentRoot.addView(BossUi.buildEmptyCard(this, "当前没有待审批提案。"));
+ }
+ }
+
+ private void renderSignals(@Nullable JSONArray signals) {
+ if (signals == null || signals.length() == 0) {
+ contentRoot.addView(BossUi.buildEmptyCard(this, "当前还没有进化信号。"));
+ return;
+ }
+ for (int i = 0; i < Math.min(signals.length(), 8); i++) {
+ JSONObject signal = signals.optJSONObject(i);
+ if (signal == null) continue;
+ contentRoot.addView(BossUi.buildWechatMenuRow(
+ this,
+ signal.optString("kind", "signal"),
+ signal.optString("requestText", ""),
+ signal.optString("createdAt", "-"),
+ null,
+ null
+ ));
+ }
+ }
+
+ private void renderRules(@Nullable JSONArray rules) {
+ if (rules == null || rules.length() == 0) {
+ contentRoot.addView(BossUi.buildEmptyCard(this, "当前还没有已生效规则。"));
+ return;
+ }
+ for (int i = 0; i < Math.min(rules.length(), 8); i++) {
+ JSONObject rule = rules.optJSONObject(i);
+ if (rule == null) continue;
+ contentRoot.addView(BossUi.buildWechatMenuRow(
+ this,
+ rule.optString("ruleType", "rule"),
+ rule.optString("sourceProposalId", "直接创建"),
+ rule.optString("createdAt", "-"),
+ null,
+ null
+ ));
+ }
+ }
+
+ private int countPendingProposals(@Nullable JSONArray proposals) {
+ if (proposals == null) {
+ return 0;
+ }
+ int count = 0;
+ for (int i = 0; i < proposals.length(); i++) {
+ JSONObject proposal = proposals.optJSONObject(i);
+ if (proposal != null && "pending_review".equals(proposal.optString("status", ""))) {
+ count += 1;
+ }
+ }
+ return count;
+ }
+
+ private void switchMode(String mode) {
+ if (!contentLoaded) {
+ showMessage("自动进化尚未加载完成。");
+ return;
+ }
+ setRefreshing(true);
+ executor.execute(() -> {
+ try {
+ BossApiClient.ApiResponse response = apiClient.updateMasterAgentEvolutionMode(mode);
+ if (!response.ok()) {
+ throw new IllegalStateException(response.message());
+ }
+ runOnUiThread(() -> {
+ showMessage("已切到 " + ("autonomous".equals(mode) ? "完全自我进化" : "受控自动进化"));
+ setResult(RESULT_OK);
+ reload();
+ });
+ } catch (Exception error) {
+ runOnUiThread(() -> {
+ setRefreshing(false);
+ showMessage("切换失败:" + error.getMessage());
+ });
+ }
+ });
+ }
+
+ private void reviewProposal(String proposalId, boolean approve) {
+ if (proposalId == null || proposalId.isEmpty()) {
+ showMessage("缺少 proposalId");
+ return;
+ }
+ setRefreshing(true);
+ executor.execute(() -> {
+ try {
+ BossApiClient.ApiResponse response = approve
+ ? apiClient.approveMasterAgentEvolutionProposal(proposalId)
+ : apiClient.rejectMasterAgentEvolutionProposal(proposalId);
+ if (!response.ok()) {
+ throw new IllegalStateException(response.message());
+ }
+ runOnUiThread(() -> {
+ showMessage(approve ? "提案已批准" : "提案已拒绝");
+ setResult(RESULT_OK);
+ reload();
+ });
+ } catch (Exception error) {
+ runOnUiThread(() -> {
+ setRefreshing(false);
+ showMessage((approve ? "批准失败:" : "拒绝失败:") + error.getMessage());
+ });
+ }
+ });
+ }
+}
diff --git a/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java b/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java
index cdb9b3a..f2a4129 100644
--- a/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java
+++ b/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java
@@ -93,6 +93,7 @@ public class ProjectDetailActivity extends BossScreenActivity {
private ActivityResultLauncher masterAgentPromptLauncher;
private ActivityResultLauncher masterAgentTakeoverLauncher;
private ActivityResultLauncher masterAgentMemoryLauncher;
+ private ActivityResultLauncher masterAgentEvolutionLauncher;
private ActivityResultLauncher forwardTargetLauncher;
private ActivityResultLauncher imagePickerLauncher;
private ActivityResultLauncher videoPickerLauncher;
@@ -259,6 +260,14 @@ public class ProjectDetailActivity extends BossScreenActivity {
}
}
);
+ masterAgentEvolutionLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == RESULT_OK) {
+ reload(true);
+ }
+ }
+ );
forwardTargetLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
@@ -1358,12 +1367,17 @@ public class ProjectDetailActivity extends BossScreenActivity {
masterAgentTakeoverLauncher.launch(intent);
}
+ private void openMasterAgentEvolution() {
+ Intent intent = new Intent(this, MasterAgentEvolutionActivity.class);
+ masterAgentEvolutionLauncher.launch(intent);
+ }
+
private void showMasterAgentMoreMenu() {
if (!isMasterAgentConversation()) {
return;
}
new AlertDialog.Builder(this)
- .setItems(new CharSequence[]{"模型", "推理强度", "全局接管", "提示词", "记忆", "会话信息", "刷新"}, (dialog, which) -> {
+ .setItems(new CharSequence[]{"模型", "推理强度", "全局接管", "自动进化", "提示词", "记忆", "会话信息", "刷新"}, (dialog, which) -> {
switch (which) {
case 0:
showMasterAgentModelPicker();
@@ -1375,15 +1389,18 @@ public class ProjectDetailActivity extends BossScreenActivity {
openMasterAgentTakeoverSettings();
break;
case 3:
- openMasterAgentPromptProfile();
+ openMasterAgentEvolution();
break;
case 4:
- openMasterAgentMemories();
+ openMasterAgentPromptProfile();
break;
case 5:
- openConversationInfo();
+ openMasterAgentMemories();
break;
case 6:
+ openConversationInfo();
+ break;
+ case 7:
reload(true);
break;
default:
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 daab70c..5661cb6 100644
--- a/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java
+++ b/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java
@@ -19,6 +19,8 @@ public final class WechatSurfaceMapper {
);
private static final List ROOT_ME_MENU_ITEMS = Arrays.asList(
+ new MeMenuItem("master_agent_prompt", "主 Agent 提示词 / 记忆", "配置全局主提示词、当前主提示词和用户记忆"),
+ new MeMenuItem("master_agent_evolution", "主 Agent 自动进化", "查看进化信号、提案与自动采纳规则"),
new MeMenuItem("security", "账号与安全", "修改登录密码、设备安全与身份校验"),
new MeMenuItem("settings", "设置", "默认首页、提醒方式与危险操作确认"),
new MeMenuItem("ops", "运维与修复", "查看运维会话、修复回放与 standby 切换"),
diff --git a/android/app/src/test/java/com/hyzq/boss/BossApiClientDispatchPlansTest.java b/android/app/src/test/java/com/hyzq/boss/BossApiClientDispatchPlansTest.java
index c0b587e..d90ed56 100644
--- a/android/app/src/test/java/com/hyzq/boss/BossApiClientDispatchPlansTest.java
+++ b/android/app/src/test/java/com/hyzq/boss/BossApiClientDispatchPlansTest.java
@@ -210,6 +210,44 @@ public class BossApiClientDispatchPlansTest {
assertEquals("GET", connection.requestMethodValue);
}
+ @Test
+ public void getMasterAgentEvolutionUsesDashboardEndpoint() throws Exception {
+ RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/master-agent/evolution"));
+ RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
+
+ BossApiClient.ApiResponse response = apiClient.getMasterAgentEvolution();
+
+ assertEquals(200, response.statusCode);
+ assertEquals("/api/v1/master-agent/evolution", apiClient.lastPath);
+ assertEquals("GET", connection.requestMethodValue);
+ }
+
+ @Test
+ public void updateMasterAgentEvolutionModeWritesModePayload() throws Exception {
+ RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/master-agent/evolution/config"));
+ RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
+
+ BossApiClient.ApiResponse response = apiClient.updateMasterAgentEvolutionMode("controlled");
+
+ assertEquals(200, response.statusCode);
+ assertEquals("/api/v1/master-agent/evolution/config", apiClient.lastPath);
+ assertEquals("POST", connection.requestMethodValue);
+ assertEquals("{\"mode\":\"controlled\"}", connection.requestBody());
+ }
+
+ @Test
+ public void approveMasterAgentEvolutionProposalUsesProposalEndpoint() throws Exception {
+ RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/master-agent/evolution/proposals/proposal-1/approve"));
+ RecordingBossApiClient apiClient = new RecordingBossApiClient(connection);
+
+ BossApiClient.ApiResponse response = apiClient.approveMasterAgentEvolutionProposal("proposal-1");
+
+ assertEquals(200, response.statusCode);
+ assertEquals("/api/v1/master-agent/evolution/proposals/proposal-1/approve", apiClient.lastPath);
+ assertEquals("POST", connection.requestMethodValue);
+ assertEquals("{}", connection.requestBody());
+ }
+
@Test
public void createMasterAgentMemoryWritesStructuredPayload() throws Exception {
RecordingConnection connection = new RecordingConnection(new URL("https://boss.hyzq.net/api/v1/projects/master-agent/memories"));
diff --git a/android/app/src/test/java/com/hyzq/boss/BossUiRootSurfaceTest.java b/android/app/src/test/java/com/hyzq/boss/BossUiRootSurfaceTest.java
index ee32960..ff171c5 100644
--- a/android/app/src/test/java/com/hyzq/boss/BossUiRootSurfaceTest.java
+++ b/android/app/src/test/java/com/hyzq/boss/BossUiRootSurfaceTest.java
@@ -40,7 +40,7 @@ public class BossUiRootSurfaceTest {
ReflectionHelpers.callInstanceMethod(activity, "renderMeRoot");
LinearLayout content = ReflectionHelpers.getField(activity, "screenContent");
- assertEquals("我的页应是资料头 + 6 条菜单", 7, content.getChildCount());
+ assertEquals("我的页应是资料头 + 8 条菜单", 9, content.getChildCount());
View header = content.getChildAt(0);
assertEquals("资料头不应保留浮层卡片感", 0f, header.getElevation(), 0.01f);
@@ -49,6 +49,8 @@ public class BossUiRootSurfaceTest {
assertTrue(viewTreeContainsText(header, "最高管理员"));
assertTrue(viewTreeContainsText(header, "主控账号已启用安全保护"));
+ assertTrue(viewTreeContainsText(content, "主 Agent 提示词 / 记忆"));
+ assertTrue(viewTreeContainsText(content, "主 Agent 自动进化"));
assertTrue(viewTreeContainsText(content, "账号与安全"));
assertTrue(viewTreeContainsText(content, "设置"));
assertTrue(viewTreeContainsText(content, "运维与修复"));
diff --git a/android/app/src/test/java/com/hyzq/boss/MasterAgentEvolutionActivityTest.java b/android/app/src/test/java/com/hyzq/boss/MasterAgentEvolutionActivityTest.java
new file mode 100644
index 0000000..83fa0de
--- /dev/null
+++ b/android/app/src/test/java/com/hyzq/boss/MasterAgentEvolutionActivityTest.java
@@ -0,0 +1,126 @@
+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.JSONArray;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = 34)
+public class MasterAgentEvolutionActivityTest {
+ @Test
+ public void renderDashboardShowsModePendingProposalSignalAndRule() throws Exception {
+ TestMasterAgentEvolutionActivity activity = Robolectric
+ .buildActivity(TestMasterAgentEvolutionActivity.class, new Intent())
+ .setup()
+ .get();
+
+ JSONObject payload = new JSONObject()
+ .put("config", new JSONObject()
+ .put("mode", "autonomous")
+ .put("autoApplyLowRiskRules", true))
+ .put("signals", new JSONArray()
+ .put(new JSONObject()
+ .put("signalId", "signal-1")
+ .put("kind", "repeated_question")
+ .put("requestText", "当前主节点在线吗")
+ .put("createdAt", "2026-04-16T12:00:00+08:00")))
+ .put("proposals", new JSONArray()
+ .put(new JSONObject()
+ .put("proposalId", "proposal-1")
+ .put("status", "pending_review")
+ .put("proposalType", "fast_path_rule")
+ .put("riskLevel", "low")
+ .put("createdAt", "2026-04-16T12:01:00+08:00")
+ .put("title", "新增 Fast Path")
+ .put("summary", "把状态查询加入本地直答")))
+ .put("rules", new JSONArray()
+ .put(new JSONObject()
+ .put("ruleId", "rule-1")
+ .put("ruleType", "routing_preference_patch")
+ .put("sourceProposalId", "proposal-2")
+ .put("createdAt", "2026-04-16T12:02:00+08:00")));
+
+ ReflectionHelpers.callInstanceMethod(
+ activity,
+ "renderDashboard",
+ ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
+ );
+
+ View content = activity.findViewById(R.id.screen_content);
+ assertTrue(viewTreeContainsText(content, "完全自我进化"));
+ assertTrue(viewTreeContainsText(content, "待处理提案"));
+ assertTrue(viewTreeContainsText(content, "新增 Fast Path"));
+ assertTrue(viewTreeContainsText(content, "repeated_question"));
+ assertTrue(viewTreeContainsText(content, "routing_preference_patch"));
+ }
+
+ @Test
+ public void matchingRealtimeEventTriggersReload() throws Exception {
+ TestMasterAgentEvolutionActivity activity = Robolectric
+ .buildActivity(TestMasterAgentEvolutionActivity.class, new Intent())
+ .setup()
+ .resume()
+ .get();
+ activity.reloadEnabled = true;
+ activity.reloadCount = 0;
+
+ ReflectionHelpers.callInstanceMethod(
+ activity,
+ "handleRealtimeEvent",
+ ReflectionHelpers.ClassParameter.from(
+ BossRealtimeEvent.class,
+ new BossRealtimeEvent("master_agent.settings.updated", new JSONObject())
+ )
+ );
+ Shadows.shadowOf(activity.getMainLooper()).idle();
+
+ assertEquals(1, activity.reloadCount);
+ }
+
+ 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;
+ }
+
+ public static class TestMasterAgentEvolutionActivity extends MasterAgentEvolutionActivity {
+ private boolean reloadEnabled;
+ private int reloadCount;
+
+ @Override
+ protected void reload() {
+ if (!reloadEnabled) {
+ return;
+ }
+ reloadCount += 1;
+ setRefreshing(false);
+ }
+ }
+}
diff --git a/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityMasterAgentMenuTest.java b/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityMasterAgentMenuTest.java
index b6687a4..6c2543b 100644
--- a/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityMasterAgentMenuTest.java
+++ b/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityMasterAgentMenuTest.java
@@ -45,10 +45,11 @@ public class ProjectDetailActivityMasterAgentMenuTest {
assertMenuItem(listView, 0, "模型");
assertMenuItem(listView, 1, "推理强度");
assertMenuItem(listView, 2, "全局接管");
- assertMenuItem(listView, 3, "提示词");
- assertMenuItem(listView, 4, "记忆");
- assertMenuItem(listView, 5, "会话信息");
- assertMenuItem(listView, 6, "刷新");
+ assertMenuItem(listView, 3, "自动进化");
+ assertMenuItem(listView, 4, "提示词");
+ assertMenuItem(listView, 5, "记忆");
+ assertMenuItem(listView, 6, "会话信息");
+ assertMenuItem(listView, 7, "刷新");
}
@Test
diff --git a/android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperMeMenuTest.java b/android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperMeMenuTest.java
new file mode 100644
index 0000000..78f026c
--- /dev/null
+++ b/android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperMeMenuTest.java
@@ -0,0 +1,17 @@
+package com.hyzq.boss;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class WechatSurfaceMapperMeMenuTest {
+ @Test
+ public void rootMeMenuIncludesMasterAgentEvolutionEntryAfterPromptMemory() {
+ WechatSurfaceMapper.MeMenuItem[] items = WechatSurfaceMapper.rootMeMenuItems();
+
+ assertEquals("master_agent_prompt", items[0].key);
+ assertEquals("主 Agent 提示词 / 记忆", items[0].title);
+ assertEquals("master_agent_evolution", items[1].key);
+ assertEquals("主 Agent 自动进化", items[1].title);
+ }
+}
diff --git a/android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java b/android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java
index 01e451a..df2a6d6 100644
--- a/android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java
+++ b/android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java
@@ -192,7 +192,7 @@ public class WechatSurfaceMapperTest {
@Test
public void rootMeMenuTitles_matchLegacyWechatMenuWithOpsEntry() throws Exception {
assertArrayEquals(
- new String[]{"账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
+ new String[]{"主 Agent 提示词 / 记忆", "主 Agent 自动进化", "账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
WechatSurfaceMapper.rootMeMenuTitles()
);
}
@@ -208,7 +208,7 @@ public class WechatSurfaceMapperTest {
@Test
public void mainPage_keepsOpsEntryInStableWechatMenuOrder() throws Exception {
assertArrayEquals(
- new String[]{"账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
+ new String[]{"主 Agent 提示词 / 记忆", "主 Agent 自动进化", "账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
WechatSurfaceMapper.rootMeMenuTitles()
);
}
@@ -380,15 +380,18 @@ public class WechatSurfaceMapperTest {
public void meMenuItems_useStableKeysInsteadOfDisplayTitlesForRouting() throws Exception {
WechatSurfaceMapper.MeMenuItem[] items = WechatSurfaceMapper.rootMeMenuItems();
- assertEquals(6, items.length);
- assertEquals("security", items[0].key);
- assertEquals("账号与安全", items[0].title);
- assertEquals("settings", items[1].key);
- assertEquals("ops", items[2].key);
- assertEquals("运维与修复", items[2].title);
- assertEquals("ai_accounts", items[3].key);
- assertEquals("skills", items[4].key);
- assertEquals("about", items[5].key);
+ assertEquals(8, items.length);
+ assertEquals("master_agent_prompt", items[0].key);
+ assertEquals("主 Agent 提示词 / 记忆", items[0].title);
+ assertEquals("master_agent_evolution", items[1].key);
+ assertEquals("主 Agent 自动进化", items[1].title);
+ assertEquals("security", items[2].key);
+ assertEquals("settings", items[3].key);
+ assertEquals("ops", items[4].key);
+ assertEquals("运维与修复", items[4].title);
+ assertEquals("ai_accounts", items[5].key);
+ assertEquals("skills", items[6].key);
+ assertEquals("about", items[7].key);
}
@Test
diff --git a/docs/architecture/ai_handoff_index_cn.md b/docs/architecture/ai_handoff_index_cn.md
index 9236ab8..7bc734d 100644
--- a/docs/architecture/ai_handoff_index_cn.md
+++ b/docs/architecture/ai_handoff_index_cn.md
@@ -54,6 +54,7 @@
- `android/app/src/main/java/com/hyzq/boss/MainActivity.java`:原生入口 Activity
- `android/app/src/main/java/com/hyzq/boss/BossApiClient.java`:原生 API 客户端
- `android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java`:原生聊天优先项目页,只保留目标/版本轻入口
+- `android/app/src/main/java/com/hyzq/boss/MasterAgentEvolutionActivity.java`:原生主 Agent 自动进化页,可查看信号/提案/规则并切换模式
- `android/app/src/main/java/com/hyzq/boss/ConversationInfoActivity.java`:原生微信式会话信息页,支持线程改名和发起群聊
- `android/app/src/main/java/com/hyzq/boss/GroupInfoActivity.java`:原生群资料页,支持群名修改与成员查看
- `android/app/src/main/java/com/hyzq/boss/GroupCreateActivity.java`:原生独立群聊创建页
@@ -156,6 +157,7 @@
- `我的` 根页当前保留 `账号与安全 / 设置 / 运维与修复 / AI 账号 / 技能 / 关于`
- `我的 > 主 Agent 提示词 / 记忆` 当前可编辑管理员全局主提示词、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 项目记忆
- `我的 > 主 Agent 自动进化` 当前可查看进化信号、待审批提案、已生效规则,并切换 `controlled / autonomous`
+- 原生 Android 也已接上 `我的 > 主 Agent 自动进化` 与 `主 Agent 会话右上角 ... > 自动进化` 两个入口
- `我的 > AI 账号` 必须可查看和切换 `主 GPT / 备用 GPT / API 容灾`
- `我的 > 技能` 必须按绑定设备展示 Skill,并支持一键复制调用语句
- `设备` 页当前只允许出现生产设备,旧演示脏数据不能回流到正式视图
diff --git a/docs/architecture/api_and_service_inventory_cn.md b/docs/architecture/api_and_service_inventory_cn.md
index 3b59a56..9ee1ba7 100644
--- a/docs/architecture/api_and_service_inventory_cn.md
+++ b/docs/architecture/api_and_service_inventory_cn.md
@@ -27,6 +27,7 @@
- 当前原生活动页:
- `MainActivity`
- `ProjectDetailActivity`
+ - `MasterAgentEvolutionActivity`
- `ConversationInfoActivity`
- `ThreadStatusActivity`
- `GroupInfoActivity`
@@ -74,7 +75,7 @@
- 保留版本与 OTA 操作
- 当前已补上 OTA 下载进度、失败重试、安装授权提示和返回关于页后的本地状态恢复
- 当前 `我的` 根页:
- - 保留 `账号与安全 / 设置 / 运维与修复 / AI 账号 / 技能 / 关于`
+ - 保留 `主 Agent 提示词 / 记忆 / 主 Agent 自动进化 / 账号与安全 / 设置 / 运维与修复 / AI 账号 / 技能 / 关于`
- `运维与修复` 直接进入 `OpsCenterActivity`
- 当前 `OpenAiOnboardingActivity`:
- 会先自动打开 `OpenAI Platform` 登录页
@@ -149,7 +150,7 @@
- `GET /me/settings`
- `GET /me/skills`
- `GET /me/master-agent`
-- `GET /me/master-agent`
+- `GET /me/master-agent/evolution`
## 3. Web API 路由
diff --git a/docs/architecture/current_runtime_and_deploy_status_cn.md b/docs/architecture/current_runtime_and_deploy_status_cn.md
index f1ac030..c03c286 100644
--- a/docs/architecture/current_runtime_and_deploy_status_cn.md
+++ b/docs/architecture/current_runtime_and_deploy_status_cn.md
@@ -1,6 +1,6 @@
# Boss 当前运行与部署状态
-更新时间:`2026-04-03`
+更新时间:`2026-04-16`
## 1. 本地状态
@@ -136,6 +136,7 @@ cd /Users/kris/code/boss
- 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有预设不适用时才需要填写自定义模型
- 当前 `我的 > 主 Agent 提示词 / 记忆` 页面已接通:管理员全局主提示词只读展示、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 跨项目项目记忆都可以在 Web 端查看和编辑;当前对话设置按登录账号隔离,管理员全局主提示词不可覆盖
- 当前 `我的 > 主 Agent 自动进化` 页面已接通:Web `/me/master-agent/evolution` 可查看最近信号、待审批提案和已生效规则,并允许管理员切换 `controlled / autonomous`、批准或拒绝提案
+- 当前原生 Android 也已接通 `主 Agent 自动进化`:`我的` 根页可直接进入,`master-agent` 会话右上角 `...` 菜单也可直达;页面支持查看最近信号、待审批提案、已生效规则,并可直接切换 `controlled / autonomous` 与批准/拒绝提案
- 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新`
- 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果存在新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口
- 当前如果主控身份还是 `Master Codex Node`,但该节点离线或执行立即失败,主 Agent 会优先尝试已配置的 `OpenAI API / 阿里百炼 Qwen` 备用账号,不再把失败日志直接原样回给用户