From 7cc33d391b9971681d7002fa6f7f038db4511cb5 Mon Sep 17 00:00:00 2001 From: kris Date: Sun, 5 Apr 2026 08:52:21 +0800 Subject: [PATCH] feat: move global takeover into master-agent menu --- android/app/src/main/AndroidManifest.xml | 1 + .../hyzq/boss/MasterAgentPromptActivity.java | 29 ---- .../boss/MasterAgentTakeoverActivity.java | 129 ++++++++++++++++++ .../com/hyzq/boss/ProjectDetailActivity.java | 31 ++++- .../boss/MasterAgentPromptActivityTest.java | 15 +- .../boss/MasterAgentTakeoverActivityTest.java | 65 +++++++++ ...jectDetailActivityMasterAgentMenuTest.java | 9 +- 7 files changed, 228 insertions(+), 51 deletions(-) create mode 100644 android/app/src/main/java/com/hyzq/boss/MasterAgentTakeoverActivity.java create mode 100644 android/app/src/test/java/com/hyzq/boss/MasterAgentTakeoverActivityTest.java diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2558140..ea6bbfe 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -52,6 +52,7 @@ + diff --git a/android/app/src/main/java/com/hyzq/boss/MasterAgentPromptActivity.java b/android/app/src/main/java/com/hyzq/boss/MasterAgentPromptActivity.java index 30a4c7f..473eb6d 100644 --- a/android/app/src/main/java/com/hyzq/boss/MasterAgentPromptActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/MasterAgentPromptActivity.java @@ -12,7 +12,6 @@ import android.widget.Spinner; import android.widget.TextView; import androidx.annotation.Nullable; -import androidx.appcompat.widget.SwitchCompat; import org.json.JSONObject; @@ -33,14 +32,12 @@ public class MasterAgentPromptActivity extends BossScreenActivity { private @Nullable String userPromptText; private @Nullable String projectPromptOverrideText; private @Nullable String backendOverrideText; - private boolean globalTakeoverEnabled; private boolean clawSelectable; private @Nullable String clawReasonLabel; private final List backendOverrideValues = new ArrayList<>(); private EditText userPromptInput; private EditText projectPromptInput; private Spinner backendSpinner; - private SwitchCompat globalTakeoverSwitch; private TextView previewTextView; @Override @@ -94,7 +91,6 @@ public class MasterAgentPromptActivity extends BossScreenActivity { projectControls == null ? "" : projectControls.optString("promptOverride", "") ); backendOverrideText = projectControls == null ? "" : projectControls.optString("backendOverride", ""); - globalTakeoverEnabled = projectControls != null && projectControls.optBoolean("globalTakeoverEnabled", false); clawSelectable = clawAvailability != null && clawAvailability.optBoolean("selectable", false); clawReasonLabel = clawAvailability == null ? "" : clawAvailability.optString("reasonLabel", ""); @@ -164,16 +160,6 @@ public class MasterAgentPromptActivity extends BossScreenActivity { backendSpinner )); - globalTakeoverSwitch = new SwitchCompat(this); - globalTakeoverSwitch.setText("开启"); - globalTakeoverSwitch.setChecked(globalTakeoverEnabled); - globalTakeoverSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> refreshPreview()); - appendContent(BossUi.buildFormCell( - this, - "全局主 Agent 协同接管", - "为线程会话默认开启主 Agent 协同推进。不会抢走你直接控制线程开发的能力。", - globalTakeoverSwitch - )); if (!clawSelectable) { appendContent(BossUi.buildSoftPanel( this, @@ -250,12 +236,6 @@ public class MasterAgentPromptActivity extends BossScreenActivity { } else if (TextUtils.equals(backendOverrideText, "claw-runtime") && !clawSelectable) { builder.append("【执行后端】\n默认(Claw Runtime 当前不可用,运行时会自动回退)\n\n"); } - boolean globalTakeover = globalTakeoverSwitch != null - ? globalTakeoverSwitch.isChecked() - : globalTakeoverEnabled; - builder.append("【全局主 Agent 协同接管】\n") - .append(globalTakeover ? "已开启" : "已关闭") - .append("(不会抢走你直接控制线程开发)\n\n"); if (builder.length() == 0) { return "当前没有任何提示词内容。"; } @@ -272,7 +252,6 @@ public class MasterAgentPromptActivity extends BossScreenActivity { final String backendOverride = backendSpinner == null ? "" : backendOverrideValues.get(backendSpinner.getSelectedItemPosition()); - final boolean globalTakeover = globalTakeoverSwitch != null && globalTakeoverSwitch.isChecked(); setRefreshing(true); executor.execute(() -> { try { @@ -284,14 +263,6 @@ public class MasterAgentPromptActivity extends BossScreenActivity { if (!response.ok()) { throw new IllegalStateException(response.message()); } - BossApiClient.ApiResponse controlsResponse = apiClient.updateProjectTakeoverSettings( - projectId, - null, - globalTakeover - ); - if (!controlsResponse.ok()) { - throw new IllegalStateException(controlsResponse.message()); - } runOnUiThread(() -> { showMessage("提示词已保存"); setResult(RESULT_OK); diff --git a/android/app/src/main/java/com/hyzq/boss/MasterAgentTakeoverActivity.java b/android/app/src/main/java/com/hyzq/boss/MasterAgentTakeoverActivity.java new file mode 100644 index 0000000..20c64ba --- /dev/null +++ b/android/app/src/main/java/com/hyzq/boss/MasterAgentTakeoverActivity.java @@ -0,0 +1,129 @@ +package com.hyzq.boss; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.appcompat.widget.SwitchCompat; + +import org.json.JSONObject; + +public class MasterAgentTakeoverActivity extends BossScreenActivity { + public static final String EXTRA_PROJECT_ID = "project_id"; + public static final String EXTRA_PROJECT_NAME = "project_name"; + + private String projectId; + private String projectName; + private boolean contentLoaded; + private boolean globalTakeoverEnabled; + private SwitchCompat globalTakeoverSwitch; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + projectId = getIntent().getStringExtra(EXTRA_PROJECT_ID); + projectName = getIntent().getStringExtra(EXTRA_PROJECT_NAME); + configureScreen("全局接管", projectName == null ? "主 Agent 协同推进" : projectName); + setHeaderAction("保存", v -> saveTakeoverSettings()); + updateSaveAvailability(); + reload(); + } + + @Override + protected void reload() { + if (projectId == null || projectId.isEmpty()) { + replaceContent(BossUi.buildEmptyCard(this, "缺少 projectId。")); + setRefreshing(false); + contentLoaded = false; + updateSaveAvailability(); + return; + } + setRefreshing(true); + executor.execute(() -> { + try { + BossApiClient.ApiResponse response = apiClient.getProjectAgentControls(projectId); + if (!response.ok()) { + throw new IllegalStateException(response.message()); + } + runOnUiThread(() -> renderTakeoverSettings(response.json)); + } catch (Exception error) { + runOnUiThread(() -> { + setRefreshing(false); + contentLoaded = false; + updateSaveAvailability(); + replaceContent(BossUi.buildEmptyCard(this, "全局接管加载失败:" + error.getMessage())); + }); + } + }); + } + + private void renderTakeoverSettings(JSONObject payload) { + JSONObject controls = payload.optJSONObject("controls"); + globalTakeoverEnabled = controls != null && controls.optBoolean("globalTakeoverEnabled", false); + + replaceContent(); + appendContent(BossUi.buildSimpleProfileHeader( + this, + projectName == null ? "主 Agent" : projectName, + "全局主 Agent 协同推进", + "为线程会话默认开启协同推进,不会抢走你继续直接控制线程开发的能力。" + )); + + globalTakeoverSwitch = new SwitchCompat(this); + globalTakeoverSwitch.setText("开启"); + globalTakeoverSwitch.setChecked(globalTakeoverEnabled); + appendContent(BossUi.buildFormCell( + this, + "全局主 Agent 协同接管", + "开启后,线程会话默认跟随全局协同推进;线程会话仍可单独覆盖。", + globalTakeoverSwitch + )); + appendContent(BossUi.buildSoftPanel( + this, + "说明", + "主 Agent 会理解项目状态、给建议、补调度方案,但不会因为介入就抢走你继续直接控制线程开发的能力。", + "线程级开关优先于这里的全局默认。" + )); + + contentLoaded = true; + updateSaveAvailability(); + setRefreshing(false); + } + + private void saveTakeoverSettings() { + if (!contentLoaded) { + showMessage("全局接管尚未加载完成,请先刷新成功后再保存。"); + return; + } + final boolean enabled = globalTakeoverSwitch != null && globalTakeoverSwitch.isChecked(); + setRefreshing(true); + executor.execute(() -> { + try { + BossApiClient.ApiResponse response = apiClient.updateProjectTakeoverSettings( + projectId, + null, + enabled + ); + if (!response.ok()) { + throw new IllegalStateException(response.message()); + } + runOnUiThread(() -> { + showMessage(enabled ? "已开启全局主 Agent 协同接管" : "已关闭全局主 Agent 协同接管"); + setResult(RESULT_OK); + finish(); + }); + } catch (Exception error) { + runOnUiThread(() -> { + setRefreshing(false); + showMessage("保存失败:" + error.getMessage()); + }); + } + }); + } + + private void updateSaveAvailability() { + if (headerActionButton != null) { + headerActionButton.setEnabled(contentLoaded); + headerActionButton.setAlpha(contentLoaded ? 1f : 0.45f); + } + } +} 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 d598c20..3c42400 100644 --- a/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java @@ -77,6 +77,7 @@ public class ProjectDetailActivity extends BossScreenActivity { private ProjectChatUiState.SelectionState selectionState = ProjectChatUiState.emptySelection(); private ActivityResultLauncher conversationInfoLauncher; private ActivityResultLauncher masterAgentPromptLauncher; + private ActivityResultLauncher masterAgentTakeoverLauncher; private ActivityResultLauncher masterAgentMemoryLauncher; private ActivityResultLauncher forwardTargetLauncher; private ActivityResultLauncher imagePickerLauncher; @@ -181,6 +182,14 @@ public class ProjectDetailActivity extends BossScreenActivity { } } ); + masterAgentTakeoverLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) { + reload(true); + } + } + ); masterAgentMemoryLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { @@ -790,12 +799,23 @@ public class ProjectDetailActivity extends BossScreenActivity { masterAgentMemoryLauncher.launch(intent); } + private void openMasterAgentTakeoverSettings() { + if (projectId == null || projectId.isEmpty()) { + showMessage("缺少 projectId"); + return; + } + Intent intent = new Intent(this, MasterAgentTakeoverActivity.class); + intent.putExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_ID, projectId); + intent.putExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_NAME, initialProjectName); + masterAgentTakeoverLauncher.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(); @@ -804,15 +824,18 @@ public class ProjectDetailActivity extends BossScreenActivity { showMasterAgentReasoningPicker(); break; case 2: - openMasterAgentPromptProfile(); + openMasterAgentTakeoverSettings(); break; case 3: - openMasterAgentMemories(); + openMasterAgentPromptProfile(); break; case 4: - openConversationInfo(); + openMasterAgentMemories(); break; case 5: + openConversationInfo(); + break; + case 6: reload(true); break; default: diff --git a/android/app/src/test/java/com/hyzq/boss/MasterAgentPromptActivityTest.java b/android/app/src/test/java/com/hyzq/boss/MasterAgentPromptActivityTest.java index 1837e17..162df79 100644 --- a/android/app/src/test/java/com/hyzq/boss/MasterAgentPromptActivityTest.java +++ b/android/app/src/test/java/com/hyzq/boss/MasterAgentPromptActivityTest.java @@ -54,8 +54,7 @@ public class MasterAgentPromptActivityTest { .put("userPrompt", new JSONObject().put("content", "用户私有主提示词")) .put("projectControls", new JSONObject() .put("promptOverride", "当前对话提示词") - .put("backendOverride", "claw-runtime") - .put("globalTakeoverEnabled", true)); + .put("backendOverride", "claw-runtime")); ReflectionHelpers.callInstanceMethod( activity, @@ -69,7 +68,6 @@ public class MasterAgentPromptActivityTest { assertTrue(viewTreeContainsText(content, "用户私有主提示词")); assertTrue(viewTreeContainsText(content, "当前对话提示词")); assertTrue(viewTreeContainsText(content, "执行后端")); - assertTrue(viewTreeContainsText(content, "全局主 Agent 协同接管")); assertTrue(viewTreeContainsText(content, "合成预览")); } @@ -286,17 +284,6 @@ public class MasterAgentPromptActivityTest { // JVM 单测不需要落 Android 侧身份缓存。 } - @Override - public ApiResponse updateProjectTakeoverSettings(String projectId, Boolean takeoverEnabled, Boolean globalTakeoverEnabled) { - try { - return new ApiResponse( - 200, - new JSONObject().put("ok", true) - ); - } catch (Exception error) { - throw new IllegalStateException(error); - } - } } private static final class InMemorySharedPreferences implements android.content.SharedPreferences { diff --git a/android/app/src/test/java/com/hyzq/boss/MasterAgentTakeoverActivityTest.java b/android/app/src/test/java/com/hyzq/boss/MasterAgentTakeoverActivityTest.java new file mode 100644 index 0000000..92aaa07 --- /dev/null +++ b/android/app/src/test/java/com/hyzq/boss/MasterAgentTakeoverActivityTest.java @@ -0,0 +1,65 @@ +package com.hyzq.boss; + +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.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = 34) +public class MasterAgentTakeoverActivityTest { + @Test + public void renderTakeoverSettingsShowsStandaloneGlobalTakeoverSection() throws Exception { + MasterAgentTakeoverActivity activity = Robolectric + .buildActivity( + MasterAgentTakeoverActivity.class, + new Intent() + .putExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_ID, "master-agent") + .putExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_NAME, "主 Agent") + ) + .setup() + .get(); + + JSONObject payload = new JSONObject() + .put("controls", new JSONObject().put("globalTakeoverEnabled", true)); + + ReflectionHelpers.callInstanceMethod( + activity, + "renderTakeoverSettings", + ReflectionHelpers.ClassParameter.from(JSONObject.class, payload) + ); + + View content = activity.findViewById(R.id.screen_content); + assertTrue(viewTreeContainsText(content, "全局主 Agent 协同接管")); + assertTrue(viewTreeContainsText(content, "线程会话默认跟随全局协同推进")); + } + + 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; + } +} 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 03ebd95..b6687a4 100644 --- a/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityMasterAgentMenuTest.java +++ b/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityMasterAgentMenuTest.java @@ -44,10 +44,11 @@ public class ProjectDetailActivityMasterAgentMenuTest { assertMenuItem(listView, 0, "模型"); assertMenuItem(listView, 1, "推理强度"); - assertMenuItem(listView, 2, "提示词"); - assertMenuItem(listView, 3, "记忆"); - assertMenuItem(listView, 4, "会话信息"); - assertMenuItem(listView, 5, "刷新"); + assertMenuItem(listView, 2, "全局接管"); + assertMenuItem(listView, 3, "提示词"); + assertMenuItem(listView, 4, "记忆"); + assertMenuItem(listView, 5, "会话信息"); + assertMenuItem(listView, 6, "刷新"); } @Test