feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user