修复: - main.ts 退出释放模型显存改用 getSetting(serverUrl),不再硬编码 127.0.0.1:11434(避免非默认地址时释放请求打到错误端口) - 备份导出/导入并入 localStorage 持久化状态(会话摘要、度量历史、轨迹降级缓存、主题),版本升级到 v2,实现完整备份 - 工具数量改为 getEnabledToolDefinitions().length 动态计算,删除写死"32 个"的硬编码 - 记忆日志区分操作来源:memory:write 透传 reason,标注"新增记忆/替换/删除/清空/TTL 衰减清理/访问统计写回(无新条目)",避免"写了但看不到新记忆"的困惑 可维护性: - 上下文压力逻辑收敛到统一 calculateContextStats,删除 getContextPressureLevel / getTrendAwareCompressThreshold 的重复实现 - 消除 validateToolArgs 同名碰撞(agent-engine 本地版改名 validateToolArgsQuick) - 子代理工具集改用 getEnabledToolDefinitions() 基线,跟随全局启用开关与 Plan 模式 - 抽取 html-utils.ts 纯函数模块(实体解码/HTML→文本/HTML→Markdown/拦截页检测/相关性评分),tool-handlers-system 净减约 190 行重复代码 - 统一静态导入(savePlanTracker/setPlanModeActive/collectDiagnostics/addWrittenFile) - console.* 使用处补充豁免说明(启动/退出/刷盘阶段无渲染进程可推送日志) - run_command 工具描述改为反映可配置执行模式 测试: - 新增 7 个测试文件 + 扩展 2 个,共 273 个测试(原 34 → 273) - 覆盖 agent-engine / agent-safety / context-manager / tool-registry / result-formatter / tool-parsing / memory-service / crypto / build-context / html-utils / utils / tool-handlers-fs - 全部通过 npm run typecheck && npm test && npm run build
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { buildContext } from '../src/renderer/services/context-manager.js';
|
||||
import type { OllamaMessage } from '../src/renderer/types.js';
|
||||
|
||||
function makeMsgs(n: number): OllamaMessage[] {
|
||||
return Array.from({ length: n }, (_, i) => ({ role: 'user' as const, content: `消息 ${i}` }));
|
||||
}
|
||||
|
||||
describe('buildContext — 滑动窗口构建', () => {
|
||||
it('消息数不超过窗口时原样返回', () => {
|
||||
const msgs = makeMsgs(5);
|
||||
const out = buildContext(msgs, { windowSize: 20, maxTokens: 131072 });
|
||||
expect(out.length).toBe(5);
|
||||
expect(out[0].content).toBe('消息 0');
|
||||
});
|
||||
|
||||
it('超过窗口时保留最近 windowSize 条', () => {
|
||||
const msgs = makeMsgs(30);
|
||||
const out = buildContext(msgs, { windowSize: 10, maxTokens: 131072 });
|
||||
// 最近的 10 条(索引 20-29)保留
|
||||
expect(out.some(m => m.content === '消息 25')).toBe(true);
|
||||
expect(out.some(m => m.content === '消息 0')).toBe(false);
|
||||
});
|
||||
|
||||
it('system 消息置于最前', () => {
|
||||
const msgs: OllamaMessage[] = [
|
||||
{ role: 'user', content: '你好' },
|
||||
{ role: 'system', content: '你是助手' },
|
||||
];
|
||||
const out = buildContext(msgs, { windowSize: 20, maxTokens: 131072 });
|
||||
expect(out[0].role).toBe('system');
|
||||
expect(out[0].content).toContain('你是助手');
|
||||
});
|
||||
|
||||
it('注入 memoryContext 与 workspaceContext 动态前缀', () => {
|
||||
const out = buildContext([], {
|
||||
windowSize: 20,
|
||||
maxTokens: 131072,
|
||||
memoryContext: '[memory 上下文]',
|
||||
workspaceContext: '[workspace 目录]',
|
||||
});
|
||||
const sys = out.find(m => m.role === 'system');
|
||||
expect(sys?.content).toContain('[memory 上下文]');
|
||||
expect(sys?.content).toContain('[workspace 目录]');
|
||||
});
|
||||
|
||||
it('合并重复 system 消息为单条', () => {
|
||||
const msgs: OllamaMessage[] = [
|
||||
{ role: 'system', content: '规则 A' },
|
||||
{ role: 'system', content: '规则 B' },
|
||||
{ role: 'user', content: '你好' },
|
||||
];
|
||||
const out = buildContext(msgs, { windowSize: 20, maxTokens: 131072 });
|
||||
const sysCount = out.filter(m => m.role === 'system').length;
|
||||
expect(sysCount).toBeLessThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('token 超限时裁剪(需消息数超过窗口才触发)', () => {
|
||||
// 25 条 > windowSize 20,走滑动窗口+裁剪路径
|
||||
// 每条 500 字符 ≈ 125 token;小预算触发裁剪
|
||||
const msgs: OllamaMessage[] = [];
|
||||
for (let i = 0; i < 25; i++) msgs.push({ role: 'user', content: 'x'.repeat(500) });
|
||||
const out = buildContext(msgs, { windowSize: 20, maxTokens: 300 });
|
||||
// 只保护最近 6 条,其余被裁剪
|
||||
expect(out.length).toBeLessThan(25);
|
||||
expect(out.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('大预算时不裁剪(25 条返回 windowSize+摘要)', () => {
|
||||
const msgs: OllamaMessage[] = [];
|
||||
for (let i = 0; i < 25; i++) msgs.push({ role: 'user', content: 'x'.repeat(500) });
|
||||
const out = buildContext(msgs, { windowSize: 20, maxTokens: 100000 });
|
||||
// 25 条 → 部分摘要,不丢失全部 → 至少 20 条窗口内的
|
||||
expect(out.length).toBeGreaterThanOrEqual(20);
|
||||
});
|
||||
|
||||
it('空消息列表返回空(无 system 时)', () => {
|
||||
expect(buildContext([], { windowSize: 20, maxTokens: 131072 })).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user