universal-ai-brain
Version:
🧠UNIVERSAL AI BRAIN 3.3 - The world's most advanced cognitive architecture with 24 specialized systems, MongoDB 8.1 $rankFusion hybrid search, latest Voyage 3.5 embeddings, and framework-agnostic design. Works with Mastra, Vercel AI, LangChain, OpenAI A
532 lines (463 loc) • 13.8 kB
text/typescript
/**
* @file WorkflowCollection - MongoDB collection operations for agent workflows
*
* This class provides CRUD operations and specialized queries for agent workflows,
* implementing workflow state management and execution tracking.
*/
import { Collection, Db, ObjectId } from 'mongodb';
import { AgentWorkflow, WorkflowStatus, WorkflowStep } from '../types/index';
import { BaseCollection } from './BaseCollection';
export interface WorkflowFilter {
agentId?: string | ObjectId;
status?: WorkflowStatus;
framework?: string;
tags?: string[];
createdAfter?: Date;
createdBefore?: Date;
startedAfter?: Date;
startedBefore?: Date;
completedAfter?: Date;
completedBefore?: Date;
}
export interface WorkflowUpdateData {
name?: string;
description?: string;
status?: WorkflowStatus;
currentStepIndex?: number;
steps?: WorkflowStep[];
variables?: Record<string, any>;
metadata?: Record<string, any>;
tags?: string[];
startedAt?: Date;
completedAt?: Date;
error?: string;
}
export interface WorkflowExecutionOptions {
resumeFromStep?: number;
variables?: Record<string, any>;
timeout?: number;
}
/**
* WorkflowCollection - Complete CRUD operations for agent workflows
*
* Features:
* - Workflow state management
* - Step-by-step execution tracking
* - Error handling and recovery
* - Variable management
* - Performance monitoring
*/
export class WorkflowCollection extends BaseCollection<AgentWorkflow> {
protected collectionName = 'agent_workflows';
constructor(db: Db) {
super(db);
this.initializeCollection();
}
/**
* Create a new workflow
*/
async createWorkflow(workflowData: Omit<AgentWorkflow, '_id' | 'createdAt' | 'updatedAt'>): Promise<AgentWorkflow> {
const now = new Date();
const workflow: AgentWorkflow = {
...workflowData,
_id: new ObjectId(),
createdAt: now,
updatedAt: now,
status: workflowData.status || WorkflowStatus.DRAFT,
currentStepIndex: 0,
metadata: workflowData.metadata || {}
};
await this.validateDocument(workflow);
const result = await this.collection.insertOne(workflow);
if (!result.acknowledged) {
throw new Error('Failed to create workflow');
}
return workflow;
}
/**
* Get workflow by ID
*/
async getWorkflow(workflowId: string | ObjectId): Promise<AgentWorkflow | null> {
const objectId = typeof workflowId === 'string' ? new ObjectId(workflowId) : workflowId;
return await this.collection.findOne({ _id: objectId });
}
/**
* Update workflow
*/
async updateWorkflow(workflowId: string | ObjectId, updateData: WorkflowUpdateData): Promise<AgentWorkflow | null> {
const objectId = typeof workflowId === 'string' ? new ObjectId(workflowId) : workflowId;
const now = new Date();
const updateDoc = {
...updateData,
updatedAt: now
};
const result = await this.collection.findOneAndUpdate(
{ _id: objectId },
{ $set: updateDoc },
{ returnDocument: 'after' }
);
return result.value;
}
/**
* Update workflow status
*/
async updateWorkflowStatus(
workflowId: string | ObjectId,
status: WorkflowStatus,
error?: string
): Promise<boolean> {
const objectId = typeof workflowId === 'string' ? new ObjectId(workflowId) : workflowId;
const now = new Date();
const updateDoc: any = {
status,
updatedAt: now
};
if (status === WorkflowStatus.ACTIVE && !await this.hasStartedAt(objectId)) {
(updateDoc as any).startedAt = now;
}
if (status === WorkflowStatus.COMPLETED || status === WorkflowStatus.FAILED) {
(updateDoc as any).completedAt = now;
}
if (error) {
updateDoc.error = error;
}
const result = await this.collection.updateOne(
{ _id: objectId },
{ $set: updateDoc }
);
return result.modifiedCount > 0;
}
/**
* Update current step
*/
async updateCurrentStep(
workflowId: string | ObjectId,
stepIndex: number,
stepResult?: any
): Promise<boolean> {
const objectId = typeof workflowId === 'string' ? new ObjectId(workflowId) : workflowId;
const now = new Date();
const updateDoc: any = {
currentStepIndex: stepIndex,
updatedAt: now
};
// Update step result if provided
if (stepResult !== undefined) {
updateDoc[`steps.${stepIndex}.result`] = stepResult;
updateDoc[`steps.${stepIndex}.completedAt`] = now;
}
const result = await this.collection.updateOne(
{ _id: objectId },
{ $set: updateDoc }
);
return result.modifiedCount > 0;
}
/**
* Update workflow variables
*/
async updateWorkflowVariables(
workflowId: string | ObjectId,
variables: Record<string, any>
): Promise<boolean> {
const objectId = typeof workflowId === 'string' ? new ObjectId(workflowId) : workflowId;
const result = await this.collection.updateOne(
{ _id: objectId },
{
$set: {
variables,
updatedAt: new Date()
}
}
);
return result.modifiedCount > 0;
}
/**
* Add step result
*/
async addStepResult(
workflowId: string | ObjectId,
stepIndex: number,
result: any,
error?: string
): Promise<boolean> {
const objectId = typeof workflowId === 'string' ? new ObjectId(workflowId) : workflowId;
const now = new Date();
const updateDoc: any = {
[`steps.${stepIndex}.result`]: result,
[`steps.${stepIndex}.completedAt`]: now,
updatedAt: now
};
if (error) {
updateDoc[`steps.${stepIndex}.error`] = error;
}
const updateResult = await this.collection.updateOne(
{ _id: objectId },
{ $set: updateDoc }
);
return updateResult.modifiedCount > 0;
}
/**
* Delete workflow
*/
async deleteWorkflow(workflowId: string | ObjectId): Promise<boolean> {
const objectId = typeof workflowId === 'string' ? new ObjectId(workflowId) : workflowId;
const result = await this.collection.deleteOne({ _id: objectId });
return result.deletedCount > 0;
}
/**
* List workflows with filtering and pagination
*/
async listWorkflows(
filter: WorkflowFilter = {},
options: {
limit?: number;
skip?: number;
sort?: Record<string, 1 | -1>;
} = {}
): Promise<{ workflows: AgentWorkflow[]; total: number }> {
const mongoFilter = this.buildMongoFilter(filter);
const { limit = 50, skip = 0, sort = { createdAt: -1 } } = options;
const [workflows, total] = await Promise.all([
this.collection
.find(mongoFilter)
.sort(sort)
.skip(skip)
.limit(limit)
.toArray(),
this.collection.countDocuments(mongoFilter)
]);
return { workflows, total };
}
/**
* Get workflows by agent
*/
async getAgentWorkflows(
agentId: string | ObjectId,
status?: WorkflowStatus
): Promise<AgentWorkflow[]> {
const objectId = typeof agentId === 'string' ? new ObjectId(agentId) : agentId;
const filter: any = { agentId: objectId };
if (status) {
filter.status = status;
}
return await this.collection
.find(filter)
.sort({ createdAt: -1 })
.toArray();
}
/**
* Get running workflows
*/
async getRunningWorkflows(): Promise<AgentWorkflow[]> {
return await this.collection
.find({ status: WorkflowStatus.ACTIVE })
.sort({ startedAt: 1 } as any)
.toArray();
}
/**
* Get workflows by status
*/
async getWorkflowsByStatus(status: WorkflowStatus): Promise<AgentWorkflow[]> {
return await this.collection
.find({ status })
.sort({ createdAt: -1 })
.toArray();
}
/**
* Search workflows
*/
async searchWorkflows(query: string, limit: number = 20): Promise<AgentWorkflow[]> {
const searchFilter = {
$or: [
{ name: { $regex: query, $options: 'i' } },
{ description: { $regex: query, $options: 'i' } }
]
};
return await this.collection
.find(searchFilter)
.limit(limit)
.toArray();
}
/**
* Get workflow statistics
*/
async getWorkflowStats(agentId?: string | ObjectId): Promise<{
total: number;
byStatus: Record<WorkflowStatus, number>;
byFramework: Record<string, number>;
averageExecutionTime: number;
successRate: number;
}> {
const matchStage = agentId
? { $match: { agentId: typeof agentId === 'string' ? new ObjectId(agentId) : agentId } }
: { $match: {} };
const pipeline = [
matchStage,
{
$facet: {
total: [{ $count: 'count' }],
byStatus: [
{ $group: { _id: '$status', count: { $sum: 1 } } }
],
byFramework: [
{ $group: { _id: '$framework', count: { $sum: 1 } } }
],
executionTimes: [
{
$match: {
startedAt: { $exists: true },
completedAt: { $exists: true }
}
},
{
$project: {
executionTime: {
$subtract: ['$completedAt', '$startedAt']
}
}
},
{
$group: {
_id: null,
avgTime: { $avg: '$executionTime' }
}
}
],
successRate: [
{
$group: {
_id: null,
total: { $sum: 1 },
completed: {
$sum: {
$cond: [{ $eq: ['$status', 'completed'] }, 1, 0]
}
}
}
},
{
$project: {
rate: {
$cond: [
{ $eq: ['$total', 0] },
0,
{ $divide: ['$completed', '$total'] }
]
}
}
}
]
}
}
];
const [result] = await this.collection.aggregate(pipeline).toArray();
return {
total: result.total[0]?.count || 0,
byStatus: result.byStatus.reduce((acc, item) => {
acc[item._id] = item.count;
return acc;
}, {}),
byFramework: result.byFramework.reduce((acc, item) => {
acc[item._id] = item.count;
return acc;
}, {}),
averageExecutionTime: result.executionTimes[0]?.avgTime || 0,
successRate: result.successRate[0]?.rate || 0
};
}
/**
* Cleanup old workflows
*/
async cleanupOldWorkflows(olderThanDays: number = 30): Promise<number> {
const cutoffDate = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000);
const result = await this.collection.deleteMany({
status: { $in: [WorkflowStatus.COMPLETED, WorkflowStatus.FAILED, WorkflowStatus.CANCELLED] },
completedAt: { $lt: cutoffDate }
});
return result.deletedCount;
}
/**
* Check if workflow has started
*/
private async hasStartedAt(workflowId: ObjectId): Promise<boolean> {
const workflow = await this.collection.findOne(
{ _id: workflowId },
{ projection: { startedAt: 1 } }
);
return !!(workflow?.startedAt);
}
/**
* Build MongoDB filter from WorkflowFilter
*/
private buildMongoFilter(filter: WorkflowFilter): any {
const mongoFilter: any = {};
if (filter.agentId) {
const objectId = typeof filter.agentId === 'string' ? new ObjectId(filter.agentId) : filter.agentId;
mongoFilter.agentId = objectId;
}
if (filter.status) {
mongoFilter.status = filter.status;
}
if (filter.framework) {
mongoFilter.framework = filter.framework;
}
if (filter.tags && filter.tags.length > 0) {
mongoFilter.tags = { $in: filter.tags };
}
if (filter.createdAfter || filter.createdBefore) {
mongoFilter.createdAt = {};
if (filter.createdAfter) {
mongoFilter.createdAt.$gte = filter.createdAfter;
}
if (filter.createdBefore) {
mongoFilter.createdAt.$lte = filter.createdBefore;
}
}
if (filter.startedAfter || filter.startedBefore) {
mongoFilter.startedAt = {};
if (filter.startedAfter) {
mongoFilter.startedAt.$gte = filter.startedAfter;
}
if (filter.startedBefore) {
mongoFilter.startedAt.$lte = filter.startedBefore;
}
}
if (filter.completedAfter || filter.completedBefore) {
mongoFilter.completedAt = {};
if (filter.completedAfter) {
mongoFilter.completedAt.$gte = filter.completedAfter;
}
if (filter.completedBefore) {
mongoFilter.completedAt.$lte = filter.completedBefore;
}
}
return mongoFilter;
}
/**
* Create indexes for optimal performance
*/
async createIndexes(): Promise<void> {
await Promise.all([
// Primary indexes
this.collection.createIndex({ agentId: 1, status: 1 }),
this.collection.createIndex({ status: 1 }),
this.collection.createIndex({ framework: 1 }),
this.collection.createIndex({ createdAt: -1 }),
this.collection.createIndex({ startedAt: -1 }),
this.collection.createIndex({ completedAt: -1 }),
// Compound indexes
this.collection.createIndex({ agentId: 1, createdAt: -1 }),
this.collection.createIndex({ status: 1, startedAt: 1 }),
this.collection.createIndex({ status: 1, completedAt: -1 }),
// Text search index
this.collection.createIndex({
name: 'text',
description: 'text'
}, {
name: 'workflow_text_search'
}),
// Tag index
this.collection.createIndex({ tags: 1 })
]);
}
}