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