mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-31 00:22:30 +00:00
feat(cli): add dbx-cli runtime and agent integration (#229)
Feature/dbx cli runtime
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user