Skip to content

Commit

Permalink
Update Babel configuration, ESLint rules, and package dependencies; r…
Browse files Browse the repository at this point in the history
…emove Jest config and workflow controller

- Updated Babel preset to target the current Node version.
- Added ESLint rule to disable focused tests in Jest.
- Removed obsolete Jest configuration file.
- Updated package dependencies, including Babel and added new dependencies: `dependency-graph` and `fastq`.
- Added a new CLI command for refreshing the project environment.
- Refactored task management in the application by introducing a TaskManager and subscribing to task and workflow status updates.
- Removed the workflow controller to streamline task management.
- Enhanced agent utility functions with a new cloning mechanism for agents.

Related GH Issue: kaiban-ai#153
  • Loading branch information
ernestocarrasco committed Jan 15, 2025
1 parent 9776374 commit 122cdc9
Show file tree
Hide file tree
Showing 22 changed files with 3,441 additions and 1,029 deletions.
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
// "presets": ["@babel/preset-env"],
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
// "plugins": ["@babel/plugin-syntax-import-meta"]
}
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default [
'@typescript-eslint/no-require-imports': 'off',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'jest/no-focused-tests': 'off',
},
},
];
15 changes: 0 additions & 15 deletions jest.config.js

This file was deleted.

21 changes: 21 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const config = {
testEnvironment: 'node',
transform: {
'^.+\\.[t|j]sx?$': 'babel-jest',
},
collectCoverageFrom: ['src/**/*.js', '!**/node_modules/**', '!**/vendor/**'],
moduleNameMapper: {
'^kaibanjs$': '<rootDir>/dist/bundle.cjs',
},
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
testMatch: ['<rootDir>/tests/**/*.test.js'],
};

export default config;
2,821 changes: 1,925 additions & 896 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"format:check": "prettier --check .",
"format:fix": "prettier --write . --list-different",
"prepare": "husky",
"cli": "node ./xscripts/cli.mjs"
"cli": "node ./xscripts/cli.mjs",
"refresh": "rm -rf node_modules package-lock.json && rm -rf dist && npm install"
},
"bin": {
"kaiban": "./xscripts/cli.mjs"
Expand Down Expand Up @@ -96,21 +97,22 @@
"@telemetrydeck/sdk": "^2.0.4",
"ansis": "^3.3.2",
"chalk": "^5.3.0",
"dependency-graph": "^1.0.0",
"dotenv": "^16.4.5",
"fastq": "^1.18.0",
"figlet": "^1.7.0",
"langchain": "0.2.10",
"loglevel": "^1.9.1",
"ora": "^8.1.0",
"p-queue": "^8.0.1",
"readline": "^1.3.0",
"uuid": "10.0.0",
"zod-to-json-schema": "^3.23.2",
"zustand": "4.5.2"
},
"devDependencies": {
"@babel/core": "^7.24.9",
"@babel/core": "^7.26.0",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/preset-env": "^7.24.8",
"@babel/preset-env": "^7.26.0",
"@eslint/js": "^9.11.0",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "26.0.1",
Expand All @@ -129,7 +131,7 @@
"globals": "^15.9.0",
"husky": "^9.1.6",
"init-package-json": "^6.0.3",
"jest": "29.7.0",
"jest": "^29.7.0",
"lint-staged": "^15.2.10",
"prettier": "2.3.2",
"rollup": "4.21.1",
Expand Down
21 changes: 20 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import { v4 as uuidv4 } from 'uuid';
import { createTeamStore } from './stores';
import { ReactChampionAgent } from './agents';
import TaskManager from './managers/taskManager';
import { subscribeTaskStatusUpdates } from './subscribers/taskSubscriber';
import { subscribeWorkflowStatusUpdates } from './subscribers/teamSubscriber';
import { TASK_STATUS_enum, WORKFLOW_STATUS_enum } from './utils/enums';

class Agent {
Expand Down Expand Up @@ -96,13 +99,14 @@ class Agent {
class Task {
constructor({
title = '',
id = uuidv4(),
description,
expectedOutput,
agent,
isDeliverable = false,
externalValidationRequired = false,
}) {
this.id = uuidv4();
this.id = id;
this.title = title; // Title is now optional with a default empty string
this.description = description;
this.expectedOutput = expectedOutput;
Expand Down Expand Up @@ -150,9 +154,24 @@ class Team {
logLevel,
});

// ──── Task Manager Initialization ────────────────────────────
//
// Activates the task manager to monitor and manage task transitions and overall workflow states:
// - Monitors changes in task statuses, handling transitions from TODO to DONE.
// - Ensures tasks proceed seamlessly through their lifecycle stages within the application.
// ─────────────────────────────────────────────────────────────────────
this.taskManager = new TaskManager(this.store);
this.taskManager.start();

// Add agents and tasks to the store, they will be set with the store automatically
this.store.getState().addAgents(agents);
this.store.getState().addTasks(tasks);

// Subscribe to task updates: Used mainly for logging purposes
subscribeTaskStatusUpdates(this.store);

// Subscribe to WorkflowStatus updates: Used mainly for loggin purposes
subscribeWorkflowStatusUpdates(this.store);
}

/**
Expand Down
126 changes: 126 additions & 0 deletions src/managers/executionStrategies/hierarchyExecutionStrategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import DepGraph from 'dependency-graph';
import { TASK_STATUS_enum } from '../../utils/enums';
import WorkflowExecutionStrategy from './workflowExecutionStrategy';

/**
* Class for hierarchical workflow execution strategy
*
* This strategy is used when tasks have dependencies on each other, and the workflow needs to be executed in a hierarchical manner.
* It ensures that tasks are executed in the correct order, taking into account dependencies and ensuring that tasks are not started
* until all of their prerequisites are complete.
*/
class HierarchyExecutionStrategy extends WorkflowExecutionStrategy {
constructor(useTeamStore) {
super(useTeamStore);
this.graph = new DepGraph();

// Initialize dependency graph
const tasks = useTeamStore.getState().tasks;
tasks.forEach((task) => {
this.graph.addNode(task.id);
});

// Add dependencies
tasks.forEach((task) => {
if (task.dependsOn) {
task.dependsOn.forEach((depId) => {
this.graph.addDependency(task.id, depId);
});
}
});
}

isAgentBusy(agent, tasks) {
return tasks.some(
(t) => t.agent.id === agent.id && t.status === TASK_STATUS_enum.DOING
);
}

/**
* Gets all tasks that the given task depends on (its prerequisites)
* @param {Object} task - The task to find dependencies for
* @param {Array} allTasks - Array of all tasks in the workflow
* @returns {Array} Array of task objects that are dependencies of the given task
*/
getTaskDependencies(task, allTasks) {
if (!task.dependsOn || task.dependsOn.length === 0) {
return [];
}
return allTasks.filter((t) => task.dependsOn.includes(t.id));
}

/**
* Gets all tasks that depend on the given task (tasks that have this task as a prerequisite)
* @param {Object} task - The task to find dependent tasks for
* @param {Array} allTasks - Array of all tasks in the workflow
* @returns {Array} Array of task objects that depend on the given task
*/
getAllTasksDependingOn(task, allTasks) {
return allTasks.filter((t) => t.dependsOn && t.dependsOn.includes(task.id));
}

/**
* Gets all tasks that are ready to be executed
* @param {Array} allTasks - Array of all tasks in the workflow
* @returns {Array} Array of task objects that are ready to be executed
*/
getReadyTasks(allTasks) {
return allTasks.filter((task) => {
// Task must be in TODO status
if (task.status !== TASK_STATUS_enum.TODO) return false;

// All dependencies must be DONE
const deps = this.getTaskDependencies(task, allTasks);
return (
deps.length === 0 ||
deps.every((dep) => dep.status === TASK_STATUS_enum.DONE)
);
});
}

async execute(changedTasks, allTasks) {
// Handle changed tasks first
for (const changedTask of changedTasks) {
switch (changedTask.status) {
case TASK_STATUS_enum.DOING:
// Execute the task
await super._executeTask(changedTask);
break;

case TASK_STATUS_enum.REVISE:
{
// Block all dependent tasks
const dependentTasks = this.getAllTasksDependingOn(
changedTask,
allTasks
);
dependentTasks.forEach((task) => {
this.useTeamStore
.getState()
.updateTaskStatus(task.id, TASK_STATUS_enum.BLOCKED);
});
}

break;
}
}

// Find and execute all possible tasks
const executableTasks = allTasks.filter((task) => {
if (task.status !== TASK_STATUS_enum.TODO) return false;

// Check if task has no dependencies or all dependencies are done
const deps = this.getTaskDependencies(task, allTasks);
return (
deps.length === 0 ||
deps.every((dep) => dep.status === TASK_STATUS_enum.DONE)
);
});

executableTasks.forEach((task) => {
super._executeTask(task);
});
}
}

export default HierarchyExecutionStrategy;
14 changes: 14 additions & 0 deletions src/managers/executionStrategies/managerLLMExecutionStrategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import WorkflowExecutionStrategy from './workflowExecutionStrategy';

class ManagerLLMStrategy extends WorkflowExecutionStrategy {
constructor(useTeamStore) {
super(useTeamStore);
}

execute(_changedTasks, _allTasks) {
// TODO: Implement ManagerLLMStrategy.execute()
throw new Error('ManagerLLMStrategy.execute() not implemented');
}
}

export default ManagerLLMStrategy;
55 changes: 55 additions & 0 deletions src/managers/executionStrategies/sequentialExecutionStrategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fastq from 'fastq';
import { TASK_STATUS_enum } from '../../utils/enums';
import WorkflowExecutionStrategy from './workflowExecutionStrategy';

class SequentialExecutionStrategy extends WorkflowExecutionStrategy {
constructor(useTeamStore) {
super(useTeamStore);
this.taskQueue = fastq(async (task, callback) => {
await this._executeTask(task);
callback();
}, 1);
}

async execute(changedTasks, allTasks) {
// Implement the logic for the sequential execution strategy
// This method should handle the tasks in the order they are received
// and ensure that tasks are executed sequentially
for (const changedTask of changedTasks) {
switch (changedTask.status) {
case TASK_STATUS_enum.DOING:
this.taskQueue.push(changedTask);
break;
case TASK_STATUS_enum.REVISE:
{
// Find the index of the current revise task
const taskIndex = allTasks.findIndex(
(t) => t.id === changedTask.id
);

// Move all subsequent tasks to TODO
for (let i = taskIndex + 1; i < allTasks.length; i++) {
this._updateTaskStatus(allTasks[i].id, TASK_STATUS_enum.TODO);
}

this._updateTaskStatus(changedTask.id, TASK_STATUS_enum.DOING);
}
break;
case TASK_STATUS_enum.DONE:
{
const tasks = this.useTeamStore.getState().tasks;
const nextTask = tasks.find(
(t) => t.status === TASK_STATUS_enum.TODO
);
console.log({ nextTask });
if (nextTask) {
this._updateTaskStatus(nextTask.id, TASK_STATUS_enum.DOING);
}
}
break;
}
}
}
}

export default SequentialExecutionStrategy;
Loading

0 comments on commit 122cdc9

Please sign in to comment.