feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m45s
CI / 全量测试 (Electron ABI) (push) Failing after 6m28s
CI / 产物编译验证 (push) Successful in 11m18s

This commit is contained in:
2026-09-08 14:30:27 +08:00
parent 69776e447f
commit 4cd6e997b5
86 changed files with 4303 additions and 956 deletions
+49 -17
View File
@@ -14,11 +14,7 @@ const MAX_DIFF_FILE_BYTES = 10 * 1024 * 1024;
import type { IMetonaTool, ToolExecutionContext } from '../../types/metona-tool';
import type { MetonaToolDef } from '../../../harness/types';
import { MetonaToolCategory, MetonaRiskLevel } from '../../../harness/types';
import {
safeResolvePath,
extractErrorMessage,
decodeBufferWithDetection,
} from './file-guard';
import { safeResolvePath, extractErrorMessage, decodeBufferWithDetection } from './file-guard';
interface DiffLine {
type: 'context' | 'added' | 'removed';
@@ -53,12 +49,14 @@ function computeDiff(oldLines: string[], newLines: string[]): DiffLine[] {
// 回溯生成 diff
const result: DiffLine[] = [];
let i = sm, j = sn;
let i = sm,
j = sn;
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && oldSliced[i - 1] === newSliced[j - 1]) {
result.unshift({ type: 'context', oldLineNo: i, newLineNo: j, content: oldSliced[i - 1] });
i--; j--;
i--;
j--;
} else if (j > 0 && (i === 0 || lcs[idx(i, j - 1)] >= lcs[idx(i - 1, j)])) {
result.unshift({ type: 'added', oldLineNo: null, newLineNo: j, content: newSliced[j - 1] });
j--;
@@ -72,7 +70,12 @@ function computeDiff(oldLines: string[], newLines: string[]): DiffLine[] {
}
/** 生成 unified diff 格式字符串 */
function formatUnifiedDiff(diffLines: DiffLine[], oldLabel: string, newLabel: string, contextLines: number = 3): string {
function formatUnifiedDiff(
diffLines: DiffLine[],
oldLabel: string,
newLabel: string,
contextLines: number = 3,
): string {
const lines: string[] = [];
lines.push(`--- ${oldLabel}`);
lines.push(`+++ ${newLabel}`);
@@ -89,7 +92,11 @@ function formatUnifiedDiff(diffLines: DiffLine[], oldLabel: string, newLabel: st
if (hunkLines.length > 0) {
// 移除尾部多余的 context 行
const trimmed: string[] = [...hunkLines];
while (trimmed.length > 0 && trimmed[trimmed.length - 1].startsWith(' ') && contextSinceChange > 0) {
while (
trimmed.length > 0 &&
trimmed[trimmed.length - 1].startsWith(' ') &&
contextSinceChange > 0
) {
trimmed.pop();
contextSinceChange--;
}
@@ -142,10 +149,29 @@ function formatUnifiedDiff(diffLines: DiffLine[], oldLabel: string, newLabel: st
return lines.join('\n');
}
/**
* v0.8.2 P2-1: 文本级 unified diff 计算导出(file_editor dry_run 复用)。
* dry_run 预览此前返回 {original, modified} 两段裸文本 —— 模型与用户都要自行
* 比对差异。现与 diff_viewer 同源(LCS + unified 格式),渲染端可统一以
* diff 视图展示。
*/
export function computeUnifiedDiffText(
oldText: string,
newText: string,
oldLabel: string,
newLabel: string,
contextLines: number = 3,
): string {
const oldLines = oldText.length > 0 ? oldText.split('\n') : [];
const newLines = newText.length > 0 ? newText.split('\n') : [];
return formatUnifiedDiff(computeDiff(oldLines, newLines), oldLabel, newLabel, contextLines);
}
export class DiffViewerTool implements IMetonaTool {
readonly definition: MetonaToolDef = {
name: 'diff_viewer',
description: 'Compare two files or two text snippets and show differences. Generates unified diff format output. Useful for reviewing changes before applying or comparing configurations.',
description:
'Compare two files or two text snippets and show differences. Generates unified diff format output. Useful for reviewing changes before applying or comparing configurations.',
parameters: {
type: 'object',
properties: {
@@ -158,7 +184,10 @@ export class DiffViewerTool implements IMetonaTool {
file_b: { type: 'string', description: 'Second file path (for files mode)' },
text_a: { type: 'string', description: 'First text content (for text mode)' },
text_b: { type: 'string', description: 'Second text content (for text mode)' },
context_lines: { type: 'number', description: 'Context lines around changes (default 3, max 10)' },
context_lines: {
type: 'number',
description: 'Context lines around changes (default 3, max 10)',
},
},
required: ['mode'],
},
@@ -254,16 +283,19 @@ export class DiffViewerTool implements IMetonaTool {
lines_removed: removedCount,
lines_unchanged: contextCount,
total_changes: addedCount + removedCount,
similarity: (oldSlicedLen + newSlicedLen) > 0
? Math.round((contextCount * 2 / (oldSlicedLen + newSlicedLen)) * 100) / 100
: 1,
similarity:
oldSlicedLen + newSlicedLen > 0
? Math.round(((contextCount * 2) / (oldSlicedLen + newSlicedLen)) * 100) / 100
: 1,
truncated,
};
// D4.6: unifiedDiff 大小限制
const MAX_DIFF_CHARS = 50_000; const truncatedDiff = unifiedDiff.length > MAX_DIFF_CHARS
? unifiedDiff.slice(0, MAX_DIFF_CHARS) + '\n... (diff truncated)'
: unifiedDiff;
const MAX_DIFF_CHARS = 50_000;
const truncatedDiff =
unifiedDiff.length > MAX_DIFF_CHARS
? unifiedDiff.slice(0, MAX_DIFF_CHARS) + '\n... (diff truncated)'
: unifiedDiff;
return {
success: true,