跳到主要内容

Background Tasks

Agent task background execution and status tracking

Overview

Background task management allows users to continue Agent task execution while navigating between pages and receive notifications when tasks complete.

Architecture


Data Structures

BackgroundTask

interface BackgroundTask {
/** Unique task ID */
taskId: string;
/** Agent session ID (used for stopping) */
sessionId: string;
/** User prompt (for display) */
prompt: string;
/** Task status */
status: "running" | "completed" | "error" | "cancelled";
/** Start time */
startedAt: Date;
/** Completion time */
completedAt?: Date;
/** Error message */
errorMessage?: string;
/** API cost */
cost?: number;
/** Execution duration (ms) */
duration?: number;
}

Core Implementation

BackgroundTaskManager

// packages/core/src/services/background-tasks.ts

type TaskListener = (tasks: BackgroundTask[]) => void;

export class BackgroundTaskManager {
private tasks = new Map<string, BackgroundTask>();
private listeners = new Set<TaskListener>();
private abortControllers = new Map<string, AbortController>();

/**
* Add a new task
*/
addTask(task: {
taskId: string;
sessionId: string;
prompt: string;
}): BackgroundTask;

/**
* Update task status
*/
updateStatus(
taskId: string,
update: {
status: BackgroundTask["status"];
errorMessage?: string;
cost?: number;
duration?: number;
}
): void;

/**
* Stop a task
*/
stopTask(taskId: string): void;

/**
* Get a task
*/
getTask(taskId: string): BackgroundTask | undefined;

/**
* Get all tasks
*/
getAllTasks(): BackgroundTask[];

/**
* Get running task count
*/
getRunningCount(): number;

/**
* Get AbortSignal
*/
getAbortSignal(taskId: string): AbortSignal | undefined;

/**
* Subscribe to task status changes
*/
subscribe(listener: TaskListener): () => void;

/**
* Clean up completed task
*/
cleanup(taskId: string): void;

/**
* Clear all tasks
*/
clearAll(): void;
}

// Singleton export
export const backgroundTaskManager = new BackgroundTaskManager();

Gateway Endpoints

Task Subscription (SSE)

GET /api/agent/tasks/subscribe

Subscribe to real-time task status updates via Server-Sent Events.

Response Stream:

// Initial state
{ "type": "tasks", "tasks": BackgroundTask[] }

// Updates
{ "type": "tasks", "tasks": BackgroundTask[] }

// Heartbeat
{ "type": "ping" }

Stop Task

POST /api/agent/tasks/:taskId/stop

Stop a running task.

Response:

{
"success": true
}

Frontend Hook

useBackgroundTasks

// apps/desktop/src/hooks/use-background-tasks.ts

export interface UseBackgroundTasksReturn {
tasks: BackgroundTask[];
runningTasks: BackgroundTask[];
runningCount: number;
stopTask: (taskId: string) => Promise<void>;
isConnected: boolean;
}

export function useBackgroundTasks(): UseBackgroundTasksReturn {
const [tasks, setTasks] = useState<BackgroundTask[]>([]);
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
const gateway = getGatewayClient();
let eventSource: EventSource | null = null;
let reconnectTimer: NodeJS.Timeout | null = null;

const connect = () => {
eventSource = new EventSource(
`${gateway.baseUrl}/api/agent/tasks/subscribe`
);

eventSource.onopen = () => {
setIsConnected(true);
};

eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "tasks") {
setTasks(data.tasks);
}
};

eventSource.onerror = () => {
setIsConnected(false);
eventSource?.close();
// Reconnect
reconnectTimer = setTimeout(connect, 5000);
};
};

connect();

return () => {
eventSource?.close();
if (reconnectTimer) clearTimeout(reconnectTimer);
};
}, []);

const stopTask = useCallback(async (taskId: string) => {
const gateway = getGatewayClient();
await fetch(`${gateway.baseUrl}/api/agent/tasks/${taskId}/stop`, {
method: "POST",
});
}, []);

const runningTasks = tasks.filter((t) => t.status === "running");

return {
tasks,
runningTasks,
runningCount: runningTasks.length,
stopTask,
isConnected,
};
}

Task Lifecycle


Integration with useAgent

// apps/desktop/src/hooks/use-agent.ts

import { backgroundTaskManager } from "@viben/core";

export function useAgent(options: UseAgentOptions): UseAgentReturn {
const [taskId] = useState(() => Date.now().toString());

const runAgent = useCallback(async (prompt: string) => {
// Register background task
backgroundTaskManager.addTask({
taskId,
sessionId: "", // Updated in session message
prompt,
});

try {
// SSE connection...
for await (const message of connectSSE(...)) {
if (message.type === "session") {
// Update sessionId
const task = backgroundTaskManager.getTask(taskId);
if (task) task.sessionId = message.sessionId;
}

if (message.type === "result") {
backgroundTaskManager.updateStatus(taskId, {
status: "completed",
cost: message.cost,
duration: message.duration,
});
}

if (message.type === "error") {
backgroundTaskManager.updateStatus(taskId, {
status: "error",
errorMessage: message.message,
});
}
}
} catch (error) {
backgroundTaskManager.updateStatus(taskId, {
status: "error",
errorMessage: error.message,
});
}
}, [taskId]);

const stopAgent = useCallback(() => {
backgroundTaskManager.stopTask(taskId);
}, [taskId]);

// ...
}

Task Status

StatusDescription
runningTask is executing
completedTask finished successfully
errorTask failed with error
cancelledTask was cancelled by user