- 版本号更新: 1.0.0 → 1.1.0,全量同步 package.json/lock/README/docs/UI - 上下文长度自动检测: 切换模型时从 model_info 读取实际 context_length - SOUL.md 支持: 每次发送自动扫描工作空间 SOUL.md,注入为首条 system 消息且不可压缩 - 系统提示词卡片: AI 回复顶部可折叠展示实际发送给模型的完整 system prompt - Token 校准: 利用 Ollama 返回的实际计数动态修正估算器(EMA) - 消息重要性评分: 纯规则打分,trim/summarize 按重要性而非时间顺序 - 结构化压缩: LLM 压缩输出 JSON 格式(topics/decisions/pendingTasks/constraints) - 技能系统 v1.1: 语义匹配(embedding) + 参数自优化 + 未使用衰减 + 技能链合并 - 修复: 100+ TypeScript strict 模式编译错误清零
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
/**
|
|
* Crypto — Metona 会话加密模块
|
|
* Electron 环境下 crypto.subtle 始终可用,无需 JS 回退实现
|
|
*/
|
|
|
|
const MAGIC = new TextEncoder().encode('METONA1\0');
|
|
const PASSPHRASE = 'metona-ollama-v2';
|
|
const PBKDF2_ITERATIONS = 100000;
|
|
|
|
async function deriveKeyBytes(salt: Uint8Array): Promise<Uint8Array> {
|
|
const passBytes = new TextEncoder().encode(PASSPHRASE);
|
|
const keyMaterial = await (crypto.subtle as any).importKey('raw', passBytes, 'PBKDF2', false, ['deriveKey']);
|
|
const key = await crypto.subtle.deriveKey(
|
|
{ name: 'PBKDF2', salt: salt as any, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' },
|
|
keyMaterial,
|
|
{ name: 'AES-GCM', length: 256 },
|
|
true,
|
|
['encrypt', 'decrypt']
|
|
);
|
|
const raw = await crypto.subtle.exportKey('raw', key);
|
|
return new Uint8Array(raw);
|
|
}
|
|
|
|
export async function encryptData(data: unknown): Promise<Blob> {
|
|
const json = new TextEncoder().encode(JSON.stringify(data));
|
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
const keyBytes = await deriveKeyBytes(salt);
|
|
|
|
const key = await crypto.subtle.importKey('raw', keyBytes as any, 'AES-GCM', false, ['encrypt']);
|
|
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, json);
|
|
const payload = new Uint8Array(ciphertext);
|
|
|
|
const flag = new Uint8Array([1]); // mode=1: AES-GCM
|
|
return new Blob([MAGIC, salt, iv, flag, payload]);
|
|
}
|
|
|
|
export async function decryptData(buffer: ArrayBuffer): Promise<unknown> {
|
|
const data = new Uint8Array(buffer);
|
|
for (let i = 0; i < 8; i++) {
|
|
if (data[i] !== MAGIC[i]) throw new Error('不是有效的 .metona 文件');
|
|
}
|
|
|
|
const salt = data.slice(8, 24);
|
|
const iv = data.slice(24, 36);
|
|
const mode = data[36];
|
|
const payload = data.slice(37);
|
|
const keyBytes = await deriveKeyBytes(salt);
|
|
|
|
if (mode !== 1) {
|
|
throw new Error('不支持的加密格式,请使用最新版本重新导出');
|
|
}
|
|
|
|
const key = await crypto.subtle.importKey('raw', keyBytes as any, 'AES-GCM', false, ['decrypt']);
|
|
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, payload);
|
|
const jsonBytes = new Uint8Array(decrypted);
|
|
|
|
return JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
}
|