feat: add master agent evolution engine core

This commit is contained in:
kris
2026-04-16 05:21:50 +08:00
parent f496838ced
commit de6257a819
14 changed files with 1551 additions and 8 deletions

View File

@@ -24,6 +24,7 @@
7. `docs/architecture/thread_context_budget_and_handoff_protocol_cn.md` 7. `docs/architecture/thread_context_budget_and_handoff_protocol_cn.md`
8. `prompts/codex_fullstack_build_and_deploy_prompt_cn.md` 8. `prompts/codex_fullstack_build_and_deploy_prompt_cn.md`
9. `docs/superpowers/specs/2026-04-16-master-agent-fast-path-design.md` 9. `docs/superpowers/specs/2026-04-16-master-agent-fast-path-design.md`
10. `docs/superpowers/specs/2026-04-16-master-agent-evolution-engine-design.md`
## 3. 当前有效实现边界 ## 3. 当前有效实现边界
@@ -36,6 +37,7 @@
- `src/lib/boss-device-auth.ts`:设备 token / 登录会话混合鉴权辅助 - `src/lib/boss-device-auth.ts`:设备 token / 登录会话混合鉴权辅助
- `src/lib/boss-events.ts`SSE 事件总线 - `src/lib/boss-events.ts`SSE 事件总线
- `src/lib/boss-master-agent.ts`:主 Agent 真实回复链路、Master Codex Node relay 与 API 容灾逻辑 - `src/lib/boss-master-agent.ts`:主 Agent 真实回复链路、Master Codex Node relay 与 API 容灾逻辑
- `src/lib/master-agent-evolution.ts`:主 Agent 自动进化引擎,负责 signal、proposal、rule 与 controlled / autonomous 模式
- `src/lib/boss-attachments.ts`:附件类型识别、分析状态决策和下载头 - `src/lib/boss-attachments.ts`:附件类型识别、分析状态决策和下载头
- `src/lib/boss-storage.ts`:附件存储抽象、配置校验和脱敏输出 - `src/lib/boss-storage.ts`:附件存储抽象、配置校验和脱敏输出
- `src/lib/boss-storage-server-file.ts`:服务器文件存储上传 / 读取 - `src/lib/boss-storage-server-file.ts`:服务器文件存储上传 / 读取

View File

@@ -163,6 +163,7 @@ cd /Users/kris/code/boss
- 主 Agent 当前真实对话链路已验证通过:`Boss Web -> /api/v1/projects/master-agent/messages -> master-agent task queue -> local-agent -> codex exec -> /complete -> 项目消息账本` - 主 Agent 当前真实对话链路已验证通过:`Boss Web -> /api/v1/projects/master-agent/messages -> master-agent task queue -> local-agent -> codex exec -> /complete -> 项目消息账本`
- 主 Agent 单聊当前已改成“快速入队 + 异步回流”:`POST /api/v1/projects/master-agent/messages` 会先返回 `masterReplyState + task`,真实回复随后再回写消息账本 - 主 Agent 单聊当前已改成“快速入队 + 异步回流”:`POST /api/v1/projects/master-agent/messages` 会先返回 `masterReplyState + task`,真实回复随后再回写消息账本
- 当前对话级 `agentControls` 已经生效:`master-agent` 会话支持 `modelOverride / reasoningEffortOverride` 强制覆盖,也支持 `fastModelOverride / fastReasoningEffortOverride / smartModelOverride / smartReasoningEffortOverride` 这组策略默认值;主 Agent 普通对话默认按 fast 档选模型,深度任务可按 smart 档选模型,手动强制覆盖仍然优先级最高 - 当前对话级 `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`
- 当前 `group_dispatch_plan / device_import_resolution / attachment_analysis` 三类深度任务已经会把 `smart*` 策略下发到任务队列,并随任务持久化 `executionModel / executionReasoningEffort`local-agent 执行这类任务时会优先吃任务级模型,不再只依赖本机固定默认模型 - 当前 `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`;不可用时保存接口会直接拒绝,并返回人类可读原因 - 当前对话级 `agentControls` 也已支持 `backendOverride``master-agent` 会话可在 `Claw Runtime``Hermes Runtime` 可用时显式选择 `claw-runtime / hermes-runtime`,普通单线程会话当前只开放 `hermes-runtime`;不可用时保存接口会直接拒绝,并返回人类可读原因
- 原生 Android 当前会把 `master-agent` 的等待态保留在消息流里:发送后常驻显示“主 Agent 思考中”,超时后改成“主 Agent 回复超时 + 重试等待”,收到新回复后会自动清掉,不再只靠 toast 提示 - 原生 Android 当前会把 `master-agent` 的等待态保留在消息流里:发送后常驻显示“主 Agent 思考中”,超时后改成“主 Agent 回复超时 + 重试等待”,收到新回复后会自动清掉,不再只靠 toast 提示

View File

@@ -0,0 +1,287 @@
# Master Agent Evolution Engine Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 给 Boss 主Agent 增加一套统一的自动进化引擎,并支持 `controlled``autonomous` 两种模式以及双分支交付。
**Architecture:** 在现有 `boss-data``boss-master-agent` 上新增 evolution state、engine 和 API。共享内核一次实现分支差异只保留在默认模式与自动采纳开关上避免维护两份逻辑。
**Tech Stack:** Next.js 16、TypeScript、文件型状态存储、Node test runner
---
### Task 1: 增加 Evolution 状态模型与持久化
**Files:**
- Modify: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/lib/boss-data.ts`
- Test: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/tests/master-agent-evolution-state.test.ts`
- [ ] **Step 1: 写失败测试,定义 evolution state 的默认结构与读写**
```ts
test("boss state 初始化时包含 master agent evolution 默认配置与空集合", async () => {
const state = await readState();
assert.deepEqual(state.masterAgentEvolutionConfig?.mode, "controlled");
assert.deepEqual(state.masterAgentEvolutionSignals, []);
assert.deepEqual(state.masterAgentEvolutionProposals, []);
assert.deepEqual(state.masterAgentEvolutionRules, []);
assert.deepEqual(state.masterAgentEvolutionRunLogs, []);
});
```
- [ ] **Step 2: 运行测试,确认当前失败**
Run: `npx tsx --test tests/master-agent-evolution-state.test.ts`
Expected: FAIL提示缺少 `masterAgentEvolution*` 字段或断言不匹配
- [ ] **Step 3: 在 `boss-data.ts` 中新增类型和默认状态**
```ts
export type MasterAgentEvolutionMode = "controlled" | "autonomous";
export interface MasterAgentEvolutionConfig {
mode: MasterAgentEvolutionMode;
autoApplyLowRiskRules: boolean;
updatedAt: string;
}
```
- [ ] **Step 4: 扩展 `BossState`、normalize 逻辑和默认初始值**
Run: `npx tsx --test tests/master-agent-evolution-state.test.ts`
Expected: PASS
- [ ] **Step 5: 提交**
```bash
git add src/lib/boss-data.ts tests/master-agent-evolution-state.test.ts
git commit -m "feat: add master agent evolution state"
```
### Task 2: 实现 Evolution Engine 核心
**Files:**
- Create: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/lib/master-agent-evolution.ts`
- Test: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/tests/master-agent-evolution-engine.test.ts`
- [ ] **Step 1: 写失败测试,覆盖 signal -> proposal 的最小闭环**
```ts
test("recording repeated deterministic questions creates a fast_path_rule proposal", async () => {
await recordMasterAgentEvolutionSignal({
kind: "repeated_question",
projectId: "master-agent",
account: "17600003315",
content: "当前主节点在线吗",
});
const proposals = await listMasterAgentEvolutionProposals();
assert.equal(proposals[0]?.proposalType, "fast_path_rule");
});
```
- [ ] **Step 2: 运行测试,确认失败**
Run: `npx tsx --test tests/master-agent-evolution-engine.test.ts`
Expected: FAIL提示函数或 proposal 类型不存在
- [ ] **Step 3: 实现最小 engine API**
```ts
export async function recordMasterAgentEvolutionSignal(...) {}
export async function listMasterAgentEvolutionProposals(...) {}
export async function applyMasterAgentEvolutionProposal(...) {}
```
- [ ] **Step 4: 实现第一批 proposal 生成规则**
规则最小集:
- repeated deterministic questions -> `fast_path_rule`
- repeated backend/model corrections -> `routing_preference_patch`
- repeated user preference statements -> `memory_patch`
- [ ] **Step 5: 运行测试确认通过**
Run: `npx tsx --test tests/master-agent-evolution-engine.test.ts`
Expected: PASS
- [ ] **Step 6: 提交**
```bash
git add src/lib/master-agent-evolution.ts tests/master-agent-evolution-engine.test.ts
git commit -m "feat: add master agent evolution engine"
```
### Task 3: 把 Evolution Engine 挂到主Agent主链
**Files:**
- Modify: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/lib/boss-master-agent.ts`
- Test: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/tests/master-agent-evolution-integration.test.ts`
- [ ] **Step 1: 写失败测试,验证快路径未命中后会记录 signal**
```ts
test("master agent slow deterministic query records evolution signal when fast path misses", async () => {
await replyToMasterAgentUserMessage({
requestText: "当前线程是不是正在运行",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
mode: "enqueue",
});
const signals = await listMasterAgentEvolutionSignals();
assert.ok(signals.some((item) => item.kind === "fast_path_candidate"));
});
```
- [ ] **Step 2: 运行测试,确认失败**
Run: `npx tsx --test tests/master-agent-evolution-integration.test.ts`
Expected: FAIL
- [ ] **Step 3: 在 `replyToMasterAgentUserMessage()` 和相关成功/失败路径中记录 signal**
最小挂载点:
- Fast Path 命中
- Fast Path 未命中但命中确定性查询模式
- 后端 fallback
- 用户手动切模型/切后端/切接管
- [ ] **Step 4: 运行测试确认通过**
Run: `npx tsx --test tests/master-agent-evolution-integration.test.ts`
Expected: PASS
- [ ] **Step 5: 提交**
```bash
git add src/lib/boss-master-agent.ts tests/master-agent-evolution-integration.test.ts
git commit -m "feat: integrate evolution engine into master agent"
```
### Task 4: 实现 Evolution API
**Files:**
- Create: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/app/api/v1/master-agent/evolution/route.ts`
- Create: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/app/api/v1/master-agent/evolution/config/route.ts`
- Create: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/app/api/v1/master-agent/evolution/proposals/[proposalId]/approve/route.ts`
- Create: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/app/api/v1/master-agent/evolution/proposals/[proposalId]/reject/route.ts`
- Test: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/tests/master-agent-evolution-routes.test.ts`
- [ ] **Step 1: 写失败测试,定义 GET/POST contract**
```ts
test("GET /api/v1/master-agent/evolution returns config proposals and rules", async () => {
const response = await GET(request);
const payload = await response.json();
assert.equal(payload.ok, true);
assert.ok(Array.isArray(payload.proposals));
});
```
- [ ] **Step 2: 运行测试,确认失败**
Run: `npx tsx --test tests/master-agent-evolution-routes.test.ts`
Expected: FAIL
- [ ] **Step 3: 实现读接口和 config 切换接口**
- [ ] **Step 4: 实现 approve / reject 接口**
- [ ] **Step 5: 运行测试确认通过**
Run: `npx tsx --test tests/master-agent-evolution-routes.test.ts`
Expected: PASS
- [ ] **Step 6: 提交**
```bash
git add src/app/api/v1/master-agent/evolution src/app/api/v1/master-agent/evolution/config src/app/api/v1/master-agent/evolution/proposals tests/master-agent-evolution-routes.test.ts
git commit -m "feat: add master agent evolution routes"
```
### Task 5: 加入 Controlled / Autonomous 自动采纳分界
**Files:**
- Modify: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/src/lib/master-agent-evolution.ts`
- Test: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/tests/master-agent-evolution-autonomous.test.ts`
- [ ] **Step 1: 写失败测试,验证 controlled 不自动采纳、autonomous 可自动采纳低风险 proposal**
```ts
test("controlled mode keeps proposal pending_review", async () => {
await setMasterAgentEvolutionMode("controlled");
// record signal...
assert.equal(proposals[0]?.status, "pending_review");
});
test("autonomous mode auto applies low risk routing preference patch", async () => {
await setMasterAgentEvolutionMode("autonomous");
// record signal...
assert.equal(proposals[0]?.status, "auto_applied");
});
```
- [ ] **Step 2: 运行测试,确认失败**
Run: `npx tsx --test tests/master-agent-evolution-autonomous.test.ts`
Expected: FAIL
- [ ] **Step 3: 实现 mode gating 与低风险自动采纳白名单**
白名单:
- `memory_patch`
- `routing_preference_patch`
- `fast_path_rule`
黑名单:
- 管理员全局主提示词修改
- 高风险提示词覆盖
- [ ] **Step 4: 运行测试确认通过**
Run: `npx tsx --test tests/master-agent-evolution-autonomous.test.ts`
Expected: PASS
- [ ] **Step 5: 提交**
```bash
git add src/lib/master-agent-evolution.ts tests/master-agent-evolution-autonomous.test.ts
git commit -m "feat: add controlled and autonomous evolution modes"
```
### Task 6: 更新交接文档并切双分支
**Files:**
- Modify: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/docs/architecture/ai_handoff_index_cn.md`
- Modify: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/docs/architecture/current_runtime_and_deploy_status_cn.md`
- Modify: `/Users/kris/.config/superpowers/worktrees/boss/codex-hermes-backend-mvp/docs/superpowers/specs/2026-04-16-master-agent-evolution-engine-design.md`
- [ ] **Step 1: 文档中加入 evolution engine 与两种模式说明**
- [ ] **Step 2: 运行组合验证**
Run: `npx tsx --test tests/master-agent-evolution*.test.ts tests/master-agent-message-queue.test.ts tests/master-agent-chat-controls.test.ts`
Expected: PASS
Run: `npm run build`
Expected: PASS
- [ ] **Step 3: 提交共享内核**
```bash
git add docs/architecture/ai_handoff_index_cn.md docs/architecture/current_runtime_and_deploy_status_cn.md docs/superpowers/specs/2026-04-16-master-agent-evolution-engine-design.md
git commit -m "docs: document master agent evolution engine"
```
- [ ] **Step 4: 创建 controlled 分支并推送**
```bash
git branch codex/master-agent-controlled-evolution
git push gitea codex/master-agent-controlled-evolution
```
- [ ] **Step 5: 创建 autonomous 分支,在其上把默认 mode 切到 autonomous 并推送**
```bash
git checkout -b codex/master-agent-autonomous-evolution
git push gitea codex/master-agent-autonomous-evolution
```

View File

@@ -0,0 +1,198 @@
# 主Agent 自动进化引擎设计
更新时间:`2026-04-16`
## 1. 背景
当前 Boss 主Agent 已经具备:
- 长期记忆:`masterAgentMemories`
- 提示词策略:`masterAgentPromptPolicy``userMasterPrompts`
- 对话控制:`userProjectAgentControls`
- 运行时自适应Master Node / Hermes / Claw / API fallback
但它还不具备真正的自动进化闭环。现状只能“记住”与“回退”,不能持续发现问题、形成提案、沉淀规则并在后续回复中生效。
## 2. 目标
- 给主Agent增加统一的 `Evolution Engine`
- 支持两种能力范围:
- `controlled`:受控自动进化,只生成提案,等待用户批准后落地
- `autonomous`:完全自我进化,对高置信度提案自动落地
- 进化逻辑对现有主链最小侵入,不重写现有回复链、任务链、记忆链
- 所有进化行为可追踪、可审计、可回滚
## 3. 核心概念
### 3.1 Evolution Signal
进化信号,记录“为什么应该考虑进化”。
第一批信号来源:
- 高频重复问句
- 慢回复问题
- 用户纠正主Agent
- 模型/后端频繁手动切换
- 后端回退或执行失败
- Fast Path 未命中但属于确定性状态问题
### 3.2 Evolution Proposal
进化提案,记录“建议怎么改”。
第一批提案类型:
- `fast_path_rule`
- `prompt_policy_patch`
- `memory_patch`
- `agent_control_patch`
- `routing_preference_patch`
### 3.3 Evolution Rule
已生效规则,表示被批准或自动采纳后真正进入系统长期状态的变更。
### 3.4 Evolution Run Log
进化执行日志,记录某次信号分析、提案生成、采纳、拒绝、自动落地的全过程。
## 4. 运行模式
### 4.1 Controlled
受控自动进化:
- 主Agent可以自动记录 signal
- 可以自动生成 proposal
- 不允许自动改长期状态
- 只能在用户批准后写入 memory / prompt / controls / fast-path rules
适合生产稳态与审计要求更高的环境。
### 4.2 Autonomous
完全自我进化:
- 主Agent可以自动记录 signal
- 自动生成 proposal
- 对满足阈值的 proposal 直接自动采纳
- 自动把结果写入长期状态
- 仍保留完整日志,允许后续回滚
适合开发期快速迭代,用更高风险换更快自优化速度。
## 5. 架构
### 5.1 新增状态模型
`BossState` 中新增:
- `masterAgentEvolutionConfig`
- `masterAgentEvolutionSignals`
- `masterAgentEvolutionProposals`
- `masterAgentEvolutionRules`
- `masterAgentEvolutionRunLogs`
### 5.2 新增引擎文件
- `src/lib/master-agent-evolution.ts`
- 统一入口
- signal 归一化
- proposal 生成
- 自动采纳判断
- 规则落地
- `src/lib/master-agent-evolution-rules.ts`
- Fast Path 规则、路由偏好规则解析
- `src/lib/master-agent-evolution-projections.ts`
- 给 Web/API 返回 evolution 状态摘要
### 5.3 挂载点
第一批最小侵入挂载点:
- `replyToMasterAgentUserMessage()`
- 记录用户问题、Fast Path 命中/未命中、慢路径耗时、模型/后端实际选择
- `appendMasterAgentSystemReply()`
- 记录主Agent输出结果
- `completeMasterAgentTask()`
- 记录异步任务成功/失败、fallback、超时
- `updateProjectAgentControls()` / `updateMasterAgentPromptPolicy()` / `createUserMasterMemory()`
- 记录用户主动纠偏行为,作为 evolution signal 输入
## 6. 第一批自动进化能力
### Controlled 与 Autonomous 共用
- 记录重复问句
- 记录低价值慢路径
- 记录用户纠正模型/后端/接管设置
- 自动生成三类提案:
- 新增 Fast Path 规则
- 调整默认快模型/强模型/后端偏好
- 追加 workflow_rule / user_preference 记忆
### Controlled 专属行为
- 所有提案状态默认为 `pending_review`
- 需要用户批准后才能落地
### Autonomous 专属行为
- 当 proposal 满足“高置信度 + 低破坏性”时自动落地
- 第一批只允许自动落地:
- `memory_patch`
- `routing_preference_patch`
- `fast_path_rule`
- 不允许自动改管理员全局主提示词
## 7. 前台入口
第一批只做最小 Web 能力:
- `GET /api/v1/master-agent/evolution`
- 返回 config / pending proposals / recent run logs / effective rules
- `POST /api/v1/master-agent/evolution/config`
- 切换 `controlled` / `autonomous`
- `POST /api/v1/master-agent/evolution/proposals/[proposalId]/approve`
- `POST /api/v1/master-agent/evolution/proposals/[proposalId]/reject`
先不做复杂 UI先把数据和接口打通必要时从 `我的 > 主Agent` 进入。
## 8. 双分支策略
统一内核先落在共享提交里:
- evolution state
- evolution engine
- proposal / rule / run-log 结构
- API 与测试
然后从共享基线分两条分支:
- `codex/master-agent-controlled-evolution`
- 默认 `mode=controlled`
- 关闭自动采纳
- `codex/master-agent-autonomous-evolution`
- 默认 `mode=autonomous`
- 开启自动采纳阈值逻辑
用户先使用 autonomous 分支验证真实开发效率;一旦发现策略漂移或错误写入,可直接回退 controlled 分支。
## 9. 风险控制
- 管理员全局主提示词永远不允许 autonomous 自动改写
- 自动落地范围先限制在低风险对象
- 每次自动采纳都必须写 run log
- 所有 rule 都要带 `sourceProposalId`
- 所有 proposal 都要可拒绝、可归档、可回滚
## 10. 验证基线
- `npx tsx --test tests/master-agent-evolution*.test.ts`
- `npx tsx --test tests/master-agent-message-queue.test.ts tests/master-agent-chat-controls.test.ts`
- `npm run build`
- 线上切到 `autonomous` 后:
- 主Agent可记录进化信号
- 可自动生成并落地低风险规则
- `controlled` 分支同一套信号与提案只记录、不自动采纳

View File

@@ -0,0 +1,44 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { jsonNoStore } from "@/lib/api-response";
import { setMasterAgentEvolutionMode } from "@/lib/master-agent-evolution";
export const runtime = "nodejs";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return jsonNoStore({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
return jsonNoStore({ ok: false, message: "METHOD_NOT_ALLOWED" }, { status: 405 });
}
export async function POST(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
if (session.role !== "highest_admin") {
return NextResponse.json({ ok: false, message: "FORBIDDEN" }, { status: 403 });
}
const rawBody = await request.text().catch(() => "");
let body: unknown;
try {
body = JSON.parse(rawBody);
} catch {
return NextResponse.json({ ok: false, message: "INVALID_JSON_PAYLOAD" }, { status: 400 });
}
if (!body || typeof body !== "object" || Array.isArray(body)) {
return NextResponse.json({ ok: false, message: "INVALID_EVOLUTION_CONFIG_PAYLOAD" }, { status: 400 });
}
const payload = body as { mode?: unknown };
if (payload.mode !== "controlled" && payload.mode !== "autonomous") {
return NextResponse.json({ ok: false, message: "INVALID_EVOLUTION_MODE" }, { status: 400 });
}
const config = await setMasterAgentEvolutionMode(payload.mode);
return NextResponse.json({ ok: true, config });
}

View File

@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { approveMasterAgentEvolutionProposal } from "@/lib/master-agent-evolution";
export const runtime = "nodejs";
export async function POST(
request: NextRequest,
context: { params: Promise<{ proposalId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
if (session.role !== "highest_admin") {
return NextResponse.json({ ok: false, message: "FORBIDDEN" }, { status: 403 });
}
const { proposalId } = await context.params;
try {
const proposal = await approveMasterAgentEvolutionProposal(proposalId, session.account);
return NextResponse.json({ ok: true, proposal });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { rejectMasterAgentEvolutionProposal } from "@/lib/master-agent-evolution";
export const runtime = "nodejs";
export async function POST(
request: NextRequest,
context: { params: Promise<{ proposalId: string }> },
) {
const session = await requireRequestSession(request);
if (!session) {
return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
if (session.role !== "highest_admin") {
return NextResponse.json({ ok: false, message: "FORBIDDEN" }, { status: 403 });
}
const { proposalId } = await context.params;
try {
const proposal = await rejectMasterAgentEvolutionProposal(proposalId, session.account);
return NextResponse.json({ ok: true, proposal });
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },
{ status: 400 },
);
}
}

View File

@@ -0,0 +1,16 @@
import { NextRequest } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { jsonNoStore } from "@/lib/api-response";
import { getMasterAgentEvolutionDashboard } from "@/lib/master-agent-evolution";
export const runtime = "nodejs";
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return jsonNoStore({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
const dashboard = await getMasterAgentEvolutionDashboard();
return jsonNoStore({ ok: true, ...dashboard });
}

View File

@@ -270,6 +270,86 @@ export interface MasterAgentMemory {
archived: boolean; archived: boolean;
} }
export type MasterAgentEvolutionMode = "controlled" | "autonomous";
export type MasterAgentEvolutionSignalKind =
| "repeated_question"
| "fast_path_candidate"
| "slow_path"
| "user_correction"
| "backend_fallback";
export type MasterAgentEvolutionProposalType =
| "fast_path_rule"
| "prompt_policy_patch"
| "memory_patch"
| "agent_control_patch"
| "routing_preference_patch";
export type MasterAgentEvolutionProposalStatus =
| "pending_review"
| "approved"
| "rejected"
| "auto_applied";
export type MasterAgentEvolutionRunAction =
| "signal_recorded"
| "proposal_created"
| "proposal_approved"
| "proposal_rejected"
| "proposal_auto_applied";
export interface MasterAgentEvolutionConfig {
mode: MasterAgentEvolutionMode;
autoApplyLowRiskRules: boolean;
updatedAt: string;
}
export interface MasterAgentEvolutionSignal {
signalId: string;
kind: MasterAgentEvolutionSignalKind;
account: string;
projectId: string;
requestText: string;
replyText?: string;
metadataJson?: Record<string, unknown>;
createdAt: string;
}
export interface MasterAgentEvolutionProposal {
proposalId: string;
proposalType: MasterAgentEvolutionProposalType;
status: MasterAgentEvolutionProposalStatus;
account: string;
projectId: string;
title: string;
summary: string;
patchJson: Record<string, unknown>;
sourceSignalIds: string[];
confidence: number;
riskLevel: "low" | "medium" | "high";
createdAt: string;
updatedAt: string;
}
export interface MasterAgentEvolutionRule {
ruleId: string;
ruleType: MasterAgentEvolutionProposalType;
account: string;
projectId?: string;
sourceProposalId: string;
patchJson: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}
export interface MasterAgentEvolutionRunLog {
runId: string;
action: MasterAgentEvolutionRunAction;
account: string;
projectId: string;
signalId?: string;
proposalId?: string;
note: string;
createdAt: string;
}
export interface GoalItem { export interface GoalItem {
id: string; id: string;
text: string; text: string;
@@ -1078,6 +1158,11 @@ export interface BossState {
masterAgentPromptPolicy: MasterAgentPromptPolicy | null; masterAgentPromptPolicy: MasterAgentPromptPolicy | null;
userMasterPrompts: UserMasterPrompt[]; userMasterPrompts: UserMasterPrompt[];
masterAgentMemories: MasterAgentMemory[]; masterAgentMemories: MasterAgentMemory[];
masterAgentEvolutionConfig: MasterAgentEvolutionConfig;
masterAgentEvolutionSignals: MasterAgentEvolutionSignal[];
masterAgentEvolutionProposals: MasterAgentEvolutionProposal[];
masterAgentEvolutionRules: MasterAgentEvolutionRule[];
masterAgentEvolutionRunLogs: MasterAgentEvolutionRunLog[];
userProjectAgentControls: UserProjectAgentControls[]; userProjectAgentControls: UserProjectAgentControls[];
threadContextSnapshots: ThreadContextSnapshot[]; threadContextSnapshots: ThreadContextSnapshot[];
threadHandoffPackages: ThreadHandoffPackage[]; threadHandoffPackages: ThreadHandoffPackage[];
@@ -1451,6 +1536,15 @@ const initialState: BossState = {
masterAgentPromptPolicy: null, masterAgentPromptPolicy: null,
userMasterPrompts: [], userMasterPrompts: [],
masterAgentMemories: [], masterAgentMemories: [],
masterAgentEvolutionConfig: {
mode: "controlled",
autoApplyLowRiskRules: false,
updatedAt: nowIso(),
},
masterAgentEvolutionSignals: [],
masterAgentEvolutionProposals: [],
masterAgentEvolutionRules: [],
masterAgentEvolutionRunLogs: [],
userProjectAgentControls: [], userProjectAgentControls: [],
masterAgentTasks: [], masterAgentTasks: [],
dispatchPlans: [], dispatchPlans: [],
@@ -2967,6 +3061,101 @@ function normalizeUserMasterMemory(
}; };
} }
function normalizeMasterAgentEvolutionConfig(
raw: Partial<MasterAgentEvolutionConfig> | null | undefined,
fallback?: MasterAgentEvolutionConfig,
): MasterAgentEvolutionConfig {
return {
mode: raw?.mode === "autonomous" ? "autonomous" : fallback?.mode ?? "controlled",
autoApplyLowRiskRules:
typeof raw?.autoApplyLowRiskRules === "boolean"
? raw.autoApplyLowRiskRules
: fallback?.autoApplyLowRiskRules ?? false,
updatedAt: raw?.updatedAt ?? fallback?.updatedAt ?? nowIso(),
};
}
function normalizeMasterAgentEvolutionSignal(
raw: Partial<MasterAgentEvolutionSignal>,
fallback?: MasterAgentEvolutionSignal,
): MasterAgentEvolutionSignal {
return {
signalId: raw.signalId ?? fallback?.signalId ?? randomToken("evo-signal"),
kind: raw.kind ?? fallback?.kind ?? "slow_path",
account: trimToDefined(raw.account ?? fallback?.account) ?? "",
projectId: trimToDefined(raw.projectId ?? fallback?.projectId) ?? "master-agent",
requestText: trimToDefined(raw.requestText ?? fallback?.requestText) ?? "",
replyText: trimToDefined(raw.replyText ?? fallback?.replyText),
metadataJson:
raw.metadataJson && typeof raw.metadataJson === "object"
? raw.metadataJson
: fallback?.metadataJson ?? {},
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
};
}
function normalizeMasterAgentEvolutionProposal(
raw: Partial<MasterAgentEvolutionProposal>,
fallback?: MasterAgentEvolutionProposal,
): MasterAgentEvolutionProposal {
return {
proposalId: raw.proposalId ?? fallback?.proposalId ?? randomToken("evo-proposal"),
proposalType: raw.proposalType ?? fallback?.proposalType ?? "memory_patch",
status: raw.status ?? fallback?.status ?? "pending_review",
account: trimToDefined(raw.account ?? fallback?.account) ?? "",
projectId: trimToDefined(raw.projectId ?? fallback?.projectId) ?? "master-agent",
title: trimToDefined(raw.title ?? fallback?.title) ?? "进化提案",
summary: trimToDefined(raw.summary ?? fallback?.summary) ?? "",
patchJson:
raw.patchJson && typeof raw.patchJson === "object"
? raw.patchJson
: fallback?.patchJson ?? {},
sourceSignalIds: dedupeStrings(
ensureArray(raw.sourceSignalIds, fallback?.sourceSignalIds ?? []).map((value) => value.trim()),
),
confidence:
typeof raw.confidence === "number" ? raw.confidence : fallback?.confidence ?? 0.5,
riskLevel: raw.riskLevel ?? fallback?.riskLevel ?? "low",
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(),
};
}
function normalizeMasterAgentEvolutionRule(
raw: Partial<MasterAgentEvolutionRule>,
fallback?: MasterAgentEvolutionRule,
): MasterAgentEvolutionRule {
return {
ruleId: raw.ruleId ?? fallback?.ruleId ?? randomToken("evo-rule"),
ruleType: raw.ruleType ?? fallback?.ruleType ?? "memory_patch",
account: trimToDefined(raw.account ?? fallback?.account) ?? "",
projectId: trimToDefined(raw.projectId ?? fallback?.projectId),
sourceProposalId: trimToDefined(raw.sourceProposalId ?? fallback?.sourceProposalId) ?? "",
patchJson:
raw.patchJson && typeof raw.patchJson === "object"
? raw.patchJson
: fallback?.patchJson ?? {},
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(),
};
}
function normalizeMasterAgentEvolutionRunLog(
raw: Partial<MasterAgentEvolutionRunLog>,
fallback?: MasterAgentEvolutionRunLog,
): MasterAgentEvolutionRunLog {
return {
runId: raw.runId ?? fallback?.runId ?? randomToken("evo-run"),
action: raw.action ?? fallback?.action ?? "signal_recorded",
account: trimToDefined(raw.account ?? fallback?.account) ?? "",
projectId: trimToDefined(raw.projectId ?? fallback?.projectId) ?? "master-agent",
signalId: trimToDefined(raw.signalId ?? fallback?.signalId),
proposalId: trimToDefined(raw.proposalId ?? fallback?.proposalId),
note: trimToDefined(raw.note ?? fallback?.note) ?? "",
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
};
}
function normalizeProject(raw: Partial<Project>, fallback?: Project): Project { function normalizeProject(raw: Partial<Project>, fallback?: Project): Project {
const base = fallback ?? cloneInitialState().projects[0]; const base = fallback ?? cloneInitialState().projects[0];
const projectId = raw.id ?? base.id; const projectId = raw.id ?? base.id;
@@ -3495,6 +3684,46 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
base.masterAgentMemories[index % Math.max(1, base.masterAgentMemories.length)], base.masterAgentMemories[index % Math.max(1, base.masterAgentMemories.length)],
), ),
), ),
masterAgentEvolutionConfig: normalizeMasterAgentEvolutionConfig(
raw.masterAgentEvolutionConfig,
base.masterAgentEvolutionConfig,
),
masterAgentEvolutionSignals: ensureArray(
raw.masterAgentEvolutionSignals,
base.masterAgentEvolutionSignals,
).map((signal, index) =>
normalizeMasterAgentEvolutionSignal(
signal,
base.masterAgentEvolutionSignals[index % Math.max(1, base.masterAgentEvolutionSignals.length)],
),
),
masterAgentEvolutionProposals: ensureArray(
raw.masterAgentEvolutionProposals,
base.masterAgentEvolutionProposals,
).map((proposal, index) =>
normalizeMasterAgentEvolutionProposal(
proposal,
base.masterAgentEvolutionProposals[index % Math.max(1, base.masterAgentEvolutionProposals.length)],
),
),
masterAgentEvolutionRules: ensureArray(
raw.masterAgentEvolutionRules,
base.masterAgentEvolutionRules,
).map((rule, index) =>
normalizeMasterAgentEvolutionRule(
rule,
base.masterAgentEvolutionRules[index % Math.max(1, base.masterAgentEvolutionRules.length)],
),
),
masterAgentEvolutionRunLogs: ensureArray(
raw.masterAgentEvolutionRunLogs,
base.masterAgentEvolutionRunLogs,
).map((log, index) =>
normalizeMasterAgentEvolutionRunLog(
log,
base.masterAgentEvolutionRunLogs[index % Math.max(1, base.masterAgentEvolutionRunLogs.length)],
),
),
userProjectAgentControls: ensureArray( userProjectAgentControls: ensureArray(
raw.userProjectAgentControls, raw.userProjectAgentControls,
base.userProjectAgentControls, base.userProjectAgentControls,
@@ -4991,6 +5220,191 @@ export async function touchUserMasterMemories(memoryIds: string[], account: stri
}); });
} }
export async function getMasterAgentEvolutionState() {
const state = await readState();
return {
config: state.masterAgentEvolutionConfig,
signals: [...state.masterAgentEvolutionSignals],
proposals: [...state.masterAgentEvolutionProposals],
rules: [...state.masterAgentEvolutionRules],
runLogs: [...state.masterAgentEvolutionRunLogs],
};
}
export async function updateMasterAgentEvolutionConfig(input: {
mode?: MasterAgentEvolutionMode;
autoApplyLowRiskRules?: boolean;
}) {
const result = await mutateState((state) => {
const current = state.masterAgentEvolutionConfig;
const next = normalizeMasterAgentEvolutionConfig({
mode: input.mode ?? current.mode,
autoApplyLowRiskRules:
typeof input.autoApplyLowRiskRules === "boolean"
? input.autoApplyLowRiskRules
: current.autoApplyLowRiskRules,
updatedAt: nowIso(),
}, current);
if (next.mode === "controlled") {
next.autoApplyLowRiskRules = false;
}
if (next.mode === "autonomous" && input.autoApplyLowRiskRules === undefined) {
next.autoApplyLowRiskRules = true;
}
state.masterAgentEvolutionConfig = next;
return next;
});
publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
return result;
}
export async function recordMasterAgentEvolutionSignalInState(input: {
kind: MasterAgentEvolutionSignalKind;
account: string;
projectId?: string;
requestText: string;
replyText?: string;
metadataJson?: Record<string, unknown>;
}) {
const result = await mutateState((state) => {
const signal = normalizeMasterAgentEvolutionSignal({
signalId: randomToken("evo-signal"),
kind: input.kind,
account: input.account,
projectId: input.projectId ?? "master-agent",
requestText: input.requestText,
replyText: input.replyText,
metadataJson: input.metadataJson ?? {},
createdAt: nowIso(),
});
state.masterAgentEvolutionSignals.unshift(signal);
state.masterAgentEvolutionRunLogs.unshift(
normalizeMasterAgentEvolutionRunLog({
runId: randomToken("evo-run"),
action: "signal_recorded",
account: signal.account,
projectId: signal.projectId,
signalId: signal.signalId,
note: `记录进化信号:${signal.kind}`,
createdAt: nowIso(),
}),
);
return signal;
});
publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
return result;
}
export async function createMasterAgentEvolutionProposalInState(input: {
proposalType: MasterAgentEvolutionProposalType;
account: string;
projectId?: string;
title: string;
summary: string;
patchJson: Record<string, unknown>;
sourceSignalIds: string[];
confidence: number;
riskLevel?: "low" | "medium" | "high";
status?: MasterAgentEvolutionProposalStatus;
}) {
const result = await mutateState((state) => {
const now = nowIso();
const proposal = normalizeMasterAgentEvolutionProposal({
proposalId: randomToken("evo-proposal"),
proposalType: input.proposalType,
status: input.status ?? "pending_review",
account: input.account,
projectId: input.projectId ?? "master-agent",
title: input.title,
summary: input.summary,
patchJson: input.patchJson,
sourceSignalIds: input.sourceSignalIds,
confidence: input.confidence,
riskLevel: input.riskLevel ?? "low",
createdAt: now,
updatedAt: now,
});
state.masterAgentEvolutionProposals.unshift(proposal);
state.masterAgentEvolutionRunLogs.unshift(
normalizeMasterAgentEvolutionRunLog({
runId: randomToken("evo-run"),
action: proposal.status === "auto_applied" ? "proposal_auto_applied" : "proposal_created",
account: proposal.account,
projectId: proposal.projectId,
proposalId: proposal.proposalId,
note: proposal.title,
createdAt: now,
}),
);
return proposal;
});
publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
return result;
}
export async function updateMasterAgentEvolutionProposalStatus(input: {
proposalId: string;
status: MasterAgentEvolutionProposalStatus;
account: string;
note?: string;
}) {
const result = await mutateState((state) => {
const proposal = state.masterAgentEvolutionProposals.find((item) => item.proposalId === input.proposalId);
if (!proposal) {
throw new Error("MASTER_AGENT_EVOLUTION_PROPOSAL_NOT_FOUND");
}
proposal.status = input.status;
proposal.updatedAt = nowIso();
state.masterAgentEvolutionRunLogs.unshift(
normalizeMasterAgentEvolutionRunLog({
runId: randomToken("evo-run"),
action:
input.status === "approved"
? "proposal_approved"
: input.status === "rejected"
? "proposal_rejected"
: input.status === "auto_applied"
? "proposal_auto_applied"
: "proposal_created",
account: input.account,
projectId: proposal.projectId,
proposalId: proposal.proposalId,
note: input.note ?? proposal.title,
createdAt: nowIso(),
}),
);
return proposal;
});
publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
return result;
}
export async function createMasterAgentEvolutionRuleInState(input: {
ruleType: MasterAgentEvolutionProposalType;
account: string;
projectId?: string;
sourceProposalId: string;
patchJson: Record<string, unknown>;
}) {
const result = await mutateState((state) => {
const now = nowIso();
const rule = normalizeMasterAgentEvolutionRule({
ruleId: randomToken("evo-rule"),
ruleType: input.ruleType,
account: input.account,
projectId: input.projectId,
sourceProposalId: input.sourceProposalId,
patchJson: input.patchJson,
createdAt: now,
updatedAt: now,
});
state.masterAgentEvolutionRules.unshift(rule);
return rule;
});
publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
return result;
}
function normalizeAutoMemoryText(value: string | undefined) { function normalizeAutoMemoryText(value: string | undefined) {
return (value ?? "") return (value ?? "")
.replace(/\s+/g, " ") .replace(/\s+/g, " ")

View File

@@ -60,6 +60,7 @@ import {
listUserMasterMemoriesView, listUserMasterMemoriesView,
} from "@/lib/boss-projections"; } from "@/lib/boss-projections";
import { normalizeRemoteExecutionResult } from "@/lib/execution/remote-runtime-adapter"; import { normalizeRemoteExecutionResult } from "@/lib/execution/remote-runtime-adapter";
import { recordMasterAgentEvolutionSignal } from "@/lib/master-agent-evolution";
type MasterAgentReplyState = "queued" | "running" | "completed"; type MasterAgentReplyState = "queued" | "running" | "completed";
type MasterAgentExecutionIntent = "chat" | "deep_task"; type MasterAgentExecutionIntent = "chat" | "deep_task";
@@ -1950,6 +1951,27 @@ async function appendFastPathError(
}; };
} }
async function tryRecordMasterAgentEvolutionSignal(input: {
kind: "fast_path_candidate" | "user_correction" | "backend_fallback";
account: string;
requestText: string;
replyText?: string;
metadataJson?: Record<string, unknown>;
}) {
try {
await recordMasterAgentEvolutionSignal({
kind: input.kind,
account: input.account,
projectId: "master-agent",
requestText: input.requestText,
replyText: input.replyText,
metadataJson: input.metadataJson,
});
} catch {
// Evolution capture is best-effort and must not break replies.
}
}
function buildModelSummaryReply(context: MasterAgentFastIntentContext, requestText: string) { function buildModelSummaryReply(context: MasterAgentFastIntentContext, requestText: string) {
const normalized = normalizeLexicalText(requestText); const normalized = normalizeLexicalText(requestText);
const manualModel = context.agentControls?.modelOverride?.trim() || ""; const manualModel = context.agentControls?.modelOverride?.trim() || "";
@@ -2060,10 +2082,15 @@ async function tryHandleMasterAgentBackendSwitchCommand(params: {
{ backendOverride: requestedBackend }, { backendOverride: requestedBackend },
params.requestedByAccount, params.requestedByAccount,
); );
return appendFastPathReply( const reply = `已把默认后端切到 ${requestedBackend}`;
`已把默认后端切到 ${requestedBackend}`, await tryRecordMasterAgentEvolutionSignal({
buildMasterAgentModelSenderLabel(params.context.effectiveChatPolicy.model), kind: "user_correction",
); account: params.requestedByAccount,
requestText: params.requestText,
replyText: reply,
metadataJson: { requestedBackend },
});
return appendFastPathReply(reply, buildMasterAgentModelSenderLabel(params.context.effectiveChatPolicy.model));
} }
async function tryHandleMasterAgentTakeoverCommand(params: { async function tryHandleMasterAgentTakeoverCommand(params: {
@@ -2099,10 +2126,15 @@ async function tryHandleMasterAgentTakeoverCommand(params: {
{ globalTakeoverEnabled: nextEnabled }, { globalTakeoverEnabled: nextEnabled },
params.requestedByAccount, params.requestedByAccount,
); );
return appendFastPathReply( const reply = nextEnabled ? "已开启全局接管。" : "已关闭全局接管。";
nextEnabled ? "已开启全局接管。" : "已关闭全局接管。", await tryRecordMasterAgentEvolutionSignal({
buildMasterAgentModelSenderLabel(params.context.effectiveChatPolicy.model), kind: "user_correction",
); account: params.requestedByAccount,
requestText: params.requestText,
replyText: reply,
metadataJson: { globalTakeoverEnabled: nextEnabled },
});
return appendFastPathReply(reply, buildMasterAgentModelSenderLabel(params.context.effectiveChatPolicy.model));
} }
async function tryHandleMasterAgentExecutionModeStatusQuery(params: { async function tryHandleMasterAgentExecutionModeStatusQuery(params: {
@@ -2228,6 +2260,13 @@ async function tryHandleMasterAgentModelCommand(params: {
await updateProjectAgentControls("master-agent", patch, params.requestedByAccount); await updateProjectAgentControls("master-agent", patch, params.requestedByAccount);
const reply = `已把主 Agent 的${scopeLabel}切到 ${requestedModel}${availableModelsSuffix}`; const reply = `已把主 Agent 的${scopeLabel}切到 ${requestedModel}${availableModelsSuffix}`;
await tryRecordMasterAgentEvolutionSignal({
kind: "user_correction",
account: params.requestedByAccount,
requestText: params.requestText,
replyText: reply,
metadataJson: { scope, requestedModel },
});
return appendFastPathReply(reply, buildMasterAgentModelSenderLabel(requestedModel)); return appendFastPathReply(reply, buildMasterAgentModelSenderLabel(requestedModel));
} }
@@ -3219,6 +3258,17 @@ export async function replyToMasterAgentUserMessage(params: {
return fastIntentResult; 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(); const runtime = await getMasterAgentRuntimeAccount();
if (!runtime?.account) { if (!runtime?.account) {

View File

@@ -0,0 +1,226 @@
import {
createMasterAgentEvolutionProposalInState,
createMasterAgentEvolutionRuleInState,
createUserMasterMemory,
getMasterAgentEvolutionState,
recordMasterAgentEvolutionSignalInState,
updateMasterAgentEvolutionConfig,
updateMasterAgentEvolutionProposalStatus,
updateProjectAgentControls,
} from "@/lib/boss-data";
import type {
MasterAgentEvolutionProposal,
MasterAgentEvolutionProposalType,
MasterAgentEvolutionSignalKind,
MasterAgentEvolutionMode,
} from "@/lib/boss-data";
const AUTO_APPLY_LOW_RISK_TYPES = new Set<MasterAgentEvolutionProposalType>([
"fast_path_rule",
"memory_patch",
"routing_preference_patch",
]);
function isDeterministicStatusQuestion(text: string) {
return /(当前|现在|有没有|是否|什么|哪个|在线|状态|模型|后端|接管|gui|cli|hermes|claw)/i.test(text);
}
function inferProposalFromSignal(input: {
kind: MasterAgentEvolutionSignalKind;
account: string;
projectId: string;
requestText: string;
signalId: string;
}): Omit<
Parameters<typeof createMasterAgentEvolutionProposalInState>[0],
"status"
> | null {
const requestText = input.requestText.trim();
if (
(input.kind === "repeated_question" || input.kind === "fast_path_candidate") &&
isDeterministicStatusQuestion(requestText)
) {
return {
proposalType: "fast_path_rule",
account: input.account,
projectId: input.projectId,
title: `新增 Fast Path${requestText.slice(0, 32)}`,
summary: `检测到确定性问题反复进入主链,建议把“${requestText}”归入本地 Fast Path。`,
patchJson: {
matcher: requestText,
action: "local_status_reply",
},
sourceSignalIds: [input.signalId],
confidence: 0.82,
riskLevel: "low",
};
}
if (input.kind === "user_correction") {
return {
proposalType: "memory_patch",
account: input.account,
projectId: input.projectId,
title: "沉淀用户纠正",
summary: `检测到用户纠正主Agent行为建议写入长期工作规则${requestText}`,
patchJson: {
scope: "global",
memoryType: "workflow_rule",
title: "用户纠正 · 工作规则",
content: requestText,
},
sourceSignalIds: [input.signalId],
confidence: 0.78,
riskLevel: "low",
};
}
if (input.kind === "backend_fallback") {
return {
proposalType: "routing_preference_patch",
account: input.account,
projectId: input.projectId,
title: "调整后端路由偏好",
summary: "检测到后端回退,建议后续优先选择最近稳定的可用后端。",
patchJson: {
backendPreference: "prefer_available_runtime",
},
sourceSignalIds: [input.signalId],
confidence: 0.72,
riskLevel: "low",
};
}
return null;
}
async function applyLowRiskProposal(proposal: MasterAgentEvolutionProposal) {
if (proposal.proposalType === "memory_patch") {
const patch = proposal.patchJson as {
scope?: "global" | "project";
projectId?: string;
title?: string;
content?: string;
memoryType?: "workflow_rule" | "user_preference" | "decision" | "project_progress";
tags?: string[];
};
await createUserMasterMemory({
account: proposal.account,
scope: patch.scope ?? "global",
projectId: patch.scope === "project" ? patch.projectId ?? proposal.projectId : undefined,
title: patch.title ?? proposal.title,
content: patch.content ?? proposal.summary,
memoryType: patch.memoryType ?? "workflow_rule",
tags: patch.tags ?? ["evolution"],
});
}
if (proposal.proposalType === "routing_preference_patch") {
const patch = proposal.patchJson as { backendOverride?: "hermes-runtime" | "claw-runtime" };
if (patch.backendOverride) {
await updateProjectAgentControls("master-agent", { backendOverride: patch.backendOverride }, proposal.account);
}
}
await createMasterAgentEvolutionRuleInState({
ruleType: proposal.proposalType,
account: proposal.account,
projectId: proposal.projectId,
sourceProposalId: proposal.proposalId,
patchJson: proposal.patchJson,
});
}
async function maybeAutoApplyProposal(proposal: MasterAgentEvolutionProposal) {
const state = await getMasterAgentEvolutionState();
if (
state.config.mode !== "autonomous" ||
!state.config.autoApplyLowRiskRules ||
proposal.riskLevel !== "low" ||
proposal.confidence < 0.75 ||
!AUTO_APPLY_LOW_RISK_TYPES.has(proposal.proposalType)
) {
return proposal;
}
await applyLowRiskProposal(proposal);
return updateMasterAgentEvolutionProposalStatus({
proposalId: proposal.proposalId,
status: "auto_applied",
account: proposal.account,
note: "autonomous 模式自动采纳低风险提案",
});
}
export async function setMasterAgentEvolutionMode(mode: MasterAgentEvolutionMode) {
return updateMasterAgentEvolutionConfig({
mode,
autoApplyLowRiskRules: mode === "autonomous",
});
}
export async function listMasterAgentEvolutionSignals() {
return (await getMasterAgentEvolutionState()).signals;
}
export async function listMasterAgentEvolutionProposals() {
return (await getMasterAgentEvolutionState()).proposals;
}
export async function getMasterAgentEvolutionDashboard() {
return getMasterAgentEvolutionState();
}
export async function recordMasterAgentEvolutionSignal(input: {
kind: MasterAgentEvolutionSignalKind;
account: string;
projectId?: string;
requestText: string;
replyText?: string;
metadataJson?: Record<string, unknown>;
}) {
const signal = await recordMasterAgentEvolutionSignalInState({
kind: input.kind,
account: input.account,
projectId: input.projectId ?? "master-agent",
requestText: input.requestText,
replyText: input.replyText,
metadataJson: input.metadataJson,
});
const proposalInput = inferProposalFromSignal({
kind: signal.kind,
account: signal.account,
projectId: signal.projectId,
requestText: signal.requestText,
signalId: signal.signalId,
});
if (!proposalInput) {
return { signal, proposal: null };
}
const proposal = await createMasterAgentEvolutionProposalInState(proposalInput);
const appliedProposal = await maybeAutoApplyProposal(proposal);
return { signal, proposal: appliedProposal };
}
export async function approveMasterAgentEvolutionProposal(proposalId: string, account: string) {
const proposal = (await getMasterAgentEvolutionState()).proposals.find((item) => item.proposalId === proposalId);
if (!proposal) {
throw new Error("MASTER_AGENT_EVOLUTION_PROPOSAL_NOT_FOUND");
}
await applyLowRiskProposal(proposal);
return updateMasterAgentEvolutionProposalStatus({
proposalId,
status: "approved",
account,
note: "用户批准进化提案",
});
}
export async function rejectMasterAgentEvolutionProposal(proposalId: string, account: string) {
return updateMasterAgentEvolutionProposalStatus({
proposalId,
status: "rejected",
account,
note: "用户拒绝进化提案",
});
}

View File

@@ -0,0 +1,73 @@
import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
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"];
async function setup() {
if (runtimeRoot) {
return;
}
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-evolution-engine-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [data, evolution] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/master-agent-evolution.ts"),
]);
readState = data.readState;
recordMasterAgentEvolutionSignal = evolution.recordMasterAgentEvolutionSignal;
listMasterAgentEvolutionProposals = evolution.listMasterAgentEvolutionProposals;
setMasterAgentEvolutionMode = evolution.setMasterAgentEvolutionMode;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await rm(runtimeRoot, { recursive: true, force: true });
await mkdir(runtimeRoot, { recursive: true });
});
test("recording repeated deterministic questions creates a pending fast_path_rule proposal in controlled mode", async () => {
const result = await recordMasterAgentEvolutionSignal({
kind: "repeated_question",
projectId: "master-agent",
account: "17600003315",
requestText: "当前主节点在线吗",
});
assert.equal(result.signal.kind, "repeated_question");
assert.equal(result.proposal?.proposalType, "fast_path_rule");
assert.equal(result.proposal?.status, "pending_review");
const proposals = await listMasterAgentEvolutionProposals();
assert.equal(proposals[0]?.proposalType, "fast_path_rule");
assert.equal(proposals[0]?.status, "pending_review");
});
test("autonomous mode auto applies low risk fast path proposals as evolution rules", async () => {
await setMasterAgentEvolutionMode("autonomous");
const result = await recordMasterAgentEvolutionSignal({
kind: "repeated_question",
projectId: "master-agent",
account: "17600003315",
requestText: "当前主节点在线吗",
});
assert.equal(result.proposal?.status, "auto_applied");
const state = await readState();
assert.equal(state.masterAgentEvolutionRules.length, 1);
assert.equal(state.masterAgentEvolutionRules[0]?.ruleType, "fast_path_rule");
});

View File

@@ -0,0 +1,125 @@
import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { NextRequest } from "next/server";
let runtimeRoot = "";
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
let recordMasterAgentEvolutionSignal: (typeof import("../src/lib/master-agent-evolution"))["recordMasterAgentEvolutionSignal"];
let GETEvolutionRoute: (typeof import("../src/app/api/v1/master-agent/evolution/route"))["GET"];
let POSTEvolutionConfigRoute: (typeof import("../src/app/api/v1/master-agent/evolution/config/route"))["POST"];
let POSTApproveRoute: (typeof import("../src/app/api/v1/master-agent/evolution/proposals/[proposalId]/approve/route"))["POST"];
let POSTRejectRoute: (typeof import("../src/app/api/v1/master-agent/evolution/proposals/[proposalId]/reject/route"))["POST"];
let AUTH_SESSION_COOKIE = "";
async function setup() {
if (runtimeRoot) {
return;
}
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-evolution-routes-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [data, auth, evolution, evolutionRoute, configRoute, approveRoute, rejectRoute] = await Promise.all([
import("../src/lib/boss-data.ts"),
import("../src/lib/boss-auth.ts"),
import("../src/lib/master-agent-evolution.ts"),
import("../src/app/api/v1/master-agent/evolution/route.ts"),
import("../src/app/api/v1/master-agent/evolution/config/route.ts"),
import("../src/app/api/v1/master-agent/evolution/proposals/[proposalId]/approve/route.ts"),
import("../src/app/api/v1/master-agent/evolution/proposals/[proposalId]/reject/route.ts"),
]);
createAuthSession = data.createAuthSession;
recordMasterAgentEvolutionSignal = evolution.recordMasterAgentEvolutionSignal;
GETEvolutionRoute = evolutionRoute.GET;
POSTEvolutionConfigRoute = configRoute.POST;
POSTApproveRoute = approveRoute.POST;
POSTRejectRoute = rejectRoute.POST;
AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE;
}
async function createAdminRequest(url: string, body?: unknown) {
const session = await createAuthSession({
account: "17600003315",
role: "highest_admin",
displayName: "Boss 超级管理员",
loginMethod: "password",
});
return new NextRequest(url, {
method: body === undefined ? "GET" : "POST",
headers: {
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
...(body === undefined ? {} : { "content-type": "application/json" }),
},
body: body === undefined ? undefined : JSON.stringify(body),
});
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await rm(runtimeRoot, { recursive: true, force: true });
await mkdir(runtimeRoot, { recursive: true });
});
test("GET /api/v1/master-agent/evolution returns config proposals and rules", async () => {
const response = await GETEvolutionRoute(
await createAdminRequest("http://127.0.0.1:3000/api/v1/master-agent/evolution"),
);
assert.equal(response.status, 200);
const payload = await response.json() as { ok: boolean; proposals: unknown[]; rules: unknown[] };
assert.equal(payload.ok, true);
assert.ok(Array.isArray(payload.proposals));
assert.ok(Array.isArray(payload.rules));
});
test("POST /api/v1/master-agent/evolution/config switches mode", async () => {
const response = await POSTEvolutionConfigRoute(
await createAdminRequest("http://127.0.0.1:3000/api/v1/master-agent/evolution/config", { mode: "autonomous" }),
);
assert.equal(response.status, 200);
const payload = await response.json() as { ok: boolean; config?: { mode?: string } };
assert.equal(payload.ok, true);
assert.equal(payload.config?.mode, "autonomous");
});
test("approve and reject evolution proposals update proposal status", async () => {
const created = await recordMasterAgentEvolutionSignal({
kind: "repeated_question",
account: "17600003315",
requestText: "当前主节点在线吗",
});
const proposalId = created.proposal?.proposalId ?? "";
assert.ok(proposalId);
const approveResponse = await POSTApproveRoute(
await createAdminRequest(`http://127.0.0.1:3000/api/v1/master-agent/evolution/proposals/${proposalId}/approve`, {}),
{ params: Promise.resolve({ proposalId }) },
);
assert.equal(approveResponse.status, 200);
const approvePayload = await approveResponse.json() as { ok: boolean; proposal?: { status?: string } };
assert.equal(approvePayload.ok, true);
assert.equal(approvePayload.proposal?.status, "approved");
const created2 = await recordMasterAgentEvolutionSignal({
kind: "backend_fallback",
account: "17600003315",
requestText: "切到稳定后端",
});
const proposalId2 = created2.proposal?.proposalId ?? "";
assert.ok(proposalId2);
const rejectResponse = await POSTRejectRoute(
await createAdminRequest(`http://127.0.0.1:3000/api/v1/master-agent/evolution/proposals/${proposalId2}/reject`, {}),
{ params: Promise.resolve({ proposalId: proposalId2 }) },
);
assert.equal(rejectResponse.status, 200);
const rejectPayload = await rejectResponse.json() as { ok: boolean; proposal?: { status?: string } };
assert.equal(rejectPayload.ok, true);
assert.equal(rejectPayload.proposal?.status, "rejected");
});

View File

@@ -0,0 +1,49 @@
import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let updateMasterAgentEvolutionConfig: (typeof import("../src/lib/boss-data"))["updateMasterAgentEvolutionConfig"];
async function setup() {
if (runtimeRoot) {
return;
}
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-evolution-state-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const data = await import("../src/lib/boss-data.ts");
readState = data.readState;
updateMasterAgentEvolutionConfig = data.updateMasterAgentEvolutionConfig;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test.beforeEach(async () => {
await setup();
await rm(runtimeRoot, { recursive: true, force: true });
await mkdir(runtimeRoot, { recursive: true });
});
test("boss state 初始化时包含 master agent evolution 默认配置与空集合", async () => {
const state = await readState();
assert.equal(state.masterAgentEvolutionConfig.mode, "controlled");
assert.equal(state.masterAgentEvolutionConfig.autoApplyLowRiskRules, false);
assert.deepEqual(state.masterAgentEvolutionSignals, []);
assert.deepEqual(state.masterAgentEvolutionProposals, []);
assert.deepEqual(state.masterAgentEvolutionRules, []);
assert.deepEqual(state.masterAgentEvolutionRunLogs, []);
});
test("master agent evolution 配置可切换 autonomous 并自动打开低风险自动采纳", async () => {
const config = await updateMasterAgentEvolutionConfig({ mode: "autonomous" });
assert.equal(config.mode, "autonomous");
assert.equal(config.autoApplyLowRiskRules, true);
});