fix(coding-agent): suppress hashline output for agents without edit tool

Hashline prefixes (LINE:HASH|content) in read/grep output exist solely to
support the hashline edit mode. Agents that don't have the edit tool
(e.g. explore, plan, reviewer) gain nothing from them — they just waste
tokens and add noise.

resolveFileDisplayMode now takes a session-like object with an optional
hasEditTool flag. When false, hashlines are suppressed regardless of
settings. ToolSession gains hasEditTool (set from toolNames in the SDK),
and AgentSession exposes it as a getter over the tool registry.
This commit is contained in:
can1357
2026-02-16 13:43:35 +01:00
parent 774acb04d6
commit 13d06f712c
7 changed files with 40 additions and 18 deletions
+3
View File
@@ -701,6 +701,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
cwd,
hasUI: options.hasUI ?? false,
enableLsp,
get hasEditTool() {
return !options.toolNames || options.toolNames.includes("edit");
},
skipPythonPreflight: options.skipPythonPreflight,
contextFiles,
skills,
@@ -1072,6 +1072,11 @@ export class AgentSession {
return this.agent.state.tools.map(t => t.name);
}
/** Whether the edit tool is registered in this session. */
get hasEditTool(): boolean {
return this.#toolRegistry.has("edit");
}
/**
* Get a tool by name from the registry.
*/
@@ -1518,7 +1523,7 @@ export class AgentSession {
if (fileMentions.length > 0) {
const fileMentionMessages = await generateFileMentionMessages(fileMentions, this.sessionManager.getCwd(), {
autoResizeImages: this.settings.get("images.autoResize"),
useHashLines: resolveFileDisplayMode(this.settings).hashLines,
useHashLines: resolveFileDisplayMode(this).hashLines,
});
messages.push(...fileMentionMessages);
}
+2 -2
View File
@@ -62,7 +62,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
readonly parameters = grepSchema;
constructor(private readonly session: ToolSession) {
const displayMode = resolveFileDisplayMode(session.settings);
const displayMode = resolveFileDisplayMode(session);
this.description = renderPromptTemplate(grepDescription, {
IS_HASHLINE_MODE: displayMode.hashLines,
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
@@ -103,7 +103,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
const effectiveMultiline = multiline ?? patternHasNewline;
const useHashLines = resolveFileDisplayMode(this.session.settings).hashLines;
const useHashLines = resolveFileDisplayMode(this.session).hashLines;
const searchPath = resolveToCwd(searchDir || ".", this.session.cwd);
const scopePath = (() => {
const relative = path.relative(this.session.cwd, searchPath).replace(/\\/g, "/");
+2
View File
@@ -119,6 +119,8 @@ export interface ToolSession {
promptTemplates?: PromptTemplate[];
/** Whether LSP integrations are enabled */
enableLsp?: boolean;
/** Whether the edit tool is available in this session (controls hashline output) */
hasEditTool?: boolean;
/** Event bus for tool/extension communication */
eventBus?: EventBus;
/** Output schema for structured completion (subagents) */
+3 -3
View File
@@ -544,7 +544,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
readonly #autoResizeImages: boolean;
constructor(private readonly session: ToolSession) {
const displayMode = resolveFileDisplayMode(session.settings);
const displayMode = resolveFileDisplayMode(session);
this.#autoResizeImages = session.settings.get("images.autoResize");
this.description = renderPromptTemplate(readDescription, {
DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
@@ -562,7 +562,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
): Promise<AgentToolResult<ReadToolDetails>> {
const { path: readPath, offset, limit } = params;
const displayMode = resolveFileDisplayMode(this.session.settings);
const displayMode = resolveFileDisplayMode(this.session);
// Handle internal URLs (agent://, skill://)
const internalRouter = this.session.internalRouter;
@@ -848,7 +848,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
async #handleInternalUrl(url: string, offset?: number, limit?: number): Promise<AgentToolResult<ReadToolDetails>> {
const internalRouter = this.session.internalRouter!;
const displayMode = resolveFileDisplayMode(this.session.settings);
const displayMode = resolveFileDisplayMode(this.session);
// Check if URL has query extraction (agent:// only)
let parsed: URL;
@@ -1,25 +1,34 @@
/**
* Resolve line-display mode for file-like outputs (read, grep, @file mentions).
*/
export interface FileDisplayModeSettings {
get(key: "readLineNumbers" | "readHashLines" | "edit.mode"): unknown;
}
export interface FileDisplayMode {
lineNumbers: boolean;
hashLines: boolean;
}
/**
* Computes effective line display mode from settings/env.
* Hashline mode takes precedence and implies line-addressed output everywhere.
*/
export function resolveFileDisplayMode(settings: FileDisplayModeSettings): FileDisplayMode {
const hashLines =
settings.get("readHashLines") === true ||
settings.get("edit.mode") === "hashline" ||
Bun.env.PI_EDIT_VARIANT === "hashline";
/** Session-like object providing settings and tool availability for display mode resolution. */
export interface FileDisplayModeSession {
/** Whether the edit tool is available. Hashlines are suppressed without it. */
hasEditTool?: boolean;
settings: {
get(key: "readLineNumbers" | "readHashLines" | "edit.mode"): unknown;
};
}
/**
* Computes effective line display mode from session settings/env.
* Hashline mode takes precedence and implies line-addressed output everywhere.
* Hashlines are suppressed when the edit tool is not available (e.g. explore agents).
*/
export function resolveFileDisplayMode(session: FileDisplayModeSession): FileDisplayMode {
const { settings } = session;
const hasEditTool = session.hasEditTool ?? true;
const hashLines =
hasEditTool &&
(settings.get("readHashLines") === true ||
settings.get("edit.mode") === "hashline" ||
Bun.env.PI_EDIT_VARIANT === "hashline");
return {
hashLines,
lineNumbers: hashLines || settings.get("readLineNumbers") === true,