From 8cab107a48aca5c8d72a6660fb9bcf2cd7c57542 Mon Sep 17 00:00:00 2001 From: can1357 Date: Wed, 18 Mar 2026 23:20:21 +0100 Subject: [PATCH] feat(coding-agent): added automatic stripping of hashline prefixes from write tool output - Added automatic stripping of hashline display prefixes (LINE#ID:) from write tool content when hashline edit mode is enabled, preventing models from accidentally copying display markers into files. - Implemented stripHashlinePrefixes() utility to remove hashline-only prefixes without affecting diff markers. - Added stripWriteContent() helper to conditionally clean write content based on file display mode configuration. - Updated write tool result message to notify users when hashline prefixes were auto-stripped. --- packages/coding-agent/CHANGELOG.md | 2 ++ packages/coding-agent/src/patch/index.ts | 21 ++++++++++++++++++ packages/coding-agent/src/tools/write.ts | 28 ++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 333b4f9ba..c69d0635e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,8 +1,10 @@ # Changelog ## [Unreleased] + ### Added +- Added automatic stripping of hashline display prefixes (LINE#ID:) from write tool content when hashline edit mode is enabled, preventing the model from accidentally copying display markers into files - Added `mcpServerName` and `mcpToolName` optional properties to custom tools for MCP server discovery and search metadata ## [13.13.1] - 2026-03-18 diff --git a/packages/coding-agent/src/patch/index.ts b/packages/coding-agent/src/patch/index.ts index 53e0f872a..64fb7a2b3 100644 --- a/packages/coding-agent/src/patch/index.ts +++ b/packages/coding-agent/src/patch/index.ts @@ -136,6 +136,27 @@ export function stripNewLinePrefixes(lines: string[]): string[] { }); } +/** + * Strip hashline display prefixes only (no diff markers). + * + * Unlike {@link stripNewLinePrefixes} which also handles `+` diff markers, + * this only strips `LINE#ID:` / `#ID:` prefixes. Used by the write tool + * where diff markers are not applicable. + * + * Returns the original array reference when no stripping is needed. + */ +export function stripHashlinePrefixes(lines: string[]): string[] { + let hashPrefixCount = 0; + let nonEmpty = 0; + for (const l of lines) { + if (l.length === 0) continue; + nonEmpty++; + if (HASHLINE_PREFIX_RE.test(l)) hashPrefixCount++; + } + if (nonEmpty === 0 || hashPrefixCount !== nonEmpty) return lines; + return lines.map(l => l.replace(HASHLINE_PREFIX_RE, "")); +} + export function hashlineParseText(edit: string[] | string | null): string[] { if (edit === null) return []; if (typeof edit === "string") { diff --git a/packages/coding-agent/src/tools/write.ts b/packages/coding-agent/src/tools/write.ts index cb5900c4e..223119bd7 100644 --- a/packages/coding-agent/src/tools/write.ts +++ b/packages/coding-agent/src/tools/write.ts @@ -14,9 +14,11 @@ import { renderPromptTemplate } from "../config/prompt-templates"; import type { RenderResultOptions } from "../extensibility/custom-tools/types"; import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp"; import { getLanguageFromPath, type Theme } from "../modes/theme/theme"; +import { stripHashlinePrefixes } from "../patch"; import writeDescription from "../prompts/tools/write.md" with { type: "text" }; import type { ToolSession } from "../sdk"; import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui"; +import { resolveFileDisplayMode } from "../utils/file-display-mode"; import { checkAutoGeneratedFile } from "./auto-generated-guard"; import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation"; import { type OutputMeta, outputMeta } from "./output-meta"; @@ -60,6 +62,22 @@ function getLspBatchRequest(toolCall: ToolCallContext | undefined): { id: string return { id: toolCall.batchId, flush: !hasLaterWrites }; } +/** + * Strip hashline display prefixes from write content. + * + * Only active when hashline edit mode is enabled — the model sees `LINE#ID:` + * prefixes in read output and sometimes copies them into write content. + */ +function stripWriteContent(session: ToolSession, content: string): { text: string; stripped: boolean } { + if (!resolveFileDisplayMode(session).hashLines) { + return { text: content, stripped: false }; + } + const lines = content.split("\n"); + const cleaned = stripHashlinePrefixes(lines); + if (cleaned === lines) return { text: content, stripped: false }; + return { text: cleaned.join("\n"), stripped: true }; +} + // ═══════════════════════════════════════════════════════════════════════════ // Tool Class // ═══════════════════════════════════════════════════════════════════════════ @@ -109,10 +127,16 @@ export class WriteTool implements AgentTool