feat: finish app-side master agent control surfaces
This commit is contained in:
@@ -106,7 +106,7 @@ Android APK:
|
|||||||
- Android 真机无线调试如果要尽量稳定,优先使用“同一局域网 + 初次 USB 启用后执行 `adb tcpip 5555` + `adb connect <phone-ip>:5555`”这条链路;它通常比只依赖系统“无线调试配对码”更稳
|
- Android 真机无线调试如果要尽量稳定,优先使用“同一局域网 + 初次 USB 启用后执行 `adb tcpip 5555` + `adb connect <phone-ip>:5555`”这条链路;它通常比只依赖系统“无线调试配对码”更稳
|
||||||
- Android 系统层面对“无线调试”没有真正的永久不掉线开关;重启手机、切 Wi‑Fi、切热点、ADB server 重启、USB 调试被重新切换后,都可能导致无线调试自动失效
|
- Android 系统层面对“无线调试”没有真正的永久不掉线开关;重启手机、切 Wi‑Fi、切热点、ADB server 重启、USB 调试被重新切换后,都可能导致无线调试自动失效
|
||||||
- 真机调试时建议固定同一 SSID、避免代理/VPN 改路、开发者选项里开启“保持唤醒”,并在需要长时间稳定调试时优先保留 USB 兜底;如果必须完全避免自动断开,不要只依赖无线调试
|
- 真机调试时建议固定同一 SSID、避免代理/VPN 改路、开发者选项里开启“保持唤醒”,并在需要长时间稳定调试时优先保留 USB 兜底;如果必须完全避免自动断开,不要只依赖无线调试
|
||||||
- 当前原生活动页已经覆盖:会话首页、项目详情、项目目标、版本记录、会话信息、群资料、发起群聊、消息转发、线程详情、设备详情、添加设备、账号与安全、设置、AI 账号、主 Agent 提示词 / 记忆、技能、运维中心、关于
|
- 当前原生活动页已经覆盖:会话首页、项目详情、项目目标、版本记录、会话信息、群资料、发起群聊、消息转发、线程详情、设备详情、添加设备、账号与安全、设置、AI 账号、主 Agent 提示词、主 Agent 记忆、全局接管、主 Agent 自动进化、技能、运维中心、关于
|
||||||
- 当前原生一级体验已回退到微信式交互:`会话 / 设备 / 我的` 固定底部 tab,会话首页是简单聊天列表,`主 Agent / 审计对话` 以普通置顶会话样式排在最前;项目详情页是聊天优先,只保留 `项目目标 / 版本记录` 两个轻入口
|
- 当前原生一级体验已回退到微信式交互:`会话 / 设备 / 我的` 固定底部 tab,会话首页是简单聊天列表,`主 Agent / 审计对话` 以普通置顶会话样式排在最前;项目详情页是聊天优先,只保留 `项目目标 / 版本记录` 两个轻入口
|
||||||
- 当前会话首页右上角已切回 `+` 入口:直接从首页发起独立群聊;设备页右上角仍是 `+添加`
|
- 当前会话首页右上角已切回 `+` 入口:直接从首页发起独立群聊;设备页右上角仍是 `+添加`
|
||||||
- 当前会话首页已升级成“项目聚合 + 线程下钻”的结构:如果某个 Codex 文件夹只导入了 1 个线程,会话列表直接显示这个线程;如果同一文件夹导入了多个线程,会话首页只显示该文件夹归档项,点进去再看这个项目下的全部线程
|
- 当前会话首页已升级成“项目聚合 + 线程下钻”的结构:如果某个 Codex 文件夹只导入了 1 个线程,会话列表直接显示这个线程;如果同一文件夹导入了多个线程,会话首页只显示该文件夹归档项,点进去再看这个项目下的全部线程
|
||||||
@@ -127,7 +127,7 @@ Android APK:
|
|||||||
- 当前 `AI 账号` 页顶部会显式展示“当前主控身份”,并提供 `校验主控 / 测试主 Agent 对话` 两个动作,切换主控后可直接验证聊天通路
|
- 当前 `AI 账号` 页顶部会显式展示“当前主控身份”,并提供 `校验主控 / 测试主 Agent 对话` 两个动作,切换主控后可直接验证聊天通路
|
||||||
- 当前阿里百炼备用链已完成一次真实线上闭环验证:手动切到 `aliyun-qwen-backup` 后,`POST /api/v1/projects/master-agent/messages` 会返回 `queued`,并已实际回流 `阿里备用链正常。` 到 `master-agent` 会话
|
- 当前阿里百炼备用链已完成一次真实线上闭环验证:手动切到 `aliyun-qwen-backup` 后,`POST /api/v1/projects/master-agent/messages` 会返回 `queued`,并已实际回流 `阿里备用链正常。` 到 `master-agent` 会话
|
||||||
- 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有在预设不适用时才需要填自定义模型
|
- 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有在预设不适用时才需要填自定义模型
|
||||||
- 当前 `我的 > 主 Agent 提示词 / 记忆` 页面已补:管理员全局主提示词只读展示、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 跨项目项目记忆的新增、编辑、删除接口;当前对话设置按登录账号隔离,管理员全局主提示词不可覆盖
|
- 当前 `我的` 根页已拆出 `主 Agent 提示词 / 主 Agent 记忆 / 全局接管 / 主 Agent 自动进化` 四个独立入口;其中提示词页支持管理员全局主提示词只读展示、用户主提示词、当前对话附加提示词与执行后端切换,记忆页支持用户通用记忆 / 跨项目项目记忆的新增、编辑与归档
|
||||||
- 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新`
|
- 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新`
|
||||||
- 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果有新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口
|
- 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果有新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口
|
||||||
- 当前 `OpenAiOnboardingActivity` 在登录成功后会直接给出 `测试主 Agent 对话` 入口,可一键跳到 `master-agent` 聊天页
|
- 当前 `OpenAiOnboardingActivity` 在登录成功后会直接给出 `测试主 Agent 对话` 入口,可一键跳到 `master-agent` 聊天页
|
||||||
@@ -326,7 +326,7 @@ npm run aab:release
|
|||||||
- 登录成功后的进入首页链路已做稳态处理:会先确认 `/api/auth/session` 可读,再执行 `replace(/conversations)`,并附带一次原生级兜底跳转,避免真机 WebView 偶发停留在“正在进入会话首页”
|
- 登录成功后的进入首页链路已做稳态处理:会先确认 `/api/auth/session` 可读,再执行 `replace(/conversations)`,并附带一次原生级兜底跳转,避免真机 WebView 偶发停留在“正在进入会话首页”
|
||||||
- `/api/v1/events` 已作为 SSE 出口使用,会话页、设备页、技能页和项目详情页会按事件自动刷新,不再只靠手动刷新
|
- `/api/v1/events` 已作为 SSE 出口使用,会话页、设备页、技能页和项目详情页会按事件自动刷新,不再只靠手动刷新
|
||||||
- 我的页新增 `技能` 入口,`/me/skills` 会按设备分组展示 Skill,并支持一键复制调用语句
|
- 我的页新增 `技能` 入口,`/me/skills` 会按设备分组展示 Skill,并支持一键复制调用语句
|
||||||
- 我的页新增 `主 Agent 提示词 / 记忆` 入口,`/me/master-agent` 会展示管理员全局主提示词、用户主提示词、当前对话附加提示词、组合预览,以及当前用户的通用记忆和跨项目项目记忆
|
- 我的页已拆出 `主 Agent 提示词 / 主 Agent 记忆 / 全局接管 / 主 Agent 自动进化` 入口;`/me/master-agent` 继续展示管理员全局主提示词、用户主提示词、当前对话附加提示词、组合预览,以及当前用户的通用记忆和跨项目项目记忆
|
||||||
- 我的页新增 `AI 账号` 入口,`/me/ai-accounts` 会展示主 GPT / 备用 GPT / API 容灾,并明确主链路优先走已登录 `ChatGPT Plus / Codex` 的 `Master Codex Node`
|
- 我的页新增 `AI 账号` 入口,`/me/ai-accounts` 会展示主 GPT / 备用 GPT / API 容灾,并明确主链路优先走已登录 `ChatGPT Plus / Codex` 的 `Master Codex Node`
|
||||||
- `AI 账号` 页面当前已补上显式 `登录指引`:手机端不会直接弹出 ChatGPT OAuth;主 GPT 的登录动作必须在绑定电脑上的 Codex / ChatGPT Plus 会话里完成,再回手机端点“测试连接 / 校验连接”
|
- `AI 账号` 页面当前已补上显式 `登录指引`:手机端不会直接弹出 ChatGPT OAuth;主 GPT 的登录动作必须在绑定电脑上的 Codex / ChatGPT Plus 会话里完成,再回手机端点“测试连接 / 校验连接”
|
||||||
- `AI 账号` 页面当前已升级成双入口:首页会显式展示 `登录 OpenAI 平台账号` 和 `绑定电脑上的 Codex 节点`
|
- `AI 账号` 页面当前已升级成双入口:首页会显式展示 `登录 OpenAI 平台账号` 和 `绑定电脑上的 Codex 节点`
|
||||||
|
|||||||
@@ -1818,6 +1818,16 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
intent.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_ID, "master-agent");
|
intent.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_ID, "master-agent");
|
||||||
intent.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME, "主 Agent");
|
intent.putExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME, "主 Agent");
|
||||||
break;
|
break;
|
||||||
|
case "master_agent_memory":
|
||||||
|
intent = new Intent(this, MasterAgentMemoryActivity.class);
|
||||||
|
intent.putExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_ID, "master-agent");
|
||||||
|
intent.putExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_NAME, "主 Agent");
|
||||||
|
break;
|
||||||
|
case "master_agent_takeover":
|
||||||
|
intent = new Intent(this, MasterAgentTakeoverActivity.class);
|
||||||
|
intent.putExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_ID, "master-agent");
|
||||||
|
intent.putExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_NAME, "主 Agent");
|
||||||
|
break;
|
||||||
case "master_agent_evolution":
|
case "master_agent_evolution":
|
||||||
intent = new Intent(this, MasterAgentEvolutionActivity.class);
|
intent = new Intent(this, MasterAgentEvolutionActivity.class);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class MasterAgentEvolutionActivity extends BossScreenActivity {
|
|||||||
private @Nullable String currentMode;
|
private @Nullable String currentMode;
|
||||||
private @Nullable String statusMessage;
|
private @Nullable String statusMessage;
|
||||||
private boolean statusIsError;
|
private boolean statusIsError;
|
||||||
|
private boolean canManageEvolution = true;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@@ -105,6 +106,7 @@ public class MasterAgentEvolutionActivity extends BossScreenActivity {
|
|||||||
JSONArray signals = payload.optJSONArray("signals");
|
JSONArray signals = payload.optJSONArray("signals");
|
||||||
JSONArray proposals = payload.optJSONArray("proposals");
|
JSONArray proposals = payload.optJSONArray("proposals");
|
||||||
JSONArray rules = payload.optJSONArray("rules");
|
JSONArray rules = payload.optJSONArray("rules");
|
||||||
|
canManageEvolution = payload.optBoolean("canManage", true);
|
||||||
|
|
||||||
currentMode = config == null ? "controlled" : config.optString("mode", "controlled");
|
currentMode = config == null ? "controlled" : config.optString("mode", "controlled");
|
||||||
boolean autoApplyLowRiskRules = config != null && config.optBoolean("autoApplyLowRiskRules", false);
|
boolean autoApplyLowRiskRules = config != null && config.optBoolean("autoApplyLowRiskRules", false);
|
||||||
@@ -116,7 +118,9 @@ public class MasterAgentEvolutionActivity extends BossScreenActivity {
|
|||||||
this,
|
this,
|
||||||
"主 Agent 自动进化",
|
"主 Agent 自动进化",
|
||||||
"最近在学什么、打算怎么改、已经生效了什么",
|
"最近在学什么、打算怎么改、已经生效了什么",
|
||||||
"支持在这里切换 controlled / autonomous,并直接审核待处理提案。"
|
canManageEvolution
|
||||||
|
? "支持在这里切换 controlled / autonomous,并直接审核待处理提案。"
|
||||||
|
: "当前是只读视角,可以查看主 Agent 正在学习和生效的规则。"
|
||||||
));
|
));
|
||||||
|
|
||||||
contentRoot.addView(BossUi.buildSoftPanel(
|
contentRoot.addView(BossUi.buildSoftPanel(
|
||||||
@@ -127,13 +131,17 @@ public class MasterAgentEvolutionActivity extends BossScreenActivity {
|
|||||||
));
|
));
|
||||||
maybeRenderStatusBanner();
|
maybeRenderStatusBanner();
|
||||||
|
|
||||||
Button controlledButton = BossUi.buildMiniActionButton(this, "切到受控模式", false);
|
if (canManageEvolution) {
|
||||||
controlledButton.setEnabled(!"controlled".equals(currentMode));
|
Button controlledButton = BossUi.buildMiniActionButton(this, "切到受控模式", false);
|
||||||
controlledButton.setOnClickListener(v -> switchMode("controlled"));
|
controlledButton.setEnabled(!"controlled".equals(currentMode));
|
||||||
Button autonomousButton = BossUi.buildMiniActionButton(this, "切到全自动模式", true);
|
controlledButton.setOnClickListener(v -> switchMode("controlled"));
|
||||||
autonomousButton.setEnabled(!"autonomous".equals(currentMode));
|
Button autonomousButton = BossUi.buildMiniActionButton(this, "切到全自动模式", true);
|
||||||
autonomousButton.setOnClickListener(v -> switchMode("autonomous"));
|
autonomousButton.setEnabled(!"autonomous".equals(currentMode));
|
||||||
contentRoot.addView(BossUi.buildInlineActionRow(this, controlledButton, autonomousButton));
|
autonomousButton.setOnClickListener(v -> switchMode("autonomous"));
|
||||||
|
contentRoot.addView(BossUi.buildInlineActionRow(this, controlledButton, autonomousButton));
|
||||||
|
} else {
|
||||||
|
contentRoot.addView(BossUi.buildEmptyCard(this, "你当前没有管理权限,模式切换和提案审批仅管理员可操作。"));
|
||||||
|
}
|
||||||
|
|
||||||
contentRoot.addView(BossUi.buildWechatMenuRow(
|
contentRoot.addView(BossUi.buildWechatMenuRow(
|
||||||
this,
|
this,
|
||||||
@@ -192,11 +200,13 @@ public class MasterAgentEvolutionActivity extends BossScreenActivity {
|
|||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
));
|
));
|
||||||
Button rejectButton = BossUi.buildMiniActionButton(this, "拒绝", false);
|
if (canManageEvolution) {
|
||||||
rejectButton.setOnClickListener(v -> reviewProposal(proposalId, false));
|
Button rejectButton = BossUi.buildMiniActionButton(this, "拒绝", false);
|
||||||
Button approveButton = BossUi.buildMiniActionButton(this, "批准", true);
|
rejectButton.setOnClickListener(v -> reviewProposal(proposalId, false));
|
||||||
approveButton.setOnClickListener(v -> reviewProposal(proposalId, true));
|
Button approveButton = BossUi.buildMiniActionButton(this, "批准", true);
|
||||||
contentRoot.addView(BossUi.buildInlineActionRow(this, rejectButton, approveButton));
|
approveButton.setOnClickListener(v -> reviewProposal(proposalId, true));
|
||||||
|
contentRoot.addView(BossUi.buildInlineActionRow(this, rejectButton, approveButton));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!rendered) {
|
if (!rendered) {
|
||||||
contentRoot.addView(BossUi.buildEmptyCard(this, "当前没有待审批提案。"));
|
contentRoot.addView(BossUi.buildEmptyCard(this, "当前没有待审批提案。"));
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
|||||||
appendContent(BossUi.buildSoftPanel(
|
appendContent(BossUi.buildSoftPanel(
|
||||||
this,
|
this,
|
||||||
"记忆说明",
|
"记忆说明",
|
||||||
"主 Agent 会自动沉淀长期有用的信息。你也可以在这里手动新增、编辑或删除。",
|
"主 Agent 会自动沉淀长期有用的信息。你也可以在这里手动新增、编辑或归档。",
|
||||||
"底层是结构化存储,项目记忆会显示真实 projectId。"
|
"底层是结构化存储,项目记忆会显示真实 projectId。"
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -242,23 +242,23 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
|||||||
));
|
));
|
||||||
|
|
||||||
if (memory != null) {
|
if (memory != null) {
|
||||||
builder.setNeutralButton("删除", (dialog, which) -> confirmDeleteMemory(memory));
|
builder.setNeutralButton("归档", (dialog, which) -> confirmArchiveMemory(memory));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmDeleteMemory(JSONObject memory) {
|
private void confirmArchiveMemory(JSONObject memory) {
|
||||||
final String memoryId = memory.optString("memoryId", "");
|
final String memoryId = memory.optString("memoryId", "");
|
||||||
if (memoryId.isEmpty()) {
|
if (memoryId.isEmpty()) {
|
||||||
showMessage("缺少 memoryId");
|
showMessage("缺少 memoryId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle("删除记忆")
|
.setTitle("归档记忆")
|
||||||
.setMessage("确定删除这条记忆吗?")
|
.setMessage("确定归档这条记忆吗?归档后会从当前列表移除,不是永久删除。")
|
||||||
.setNegativeButton("取消", null)
|
.setNegativeButton("取消", null)
|
||||||
.setPositiveButton("删除", (dialog, which) -> deleteMemory(memoryId))
|
.setPositiveButton("归档", (dialog, which) -> archiveMemory(memoryId))
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +329,7 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteMemory(String memoryId) {
|
private void archiveMemory(String memoryId) {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -338,14 +338,14 @@ public class MasterAgentMemoryActivity extends BossScreenActivity {
|
|||||||
throw new IllegalStateException(response.message());
|
throw new IllegalStateException(response.message());
|
||||||
}
|
}
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
showMessage("记忆已删除");
|
showMessage("记忆已归档");
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
} catch (Exception error) {
|
} catch (Exception error) {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
showMessage("记忆删除失败:" + error.getMessage());
|
showMessage("记忆归档失败:" + error.getMessage());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
|||||||
private @Nullable String backendOverrideText;
|
private @Nullable String backendOverrideText;
|
||||||
private boolean clawSelectable;
|
private boolean clawSelectable;
|
||||||
private @Nullable String clawReasonLabel;
|
private @Nullable String clawReasonLabel;
|
||||||
|
private boolean hermesSelectable;
|
||||||
|
private @Nullable String hermesReasonLabel;
|
||||||
private final List<String> backendOverrideValues = new ArrayList<>();
|
private final List<String> backendOverrideValues = new ArrayList<>();
|
||||||
private EditText userPromptInput;
|
private EditText userPromptInput;
|
||||||
private EditText projectPromptInput;
|
private EditText projectPromptInput;
|
||||||
@@ -84,6 +86,7 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
|||||||
userPrompt = payload.optJSONObject("userPrompt");
|
userPrompt = payload.optJSONObject("userPrompt");
|
||||||
projectControls = payload.optJSONObject("projectControls");
|
projectControls = payload.optJSONObject("projectControls");
|
||||||
JSONObject clawAvailability = payload.optJSONObject("clawAvailability");
|
JSONObject clawAvailability = payload.optJSONObject("clawAvailability");
|
||||||
|
JSONObject hermesAvailability = payload.optJSONObject("hermesAvailability");
|
||||||
adminPromptText = promptPolicy == null ? null : promptPolicy.optString("globalPrompt", "");
|
adminPromptText = promptPolicy == null ? null : promptPolicy.optString("globalPrompt", "");
|
||||||
userPromptText = userPrompt == null ? "" : userPrompt.optString("content", "");
|
userPromptText = userPrompt == null ? "" : userPrompt.optString("content", "");
|
||||||
projectPromptOverrideText = payload.optString(
|
projectPromptOverrideText = payload.optString(
|
||||||
@@ -93,6 +96,8 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
|||||||
backendOverrideText = projectControls == null ? "" : projectControls.optString("backendOverride", "");
|
backendOverrideText = projectControls == null ? "" : projectControls.optString("backendOverride", "");
|
||||||
clawSelectable = clawAvailability != null && clawAvailability.optBoolean("selectable", false);
|
clawSelectable = clawAvailability != null && clawAvailability.optBoolean("selectable", false);
|
||||||
clawReasonLabel = clawAvailability == null ? "" : clawAvailability.optString("reasonLabel", "");
|
clawReasonLabel = clawAvailability == null ? "" : clawAvailability.optString("reasonLabel", "");
|
||||||
|
hermesSelectable = hermesAvailability != null && hermesAvailability.optBoolean("selectable", false);
|
||||||
|
hermesReasonLabel = hermesAvailability == null ? "" : hermesAvailability.optString("reasonLabel", "");
|
||||||
|
|
||||||
replaceContent();
|
replaceContent();
|
||||||
appendContent(BossUi.buildSimpleProfileHeader(
|
appendContent(BossUi.buildSimpleProfileHeader(
|
||||||
@@ -138,6 +143,10 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
|||||||
backendOverrideValues.add("claw-runtime");
|
backendOverrideValues.add("claw-runtime");
|
||||||
backendLabels.add("Claw Runtime");
|
backendLabels.add("Claw Runtime");
|
||||||
}
|
}
|
||||||
|
if (hermesSelectable) {
|
||||||
|
backendOverrideValues.add("hermes-runtime");
|
||||||
|
backendLabels.add("Hermes Runtime");
|
||||||
|
}
|
||||||
|
|
||||||
backendSpinner = new Spinner(this);
|
backendSpinner = new Spinner(this);
|
||||||
backendSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, backendLabels));
|
backendSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, backendLabels));
|
||||||
@@ -170,6 +179,16 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
|||||||
: "恢复可用后,执行后端下拉框会重新出现 Claw Runtime。"
|
: "恢复可用后,执行后端下拉框会重新出现 Claw Runtime。"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if (!hermesSelectable) {
|
||||||
|
appendContent(BossUi.buildSoftPanel(
|
||||||
|
this,
|
||||||
|
"Hermes Runtime 当前不可用",
|
||||||
|
TextUtils.isEmpty(hermesReasonLabel) ? "当前环境未满足 Hermes Runtime 的启动条件。" : hermesReasonLabel,
|
||||||
|
TextUtils.equals(backendOverrideText, "hermes-runtime")
|
||||||
|
? "当前对话之前保存过 Hermes Runtime,运行时会自动回退到默认后端。"
|
||||||
|
: "恢复可用后,执行后端下拉框会重新出现 Hermes Runtime。"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
previewTextView = new TextView(this);
|
previewTextView = new TextView(this);
|
||||||
previewTextView.setText(buildPreviewText());
|
previewTextView.setText(buildPreviewText());
|
||||||
@@ -235,6 +254,8 @@ public class MasterAgentPromptActivity extends BossScreenActivity {
|
|||||||
builder.append("【执行后端】\n").append(backendValue).append("\n\n");
|
builder.append("【执行后端】\n").append(backendValue).append("\n\n");
|
||||||
} else if (TextUtils.equals(backendOverrideText, "claw-runtime") && !clawSelectable) {
|
} else if (TextUtils.equals(backendOverrideText, "claw-runtime") && !clawSelectable) {
|
||||||
builder.append("【执行后端】\n默认(Claw Runtime 当前不可用,运行时会自动回退)\n\n");
|
builder.append("【执行后端】\n默认(Claw Runtime 当前不可用,运行时会自动回退)\n\n");
|
||||||
|
} else if (TextUtils.equals(backendOverrideText, "hermes-runtime") && !hermesSelectable) {
|
||||||
|
builder.append("【执行后端】\n默认(Hermes Runtime 当前不可用,运行时会自动回退)\n\n");
|
||||||
}
|
}
|
||||||
if (builder.length() == 0) {
|
if (builder.length() == 0) {
|
||||||
return "当前没有任何提示词内容。";
|
return "当前没有任何提示词内容。";
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ public final class WechatSurfaceMapper {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private static final List<MeMenuItem> ROOT_ME_MENU_ITEMS = Arrays.asList(
|
private static final List<MeMenuItem> ROOT_ME_MENU_ITEMS = Arrays.asList(
|
||||||
new MeMenuItem("master_agent_prompt", "主 Agent 提示词 / 记忆", "配置全局主提示词、当前主提示词和用户记忆"),
|
new MeMenuItem("master_agent_prompt", "主 Agent 提示词", "配置全局主提示词和当前对话提示词"),
|
||||||
|
new MeMenuItem("master_agent_memory", "主 Agent 记忆", "维护用户通用记忆和项目记忆"),
|
||||||
|
new MeMenuItem("master_agent_takeover", "全局接管", "设置主 Agent 是否默认协同推进线程"),
|
||||||
new MeMenuItem("master_agent_evolution", "主 Agent 自动进化", "查看进化信号、提案与自动采纳规则"),
|
new MeMenuItem("master_agent_evolution", "主 Agent 自动进化", "查看进化信号、提案与自动采纳规则"),
|
||||||
new MeMenuItem("security", "账号与安全", "修改登录密码、设备安全与身份校验"),
|
new MeMenuItem("security", "账号与安全", "修改登录密码、设备安全与身份校验"),
|
||||||
new MeMenuItem("settings", "设置", "默认首页、提醒方式与危险操作确认"),
|
new MeMenuItem("settings", "设置", "默认首页、提醒方式与危险操作确认"),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class BossUiRootSurfaceTest {
|
|||||||
ReflectionHelpers.callInstanceMethod(activity, "renderMeRoot");
|
ReflectionHelpers.callInstanceMethod(activity, "renderMeRoot");
|
||||||
|
|
||||||
LinearLayout content = ReflectionHelpers.getField(activity, "screenContent");
|
LinearLayout content = ReflectionHelpers.getField(activity, "screenContent");
|
||||||
assertEquals("我的页应是资料头 + 8 条菜单", 9, content.getChildCount());
|
assertEquals("我的页应是资料头 + 10 条菜单", 11, content.getChildCount());
|
||||||
|
|
||||||
View header = content.getChildAt(0);
|
View header = content.getChildAt(0);
|
||||||
assertEquals("资料头不应保留浮层卡片感", 0f, header.getElevation(), 0.01f);
|
assertEquals("资料头不应保留浮层卡片感", 0f, header.getElevation(), 0.01f);
|
||||||
@@ -49,7 +49,9 @@ public class BossUiRootSurfaceTest {
|
|||||||
assertTrue(viewTreeContainsText(header, "最高管理员"));
|
assertTrue(viewTreeContainsText(header, "最高管理员"));
|
||||||
assertTrue(viewTreeContainsText(header, "主控账号已启用安全保护"));
|
assertTrue(viewTreeContainsText(header, "主控账号已启用安全保护"));
|
||||||
|
|
||||||
assertTrue(viewTreeContainsText(content, "主 Agent 提示词 / 记忆"));
|
assertTrue(viewTreeContainsText(content, "主 Agent 提示词"));
|
||||||
|
assertTrue(viewTreeContainsText(content, "主 Agent 记忆"));
|
||||||
|
assertTrue(viewTreeContainsText(content, "全局接管"));
|
||||||
assertTrue(viewTreeContainsText(content, "主 Agent 自动进化"));
|
assertTrue(viewTreeContainsText(content, "主 Agent 自动进化"));
|
||||||
assertTrue(viewTreeContainsText(content, "账号与安全"));
|
assertTrue(viewTreeContainsText(content, "账号与安全"));
|
||||||
assertTrue(viewTreeContainsText(content, "设置"));
|
assertTrue(viewTreeContainsText(content, "设置"));
|
||||||
|
|||||||
@@ -31,6 +31,38 @@ public class MainActivityMeEntryNavigationTest {
|
|||||||
assertEquals("主 Agent", started.getStringExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME));
|
assertEquals("主 Agent", started.getStringExtra(MasterAgentPromptActivity.EXTRA_PROJECT_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void masterAgentMemoryMeEntryOpensMemoryActivityForMasterAgent() {
|
||||||
|
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().get();
|
||||||
|
|
||||||
|
ReflectionHelpers.callInstanceMethod(
|
||||||
|
activity,
|
||||||
|
"openMeEntry",
|
||||||
|
ReflectionHelpers.ClassParameter.from(String.class, "master_agent_memory")
|
||||||
|
);
|
||||||
|
|
||||||
|
Intent started = Shadows.shadowOf(activity).getNextStartedActivity();
|
||||||
|
assertEquals(MasterAgentMemoryActivity.class.getName(), started.getComponent().getClassName());
|
||||||
|
assertEquals("master-agent", started.getStringExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_ID));
|
||||||
|
assertEquals("主 Agent", started.getStringExtra(MasterAgentMemoryActivity.EXTRA_PROJECT_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void masterAgentTakeoverMeEntryOpensTakeoverActivityForMasterAgent() {
|
||||||
|
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().get();
|
||||||
|
|
||||||
|
ReflectionHelpers.callInstanceMethod(
|
||||||
|
activity,
|
||||||
|
"openMeEntry",
|
||||||
|
ReflectionHelpers.ClassParameter.from(String.class, "master_agent_takeover")
|
||||||
|
);
|
||||||
|
|
||||||
|
Intent started = Shadows.shadowOf(activity).getNextStartedActivity();
|
||||||
|
assertEquals(MasterAgentTakeoverActivity.class.getName(), started.getComponent().getClassName());
|
||||||
|
assertEquals("master-agent", started.getStringExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_ID));
|
||||||
|
assertEquals("主 Agent", started.getStringExtra(MasterAgentTakeoverActivity.EXTRA_PROJECT_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void masterAgentEvolutionMeEntryOpensEvolutionActivity() {
|
public void masterAgentEvolutionMeEntryOpensEvolutionActivity() {
|
||||||
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().get();
|
MainActivity activity = Robolectric.buildActivity(MainActivity.class).setup().get();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.hyzq.boss;
|
package com.hyzq.boss;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -91,6 +92,43 @@ public class MasterAgentEvolutionActivityTest {
|
|||||||
assertEquals(1, activity.reloadCount);
|
assertEquals(1, activity.reloadCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void renderDashboardHidesManageActionsForReadonlyView() throws Exception {
|
||||||
|
TestMasterAgentEvolutionActivity activity = Robolectric
|
||||||
|
.buildActivity(TestMasterAgentEvolutionActivity.class, new Intent())
|
||||||
|
.setup()
|
||||||
|
.get();
|
||||||
|
|
||||||
|
JSONObject payload = new JSONObject()
|
||||||
|
.put("canManage", false)
|
||||||
|
.put("config", new JSONObject()
|
||||||
|
.put("mode", "controlled")
|
||||||
|
.put("autoApplyLowRiskRules", false))
|
||||||
|
.put("signals", new JSONArray())
|
||||||
|
.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());
|
||||||
|
|
||||||
|
ReflectionHelpers.callInstanceMethod(
|
||||||
|
activity,
|
||||||
|
"renderDashboard",
|
||||||
|
ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
|
||||||
|
);
|
||||||
|
|
||||||
|
View content = activity.findViewById(R.id.screen_content);
|
||||||
|
assertTrue(viewTreeContainsText(content, "当前是只读视角"));
|
||||||
|
assertTrue(viewTreeContainsText(content, "你当前没有管理权限"));
|
||||||
|
assertFalse(viewTreeContainsText(content, "切到全自动模式"));
|
||||||
|
assertFalse(viewTreeContainsText(content, "批准"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void renderDashboardShowsStatusBannerAndFormattedTimes() throws Exception {
|
public void renderDashboardShowsStatusBannerAndFormattedTimes() throws Exception {
|
||||||
TestMasterAgentEvolutionActivity activity = Robolectric
|
TestMasterAgentEvolutionActivity activity = Robolectric
|
||||||
|
|||||||
@@ -193,6 +193,86 @@ public class MasterAgentPromptActivityTest {
|
|||||||
assertTrue(viewTreeContainsText(content, "未检测到有效的 Claw 启动脚本"));
|
assertTrue(viewTreeContainsText(content, "未检测到有效的 Claw 启动脚本"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void renderPromptProfileIncludesHermesRuntimeWhenSelectable() 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", "hermes-runtime"))
|
||||||
|
.put("clawAvailability", new JSONObject()
|
||||||
|
.put("status", "ready")
|
||||||
|
.put("selectable", true)
|
||||||
|
.put("reasonLabel", "Claw Runtime 可用。"))
|
||||||
|
.put("hermesAvailability", new JSONObject()
|
||||||
|
.put("status", "ready")
|
||||||
|
.put("selectable", true)
|
||||||
|
.put("reasonLabel", "Hermes Runtime 可用。"));
|
||||||
|
|
||||||
|
ReflectionHelpers.callInstanceMethod(
|
||||||
|
activity,
|
||||||
|
"renderPromptProfile",
|
||||||
|
ReflectionHelpers.ClassParameter.from(JSONObject.class, payload)
|
||||||
|
);
|
||||||
|
|
||||||
|
Spinner backendSpinner = ReflectionHelpers.getField(activity, "backendSpinner");
|
||||||
|
assertEquals(3, backendSpinner.getAdapter().getCount());
|
||||||
|
assertEquals(2, backendSpinner.getSelectedItemPosition());
|
||||||
|
assertEquals("Hermes Runtime", String.valueOf(backendSpinner.getAdapter().getItem(2)));
|
||||||
|
TextView previewTextView = ReflectionHelpers.getField(activity, "previewTextView");
|
||||||
|
assertTrue(String.valueOf(previewTextView.getText()).contains("【执行后端】"));
|
||||||
|
assertTrue(String.valueOf(previewTextView.getText()).contains("hermes-runtime"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void renderPromptProfileShowsHermesUnavailableHintWhenStoredOverrideCannotBeSelected() 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", "hermes-runtime"))
|
||||||
|
.put("hermesAvailability", new JSONObject()
|
||||||
|
.put("status", "misconfigured")
|
||||||
|
.put("selectable", false)
|
||||||
|
.put("reason", "script_not_found")
|
||||||
|
.put("reasonLabel", "未检测到有效的 Hermes 启动脚本,将自动回退到默认后端。"));
|
||||||
|
|
||||||
|
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, "Hermes Runtime 当前不可用"));
|
||||||
|
assertTrue(viewTreeContainsText(content, "未检测到有效的 Hermes 启动脚本"));
|
||||||
|
assertTrue(viewTreeContainsText(content, "当前对话之前保存过 Hermes Runtime"));
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean viewTreeContainsText(View root, String expectedText) {
|
private static boolean viewTreeContainsText(View root, String expectedText) {
|
||||||
if (root instanceof TextView) {
|
if (root instanceof TextView) {
|
||||||
CharSequence text = ((TextView) root).getText();
|
CharSequence text = ((TextView) root).getText();
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ public class WechatSurfaceMapperMeMenuTest {
|
|||||||
WechatSurfaceMapper.MeMenuItem[] items = WechatSurfaceMapper.rootMeMenuItems();
|
WechatSurfaceMapper.MeMenuItem[] items = WechatSurfaceMapper.rootMeMenuItems();
|
||||||
|
|
||||||
assertEquals("master_agent_prompt", items[0].key);
|
assertEquals("master_agent_prompt", items[0].key);
|
||||||
assertEquals("主 Agent 提示词 / 记忆", items[0].title);
|
assertEquals("主 Agent 提示词", items[0].title);
|
||||||
assertEquals("master_agent_evolution", items[1].key);
|
assertEquals("master_agent_memory", items[1].key);
|
||||||
assertEquals("主 Agent 自动进化", items[1].title);
|
assertEquals("主 Agent 记忆", items[1].title);
|
||||||
|
assertEquals("master_agent_takeover", items[2].key);
|
||||||
|
assertEquals("全局接管", items[2].title);
|
||||||
|
assertEquals("master_agent_evolution", items[3].key);
|
||||||
|
assertEquals("主 Agent 自动进化", items[3].title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ public class WechatSurfaceMapperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void rootMeMenuTitles_matchLegacyWechatMenuWithOpsEntry() throws Exception {
|
public void rootMeMenuTitles_matchLegacyWechatMenuWithOpsEntry() throws Exception {
|
||||||
assertArrayEquals(
|
assertArrayEquals(
|
||||||
new String[]{"主 Agent 提示词 / 记忆", "主 Agent 自动进化", "账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
|
new String[]{"主 Agent 提示词", "主 Agent 记忆", "全局接管", "主 Agent 自动进化", "账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
|
||||||
WechatSurfaceMapper.rootMeMenuTitles()
|
WechatSurfaceMapper.rootMeMenuTitles()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ public class WechatSurfaceMapperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void mainPage_keepsOpsEntryInStableWechatMenuOrder() throws Exception {
|
public void mainPage_keepsOpsEntryInStableWechatMenuOrder() throws Exception {
|
||||||
assertArrayEquals(
|
assertArrayEquals(
|
||||||
new String[]{"主 Agent 提示词 / 记忆", "主 Agent 自动进化", "账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
|
new String[]{"主 Agent 提示词", "主 Agent 记忆", "全局接管", "主 Agent 自动进化", "账号与安全", "设置", "运维与修复", "AI 账号", "技能", "关于"},
|
||||||
WechatSurfaceMapper.rootMeMenuTitles()
|
WechatSurfaceMapper.rootMeMenuTitles()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -380,18 +380,22 @@ public class WechatSurfaceMapperTest {
|
|||||||
public void meMenuItems_useStableKeysInsteadOfDisplayTitlesForRouting() throws Exception {
|
public void meMenuItems_useStableKeysInsteadOfDisplayTitlesForRouting() throws Exception {
|
||||||
WechatSurfaceMapper.MeMenuItem[] items = WechatSurfaceMapper.rootMeMenuItems();
|
WechatSurfaceMapper.MeMenuItem[] items = WechatSurfaceMapper.rootMeMenuItems();
|
||||||
|
|
||||||
assertEquals(8, items.length);
|
assertEquals(10, items.length);
|
||||||
assertEquals("master_agent_prompt", items[0].key);
|
assertEquals("master_agent_prompt", items[0].key);
|
||||||
assertEquals("主 Agent 提示词 / 记忆", items[0].title);
|
assertEquals("主 Agent 提示词", items[0].title);
|
||||||
assertEquals("master_agent_evolution", items[1].key);
|
assertEquals("master_agent_memory", items[1].key);
|
||||||
assertEquals("主 Agent 自动进化", items[1].title);
|
assertEquals("主 Agent 记忆", items[1].title);
|
||||||
assertEquals("security", items[2].key);
|
assertEquals("master_agent_takeover", items[2].key);
|
||||||
assertEquals("settings", items[3].key);
|
assertEquals("全局接管", items[2].title);
|
||||||
assertEquals("ops", items[4].key);
|
assertEquals("master_agent_evolution", items[3].key);
|
||||||
assertEquals("运维与修复", items[4].title);
|
assertEquals("主 Agent 自动进化", items[3].title);
|
||||||
assertEquals("ai_accounts", items[5].key);
|
assertEquals("security", items[4].key);
|
||||||
assertEquals("skills", items[6].key);
|
assertEquals("settings", items[5].key);
|
||||||
assertEquals("about", items[7].key);
|
assertEquals("ops", items[6].key);
|
||||||
|
assertEquals("运维与修复", items[6].title);
|
||||||
|
assertEquals("ai_accounts", items[7].key);
|
||||||
|
assertEquals("skills", items[8].key);
|
||||||
|
assertEquals("about", items[9].key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
- 保留版本与 OTA 操作
|
- 保留版本与 OTA 操作
|
||||||
- 当前已补上 OTA 下载进度、失败重试、安装授权提示和返回关于页后的本地状态恢复
|
- 当前已补上 OTA 下载进度、失败重试、安装授权提示和返回关于页后的本地状态恢复
|
||||||
- 当前 `我的` 根页:
|
- 当前 `我的` 根页:
|
||||||
- 保留 `主 Agent 提示词 / 记忆 / 主 Agent 自动进化 / 账号与安全 / 设置 / 运维与修复 / AI 账号 / 技能 / 关于`
|
- 保留 `主 Agent 提示词 / 主 Agent 记忆 / 全局接管 / 主 Agent 自动进化 / 账号与安全 / 设置 / 运维与修复 / AI 账号 / 技能 / 关于`
|
||||||
- `运维与修复` 直接进入 `OpsCenterActivity`
|
- `运维与修复` 直接进入 `OpsCenterActivity`
|
||||||
- 当前 `OpenAiOnboardingActivity`:
|
- 当前 `OpenAiOnboardingActivity`:
|
||||||
- 会先自动打开 `OpenAI Platform` 登录页
|
- 会先自动打开 `OpenAI Platform` 登录页
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ cd /Users/kris/code/boss
|
|||||||
- 当前 `AI 账号` 页顶部会直接展示“当前主控身份”,并提供 `校验主控 / 测试主 Agent 对话` 两个入口,切换主控后不必再手动退回会话页验证
|
- 当前 `AI 账号` 页顶部会直接展示“当前主控身份”,并提供 `校验主控 / 测试主 Agent 对话` 两个入口,切换主控后不必再手动退回会话页验证
|
||||||
- 当前阿里百炼备用链已完成一次真实线上闭环验证:手动切到 `aliyun-qwen-backup` 后,`POST /api/v1/projects/master-agent/messages` 会返回 `queued`,并已实际回流 `阿里备用链正常。` 到 `master-agent` 会话
|
- 当前阿里百炼备用链已完成一次真实线上闭环验证:手动切到 `aliyun-qwen-backup` 后,`POST /api/v1/projects/master-agent/messages` 会返回 `queued`,并已实际回流 `阿里备用链正常。` 到 `master-agent` 会话
|
||||||
- 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有预设不适用时才需要填写自定义模型
|
- 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有预设不适用时才需要填写自定义模型
|
||||||
- 当前 `我的 > 主 Agent 提示词 / 记忆` 页面已接通:管理员全局主提示词只读展示、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 跨项目项目记忆都可以在 Web 端查看和编辑;当前对话设置按登录账号隔离,管理员全局主提示词不可覆盖
|
- 当前 `我的` 根页已经拆出 `主 Agent 提示词 / 主 Agent 记忆 / 全局接管 / 主 Agent 自动进化` 四个独立入口;提示词页用于管理员全局主提示词、用户主提示词、当前对话附加提示词与执行后端设置,记忆页用于用户通用记忆和项目记忆的查看、编辑与归档
|
||||||
- 当前 `我的 > 主 Agent 自动进化` 页面已接通:Web `/me/master-agent/evolution` 可查看最近信号、待审批提案和已生效规则,并允许管理员切换 `controlled / autonomous`、批准或拒绝提案
|
- 当前 `我的 > 主 Agent 自动进化` 页面已接通:Web `/me/master-agent/evolution` 可查看最近信号、待审批提案和已生效规则,并允许管理员切换 `controlled / autonomous`、批准或拒绝提案
|
||||||
- 当前原生 Android 也已接通 `主 Agent 自动进化`:`我的` 根页可直接进入,`master-agent` 会话右上角 `...` 菜单也可直达;页面支持查看最近信号、待审批提案、已生效规则,并可直接切换 `controlled / autonomous` 与批准/拒绝提案
|
- 当前原生 Android 也已接通 `主 Agent 自动进化`:`我的` 根页可直接进入,`master-agent` 会话右上角 `...` 菜单也可直达;页面支持查看最近信号、待审批提案、已生效规则。管理员可切换 `controlled / autonomous` 并批准/拒绝提案,非管理员显示只读视角
|
||||||
- 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新`
|
- 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新`
|
||||||
- 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果存在新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口
|
- 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果存在新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口
|
||||||
- 当前如果主控身份还是 `Master Codex Node`,但该节点离线或执行立即失败,主 Agent 会优先尝试已配置的 `OpenAI API / 阿里百炼 Qwen` 备用账号,不再把失败日志直接原样回给用户
|
- 当前如果主控身份还是 `Master Codex Node`,但该节点离线或执行立即失败,主 Agent 会优先尝试已配置的 `OpenAI API / 阿里百炼 Qwen` 备用账号,不再把失败日志直接原样回给用户
|
||||||
|
|||||||
@@ -12,5 +12,10 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dashboard = await getMasterAgentEvolutionDashboard();
|
const dashboard = await getMasterAgentEvolutionDashboard();
|
||||||
return jsonNoStore({ ok: true, ...dashboard });
|
return jsonNoStore({
|
||||||
|
ok: true,
|
||||||
|
canManage: session.role === "highest_admin",
|
||||||
|
role: session.role,
|
||||||
|
...dashboard,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,9 @@ test("GET /api/v1/master-agent/evolution returns config proposals and rules", as
|
|||||||
await createAdminRequest("http://127.0.0.1:3000/api/v1/master-agent/evolution"),
|
await createAdminRequest("http://127.0.0.1:3000/api/v1/master-agent/evolution"),
|
||||||
);
|
);
|
||||||
assert.equal(response.status, 200);
|
assert.equal(response.status, 200);
|
||||||
const payload = await response.json() as { ok: boolean; proposals: unknown[]; rules: unknown[] };
|
const payload = await response.json() as { ok: boolean; canManage?: boolean; proposals: unknown[]; rules: unknown[] };
|
||||||
assert.equal(payload.ok, true);
|
assert.equal(payload.ok, true);
|
||||||
|
assert.equal(payload.canManage, true);
|
||||||
assert.ok(Array.isArray(payload.proposals));
|
assert.ok(Array.isArray(payload.proposals));
|
||||||
assert.ok(Array.isArray(payload.rules));
|
assert.ok(Array.isArray(payload.rules));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user