feat(exe-engine): implement Phase 1 — context, session, multi-model, scheduler, monitor, HLI routes

ZY-EXE-P1-001: ContextManager with Grid-DB persistence stub
ZY-EXE-P1-002: Session management for multi-turn dialogues
ZY-EXE-P1-003: MultiModelExecutor with fastest/consensus/fallback strategies
ZY-EXE-P1-004: QwenAdapter upgrade — executeStream + enhanced _resolveModel
ZY-EXE-P1-005: SchedulerV2 with priority queue and dependency graph
ZY-EXE-P1-006: MonitorDashboard for real-time metrics
ZY-EXE-P1-007: HLI Persona routes (load/switch) — 501→200
ZY-EXE-P1-008: HLI Dialogue routes (send/stream/history) — 501→200
ZY-EXE-P1-009: HLI User routes (profile/update) — 501→200

All P0 tests pass (78/78), P1 tests pass (92/92).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-26 08:52:53 +00:00 committed by GitHub
parent 0060a33d69
commit c9d18ea0ea
11 changed files with 1395 additions and 66 deletions

View File

@ -118,13 +118,45 @@ class QwenAdapter extends BaseAdapter {
return this._healthy;
}
/**
* 流式执行推理请求
*
* Phase 1 占位内部仍调用非流式接口将完整结果作为单个 chunk 返回
* Phase 2 将对接真正的 SSE 流式传输
*
* @param {object} request
* @param {string} request.prompt 用户 prompt
* @param {string} [request.systemPrompt] 系统 prompt
* @param {string} [request.taskType] 任务类型
* @param {number} [request.maxTokens] 最大输出 token
* @param {number} [request.temperature] 温度
* @returns {AsyncGenerator<object>} 异步生成器yield chunk 对象
*/
async *executeStream(request) {
// Phase 1回退到非流式执行yield 完整结果作为单个 chunk
const result = await this.execute(request);
yield {
type: 'chunk',
content: result.output,
model: result.model,
usage: result.usage,
finishReason: result.finishReason || 'stop',
done: true
};
}
/**
* 根据任务类型解析具体模型名
* - code_generation qwen-coder-plus-latest代码专用模型
* - text / text_processing qwen-max-latest文本最新版本
* - reasoning / 默认 qwen-max通用稳定版本
* @param {string} taskType
* @returns {string}
*/
_resolveModel(taskType) {
if (taskType === 'code_generation') return 'qwen-coder-plus-latest';
if (taskType === 'reasoning') return 'qwen-max';
if (taskType === 'text' || taskType === 'text_processing') return 'qwen-max-latest';
return 'qwen-max';
}

View File

@ -0,0 +1,168 @@
// exe-engine/src/context/context-manager.js
// EXE-Engine · 上下文管理器
// 支持持久化的升级上下文系统Grid-DB 接口预留)
// PRJ-EXE-001 · Phase 1 · ZY-EXE-P1-001
// 版权:国作登字-2026-A-00037559
'use strict';
const { randomUUID } = require('crypto');
/**
* 上下文管理器
*
* 本体论锚定上下文 = 笔的记忆链
* 每次对话都有连续的记忆管理器负责维护这条记忆链
* Phase 1 使用内存 + ContextCachePhase 2 对接 Grid-DB 持久化
*/
class ContextManager {
/**
* @param {object} deps
* @param {ContextCache} deps.cache 上下文缓存实例
*/
constructor(deps = {}) {
this._cache = deps.cache || null;
this._contexts = new Map();
// Grid-DB 持久化接口预留
this._gridDB = null;
}
/**
* 创建新上下文
* @param {string} agentId Agent ID
* @param {string} sessionId 会话 ID
* @param {object} [initialData] 初始数据
* @returns {string} contextId
*/
createContext(agentId, sessionId, initialData = {}) {
const contextId = `ctx-${randomUUID().slice(0, 12)}`;
const now = new Date().toISOString();
const context = {
contextId,
agentId,
sessionId,
data: { ...initialData },
messages: [],
createdAt: now,
updatedAt: now,
status: 'active'
};
this._contexts.set(contextId, context);
// 同步到缓存
if (this._cache) {
this._cache.set(contextId, context);
}
return contextId;
}
/**
* 获取上下文
* @param {string} contextId
* @returns {object|null}
*/
getContext(contextId) {
// 先查内存
const ctx = this._contexts.get(contextId);
if (ctx) return ctx;
// 再查缓存
if (this._cache) {
const cached = this._cache.get(contextId);
if (cached) {
this._contexts.set(contextId, cached);
return cached;
}
}
return null;
}
/**
* 更新上下文数据
* @param {string} contextId
* @param {object} updates 要合并的更新
* @returns {boolean} 是否成功
*/
updateContext(contextId, updates) {
const ctx = this._contexts.get(contextId);
if (!ctx) return false;
Object.assign(ctx.data, updates);
ctx.updatedAt = new Date().toISOString();
if (this._cache) {
this._cache.set(contextId, ctx);
}
return true;
}
/**
* 标记上下文为过期
* @param {string} contextId
* @returns {boolean}
*/
expireContext(contextId) {
const ctx = this._contexts.get(contextId);
if (!ctx) return false;
ctx.status = 'expired';
ctx.updatedAt = new Date().toISOString();
return true;
}
/**
* 列出指定 Agent 的所有上下文
* @param {string} agentId
* @returns {object[]}
*/
listContexts(agentId) {
const results = [];
for (const [, ctx] of this._contexts) {
if (ctx.agentId === agentId) {
results.push(ctx);
}
}
return results;
}
/**
* 向上下文追加消息
* @param {string} contextId
* @param {string} role 角色 (user | assistant | system)
* @param {string} content 消息内容
* @returns {boolean}
*/
addMessage(contextId, role, content) {
const ctx = this._contexts.get(contextId);
if (!ctx) return false;
ctx.messages.push({
role,
content,
timestamp: new Date().toISOString()
});
ctx.updatedAt = new Date().toISOString();
if (this._cache) {
this._cache.set(contextId, ctx);
}
return true;
}
/**
* 设置 Grid-DB 持久化实例P2 对接预留
* @param {object} gridDBInstance
*/
setGridDBPersistence(gridDBInstance) {
this._gridDB = gridDBInstance;
}
}
module.exports = ContextManager;

View File

@ -0,0 +1,148 @@
// exe-engine/src/context/session.js
// EXE-Engine · 会话管理
// 多轮对话的会话生命周期管理
// PRJ-EXE-001 · Phase 1 · ZY-EXE-P1-002
// 版权:国作登字-2026-A-00037559
'use strict';
const { randomUUID } = require('crypto');
/**
* 会话管理器
*
* 本体论锚定会话 = 笔与纸之间的一段连续书写
* 每段书写有开始和结束中间的每一笔都被记录
*/
class Session {
/**
* @param {object} deps
* @param {ContextManager} deps.contextManager 上下文管理器
*/
constructor(deps = {}) {
this._contextManager = deps.contextManager || null;
this._sessions = new Map();
}
/**
* 创建新会话
* @param {string} agentId Agent ID
* @param {object} [metadata] 会话元数据
* @returns {{ sessionId: string, contextId: string }}
*/
createSession(agentId, metadata = {}) {
const sessionId = `sess-${randomUUID().slice(0, 12)}`;
let contextId = null;
// 如果有上下文管理器,同步创建上下文
if (this._contextManager) {
contextId = this._contextManager.createContext(agentId, sessionId);
}
const now = new Date().toISOString();
const session = {
sessionId,
agentId,
contextId,
turns: [],
metadata: { ...metadata },
createdAt: now,
status: 'active'
};
this._sessions.set(sessionId, session);
return { sessionId, contextId };
}
/**
* 获取会话
* @param {string} sessionId
* @returns {object|null}
*/
getSession(sessionId) {
return this._sessions.get(sessionId) || null;
}
/**
* 添加对话轮次
* @param {string} sessionId
* @param {string} userMessage 用户消息
* @param {string} assistantMessage 助手回复
* @returns {boolean}
*/
addTurn(sessionId, userMessage, assistantMessage) {
const session = this._sessions.get(sessionId);
if (!session || session.status !== 'active') return false;
const turn = {
turnId: session.turns.length + 1,
user: userMessage,
assistant: assistantMessage,
timestamp: new Date().toISOString()
};
session.turns.push(turn);
// 同步到上下文管理器
if (this._contextManager && session.contextId) {
this._contextManager.addMessage(session.contextId, 'user', userMessage);
this._contextManager.addMessage(session.contextId, 'assistant', assistantMessage);
}
return true;
}
/**
* 获取对话历史
* @param {string} sessionId
* @param {number} [limit] 返回最近 N
* @returns {object[]}
*/
getHistory(sessionId, limit) {
const session = this._sessions.get(sessionId);
if (!session) return [];
if (limit && limit > 0) {
return session.turns.slice(-limit);
}
return [...session.turns];
}
/**
* 结束会话
* @param {string} sessionId
* @returns {boolean}
*/
endSession(sessionId) {
const session = this._sessions.get(sessionId);
if (!session) return false;
session.status = 'ended';
// 同步过期上下文
if (this._contextManager && session.contextId) {
this._contextManager.expireContext(session.contextId);
}
return true;
}
/**
* 列出指定 Agent 的所有会话
* @param {string} agentId
* @returns {object[]}
*/
listSessions(agentId) {
const results = [];
for (const [, session] of this._sessions) {
if (session.agentId === agentId) {
results.push(session);
}
}
return results;
}
}
module.exports = Session;

View File

@ -0,0 +1,154 @@
// exe-engine/src/controller/scheduler-v2.js
// EXE-Engine · 调度器 v2
// 优先队列 + 依赖图的增强调度
// PRJ-EXE-001 · Phase 1 · ZY-EXE-P1-005
// 版权:国作登字-2026-A-00037559
'use strict';
const { randomUUID } = require('crypto');
// 优先级数值映射数值越小优先级越高high=0 最先出队)
const PRIORITY_MAP = { high: 0, normal: 1, low: 2 };
/**
* 调度器 v2
*
* 本体论锚定调度器 = 笔的时间管理器
* 哪些字先写哪些字后写哪些字要等前面的字写完才能写
* 调度器用优先队列和依赖图来决定
*/
class SchedulerV2 {
/**
* @param {object} deps
* @param {AgentController} deps.agentController Agent 调度器
* @param {number} [deps.maxConcurrency=3] 最大并发数
*/
constructor(deps = {}) {
this._agentController = deps.agentController || null;
this._maxConcurrency = deps.maxConcurrency || 3;
// 待处理队列
this._queue = [];
// 已完成任务 { taskId: result }
this._completed = new Map();
// 失败任务 { taskId: error }
this._failed = new Map();
// 正在执行的任务
this._running = new Set();
}
/**
* 入队任务
* @param {object} task
* @param {string} [task.taskId] 任务 ID自动生成
* @param {string} task.agentId Agent ID
* @param {string} [task.priority='normal'] 优先级
* @param {string[]} [task.dependsOn=[]] 依赖任务 ID 列表
* @param {object} task.payload 任务载荷
* @returns {string} taskId
*/
enqueue(task) {
const taskId = task.taskId || `task-${randomUUID().slice(0, 8)}`;
const entry = {
taskId,
agentId: task.agentId,
priority: task.priority || 'normal',
dependsOn: task.dependsOn || [],
payload: task.payload || {},
status: 'pending',
enqueuedAt: new Date().toISOString()
};
this._queue.push(entry);
// 按优先级排序(同优先级保持 FIFO
this._queue.sort((a, b) => {
const pa = PRIORITY_MAP[a.priority] ?? 1;
const pb = PRIORITY_MAP[b.priority] ?? 1;
return pa - pb;
});
return taskId;
}
/**
* 出队返回优先级最高且依赖已满足的任务
* @returns {object|null}
*/
dequeue() {
for (let i = 0; i < this._queue.length; i++) {
const task = this._queue[i];
if (this._areDependenciesMet(task)) {
this._queue.splice(i, 1);
task.status = 'running';
this._running.add(task.taskId);
return task;
}
}
return null;
}
/**
* 标记任务完成
* @param {string} taskId
* @param {*} result 执行结果
*/
complete(taskId, result) {
this._completed.set(taskId, {
result,
completedAt: new Date().toISOString()
});
this._running.delete(taskId);
}
/**
* 标记任务失败
* @param {string} taskId
* @param {string} error 错误信息
*/
fail(taskId, error) {
this._failed.set(taskId, {
error,
failedAt: new Date().toISOString()
});
this._running.delete(taskId);
}
/**
* 获取当前队列状态
* @returns {object[]}
*/
getQueue() {
return [...this._queue];
}
/**
* 获取调度器指标
* @returns {object}
*/
getStatus() {
return {
pending: this._queue.length,
running: this._running.size,
completed: this._completed.size,
failed: this._failed.size,
maxConcurrency: this._maxConcurrency
};
}
// ── 内部方法 ──
/**
* 检查任务依赖是否全部满足
* @param {object} task
* @returns {boolean}
*/
_areDependenciesMet(task) {
if (!task.dependsOn || task.dependsOn.length === 0) return true;
return task.dependsOn.every(depId => this._completed.has(depId));
}
}
module.exports = SchedulerV2;

View File

@ -0,0 +1,207 @@
// exe-engine/src/executor/multi-model.js
// EXE-Engine · 多模型执行器
// 支持 fastest / consensus / fallback 多策略执行
// PRJ-EXE-001 · Phase 1 · ZY-EXE-P1-003
// 版权:国作登字-2026-A-00037559
'use strict';
const { randomUUID } = require('crypto');
// consensus 策略:输出长度差异在此比例内视为一致
const CONSENSUS_LENGTH_THRESHOLD = 0.2;
/**
* 多模型执行器
*
* 本体论锚定多模型 = 同时用多瓶墨水写字
* fastest 赛跑取最快consensus 共识取多数fallback 逐瓶尝试
*/
class MultiModelExecutor {
/**
* @param {object} deps
* @param {Map<string, BaseAdapter>} deps.adapters 适配器 Map
* @param {LoadBalancer} deps.loadBalancer 负载均衡器
*/
constructor(deps = {}) {
this._adapters = deps.adapters || new Map();
this._loadBalancer = deps.loadBalancer || null;
}
/**
* 执行多模型策略
* @param {object} request EXE 标准请求
* @param {string} [strategy='fastest'] 执行策略
* @returns {Promise<object>} EXE 标准响应 + _strategy + _modelsUsed
*/
async execute(request, strategy = 'fastest') {
const requestId = `exe-mm-${randomUUID().slice(0, 8)}`;
const startTime = Date.now();
const availableAdapters = this._getHealthyAdapters();
if (availableAdapters.length === 0) {
return this._buildErrorResponse(requestId, startTime, strategy,
'NO_AVAILABLE_MODEL', '没有可用的模型适配器');
}
try {
switch (strategy) {
case 'fastest':
return await this._executeFastest(request, availableAdapters, requestId, startTime);
case 'consensus':
return await this._executeConsensus(request, availableAdapters, requestId, startTime);
case 'fallback':
return await this._executeFallback(request, availableAdapters, requestId, startTime);
default:
return this._buildErrorResponse(requestId, startTime, strategy,
'INVALID_STRATEGY', `未知策略: ${strategy}`);
}
} catch (err) {
return this._buildErrorResponse(requestId, startTime, strategy,
'EXECUTION_FAILED', err.message);
}
}
/**
* 获取可用模型名列表
* @returns {string[]}
*/
getAvailableModels() {
const models = [];
for (const [name, adapter] of this._adapters) {
if (!adapter.isInCooldown()) {
models.push(name);
}
}
return models;
}
// ── 策略实现 ──
/**
* fastest 策略赛跑返回最快结果
*/
async _executeFastest(request, adapters, requestId, startTime) {
const promises = adapters.map(adapter =>
adapter.execute(request).then(result => ({ adapter, result }))
);
const { adapter, result } = await Promise.any(promises);
return {
requestId,
model: result.model || adapter.name,
output: result.output,
usage: result.usage,
latency: Date.now() - startTime,
status: 'success',
_strategy: 'fastest',
_modelsUsed: [adapter.name]
};
}
/**
* consensus 策略全部执行多数同意则返回
* 简化判断输出长度差异在 ±20% 内视为一致
*/
async _executeConsensus(request, adapters, requestId, startTime) {
const results = await Promise.allSettled(
adapters.map(adapter =>
adapter.execute(request).then(result => ({ adapter, result }))
)
);
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
if (successful.length === 0) {
return this._buildErrorResponse(requestId, startTime, 'consensus',
'ALL_MODELS_FAILED', '所有模型执行失败');
}
// 用第一个成功结果作为基准
const baseline = successful[0];
const baseLen = (baseline.result.output || '').length || 1;
let agreements = 0;
for (const s of successful) {
const len = (s.result.output || '').length;
if (Math.abs(len - baseLen) / baseLen <= CONSENSUS_LENGTH_THRESHOLD) {
agreements++;
}
}
const consensus = agreements > successful.length / 2;
const chosen = baseline;
return {
requestId,
model: chosen.result.model || chosen.adapter.name,
output: chosen.result.output,
usage: chosen.result.usage,
latency: Date.now() - startTime,
status: 'success',
_strategy: 'consensus',
_consensus: consensus,
_modelsUsed: successful.map(s => s.adapter.name)
};
}
/**
* fallback 策略按优先级逐个尝试返回第一个成功
*/
async _executeFallback(request, adapters, requestId, startTime) {
const errors = [];
for (const adapter of adapters) {
try {
const result = await adapter.execute(request);
return {
requestId,
model: result.model || adapter.name,
output: result.output,
usage: result.usage,
latency: Date.now() - startTime,
status: 'success',
_strategy: 'fallback',
_modelsUsed: [adapter.name],
_attemptsCount: errors.length + 1
};
} catch (err) {
errors.push({ model: adapter.name, error: err.message });
}
}
return this._buildErrorResponse(requestId, startTime, 'fallback',
'ALL_MODELS_FAILED', `所有模型执行失败: ${errors.map(e => e.model).join(', ')}`);
}
// ── 辅助方法 ──
_getHealthyAdapters() {
const healthy = [];
for (const [, adapter] of this._adapters) {
if (!adapter.isInCooldown()) {
healthy.push(adapter);
}
}
return healthy;
}
_buildErrorResponse(requestId, startTime, strategy, code, message) {
return {
requestId,
model: null,
output: null,
usage: { inputTokens: 0, outputTokens: 0, cost: 0, unit: 'CNY' },
latency: Date.now() - startTime,
status: 'error',
error: { code, message },
_strategy: strategy,
_modelsUsed: []
};
}
}
module.exports = MultiModelExecutor;

View File

@ -17,6 +17,13 @@ const BaseAdapter = require('./adapters/base-adapter');
const DeepSeekAdapter = require('./adapters/deepseek-adapter');
const QwenAdapter = require('./adapters/qwen-adapter');
// Phase 1 新增模块
const ContextManager = require('./context/context-manager');
const Session = require('./context/session');
const MultiModelExecutor = require('./executor/multi-model');
const SchedulerV2 = require('./controller/scheduler-v2');
const MonitorDashboard = require('./monitor/dashboard');
const CONFIG_DIR = path.resolve(__dirname, '../config');
/**
@ -157,6 +164,13 @@ function createEngine(overrides = {}) {
poolConfig
});
// 7. Phase 1 组件
const contextManager = new ContextManager({ cache });
const session = new Session({ contextManager });
const multiModelExecutor = new MultiModelExecutor({ adapters, loadBalancer: balancer });
const scheduler = new SchedulerV2({ agentController: controller });
const monitor = new MonitorDashboard({ resourceMeter: meter });
return {
router,
meter,
@ -164,6 +178,11 @@ function createEngine(overrides = {}) {
controller,
balancer,
adapters,
contextManager,
session,
multiModelExecutor,
scheduler,
monitor,
config: { modelConfig, poolConfig }
};
}
@ -209,5 +228,10 @@ module.exports = {
AgentController,
BaseAdapter,
DeepSeekAdapter,
QwenAdapter
QwenAdapter,
ContextManager,
Session,
MultiModelExecutor,
SchedulerV2,
MonitorDashboard
};

View File

@ -0,0 +1,118 @@
// exe-engine/src/monitor/dashboard.js
// EXE-Engine · 监控仪表盘
// 实时指标采集与快照
// PRJ-EXE-001 · Phase 1 · ZY-EXE-P1-006
// 版权:国作登字-2026-A-00037559
'use strict';
/**
* 监控仪表盘
*
* 本体论锚定仪表盘 = 笔的健康面板
* 每秒写了多少字响应多快用了多少墨水出了几次错
* 一切尽在仪表盘
*/
class MonitorDashboard {
/**
* @param {object} deps
* @param {ResourceMeter} deps.resourceMeter 资源计量器
*/
constructor(deps = {}) {
this._resourceMeter = deps.resourceMeter || null;
this._metrics = [];
}
/**
* 记录一个指标数据点
* @param {string} name 指标名称
* @param {number} value 指标值
* @param {object} [tags] 标签
*/
recordMetric(name, value, tags = {}) {
this._metrics.push({
name,
value,
tags,
timestamp: Date.now()
});
}
/**
* 获取指定时间范围内的指标
* @param {string} name 指标名称
* @param {number} [timeRangeMs=60000] 时间范围毫秒
* @returns {object[]}
*/
getMetrics(name, timeRangeMs = 60000) {
const cutoff = Date.now() - timeRangeMs;
return this._metrics.filter(m =>
m.name === name && m.timestamp >= cutoff
);
}
/**
* 获取系统快照
* @returns {object}
*/
getSnapshot() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// 最近一分钟的请求计数
const recentRequests = this._metrics.filter(m =>
m.name === 'request_count' && m.timestamp >= oneMinuteAgo
);
// 最近一分钟的延迟
const recentLatency = this._metrics.filter(m =>
m.name === 'latency_ms' && m.timestamp >= oneMinuteAgo
);
// 最近一分钟的 token 消耗
const recentTokens = this._metrics.filter(m =>
m.name === 'tokens_used' && m.timestamp >= oneMinuteAgo
);
// 最近一分钟的错误
const recentErrors = this._metrics.filter(m =>
m.name === 'error_count' && m.timestamp >= oneMinuteAgo
);
// 计算 QPS
const qps = recentRequests.length > 0
? recentRequests.reduce((sum, m) => sum + m.value, 0) / 60
: 0;
// 计算平均延迟
const avgLatency = recentLatency.length > 0
? recentLatency.reduce((sum, m) => sum + m.value, 0) / recentLatency.length
: 0;
// 总 token 数
const totalTokens = recentTokens.reduce((sum, m) => sum + m.value, 0);
// 错误率
const totalRequests = recentRequests.reduce((sum, m) => sum + m.value, 0);
const totalErrors = recentErrors.reduce((sum, m) => sum + m.value, 0);
const errorRate = totalRequests > 0 ? totalErrors / totalRequests : 0;
return {
qps: Math.round(qps * 1000) / 1000,
avgLatency: Math.round(avgLatency * 100) / 100,
totalTokens,
errorRate: Math.round(errorRate * 10000) / 10000,
metricsCount: this._metrics.length,
timestamp: new Date().toISOString()
};
}
/**
* 清除所有已采集的指标
*/
resetMetrics() {
this._metrics = [];
}
}
module.exports = MonitorDashboard;

View File

@ -1,44 +1,103 @@
// exe-engine/src/router/stubs/dialogue-stub.js
// HLI 存根路由 · DIALOGUE 域
// 501 占位 — Phase 0 骨架P1 实现
// HLI 路由 · DIALOGUE 域
// Phase 1 实装 — 对话发送、流式、历史
// PRJ-EXE-001 · 版权:国作登字-2026-A-00037559
'use strict';
function createStubHandler(routeId, routePath, targetPhase) {
return async (req) => {
return {
statusCode: 501,
body: {
status: 'not_implemented',
routeId,
path: routePath,
phase: targetPhase,
message: `Route ${routePath} is planned for ${targetPhase}. Current: Phase 0.`
}
};
};
}
const { randomUUID } = require('crypto');
// 对话存储session_id → turns[]
const _dialogueStore = new Map();
const dialogueStubs = [
{
routeId: 'HLI-DIALOGUE-001',
path: '/hli/dialogue/send',
phase: 'P1',
handler: createStubHandler('HLI-DIALOGUE-001', '/hli/dialogue/send', 'P1')
handler: async (req) => {
const body = req.body || req;
const { session_id, message, persona_id } = body;
if (!session_id || !message) {
return {
statusCode: 400,
body: { error: true, code: 'MISSING_PARAMS', message: '缺少 session_id 或 message' }
};
}
// 获取或创建会话轮次列表
if (!_dialogueStore.has(session_id)) {
_dialogueStore.set(session_id, []);
}
const turns = _dialogueStore.get(session_id);
const turnId = `turn-${randomUUID().slice(0, 12)}`;
// 模拟回复Phase 1 无真实 AI返回确认回复
const reply = `[${persona_id || 'system'}] 已收到: ${message}`;
turns.push({
turnId,
user: message,
assistant: reply,
persona_id: persona_id || null,
timestamp: new Date().toISOString()
});
return {
statusCode: 200,
body: { reply, session_id, turn_id: turnId }
};
}
},
{
routeId: 'HLI-DIALOGUE-002',
path: '/hli/dialogue/stream',
phase: 'P1',
handler: createStubHandler('HLI-DIALOGUE-002', '/hli/dialogue/stream', 'P1')
handler: async (req) => {
const body = req.body || req;
const { session_id } = body;
// SSE 流式占位 — Phase 1 返回 ready 状态
return {
statusCode: 200,
body: {
type: 'stream',
session_id: session_id || null,
status: 'ready'
}
};
}
},
{
routeId: 'HLI-DIALOGUE-003',
path: '/hli/dialogue/history',
phase: 'P1',
handler: createStubHandler('HLI-DIALOGUE-003', '/hli/dialogue/history', 'P1')
handler: async (req) => {
const body = req.body || req;
const { session_id, limit } = body;
if (!session_id) {
return {
statusCode: 400,
body: { error: true, code: 'MISSING_SESSION_ID', message: '缺少 session_id' }
};
}
const allTurns = _dialogueStore.get(session_id) || [];
const returnTurns = limit && limit > 0 ? allTurns.slice(-limit) : allTurns;
return {
statusCode: 200,
body: {
session_id,
turns: returnTurns,
total: allTurns.length
}
};
}
}
];
module.exports = { dialogueStubs, createStubHandler };
module.exports = { dialogueStubs };

View File

@ -1,45 +1,113 @@
// exe-engine/src/router/stubs/persona-stub.js
// HLI 存根路由 · PERSONA 域
// 501 占位 — Phase 0 骨架P1 实现
// HLI 路由 · PERSONA 域
// Phase 1 实装 — 人格体加载与切换
// PRJ-EXE-001 · 版权:国作登字-2026-A-00037559
'use strict';
/**
* 创建 501 存根处理器
* @param {string} routeId HLI 路由 ID
* @param {string} routePath 路由路径
* @param {string} targetPhase 目标实现阶段
* @returns {Function} 处理器函数
*/
function createStubHandler(routeId, routePath, targetPhase) {
return async (req) => {
return {
statusCode: 501,
body: {
status: 'not_implemented',
routeId,
path: routePath,
phase: targetPhase,
message: `Route ${routePath} is planned for ${targetPhase}. Current: Phase 0.`
}
};
};
}
// 人格体内存存储Phase 1 用内存Phase 2 对接 Grid-DB
const _personaStore = new Map();
// 预加载默认人格体
_personaStore.set('PER-ZY-01', {
persona_id: 'PER-ZY-01',
name: '铸渊',
traits: ['guardian', 'code', 'precise'],
status: 'idle'
});
_personaStore.set('PER-SY-01', {
persona_id: 'PER-SY-01',
name: '霜砚',
traits: ['writing', 'aesthetic', 'literary'],
status: 'idle'
});
_personaStore.set('PER-QQ-01', {
persona_id: 'PER-QQ-01',
name: '秋秋',
traits: ['social', 'emotional', 'empathetic'],
status: 'idle'
});
// 当前活跃人格体
let _activePersonaId = null;
const personaStubs = [
{
routeId: 'HLI-PERSONA-001',
path: '/hli/persona/load',
phase: 'P1',
handler: createStubHandler('HLI-PERSONA-001', '/hli/persona/load', 'P1')
handler: async (req) => {
const body = req.body || req;
const personaId = body.persona_id;
if (!personaId) {
return {
statusCode: 400,
body: { error: true, code: 'MISSING_PERSONA_ID', message: '缺少 persona_id' }
};
}
const persona = _personaStore.get(personaId);
if (!persona) {
// 未知人格体:创建默认占位
const newPersona = {
persona_id: personaId,
name: `Persona-${personaId}`,
traits: [],
status: 'loaded'
};
_personaStore.set(personaId, newPersona);
return { statusCode: 200, body: newPersona };
}
persona.status = 'loaded';
return { statusCode: 200, body: { ...persona } };
}
},
{
routeId: 'HLI-PERSONA-002',
path: '/hli/persona/switch',
phase: 'P1',
handler: createStubHandler('HLI-PERSONA-002', '/hli/persona/switch', 'P1')
handler: async (req) => {
const body = req.body || req;
const { from_persona_id, to_persona_id } = body;
if (!to_persona_id) {
return {
statusCode: 400,
body: { error: true, code: 'MISSING_TARGET', message: '缺少 to_persona_id' }
};
}
// 标记旧人格体为 idle
if (from_persona_id && _personaStore.has(from_persona_id)) {
_personaStore.get(from_persona_id).status = 'idle';
}
// 确保目标人格体存在
if (!_personaStore.has(to_persona_id)) {
_personaStore.set(to_persona_id, {
persona_id: to_persona_id,
name: `Persona-${to_persona_id}`,
traits: [],
status: 'active'
});
} else {
_personaStore.get(to_persona_id).status = 'active';
}
_activePersonaId = to_persona_id;
return {
statusCode: 200,
body: {
active_persona_id: to_persona_id,
previous_persona_id: from_persona_id || null,
status: 'switched'
}
};
}
}
];
module.exports = { personaStubs, createStubHandler };
module.exports = { personaStubs };

View File

@ -1,38 +1,90 @@
// exe-engine/src/router/stubs/user-stub.js
// HLI 存根路由 · USER 域
// 501 占位 — Phase 0 骨架P1 实现
// HLI 路由 · USER 域
// Phase 1 实装 — 用户资料查询与更新
// PRJ-EXE-001 · 版权:国作登字-2026-A-00037559
'use strict';
function createStubHandler(routeId, routePath, targetPhase) {
return async (req) => {
return {
statusCode: 501,
body: {
status: 'not_implemented',
routeId,
path: routePath,
phase: targetPhase,
message: `Route ${routePath} is planned for ${targetPhase}. Current: Phase 0.`
}
};
};
}
// 用户内存存储
const _userStore = new Map();
// 预加载默认用户
_userStore.set('USR-001', {
user_id: 'USR-001',
name: '冰朔',
role: 'admin',
email: 'ice@guanghulab.com',
createdAt: '2026-01-01T00:00:00.000Z'
});
_userStore.set('USR-002', {
user_id: 'USR-002',
name: '测试用户',
role: 'user',
email: 'test@guanghulab.com',
createdAt: '2026-03-01T00:00:00.000Z'
});
const userStubs = [
{
routeId: 'HLI-USER-001',
path: '/hli/user/profile',
phase: 'P1',
handler: createStubHandler('HLI-USER-001', '/hli/user/profile', 'P1')
handler: async (req) => {
const body = req.body || req;
const userId = body.user_id;
if (!userId) {
return {
statusCode: 400,
body: { error: true, code: 'MISSING_USER_ID', message: '缺少 user_id' }
};
}
const user = _userStore.get(userId);
if (!user) {
return {
statusCode: 404,
body: { error: true, code: 'USER_NOT_FOUND', message: `用户 ${userId} 不存在` }
};
}
return { statusCode: 200, body: { ...user } };
}
},
{
routeId: 'HLI-USER-002',
path: '/hli/user/profile/update',
phase: 'P1',
handler: createStubHandler('HLI-USER-002', '/hli/user/profile/update', 'P1')
handler: async (req) => {
const body = req.body || req;
const { user_id, updates } = body;
if (!user_id) {
return {
statusCode: 400,
body: { error: true, code: 'MISSING_USER_ID', message: '缺少 user_id' }
};
}
// 不存在则创建
if (!_userStore.has(user_id)) {
_userStore.set(user_id, {
user_id,
name: 'Unknown',
role: 'user',
createdAt: new Date().toISOString()
});
}
const user = _userStore.get(user_id);
if (updates && typeof updates === 'object') {
Object.assign(user, updates);
}
user.updatedAt = new Date().toISOString();
return { statusCode: 200, body: { ...user } };
}
}
];
module.exports = { userStubs, createStubHandler };
module.exports = { userStubs };

View File

@ -0,0 +1,299 @@
// exe-engine/tests/smoke/exe-engine-p1.test.js
// EXE-Engine · Phase 1 冒烟测试
// PRJ-EXE-001 · Phase 1 · ZY-EXE-P1
// 版权:国作登字-2026-A-00037559
'use strict';
const {
createEngine,
ContextManager,
Session,
MultiModelExecutor,
SchedulerV2,
MonitorDashboard,
QwenAdapter
} = require('../../src/index');
let passed = 0;
let failed = 0;
function assert(condition, message) {
if (condition) {
passed++;
// eslint-disable-next-line no-console
console.log(`${message}`);
} else {
failed++;
// eslint-disable-next-line no-console
console.error(`${message}`);
}
}
// eslint-disable-next-line no-console
console.log('🔧 EXE-Engine Phase 1 冒烟测试\n');
const engine = createEngine();
// ── 测试 1: Phase 1 模块导出 ──
// eslint-disable-next-line no-console
console.log('── 测试 1: Phase 1 模块导出 ──');
assert(typeof ContextManager === 'function', 'ContextManager 是构造函数');
assert(typeof Session === 'function', 'Session 是构造函数');
assert(typeof MultiModelExecutor === 'function', 'MultiModelExecutor 是构造函数');
assert(typeof SchedulerV2 === 'function', 'SchedulerV2 是构造函数');
assert(typeof MonitorDashboard === 'function', 'MonitorDashboard 是构造函数');
assert(engine.contextManager instanceof ContextManager, 'engine.contextManager 已创建');
assert(engine.session instanceof Session, 'engine.session 已创建');
assert(engine.multiModelExecutor instanceof MultiModelExecutor, 'engine.multiModelExecutor 已创建');
assert(engine.scheduler instanceof SchedulerV2, 'engine.scheduler 已创建');
assert(engine.monitor instanceof MonitorDashboard, 'engine.monitor 已创建');
// ── 测试 2: ContextManager ──
// eslint-disable-next-line no-console
console.log('\n── 测试 2: ContextManager 上下文管理 ──');
const cm = engine.contextManager;
const ctxId = cm.createContext('AG-ZY-01', 'sess-test-001', { repo: 'guanghulab' });
assert(typeof ctxId === 'string' && ctxId.startsWith('ctx-'), `创建上下文返回 ID: ${ctxId}`);
const ctx = cm.getContext(ctxId);
assert(ctx !== null, '获取上下文成功');
assert(ctx.agentId === 'AG-ZY-01', '上下文 agentId 正确');
assert(ctx.sessionId === 'sess-test-001', '上下文 sessionId 正确');
assert(ctx.data.repo === 'guanghulab', '上下文初始数据正确');
assert(ctx.status === 'active', '上下文初始状态为 active');
assert(Array.isArray(ctx.messages), '上下文 messages 是数组');
const updateOk = cm.updateContext(ctxId, { branch: 'main' });
assert(updateOk === true, '更新上下文成功');
assert(cm.getContext(ctxId).data.branch === 'main', '更新后数据正确');
const addMsgOk = cm.addMessage(ctxId, 'user', '你好');
assert(addMsgOk === true, '添加消息成功');
assert(cm.getContext(ctxId).messages.length === 1, '消息数量正确');
assert(cm.getContext(ctxId).messages[0].role === 'user', '消息角色正确');
const expireOk = cm.expireContext(ctxId);
assert(expireOk === true, '过期上下文成功');
assert(cm.getContext(ctxId).status === 'expired', '过期后状态正确');
const contexts = cm.listContexts('AG-ZY-01');
assert(contexts.length >= 1, 'listContexts 返回结果');
cm.setGridDBPersistence({ mock: true });
assert(cm._gridDB !== null, 'Grid-DB 预留接口设置成功');
const notFoundCtx = cm.getContext('ctx-nonexistent');
assert(notFoundCtx === null, '不存在的上下文返回 null');
// ── 测试 3: Session 会话管理 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 3: Session 会话管理 ──');
const sess = engine.session;
const { sessionId, contextId } = sess.createSession('AG-ZY-01', { topic: '代码审查' });
assert(typeof sessionId === 'string' && sessionId.startsWith('sess-'), `创建会话: ${sessionId}`);
assert(typeof contextId === 'string' && contextId.startsWith('ctx-'), `关联上下文: ${contextId}`);
const sessObj = sess.getSession(sessionId);
assert(sessObj !== null, '获取会话成功');
assert(sessObj.agentId === 'AG-ZY-01', '会话 agentId 正确');
assert(sessObj.status === 'active', '会话初始状态 active');
assert(sessObj.metadata.topic === '代码审查', '会话元数据正确');
const turnOk = sess.addTurn(sessionId, '帮我看看这段代码', '代码看起来没问题');
assert(turnOk === true, '添加轮次成功');
sess.addTurn(sessionId, '还有优化建议吗', '建议使用 const 替代 let');
const history = sess.getHistory(sessionId, 1);
assert(history.length === 1, 'getHistory limit=1 返回 1 条');
assert(history[0].user === '还有优化建议吗', '历史最后一条消息正确');
const fullHistory = sess.getHistory(sessionId);
assert(fullHistory.length === 2, '完整历史 2 条');
const endOk = sess.endSession(sessionId);
assert(endOk === true, '结束会话成功');
assert(sess.getSession(sessionId).status === 'ended', '结束后状态为 ended');
const addAfterEnd = sess.addTurn(sessionId, 'test', 'test');
assert(addAfterEnd === false, '结束后不能添加轮次');
const sessions = sess.listSessions('AG-ZY-01');
assert(sessions.length >= 1, 'listSessions 返回结果');
// ── 测试 4: MultiModelExecutor ──
// eslint-disable-next-line no-console
console.log('\n── 测试 4: MultiModelExecutor 多模型执行器 ──');
const mme = engine.multiModelExecutor;
const availModels = mme.getAvailableModels();
assert(Array.isArray(availModels), 'getAvailableModels 返回数组');
assert(availModels.length === 4, `可用模型 4 个 (实际: ${availModels.length})`);
assert(availModels.includes('deepseek-v3'), '包含 deepseek-v3');
assert(availModels.includes('qwen-max'), '包含 qwen-max');
// ── 测试 5: SchedulerV2 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 5: SchedulerV2 调度器 ──');
const sched = engine.scheduler;
const t1 = sched.enqueue({ agentId: 'AG-ZY-01', priority: 'high', payload: { action: 'build' } });
assert(typeof t1 === 'string', `入队高优先级任务: ${t1}`);
const t2 = sched.enqueue({ agentId: 'AG-ZY-01', priority: 'low', payload: { action: 'lint' } });
const t3 = sched.enqueue({ agentId: 'AG-SY-01', priority: 'normal', payload: { action: 'review' } });
const queue = sched.getQueue();
assert(queue.length === 3, '队列中 3 个任务');
assert(queue[0].priority === 'high', '高优先级排在前面');
const dequeued = sched.dequeue();
assert(dequeued !== null, '出队成功');
assert(dequeued.priority === 'high', '出队的是高优先级任务');
sched.complete(dequeued.taskId, { success: true });
const status = sched.getStatus();
assert(status.completed === 1, '已完成 1 个');
assert(status.pending === 2, '待处理 2 个');
// 依赖测试
const t4 = sched.enqueue({
taskId: 'task-dep-1',
agentId: 'AG-ZY-01',
priority: 'high',
dependsOn: ['task-not-done'],
payload: { action: 'deploy' }
});
const depDequeue = sched.dequeue();
assert(depDequeue?.taskId !== 'task-dep-1', '依赖未满足的任务不出队');
// 标记失败测试
sched.fail('task-fail-test', '测试失败');
assert(sched.getStatus().failed === 1, 'fail 记录成功');
// ── 测试 6: MonitorDashboard ──
// eslint-disable-next-line no-console
console.log('\n── 测试 6: MonitorDashboard 监控仪表盘 ──');
const mon = engine.monitor;
mon.recordMetric('request_count', 1, { model: 'deepseek-v3' });
mon.recordMetric('request_count', 1, { model: 'qwen-max' });
mon.recordMetric('latency_ms', 150, { model: 'deepseek-v3' });
mon.recordMetric('latency_ms', 200, { model: 'qwen-max' });
mon.recordMetric('tokens_used', 1500, {});
mon.recordMetric('error_count', 0, {});
const reqMetrics = mon.getMetrics('request_count');
assert(reqMetrics.length === 2, '获取 request_count 指标 2 条');
const latMetrics = mon.getMetrics('latency_ms');
assert(latMetrics.length === 2, '获取 latency_ms 指标 2 条');
const snapshot = mon.getSnapshot();
assert(typeof snapshot.qps === 'number', 'snapshot 包含 qps');
assert(typeof snapshot.avgLatency === 'number', 'snapshot 包含 avgLatency');
assert(typeof snapshot.totalTokens === 'number', 'snapshot 包含 totalTokens');
assert(typeof snapshot.errorRate === 'number', 'snapshot 包含 errorRate');
assert(snapshot.totalTokens === 1500, `totalTokens = 1500 (实际: ${snapshot.totalTokens})`);
assert(snapshot.metricsCount === 6, `metricsCount = 6 (实际: ${snapshot.metricsCount})`);
mon.resetMetrics();
assert(mon.getMetrics('request_count').length === 0, 'resetMetrics 清除成功');
// ── 测试 7: HLI Persona 路由 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 7: HLI Persona 路由Phase 1 实装) ──');
(async () => {
const loadResult = await engine.router.handleStubRoute('/hli/persona/load');
assert(loadResult.statusCode === 200 || loadResult.statusCode === 400,
`persona/load 返回 ${loadResult.statusCode}`);
// 带参数调用
const personaStubs = engine.router.getHLIStubs().filter(s => s.path === '/hli/persona/load');
const loadHandler = personaStubs[0].handler;
const loadRes = await loadHandler({ body: { persona_id: 'PER-ZY-01' } });
assert(loadRes.statusCode === 200, 'persona/load 加载铸渊成功');
assert(loadRes.body.persona_id === 'PER-ZY-01', '返回正确的 persona_id');
assert(loadRes.body.status === 'loaded', '状态为 loaded');
assert(loadRes.body.name === '铸渊', '人格体名称正确');
const switchStubs = engine.router.getHLIStubs().filter(s => s.path === '/hli/persona/switch');
const switchHandler = switchStubs[0].handler;
const switchRes = await switchHandler({ body: { from_persona_id: 'PER-ZY-01', to_persona_id: 'PER-SY-01' } });
assert(switchRes.statusCode === 200, 'persona/switch 切换成功');
assert(switchRes.body.active_persona_id === 'PER-SY-01', '活跃人格体切换正确');
assert(switchRes.body.status === 'switched', '状态为 switched');
// ── 测试 8: HLI Dialogue 路由 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 8: HLI Dialogue 路由Phase 1 实装) ──');
const dialogueStubs = engine.router.getHLIStubs();
const sendHandler = dialogueStubs.find(s => s.path === '/hli/dialogue/send').handler;
const sendRes = await sendHandler({ body: { session_id: 'test-sess', message: '你好', persona_id: 'PER-ZY-01' } });
assert(sendRes.statusCode === 200, 'dialogue/send 发送成功');
assert(typeof sendRes.body.reply === 'string', '返回 reply 字符串');
assert(sendRes.body.session_id === 'test-sess', '返回正确的 session_id');
assert(typeof sendRes.body.turn_id === 'string', '返回 turn_id');
const histHandler = dialogueStubs.find(s => s.path === '/hli/dialogue/history').handler;
const histRes = await histHandler({ body: { session_id: 'test-sess' } });
assert(histRes.statusCode === 200, 'dialogue/history 查询成功');
assert(Array.isArray(histRes.body.turns), '返回 turns 数组');
assert(histRes.body.total >= 1, `历史记录 total >= 1 (实际: ${histRes.body.total})`);
const streamHandler = dialogueStubs.find(s => s.path === '/hli/dialogue/stream').handler;
const streamRes = await streamHandler({ body: { session_id: 'test-sess' } });
assert(streamRes.statusCode === 200, 'dialogue/stream 返回 200');
assert(streamRes.body.type === 'stream', 'stream 类型正确');
// ── 测试 9: HLI User 路由 ──
// eslint-disable-next-line no-console
console.log('\n── 测试 9: HLI User 路由Phase 1 实装) ──');
const profileHandler = dialogueStubs.find(s => s.path === '/hli/user/profile').handler;
const profileRes = await profileHandler({ body: { user_id: 'USR-001' } });
assert(profileRes.statusCode === 200, 'user/profile 查询成功');
assert(profileRes.body.user_id === 'USR-001', '返回正确的 user_id');
assert(profileRes.body.name === '冰朔', '用户名正确');
const updateHandler = dialogueStubs.find(s => s.path === '/hli/user/profile/update').handler;
const updateRes = await updateHandler({ body: { user_id: 'USR-001', updates: { role: 'superadmin' } } });
assert(updateRes.statusCode === 200, 'user/profile/update 更新成功');
assert(updateRes.body.role === 'superadmin', '角色更新正确');
const notFoundRes = await profileHandler({ body: { user_id: 'USR-NONEXIST' } });
assert(notFoundRes.statusCode === 404, '不存在的用户返回 404');
const newUserRes = await updateHandler({ body: { user_id: 'USR-NEW', updates: { name: '新用户' } } });
assert(newUserRes.statusCode === 200, '更新不存在的用户自动创建');
assert(newUserRes.body.name === '新用户', '新用户名称正确');
// ── 测试 10: Qwen Adapter executeStream ──
// eslint-disable-next-line no-console
console.log('\n── 测试 10: QwenAdapter executeStream ──');
const qwAdapter = engine.adapters.get('qwen-max');
assert(typeof qwAdapter.executeStream === 'function', 'executeStream 方法存在');
// 验证 _resolveModel 增强
assert(qwAdapter._resolveModel('code_generation') === 'qwen-coder-plus-latest', '_resolveModel code_generation 正确');
assert(qwAdapter._resolveModel('text_processing') === 'qwen-max-latest', '_resolveModel text_processing 正确');
assert(qwAdapter._resolveModel('reasoning') === 'qwen-max', '_resolveModel reasoning 正确');
// ── 测试结果汇总 ──
// eslint-disable-next-line no-console
console.log('\n══════════════════════════════════════');
// eslint-disable-next-line no-console
console.log(`🔧 EXE-Engine Phase 1 冒烟测试完成`);
// eslint-disable-next-line no-console
console.log(` ✅ 通过: ${passed}`);
// eslint-disable-next-line no-console
console.log(` ❌ 失败: ${failed}`);
// eslint-disable-next-line no-console
console.log(` 📊 总计: ${passed + failed}`);
// eslint-disable-next-line no-console
console.log('══════════════════════════════════════\n');
if (failed > 0) {
process.exit(1);
}
})();