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
+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 {