Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ The IPC layer is the boundary between backend and frontend. Follow these rules t
Notice when you've made the same change many times, refactor to create a shared function
or component, update all the duplicated code, and then continue on with the original work.
When repeating string literals (especially in error messages, UI text, or system instructions), extract them to named constants in a relevant constants/utils file - never define the same string literal multiple times across files.
Before defining a constant, search the codebase to see if it already exists and import it instead.
If a constant exists in a layer-specific location (`services/`, `utils/main/`) but is needed across layers, move it to a shared location (`src/constants/`, `src/types/`) rather than duplicating it.

**Avoid unnecessary callback indirection**: If a hook detects a condition and has access to all data needed to handle it, let it handle the action directly rather than passing callbacks up to parent components. Keep hooks self-contained when possible.

Expand Down
7 changes: 6 additions & 1 deletion src/components/tools/FileEditToolCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useToolExpansion, getStatusDisplay, type ToolStatus } from "./shared/to
import { TooltipWrapper, Tooltip } from "../Tooltip";
import { DiffContainer, DiffRenderer, SelectableDiffRenderer } from "../shared/DiffRenderer";
import { KebabMenu, type KebabMenuItem } from "../KebabMenu";
import { WRITE_DENIED_PREFIX } from "@/types/tools";

type FileEditOperationArgs =
| FileEditReplaceStringToolArgs
Expand Down Expand Up @@ -97,7 +98,11 @@ export const FileEditToolCall: React.FC<FileEditToolCallProps> = ({
status = "pending",
onReviewNote,
}) => {
const { expanded, toggleExpanded } = useToolExpansion(true);
// Collapse WRITE DENIED errors by default since they're common and expected
const isWriteDenied = result && !result.success && result.error?.startsWith(WRITE_DENIED_PREFIX);
const initialExpanded = !isWriteDenied;

const { expanded, toggleExpanded } = useToolExpansion(initialExpanded);
const [showRaw, setShowRaw] = React.useState(false);
const [copied, setCopied] = React.useState(false);

Expand Down
6 changes: 1 addition & 5 deletions src/services/tools/fileCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import type * as fs from "fs";
import * as path from "path";
import { createPatch } from "diff";

/**
* Prefix for all file write error messages.
* This consistent prefix helps models detect when writes fail and need to retry.
*/
export const WRITE_DENIED_PREFIX = "WRITE DENIED, FILE UNMODIFIED:";
// WRITE_DENIED_PREFIX moved to @/types/tools for frontend/backend sharing

/**
* Maximum file size for file operations (1MB)
Expand Down
3 changes: 2 additions & 1 deletion src/services/tools/file_edit_insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as path from "path";
import type { FileEditInsertToolResult } from "@/types/tools";
import type { ToolConfiguration, ToolFactory } from "@/utils/tools/tools";
import { TOOL_DEFINITIONS } from "@/utils/tools/toolDefinitions";
import { validatePathInCwd, WRITE_DENIED_PREFIX } from "./fileCommon";
import { validatePathInCwd } from "./fileCommon";
import { WRITE_DENIED_PREFIX } from "@/types/tools";
import { executeFileEditOperation } from "./file_edit_operation";

/**
Expand Down
2 changes: 1 addition & 1 deletion src/services/tools/file_edit_operation.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect } from "bun:test";
import { executeFileEditOperation } from "./file_edit_operation";
import { WRITE_DENIED_PREFIX } from "./fileCommon";
import { WRITE_DENIED_PREFIX } from "@/types/tools";

const TEST_CWD = "/tmp";

Expand Down
8 changes: 2 additions & 6 deletions src/services/tools/file_edit_operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ import * as fs from "fs/promises";
import * as path from "path";
import writeFileAtomic from "write-file-atomic";
import type { FileEditDiffSuccessBase, FileEditErrorResult } from "@/types/tools";
import { WRITE_DENIED_PREFIX } from "@/types/tools";
import type { ToolConfiguration } from "@/utils/tools/tools";
import {
generateDiff,
validateFileSize,
validatePathInCwd,
WRITE_DENIED_PREFIX,
} from "./fileCommon";
import { generateDiff, validateFileSize, validatePathInCwd } from "./fileCommon";

type FileEditOperationResult<TMetadata> =
| {
Expand Down
6 changes: 6 additions & 0 deletions src/types/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export interface FileEditInsertToolArgs {

export type FileEditInsertToolResult = FileEditDiffSuccessBase | FileEditErrorResult;

/**
* Prefix for file write denial error messages.
* This consistent prefix helps both the UI and models detect when writes fail.
*/
export const WRITE_DENIED_PREFIX = "WRITE DENIED, FILE UNMODIFIED:";

export type FileEditToolArgs =
| FileEditReplaceStringToolArgs
| FileEditReplaceLinesToolArgs
Expand Down