v0.17.1: 退出释放显存修正 + 备份完整性 + 核心逻辑测试补课 + 上下文逻辑收敛 + 记忆日志可读性
CI / verify (push) Successful in 1m2s

修复:
- 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:
2026-08-26 15:02:47 +08:00
parent 0b172d30c0
commit b66945c8a7
32 changed files with 2082 additions and 326 deletions
+98
View File
@@ -0,0 +1,98 @@
import { describe, it, expect } from 'vitest';
import {
generateId,
formatTime,
truncate,
formatSize,
escapeHtml,
detectLanguage,
} from '../src/renderer/utils/utils.js';
describe('generateId', () => {
it('生成唯一 ID', () => {
const a = generateId();
const b = generateId();
expect(a).not.toBe(b);
});
});
describe('formatTime', () => {
it('格式化为 YYYY-MM-DD HH:MM:SS', () => {
const ts = new Date(2026, 7, 26, 14, 30, 5).getTime();
const out = formatTime(ts);
expect(out).toMatch(/^2026-08-26 14:30:05$/);
});
});
describe('truncate', () => {
it('短文本原样返回', () => {
expect(truncate('hello', 10)).toBe('hello');
});
it('超长文本截断加省略号', () => {
expect(truncate('x'.repeat(20), 5)).toBe('xxxxx...');
});
it('空字符串返回空', () => {
expect(truncate('')).toBe('');
});
});
describe('formatSize', () => {
it('字节格式化到适当单位', () => {
expect(formatSize(0)).toBe('');
expect(formatSize(512)).toBe('512.0 B');
expect(formatSize(1024)).toBe('1.0 KB');
expect(formatSize(1024 * 1024)).toBe('1.0 MB');
expect(formatSize(1024 * 1024 * 1024)).toBe('1.0 GB');
});
it('大数值进位到 TB', () => {
expect(formatSize(1024 ** 4)).toBe('1.0 TB');
});
});
describe('escapeHtml', () => {
it('转义 HTML 特殊字符', () => {
expect(escapeHtml('<script>alert("x")</script>')).toBe('&lt;script&gt;alert(&quot;x&quot;)&lt;/script&gt;');
});
it('转义单引号与 &', () => {
expect(escapeHtml("a'b & c")).toBe('a&#39;b &amp; c');
});
it('null/undefined 返回空串', () => {
expect(escapeHtml(null)).toBe('');
expect(escapeHtml(undefined)).toBe('');
});
it('数字值被字符串化并转义', () => {
expect(escapeHtml(42)).toBe('42');
});
});
describe('detectLanguage', () => {
it('常见扩展名识别', () => {
expect(detectLanguage('main.ts')).toBe('typescript');
expect(detectLanguage('app.py')).toBe('python');
expect(detectLanguage('index.js')).toBe('javascript');
expect(detectLanguage('style.css')).toBe('css');
expect(detectLanguage('data.json')).toBe('json');
});
it('特殊文件名识别', () => {
expect(detectLanguage('Dockerfile')).toBe('dockerfile');
expect(detectLanguage('Makefile')).toBe('makefile');
});
it('未知扩展名返回自身', () => {
// 有扩展名:未知映射返回扩展名本身
expect(detectLanguage('file.xyz')).toBe('xyz');
// 无扩展名:split 后 pop 得到整个文件名,未命中映射返回原值
expect(detectLanguage('noext')).toBe('noext');
});
it('大小写不敏感', () => {
expect(detectLanguage('MAIN.TS')).toBe('typescript');
});
});