diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue
index a4acc4e58..bd013e582 100644
--- a/apps/desktop/src/App.vue
+++ b/apps/desktop/src/App.vue
@@ -72,6 +72,7 @@ import {
import { isPreviewTab } from "@/lib/tabs/tabPresentation";
import { supportsSqlFileExecution } from "@/lib/database/databaseCapabilities";
import { classifyAiSqlExecution } from "@/lib/ai/aiSqlExecutionPolicy";
+import { buildAppendedEditorSql } from "@/lib/ai/aiSqlAppend";
import { buildHistoryAiAnalysisPrompt } from "@/lib/history/historyAiAnalysis";
import { countAvailableAgentDriverUpdates, type AgentDriverUpdateBadgeState } from "@/lib/connection/agentDriverUpdateBadge";
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
@@ -1250,16 +1251,25 @@ function onAiReplaceSql(sql: string) {
queryStore.updateSql(tabId, sql);
}
-function onAiExecuteSql(sql: string) {
- const tabId = ensureQueryTab();
- queryStore.updateSql(tabId, sql);
+function runAiGeneratedSql(sql: string) {
selectedSql.value = "";
nextTick(() => tryExecute(sql));
}
+function onAiExecuteSql(sql: string) {
+ const tabId = ensureQueryTab();
+ queryStore.updateSql(tabId, buildAppendedEditorSql(activeTab.value?.sql || "", sql));
+ runAiGeneratedSql(sql);
+}
+
+function onAiTempRunSql(sql: string) {
+ ensureQueryTab();
+ runAiGeneratedSql(sql);
+}
+
function onAiRequestAutoExecuteSql(sql: string) {
const tabId = ensureQueryTab();
- queryStore.updateSql(tabId, sql);
+ queryStore.updateSql(tabId, buildAppendedEditorSql(activeTab.value?.sql || "", sql));
selectedSql.value = "";
const decision = classifyAiSqlExecution(sql, activeConnection.value);
@@ -1281,7 +1291,7 @@ function onAiRequestAutoExecuteSql(sql: string) {
function onAiOpenExplainPlan(sql: string) {
const tabId = ensureQueryTab();
- queryStore.updateSql(tabId, sql);
+ queryStore.updateSql(tabId, buildAppendedEditorSql(activeTab.value?.sql || "", sql));
selectedSql.value = "";
nextTick(() => {
void tryExplain(sql);
@@ -1952,7 +1962,18 @@ onUnmounted(() => {
+
diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts
index 55b3b5a70..0c7dcbe3f 100644
--- a/apps/desktop/src/i18n/locales/en.ts
+++ b/apps/desktop/src/i18n/locales/en.ts
@@ -1234,6 +1234,7 @@ export default {
copySql: "Copy SQL",
copyCode: "Copy Code",
executeSql: "Execute SQL",
+ tempRunSql: "Run without modifying editor",
copyAll: "Copy All",
copied: "Copied",
copyTestResult: "Copy test result",
diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts
index ae3bb6b88..a672c9f53 100644
--- a/apps/desktop/src/i18n/locales/es.ts
+++ b/apps/desktop/src/i18n/locales/es.ts
@@ -1196,6 +1196,7 @@ export default withEnglishFallback({
copySql: "Copiar SQL",
copyCode: "Copiar código",
executeSql: "Ejecutar SQL",
+ tempRunSql: "Ejecutar sin modificar",
copyAll: "Copiar todo",
copied: "Copiado",
copyTestResult: "Copiar resultado de prueba",
diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts
index e26f83b2a..9763bf219 100644
--- a/apps/desktop/src/i18n/locales/it.ts
+++ b/apps/desktop/src/i18n/locales/it.ts
@@ -1177,6 +1177,7 @@ export default withEnglishFallback({
copySql: "Copia SQL",
copyCode: "Copia Codice",
executeSql: "Esegui SQL",
+ tempRunSql: "Esegui senza modificare",
copyAll: "Copia Tutto",
copied: "Copiato",
copyTestResult: "Copia risultato test",
diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts
index 1a9de2f61..0a65e1060 100644
--- a/apps/desktop/src/i18n/locales/ja.ts
+++ b/apps/desktop/src/i18n/locales/ja.ts
@@ -1199,6 +1199,7 @@ export default withEnglishFallback({
copySql: "SQLをコピー",
copyCode: "コードをコピー",
executeSql: "SQLを実行",
+ tempRunSql: "編集せずに実行",
copyAll: "すべてコピー",
copied: "コピーしました",
copyTestResult: "テスト結果をコピー",
diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts
index 313743b0c..5b912fc1f 100644
--- a/apps/desktop/src/i18n/locales/pt-BR.ts
+++ b/apps/desktop/src/i18n/locales/pt-BR.ts
@@ -1197,6 +1197,7 @@ export default withEnglishFallback({
copySql: "Copiar SQL",
copyCode: "Copiar Código",
executeSql: "Executar SQL",
+ tempRunSql: "Executar sem modificar",
copyAll: "Copiar Tudo",
copied: "Copiado",
copyTestResult: "Copiar resultado do teste",
diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts
index bf212257a..e9289e7a2 100644
--- a/apps/desktop/src/i18n/locales/zh-CN.ts
+++ b/apps/desktop/src/i18n/locales/zh-CN.ts
@@ -1235,6 +1235,7 @@ export default withEnglishFallback({
copySql: "复制 SQL",
copyCode: "复制代码",
executeSql: "立即执行",
+ tempRunSql: "临时运行",
copyAll: "复制全部",
copied: "已复制",
copyTestResult: "复制测试结果",
diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts
index ef084e956..b4e2b4192 100644
--- a/apps/desktop/src/i18n/locales/zh-TW.ts
+++ b/apps/desktop/src/i18n/locales/zh-TW.ts
@@ -1177,6 +1177,7 @@ export default withEnglishFallback({
copySql: "複製 SQL",
copyCode: "複製程式碼",
executeSql: "立即執行",
+ tempRunSql: "臨時執行",
copyAll: "複製全部",
copied: "已複製",
copyTestResult: "複製測試結果",
diff --git a/apps/desktop/src/lib/ai/aiSqlAppend.ts b/apps/desktop/src/lib/ai/aiSqlAppend.ts
new file mode 100644
index 000000000..11e01ce95
--- /dev/null
+++ b/apps/desktop/src/lib/ai/aiSqlAppend.ts
@@ -0,0 +1,19 @@
+/**
+ * Build the editor content after appending AI-generated SQL to existing editor SQL.
+ * Preserves the existing editor content exactly and adds only the newline separator
+ * needed to leave a blank line before the appended SQL.
+ */
+export function buildAppendedEditorSql(currentEditorSql: string, newSql: string): string {
+ if (!currentEditorSql) return newSql;
+
+ let separator = "\n\n";
+ if (currentEditorSql.endsWith("\r\n\r\n") || currentEditorSql.endsWith("\n\n")) {
+ separator = "";
+ } else if (currentEditorSql.endsWith("\r\n")) {
+ separator = "\r\n";
+ } else if (currentEditorSql.endsWith("\n")) {
+ separator = "\n";
+ }
+
+ return `${currentEditorSql}${separator}${newSql}`;
+}
diff --git a/packages/app-tests/aiSqlAppend.test.ts b/packages/app-tests/aiSqlAppend.test.ts
new file mode 100644
index 000000000..fd6130d24
--- /dev/null
+++ b/packages/app-tests/aiSqlAppend.test.ts
@@ -0,0 +1,35 @@
+import { strict as assert } from "node:assert";
+import { test } from "vitest";
+import { buildAppendedEditorSql } from "../../apps/desktop/src/lib/ai/aiSqlAppend.ts";
+
+test("buildAppendedEditorSql returns newSql unchanged when editor is empty", () => {
+ assert.equal(buildAppendedEditorSql("", "SELECT 1"), "SELECT 1");
+});
+
+test("buildAppendedEditorSql prepends blank-line separator when editor has content", () => {
+ assert.equal(buildAppendedEditorSql("SELECT 1", "SELECT 2"), "SELECT 1\n\nSELECT 2");
+});
+
+test("buildAppendedEditorSql preserves multiline existing content", () => {
+ assert.equal(buildAppendedEditorSql("SELECT *\nFROM users", "SELECT *\nFROM orders"), "SELECT *\nFROM users\n\nSELECT *\nFROM orders");
+});
+
+test("buildAppendedEditorSql preserves trailing newlines already present in the editor", () => {
+ assert.equal(buildAppendedEditorSql("SELECT 1\n\n\n", "SELECT 2"), "SELECT 1\n\n\nSELECT 2");
+});
+
+test("buildAppendedEditorSql preserves trailing spaces", () => {
+ assert.equal(buildAppendedEditorSql("SELECT 1 ", "SELECT 2"), "SELECT 1 \n\nSELECT 2");
+});
+
+test("buildAppendedEditorSql preserves trailing tabs", () => {
+ assert.equal(buildAppendedEditorSql("SELECT 1\t\t", "SELECT 2"), "SELECT 1\t\t\n\nSELECT 2");
+});
+
+test("buildAppendedEditorSql preserves whitespace-only editor content", () => {
+ assert.equal(buildAppendedEditorSql(" \t ", "SELECT 2"), " \t \n\nSELECT 2");
+});
+
+test("buildAppendedEditorSql preserves unfinished SQL", () => {
+ assert.equal(buildAppendedEditorSql("SELECT * FROM", "SELECT * FROM users"), "SELECT * FROM\n\nSELECT * FROM users");
+});