From 755a36b09f1e517ef0b19aaf6a4c3d152827165a Mon Sep 17 00:00:00 2001 From: Ammar Date: Fri, 14 Nov 2025 20:01:03 +0000 Subject: [PATCH 1/4] feat: allow file_edit_insert to create files implicitly --- src/services/tools/file_edit_insert.test.ts | 17 +++++++++++++++-- src/services/tools/file_edit_insert.ts | 12 ++---------- src/types/tools.ts | 2 +- src/utils/tools/toolDefinitions.ts | 15 +++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/services/tools/file_edit_insert.test.ts b/src/services/tools/file_edit_insert.test.ts index 94113cae5..b3abb66bf 100644 --- a/src/services/tools/file_edit_insert.test.ts +++ b/src/services/tools/file_edit_insert.test.ts @@ -96,13 +96,12 @@ describe("file_edit_insert tool", () => { } }); - it("creates new file when create flag is provided", async () => { + it("creates a new file without requiring create flag or guards", async () => { const newFile = path.join(testDir, "new.txt"); const tool = createTestTool(testDir); const args: FileEditInsertToolArgs = { file_path: path.relative(testDir, newFile), content: "Hello world!\n", - create: true, }; const result = (await tool.execute!(args, mockToolCallOptions)) as FileEditInsertToolResult; @@ -110,6 +109,20 @@ describe("file_edit_insert tool", () => { expect(await fs.readFile(newFile, "utf-8")).toBe("Hello world!\n"); }); + it("still supports the legacy create flag for backwards compatibility", async () => { + const newFile = path.join(testDir, "legacy.txt"); + const tool = createTestTool(testDir); + const args: FileEditInsertToolArgs = { + file_path: path.relative(testDir, newFile), + content: "Legacy\n", + create: true, + }; + + const result = (await tool.execute!(args, mockToolCallOptions)) as FileEditInsertToolResult; + expect(result.success).toBe(true); + expect(await fs.readFile(newFile, "utf-8")).toBe("Legacy\n"); + }); + it("fails when no guards are provided", async () => { const tool = createTestTool(testDir); const args: FileEditInsertToolArgs = { diff --git a/src/services/tools/file_edit_insert.ts b/src/services/tools/file_edit_insert.ts index 524a634ce..6f0c49d1e 100644 --- a/src/services/tools/file_edit_insert.ts +++ b/src/services/tools/file_edit_insert.ts @@ -48,7 +48,7 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration) description: TOOL_DEFINITIONS.file_edit_insert.description, inputSchema: TOOL_DEFINITIONS.file_edit_insert.schema, execute: async ( - { file_path, content, before, after, create }: FileEditInsertToolArgs, + { file_path, content, before, after }: FileEditInsertToolArgs, { abortSignal } ): Promise => { try { @@ -71,14 +71,6 @@ export const createFileEditInsertTool: ToolFactory = (config: ToolConfiguration) const exists = await fileExists(config.runtime, resolvedPath, abortSignal); if (!exists) { - if (!create) { - return { - success: false, - error: `File not found: ${file_path}. Set create: true to create it.`, - note: `${EDIT_FAILED_NOTE_PREFIX} File does not exist. Set create: true to create it, or check the file path.`, - }; - } - try { await writeFileString(config.runtime, resolvedPath, content, abortSignal); } catch (err) { @@ -139,7 +131,7 @@ function insertContent( } if (before === undefined && after === undefined) { - return guardFailure("Provide either a before or after guard to anchor the insertion point."); + return guardFailure("Provide either a before or after guard when editing existing files."); } return insertWithGuards(originalContent, contentToInsert, { before, after }); diff --git a/src/types/tools.ts b/src/types/tools.ts index 7a060b233..76dcdd813 100644 --- a/src/types/tools.ts +++ b/src/types/tools.ts @@ -74,7 +74,7 @@ export interface FileEditErrorResult { export interface FileEditInsertToolArgs { file_path: string; content: string; - /** When true, create the file if it doesn't exist */ + /** @deprecated Legacy no-op. Files are created automatically when missing. */ create?: boolean; /** Optional substring that must appear immediately before the insertion point */ before?: string; diff --git a/src/utils/tools/toolDefinitions.ts b/src/utils/tools/toolDefinitions.ts index e63f7f221..511ef4687 100644 --- a/src/utils/tools/toolDefinitions.ts +++ b/src/utils/tools/toolDefinitions.ts @@ -116,13 +116,16 @@ export const TOOL_DEFINITIONS = { description: "Insert content into a file using substring guards. " + "Provide exactly one of before or after to anchor the operation when editing an existing file. " + - "Set create: true to write a brand new file without guards. " + + "Missing files are created automatically, so guards are optional when creating new files. " + `Optional before/after substrings must uniquely match surrounding content. ${TOOL_EDIT_WARNING}`, schema: z .object({ file_path: FILE_EDIT_FILE_PATH, content: z.string().describe("The content to insert"), - create: z.boolean().optional().describe("If true, create the file if it does not exist"), + create: z + .boolean() + .optional() + .describe("Legacy compatibility; files are created automatically when missing, so this flag is ignored."), before: z .string() .min(1) @@ -134,14 +137,6 @@ export const TOOL_DEFINITIONS = { .optional() .describe("Optional substring that must appear immediately after the insertion point"), }) - .refine( - (data) => data.create === true || data.before !== undefined || data.after !== undefined, - { - message: - "Provide before or after when editing existing files, or set create: true to write a new file.", - path: ["before"], - } - ) .refine((data) => !(data.before !== undefined && data.after !== undefined), { message: "Provide only one of before or after (not both).", path: ["before"], From 003a076445a29297a36b8257e40d403a788534cc Mon Sep 17 00:00:00 2001 From: Ammar Date: Fri, 14 Nov 2025 20:05:49 +0000 Subject: [PATCH 2/4] fix: make telemetry version access safe --- src/telemetry/utils.ts | 13 ++++++++++++- src/utils/tools/toolDefinitions.ts | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/telemetry/utils.ts b/src/telemetry/utils.ts index be35a1d00..fd07398a7 100644 --- a/src/telemetry/utils.ts +++ b/src/telemetry/utils.ts @@ -8,9 +8,20 @@ import { VERSION } from "../version"; /** * Get base telemetry properties included with all events */ +function resolveGitDescribe(value: unknown): string { + if (typeof value === "object" && value !== null && "git_describe" in value) { + const describe = (value as Record).git_describe; + if (typeof describe === "string") { + return describe; + } + } + + return "unknown"; +} + export function getBaseTelemetryProperties(): BaseTelemetryProperties { return { - version: String(VERSION.git_describe), + version: resolveGitDescribe(VERSION), platform: window.api?.platform || "unknown", electronVersion: window.api?.versions?.electron || "unknown", }; diff --git a/src/utils/tools/toolDefinitions.ts b/src/utils/tools/toolDefinitions.ts index 511ef4687..9b2f0f21a 100644 --- a/src/utils/tools/toolDefinitions.ts +++ b/src/utils/tools/toolDefinitions.ts @@ -125,7 +125,9 @@ export const TOOL_DEFINITIONS = { create: z .boolean() .optional() - .describe("Legacy compatibility; files are created automatically when missing, so this flag is ignored."), + .describe( + "Legacy compatibility; files are created automatically when missing, so this flag is ignored." + ), before: z .string() .min(1) From 25c85fa3851e50d7f9e640366b6cdcf4da18628a Mon Sep 17 00:00:00 2001 From: Ammar Date: Fri, 14 Nov 2025 20:39:21 +0000 Subject: [PATCH 3/4] test: verify bash output instead of summary --- tests/ipcMain/runtimeExecuteBash.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/ipcMain/runtimeExecuteBash.test.ts b/tests/ipcMain/runtimeExecuteBash.test.ts index dcb99484e..1decaa388 100644 --- a/tests/ipcMain/runtimeExecuteBash.test.ts +++ b/tests/ipcMain/runtimeExecuteBash.test.ts @@ -33,6 +33,7 @@ import { type SSHServerConfig, } from "../runtime/ssh-fixture"; import type { RuntimeConfig } from "../../src/types/runtime"; +import type { WorkspaceChatMessage } from "../../src/types/ipc"; import type { ToolPolicy } from "../../src/utils/tools/toolPolicy"; // Tool policy: Only allow bash tool @@ -41,6 +42,16 @@ const BASH_ONLY: ToolPolicy = [ { regex_match: "file_.*", action: "disable" }, ]; +function collectToolOutputs(events: WorkspaceChatMessage[], toolName: string): string { + return events + .filter((event: any) => event.type === "tool-call-end" && event.toolName === toolName) + .map((event: any) => { + const output = event.result?.output; + return typeof output === "string" ? output : ""; + }) + .join("\n"); +} + // Skip all tests if TEST_INTEGRATION is not set const describeIntegration = shouldRunIntegrationTests() ? describe : describe.skip; @@ -264,7 +275,8 @@ describeIntegration("Runtime Bash Execution", () => { // Verify command completed successfully (not timeout) expect(responseText).toContain("test"); - expect(responseText).toContain("data"); + const bashOutput = collectToolOutputs(events, "bash"); + expect(bashOutput).toContain('"test": "data"'); // Verify command completed quickly (not hanging until timeout) // With tokenizer preloading, both local and SSH complete in ~8s total From 0445183c06d348a2c554c783ab193d035304b115 Mon Sep 17 00:00:00 2001 From: Ammar Date: Fri, 14 Nov 2025 21:08:14 +0000 Subject: [PATCH 4/4] fix: drop file_edit_insert create flag --- src/services/tools/file_edit_insert.test.ts | 14 -------------- src/telemetry/utils.ts | 20 ++++++++------------ src/types/tools.ts | 2 -- src/utils/tools/toolDefinitions.ts | 8 +------- 4 files changed, 9 insertions(+), 35 deletions(-) diff --git a/src/services/tools/file_edit_insert.test.ts b/src/services/tools/file_edit_insert.test.ts index b3abb66bf..b3fad60a8 100644 --- a/src/services/tools/file_edit_insert.test.ts +++ b/src/services/tools/file_edit_insert.test.ts @@ -109,20 +109,6 @@ describe("file_edit_insert tool", () => { expect(await fs.readFile(newFile, "utf-8")).toBe("Hello world!\n"); }); - it("still supports the legacy create flag for backwards compatibility", async () => { - const newFile = path.join(testDir, "legacy.txt"); - const tool = createTestTool(testDir); - const args: FileEditInsertToolArgs = { - file_path: path.relative(testDir, newFile), - content: "Legacy\n", - create: true, - }; - - const result = (await tool.execute!(args, mockToolCallOptions)) as FileEditInsertToolResult; - expect(result.success).toBe(true); - expect(await fs.readFile(newFile, "utf-8")).toBe("Legacy\n"); - }); - it("fails when no guards are provided", async () => { const tool = createTestTool(testDir); const args: FileEditInsertToolArgs = { diff --git a/src/telemetry/utils.ts b/src/telemetry/utils.ts index fd07398a7..b6b30008f 100644 --- a/src/telemetry/utils.ts +++ b/src/telemetry/utils.ts @@ -8,20 +8,16 @@ import { VERSION } from "../version"; /** * Get base telemetry properties included with all events */ -function resolveGitDescribe(value: unknown): string { - if (typeof value === "object" && value !== null && "git_describe" in value) { - const describe = (value as Record).git_describe; - if (typeof describe === "string") { - return describe; - } - } - - return "unknown"; -} - export function getBaseTelemetryProperties(): BaseTelemetryProperties { + const gitDescribe = + typeof VERSION === "object" && + VERSION !== null && + typeof (VERSION as Record).git_describe === "string" + ? (VERSION as { git_describe: string }).git_describe + : "unknown"; + return { - version: resolveGitDescribe(VERSION), + version: gitDescribe, platform: window.api?.platform || "unknown", electronVersion: window.api?.versions?.electron || "unknown", }; diff --git a/src/types/tools.ts b/src/types/tools.ts index 76dcdd813..633d151f7 100644 --- a/src/types/tools.ts +++ b/src/types/tools.ts @@ -74,8 +74,6 @@ export interface FileEditErrorResult { export interface FileEditInsertToolArgs { file_path: string; content: string; - /** @deprecated Legacy no-op. Files are created automatically when missing. */ - create?: boolean; /** Optional substring that must appear immediately before the insertion point */ before?: string; /** Optional substring that must appear immediately after the insertion point */ diff --git a/src/utils/tools/toolDefinitions.ts b/src/utils/tools/toolDefinitions.ts index 9b2f0f21a..b8c838e8a 100644 --- a/src/utils/tools/toolDefinitions.ts +++ b/src/utils/tools/toolDefinitions.ts @@ -116,18 +116,12 @@ export const TOOL_DEFINITIONS = { description: "Insert content into a file using substring guards. " + "Provide exactly one of before or after to anchor the operation when editing an existing file. " + - "Missing files are created automatically, so guards are optional when creating new files. " + + "When the file does not exist, it is created automatically without guards. " + `Optional before/after substrings must uniquely match surrounding content. ${TOOL_EDIT_WARNING}`, schema: z .object({ file_path: FILE_EDIT_FILE_PATH, content: z.string().describe("The content to insert"), - create: z - .boolean() - .optional() - .describe( - "Legacy compatibility; files are created automatically when missing, so this flag is ignored." - ), before: z .string() .min(1)