diff --git a/docs/architecture/ai_handoff_index_cn.md b/docs/architecture/ai_handoff_index_cn.md index 1f23bb0..9236ab8 100644 --- a/docs/architecture/ai_handoff_index_cn.md +++ b/docs/architecture/ai_handoff_index_cn.md @@ -155,6 +155,7 @@ - `版本迭代记录` 只读,由主 Agent 汇总 - `我的` 根页当前保留 `账号与安全 / 设置 / 运维与修复 / AI 账号 / 技能 / 关于` - `我的 > 主 Agent 提示词 / 记忆` 当前可编辑管理员全局主提示词、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 项目记忆 +- `我的 > 主 Agent 自动进化` 当前可查看进化信号、待审批提案、已生效规则,并切换 `controlled / autonomous` - `我的 > AI 账号` 必须可查看和切换 `主 GPT / 备用 GPT / API 容灾` - `我的 > 技能` 必须按绑定设备展示 Skill,并支持一键复制调用语句 - `设备` 页当前只允许出现生产设备,旧演示脏数据不能回流到正式视图 diff --git a/docs/architecture/current_runtime_and_deploy_status_cn.md b/docs/architecture/current_runtime_and_deploy_status_cn.md index 1c3bf54..f1ac030 100644 --- a/docs/architecture/current_runtime_and_deploy_status_cn.md +++ b/docs/architecture/current_runtime_and_deploy_status_cn.md @@ -135,6 +135,7 @@ cd /Users/kris/code/boss - 当前阿里百炼备用链已完成一次真实线上闭环验证:手动切到 `aliyun-qwen-backup` 后,`POST /api/v1/projects/master-agent/messages` 会返回 `queued`,并已实际回流 `阿里备用链正常。` 到 `master-agent` 会话 - 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有预设不适用时才需要填写自定义模型 - 当前 `我的 > 主 Agent 提示词 / 记忆` 页面已接通:管理员全局主提示词只读展示、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 跨项目项目记忆都可以在 Web 端查看和编辑;当前对话设置按登录账号隔离,管理员全局主提示词不可覆盖 +- 当前 `我的 > 主 Agent 自动进化` 页面已接通:Web `/me/master-agent/evolution` 可查看最近信号、待审批提案和已生效规则,并允许管理员切换 `controlled / autonomous`、批准或拒绝提案 - 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新` - 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果存在新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口 - 当前如果主控身份还是 `Master Codex Node`,但该节点离线或执行立即失败,主 Agent 会优先尝试已配置的 `OpenAI API / 阿里百炼 Qwen` 备用账号,不再把失败日志直接原样回给用户 @@ -165,6 +166,7 @@ cd /Users/kris/code/boss - 当前对话级 `agentControls` 已经生效:`master-agent` 会话支持 `modelOverride / reasoningEffortOverride` 强制覆盖,也支持 `fastModelOverride / fastReasoningEffortOverride / smartModelOverride / smartReasoningEffortOverride` 这组策略默认值;主 Agent 普通对话默认按 fast 档选模型,深度任务可按 smart 档选模型,手动强制覆盖仍然优先级最高 - 当前主 Agent 自动进化引擎已接入第一阶段共享内核:`masterAgentEvolutionConfig / Signals / Proposals / Rules / RunLogs` 已进入状态模型,`GET /api/v1/master-agent/evolution` 可查看进化状态,`POST /api/v1/master-agent/evolution/config` 可在 `controlled / autonomous` 间切换,提案支持 approve / reject;`controlled` 只生成待审批提案,`autonomous` 可自动采纳低风险 `memory_patch / routing_preference_patch / fast_path_rule` - 当前 `codex/master-agent-autonomous-evolution` 分支把默认 evolution mode 改成 `autonomous`,新初始化状态会默认开启低风险自动采纳;`codex/master-agent-controlled-evolution` 保持 `controlled` 默认值 +- 当前主 Agent 会在 fast path 判断前捕获状态类问题信号;重复短问句会沉淀为 `repeated_question`,显式后端回退会附带 `fallbackToBackendId`,在 autonomous 模式下可自动写回 `backendOverride` - 当前 `group_dispatch_plan / device_import_resolution / attachment_analysis` 三类深度任务已经会把 `smart*` 策略下发到任务队列,并随任务持久化 `executionModel / executionReasoningEffort`;local-agent 执行这类任务时会优先吃任务级模型,不再只依赖本机固定默认模型 - 当前对话级 `agentControls` 也已支持 `backendOverride`:`master-agent` 会话可在 `Claw Runtime` 或 `Hermes Runtime` 可用时显式选择 `claw-runtime / hermes-runtime`,普通单线程会话当前只开放 `hermes-runtime`;不可用时保存接口会直接拒绝,并返回人类可读原因 - 原生 Android 当前会把 `master-agent` 的等待态保留在消息流里:发送后常驻显示“主 Agent 思考中”,超时后改成“主 Agent 回复超时 + 重试等待”,收到新回复后会自动清掉,不再只靠 toast 提示 diff --git a/src/app/me/master-agent/evolution/page.tsx b/src/app/me/master-agent/evolution/page.tsx new file mode 100644 index 0000000..2a23239 --- /dev/null +++ b/src/app/me/master-agent/evolution/page.tsx @@ -0,0 +1,36 @@ +import { RealtimeRefresh } from "@/components/app-runtime"; +import { AppShell, PageNav, StatusBar } from "@/components/app-ui"; +import { MasterAgentEvolutionClient } from "@/components/master-agent-evolution-client"; +import { requirePageSession } from "@/lib/boss-auth"; +import { getMasterAgentEvolutionDashboard } from "@/lib/master-agent-evolution"; + +export const dynamic = "force-dynamic"; + +export default async function MasterAgentEvolutionPage() { + const session = await requirePageSession(); + const dashboard = await getMasterAgentEvolutionDashboard(); + + return ( + + + + +
+
+ 这里展示主 Agent 最近捕获到的进化信号、待审批提案和已生效规则。 +
+ {session.role === "highest_admin" + ? "你是管理员,可以在这里切换受控/全自动模式,并直接审批提案。" + : "你当前是只读视角,可以查看主 Agent 正在学什么。"} +
+
+ +
+ ); +} diff --git a/src/app/me/page.tsx b/src/app/me/page.tsx index 1c389e7..ed7e54c 100644 --- a/src/app/me/page.tsx +++ b/src/app/me/page.tsx @@ -28,6 +28,11 @@ export default async function MePage() { title="主 Agent 提示词 / 记忆" description="配置全局主提示词、当前主提示词和用户记忆" /> + (response: Response): Promise { + return (await response.json()) as T; +} + +export function MasterAgentEvolutionClient({ + isAdmin, + config, + signals, + proposals, + rules, +}: { + isAdmin: boolean; + config: EvolutionConfig; + signals: EvolutionSignal[]; + proposals: EvolutionProposal[]; + rules: EvolutionRule[]; +}) { + const router = useRouter(); + const [busyKey, setBusyKey] = useState(null); + const [message, setMessage] = useState(""); + + async function switchMode(mode: "controlled" | "autonomous") { + setBusyKey(`mode:${mode}`); + const response = await fetch("/api/v1/master-agent/evolution/config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ mode }), + }); + const result = await readJson<{ ok: boolean; message?: string }>(response); + setBusyKey(null); + setMessage(result.ok ? `已切到 ${mode === "autonomous" ? "完全自我进化" : "受控自动进化"}。` : result.message ?? "切换失败。"); + if (result.ok) { + router.refresh(); + } + } + + async function reviewProposal(proposalId: string, action: "approve" | "reject") { + setBusyKey(`${action}:${proposalId}`); + const response = await fetch(`/api/v1/master-agent/evolution/proposals/${proposalId}/${action}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({}), + }); + const result = await readJson<{ ok: boolean; message?: string }>(response); + setBusyKey(null); + setMessage(result.ok ? (action === "approve" ? "提案已批准。" : "提案已拒绝。") : result.message ?? "提交失败。"); + if (result.ok) { + router.refresh(); + } + } + + const pendingProposals = proposals.filter((item) => item.status === "pending_review"); + + return ( +
+
+
+
+
自动进化模式
+
+ 当前模式:{config.mode === "autonomous" ? "完全自我进化" : "受控自动进化"}。 + {config.autoApplyLowRiskRules ? "低风险提案会自动采纳。" : "所有提案都需要人工确认。"} +
+
+ + {config.mode === "autonomous" ? "全自动" : "受控"} + +
+ {isAdmin ? ( +
+ + +
+ ) : null} +
+ +
+
+
待处理提案
+ + {pendingProposals.length} 条 + +
+
+ {pendingProposals.length === 0 ? ( +
当前没有待审批提案。
+ ) : ( + pendingProposals.map((proposal) => ( +
+
+
+
{proposal.title}
+
{proposal.summary}
+
+ + {proposal.riskLevel} / {Math.round(proposal.confidence * 100)}% + +
+
+ {proposal.proposalType} · {formatTime(proposal.createdAt)} +
+ {isAdmin ? ( +
+ + +
+ ) : null} +
+ )) + )} +
+
+ +
+
+
+
最近信号
+ + {signals.length} + +
+
+ {signals.slice(0, 8).map((signal) => ( +
+
{signal.kind}
+
{signal.requestText}
+
{formatTime(signal.createdAt)}
+
+ ))} +
+
+ +
+
+
已生效规则
+ + {rules.length} + +
+
+ {rules.slice(0, 8).map((rule) => ( +
+
{rule.ruleType}
+
+ {formatTime(rule.createdAt)} + {rule.sourceProposalId ? ` · ${rule.sourceProposalId}` : ""} +
+
+ ))} +
+
+
+ + {message ? ( +
+ {message} +
+ ) : null} +
+ ); +} diff --git a/src/lib/boss-master-agent.ts b/src/lib/boss-master-agent.ts index 0b4d126..04b4013 100644 --- a/src/lib/boss-master-agent.ts +++ b/src/lib/boss-master-agent.ts @@ -1726,10 +1726,6 @@ type MasterAgentFastIntentContext = { effectiveDeepTaskPolicy: ReturnType; }; -function buildMasterAgentRuntimeBackendLabel(context: MasterAgentFastIntentContext) { - return context.agentControls?.backendOverride?.trim() || "master-codex-node"; -} - function getMasterAgentRuntimeDevice(context: MasterAgentFastIntentContext) { const deviceId = context.runtime.account.nodeId?.trim() || @@ -1952,7 +1948,7 @@ async function appendFastPathError( } async function tryRecordMasterAgentEvolutionSignal(input: { - kind: "fast_path_candidate" | "user_correction" | "backend_fallback"; + kind: "fast_path_candidate" | "repeated_question" | "user_correction" | "backend_fallback"; account: string; requestText: string; replyText?: string; @@ -1972,6 +1968,47 @@ async function tryRecordMasterAgentEvolutionSignal(input: { } } +function isMasterAgentEvolutionStatusLikeRequest(requestText: string) { + return /(当前|现在|有没有|是否|哪个|什么|在线吗|状态)/i.test(requestText); +} + +function normalizeMasterAgentEvolutionQuestion(requestText: string) { + return normalizeLexicalText(requestText) + .replace(/当前|现在|目前|一下|帮我|请问|请/g, "") + .replace(/吗|呢|呀|啊/g, "") + .trim(); +} + +async function isRepeatedMasterAgentStatusQuestion(params: { + requestText: string; + requestMessageId?: string; +}) { + const normalizedRequest = normalizeMasterAgentEvolutionQuestion(params.requestText); + if (!normalizedRequest) { + return false; + } + const state = await readState(); + const masterProject = state.projects.find((project) => project.id === "master-agent"); + if (!masterProject) { + return false; + } + const priorUserMessages = [...masterProject.messages] + .reverse() + .filter((message) => message.sender === "user" && message.id !== params.requestMessageId) + .slice(0, 8); + return priorUserMessages.some((message) => { + const normalizedBody = normalizeMasterAgentEvolutionQuestion(message.body); + if (!normalizedBody) { + return false; + } + return ( + normalizedBody === normalizedRequest || + normalizedBody.includes(normalizedRequest) || + normalizedRequest.includes(normalizedBody) + ); + }); +} + function buildModelSummaryReply(context: MasterAgentFastIntentContext, requestText: string) { const normalized = normalizeLexicalText(requestText); const manualModel = context.agentControls?.modelOverride?.trim() || ""; @@ -3250,6 +3287,22 @@ export async function replyToMasterAgentUserMessage(params: { currentSessionExpiresAt?: string; mode?: "wait" | "enqueue"; }) { + if (isMasterAgentEvolutionStatusLikeRequest(params.requestText)) { + const repeatedQuestion = await isRepeatedMasterAgentStatusQuestion({ + requestText: params.requestText, + requestMessageId: params.requestMessageId, + }); + await tryRecordMasterAgentEvolutionSignal({ + kind: repeatedQuestion ? "repeated_question" : "fast_path_candidate", + account: params.requestedByAccount, + requestText: params.requestText, + metadataJson: { + source: "replyToMasterAgentUserMessage.pre_fast_intent", + repeatedQuestion, + }, + }); + } + const fastIntentResult = await tryHandleMasterAgentFastIntent({ requestText: params.requestText, requestedByAccount: params.requestedByAccount, @@ -3258,17 +3311,6 @@ export async function replyToMasterAgentUserMessage(params: { return fastIntentResult; } - if (/(当前|现在|有没有|是否|哪个|什么|在线吗|状态)/i.test(params.requestText)) { - await tryRecordMasterAgentEvolutionSignal({ - kind: "fast_path_candidate", - account: params.requestedByAccount, - requestText: params.requestText, - metadataJson: { - source: "replyToMasterAgentUserMessage.pre_slow_path", - }, - }); - } - const runtime = await getMasterAgentRuntimeAccount(); if (!runtime?.account) { @@ -3311,6 +3353,22 @@ export async function replyToMasterAgentUserMessage(params: { }; const selectedBackend = await selectExecutionBackend(backendSelectionInput); const backendChoices = listExecutionBackendChoices(backendSelectionInput); + const requestedBackendId = executionConfig.agentControls?.backendOverride?.trim() || ""; + if ( + requestedBackendId && + selectedBackend.backendId !== requestedBackendId && + (selectedBackend.backendId === CLAW_BACKEND_ID || selectedBackend.backendId === HERMES_BACKEND_ID) + ) { + await tryRecordMasterAgentEvolutionSignal({ + kind: "backend_fallback", + account: params.requestedByAccount, + requestText: params.requestText, + metadataJson: { + requestedBackendId, + fallbackToBackendId: selectedBackend.backendId, + }, + }); + } const agentControls = executionConfig.agentControls; const masterExecutionPrompt = buildMasterCodexNodePrompt( state, diff --git a/src/lib/master-agent-chat-menu.ts b/src/lib/master-agent-chat-menu.ts index 5eec85f..5e8d58d 100644 --- a/src/lib/master-agent-chat-menu.ts +++ b/src/lib/master-agent-chat-menu.ts @@ -6,11 +6,12 @@ export const MASTER_AGENT_CHAT_PAGE_ANCHORS = { } as const; export const MASTER_AGENT_TAKEOVER_PAGE_HREF = "/me/master-agent/takeover"; +export const MASTER_AGENT_EVOLUTION_PAGE_HREF = "/me/master-agent/evolution"; export type MasterAgentChatPageAnchors = typeof MASTER_AGENT_CHAT_PAGE_ANCHORS; export type MasterAgentChatMenuItem = { - key: "prompt" | "model" | "reasoning_effort" | "takeover" | "memory" | "refresh"; + key: "prompt" | "model" | "reasoning_effort" | "takeover" | "evolution" | "memory" | "refresh"; label: string; href?: string; action?: "refresh"; @@ -37,6 +38,11 @@ export function getMasterAgentChatMenuItems(projectId: string): MasterAgentChatM label: "全局接管", href: MASTER_AGENT_TAKEOVER_PAGE_HREF, }, + { + key: "evolution", + label: "自动进化", + href: MASTER_AGENT_EVOLUTION_PAGE_HREF, + }, { key: "prompt", label: "提示词", diff --git a/src/lib/master-agent-evolution.ts b/src/lib/master-agent-evolution.ts index a7def83..90449b8 100644 --- a/src/lib/master-agent-evolution.ts +++ b/src/lib/master-agent-evolution.ts @@ -31,6 +31,7 @@ function inferProposalFromSignal(input: { projectId: string; requestText: string; signalId: string; + metadataJson?: Record; }): Omit< Parameters[0], "status" @@ -76,6 +77,11 @@ function inferProposalFromSignal(input: { } if (input.kind === "backend_fallback") { + const fallbackToBackendId = + input.metadataJson?.fallbackToBackendId === "hermes-runtime" || + input.metadataJson?.fallbackToBackendId === "claw-runtime" + ? input.metadataJson.fallbackToBackendId + : undefined; return { proposalType: "routing_preference_patch", account: input.account, @@ -84,9 +90,10 @@ function inferProposalFromSignal(input: { summary: "检测到后端回退,建议后续优先选择最近稳定的可用后端。", patchJson: { backendPreference: "prefer_available_runtime", + backendOverride: fallbackToBackendId, }, sourceSignalIds: [input.signalId], - confidence: 0.72, + confidence: fallbackToBackendId ? 0.8 : 0.72, riskLevel: "low", }; } @@ -193,6 +200,7 @@ export async function recordMasterAgentEvolutionSignal(input: { projectId: signal.projectId, requestText: signal.requestText, signalId: signal.signalId, + metadataJson: signal.metadataJson, }); if (!proposalInput) { return { signal, proposal: null }; diff --git a/tests/config-pages-realtime-refresh.test.ts b/tests/config-pages-realtime-refresh.test.ts index 25339c1..e118285 100644 --- a/tests/config-pages-realtime-refresh.test.ts +++ b/tests/config-pages-realtime-refresh.test.ts @@ -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"); +}); diff --git a/tests/master-agent-chat-menu.test.ts b/tests/master-agent-chat-menu.test.ts index c0145e8..c8e5209 100644 --- a/tests/master-agent-chat-menu.test.ts +++ b/tests/master-agent-chat-menu.test.ts @@ -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 专属菜单", () => { diff --git a/tests/master-agent-evolution-engine.test.ts b/tests/master-agent-evolution-engine.test.ts index 6e1a97a..806f00b 100644 --- a/tests/master-agent-evolution-engine.test.ts +++ b/tests/master-agent-evolution-engine.test.ts @@ -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"); +}); diff --git a/tests/master-agent-message-queue.test.ts b/tests/master-agent-message-queue.test.ts index 6c74842..7a2da5a 100644 --- a/tests/master-agent-message-queue.test.ts +++ b/tests/master-agent-message-queue.test.ts @@ -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",