feat: v0.3.18 上下文压缩机制修复 + MCP 工具就绪竞态修复 + 记忆系统加固
【上下文压缩机制修复】 - engine.ts 新增 lastRealInputTokens 记录 LLM 返回的真实输入 token,压缩判断取 max(估算值, 真实值),避免估算偏低导致不压缩但 API 413 - 修复 effectiveContextWindow 缺少默认值导致 compressionThreshold 变 NaN、压缩永不触发的 bug(添加 ?? 128_000 兜底) - compressMessages 保留区从固定 10 条改为按 token 预算动态截断(50% 上下文窗口) - 二次截断 charsPerToken 从 2 调整为 1.0,与 CJK_TOKEN_RATIO 一致 - 压缩后重置 lastRealInputTokens,避免跨迭代污染 - 每个 run 开始时重置 lastRealInputTokens 【前端 Token 显示修复】 - 区分"累计消耗"和"上下文占用"语义——之前 totalTokens(累计) / contextWindow(单次窗口) 得出无意义百分比 - TokenUsage.tsx 上下文占用改用 lastInputTokens,新增压缩节省行(绿色,仅当 > 0 时显示) - agent-store.ts TokenUsage 接口新增 lastInputTokens 和 lastCompressedSaved 字段,4 处初始值统一更新 - useAgentStream.ts usage 事件 lastInputTokens 替换不累加,compressed 事件通过 streamEvent 接收 savedTokens - handlers.ts onCompressed 同时发 toast + streamEvent,解决"压缩触发但前端 token 显示不降"缺陷 - 旧数据兼容使用 ?? 0,保证历史会话加载不崩溃 【MCP 工具就绪竞态修复】 - 修复输入框永久显示"工具加载中"的竞态条件:MCP initialize 几乎立即 resolve(connectServer 不 await),tools:ready 事件在前端监听器注册前已发出 - main.ts 维护 toolsReady 标志 + 注册 tools:isReady IPC handler 查询当前状态 - preload.ts 暴露 tools.isReady() 方法 - App.tsx 注册 onReady 监听器后立即查询 isReady(),无论事件是否错过都能恢复正确状态 【记忆系统加固】 - consolidator.ts 新增 runningPromise + waitForCompletion(35s),before-quit 等待固化完成,防止退出时异步 consolidate 数据丢失 - 固化到 MEMORY.md 的同时写入 semantic_memories 表,解决双轨存储无交叉验证问题 - manager.ts tokenize 按中英文标点切分子句后再做 bigram,优化中文分词 - 清理正则冗余括号 【SOUL.md 降级处理】 - context-builder.ts SOUL.md 为空或不存在时降级到默认身份,向前端发 toast 提示用户 - 新增 fallbackRoleNotified 去重标志,仅首次降级通知,避免每次发消息都弹 toast - SOUL.md 恢复内容时重置标志 【Token 估算调整】 - token-estimator.ts CJK_TOKEN_RATIO 从 1.5 调整为 1.0 【MCP 异步初始化】 - main.ts MCP 完成后广播 tools:ready 事件,前端 UI 据以控制输入框可用性 - preload.ts + global.d.ts 暴露 tools.onReady() 监听器 - App.tsx + ChatInput.tsx toolsReady 状态控制输入框 【版本号】 - package.json + package-lock.json 从 0.3.16 升级到 0.3.18
This commit is contained in:
@@ -25,6 +25,7 @@ import type { IMetonaProviderAdapter } from '../types/metona-adapter';
|
||||
import type { MetonaRequest, MetonaMessage } from '../types';
|
||||
import type { WorkspaceService } from '../../services/workspace.service';
|
||||
import type { IterationStep } from '../agent-loop/types';
|
||||
import type { MemoryManager } from './manager';
|
||||
|
||||
/** 允许写入的 MEMORY.md 分区(与 WorkspaceService.MEMORY_TEMPLATE 对齐) */
|
||||
const ALLOWED_SECTIONS = ['用户偏好', '项目上下文', '重要决策', '待办事项', '已知问题'] as const;
|
||||
@@ -43,6 +44,11 @@ export interface ConsolidationResult {
|
||||
}
|
||||
|
||||
export class MemoryConsolidator {
|
||||
/** v0.3.18 修复: 跟踪进行中的 consolidate 任务,供应用退出时等待 */
|
||||
private runningPromise: Promise<ConsolidationResult> | null = null;
|
||||
/** v0.3.18 修复: 注入 MemoryManager,实现 DB 记忆与 MEMORY.md 双轨交叉写入 */
|
||||
private memoryManager: MemoryManager | null = null;
|
||||
|
||||
constructor(
|
||||
private adapter: IMetonaProviderAdapter,
|
||||
private workspaceService: WorkspaceService,
|
||||
@@ -55,6 +61,47 @@ export class MemoryConsolidator {
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.18 修复: 注入 MemoryManager,使 consolidate 同步写入 semantic_memories 表
|
||||
*/
|
||||
setMemoryManager(memoryManager: MemoryManager): void {
|
||||
this.memoryManager = memoryManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.18 修复: 是否有进行中的 consolidate 任务
|
||||
*/
|
||||
isRunning(): boolean {
|
||||
return this.runningPromise !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.18 修复: 等待进行中的 consolidate 任务完成(应用退出时调用)
|
||||
*
|
||||
* @param timeoutMs 超时时间(默认 35 秒,略大于 LLM 调用的 30 秒超时)
|
||||
* @returns true 表示已完成,false 表示超时
|
||||
*/
|
||||
async waitForCompletion(timeoutMs: number = 35_000): Promise<boolean> {
|
||||
if (!this.runningPromise) return true;
|
||||
let timedOut = false;
|
||||
let timerHandle: ReturnType<typeof setTimeout> | undefined;
|
||||
const timer = new Promise<void>((resolve) => {
|
||||
timerHandle = setTimeout(() => {
|
||||
timedOut = true;
|
||||
resolve();
|
||||
}, timeoutMs);
|
||||
});
|
||||
try {
|
||||
await Promise.race([
|
||||
this.runningPromise.catch(() => {}),
|
||||
timer,
|
||||
]);
|
||||
return !timedOut;
|
||||
} finally {
|
||||
if (timerHandle) clearTimeout(timerHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 固化本次对话的重要记忆到 MEMORY.md
|
||||
*
|
||||
@@ -67,6 +114,32 @@ export class MemoryConsolidator {
|
||||
userMessage: string,
|
||||
assistantAnswer: string,
|
||||
iterations: IterationStep[],
|
||||
): Promise<ConsolidationResult> {
|
||||
// v0.3.18 修复: 跟踪进行中的 consolidate,供应用退出时等待
|
||||
const promise = this.executeConsolidation(userMessage, assistantAnswer, iterations);
|
||||
this.runningPromise = promise;
|
||||
try {
|
||||
return await promise;
|
||||
} finally {
|
||||
// 只有当前 promise 仍是自己时才清理,避免被后续调用覆盖
|
||||
if (this.runningPromise === promise) {
|
||||
this.runningPromise = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际执行固化的内部方法
|
||||
*
|
||||
* v0.3.18 修复: 同步写入 semantic_memories 表,实现 DB 记忆与 MEMORY.md 双轨交叉
|
||||
* 之前仅写 MEMORY.md,导致 DB 检索不到用户偏好等非工具触发的重要信息。
|
||||
* 现在每条固化到 MEMORY.md 的条目也同步存入 semantic_memories 表,
|
||||
* 使 TF-IDF 检索能命中跨会话的用户偏好/决策/项目上下文。
|
||||
*/
|
||||
private async executeConsolidation(
|
||||
userMessage: string,
|
||||
assistantAnswer: string,
|
||||
iterations: IterationStep[],
|
||||
): Promise<ConsolidationResult> {
|
||||
try {
|
||||
// 1. 构建对话摘要
|
||||
@@ -105,6 +178,27 @@ export class MemoryConsolidator {
|
||||
try {
|
||||
this.workspaceService.appendMemory(item.section, truncatedEntry);
|
||||
validEntries.push({ section: item.section, entry: truncatedEntry });
|
||||
|
||||
// v0.3.18 修复: 同步写入 semantic_memories 表,实现 DB 记忆与 MEMORY.md 双轨交叉
|
||||
// 之前仅写 MEMORY.md,导致 DB 检索不到用户偏好等非工具触发的重要信息。
|
||||
// 现在每条固化到 MEMORY.md 的条目也存入 semantic_memories,
|
||||
// key 用 section(如"用户偏好"),value 用 entry 内容,
|
||||
// confidence 按 section 语义分级(偏好/决策最高,待办最低)
|
||||
if (this.memoryManager) {
|
||||
try {
|
||||
const confidence = this.getSectionConfidence(item.section);
|
||||
this.memoryManager.store({
|
||||
type: 'semantic',
|
||||
content: truncatedEntry,
|
||||
summary: `[${item.section}] ${truncatedEntry.slice(0, 60)}`,
|
||||
source: 'agent_thought',
|
||||
importance: confidence,
|
||||
});
|
||||
} catch (dbErr) {
|
||||
// DB 写入失败不影响 MEMORY.md 已写入的结果,仅记录日志
|
||||
log.warn(`[MemoryConsolidator] Failed to sync to semantic_memories:`, dbErr);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn(`[MemoryConsolidator] Failed to append to section "${item.section}":`, err);
|
||||
skipped++;
|
||||
@@ -278,6 +372,29 @@ export class MemoryConsolidator {
|
||||
return (ALLOWED_SECTIONS as readonly string[]).includes(item.section) && item.entry.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.3.18 修复: 按 MEMORY.md 分区语义返回 confidence(重要度)
|
||||
*
|
||||
* 用于同步写入 semantic_memories 时的 importance 字段:
|
||||
* - 用户偏好/重要决策:0.9(长期有效,跨会话高价值)
|
||||
* - 项目上下文/已知问题:0.7(项目级,中等价值)
|
||||
* - 待办事项:0.5(短期,可能很快完成)
|
||||
*/
|
||||
private getSectionConfidence(section: string): number {
|
||||
switch (section) {
|
||||
case '用户偏好':
|
||||
case '重要决策':
|
||||
return 0.9;
|
||||
case '项目上下文':
|
||||
case '已知问题':
|
||||
return 0.7;
|
||||
case '待办事项':
|
||||
return 0.5;
|
||||
default:
|
||||
return 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断字符串
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user