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` 备用账号,不再把失败日志直接原样回给用户