feat(cli): add dbx-cli runtime and agent integration (#229)

Feature/dbx cli runtime
This commit is contained in:
skyler
2026-05-12 17:22:36 +08:00
committed by GitHub
37 changed files with 6679 additions and 56 deletions
+18 -10
View File
@@ -15,6 +15,7 @@ import UpdateDialog from "@/components/layout/UpdateDialog.vue";
import LoginPage from "@/components/auth/LoginPage.vue";
import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore";
import { useAgentRuntimeStore } from "@/stores/agentRuntimeStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { useSavedSqlStore } from "@/stores/savedSqlStore";
import { useToast } from "@/composables/useToast";
@@ -37,6 +38,7 @@ import { isCloseTabShortcut, isExecuteSqlShortcut } from "@/lib/keyboardShortcut
import { isPreviewTab } from "@/lib/tabPresentation";
import { SQL_FILE_UNSUPPORTED_TYPES } from "@/lib/databaseCapabilities";
import { classifyAiSqlExecution } from "@/lib/aiSqlExecutionPolicy";
import { restoreStartupAgentRuntime } from "@/lib/appStartup";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -46,6 +48,7 @@ import type { HistoryEntry } from "@/lib/tauri";
const { t } = useI18n();
const connectionStore = useConnectionStore();
const queryStore = useQueryStore();
const agentRuntimeStore = useAgentRuntimeStore();
const settingsStore = useSettingsStore();
const savedSqlStore = useSavedSqlStore();
const { message: toastMessage, visible: toastVisible, toast } = useToast();
@@ -158,6 +161,7 @@ watch(
() => queryStore.activeTabId,
() => {
selectedSql.value = "";
agentRuntimeStore.setSelectedSql("");
activeOutputView.value = "result";
},
);
@@ -445,15 +449,14 @@ function onLoginSuccess() {
}
function initApp() {
savedSqlStore
.initFromStorage()
.then(() => connectionStore.initFromDisk())
.then(() => {
reconnectRestoredTabs();
})
.catch((e: any) => {
toast(t("connection.loadFailed", { message: e?.message || String(e) }), 5000);
});
restoreStartupAgentRuntime({
initSavedSql: () => savedSqlStore.initFromStorage(),
initConnections: () => connectionStore.initFromDisk(),
reconnectRestoredTabs,
scheduleSync: () => agentRuntimeStore.scheduleSync(),
}).catch((e: any) => {
toast(t("connection.loadFailed", { message: e?.message || String(e) }), 5000);
});
settingsStore.initAiConfig();
}
@@ -614,7 +617,12 @@ onUnmounted(() => {
if (queryStore.activeTabId) queryStore.updateSql(queryStore.activeTabId, v);
}
"
@editor-selection-change="(v: string) => (selectedSql = v)"
@editor-selection-change="
(v: string) => {
selectedSql = v;
agentRuntimeStore.setSelectedSql(v);
}
"
@editor-cursor-change="(p: number) => (cursorPos = p)"
@format-error="toast(t('toolbar.formatSqlFailed'))"
@reload="
@@ -0,0 +1,78 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted } from "vue";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { useAgentRuntimeStore } from "@/stores/agentRuntimeStore";
const agentRuntimeStore = useAgentRuntimeStore();
let refreshTimer: number | null = null;
const active = computed(() => agentRuntimeStore.activeHandoff);
const riskTone = computed(() => {
if (!active.value) return "secondary";
return active.value.riskLevel === "critical" || active.value.isProduction ? "destructive" : "secondary";
});
async function refresh() {
try {
await agentRuntimeStore.loadHandoffs();
} catch (err) {
console.debug("[DBX] Agent handoff refresh skipped:", err);
}
}
async function rejectActive() {
if (!active.value) return;
await agentRuntimeStore.rejectHandoff(active.value.id);
}
onMounted(() => {
void refresh();
refreshTimer = window.setInterval(() => void refresh(), 5000);
});
onUnmounted(() => {
if (refreshTimer) window.clearInterval(refreshTimer);
});
</script>
<template>
<Dialog v-model:open="agentRuntimeStore.handoffDialogOpen">
<DialogContent class="max-w-3xl">
<DialogHeader>
<DialogTitle>DBX Agent Handoff</DialogTitle>
</DialogHeader>
<div v-if="active" class="space-y-4">
<div class="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
<Badge :variant="riskTone">{{ active.riskLevel }}</Badge>
<span>{{ active.connectionName }}</span>
<span v-if="active.database">/ {{ active.database }}</span>
<span>/ {{ active.operationClass }}</span>
<span v-if="active.status === 'shown'">/ shown</span>
</div>
<div class="space-y-1">
<h3 class="text-sm font-semibold">{{ active.title }}</h3>
<p v-if="active.description" class="text-sm text-muted-foreground">{{ active.description }}</p>
</div>
<pre class="max-h-96 overflow-auto rounded-md border bg-muted/60 p-3 text-xs leading-relaxed">{{
active.sql
}}</pre>
<p class="text-xs text-muted-foreground">
Review only: DBX does not execute agent handoff SQL from this dialog.
</p>
</div>
<div v-else class="text-sm text-muted-foreground">No pending agent handoffs.</div>
<DialogFooter>
<Button variant="outline" @click="agentRuntimeStore.handoffDialogOpen = false">Close</Button>
<Button v-if="active" variant="destructive" @click="rejectActive">Reject</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>
+20 -1
View File
@@ -69,9 +69,10 @@ import {
quoteTableIdentifier,
} from "@/lib/tableSelectSql";
import { isHiddenGridColumn, usesSyntheticRowIdKey } from "@/lib/tableEditing";
import { displayCellValue, type CellValue } from "@/lib/cellValue";
import { formatGridSqlLiteral } from "@/lib/dataGridSql";
import { matchesRowStatusFilter, type RowStatus, type RowStatusFilter } from "@/lib/gridRowStatus";
import { displayCellValue, type CellValue } from "@/lib/cellValue";
import { useAgentRuntimeStore } from "@/stores/agentRuntimeStore";
import { useToast } from "@/composables/useToast";
import { useDataGridExport } from "@/composables/useDataGridExport";
@@ -81,6 +82,7 @@ import { useDataGridEditor } from "@/composables/useDataGridEditor";
const { t } = useI18n();
const { toast } = useToast();
const agentRuntimeStore = useAgentRuntimeStore();
const props = defineProps<{
result: QueryResult;
@@ -1134,6 +1136,22 @@ const activeCellDetail = computed(() => {
const detailEditValue = ref("");
const isEditingDetail = ref(false);
watch(
selectedCells,
(data) => {
agentRuntimeStore.setSelection(
selectedCellCount.value > 0
? {
type: "grid-cells",
range: selectedRange.value,
data,
}
: { type: "none" },
);
},
{ deep: true },
);
function startDetailEdit() {
const detail = activeCellDetail.value;
if (!detail || !detail.isEditable) return;
@@ -1655,6 +1673,7 @@ watch(
onUnmounted(() => {
cleanupFrames();
agentRuntimeStore.setSelection({ type: "none" });
onDdlResizeEnd();
finishCellSelection();
clearTimeout(_searchTimer);
+2
View File
@@ -4,6 +4,7 @@ import { useI18n } from "vue-i18n";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import ConnectionDialog from "@/components/connection/ConnectionDialog.vue";
import AgentHandoffDialog from "@/components/agent/AgentHandoffDialog.vue";
import EditorSettingsDialog from "@/components/editor/EditorSettingsDialog.vue";
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
const DataTransferDialog = defineAsyncComponent(() => import("@/components/transfer/DataTransferDialog.vue"));
@@ -92,6 +93,7 @@ watch(
</script>
<template>
<AgentHandoffDialog />
<ConnectionDialog
:open="showConnectionDialog"
:edit-config="editConfig"
+48
View File
@@ -0,0 +1,48 @@
export type AgentHandoffStatus = "queued" | "shown" | "approved" | "rejected" | "executed" | "failed";
export interface AgentHandoffItem {
id: string;
createdAt: string;
createdBy: string;
connectionId: string;
connectionName: string;
database?: string;
title: string;
description?: string;
sql: string;
operationClass: string;
riskLevel: string;
isProduction: boolean;
status: AgentHandoffStatus;
resultSummary?: string;
error?: string;
}
export function isPendingHandoff(item: AgentHandoffItem): boolean {
return item.status === "queued" || item.status === "shown";
}
export function mergeLoadedHandoffs(
items: AgentHandoffItem[],
ignoredIds: ReadonlySet<string> = new Set(),
): AgentHandoffItem[] {
return items
.filter((item) => isPendingHandoff(item) && !ignoredIds.has(item.id))
.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
}
export function updateHandoffStatus(
items: AgentHandoffItem[],
id: string,
status: AgentHandoffStatus,
): AgentHandoffItem[] {
return mergeLoadedHandoffs(items.map((item) => (item.id === id ? { ...item, status } : item)));
}
export function deriveHandoffDialogState(items: AgentHandoffItem[], activeId: string | null) {
const active = (activeId ? items.find((item) => item.id === activeId) : undefined) ?? items[0] ?? null;
return {
open: items.length > 0,
active,
};
}
+62
View File
@@ -0,0 +1,62 @@
import type { ConnectionConfig, QueryResult, QueryTab } from "@/types/database";
export const DEFAULT_RESULT_SAMPLE_LIMIT = 50;
export interface RuntimeResultSample {
columns: string[];
rows: QueryResult["rows"];
truncated: boolean;
executionTimeMs: number;
sampleLimit: number;
}
export interface AgentRuntimeSnapshot {
activeConnectionId?: string;
activeConnectionName?: string;
database?: string;
schema?: string;
activeTabId?: string;
activeTabTitle?: string;
sql?: string;
selectedSql?: string;
selection: unknown;
result?: RuntimeResultSample;
}
export interface BuildAgentRuntimeSnapshotOptions {
tabs: QueryTab[];
activeTabId: string | null;
getConnection: (connectionId: string) => Pick<ConnectionConfig, "name"> | undefined;
selectedSql?: string;
selection?: unknown;
resultSampleLimit?: number;
}
export function buildAgentRuntimeSnapshot(options: BuildAgentRuntimeSnapshotOptions): AgentRuntimeSnapshot {
const tab = options.tabs.find((item) => item.id === options.activeTabId);
const conn = tab ? options.getConnection(tab.connectionId) : undefined;
const selectedSql = options.selectedSql?.trim();
const limit = options.resultSampleLimit ?? DEFAULT_RESULT_SAMPLE_LIMIT;
const result = tab?.result
? {
columns: tab.result.columns,
rows: tab.result.rows.slice(0, limit),
truncated: tab.result.rows.length > limit || !!tab.result.truncated,
executionTimeMs: tab.result.execution_time_ms,
sampleLimit: limit,
}
: undefined;
return {
activeConnectionId: tab?.connectionId,
activeConnectionName: conn?.name,
database: tab?.database,
schema: tab?.schema,
activeTabId: tab?.id,
activeTabTitle: tab?.title,
sql: tab?.sql,
selectedSql: selectedSql || undefined,
selection: options.selection ?? { type: "none" },
result,
};
}
+6
View File
@@ -72,6 +72,12 @@ export const executeScript = forward("executeScript");
export const executeInTransaction = forward("executeInTransaction");
export const cancelQuery = forward("cancelQuery");
// Agent Runtime
export const agentRuntimeUpdateSnapshot = forward("agentRuntimeUpdateSnapshot");
export const agentRuntimeLoadHandoffs = forward("agentRuntimeLoadHandoffs");
export const agentRuntimeMarkHandoffShown = forward("agentRuntimeMarkHandoffShown");
export const agentRuntimeRejectHandoff = forward("agentRuntimeRejectHandoff");
// AI
export const aiComplete = forward("aiComplete");
export const aiStream = forward("aiStream");
+13
View File
@@ -0,0 +1,13 @@
export interface RestoreStartupAgentRuntimeOptions {
initSavedSql: () => Promise<unknown>;
initConnections: () => Promise<unknown>;
reconnectRestoredTabs: () => Promise<unknown> | unknown;
scheduleSync: () => void;
}
export async function restoreStartupAgentRuntime(options: RestoreStartupAgentRuntimeOptions) {
await options.initSavedSql();
await options.initConnections();
await options.reconnectRestoredTabs();
options.scheduleSync();
}
+16
View File
@@ -39,6 +39,8 @@ import type {
TableImportSummary,
TableImportProgress,
} from "./tauri";
import type { AgentRuntimeSnapshot } from "@/lib/agentRuntimeSnapshot";
import type { AgentHandoffItem } from "@/lib/agentHandoff";
// ---------------------------------------------------------------------------
// Helpers
@@ -290,6 +292,20 @@ export async function cancelQuery(executionId: string): Promise<boolean> {
return post("/api/query/cancel", { executionId });
}
export async function agentRuntimeUpdateSnapshot(_snapshot: AgentRuntimeSnapshot): Promise<void> {}
export async function agentRuntimeLoadHandoffs(): Promise<AgentHandoffItem[]> {
return [];
}
export async function agentRuntimeMarkHandoffShown(_id: string): Promise<boolean> {
return false;
}
export async function agentRuntimeRejectHandoff(_id: string): Promise<boolean> {
return false;
}
// ---------------------------------------------------------------------------
// AI
// ---------------------------------------------------------------------------
+18
View File
@@ -19,6 +19,8 @@ import type {
SavedSqlFolder,
SavedSqlLibrary,
} from "@/types/database";
import type { AgentRuntimeSnapshot } from "@/lib/agentRuntimeSnapshot";
import type { AgentHandoffItem } from "@/lib/agentHandoff";
import type { AiConfig } from "@/stores/settingsStore";
export interface AiMessage {
@@ -80,6 +82,22 @@ export async function loadAiConfig(): Promise<AiConfig | null> {
return invoke("load_ai_config");
}
export async function agentRuntimeUpdateSnapshot(snapshot: AgentRuntimeSnapshot): Promise<void> {
return invoke("agent_runtime_update_snapshot", { snapshot });
}
export async function agentRuntimeLoadHandoffs(): Promise<AgentHandoffItem[]> {
return invoke("agent_runtime_load_handoffs");
}
export async function agentRuntimeMarkHandoffShown(id: string): Promise<boolean> {
return invoke("agent_runtime_mark_handoff_shown", { id });
}
export async function agentRuntimeRejectHandoff(id: string): Promise<boolean> {
return invoke("agent_runtime_reject_handoff", { id });
}
// --- AI Conversations ---
export interface AiChatMessage {
+109
View File
@@ -0,0 +1,109 @@
import { defineStore } from "pinia";
import { computed, ref } from "vue";
import * as api from "@/lib/api";
import {
deriveHandoffDialogState,
mergeLoadedHandoffs,
updateHandoffStatus,
type AgentHandoffItem,
} from "@/lib/agentHandoff";
import { buildAgentRuntimeSnapshot, DEFAULT_RESULT_SAMPLE_LIMIT } from "@/lib/agentRuntimeSnapshot";
import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore";
export const useAgentRuntimeStore = defineStore("agentRuntime", () => {
const selection = ref<unknown>({ type: "none" });
const selectedSql = ref("");
const handoffs = ref<AgentHandoffItem[]>([]);
const activeHandoffId = ref<string | null>(null);
const handoffDialogOpen = ref(false);
const locallyClosedHandoffIds = new Set<string>();
let timer: ReturnType<typeof setTimeout> | null = null;
const activeHandoff = computed(() => handoffs.value.find((item) => item.id === activeHandoffId.value) ?? null);
function setSelection(value: unknown) {
selection.value = value;
scheduleSync();
}
function setSelectedSql(value: string) {
selectedSql.value = value;
scheduleSync();
}
function scheduleSync() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
void syncNow();
}, 100);
}
async function syncNow() {
if (!globalThis.localStorage) return;
try {
const connectionStore = useConnectionStore();
const queryStore = useQueryStore();
const snapshot = buildAgentRuntimeSnapshot({
tabs: queryStore.tabs,
activeTabId: queryStore.activeTabId,
getConnection: (connectionId) => connectionStore.getConfig(connectionId),
selectedSql: selectedSql.value,
selection: selection.value,
resultSampleLimit: DEFAULT_RESULT_SAMPLE_LIMIT,
});
await api.agentRuntimeUpdateSnapshot(snapshot);
} catch (err) {
console.debug("[DBX] Agent runtime snapshot sync skipped:", err);
}
}
async function loadHandoffs() {
const loaded = mergeLoadedHandoffs(await api.agentRuntimeLoadHandoffs(), locallyClosedHandoffIds);
handoffs.value = loaded;
const state = deriveHandoffDialogState(loaded, activeHandoffId.value);
activeHandoffId.value = state.active?.id ?? null;
handoffDialogOpen.value = state.open;
if (state.active?.status === "queued") {
await markHandoffShown(state.active.id);
}
}
async function markHandoffShown(id: string) {
handoffs.value = updateHandoffStatus(handoffs.value, id, "shown");
await api.agentRuntimeMarkHandoffShown(id);
}
async function rejectHandoff(id: string) {
await api.agentRuntimeRejectHandoff(id);
locallyClosedHandoffIds.add(id);
handoffs.value = updateHandoffStatus(handoffs.value, id, "rejected");
const state = deriveHandoffDialogState(handoffs.value, activeHandoffId.value === id ? null : activeHandoffId.value);
activeHandoffId.value = state.active?.id ?? null;
handoffDialogOpen.value = state.open;
}
function setActiveHandoff(id: string) {
activeHandoffId.value = id;
const item = handoffs.value.find((handoff) => handoff.id === id);
if (item?.status === "queued") void markHandoffShown(id);
}
return {
selection,
selectedSql,
handoffs,
activeHandoff,
handoffDialogOpen,
setSelection,
setSelectedSql,
scheduleSync,
syncNow,
loadHandoffs,
markHandoffShown,
rejectHandoff,
setActiveHandoff,
};
});
+32 -3
View File
@@ -10,22 +10,31 @@ import { analyzeEditableQuery, allPrimaryKeysPresent } from "@/lib/sqlAnalysis";
import { restoreOpenTabsState, serializeOpenTabs } from "@/lib/openTabsPersistence";
import * as api from "@/lib/api";
import { useConnectionStore } from "@/stores/connectionStore";
import { useAgentRuntimeStore } from "@/stores/agentRuntimeStore";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import type { SavedSqlFile } from "@/types/database";
const STORAGE_KEY = "dbx-open-tabs";
const ACTIVE_TAB_KEY = "dbx-active-tab";
function browserStorage(): Storage | undefined {
return globalThis.localStorage;
}
function saveTabs(tabs: QueryTab[], activeTabId: string | null) {
const storage = browserStorage();
if (!storage) return;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(serializeOpenTabs(tabs)));
localStorage.setItem(ACTIVE_TAB_KEY, activeTabId || "");
storage.setItem(STORAGE_KEY, JSON.stringify(serializeOpenTabs(tabs)));
storage.setItem(ACTIVE_TAB_KEY, activeTabId || "");
} catch {}
}
function loadSavedTabs(): { tabs: QueryTab[]; activeTabId: string | null } {
const storage = browserStorage();
if (!storage) return { tabs: [], activeTabId: null };
try {
return restoreOpenTabsState(localStorage.getItem(STORAGE_KEY), localStorage.getItem(ACTIVE_TAB_KEY), {
return restoreOpenTabsState(storage.getItem(STORAGE_KEY), storage.getItem(ACTIVE_TAB_KEY), {
queryOnly: isTauriRuntime(),
});
} catch {
@@ -72,11 +81,18 @@ export const useQueryStore = defineStore("query", () => {
return tabs.value.find((t) => t.connectionId === connectionId && t.database === database && t.title === title);
}
function scheduleAgentRuntimeSync() {
useAgentRuntimeStore().scheduleSync();
}
watch(activeTabId, () => scheduleAgentRuntimeSync());
function createTab(connectionId: string, database: string, title?: string, mode: QueryTab["mode"] = "query") {
if (title) {
const existing = findTabByTitle(connectionId, database, title);
if (existing) {
activeTabId.value = existing.id;
scheduleAgentRuntimeSync();
return existing.id;
}
}
@@ -95,6 +111,7 @@ export const useQueryStore = defineStore("query", () => {
};
tabs.value.push(tab);
activeTabId.value = id;
scheduleAgentRuntimeSync();
return id;
}
@@ -145,6 +162,7 @@ export const useQueryStore = defineStore("query", () => {
if (activeTabId.value === id) {
activeTabId.value = tabs.value[Math.min(idx, tabs.value.length - 1)]?.id ?? null;
}
scheduleAgentRuntimeSync();
}
function closeOtherTabs(id: string) {
@@ -153,6 +171,7 @@ export const useQueryStore = defineStore("query", () => {
const next = closeOtherTabsState(tabs.value, activeTabId.value, id);
tabs.value = next.tabs;
activeTabId.value = next.activeTabId;
scheduleAgentRuntimeSync();
}
function closeAllTabs() {
@@ -161,6 +180,7 @@ export const useQueryStore = defineStore("query", () => {
const next = closeAllTabsState(tabs.value, activeTabId.value);
tabs.value = next.tabs;
activeTabId.value = next.activeTabId;
scheduleAgentRuntimeSync();
}
function updateSql(id: string, sql: string) {
@@ -169,6 +189,7 @@ export const useQueryStore = defineStore("query", () => {
tab.sql = sql;
tab.resultSortedSql = undefined;
tab.resultBaseSql = undefined;
scheduleAgentRuntimeSync();
}
}
@@ -183,6 +204,7 @@ export const useQueryStore = defineStore("query", () => {
const existing = tabs.value.find((tab) => tab.savedSqlId === file.id);
if (existing) {
activeTabId.value = existing.id;
scheduleAgentRuntimeSync();
return existing.id;
}
@@ -202,6 +224,7 @@ export const useQueryStore = defineStore("query", () => {
};
tabs.value.push(tab);
activeTabId.value = id;
scheduleAgentRuntimeSync();
return id;
}
@@ -224,6 +247,7 @@ export const useQueryStore = defineStore("query", () => {
tab.resultSortedSql = undefined;
clearExplain(tab);
tab.tableMeta = undefined;
scheduleAgentRuntimeSync();
}
function updateSchema(id: string, schema: string | undefined) {
@@ -231,6 +255,7 @@ export const useQueryStore = defineStore("query", () => {
if (!tab || tab.schema === schema) return;
tab.schema = schema;
if (tab.mode === "objects") tab.objectBrowser = { ...tab.objectBrowser, schema };
scheduleAgentRuntimeSync();
}
function updateConnection(id: string, connectionId: string, database = "") {
@@ -245,6 +270,7 @@ export const useQueryStore = defineStore("query", () => {
tab.resultSortedSql = undefined;
clearExplain(tab);
tab.tableMeta = undefined;
scheduleAgentRuntimeSync();
}
function setTableMeta(id: string, meta: NonNullable<QueryTab["tableMeta"]>) {
@@ -410,6 +436,7 @@ export const useQueryStore = defineStore("query", () => {
console.info("[DBX][executeTabSql:metadata:start]", { traceId, elapsed: elapsed() });
await analyzeQueryMetadata(current, current.resultBaseSql);
console.info("[DBX][executeTabSql:metadata:done]", { traceId, elapsed: elapsed() });
scheduleAgentRuntimeSync();
} else {
console.warn("[DBX][executeTabSql:stale-result]", {
traceId,
@@ -428,6 +455,7 @@ export const useQueryStore = defineStore("query", () => {
if (current.mode !== "data") current.tableMeta = undefined;
current.resultBaseSql = options?.resultBaseSql ?? sql;
current.resultSortedSql = options?.resultSortedSql;
scheduleAgentRuntimeSync();
}
} finally {
const current = tabs.value.find((t) => t.id === id);
@@ -538,6 +566,7 @@ export const useQueryStore = defineStore("query", () => {
if (!tab?.results || index < 0 || index >= tab.results.length) return;
tab.activeResultIndex = index;
tab.result = tab.results[index];
scheduleAgentRuntimeSync();
}
function trimResultCache() {