UNPKG

taskforce-aiagent

Version:

TaskForce is a modular, open-source, production-ready TypeScript agent framework for orchestrating AI agents, LLM-powered autonomous agents, task pipelines, dynamic toolchains, RAG workflows and memory/retrieval systems.

802 lines (797 loc) โ€ข 40.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TaskForce = void 0; const fs_1 = __importDefault(require("fs")); const chalk_1 = __importDefault(require("chalk")); const task_js_1 = require("../tasks/task.js"); const agent_helper_js_1 = require("../agents/agent.helper.js"); const smartManagerAgent_js_1 = require("../agents/smartManagerAgent.js"); const agentRegistry_js_1 = require("../agents/agentRegistry.js"); const log_helper_js_1 = require("../helpers/log.helper.js"); const helper_js_1 = require("../helpers/helper.js"); const memoryFactory_js_1 = require("../memory/memoryFactory.js"); const enum_js_1 = require("../configs/enum.js"); const readline_sync_1 = __importDefault(require("readline-sync")); const openai_1 = require("openai"); const events_1 = require("events"); const path_1 = __importDefault(require("path")); const dotenv_1 = __importDefault(require("dotenv")); const delegation_guard_js_1 = require("../agents/delegation.guard.js"); const summarize_helper_js_1 = require("../helpers/summarize.helper.js"); dotenv_1.default.config(); const openai = new openai_1.OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const emitting = process.env.EMITTING; class TaskForce extends events_1.EventEmitter { constructor(config) { super(); this.globalReplanAttemptCount = 0; this.maxGlobalReplanLimit = 2; this.agents = config.agents; this.tasks = config.tasks; this.verbose = config.verbose ?? false; this.memory = config.memory ?? false; this.executionMode = config.executionMode ?? enum_js_1.ExecutionMode.Sequential; this.allowParallelProcesses = config.allowParallelProcesses ?? false; this.maxRetryPerTask = config.maxRetryPerTask ?? 5; this.maxDelegatePerTask = config.maxDelegatePerTask ?? 3; this.enableReplanning = config.enableReplanning ?? true; this.enableAIPlanning = config.enableAIPlanning ?? true; this.retriever = config.retriever; // Validate task dependencies const allTaskIds = new Set(this.tasks.map((t) => t.id)); for (const task of this.tasks) { if (task.inputFromTask && !allTaskIds.has(task.inputFromTask)) { throw new Error(`[TaskForce] Task '${task.name}' references missing inputFromTask ID '${task.inputFromTask}'`); } } this.agents.forEach((agent) => { agent.setVerbose(this.verbose); agentRegistry_js_1.AgentRegistry.register(agent); if (agent.toolExecutor?.setTaskForce) { agent.toolExecutor.setTaskForce(this); } }); // Attach memory to agents if global memory is enabled if (this.memory) { for (const agent of this.agents) { if (!agent.memoryScope || agent.memoryScope === enum_js_1.MemoryScope.None) continue; // Attach memory only if not externally set if (!agent.memoryProvider && !agent.memoryInitialized) { if (agent.memoryScope === enum_js_1.MemoryScope.Short) { agent.memoryProvider = (0, memoryFactory_js_1.MemoryProvider)(agent.name, agent.memoryScope); } else if (agent.memoryScope === enum_js_1.MemoryScope.Long) { const mode = agent.memoryMode ?? enum_js_1.MemoryMode.Same; agent.memoryProvider = (0, memoryFactory_js_1.MemoryProvider)(agent.name, agent.memoryScope, mode); } } } } if (this.executionMode === enum_js_1.ExecutionMode.Hierarchical || this.executionMode === enum_js_1.ExecutionMode.AiDriven) { this.managerAgent = new smartManagerAgent_js_1.SmartManagerAgent({ name: "Smart Manager", role: "Autonomous Task Coordinator", goal: "Dynamically plan, assign, and evaluate tasks based on input context.", backstory: "You are responsible for delegating and validating the entire task pipeline.", model: config.managerModel || process.env.DEFAULT_MANAGER_MODEL, allowDelegation: false, taskForce: this, }); this.managerAgent.setVerbose(this.verbose); } } emitStep(payload) { this.emit("step", { ...payload, timestamp: Date.now(), }); } async run(inputs) { (0, log_helper_js_1.initializeTaskforceLogFile)(); if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿš€ [Task Force] Running with ${this.agents .map((a) => a.name) .join(", ")} agents and ${this.tasks .map((t) => t.name) .join(", ")} tasks`, chalk_1.default.yellow); } let finalContext; let executedTaskIds = []; if (this.executionMode === enum_js_1.ExecutionMode.Hierarchical) { if (!this.managerAgent) { throw new Error("Manager agent not set for hierarchical execution."); } if (this.tasks.length === 1 && this.enableAIPlanning) { const mainTask = this.tasks[0]; const subtasks = await this.decomposeMainTask(mainTask); if (this.verbose) { (0, log_helper_js_1.TFLog)(`[TaskForce] Decomposed main task '${mainTask.name}' into ${subtasks.length} subtasks.`, chalk_1.default.green); } this.tasks = subtasks; } const { taskPlan, context } = await this.createTaskPlan(inputs); executedTaskIds = taskPlan.map((t) => t.id); if (this.allowParallelProcesses) { finalContext = await this.runParallelHierarchicalFiltered(taskPlan, context); } else { finalContext = await this.runHierarchicalFiltered(taskPlan, context); } finalContext = await this.reviewFinalOutput(finalContext, this.allowParallelProcesses); } else if (this.executionMode === enum_js_1.ExecutionMode.AiDriven) { const plan = await (0, agent_helper_js_1.generateDynamicPlan)({ inputs, agents: this.agents, model: this.managerAgent?.model ?? enum_js_1.SupportedModel.GPT_4O_MINI, verbose: this.verbose, }); this.tasks = plan.tasks; this.agents = plan.agents; switch (plan.executionMode) { case "parallel": finalContext = await this.runParallelHierarchicalFiltered(this.tasks, inputs); break; case "hierarchical": finalContext = await this.runHierarchicalFiltered(this.tasks, inputs); break; default: finalContext = await this.runSequential(inputs); } } else { executedTaskIds = this.tasks.map((t) => t.id); finalContext = await this.runSequential(inputs); } finalContext = (0, helper_js_1.cleanFinalContext)(finalContext); return { result: finalContext, executedTaskIds, }; } async reviewFinalOutput(finalContext, isParallel) { if (!this.enableReplanning) { return finalContext; } for (const [taskId, output] of Object.entries(finalContext)) { if (typeof output === "string") { if (output.includes("please provide") || output.includes("DELEGATE(")) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ” System Check rejected final output due to incomplete or delegated result in task '${taskId}'`, chalk_1.default.cyan); } finalContext.__replanReason__ = `System Check: Unresolved output in task '${taskId}'`; return this.replanRun(finalContext, isParallel); } try { const parsed = JSON.parse(output); if (parsed?.__delegate__) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ” System Check found unresolved __delegate__ in task '${taskId}'`, chalk_1.default.cyan); } finalContext.__replanReason__ = `System Check: __delegate__ found in task '${taskId}'`; return this.replanRun(finalContext, isParallel); } } catch { // ignore } } } // General review by the Manager Agent const review = await this.managerAgent.reviewFinalOutput(finalContext); if (review.action === "replan") { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ” [Manager Agent] rejected final output: ${review.reason}`, chalk_1.default.magenta); } finalContext.__replanReason__ = `Manager Agent: ${review.reason}`; return this.replanRun(finalContext, isParallel); } if (review.action === "accept" && this.verbose) { (0, log_helper_js_1.TFLog)(`โœ… [Manager Agent] Final output accepted.`, chalk_1.default.greenBright); } return finalContext; } computeReplanTasks(failedTasks, taskList) { const replanTasks = new Set(failedTasks); const taskMap = new Map(taskList.map((t) => [t.id, t])); let added = true; while (added) { added = false; for (const task of taskList) { if (task.inputFromTask && replanTasks.has(task.inputFromTask) && !replanTasks.has(task.id)) { replanTasks.add(task.id); added = true; } } } return replanTasks; } async replanRun(inputs, isParallel) { if ((inputs.__replanCount__ || 0) >= this.maxGlobalReplanLimit || this.globalReplanAttemptCount >= this.maxGlobalReplanLimit) { (0, log_helper_js_1.TFLog)(`๐Ÿšซ Replan limit (${this.maxGlobalReplanLimit}) reached. Aborting.`, chalk_1.default.red); return inputs; } this.globalReplanAttemptCount++; inputs.__replanCount__ = (inputs.__replanCount__ || 0) + 1; if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ” Re-running full task pipeline [attempt ${inputs.__replanCount__}] due to: ${inputs.__replanReason__}`, chalk_1.default.cyan); } const { taskPlan, context } = await this.createTaskPlan(inputs); const failedTasks = Object.entries(inputs) .filter(([_, output]) => { if (typeof output !== "string") return false; if (output.trim() === "") return true; if (output.includes("please provide") || output.includes("DELEGATE(")) { return true; } try { const parsed = JSON.parse(output); return typeof parsed === "object" && parsed?.__delegate__; } catch { return false; } }) .map(([taskId]) => taskId); const replanSet = this.computeReplanTasks(failedTasks, taskPlan); const filteredTaskPlan = taskPlan.filter((t) => replanSet.has(t.id)); const filteredContext = { ...context }; // Save the outputs of successful tasks for (const [key, value] of Object.entries(inputs)) { if (!replanSet.has(key)) { filteredContext[key] = value; if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ” [Replan] Keeping output for task '${key}'`, chalk_1.default.cyan); } } } return isParallel ? await this.runParallelHierarchicalFiltered(filteredTaskPlan, filteredContext) : await this.runHierarchicalFiltered(filteredTaskPlan, filteredContext); } async runSequential(inputs) { let result = ""; let taskIndex = 0; let results = {}; while (taskIndex < this.tasks.length) { const task = this.tasks[taskIndex]; const agent = this.agents.find((a) => a.name === task.agent); if (!agent) { taskIndex++; continue; } if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ“ [Task] Starting task '${task.name}' assigned to '${agent.name}'`, chalk_1.default.yellow); } const output = await this.runTask(agent, task, inputs, task.description, task.outputFormat, result, undefined, undefined, enum_js_1.ExecutionMode.Sequential, this.retriever); if (this.verbose) { (0, log_helper_js_1.TFLog)(`โœ… [Task] Completed '${task.name}'`, chalk_1.default.yellow); (0, log_helper_js_1.TFLog)(`๐Ÿงพ Output:\n${output}\n`, chalk_1.default.white); } const delegation = await (0, agent_helper_js_1.delegateTo)(agent, output, inputs); if (delegation) { const delegateAgent = this.agents.find((a) => a.name === delegation.delegateTo); if (!delegateAgent) throw new Error(`โŒ Agent '${delegation.delegateTo}' not found`); if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿงญ ${agent.name} delegates to ${delegateAgent.name}`, chalk_1.default.red); } const delegateResult = await delegateAgent.runTask(inputs, delegation.task, undefined, output, undefined, undefined, undefined, undefined, this.retriever); result = delegateResult; // restart chain from the delegated agent taskIndex = this.tasks.findIndex((t) => t.agent === agent.name) + 1; continue; } results[task.id] = output; result = output; taskIndex++; } return results; } async runHierarchicalFiltered(taskPlan, context) { if (!this.managerAgent) { throw new Error("Hierarchical mode requires a managerAgent"); } for (const task of taskPlan) { let assignedAgent; if (typeof task.agent === "string") { const agent = this.agents.find((a) => a.name === task.agent); if (!agent) throw new Error(`Agent '${task.agent}' not found`); assignedAgent = agent; } else if (task.agent) { assignedAgent = task.agent; } else { assignedAgent = await this.managerAgent.assignAgent(task, this.agents); if (!assignedAgent) { throw new Error(`Failed to assign agent for task '${task.name}'`); } } if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿงฉ [Task] Running '${task.name}' with '${assignedAgent.name}'`, chalk_1.default.yellow); } let inputFromPreviousTask = this.getChainedInput(task, context); let finalOutput = await this.runTask(assignedAgent, task, context, task.description, task.outputFormat, inputFromPreviousTask, undefined, undefined, undefined, this.retriever); if (this.verbose) { (0, log_helper_js_1.TFLog)(`โœ… [Task] Completed '${task.name}'`, chalk_1.default.yellow); (0, log_helper_js_1.TFLog)(`๐Ÿงพ Output:\n${finalOutput}\n`, chalk_1.default.white); } finalOutput = await this.evaluateTaskLoop(task, assignedAgent, context, finalOutput); if (finalOutput.includes("DELEGATE(")) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`โš ๏ธ [Task '${task.name}'] unresolved delegation output remains`, chalk_1.default.red); } } if (!finalOutput.includes("DELEGATE(")) { context[task.id] = finalOutput; } } return context; } async runParallelHierarchicalFiltered(taskPlan, context) { // taskId โ†’ list of resolve functions (multiple tasks can wait for same input) const contextReady = new Map(); const taskPromises = taskPlan.map((task) => { return new Promise(async (resolve) => { const taskKey = task.inputFromTask; if (taskKey && !context[taskKey]) { await new Promise((waitForInput) => { if (!contextReady.has(taskKey)) { contextReady.set(taskKey, []); } contextReady.get(taskKey).push(waitForInput); }); } const assignedAgent = this.agents.find((a) => a.name === task.agent); if (!assignedAgent) throw new Error(`Agent '${task.agent}' not found`); if (this.verbose) { (0, log_helper_js_1.TFLog)(`โšก [Parallel Task] Running '${task.name}' with '${assignedAgent.name}'`, chalk_1.default.cyan); } const input = this.getChainedInput(task, context); const output = await this.runTask(assignedAgent, task, context, task.description, task.outputFormat, input, undefined, undefined, undefined, this.retriever); const finalOutput = await this.evaluateTaskLoop(task, assignedAgent, context, output); if (finalOutput.includes("DELEGATE(")) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`โš ๏ธ [Task '${task.name}'] unresolved delegation output remains`, chalk_1.default.red); } } if (!finalOutput.includes("DELEGATE(")) { context[task.id] = finalOutput; } if (contextReady.has(task.id)) { for (const resolveFn of contextReady.get(task.id)) { resolveFn(); } contextReady.delete(task.id); } resolve({ key: task.id, value: finalOutput }); }); }); const results = await Promise.all(taskPromises); const finalContext = { ...context }; for (const { key, value } of results) { finalContext[key] = value; } return finalContext; } async handleDelegationAndToolOutput(task, output, agent, context) { // Handle DELEGATE(...) const delegateMatch = output.match(/^DELEGATE\((.+?),\s*"(.*)"\)$/); if (delegateMatch) { const delegateAgentName = delegateMatch[1].trim(); const delegateTaskDesc = delegateMatch[2].trim(); const delegateAgent = this.agents.find((a) => a.name === delegateAgentName); if (!delegateAgent) throw new Error(`Agent '${delegateAgentName}' not found`); if (delegateAgent.name === agent.name) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`โš ๏ธ Skipping self-delegation: ${agent.name}`, chalk_1.default.red); } return output; } if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ“ค DELEGATE() triggered in task '${task.name}': ${agent.name} โ†’ ${delegateAgent.name}`, chalk_1.default.magenta); } if (emitting === "true") { this.emitStep({ agent: agent.name, task: task.name, action: "delegated", to: delegateAgent.name, reason: delegateTaskDesc, }); } const delegateResult = await this.runTask(delegateAgent, task, context, delegateTaskDesc, task.outputFormat, output, undefined, undefined, undefined, this.retriever); // context[task.id] = delegateResult; (0, log_helper_js_1.logTaskChaining)(agent.name, delegateAgent.name, delegateResult); return delegateResult; } // Handle TOOL(...) const toolMatch = output.match(/^TOOL\((.+?),\s*(\{.*\})\)$/); if (toolMatch) { const toolName = toolMatch[1].trim(); const toolInputRaw = toolMatch[2]; const toolExecutor = this.managerAgent?.toolExecutor; const tools = toolExecutor?.getTools(); const tool = tools?.find((t) => t.name === toolName); if (!tool) throw new Error(`Tool '${toolName}' not found`); const toolInput = JSON.parse(toolInputRaw); if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ”ง Running tool '${toolName}' for task '${task.name}' by agent '${agent.name}' with input ${toolInputRaw}`, chalk_1.default.cyan); } const toolResult = await tool.handler(toolInput); let summarized = toolResult; if (typeof toolResult === "string" && toolResult.length > 1000) { try { summarized = await (0, summarize_helper_js_1.generateMemorySummary)(toolResult); if (this.verbose) { (0, log_helper_js_1.TFLog)("๐Ÿ“˜ Tool result summarized for memory efficiency.", chalk_1.default.gray); } } catch (err) { (0, log_helper_js_1.TFLog)(`โš ๏ธ Failed to summarize tool output: ${err}`, chalk_1.default.red); summarized = toolResult; // fallback } } const rerunResult = await this.runTask(agent, task, context, task.description, task.outputFormat, toolResult, undefined, "Output was tool result", undefined, this.retriever); context[task.id] = rerunResult; return rerunResult; } return output; } topologicalSortTasks(tasks) { const sorted = []; const visited = new Set(); const taskMap = new Map(); for (const task of tasks) { taskMap.set(task.id, task); } function visit(task) { if (visited.has(task.id)) return; if (task.inputFromTask && taskMap.has(task.inputFromTask)) { visit(taskMap.get(task.inputFromTask)); } visited.add(task.id); sorted.push(task); } for (const task of tasks) { visit(task); } return sorted; } async createTaskPlan(inputData //executeInParallel: boolean ) { const context = { ...inputData }; let taskPlan = await this.managerAgent.planTasks(this.tasks, context); taskPlan = this.topologicalSortTasks(taskPlan); // Reset internal replan flags for (const task of taskPlan) { if ("__replanReasonUsed" in task) { delete task.__replanReasonUsed; } } return { taskPlan, context }; } async decomposeMainTask(mainTask) { if (!this.managerAgent) { throw new Error("ManagerAgent not initialized"); } const subtasksData = await this.managerAgent.decomposeTask(mainTask, this.agents, this.verbose); const subtasks = subtasksData.map((st) => { const taskData = { id: st.id, name: st.name, description: st.description, outputFormat: "text", agent: st.agent, }; return new task_js_1.Task(taskData); }); return subtasks; } getChainedInput(task, context) { const sourceTaskId = task.inputFromTask; if (!sourceTaskId) { return ""; } const sourceTaskOutput = context[sourceTaskId]; if (sourceTaskOutput === undefined) return ""; const sourceTask = this.tasks.find((t) => t.id === sourceTaskId); (0, log_helper_js_1.logTaskChaining)(sourceTask?.name || sourceTaskId, task.name, sourceTaskOutput); const rawInput = task.inputMapper ? task.inputMapper(sourceTaskOutput) : sourceTaskOutput; return typeof rawInput === "string" && rawInput.length > 3000 ? rawInput.slice(0, 3000) + "\n\n[...truncated]" : rawInput; } async evaluateTaskLoop(task, agent, context, initialOutput) { const guardCheck = (0, delegation_guard_js_1.checkDelegationValidity)(agent, task); if (!guardCheck.canDelegate) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿšซ Delegation blocked for '${task.name}': ${guardCheck.reason}`, chalk_1.default.red); } return `โš ๏ธ Delegation loop or hop limit reached. Accepting output as-is.\n\n${initialOutput}`; } (0, delegation_guard_js_1.updateDelegationChain)(task, agent); let finalOutput = initialOutput; let needsEvaluation = true; let retryCount = 0; let retryReasons = []; while (needsEvaluation) { const review = await this.managerAgent.evaluateTaskOutput(task, finalOutput, this.agents); // ๐Ÿšฉ Retry loop detected (same agent) if (review.action === "retry" && review.retryWith?.name === task.agent) { retryCount++; if (retryCount >= this.maxRetryPerTask) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿšซ Max retry count (${this.maxRetryPerTask}) reached for task '${task.name}' due to self-retry. Proceeding with last output.`, chalk_1.default.red); } needsEvaluation = false; break; } if (this.verbose) { (0, log_helper_js_1.TFLog)(`โš ๏ธ [Retry Loop] '${task.name}' retrying on same agent '${agent.name}'. Count: ${retryCount}`, chalk_1.default.red); } if (review.reason) { retryReasons.push(review.reason); } finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, review.reason, undefined, undefined, this.retriever); continue; } if (review.action === "delegate" && (review.delegateTo.name === task.agent || finalOutput === `DELEGATE(${review.delegateTo.name}, "${task.description}")`)) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`โš ๏ธ [Delegation Loop Detected] '${task.name}' tried to delegate back to itself. Forcing accept.`, chalk_1.default.red); } needsEvaluation = false; break; } if (this.verbose) { (0, log_helper_js_1.TFLog)(`[Manager Agent] Evaluation decision for task '${task.name}' by agent '${agent.name}': ${JSON.stringify(review, (0, helper_js_1.getSafeReplacer)())}`, chalk_1.default.blue); } if (review.action === "accept") { const delegationScore = (0, delegation_guard_js_1.checkDelegationScore)(finalOutput); if (delegationScore.isWeak) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿ“Š [DelegationScore Metrics] Task '${task.name}' by '${agent.name}' scored ${delegationScore.score}/10 โ€” Reason: ${delegationScore.reason}`, chalk_1.default.red); } if (emitting === "true") { this.emitStep({ agent: agent.name, task: task.name, action: "delegationScore", score: delegationScore.score, reason: delegationScore.reason, }); } retryCount++; if (retryCount >= this.maxRetryPerTask) { (0, log_helper_js_1.TFLog)(`๐Ÿšซ Max retry count reached despite weak delegation score.`, chalk_1.default.red); break; } finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, delegationScore.reason, undefined, undefined, this.retriever); continue; } if (agent.memoryScope !== enum_js_1.MemoryScope.None && agent.memoryProvider) { await agent.memoryProvider.storeMemory({ taskId: task.description, input: JSON.stringify(context), output: finalOutput, metadata: { agent: agent.name }, }); if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿง  [MemoryStore] Saved memory for ${task.name} by ${agent.name}`, chalk_1.default.gray); } } if (this.verbose && retryReasons.length > 0) { (0, log_helper_js_1.TFLog)(`๐Ÿ” [Retry Trace] Task '${task.name}' accepted after ${retryCount} retries. Reasons: ${retryReasons.join(" | ")}`, chalk_1.default.gray); } // delegasyon & tool handling let delegateDepth = 0; let delegated = true; while (delegated && delegateDepth < this.maxDelegatePerTask) { const processed = await this.handleDelegationAndToolOutput(task, finalOutput, agent, context); if (processed === finalOutput || processed.includes("DELEGATE(")) { if (this.verbose && processed.includes("DELEGATE(")) { (0, log_helper_js_1.TFLog)(`โš ๏ธ Unresolved DELEGATE() remains after delegation depth check`, chalk_1.default.red); } delegated = false; } else { finalOutput = processed; delegateDepth++; } } if (delegateDepth >= this.maxDelegatePerTask) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿšซ Max delegation depth for '${task.name}'. Forcing execution by '${agent.name}'.`, chalk_1.default.red); } agent.setDelegationDisallowed(true); finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, undefined, "Delegation blocked. Forced to execute.", undefined, this.retriever); } needsEvaluation = false; if (emitting === "true") { this.emitStep({ agent: agent.name, task: task.name, action: "completed", output: finalOutput, }); } } else if (review.action === "retry") { retryCount++; if (retryCount >= this.maxRetryPerTask) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿšซ Max retry count (${this.maxRetryPerTask}) reached for task '${task.name}'. Proceeding with last output.`, chalk_1.default.red); } needsEvaluation = false; break; } const retryAgent = review.retryWith ? review.retryWith : await this.managerAgent.assignAgent(task, this.agents); finalOutput = await this.runTask(retryAgent, task, context, task.description, task.outputFormat, finalOutput, review.reason, undefined, undefined, this.retriever); } else if (review.action === "delegate") { finalOutput = await this.runTask(review.delegateTo, task, context, task.description, task.outputFormat, finalOutput, undefined, review.reason, undefined, this.retriever); if (finalOutput.includes("DELEGATE(")) { if (this.verbose) { (0, log_helper_js_1.TFLog)(`๐Ÿšซ [DELEGATION Validation] Output still contains unresolved DELEGATE(...) for task '${task.name}'`, chalk_1.default.red); } // If can retry retryCount++; if (retryCount < this.maxRetryPerTask) { finalOutput = await this.runTask(agent, task, context, task.description, task.outputFormat, finalOutput, "Unresolved DELEGATE after handling attempt", undefined, undefined, this.retriever); continue; // evaluateTaskLoop goto top } // If can't retry, fallback to original output finalOutput = `โš ๏ธ Unresolved DELEGATE() in final output. Original content:\n${finalOutput}`; } } } return finalOutput; } async runTask(agent, task, inputs, taskDescription, outputFormat, inputFromPrevious, retryReason, delegateReason, executionMode, retriever) { if (emitting === "true") { this.emitStep({ agent: agent.name, task: task.name, action: "start", }); } let replanReason = inputs.__replanReason__ && !task.__replanReasonUsed ? inputs.__replanReason__ : undefined; const resolvedDescription = (0, helper_js_1.interpolateTemplate)(taskDescription, inputs); if (this.verbose && agent.trained) { (0, log_helper_js_1.TFLog)(`๐Ÿ“˜ [Training Enriched] Agent "${agent.name}" used training suggestions during this run.`, chalk_1.default.cyan); } const output = await agent.runTask(inputs, resolvedDescription, outputFormat, inputFromPrevious, retryReason, delegateReason, replanReason, executionMode, retriever); if (replanReason) { task.__replanReasonUsed = true; } if (replanReason && this.verbose) { (0, log_helper_js_1.TFLog)(`[Manager Agent]๐Ÿ” Replan Reason: ${replanReason}`, chalk_1.default.magenta); } return output; } async train(iterations, globalInputs = {}) { for (const agent of this.agents) { const task = this.tasks.find((t) => t.agent === agent.name); if (!task) { console.warn(`โš ๏ธ No task found for agent ${agent.name}. Skipping training.`); continue; } const trainingsDir = path_1.default.join(process.cwd(), "trainings"); fs_1.default.mkdirSync(trainingsDir, { recursive: true }); const outPath = path_1.default.join(trainingsDir, `${agent.name.toLowerCase().replace(/\s/g, "_")}_trained.json`); let previousData = { suggestions: [], quality: 0, final_summary: "", }; if (fs_1.default.existsSync(outPath)) { const raw = fs_1.default.readFileSync(outPath, "utf-8"); previousData = JSON.parse(raw); } const examples = []; for (let i = 0; i < iterations; i++) { console.log(`\n๐Ÿ” [${agent.name}] Iteration ${i + 1}/${iterations}`); const initialOutput = await agent.runTask(globalInputs, task.description, task.outputFormat, "", undefined, undefined, undefined, enum_js_1.ExecutionMode.Sequential, this.retriever, true); console.log(`\n๐Ÿ“ค Initial Output:\n${initialOutput}\n`); const feedback = readline_sync_1.default .question(`[${agent.name}] โœ๏ธ Your feedback: `) .trim(); if (!feedback) { console.log("โš ๏ธ No feedback provided. Skipping iteration."); continue; } const improvedOutput = await agent.runTask(globalInputs, task.description, task.outputFormat, initialOutput + `\n\n[Feedback from user]: ${feedback}`, undefined, undefined, undefined, enum_js_1.ExecutionMode.Sequential, this.retriever, true); console.log(`\n๐Ÿ“ฅ Improved Output:\n${improvedOutput}\n`); examples.push({ initialOutput, humanFeedback: feedback, improvedOutput, }); } const newSuggestions = examples.flatMap((e) => e.humanFeedback ? [`${e.humanFeedback}`] : []); const mergedSuggestions = [ ...(previousData.suggestions || []), ...newSuggestions, ]; const llmSummaryPrompt = `You are a training compression engine. You are given multiple human feedback comments that have been used to improve an AI agent's outputs. Your job is to extract the **underlying improvement instructions** from the feedback list and compress them into a clear and concrete list of what the agent should do better next time. Respond in the following JSON format: { "final_summary": "Do this, avoid that, include this... style directives that can be reused in future tasks.", "quality": 0-10 } Only include distilled guidance in the final_summary. Do NOT write commentary or explanations. Strip redundant prefixes like 'Improve based on:' or 'You should...' The goal is to turn the list into reusable prompt directives. Use simple English. Keep the tone neutral and instructive. Here is the feedback list: ${mergedSuggestions .map((s) => "- " + s.replace(/^Improve based on:\s*/i, "")) .join("\n")} `; const completion = await openai.chat.completions.create({ model: enum_js_1.SupportedModel.GPT_4O_MINI, messages: [{ role: "user", content: llmSummaryPrompt }], temperature: 0.7, }); let final_summary = "N/A"; let quality = 8; try { const parsed = JSON.parse(completion.choices[0].message.content || "{}"); final_summary = parsed.final_summary || "No summary returned."; quality = parsed.quality || 8; } catch { final_summary = "Failed to parse LLM summary."; } const result = { suggestions: mergedSuggestions, final_summary, quality, }; fs_1.default.writeFileSync(outPath, JSON.stringify(result, null, 2), "utf-8"); console.log(`\nโœ… Training complete for ${agent.name}. Saved to ${outPath}`); } } getTaskById(id) { return this.tasks.find((t) => t.id === id); } listTaskNames() { return this.tasks.map((t) => t.name); } getAgentByName(name) { return this.agents.find((a) => a.name === name); } listAgentNames() { return this.agents.map((a) => a.name); } } exports.TaskForce = TaskForce;