From bc87ff80d294d08b33ad2c084f637bbcb69c02ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?m=C3=BCde?= Date: Fri, 15 May 2026 20:23:22 +0200 Subject: [PATCH] agent terminal: inline +/- diffs on Write and Edit tool calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Write and Edit tool_use rows used to render as the bare file path. now they're collapsed
blocks with the actual change inside — Write shows every content line prefixed '+', Edit shows old_string as '-' lines then new_string as '+' lines. summary carries the file path + counts ('→ Edit /foo · -3 +5'). lines colored via diff-add / diff-del / diff-ctx; click to expand the full body. renderFileWriteEdit returns null for any other tool so the existing flat-row path (fmtToolUse) is untouched. --- TODO.md | 9 ----- hive-ag3nt/assets/agent.css | 16 +++++++++ hive-ag3nt/assets/app.js | 65 ++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/TODO.md b/TODO.md index dbfada2..6410b53 100644 --- a/TODO.md +++ b/TODO.md @@ -42,15 +42,6 @@ Pick anything from here when relevant. Cross-cutting design notes live in `GET /api/state` (`status: "thinking" | "idle" | "compacting" | "napping"`). JS just renders. Drops the derive-from-events-and-pray code path. -- **Terminal: inline diffs for Write/Edit.** Today a `Write` / - `Edit` tool-use row just shows the file path. Render the actual - change inline in the terminal: for `Edit`, a small `+`/`-` - per-line diff between `input.old_string` and `input.new_string`; - for `Write`, the first few lines of `input.content` (it's all - "+"). Keep collapsed by default (`
` like the existing - tool_result rollups), expand to full diff on click. Color via - the same `.diff-add` / `.diff-del` classes the dashboard - approval diff already uses. - **Terminal: `/model` slash command.** Operator-typeable model override from the terminal. Depends on the model-override work above; once an override mechanism exists, wire a `/model ` diff --git a/hive-ag3nt/assets/agent.css b/hive-ag3nt/assets/agent.css index 013fc82..5e6b0c6 100644 --- a/hive-ag3nt/assets/agent.css +++ b/hive-ag3nt/assets/agent.css @@ -333,6 +333,22 @@ details.row > summary::before { } details.row[open] > summary::before { content: '▾ '; } details.row.tool-result-block > summary { color: var(--muted); } +/* Inline diff body for Write / Edit tool_use rows: same shape as + tool-body but each line is wrapped in a span with diff-add / + diff-del / diff-ctx so + / - lines are colored. */ +details.row > pre.diff-body { + margin: 0.3em 0 0.4em 1.2em; + padding: 0.4em 0.6em; + background: rgba(255, 255, 255, 0.02); + border-left: 2px solid var(--purple-dim); + white-space: pre-wrap; + word-break: break-word; + max-height: 22em; + overflow-y: auto; +} +details.row > pre.diff-body .diff-add { color: var(--green); } +details.row > pre.diff-body .diff-del { color: var(--red); } +details.row > pre.diff-body .diff-ctx { color: var(--fg); } details.row > pre.tool-body { margin: 0.3em 0 0.4em 1.2em; padding: 0.4em 0.6em; diff --git a/hive-ag3nt/assets/app.js b/hive-ag3nt/assets/app.js index 0a40d59..12a5404 100644 --- a/hive-ag3nt/assets/app.js +++ b/hive-ag3nt/assets/app.js @@ -514,6 +514,63 @@ default: return name + ' ' + trim(JSON.stringify(input), 200); } } + // Build a tool_use row for Write/Edit as a collapsed
+ // showing the actual change. Returns null for any other tool so + // the caller falls back to the flat-row path. + // Write: every input.content line is "+". + // Edit: old_string lines as "-", new_string lines as "+". + // Not a true diff algorithm — claude's Edit blocks are already a + // contiguous old/new pair, so a literal -/+ rendering is honest. + function renderFileWriteEdit(c) { + const name = c.name || ''; + const input = c.input || {}; + if (name !== 'Write' && name !== 'Edit') return null; + const path = input.file_path || '?'; + let body; + let plus = 0; + let minus = 0; + if (name === 'Write') { + const content = String(input.content || ''); + const lines = content.split('\n'); + plus = lines.length; + body = lines.map(l => '+ ' + l).join('\n'); + } else { + const oldLines = String(input.old_string || '').split('\n'); + const newLines = String(input.new_string || '').split('\n'); + minus = oldLines.length; + plus = newLines.length; + body = oldLines.map(l => '- ' + l).join('\n') + + '\n' + + newLines.map(l => '+ ' + l).join('\n'); + } + const summary = '→ ' + name + ' ' + path + ' · ' + + (minus ? '-' + minus + ' ' : '') + '+' + plus; + return detailsDiff('tool-use', summary, body); + } + function detailsDiff(cls, summary, body) { + clearPlaceholder(); + const d = document.createElement('details'); + d.className = 'row ' + (cls || '') + (currentNoAnim ? ' no-anim' : ''); + const s = document.createElement('summary'); + s.textContent = summary; + d.appendChild(s); + const pre = document.createElement('pre'); + pre.className = 'tool-body diff-body'; + // Color each line by its leading +/-. + for (const line of body.split('\n')) { + const span = document.createElement('span'); + if (line.startsWith('+ ')) span.className = 'diff-add'; + else if (line.startsWith('- ')) span.className = 'diff-del'; + else span.className = 'diff-ctx'; + span.textContent = line + '\n'; + pre.appendChild(span); + } + d.appendChild(pre); + log.appendChild(d); + afterAppend(); + return d; + } + function renderToolResult(c) { const txt = Array.isArray(c.content) ? c.content.map(p => p.text || '').join('') @@ -547,7 +604,13 @@ const txt = (c.thinking || c.text || '').trim(); row('thinking', txt ? '· ' + txt : '· thinking …'); } - else if (c.type === 'tool_use') row('tool-use', '→ ' + fmtToolUse(c)); + else if (c.type === 'tool_use') { + // Write/Edit get a collapsed +/- diff body; everything + // else stays as the flat row produced by fmtToolUse. + if (!renderFileWriteEdit(c)) { + row('tool-use', '→ ' + fmtToolUse(c)); + } + } } return; }