feat: finish master-agent prompt and memory runtime
This commit is contained in:
@@ -20,7 +20,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
public static final String EXTRA_PROJECT_NAME = "project_name";
|
||||
|
||||
private static final String[] MEMORY_SCOPE_VALUES = {"global", "project"};
|
||||
private static final String[] MEMORY_SCOPE_LABELS = {"我的通用记忆", "当前项目记忆"};
|
||||
private static final String[] MEMORY_SCOPE_LABELS = {"我的通用记忆", "项目记忆"};
|
||||
private static final String[] MEMORY_TYPE_VALUES = {
|
||||
"user_preference",
|
||||
"project_progress",
|
||||
@@ -99,13 +99,13 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
this,
|
||||
projectName == null ? "主 Agent" : projectName,
|
||||
"自动沉淀 / 手动维护",
|
||||
"项目记忆默认挂到当前项目,通用记忆属于当前用户。"
|
||||
"项目记忆会绑定到真实项目,通用记忆属于当前用户。"
|
||||
));
|
||||
appendContent(BossUi.buildSoftPanel(
|
||||
this,
|
||||
"记忆说明",
|
||||
"主 Agent 会自动沉淀长期有用的信息。你也可以在这里手动新增、编辑或删除。",
|
||||
"底层是结构化存储,前台展示为轻量卡片。"
|
||||
"底层是结构化存储,项目记忆会显示真实 projectId。"
|
||||
));
|
||||
|
||||
renderSection(
|
||||
@@ -114,9 +114,9 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
"当前没有通用记忆。"
|
||||
);
|
||||
renderSection(
|
||||
"当前项目记忆",
|
||||
"项目记忆",
|
||||
projectMemoryItems,
|
||||
"当前项目还没有沉淀记忆。"
|
||||
"当前还没有项目记忆。"
|
||||
);
|
||||
|
||||
contentLoaded = true;
|
||||
@@ -169,7 +169,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
if (!TextUtils.isEmpty(tags)) {
|
||||
meta = TextUtils.isEmpty(meta) ? tags : meta + " · " + tags;
|
||||
}
|
||||
String badge = "project".equals(scope) ? "当前项目" : "全局";
|
||||
String badge = "project".equals(scope) ? "项目" : "全局";
|
||||
String subtitle = memoryTypeLabel(type) + (TextUtils.isEmpty(content) ? "" : " · " + content);
|
||||
return BossUi.buildWechatMenuRow(
|
||||
this,
|
||||
@@ -203,21 +203,25 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
|
||||
final EditText titleInput = BossUi.buildInput(this, "记忆标题", false);
|
||||
final EditText contentInput = BossUi.buildInput(this, "记忆内容", true);
|
||||
final EditText projectIdInput = BossUi.buildInput(this, "例如:boss-console", false);
|
||||
final EditText tagsInput = BossUi.buildInput(this, "标签,逗号分隔", false);
|
||||
contentInput.setMinLines(6);
|
||||
|
||||
if (memory != null) {
|
||||
titleInput.setText(memory.optString("title", ""));
|
||||
contentInput.setText(memory.optString("content", ""));
|
||||
projectIdInput.setText(memory.optString("projectId", ""));
|
||||
tagsInput.setText(joinTags(memory.optJSONArray("tags")));
|
||||
scopeSpinner.setSelection("project".equals(memory.optString("scope", "global")) ? 1 : 0);
|
||||
typeSpinner.setSelection(memoryTypeIndex(memory.optString("memoryType", "user_preference")));
|
||||
} else {
|
||||
scopeSpinner.setSelection(0);
|
||||
typeSpinner.setSelection(0);
|
||||
projectIdInput.setText(projectId == null || "master-agent".equals(projectId) ? "" : projectId);
|
||||
}
|
||||
|
||||
form.addView(BossUi.buildFormCell(this, "作用域", "决定是用户通用记忆还是当前项目记忆。", scopeSpinner));
|
||||
form.addView(BossUi.buildFormCell(this, "projectId", "项目记忆需要绑定到真实项目;通用记忆可以留空。", projectIdInput));
|
||||
form.addView(BossUi.buildFormCell(this, "标题", "一句话说明这条记忆。", titleInput));
|
||||
form.addView(BossUi.buildFormCell(this, "内容", "主 Agent 读取时会使用这段内容。", contentInput));
|
||||
form.addView(BossUi.buildFormCell(this, "类型", "帮助主 Agent 决定优先级与使用场景。", typeSpinner));
|
||||
@@ -230,6 +234,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
.setPositiveButton("保存", (dialog, which) -> saveMemory(
|
||||
memory,
|
||||
MEMORY_SCOPE_VALUES[scopeSpinner.getSelectedItemPosition()],
|
||||
projectIdInput.getText() == null ? "" : projectIdInput.getText().toString(),
|
||||
titleInput.getText() == null ? "" : titleInput.getText().toString(),
|
||||
contentInput.getText() == null ? "" : contentInput.getText().toString(),
|
||||
MEMORY_TYPE_VALUES[typeSpinner.getSelectedItemPosition()],
|
||||
@@ -260,6 +265,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
private void saveMemory(
|
||||
@Nullable JSONObject existingMemory,
|
||||
String scope,
|
||||
String targetProjectId,
|
||||
String title,
|
||||
String content,
|
||||
String memoryType,
|
||||
@@ -281,6 +287,11 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
}
|
||||
final JSONArray tags = parseTags(tagsText);
|
||||
final boolean projectScope = "project".equals(scope);
|
||||
final String normalizedProjectId = targetProjectId == null ? "" : targetProjectId.trim();
|
||||
if (projectScope && normalizedProjectId.isEmpty()) {
|
||||
showMessage("项目记忆必须填写真实 projectId");
|
||||
return;
|
||||
}
|
||||
final String memoryId = existingMemory == null ? "" : existingMemory.optString("memoryId", "");
|
||||
setRefreshing(true);
|
||||
executor.execute(() -> {
|
||||
@@ -288,7 +299,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("scope", scope);
|
||||
if (projectScope) {
|
||||
payload.put("projectId", projectId);
|
||||
payload.put("projectId", normalizedProjectId);
|
||||
}
|
||||
payload.put("title", normalizedTitle);
|
||||
payload.put("content", normalizedContent);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.hyzq.boss;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -24,6 +27,7 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
||||
private @Nullable String projectPromptOverrideText;
|
||||
private EditText userPromptInput;
|
||||
private EditText projectPromptInput;
|
||||
private TextView previewTextView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -111,18 +115,50 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
||||
projectPromptInput
|
||||
));
|
||||
|
||||
appendContent(BossUi.buildSoftPanel(
|
||||
previewTextView = new TextView(this);
|
||||
previewTextView.setText(buildPreviewText());
|
||||
previewTextView.setTextSize(14);
|
||||
previewTextView.setLineSpacing(0f, 1.2f);
|
||||
previewTextView.setTextColor(getColor(R.color.boss_text_primary));
|
||||
previewTextView.setPadding(0, BossUi.dp(this, 8), 0, 0);
|
||||
|
||||
LinearLayout previewPanel = new LinearLayout(this);
|
||||
previewPanel.setOrientation(LinearLayout.VERTICAL);
|
||||
previewPanel.addView(previewTextView);
|
||||
appendContent(BossUi.buildFormCell(
|
||||
this,
|
||||
"合成预览",
|
||||
buildPreviewText(),
|
||||
"保存后会立即影响主 Agent 回复。"
|
||||
"主 Agent 实际执行时会先遵守管理员全局主提示词,再追加你的私有提示词和当前对话提示词。",
|
||||
previewPanel
|
||||
));
|
||||
|
||||
TextWatcher previewWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
refreshPreview();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
};
|
||||
userPromptInput.addTextChangedListener(previewWatcher);
|
||||
projectPromptInput.addTextChangedListener(previewWatcher);
|
||||
refreshPreview();
|
||||
|
||||
contentLoaded = true;
|
||||
updateSaveAvailability();
|
||||
setRefreshing(false);
|
||||
}
|
||||
|
||||
private void refreshPreview() {
|
||||
if (previewTextView != null) {
|
||||
previewTextView.setText(buildPreviewText());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildPreviewText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (!TextUtils.isEmpty(adminPromptText)) {
|
||||
|
||||
@@ -58,7 +58,7 @@ public class MasterAgentMemoryActivityTest {
|
||||
JSONObject projectMemory = new JSONObject()
|
||||
.put("memoryId", "mem-project")
|
||||
.put("scope", "project")
|
||||
.put("projectId", "master-agent")
|
||||
.put("projectId", "boss-console")
|
||||
.put("title", "项目进度")
|
||||
.put("content", "主 Agent 对话链已接通")
|
||||
.put("memoryType", "project_progress")
|
||||
@@ -76,7 +76,7 @@ public class MasterAgentMemoryActivityTest {
|
||||
|
||||
View content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content, "我的通用记忆"));
|
||||
assertTrue(viewTreeContainsText(content, "当前项目记忆"));
|
||||
assertTrue(viewTreeContainsText(content, "项目记忆"));
|
||||
assertTrue(viewTreeContainsText(content, "优先中文回复"));
|
||||
JSONObject memories = payload.getJSONObject("memories");
|
||||
JSONArray globalMemoryItems = (JSONArray) ReflectionHelpers.callInstanceMethod(
|
||||
@@ -125,6 +125,7 @@ public class MasterAgentMemoryActivityTest {
|
||||
"saveMemory",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, null),
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "project"),
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "boss-console"),
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "项目目标"),
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "把会话页收成微信式列表"),
|
||||
ReflectionHelpers.ClassParameter.from(String.class, "project_progress"),
|
||||
@@ -133,7 +134,7 @@ public class MasterAgentMemoryActivityTest {
|
||||
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle();
|
||||
|
||||
assertEquals(
|
||||
"{\"scope\":\"project\",\"projectId\":\"master-agent\",\"title\":\"项目目标\",\"content\":\"把会话页收成微信式列表\",\"memoryType\":\"project_progress\",\"tags\":[\"ui\",\"progress\"]}",
|
||||
"{\"scope\":\"project\",\"projectId\":\"boss-console\",\"title\":\"项目目标\",\"content\":\"把会话页收成微信式列表\",\"memoryType\":\"project_progress\",\"tags\":[\"ui\",\"progress\"]}",
|
||||
((ScriptedBossApiClient) ReflectionHelpers.getField(activity, "apiClient")).connection.requestBody()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,6 +113,39 @@ public class MasterAgentPromptActivityTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void previewUpdatesWhenEditableLayersChange() 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", "当前对话提示词"));
|
||||
|
||||
ReflectionHelpers.callInstanceMethod(
|
||||
activity,
|
||||
"renderPromptProfile",
|
||||
ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
|
||||
);
|
||||
|
||||
EditText userInput = ReflectionHelpers.getField(activity, "userPromptInput");
|
||||
EditText conversationInput = ReflectionHelpers.getField(activity, "projectPromptInput");
|
||||
userInput.setText("新的用户提示词");
|
||||
conversationInput.setText("新的当前对话提示词");
|
||||
|
||||
View content = activity.findViewById(R.id.screen_content);
|
||||
assertTrue(viewTreeContainsText(content, "新的用户提示词"));
|
||||
assertTrue(viewTreeContainsText(content, "新的当前对话提示词"));
|
||||
}
|
||||
|
||||
private static boolean viewTreeContainsText(View root, String expectedText) {
|
||||
if (root instanceof TextView) {
|
||||
CharSequence text = ((TextView) root).getText();
|
||||
|
||||
Reference in New Issue
Block a user