feat: show plan diff in execution log when revise_plan is called

- apply_plan_diff now returns a YAML unified diff string
- Pure Rust LCS diff implementation (no external dependency)
- revise_plan logs the diff to execution log with ```diff fencing
- Frontend renders diff with green/red syntax highlighting
This commit is contained in:
Fam Zheng
2026-03-10 19:03:47 +00:00
parent 978af45d5f
commit 63bbbae17c
3 changed files with 104 additions and 3 deletions

View File

@@ -131,6 +131,25 @@ function formatLatency(ms: number): string {
return ms + 'ms'
}
function escapeHtml(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
function isDiffOutput(output: string): boolean {
return output.startsWith('```diff\n')
}
function renderDiff(output: string): string {
// Strip ```diff fences
const inner = output.replace(/^```diff\n/, '').replace(/\n```$/, '')
return inner.split('\n').map(line => {
const esc = escapeHtml(line)
if (line.startsWith('+')) return `<span class="diff-add">${esc}</span>`
if (line.startsWith('-')) return `<span class="diff-del">${esc}</span>`
return esc
}).join('\n')
}
function parseToolCalls(json: string): { name: string; arguments_preview: string }[] {
try {
return JSON.parse(json)
@@ -275,7 +294,7 @@ watch(logItems, () => {
<div v-if="item.entry.tool_input && item.entry.tool_name !== 'text_response'" class="exec-command">
<code>{{ item.entry.tool_input }}</code>
</div>
<pre v-if="item.entry.output">{{ item.entry.output }}</pre>
<pre v-if="item.entry.output" :class="{ 'diff-output': isDiffOutput(item.entry.output) }" v-html="isDiffOutput(item.entry.output) ? renderDiff(item.entry.output) : escapeHtml(item.entry.output)"></pre>
</div>
</div>
</template>
@@ -665,4 +684,18 @@ watch(logItems, () => {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 10px;
}
pre.diff-output .diff-add {
color: #22c55e;
background: rgba(34, 197, 94, 0.1);
display: inline-block;
width: 100%;
}
pre.diff-output .diff-del {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
display: inline-block;
width: 100%;
}
</style>