feat: expose master agent evolution dashboard

This commit is contained in:
kris
2026-04-16 05:46:45 +08:00
parent 504d112218
commit f0490de180
12 changed files with 445 additions and 23 deletions

View File

@@ -34,6 +34,7 @@ test("master agent settings pages refresh when master agent config changes", asy
for (const relativePath of [
"src/app/me/master-agent/page.tsx",
"src/app/me/master-agent/takeover/page.tsx",
"src/app/me/master-agent/evolution/page.tsx",
]) {
const source = await readSource(relativePath);
assert.match(source, /import \{ RealtimeRefresh \}/, `expected ${relativePath} to import RealtimeRefresh`);
@@ -45,3 +46,9 @@ test("master agent settings pages refresh when master agent config changes", asy
);
}
});
test("me page exposes master agent evolution entry", async () => {
const source = await readSource("src/app/me/page.tsx");
assert.match(source, /href="\/me\/master-agent\/evolution"/, "expected me page to link evolution page");
assert.match(source, /title="主 Agent 自动进化"/, "expected me page to show evolution menu title");
});

View File

@@ -2,18 +2,19 @@ import test from "node:test";
import assert from "node:assert/strict";
import { getMasterAgentChatMenuItems } from "../src/lib/master-agent-chat-menu";
test("master-agent 聊天页菜单包含全局接管、提示词、模型、推理强度、记忆和刷新", () => {
test("master-agent 聊天页菜单包含全局接管、进化、提示词、模型、推理强度、记忆和刷新", () => {
const items = getMasterAgentChatMenuItems("master-agent");
assert.deepEqual(
items.map((item) => item.key),
["model", "reasoning_effort", "takeover", "prompt", "memory", "refresh"],
["model", "reasoning_effort", "takeover", "evolution", "prompt", "memory", "refresh"],
);
assert.equal(items[0]?.href, "/me/master-agent#model-section");
assert.equal(items[1]?.href, "/me/master-agent#reasoning-effort-section");
assert.equal(items[2]?.href, "/me/master-agent/takeover");
assert.equal(items[3]?.href, "/me/master-agent#prompt-section");
assert.equal(items[4]?.href, "/me/master-agent#memory-section");
assert.equal(items[5]?.action, "refresh");
assert.equal(items[3]?.href, "/me/master-agent/evolution");
assert.equal(items[4]?.href, "/me/master-agent#prompt-section");
assert.equal(items[5]?.href, "/me/master-agent#memory-section");
assert.equal(items[6]?.action, "refresh");
});
test("普通会话不返回主 Agent 专属菜单", () => {

View File

@@ -6,6 +6,7 @@ import { mkdir, mkdtemp, rm } from "node:fs/promises";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let getProjectAgentControls: (typeof import("../src/lib/boss-data"))["getProjectAgentControls"];
let recordMasterAgentEvolutionSignal: (typeof import("../src/lib/master-agent-evolution"))["recordMasterAgentEvolutionSignal"];
let listMasterAgentEvolutionProposals: (typeof import("../src/lib/master-agent-evolution"))["listMasterAgentEvolutionProposals"];
let setMasterAgentEvolutionMode: (typeof import("../src/lib/master-agent-evolution"))["setMasterAgentEvolutionMode"];
@@ -22,6 +23,7 @@ async function setup() {
import("../src/lib/master-agent-evolution.ts"),
]);
readState = data.readState;
getProjectAgentControls = data.getProjectAgentControls;
recordMasterAgentEvolutionSignal = evolution.recordMasterAgentEvolutionSignal;
listMasterAgentEvolutionProposals = evolution.listMasterAgentEvolutionProposals;
setMasterAgentEvolutionMode = evolution.setMasterAgentEvolutionMode;
@@ -73,3 +75,25 @@ test("autonomous mode auto applies low risk fast path proposals as evolution rul
assert.equal(state.masterAgentEvolutionRules.length, 1);
assert.equal(state.masterAgentEvolutionRules[0]?.ruleType, "fast_path_rule");
});
test("autonomous mode auto applies backend fallback proposals into master-agent backend override", async () => {
await setMasterAgentEvolutionMode("autonomous");
const result = await recordMasterAgentEvolutionSignal({
kind: "backend_fallback",
projectId: "master-agent",
account: "17600003315",
requestText: "Hermes 不可用时自动回退到 Claw",
metadataJson: {
failedBackendId: "hermes-runtime",
fallbackToBackendId: "claw-runtime",
},
});
assert.equal(result.proposal?.status, "auto_applied");
const state = await readState();
const controls = await getProjectAgentControls("master-agent", "17600003315");
assert.equal(state.masterAgentEvolutionRules.length, 1);
assert.equal(state.masterAgentEvolutionRules[0]?.ruleType, "routing_preference_patch");
assert.equal(controls?.backendOverride ?? null, "claw-runtime");
});

View File

@@ -359,6 +359,43 @@ test("master-agent 查询当前后端时直接走 fast path 返回后端摘要",
assert.equal(reply?.senderLabel ?? "", "主Agent·gpt-5.4");
});
test("master-agent 重复追问状态类问题时会记录 repeated_question 进化信号", async () => {
await saveAiAccount({
accountId: "openai-repeat-status",
label: "OpenAI 主模型",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主模型",
model: "gpt-5.4",
apiKey: "sk-openai-repeat-status",
enabled: true,
setActive: true,
loginStatusNote: "用于重复状态问题测试。",
});
const firstResponse = await POST(
await createAuthedRequest("master-agent", {
body: "当前主节点在线吗",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(firstResponse.status, 200);
const secondResponse = await POST(
await createAuthedRequest("master-agent", {
body: "现在主节点在线吗",
}),
{ params: Promise.resolve({ projectId: "master-agent" }) },
);
assert.equal(secondResponse.status, 200);
const state = await readState();
assert.ok(
state.masterAgentEvolutionSignals.some((signal) => signal.kind === "repeated_question"),
"expected at least one repeated_question signal",
);
});
test("master-agent 查询全局接管状态时直接走 fast path 返回当前状态", async () => {
await saveAiAccount({
accountId: "openai-takeover-status",