feat: gate claw runtime selection by availability

This commit is contained in:
kris
2026-04-03 02:11:41 +08:00
parent 6c999fb951
commit 8e2350e89d
19 changed files with 564 additions and 123 deletions

View File

@@ -15,9 +15,10 @@ import androidx.annotation.Nullable;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class MasterAgentPromptActivity extends BossScreenActivity {
private static final String[] BACKEND_OVERRIDE_VALUES = {"", "claw-runtime"};
private static final String[] BACKEND_OVERRIDE_LABELS = {"默认", "Claw Runtime"};
public static final String EXTRA_PROJECT_ID = "project_id";
public static final String EXTRA_PROJECT_NAME = "project_name";
@@ -31,6 +32,9 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
private @Nullable String userPromptText;
private @Nullable String projectPromptOverrideText;
private @Nullable String backendOverrideText;
private boolean clawSelectable;
private @Nullable String clawReasonLabel;
private final List<String> backendOverrideValues = new ArrayList<>();
private EditText userPromptInput;
private EditText projectPromptInput;
private Spinner backendSpinner;
@@ -79,6 +83,7 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
promptPolicy = payload.optJSONObject("promptPolicy");
userPrompt = payload.optJSONObject("userPrompt");
projectControls = payload.optJSONObject("projectControls");
JSONObject clawAvailability = payload.optJSONObject("clawAvailability");
adminPromptText = promptPolicy == null ? null : promptPolicy.optString("globalPrompt", "");
userPromptText = userPrompt == null ? "" : userPrompt.optString("content", "");
projectPromptOverrideText = payload.optString(
@@ -86,6 +91,8 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
projectControls == null ? "" : projectControls.optString("promptOverride", "")
);
backendOverrideText = projectControls == null ? "" : projectControls.optString("backendOverride", "");
clawSelectable = clawAvailability != null && clawAvailability.optBoolean("selectable", false);
clawReasonLabel = clawAvailability == null ? "" : clawAvailability.optString("reasonLabel", "");
replaceContent();
appendContent(BossUi.buildSimpleProfileHeader(
@@ -123,8 +130,17 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
projectPromptInput
));
backendOverrideValues.clear();
List<String> backendLabels = new ArrayList<>();
backendOverrideValues.add("");
backendLabels.add("默认");
if (clawSelectable) {
backendOverrideValues.add("claw-runtime");
backendLabels.add("Claw Runtime");
}
backendSpinner = new Spinner(this);
backendSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, BACKEND_OVERRIDE_LABELS));
backendSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, backendLabels));
backendSpinner.setSelection(indexOfBackendOverride(backendOverrideText));
backendSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
@@ -143,6 +159,16 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
"默认沿用 Boss 当前主链;需要时可显式切到 Claw Runtime。",
backendSpinner
));
if (!clawSelectable) {
appendContent(BossUi.buildSoftPanel(
this,
"Claw Runtime 当前不可用",
TextUtils.isEmpty(clawReasonLabel) ? "当前环境未满足 Claw Runtime 的启动条件。" : clawReasonLabel,
TextUtils.equals(backendOverrideText, "claw-runtime")
? "当前对话之前保存过 Claw Runtime运行时会自动回退到默认后端。"
: "恢复可用后,执行后端下拉框会重新出现 Claw Runtime。"
));
}
previewTextView = new TextView(this);
previewTextView.setText(buildPreviewText());
@@ -203,9 +229,11 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
}
String backendValue = backendSpinner == null
? (backendOverrideText == null ? "" : backendOverrideText)
: BACKEND_OVERRIDE_VALUES[backendSpinner.getSelectedItemPosition()];
: backendOverrideValues.get(backendSpinner.getSelectedItemPosition());
if (!TextUtils.isEmpty(backendValue)) {
builder.append("【执行后端】\n").append(backendValue).append("\n\n");
} else if (TextUtils.equals(backendOverrideText, "claw-runtime") && !clawSelectable) {
builder.append("【执行后端】\n默认Claw Runtime 当前不可用,运行时会自动回退)\n\n");
}
if (builder.length() == 0) {
return "当前没有任何提示词内容。";
@@ -222,7 +250,7 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
final String promptOverride = projectPromptInput == null ? "" : projectPromptInput.getText().toString();
final String backendOverride = backendSpinner == null
? ""
: BACKEND_OVERRIDE_VALUES[backendSpinner.getSelectedItemPosition()];
: backendOverrideValues.get(backendSpinner.getSelectedItemPosition());
setRefreshing(true);
executor.execute(() -> {
try {
@@ -259,8 +287,8 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
if (TextUtils.isEmpty(value)) {
return 0;
}
for (int index = 0; index < BACKEND_OVERRIDE_VALUES.length; index += 1) {
if (value.equals(BACKEND_OVERRIDE_VALUES[index])) {
for (int index = 0; index < backendOverrideValues.size(); index += 1) {
if (value.equals(backendOverrideValues.get(index))) {
return index;
}
}

View File

@@ -156,6 +156,43 @@ public class MasterAgentPromptActivityTest {
assertTrue(viewTreeContainsText(content, "新的当前对话提示词"));
}
@Test
public void renderPromptProfileShowsClawUnavailableHintWhenBackendCannotBeSelected() throws Exception {
TestMasterAgentPromptActivity activity = Robolectric
.buildActivity(
TestMasterAgentPromptActivity.class,
new Intent()
.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_ID, "master-agent")
.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME, "主 Agent")
)
.setup()
.get();
JSONObject payload = new JSONObject()
.put("promptPolicy", new JSONObject().put("globalPrompt", "全局主提示词"))
.put("userPrompt", new JSONObject().put("content", "用户私有主提示词"))
.put("projectControls", new JSONObject()
.put("promptOverride", "当前对话提示词")
.put("backendOverride", "claw-runtime"))
.put("clawAvailability", new JSONObject()
.put("status", "misconfigured")
.put("selectable", false)
.put("reason", "script_not_found")
.put("reasonLabel", "未检测到有效的 Claw 启动脚本,将自动回退到默认后端。"));
ReflectionHelpers.callInstanceMethod(
activity,
"renderPromptProfile",
ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
);
Spinner backendSpinner = ReflectionHelpers.getField(activity, "backendSpinner");
assertEquals(1, backendSpinner.getAdapter().getCount());
View content = activity.findViewById(R.id.screen_content);
assertTrue(viewTreeContainsText(content, "Claw Runtime 当前不可用"));
assertTrue(viewTreeContainsText(content, "未检测到有效的 Claw 启动脚本"));
}
private static boolean viewTreeContainsText(View root, String expectedText) {
if (root instanceof TextView) {
CharSequence text = ((TextView) root).getText();