local-agent
Version:
A CLI agentic system for orchestrating tools and memory with per-folder scoping
436 lines • 17.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskManager = void 0;
/**
* @fileoverview
* Task State Manager for handling multi-step task logic.
* Replaces complex string matching with clear state transitions.
*/
const task_state_1 = require("./task-state");
/**
* Manages task state transitions and provides task context.
*/
class TaskManager {
state = task_state_1.TaskState.IDLE;
taskDescription = '';
completedSteps = [];
nextSteps = [];
toolsUsed = [];
stepCount = 0;
maxSteps = 50; // Increased limit for complex tasks
lastResponseWithoutTools = 0; // Track responses without tool usage
maxResponsesWithoutTools = 5; // Increased tolerance - projects need multiple steps
lastToolUsed = ''; // Track the last tool used
consecutiveNonToolResponses = 0; // More specific tracking
// Pattern registries - moved from hard-coded arrays to configurable properties
continuationPatterns = [
'created folder', 'created directory', 'made folder',
'next step', 'continue', 'now i will', 'now i need to',
'partially complete', 'still need', 'remaining',
'created basic', 'initial setup', 'first step',
'proceeding', 'moving on', 'next logical step',
'will now', 'let me', 'i will create', 'i will add',
'setting up', 'configuring', 'installing',
'let me now', 'i should', 'i need to'
];
completionPatterns = [
'task complete', 'finished', 'done', 'ready to use',
'fully functional', 'all files created', 'project is complete',
'successfully created', 'everything is set up',
'application is now ready', 'setup is complete',
'all necessary files', 'ready to run', 'task is now complete',
'have successfully', 'is now fully', 'completed the',
'you can now', 'everything needed', 'all set up'
];
stoppingPatterns = [
'that completes', 'this finishes', 'no further steps',
'that\'s all', 'nothing more', 'all done',
'no additional', 'task finished', 'work is complete'
];
continuationTools = ['create_directory', 'write_file', 'edit_file'];
/**
* Determines if a user prompt describes a multi-step task.
*/
isMultiStepTask(prompt) {
const multiStepKeywords = [
'create', 'build', 'setup', 'make', 'develop', 'implement', 'generate',
'react app', 'project', 'application', 'website', 'api', 'server',
'all files', 'complete', 'full', 'entire', 'whole'
];
const lowerPrompt = prompt.toLowerCase();
return multiStepKeywords.some(keyword => lowerPrompt.includes(keyword));
}
/**
* Starts a new task with the given description.
*/
startTask(description) {
if (this.state !== task_state_1.TaskState.IDLE) {
this.resetTask();
}
this.taskDescription = description;
this.state = task_state_1.TaskState.STARTING;
this.stepCount = 0;
// Analyze task to set initial expectations
this.analyzeTaskRequirements(description);
this.transitionToInProgress();
}
/**
* Analyzes task description to identify potential steps.
*/
analyzeTaskRequirements(description) {
const lowerDesc = description.toLowerCase();
// Example: if task mentions creating a project, we know we'll need several steps
if (lowerDesc.includes('create') &&
(lowerDesc.includes('project') || lowerDesc.includes('application'))) {
this.nextSteps = [
'Create project directory',
'Initialize configuration files',
'Set up basic structure',
'Install dependencies'
];
}
}
/**
* Transitions task to in-progress state.
*/
transitionToInProgress() {
this.state = task_state_1.TaskState.IN_PROGRESS;
}
/**
* Records that a tool was used in the current task.
*/
recordToolUse(toolName, result) {
this.toolsUsed.push(toolName);
if (this.state === task_state_1.TaskState.IN_PROGRESS) {
this.state = task_state_1.TaskState.AWAITING_TOOL;
}
}
/**
* Processes a response from the assistant and updates task state.
*/
processResponse(response, toolUsed) {
// Reset from awaiting tool state if we were in it
if (this.state === task_state_1.TaskState.AWAITING_TOOL) {
this.state = task_state_1.TaskState.IN_PROGRESS;
}
this.stepCount++;
// Track tool usage more carefully
if (toolUsed) {
this.lastToolUsed = toolUsed;
this.consecutiveNonToolResponses = 0;
this.lastResponseWithoutTools = 0;
}
else {
this.consecutiveNonToolResponses++;
this.lastResponseWithoutTools++;
}
// Update task progress based on response FIRST
this.updateTaskProgress(response, toolUsed);
// Only check for completion AFTER processing the response
// Be more conservative about marking tasks complete
if (this.shouldMarkComplete(response, toolUsed)) {
this.completeTask();
return;
}
// Safety limits - but be more lenient for complex tasks
if (this.stepCount >= this.maxSteps) {
console.log(`\n⚠️ Task has reached maximum step limit (${this.maxSteps}). Marking as complete.`);
this.completeTask();
return;
}
// Only stop if we have consecutive non-tool responses AND explicit completion signals
if (this.consecutiveNonToolResponses >= this.maxResponsesWithoutTools &&
this.hasExplicitCompletionSignal(response)) {
console.log(`\n⚠️ Task appears to be complete (${this.consecutiveNonToolResponses} responses without tools + completion signal).`);
this.completeTask();
return;
}
}
/**
* Updates task progress based on the assistant's response.
*/
updateTaskProgress(response, toolUsed) {
// Extract what was accomplished from the response
const accomplishment = this.extractAccomplishment(response);
if (accomplishment) {
this.completedSteps.push(accomplishment);
// Remove from next steps if it was there
const matchingNextStep = this.nextSteps.find(step => step.toLowerCase().includes(accomplishment.toLowerCase()));
if (matchingNextStep) {
this.nextSteps = this.nextSteps.filter(step => step !== matchingNextStep);
}
}
// Extract potential next steps from response
const potentialNextSteps = this.extractNextSteps(response);
this.nextSteps = [...this.nextSteps, ...potentialNextSteps];
}
/**
* Extracts accomplishments from the response text.
*/
extractAccomplishment(response) {
const lines = response.split('\n');
for (const line of lines) {
const lowerLine = line.toLowerCase();
if (lowerLine.includes('created') ||
lowerLine.includes('added') ||
lowerLine.includes('installed') ||
lowerLine.includes('configured')) {
return line;
}
}
return null;
}
/**
* Extracts next steps from the response text.
*/
extractNextSteps(response) {
const steps = [];
const lines = response.split('\n');
let nextStepSection = false;
for (const line of lines) {
const lowerLine = line.toLowerCase();
if (lowerLine.includes('next step') || lowerLine.includes('next, i will')) {
nextStepSection = true;
steps.push(line.replace(/next step:?|next, i will/i, '').trim());
continue;
}
if (nextStepSection && lowerLine.trim() && !lowerLine.includes('done')) {
steps.push(line.trim());
}
if (nextStepSection && (lowerLine.includes('done') || lowerLine.length === 0)) {
nextStepSection = false;
}
}
return steps;
}
/**
* Determines if task should be marked complete (more conservative).
*/
shouldMarkComplete(response, toolUsed) {
const lowerResponse = response.toLowerCase();
// Don't complete if we just used a tool - let it continue
if (toolUsed && this.continuationTools.some(tool => toolUsed.includes(tool))) {
return false;
}
// Only complete on very explicit completion signals
return this.hasExplicitCompletionSignal(response);
}
/**
* Checks for explicit completion signals.
*/
hasExplicitCompletionSignal(response) {
const lowerResponse = response.toLowerCase();
// Very explicit completion indicators
const strongCompletionPatterns = [
'task is complete', 'task completed', 'project is complete',
'all done', 'finished creating', 'everything is set up',
'application is ready', 'ready to use', 'setup complete',
'you can now run', 'you can now use', 'all files have been created'
];
return strongCompletionPatterns.some(pattern => lowerResponse.includes(pattern));
}
/**
* Original completion check (now used for reference but not for auto-completion).
*/
isTaskComplete(response) {
const lowerResponse = response.toLowerCase();
// Check for explicit completion patterns
if (this.completionPatterns.some(pattern => lowerResponse.includes(pattern))) {
return true;
}
// Check for stopping patterns
if (this.stoppingPatterns.some(pattern => lowerResponse.includes(pattern))) {
return true;
}
// For project tasks, be more careful about completion
if (this.isProjectCreationTask()) {
// Only complete if we have clear end-state indicators
if (lowerResponse.includes('npm start') ||
lowerResponse.includes('npm run') ||
lowerResponse.includes('you can now')) {
return true;
}
}
return false;
}
/**
* Checks if this is a project creation task.
*/
isProjectCreationTask() {
const lowerDesc = this.taskDescription.toLowerCase();
return (lowerDesc.includes('create') || lowerDesc.includes('build')) &&
(lowerDesc.includes('project') || lowerDesc.includes('app') ||
lowerDesc.includes('application') || lowerDesc.includes('website'));
}
/**
* Checks if response has continuation indicators.
*/
hasContinuationIndicators(response) {
const lowerResponse = response.toLowerCase();
return this.continuationPatterns.some(pattern => lowerResponse.includes(pattern));
}
/**
* Marks the task as completed.
*/
completeTask() {
this.state = task_state_1.TaskState.COMPLETED;
}
/**
* Resets the task to idle state.
*/
resetTask() {
this.state = task_state_1.TaskState.IDLE;
this.taskDescription = '';
this.completedSteps = [];
this.nextSteps = [];
this.toolsUsed = [];
this.stepCount = 0;
this.lastResponseWithoutTools = 0;
this.lastToolUsed = '';
this.consecutiveNonToolResponses = 0;
}
/**
* Gets task completion status with details.
*/
getCompletionStatus() {
if (this.state === task_state_1.TaskState.COMPLETED) {
return {
isComplete: true,
reason: 'Task marked as complete',
stepCount: this.stepCount
};
}
if (this.stepCount >= this.maxSteps) {
return {
isComplete: true,
reason: `Reached maximum steps (${this.maxSteps})`,
stepCount: this.stepCount
};
}
if (this.lastResponseWithoutTools >= this.maxResponsesWithoutTools) {
return {
isComplete: true,
reason: `No tool usage for ${this.maxResponsesWithoutTools} responses`,
stepCount: this.stepCount
};
}
return { isComplete: false, stepCount: this.stepCount };
}
/**
* Manually marks the task as complete.
*/
forceComplete(reason) {
this.state = task_state_1.TaskState.COMPLETED;
if (reason) {
console.log(`\n✅ Task completed: ${reason}`);
}
}
/**
* Determines if the task should continue automatically.
*/
shouldContinueAutomatically(response, toolUsed) {
if (this.state !== task_state_1.TaskState.IN_PROGRESS &&
this.state !== task_state_1.TaskState.AWAITING_TOOL) {
return false;
}
// Don't continue if we've hit hard safety limits
if (this.stepCount >= this.maxSteps) {
return false;
}
const lowerResponse = response.toLowerCase();
// Don't continue if there are very explicit stopping indicators
if (this.hasExplicitCompletionSignal(response)) {
return false;
}
// AGGRESSIVE CONTINUATION for project tasks
if (this.isProjectCreationTask()) {
// Continue if we just used a tool
if (toolUsed && this.continuationTools.some(tool => toolUsed.includes(tool))) {
return true;
}
// Continue if we're in early stages (< 10 steps for complex projects)
if (this.stepCount < 10 && this.completedSteps.length > 0) {
// Continue if response suggests more work needed
if (lowerResponse.includes('now i') ||
lowerResponse.includes('next i') ||
lowerResponse.includes('i will') ||
lowerResponse.includes('let me') ||
lowerResponse.includes('i need to') ||
lowerResponse.includes('i should')) {
return true;
}
// Continue if just created a directory but no files yet
if (this.lastToolUsed === 'create_directory' &&
!this.toolsUsed.includes('write_file')) {
return true;
}
// Continue if created some files but probably need more
const fileToolsUsed = this.toolsUsed.filter(tool => tool.includes('write_file') || tool.includes('create')).length;
if (fileToolsUsed > 0 && fileToolsUsed < 5) {
return true;
}
}
}
// Standard continuation logic
if (toolUsed && this.continuationTools.some(tool => toolUsed.includes(tool))) {
return true;
}
// Check for continuation indicators
if (this.continuationPatterns.some(pattern => lowerResponse.includes(pattern))) {
return true;
}
// Continue if response indicates work in progress
if (lowerResponse.includes('creating') ||
lowerResponse.includes('setting up') ||
lowerResponse.includes('configuring') ||
lowerResponse.includes('installing')) {
return true;
}
// Don't continue if we have many responses without tools AND no indication of more work
if (this.consecutiveNonToolResponses >= 3 &&
!this.hasContinuationIndicators(response)) {
return false;
}
return false;
}
/**
* Checks if a task is currently active.
*/
isTaskActive() {
return this.state === task_state_1.TaskState.IN_PROGRESS ||
this.state === task_state_1.TaskState.AWAITING_TOOL ||
this.state === task_state_1.TaskState.STARTING;
}
/**
* Gets the current task context.
*/
getTaskContext() {
return {
isMultiStep: this.isTaskActive(),
taskDescription: this.taskDescription,
completedSteps: this.completedSteps,
nextSteps: this.nextSteps,
isComplete: this.state === task_state_1.TaskState.COMPLETED
};
}
/**
* Configuration methods for adding patterns and tools.
*/
addContinuationPattern(pattern) {
if (!this.continuationPatterns.includes(pattern)) {
this.continuationPatterns.push(pattern);
}
}
addCompletionPattern(pattern) {
if (!this.completionPatterns.includes(pattern)) {
this.completionPatterns.push(pattern);
}
}
addContinuationTool(tool) {
if (!this.continuationTools.includes(tool)) {
this.continuationTools.push(tool);
}
}
}
exports.TaskManager = TaskManager;
//# sourceMappingURL=task-manager.js.map