feat(ai): run agent sessions as background tasks

This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-08-26 14:09:08 +08:00
1 parent b37a1f8538
commit eeeb4e5011
34 files changed
+3440 -184

No files matched your search

+5
View File
@@ -345,6 +345,9 @@ export const snippetSyncDownload = forward("snippetSyncDownload");
export const saveAiConversation = forward("saveAiConversation");
export const loadAiConversations = forward("loadAiConversations");
export const deleteAiConversation = forward("deleteAiConversation");
export const saveAiRun = forward("saveAiRun");
export const saveAiRunState = forward("saveAiRunState");
export const loadAiRuns = forward("loadAiRuns");
// Prompt Templates
export const loadPromptTemplates = forward("loadPromptTemplates");
@@ -821,6 +824,8 @@ export type {
AiModelInfo,
AiChatMessage,
AiConversation,
AiRun,
AiRunStatus,
PromptTemplate,
AgentDriverInfo,
AgentOfflineArtifactKind,
+36 -12
View File
@@ -60,6 +60,7 @@ import type {
AiCompletionRequest,
AiStreamChunk,
AiConversation,
AiRun,
AiModelInfo,
DriverStoreUsage,
DriverRuntimeSummary,
@@ -1590,13 +1591,16 @@ export async function aiStream(sessionId: string, request: AiCompletionRequest,
if (line.startsWith("data:")) {
const data = line.slice(5).trim();
if (data && data !== "[DONE]") {
let chunk: AiStreamChunk;
try {
const chunk: AiStreamChunk = JSON.parse(data);
onChunk(chunk);
if (chunk.done) return;
chunk = JSON.parse(data);
} catch {
// skip malformed JSON
continue;
}
if (chunk.error) throw new Error(chunk.error);
onChunk(chunk);
if (chunk.done) return;
}
}
}
@@ -1687,18 +1691,23 @@ export async function aiAgentStream(
if (line.startsWith("data:")) {
const data = line.slice(5).trim();
if (data && data !== "[DONE]") {
let parsed: unknown;
try {
const parsed = JSON.parse(data);
if (!isAgentEvent(parsed)) {
console.warn("[aiAgentStream] Skipping invalid agent event:", data);
continue;
}
onEvent(parsed);
if (parsed.type === "agent_end" || parsed.type === "error") {
result = data;
}
parsed = JSON.parse(data);
} catch {
// skip malformed JSON
continue;
}
if (!isAgentEvent(parsed)) {
console.warn("[aiAgentStream] Skipping invalid agent event:", data);
continue;
}
onEvent(parsed);
if (parsed.type === "error") {
throw new Error(parsed.message);
}
if (parsed.type === "agent_end") {
result = data;
}
}
}
@@ -2067,6 +2076,21 @@ export async function deleteAiConversation(id: string): Promise<void> {
return del(`/api/ai/conversation/${id}`);
}
// Background AI runs are a Desktop-only capability in this release. Keep the
// symbols for backend-module parity without changing Web's request-bound SSE
// lifecycle or adding a partially functional persistence API.
export async function saveAiRun(_run: AiRun): Promise<void> {
throw new Error("Background AI runs are only available in DBX Desktop");
}
export async function saveAiRunState(_conversation: AiConversation, _run: AiRun): Promise<void> {
throw new Error("Background AI runs are only available in DBX Desktop");
}
export async function loadAiRuns(): Promise<AiRun[]> {
return [];
}
// ---------------------------------------------------------------------------
// Prompt Templates
// ---------------------------------------------------------------------------
+40
View File
@@ -447,6 +447,8 @@ export interface AiStreamChunk {
delta: string;
reasoning_delta?: string;
done: boolean;
/** Web-only explicit terminal error; Tauri reports invoke failures directly. */
error?: string;
}
export async function aiStream(sessionId: string, request: AiCompletionRequest, onChunk: (chunk: AiStreamChunk) => void): Promise<void> {
@@ -904,6 +906,32 @@ export interface AiConversation {
connectionName: string;
database: string;
messages: AiChatMessage[];
/** One editable "send later" input saved while an active run occupies the
* conversation (parent PRD §5). Persisted with the conversation. */
queuedInput?: string;
createdAt: string;
updatedAt: string;
}
export type AiRunStatus = "preparing" | "queued" | "running" | "awaiting_write_confirmation" | "completed" | "failed" | "cancelled" | "interrupted" | "pending_recoverable";
export type AiRunFifoCategory = "normal_send" | "write_confirmation_resume";
export interface AiRun {
runId: string;
conversationId: string;
sessionIds: string[];
status: AiRunStatus;
connectionId: string;
database: string;
schema?: string;
pendingConfirmation?: unknown;
fifoCategory?: AiRunFifoCategory;
pendingInput?: string;
/** Highest event seq assigned to this run across all its sessions (parent
* PRD §8). Drives the unread baseline and the "updates while you were away"
* separator anchor. */
maxSeq?: number;
createdAt: string;
updatedAt: string;
}
@@ -920,6 +948,18 @@ export async function deleteAiConversation(id: string): Promise<void> {
return invoke("delete_ai_conversation", { id });
}
export async function saveAiRun(run: AiRun): Promise<void> {
return invoke("save_ai_run", { run });
}
export async function saveAiRunState(conversation: AiConversation, run: AiRun): Promise<void> {
return invoke("save_ai_run_state", { conversation, run });
}
export async function loadAiRuns(): Promise<AiRun[]> {
return invoke("load_ai_runs");
}
// --- Prompt Templates ---
export interface PromptTemplate {